Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sharing entrypoint change #1061

Merged
merged 18 commits into from
Aug 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,12 @@ export class StliteKernelWithToast {
error: "Failed to install",
});
}

public reboot(...args: Parameters<StliteKernel["reboot"]>) {
return stliteStyledPromiseToast<void>(this.kernel.reboot(...args), {
pending: "Rebooting",
success: "Successfully rebooted",
error: "Failed to reboot",
});
}
}
11 changes: 11 additions & 0 deletions packages/kernel/py/stlite-lib/stlite_lib/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Callable, Final, cast

import pyodide.ffi
from streamlit import source_util
from streamlit.proto.BackMsg_pb2 import BackMsg
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
from streamlit.runtime import Runtime, RuntimeConfig, SessionClient
Expand Down Expand Up @@ -210,7 +211,17 @@ def callback(future: asyncio.Future):

def stop(self):
self._websocket_handler.on_close()

# `Runtime.stop()` doesn't stop the running tasks immediately,
# but we don't need to wait for them to finish for the current use case,
# e.g. booting up a new server and replacing the old one.
self._runtime.stop()
Runtime._instance = None

# `source_util.get_pages()`, which is used from `PagesStrategyV1.get_initial_active_script`
# to resolve the pages info, caches the pages in the module-level variable `source_util._cached_pages`.
# We need to invalidate this cache to avoid using the old pages info when booting up a new server.
source_util.invalidate_pages_cache()


class WebSocketHandler(SessionClient):
Expand Down
14 changes: 14 additions & 0 deletions packages/kernel/src/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,20 @@ export class StliteKernel {
});
}

/**
* Reboot the Streamlit server.
* Note that we also need to refresh (rerender) the frontend app after calling this method
* to reflect the changes on the user-facing side.
*/
public reboot(entrypoint: string): Promise<void> {
return this._asyncPostMessage({
type: "reboot",
data: {
entrypoint,
},
});
}

