Spaces:
Running
Running
Set up formatting for VS Code and as a pre-commit.
Browse files- .gitignore +2 -1
- .pre-commit-config.yaml +17 -0
- .python-version +1 -0
- .vscode/settings.json +12 -0
- README.md +1 -0
- lynxkite-app/README.md +6 -0
- lynxkite-app/pyproject.toml +1 -1
- lynxkite-app/src/lynxkite_app/main.py +7 -8
- lynxkite-app/uv.lock +53 -2
- lynxkite-app/web/src/Directory.tsx +189 -159
- lynxkite-app/web/src/apiTypes.ts +41 -35
.gitignore
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
.vscode/*
|
2 |
!.vscode/extensions.json
|
|
|
3 |
.idea
|
4 |
.DS_Store
|
5 |
*.suo
|
@@ -14,4 +15,4 @@ build
|
|
14 |
joblib-cache
|
15 |
*.egg-info
|
16 |
|
17 |
-
lynxkite-app/crdt_data
|
|
|
1 |
.vscode/*
|
2 |
!.vscode/extensions.json
|
3 |
+
!.vscode/settings.json
|
4 |
.idea
|
5 |
.DS_Store
|
6 |
*.suo
|
|
|
15 |
joblib-cache
|
16 |
*.egg-info
|
17 |
|
18 |
+
lynxkite-app/crdt_data
|
.pre-commit-config.yaml
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
repos:
|
2 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
3 |
+
rev: v4.5.0
|
4 |
+
hooks:
|
5 |
+
- id: trailing-whitespace
|
6 |
+
- id: end-of-file-fixer
|
7 |
+
- id: check-yaml
|
8 |
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
9 |
+
rev: v0.9.6
|
10 |
+
hooks:
|
11 |
+
- id: ruff
|
12 |
+
args: [ --fix ]
|
13 |
+
- id: ruff-format
|
14 |
+
- repo: https://github.com/biomejs/pre-commit
|
15 |
+
rev: v1.9.4
|
16 |
+
hooks:
|
17 |
+
- id: biome-check
|
.python-version
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
3.11
|
.vscode/settings.json
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"editor.formatOnSave": true,
|
3 |
+
"ruff.enable": true,
|
4 |
+
"html.format.enable": true,
|
5 |
+
"css.format.enable": true,
|
6 |
+
"typescript.format.enable": true,
|
7 |
+
"files.insertFinalNewline": true,
|
8 |
+
"files.trimTrailingWhitespace": true,
|
9 |
+
"[python]": {
|
10 |
+
"editor.defaultFormatter": "charliermarsh.ruff"
|
11 |
+
}
|
12 |
+
}
|
README.md
CHANGED
@@ -23,6 +23,7 @@ Install everything like this:
|
|
23 |
```bash
|
24 |
uv venv
|
25 |
source .venv/bin/activate
|
|
|
26 |
# The [dev] tag is only needed if you intend on running tests
|
27 |
uv pip install -e lynxkite-core/[dev] -e lynxkite-app/[dev] -e lynxkite-graph-analytics/[dev] -e lynxkite-lynxscribe/ -e lynxkite-pillow-example/
|
28 |
```
|
|
|
23 |
```bash
|
24 |
uv venv
|
25 |
source .venv/bin/activate
|
26 |
+
uvx pre-commit install
|
27 |
# The [dev] tag is only needed if you intend on running tests
|
28 |
uv pip install -e lynxkite-core/[dev] -e lynxkite-app/[dev] -e lynxkite-graph-analytics/[dev] -e lynxkite-lynxscribe/ -e lynxkite-pillow-example/
|
29 |
```
|
lynxkite-app/README.md
CHANGED
@@ -23,3 +23,9 @@ cd web
|
|
23 |
npm i
|
24 |
npm run dev
|
25 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
npm i
|
24 |
npm run dev
|
25 |
```
|
26 |
+
|
27 |
+
To update the frontend types with the backend types:
|
28 |
+
|
29 |
+
```bash
|
30 |
+
$ uv run pydantic2ts --module lynxkite_app.main --output ./web/src/apiTypes.ts --json2ts-cmd "npx json-schema-to-typescript"
|
31 |
+
```
|
lynxkite-app/pyproject.toml
CHANGED
@@ -9,12 +9,12 @@ dependencies = [
|
|
9 |
"lynxkite-core",
|
10 |
"orjson>=3.10.13",
|
11 |
"pycrdt-websocket>=0.15.3",
|
12 |
-
"pydantic-to-typescript>=2.0.0",
|
13 |
"sse-starlette>=2.2.1",
|
14 |
]
|
15 |
|
16 |
[project.optional-dependencies]
|
17 |
dev = [
|
|
|
18 |
"pytest>=8.3.4",
|
19 |
]
|
20 |
|
|
|
9 |
"lynxkite-core",
|
10 |
"orjson>=3.10.13",
|
11 |
"pycrdt-websocket>=0.15.3",
|
|
|
12 |
"sse-starlette>=2.2.1",
|
13 |
]
|
14 |
|
15 |
[project.optional-dependencies]
|
16 |
dev = [
|
17 |
+
"pydantic-to-typescript>=2.0.0",
|
18 |
"pytest>=8.3.4",
|
19 |
]
|
20 |
|
lynxkite-app/src/lynxkite_app/main.py
CHANGED
@@ -2,12 +2,7 @@
|
|
2 |
|
3 |
import os
|
4 |
import shutil
|
5 |
-
|
6 |
-
if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true":
|
7 |
-
import cudf.pandas
|
8 |
-
|
9 |
-
cudf.pandas.install()
|
10 |
-
import dataclasses
|
11 |
import fastapi
|
12 |
import importlib
|
13 |
import pathlib
|
@@ -18,6 +13,11 @@ from lynxkite.core import ops
|
|
18 |
from lynxkite.core import workspace
|
19 |
from . import crdt
|
20 |
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
def detect_plugins():
|
23 |
plugins = {}
|
@@ -86,8 +86,7 @@ DATA_PATH = pathlib.Path(os.environ.get("LYNXKITE_DATA", "lynxkite_data"))
|
|
86 |
CRDT_PATH = pathlib.Path(os.environ.get("LYNXKITE_CRDT_DATA", "lynxkite_crdt_data"))
|
87 |
|
88 |
|
89 |
-
|
90 |
-
class DirectoryEntry:
|
91 |
name: str
|
92 |
type: str
|
93 |
|
|
|
2 |
|
3 |
import os
|
4 |
import shutil
|
5 |
+
import pydantic
|
|
|
|
|
|
|
|
|
|
|
6 |
import fastapi
|
7 |
import importlib
|
8 |
import pathlib
|
|
|
13 |
from lynxkite.core import workspace
|
14 |
from . import crdt
|
15 |
|
16 |
+
if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true":
|
17 |
+
import cudf.pandas
|
18 |
+
|
19 |
+
cudf.pandas.install()
|
20 |
+
|
21 |
|
22 |
def detect_plugins():
|
23 |
plugins = {}
|
|
|
86 |
CRDT_PATH = pathlib.Path(os.environ.get("LYNXKITE_CRDT_DATA", "lynxkite_crdt_data"))
|
87 |
|
88 |
|
89 |
+
class DirectoryEntry(pydantic.BaseModel):
|
|
|
90 |
name: str
|
91 |
type: str
|
92 |
|
lynxkite-app/uv.lock
CHANGED
@@ -194,6 +194,15 @@ wheels = [
|
|
194 |
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
195 |
]
|
196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
[[package]]
|
198 |
name = "jinja2"
|
199 |
version = "3.1.5"
|
@@ -215,17 +224,23 @@ dependencies = [
|
|
215 |
{ name = "lynxkite-core" },
|
216 |
{ name = "orjson" },
|
217 |
{ name = "pycrdt-websocket" },
|
218 |
-
{ name = "pydantic-to-typescript" },
|
219 |
{ name = "sse-starlette" },
|
220 |
]
|
221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
222 |
[package.metadata]
|
223 |
requires-dist = [
|
224 |
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
|
225 |
{ name = "lynxkite-core", virtual = "../lynxkite-core" },
|
226 |
{ name = "orjson", specifier = ">=3.10.13" },
|
227 |
{ name = "pycrdt-websocket", specifier = ">=0.15.3" },
|
228 |
-
{ name = "pydantic-to-typescript", specifier = ">=2.0.0" },
|
|
|
229 |
{ name = "sse-starlette", specifier = ">=2.2.1" },
|
230 |
]
|
231 |
|
@@ -234,6 +249,9 @@ name = "lynxkite-core"
|
|
234 |
version = "0.1.0"
|
235 |
source = { virtual = "../lynxkite-core" }
|
236 |
|
|
|
|
|
|
|
237 |
[[package]]
|
238 |
name = "markdown-it-py"
|
239 |
version = "3.0.0"
|
@@ -346,6 +364,24 @@ wheels = [
|
|
346 |
{ url = "https://files.pythonhosted.org/packages/59/ac/5e96cad01083015f7bfdb02ccafa489da8e6caa7f4c519e215f04d2bd856/orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364", size = 133388 },
|
347 |
]
|
348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
349 |
[[package]]
|
350 |
name = "pycrdt"
|
351 |
version = "0.10.9"
|
@@ -486,6 +522,21 @@ wheels = [
|
|
486 |
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
487 |
]
|
488 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
489 |
[[package]]
|
490 |
name = "python-dotenv"
|
491 |
version = "1.0.1"
|
|
|
194 |
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
195 |
]
|
196 |
|
197 |
+
[[package]]
|
198 |
+
name = "iniconfig"
|
199 |
+
version = "2.0.0"
|
200 |
+
source = { registry = "https://pypi.org/simple" }
|
201 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
|
202 |
+
wheels = [
|
203 |
+
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
204 |
+
]
|
205 |
+
|
206 |
[[package]]
|
207 |
name = "jinja2"
|
208 |
version = "3.1.5"
|
|
|
224 |
{ name = "lynxkite-core" },
|
225 |
{ name = "orjson" },
|
226 |
{ name = "pycrdt-websocket" },
|
|
|
227 |
{ name = "sse-starlette" },
|
228 |
]
|
229 |
|
230 |
+
[package.optional-dependencies]
|
231 |
+
dev = [
|
232 |
+
{ name = "pydantic-to-typescript" },
|
233 |
+
{ name = "pytest" },
|
234 |
+
]
|
235 |
+
|
236 |
[package.metadata]
|
237 |
requires-dist = [
|
238 |
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
|
239 |
{ name = "lynxkite-core", virtual = "../lynxkite-core" },
|
240 |
{ name = "orjson", specifier = ">=3.10.13" },
|
241 |
{ name = "pycrdt-websocket", specifier = ">=0.15.3" },
|
242 |
+
{ name = "pydantic-to-typescript", marker = "extra == 'dev'", specifier = ">=2.0.0" },
|
243 |
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" },
|
244 |
{ name = "sse-starlette", specifier = ">=2.2.1" },
|
245 |
]
|
246 |
|
|
|
249 |
version = "0.1.0"
|
250 |
source = { virtual = "../lynxkite-core" }
|
251 |
|
252 |
+
[package.metadata]
|
253 |
+
requires-dist = [{ name = "pytest", marker = "extra == 'dev'" }]
|
254 |
+
|
255 |
[[package]]
|
256 |
name = "markdown-it-py"
|
257 |
version = "3.0.0"
|
|
|
364 |
{ url = "https://files.pythonhosted.org/packages/59/ac/5e96cad01083015f7bfdb02ccafa489da8e6caa7f4c519e215f04d2bd856/orjson-3.10.14-cp313-cp313-win_amd64.whl", hash = "sha256:eca04dfd792cedad53dc9a917da1a522486255360cb4e77619343a20d9f35364", size = 133388 },
|
365 |
]
|
366 |
|
367 |
+
[[package]]
|
368 |
+
name = "packaging"
|
369 |
+
version = "24.2"
|
370 |
+
source = { registry = "https://pypi.org/simple" }
|
371 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
372 |
+
wheels = [
|
373 |
+
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
374 |
+
]
|
375 |
+
|
376 |
+
[[package]]
|
377 |
+
name = "pluggy"
|
378 |
+
version = "1.5.0"
|
379 |
+
source = { registry = "https://pypi.org/simple" }
|
380 |
+
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
381 |
+
wheels = [
|
382 |
+
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
383 |
+
]
|
384 |
+
|
385 |
[[package]]
|
386 |
name = "pycrdt"
|
387 |
version = "0.10.9"
|
|
|
522 |
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
523 |
]
|
524 |
|
525 |
+
[[package]]
|
526 |
+
name = "pytest"
|
527 |
+
version = "8.3.4"
|
528 |
+
source = { registry = "https://pypi.org/simple" }
|
529 |
+
dependencies = [
|
530 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
531 |
+
{ name = "iniconfig" },
|
532 |
+
{ name = "packaging" },
|
533 |
+
{ name = "pluggy" },
|
534 |
+
]
|
535 |
+
sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
|
536 |
+
wheels = [
|
537 |
+
{ url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
|
538 |
+
]
|
539 |
+
|
540 |
[[package]]
|
541 |
name = "python-dotenv"
|
542 |
version = "1.0.1"
|
lynxkite-app/web/src/Directory.tsx
CHANGED
@@ -1,172 +1,202 @@
|
|
1 |
-
// The directory browser.
|
2 |
-
import { useParams, useNavigate } from "react-router";
|
3 |
import { useState } from "react";
|
4 |
-
|
5 |
-
|
|
|
|
|
6 |
|
7 |
-
import logo from './assets/logo.png';
|
8 |
// @ts-ignore
|
9 |
-
import
|
10 |
// @ts-ignore
|
11 |
-
import
|
12 |
// @ts-ignore
|
13 |
-
import
|
14 |
// @ts-ignore
|
15 |
-
import
|
16 |
// @ts-ignore
|
17 |
-
import
|
18 |
// @ts-ignore
|
19 |
-
import Trash from
|
20 |
-
|
21 |
-
|
22 |
|
23 |
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
24 |
|
25 |
export default function () {
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
}
|
|
|
|
|
|
|
1 |
import { useState } from "react";
|
2 |
+
// The directory browser.
|
3 |
+
import { useNavigate, useParams } from "react-router";
|
4 |
+
import useSWR from "swr";
|
5 |
+
import type { DirectoryEntry } from "./apiTypes.ts";
|
6 |
|
|
|
7 |
// @ts-ignore
|
8 |
+
import File from "~icons/tabler/file";
|
9 |
// @ts-ignore
|
10 |
+
import FilePlus from "~icons/tabler/file-plus";
|
11 |
// @ts-ignore
|
12 |
+
import Folder from "~icons/tabler/folder";
|
13 |
// @ts-ignore
|
14 |
+
import FolderPlus from "~icons/tabler/folder-plus";
|
15 |
// @ts-ignore
|
16 |
+
import Home from "~icons/tabler/home";
|
17 |
// @ts-ignore
|
18 |
+
import Trash from "~icons/tabler/trash";
|
19 |
+
import logo from "./assets/logo.png";
|
|
|
20 |
|
21 |
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
22 |
|
23 |
export default function () {
|
24 |
+
const { path } = useParams();
|
25 |
+
const encodedPath = encodeURIComponent(path || "");
|
26 |
+
const list = useSWR(`/api/dir/list?path=${encodedPath}`, fetcher);
|
27 |
+
const navigate = useNavigate();
|
28 |
+
const [isCreatingDir, setIsCreatingDir] = useState(false);
|
29 |
+
const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
|
30 |
+
|
31 |
+
function link(item: DirectoryEntry) {
|
32 |
+
if (item.type === "directory") {
|
33 |
+
return `/dir/${item.name}`;
|
34 |
+
}
|
35 |
+
return `/edit/${item.name}`;
|
36 |
+
}
|
37 |
+
|
38 |
+
function shortName(item: DirectoryEntry) {
|
39 |
+
return item.name.split("/").pop();
|
40 |
+
}
|
41 |
+
|
42 |
+
function newName(list: DirectoryEntry[], baseName = "Untitled") {
|
43 |
+
let i = 0;
|
44 |
+
while (true) {
|
45 |
+
const name = `${baseName}${i ? ` ${i}` : ""}`;
|
46 |
+
if (!list.find((item) => item.name === name)) {
|
47 |
+
return name;
|
48 |
+
}
|
49 |
+
i++;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
function newWorkspaceIn(
|
54 |
+
path: string,
|
55 |
+
list: DirectoryEntry[],
|
56 |
+
workspaceName?: string,
|
57 |
+
) {
|
58 |
+
const pathSlash = path ? `${path}/` : "";
|
59 |
+
const name = workspaceName || newName(list);
|
60 |
+
navigate(`/edit/${pathSlash}${name}`, { replace: true });
|
61 |
+
}
|
62 |
+
|
63 |
+
async function newFolderIn(
|
64 |
+
path: string,
|
65 |
+
list: DirectoryEntry[],
|
66 |
+
folderName?: string,
|
67 |
+
) {
|
68 |
+
const name = folderName || newName(list, "New Folder");
|
69 |
+
const pathSlash = path ? `${path}/` : "";
|
70 |
+
|
71 |
+
const res = await fetch("/api/dir/mkdir", {
|
72 |
+
method: "POST",
|
73 |
+
headers: { "Content-Type": "application/json" },
|
74 |
+
body: JSON.stringify({ path: pathSlash + name }),
|
75 |
+
});
|
76 |
+
if (res.ok) {
|
77 |
+
navigate(`/dir/${pathSlash}${name}`);
|
78 |
+
} else {
|
79 |
+
alert("Failed to create folder.");
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
async function deleteItem(item: DirectoryEntry) {
|
84 |
+
if (!window.confirm(`Are you sure you want to delete "${item.name}"?`))
|
85 |
+
return;
|
86 |
+
const pathSlash = path ? `${path}/` : "";
|
87 |
+
|
88 |
+
const apiPath =
|
89 |
+
item.type === "directory" ? "/api/dir/delete" : "/api/delete";
|
90 |
+
await fetch(apiPath, {
|
91 |
+
method: "POST",
|
92 |
+
headers: { "Content-Type": "application/json" },
|
93 |
+
body: JSON.stringify({ path: pathSlash + item.name }),
|
94 |
+
});
|
95 |
+
}
|
96 |
+
|
97 |
+
return (
|
98 |
+
<div className="directory">
|
99 |
+
<div className="logo">
|
100 |
+
<a href="https://lynxkite.com/">
|
101 |
+
<img src={logo} className="logo-image" alt="LynxKite logo" />
|
102 |
+
</a>
|
103 |
+
<div className="tagline">The Complete Graph Data Science Platform</div>
|
104 |
+
</div>
|
105 |
+
<div className="entry-list">
|
106 |
+
{list.error && <p className="error">{list.error.message}</p>}
|
107 |
+
{list.isLoading && (
|
108 |
+
<output className="loading spinner-border">
|
109 |
+
<span className="visually-hidden">Loading...</span>
|
110 |
+
</output>
|
111 |
+
)}
|
112 |
+
|
113 |
+
{list.data && (
|
114 |
+
<>
|
115 |
+
<div className="actions">
|
116 |
+
<div className="new-workspace">
|
117 |
+
{isCreatingWorkspace && (
|
118 |
+
// @ts-ignore
|
119 |
+
<form
|
120 |
+
onSubmit={(e) => {
|
121 |
+
e.preventDefault();
|
122 |
+
newWorkspaceIn(
|
123 |
+
path || "",
|
124 |
+
list.data,
|
125 |
+
e.target.workspaceName.value.trim(),
|
126 |
+
);
|
127 |
+
}}
|
128 |
+
>
|
129 |
+
<input
|
130 |
+
type="text"
|
131 |
+
name="workspaceName"
|
132 |
+
defaultValue={newName(list.data)}
|
133 |
+
placeholder={newName(list.data)}
|
134 |
+
/>
|
135 |
+
</form>
|
136 |
+
)}
|
137 |
+
<button
|
138 |
+
type="button"
|
139 |
+
onClick={() => setIsCreatingWorkspace(true)}
|
140 |
+
>
|
141 |
+
<FolderPlus /> New workspace
|
142 |
+
</button>
|
143 |
+
</div>
|
144 |
+
|
145 |
+
<div className="new-folder">
|
146 |
+
{isCreatingDir && (
|
147 |
+
// @ts-ignore
|
148 |
+
<form
|
149 |
+
onSubmit={(e) => {
|
150 |
+
e.preventDefault();
|
151 |
+
newFolderIn(
|
152 |
+
path || "",
|
153 |
+
list.data,
|
154 |
+
e.target.folderName.value.trim(),
|
155 |
+
);
|
156 |
+
}}
|
157 |
+
>
|
158 |
+
<input
|
159 |
+
type="text"
|
160 |
+
name="folderName"
|
161 |
+
defaultValue={newName(list.data)}
|
162 |
+
placeholder={newName(list.data)}
|
163 |
+
/>
|
164 |
+
</form>
|
165 |
+
)}
|
166 |
+
<button type="button" onClick={() => setIsCreatingDir(true)}>
|
167 |
+
<FolderPlus /> New folder
|
168 |
+
</button>
|
169 |
+
</div>
|
170 |
+
</div>
|
171 |
+
|
172 |
+
{path && (
|
173 |
+
<div className="breadcrumbs">
|
174 |
+
<a href="/dir/">
|
175 |
+
<Home />
|
176 |
+
</a>{" "}
|
177 |
+
<span className="current-folder">{path}</span>
|
178 |
+
</div>
|
179 |
+
)}
|
180 |
+
|
181 |
+
{list.data.map((item: DirectoryEntry) => (
|
182 |
+
<div key={item.name} className="entry">
|
183 |
+
<a key={link(item)} href={link(item)}>
|
184 |
+
{item.type === "directory" ? <Folder /> : <File />}
|
185 |
+
{shortName(item)}
|
186 |
+
</a>
|
187 |
+
<button
|
188 |
+
type="button"
|
189 |
+
onClick={() => {
|
190 |
+
deleteItem(item);
|
191 |
+
}}
|
192 |
+
>
|
193 |
+
<Trash />
|
194 |
+
</button>
|
195 |
+
</div>
|
196 |
+
))}
|
197 |
+
</>
|
198 |
+
)}
|
199 |
+
</div>{" "}
|
200 |
+
</div>
|
201 |
+
);
|
202 |
}
|
lynxkite-app/web/src/apiTypes.ts
CHANGED
@@ -5,48 +5,54 @@
|
|
5 |
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
6 |
*/
|
7 |
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
|
12 |
-
* and run json-schema-to-typescript to regenerate this file.
|
13 |
-
*/
|
14 |
-
|
15 |
-
export interface BaseConfig {
|
16 |
-
[k: string]: unknown;
|
17 |
}
|
18 |
-
export interface
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
export interface Workspace {
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
}
|
29 |
export interface WorkspaceNode {
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
}
|
36 |
export interface WorkspaceNodeData {
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
44 |
}
|
45 |
export interface WorkspaceEdge {
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
}
|
|
|
5 |
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
6 |
*/
|
7 |
|
8 |
+
export interface DirectoryEntry {
|
9 |
+
name: string;
|
10 |
+
type: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
}
|
12 |
+
export interface SaveRequest {
|
13 |
+
path: string;
|
14 |
+
ws: Workspace;
|
15 |
+
[k: string]: unknown;
|
16 |
}
|
17 |
+
/**
|
18 |
+
* A workspace is a representation of a computational graph that consists of nodes and edges.
|
19 |
+
*
|
20 |
+
* Each node represents an operation or task, and the edges represent the flow of data between
|
21 |
+
* the nodes. Each workspace is associated with an environment, which determines the operations
|
22 |
+
* that can be performed in the workspace and the execution method for the operations.
|
23 |
+
*/
|
24 |
export interface Workspace {
|
25 |
+
env?: string;
|
26 |
+
nodes?: WorkspaceNode[];
|
27 |
+
edges?: WorkspaceEdge[];
|
28 |
+
[k: string]: unknown;
|
29 |
}
|
30 |
export interface WorkspaceNode {
|
31 |
+
id: string;
|
32 |
+
type: string;
|
33 |
+
data: WorkspaceNodeData;
|
34 |
+
position: Position;
|
35 |
+
[k: string]: unknown;
|
36 |
}
|
37 |
export interface WorkspaceNodeData {
|
38 |
+
title: string;
|
39 |
+
params: {
|
40 |
+
[k: string]: unknown;
|
41 |
+
};
|
42 |
+
display?: unknown;
|
43 |
+
error?: string | null;
|
44 |
+
[k: string]: unknown;
|
45 |
+
}
|
46 |
+
export interface Position {
|
47 |
+
x: number;
|
48 |
+
y: number;
|
49 |
+
[k: string]: unknown;
|
50 |
}
|
51 |
export interface WorkspaceEdge {
|
52 |
+
id: string;
|
53 |
+
source: string;
|
54 |
+
target: string;
|
55 |
+
sourceHandle: string;
|
56 |
+
targetHandle: string;
|
57 |
+
[k: string]: unknown;
|
58 |
}
|