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)
|