private _asyncPostMessage(
message: InMessage,
): Promise<ReplyMessageGeneralReply["data"]>;
Expand Down
7 changes: 7 additions & 0 deletions packages/kernel/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@
type: "initData";
data: WorkerInitialData;
}
export interface InMessageReboot extends InMessageBase {
type: "reboot";
data: {
entrypoint: string;
};
}
export interface InMessageWebSocketConnect extends InMessageBase {
type: "websocket:connect";
data: {
Expand All @@ -93,7 +99,7 @@
data: {
path: string;
data: string | ArrayBufferView;
opts?: Record<string, any>;

Check warning on line 102 in packages/kernel/src/types.ts

View workflow job for this annotation

GitHub Actions / test-kernel

Unexpected any. Specify a different type
};
}
export interface InMessageFileRename extends InMessageBase {
Expand All @@ -117,6 +123,7 @@
}
export type InMessage =
| InMessageInitData
| InMessageReboot
| InMessageWebSocketConnect
| InMessageWebSocketSend
| InMessageHttpRequest
Expand Down Expand Up @@ -196,7 +203,7 @@
interface ReplyMessageBase {
type: string;
error?: Error;
data?: any;

Check warning on line 206 in packages/kernel/src/types.ts

View workflow job for this annotation

GitHub Actions / test-kernel

Unexpected any. Specify a different type
}
export interface ReplyMessageHttpResponse extends ReplyMessageBase {
type: "http:response";
Expand Down
35 changes: 25 additions & 10 deletions packages/kernel/src/worker-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,8 @@ AppSession._on_scriptrunner_event = wrap_app_session_on_scriptrunner_event(AppSe
}

postProgressMessage("Booting up the Streamlit server.");
console.debug("Booting up the Streamlit server");
// The following Python code is based on streamlit.web.cli.main_run().
console.debug("Setting up the Streamlit configuration");
self.__streamlitFlagOptions__ = {
// gatherUsageStats is disabled as default, but can be enabled explicitly by setting it to true.
"browser.gatherUsageStats": false,
Expand All @@ -380,7 +380,6 @@ AppSession._on_scriptrunner_event = wrap_app_session_on_scriptrunner_event(AppSe
};
await pyodide.runPythonAsync(`
from stlite_lib.bootstrap import load_config_options, prepare
from stlite_lib.server import Server
from js import __streamlitFlagOptions__

flag_options = __streamlitFlagOptions__.to_py()
Expand All @@ -390,16 +389,14 @@ main_script_path = "${entrypoint}"
args = []

prepare(main_script_path, args)

server = Server(main_script_path)
server.start()
`);
console.debug("Booted up the Streamlit server");
console.debug("Set up the Streamlit configuration");

console.debug("Setting up the HTTP server");
// Pull the http server instance from Python world to JS world and set up it.
httpServer = pyodide.globals.get("server").copy();
console.debug("Set up the HTTP server");
console.debug("Booting up the Streamlit server");
const Server = pyodide.pyimport("stlite_lib.server.Server");
httpServer = Server(entrypoint);
httpServer.start();
console.debug("Booted up the Streamlit server");

postMessage({
type: "event:loaded",
Expand Down Expand Up @@ -438,6 +435,24 @@ server.start()

try {
switch (msg.type) {
case "reboot": {
console.debug("Reboot the Streamlit server", msg.data);

const { entrypoint } = msg.data;

httpServer.stop();

console.debug("Booting up the Streamlit server");
const Server = pyodide.pyimport("stlite_lib.server.Server");
httpServer = Server(entrypoint);
httpServer.start();
console.debug("Booted up the Streamlit server");

messagePort.postMessage({
type: "reply",
});
break;
}
case "websocket:connect": {
console.debug("websocket:connect", msg.data);

Expand Down
7 changes: 7 additions & 0 deletions packages/sharing-common/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export interface ForwardMessageBase {
type: string;
data?: unknown;
}
export interface RebootMessage extends ForwardMessageBase {
type: "reboot";
data: {
entrypoint: string;
};
}
export interface FileWriteMessage extends ForwardMessageBase {
type: "file:write";
data: {
Expand Down Expand Up @@ -34,6 +40,7 @@ export interface InstallMessage extends ForwardMessageBase {
};
}
export type ForwardMessage =
| RebootMessage
| FileWriteMessage
| FileRenameMessage
| FileUnlinkMessage
Expand Down
20 changes: 20 additions & 0 deletions packages/sharing-editor/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,25 @@ function App() {
[updateAppData],
);

const handleEntrypointChange = useCallback<EditorProps["onEntrypointChange"]>(
(entrypoint) => {
iframeRef.current?.postMessage({
type: "reboot",
data: {
entrypoint,
},
});

updateAppData((cur) => {
return {
...cur,
entrypoint,
};
});
},
[updateAppData],
);

const handleIframeMessage = useCallback<
StliteSharingIFrameProps["onMessage"]
>(
Expand Down Expand Up @@ -272,6 +291,7 @@ function App() {
onFileRename={handleFileRename}
onFileDelete={handleFileDelete}
onRequirementsChange={handleRequirementsChange}
onEntrypointChange={handleEntrypointChange}
/>
}
right={
Expand Down
162 changes: 103 additions & 59 deletions packages/sharing-editor/src/Editor/components/Tab.module.scss
Original file line number Diff line number Diff line change
@@ -1,90 +1,134 @@
@use "variables" as var;
@use "mixins";

.tabFrame {
.tab {
position: relative;
display: inline-flex;
align-items: center;
box-sizing: border-box;
margin-bottom: 4px;
font-size: 0.8rem;
background: rgba(0, 0, 0, 0.05);
border: rgba(0, 0, 0, 0.1) 1px solid;
border: rgba(0, 0, 0, 0.1) var.$border-width solid;
height: var.$tab-height;
line-height: normal;

&:hover {
background: initial;
}

&:has([role="tab"][aria-selected=true]) {
background: initial;
border-bottom: rgba(255,255,255,0) var.$border-width solid;

&::before {
content: '';
position: absolute;
top: - var.$border-width;
left: 0;
width: 100%;
height: var.$tab-highlight-height;
background-color: var(--c-primary);
}
}
}

.tabFrame:hover {
background: initial;
}

.tabFrameSelected {
background: initial;
border-top: var(--c-primary) var.$tab-highlight-height solid;
margin-top: -(var.$tab-highlight-height);
border-bottom: none;
position: relative;
}

$tabPaddingLeft: 0.5rem;
$deleteButtonSpaceWidth: 1.2rem;

.tabButton {
@include mixins.reset-button;

display: inline-flex;
align-items: center;
width: 100%;
height: 100%;
padding-left: $tabPaddingLeft;
padding-right: $deleteButtonSpaceWidth;
padding: 0 0.5rem;
}

.editableTabBody {
display: inline-block;
position: relative;

.fileNameForm {
position: absolute;
width: 100%;
left: 0;
top: 0;
}

.fileNameInput {
@include mixins.reset-input;

display: inline-block;
width: 100%;
}
.fileNameInputError {
border: red 1px solid;
}
}

.deleteButtonContainer {
position: absolute;
right: 0;
top: 0;
bottom: 0;
.entrypointIndicator {
display: flex;
align-items: center;
padding-top: var.$tab-highlight-height;
pointer-events: none;
justify-content: center;
margin-right: 0.3rem;
position: relative;

.tooltip {
display: none;

position: absolute;
top: 0;
transform: translate(0, -50%);
left: 100%;
z-index: 1;
font-size: 0.7rem;
text-align: center;
border-radius: 6px;
padding: 0.3rem 0.5rem;
background-color: rgba(0,0,0,0.6);
color: #fff;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
pointer-events: none;
}

&:hover .tooltip {
display: block;
}
}

.deleteButton {
.dropdownButton {
@include mixins.reset-button;

font-size: 0.6rem;

padding: 0.2rem;

pointer-events: initial;
}
.deleteButton:not([disabled]):hover {
color: var(--c-primary);
}

.selectedTab {
display: inline-block;
padding-left: $tabPaddingLeft;
padding-right: $deleteButtonSpaceWidth;
}
display: flex;
width: 1rem;
height: 100%;
align-items: center;
justify-content: center;
margin-left: -0.5rem;

.selectedTabInner {
position: relative;
}
font-size: 0.6rem;
cursor: pointer;

.fileNameForm {
display: inline-block;
position: absolute;
width: 100%;
left: 0;
top: 0;
&:hover {
color: var(--c-primary);
}
}

.fileNameInput {
@include mixins.reset-input;

display: inline-block;
width: 100%;
}
.fileNameInputError {
border: red 1px solid;
.dropdownContent {
display: flex;
flex-direction: column;
background-color: var(--c-background);
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);

button {
background: none;
border: none;
padding: 8px 16px;
text-align: left;
cursor: pointer;
width: 100%;

&:hover {
background-color: var(--c-background-hover);
}
}
}
Loading
Loading