Spaces:
Running
Running
Add an execute button and endpoint.
Browse files
lynxkite-app/src/lynxkite_app/main.py
CHANGED
|
@@ -151,6 +151,14 @@ async def upload(req: fastapi.Request):
|
|
| 151 |
return {"status": "ok"}
|
| 152 |
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
class SPAStaticFiles(StaticFiles):
|
| 155 |
"""Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
|
| 156 |
|
|
|
|
| 151 |
return {"status": "ok"}
|
| 152 |
|
| 153 |
|
| 154 |
+
@app.post("/api/execute_workspace")
|
| 155 |
+
async def execute_workspace(name: str):
|
| 156 |
+
"""Trigger and await the execution of a workspace."""
|
| 157 |
+
room = await crdt.ws_websocket_server.get_room(name)
|
| 158 |
+
ws_pyd = workspace.Workspace.model_validate(room.ws.to_py())
|
| 159 |
+
await crdt.execute(name, room.ws, ws_pyd)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
class SPAStaticFiles(StaticFiles):
|
| 163 |
"""Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
|
| 164 |
|
lynxkite-app/web/src/index.css
CHANGED
|
@@ -54,7 +54,7 @@ body {
|
|
| 54 |
display: flex;
|
| 55 |
align-items: center;
|
| 56 |
|
| 57 |
-
|
| 58 |
color: oklch(75% 0.13 230);
|
| 59 |
font-size: 1.5em;
|
| 60 |
padding: 0 10px;
|
|
|
|
| 54 |
display: flex;
|
| 55 |
align-items: center;
|
| 56 |
|
| 57 |
+
.btn {
|
| 58 |
color: oklch(75% 0.13 230);
|
| 59 |
font-size: 1.5em;
|
| 60 |
padding: 0 10px;
|
lynxkite-app/web/src/workspace/Workspace.tsx
CHANGED
|
@@ -26,6 +26,8 @@ import Atom from "~icons/tabler/atom.jsx";
|
|
| 26 |
// @ts-ignore
|
| 27 |
import Backspace from "~icons/tabler/backspace.jsx";
|
| 28 |
// @ts-ignore
|
|
|
|
|
|
|
| 29 |
import Close from "~icons/tabler/x.jsx";
|
| 30 |
import type { Workspace, WorkspaceNode } from "../apiTypes.ts";
|
| 31 |
import favicon from "../assets/favicon.ico";
|
|
@@ -181,12 +183,16 @@ function LynxKiteFlow() {
|
|
| 181 |
useEffect(() => {
|
| 182 |
const handleKeyDown = (event: KeyboardEvent) => {
|
| 183 |
// Show the node search dialog on "/".
|
| 184 |
-
if (
|
|
|
|
| 185 |
event.preventDefault();
|
| 186 |
setNodeSearchSettings({
|
| 187 |
pos: { x: 100, y: 100 },
|
| 188 |
boxes: catalog.data![state.workspace.env!],
|
| 189 |
});
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
| 191 |
};
|
| 192 |
// TODO: Switch to keydown once https://github.com/xyflow/xyflow/pull/5055 is merged.
|
|
@@ -318,6 +324,12 @@ function LynxKiteFlow() {
|
|
| 318 |
setMessage("File upload failed.");
|
| 319 |
}
|
| 320 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
return (
|
| 322 |
<div className="workspace">
|
| 323 |
<div className="top-bar bg-neutral">
|
|
@@ -334,13 +346,16 @@ function LynxKiteFlow() {
|
|
| 334 |
}}
|
| 335 |
/>
|
| 336 |
<div className="tools text-secondary">
|
| 337 |
-
<
|
| 338 |
<Atom />
|
| 339 |
-
</
|
| 340 |
-
<
|
| 341 |
<Backspace />
|
| 342 |
-
</
|
| 343 |
-
<
|
|
|
|
|
|
|
|
|
|
| 344 |
<Close />
|
| 345 |
</a>
|
| 346 |
</div>
|
|
|
|
| 26 |
// @ts-ignore
|
| 27 |
import Backspace from "~icons/tabler/backspace.jsx";
|
| 28 |
// @ts-ignore
|
| 29 |
+
import Restart from "~icons/tabler/rotate-clockwise.jsx";
|
| 30 |
+
// @ts-ignore
|
| 31 |
import Close from "~icons/tabler/x.jsx";
|
| 32 |
import type { Workspace, WorkspaceNode } from "../apiTypes.ts";
|
| 33 |
import favicon from "../assets/favicon.ico";
|
|
|
|
| 183 |
useEffect(() => {
|
| 184 |
const handleKeyDown = (event: KeyboardEvent) => {
|
| 185 |
// Show the node search dialog on "/".
|
| 186 |
+
if (nodeSearchSettings || isTypingInFormElement()) return;
|
| 187 |
+
if (event.key === "/") {
|
| 188 |
event.preventDefault();
|
| 189 |
setNodeSearchSettings({
|
| 190 |
pos: { x: 100, y: 100 },
|
| 191 |
boxes: catalog.data![state.workspace.env!],
|
| 192 |
});
|
| 193 |
+
} else if (event.key === "r") {
|
| 194 |
+
event.preventDefault();
|
| 195 |
+
executeWorkspace();
|
| 196 |
}
|
| 197 |
};
|
| 198 |
// TODO: Switch to keydown once https://github.com/xyflow/xyflow/pull/5055 is merged.
|
|
|
|
| 324 |
setMessage("File upload failed.");
|
| 325 |
}
|
| 326 |
}
|
| 327 |
+
async function executeWorkspace() {
|
| 328 |
+
const response = await axios.post(`/api/execute_workspace?name=${path}`);
|
| 329 |
+
if (response.status !== 200) {
|
| 330 |
+
setMessage("Workspace execution failed.");
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
return (
|
| 334 |
<div className="workspace">
|
| 335 |
<div className="top-bar bg-neutral">
|
|
|
|
| 346 |
}}
|
| 347 |
/>
|
| 348 |
<div className="tools text-secondary">
|
| 349 |
+
<button className="btn btn-link">
|
| 350 |
<Atom />
|
| 351 |
+
</button>
|
| 352 |
+
<button className="btn btn-link">
|
| 353 |
<Backspace />
|
| 354 |
+
</button>
|
| 355 |
+
<button className="btn btn-link" onClick={executeWorkspace}>
|
| 356 |
+
<Restart />
|
| 357 |
+
</button>
|
| 358 |
+
<a className="btn btn-link" href={`/dir/${parentDir}`}>
|
| 359 |
<Close />
|
| 360 |
</a>
|
| 361 |
</div>
|
lynxkite-core/src/lynxkite/core/ops.py
CHANGED
|
@@ -361,12 +361,10 @@ def load_user_scripts(workspace: str):
|
|
| 361 |
|
| 362 |
def install_requirements(req: pathlib.Path):
|
| 363 |
cmd = ["uv", "pip", "install", "-r", str(req)]
|
| 364 |
-
print(f"Running {' '.join(cmd)}")
|
| 365 |
subprocess.check_call(cmd)
|
| 366 |
|
| 367 |
|
| 368 |
def run_user_script(script_path: pathlib.Path):
|
| 369 |
-
print(f"Running {script_path}...")
|
| 370 |
spec = importlib.util.spec_from_file_location(script_path.stem, str(script_path))
|
| 371 |
module = importlib.util.module_from_spec(spec)
|
| 372 |
spec.loader.exec_module(module)
|
|
|
|
| 361 |
|
| 362 |
def install_requirements(req: pathlib.Path):
|
| 363 |
cmd = ["uv", "pip", "install", "-r", str(req)]
|
|
|
|
| 364 |
subprocess.check_call(cmd)
|
| 365 |
|
| 366 |
|
| 367 |
def run_user_script(script_path: pathlib.Path):
|
|
|
|
| 368 |
spec = importlib.util.spec_from_file_location(script_path.stem, str(script_path))
|
| 369 |
module = importlib.util.module_from_spec(spec)
|
| 370 |
spec.loader.exec_module(module)
|