Spaces:
Running
Running
Use cwd instead of LYNXKITE_DATA.
Browse files- Dockerfile +1 -1
- README.md +1 -1
- examples/AIMO +1 -1
- examples/Bio demo +1 -1
- examples/Graph RAG +1 -1
- examples/LynxScribe demo +4 -4
- examples/{aimo-examples.csv → uploads/aimo-examples.csv} +0 -0
- examples/{drug_target_data_sample.csv → uploads/drug_target_data_sample.csv} +0 -0
- examples/{example-pizza.md → uploads/example-pizza.md} +0 -0
- lynxkite-app/README.md +1 -1
- lynxkite-app/src/lynxkite_app/config.py +0 -8
- lynxkite-app/src/lynxkite_app/crdt.py +9 -8
- lynxkite-app/src/lynxkite_app/main.py +21 -18
- lynxkite-app/tests/test_main.py +7 -8
- lynxkite-app/web/playwright.config.ts +1 -1
- lynxkite-app/web/src/workspace/Workspace.tsx +1 -1
- lynxkite-core/src/lynxkite/core/workspace.py +2 -1
- lynxkite-lynxscribe/src/lynxkite_lynxscribe/lynxscribe_ops.py +6 -9
Dockerfile
CHANGED
@@ -11,6 +11,6 @@ RUN uv venv && uv pip install \
|
|
11 |
-e lynxkite-graph-analytics \
|
12 |
-e lynxkite-bio \
|
13 |
-e lynxkite-pillow-example
|
14 |
-
|
15 |
ENV PORT=7860
|
16 |
CMD ["uv", "run", "lynxkite"]
|
|
|
11 |
-e lynxkite-graph-analytics \
|
12 |
-e lynxkite-bio \
|
13 |
-e lynxkite-pillow-example
|
14 |
+
WORKDIR $HOME/app/examples
|
15 |
ENV PORT=7860
|
16 |
CMD ["uv", "run", "lynxkite"]
|
README.md
CHANGED
@@ -41,7 +41,7 @@ uv pip install -e lynxkite-core/[dev] -e lynxkite-app/[dev] -e lynxkite-graph-an
|
|
41 |
This also builds the frontend, hopefully very quickly. To run it:
|
42 |
|
43 |
```bash
|
44 |
-
|
45 |
```
|
46 |
|
47 |
If you also want to make changes to the frontend with hot reloading:
|
|
|
41 |
This also builds the frontend, hopefully very quickly. To run it:
|
42 |
|
43 |
```bash
|
44 |
+
cd examples && LYNXKITE_RELOAD=1 lynxkite
|
45 |
```
|
46 |
|
47 |
If you also want to make changes to the frontend with hot reloading:
|
examples/AIMO
CHANGED
@@ -786,7 +786,7 @@
|
|
786 |
"data": {
|
787 |
"title": "Input CSV",
|
788 |
"params": {
|
789 |
-
"filename": "
|
790 |
"key": "problem"
|
791 |
},
|
792 |
"display": null,
|
|
|
786 |
"data": {
|
787 |
"title": "Input CSV",
|
788 |
"params": {
|
789 |
+
"filename": "uploads/aimo-examples.csv",
|
790 |
"key": "problem"
|
791 |
},
|
792 |
"display": null,
|
examples/Bio demo
CHANGED
@@ -7,7 +7,7 @@
|
|
7 |
"data": {
|
8 |
"title": "Import CSV",
|
9 |
"params": {
|
10 |
-
"filename": "
|
11 |
"separator": "<auto>",
|
12 |
"columns": "<from file>"
|
13 |
},
|
|
|
7 |
"data": {
|
8 |
"title": "Import CSV",
|
9 |
"params": {
|
10 |
+
"filename": "uploads/drug_target_data_sample.csv",
|
11 |
"separator": "<auto>",
|
12 |
"columns": "<from file>"
|
13 |
},
|
examples/Graph RAG
CHANGED
@@ -7,7 +7,7 @@
|
|
7 |
"data": {
|
8 |
"title": "Input document",
|
9 |
"params": {
|
10 |
-
"filename": "
|
11 |
},
|
12 |
"display": null,
|
13 |
"error": null,
|
|
|
7 |
"data": {
|
8 |
"title": "Input document",
|
9 |
"params": {
|
10 |
+
"filename": "uploads/example-pizza.md"
|
11 |
},
|
12 |
"display": null,
|
13 |
"error": null,
|
examples/LynxScribe demo
CHANGED
@@ -138,7 +138,7 @@
|
|
138 |
"data": {
|
139 |
"title": "Scenario selector",
|
140 |
"params": {
|
141 |
-
"scenario_file": "/
|
142 |
"node_types": "intent_cluster"
|
143 |
},
|
144 |
"display": null,
|
@@ -252,9 +252,9 @@
|
|
252 |
"data": {
|
253 |
"title": "Knowledge base",
|
254 |
"params": {
|
255 |
-
"template_cluster_path": "/
|
256 |
-
"edges_path": "/
|
257 |
-
"nodes_path": "/
|
258 |
},
|
259 |
"display": null,
|
260 |
"error": null,
|
|
|
138 |
"data": {
|
139 |
"title": "Scenario selector",
|
140 |
"params": {
|
141 |
+
"scenario_file": "uploads/chat_api/scenarios.yaml",
|
142 |
"node_types": "intent_cluster"
|
143 |
},
|
144 |
"display": null,
|
|
|
252 |
"data": {
|
253 |
"title": "Knowledge base",
|
254 |
"params": {
|
255 |
+
"template_cluster_path": "uploads/chat_api/data/lynx/tempclusters.pickle",
|
256 |
+
"edges_path": "uploads/chat_api/data/lynx/edges.pickle",
|
257 |
+
"nodes_path": "uploads/chat_api/data/lynx/nodes.pickle"
|
258 |
},
|
259 |
"display": null,
|
260 |
"error": null,
|
examples/{aimo-examples.csv → uploads/aimo-examples.csv}
RENAMED
File without changes
|
examples/{drug_target_data_sample.csv → uploads/drug_target_data_sample.csv}
RENAMED
File without changes
|
examples/{example-pizza.md → uploads/example-pizza.md}
RENAMED
File without changes
|
lynxkite-app/README.md
CHANGED
@@ -13,7 +13,7 @@ To run the backend:
|
|
13 |
|
14 |
```bash
|
15 |
uv pip install -e .
|
16 |
-
|
17 |
```
|
18 |
|
19 |
To run the frontend:
|
|
|
13 |
|
14 |
```bash
|
15 |
uv pip install -e .
|
16 |
+
cd ../examples && LYNXKITE_RELOAD=1 lynxkite
|
17 |
```
|
18 |
|
19 |
To run the frontend:
|
lynxkite-app/src/lynxkite_app/config.py
DELETED
@@ -1,8 +0,0 @@
|
|
1 |
-
"""Some common configuration."""
|
2 |
-
|
3 |
-
import os
|
4 |
-
import pathlib
|
5 |
-
|
6 |
-
|
7 |
-
DATA_PATH = pathlib.Path(os.environ.get("LYNXKITE_DATA", "lynxkite_data"))
|
8 |
-
CRDT_PATH = pathlib.Path(os.environ.get("LYNXKITE_CRDT_DATA", "lynxkite_crdt_data"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lynxkite-app/src/lynxkite_app/crdt.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3 |
import asyncio
|
4 |
import contextlib
|
5 |
import enum
|
|
|
6 |
import fastapi
|
7 |
import os.path
|
8 |
import pycrdt
|
@@ -11,7 +12,6 @@ import pycrdt_websocket.ystore
|
|
11 |
import uvicorn
|
12 |
import builtins
|
13 |
from lynxkite.core import workspace, ops
|
14 |
-
from . import config
|
15 |
|
16 |
router = fastapi.APIRouter()
|
17 |
|
@@ -32,8 +32,9 @@ class WebsocketServer(pycrdt_websocket.WebsocketServer):
|
|
32 |
|
33 |
The workspace is loaded from "crdt_data" if it exists there, or from "data", or a new workspace is created.
|
34 |
"""
|
35 |
-
|
36 |
-
|
|
|
37 |
ystore = pycrdt_websocket.ystore.FileYStore(path)
|
38 |
ydoc = pycrdt.Doc()
|
39 |
ydoc["workspace"] = ws = pycrdt.Map()
|
@@ -165,9 +166,8 @@ def try_to_load_workspace(ws: pycrdt.Map, name: str):
|
|
165 |
ws: CRDT object to udpate with the workspace contents.
|
166 |
name: Name of the workspace to load.
|
167 |
"""
|
168 |
-
|
169 |
-
|
170 |
-
ws_pyd = workspace.load(json_path)
|
171 |
# We treat the display field as a black box, since it is a large
|
172 |
# dictionary that is meant to change as a whole.
|
173 |
crdt_update(ws, ws_pyd.model_dump(), non_collaborative_fields={"display"})
|
@@ -225,8 +225,9 @@ async def execute(
|
|
225 |
except asyncio.CancelledError:
|
226 |
return
|
227 |
print(f"Running {name} in {ws_pyd.env}...")
|
228 |
-
|
229 |
-
|
|
|
230 |
# Save user changes before executing, in case the execution fails.
|
231 |
workspace.save(ws_pyd, path)
|
232 |
ws_pyd._crdt = ws_crdt
|
|
|
3 |
import asyncio
|
4 |
import contextlib
|
5 |
import enum
|
6 |
+
import pathlib
|
7 |
import fastapi
|
8 |
import os.path
|
9 |
import pycrdt
|
|
|
12 |
import uvicorn
|
13 |
import builtins
|
14 |
from lynxkite.core import workspace, ops
|
|
|
15 |
|
16 |
router = fastapi.APIRouter()
|
17 |
|
|
|
32 |
|
33 |
The workspace is loaded from "crdt_data" if it exists there, or from "data", or a new workspace is created.
|
34 |
"""
|
35 |
+
crdt_path = pathlib.Path(".crdt")
|
36 |
+
path = crdt_path / f"{name}.crdt"
|
37 |
+
assert path.is_relative_to(crdt_path)
|
38 |
ystore = pycrdt_websocket.ystore.FileYStore(path)
|
39 |
ydoc = pycrdt.Doc()
|
40 |
ydoc["workspace"] = ws = pycrdt.Map()
|
|
|
166 |
ws: CRDT object to udpate with the workspace contents.
|
167 |
name: Name of the workspace to load.
|
168 |
"""
|
169 |
+
if os.path.exists(name):
|
170 |
+
ws_pyd = workspace.load(name)
|
|
|
171 |
# We treat the display field as a black box, since it is a large
|
172 |
# dictionary that is meant to change as a whole.
|
173 |
crdt_update(ws, ws_pyd.model_dump(), non_collaborative_fields={"display"})
|
|
|
225 |
except asyncio.CancelledError:
|
226 |
return
|
227 |
print(f"Running {name} in {ws_pyd.env}...")
|
228 |
+
cwd = pathlib.Path()
|
229 |
+
path = cwd / name
|
230 |
+
assert path.is_relative_to(cwd), "Provided workspace path is invalid"
|
231 |
# Save user changes before executing, in case the execution fails.
|
232 |
workspace.save(ws_pyd, path)
|
233 |
ws_pyd._crdt = ws_crdt
|
lynxkite-app/src/lynxkite_app/main.py
CHANGED
@@ -11,7 +11,7 @@ from fastapi.middleware.gzip import GZipMiddleware
|
|
11 |
import starlette
|
12 |
from lynxkite.core import ops
|
13 |
from lynxkite.core import workspace
|
14 |
-
from . import crdt
|
15 |
|
16 |
|
17 |
def detect_plugins():
|
@@ -45,9 +45,12 @@ class SaveRequest(workspace.BaseConfig):
|
|
45 |
ws: workspace.Workspace
|
46 |
|
47 |
|
|
|
|
|
|
|
48 |
def save(req: SaveRequest):
|
49 |
-
path =
|
50 |
-
assert path.is_relative_to(
|
51 |
workspace.save(req.ws, path)
|
52 |
|
53 |
|
@@ -61,18 +64,17 @@ async def save_and_execute(req: SaveRequest):
|
|
61 |
|
62 |
@app.post("/api/delete")
|
63 |
async def delete_workspace(req: dict):
|
64 |
-
json_path: pathlib.Path =
|
65 |
-
crdt_path: pathlib.Path =
|
66 |
-
assert json_path.is_relative_to(
|
67 |
-
assert crdt_path.is_relative_to(config.CRDT_PATH)
|
68 |
json_path.unlink()
|
69 |
crdt_path.unlink()
|
70 |
|
71 |
|
72 |
@app.get("/api/load")
|
73 |
def load(path: str):
|
74 |
-
path =
|
75 |
-
assert path.is_relative_to(
|
76 |
if not path.exists():
|
77 |
return workspace.Workspace()
|
78 |
return workspace.load(path)
|
@@ -85,15 +87,16 @@ class DirectoryEntry(pydantic.BaseModel):
|
|
85 |
|
86 |
@app.get("/api/dir/list")
|
87 |
def list_dir(path: str):
|
88 |
-
path =
|
89 |
-
assert path.is_relative_to(
|
90 |
return sorted(
|
91 |
[
|
92 |
DirectoryEntry(
|
93 |
-
name=str(p.relative_to(
|
94 |
type="directory" if p.is_dir() else "workspace",
|
95 |
)
|
96 |
for p in path.iterdir()
|
|
|
97 |
],
|
98 |
key=lambda x: x.name,
|
99 |
)
|
@@ -101,16 +104,16 @@ def list_dir(path: str):
|
|
101 |
|
102 |
@app.post("/api/dir/mkdir")
|
103 |
def make_dir(req: dict):
|
104 |
-
path =
|
105 |
-
assert path.is_relative_to(
|
106 |
assert not path.exists(), f"{path} already exists"
|
107 |
path.mkdir()
|
108 |
|
109 |
|
110 |
@app.post("/api/dir/delete")
|
111 |
def delete_dir(req: dict):
|
112 |
-
path: pathlib.Path =
|
113 |
-
assert all([path.is_relative_to(
|
114 |
shutil.rmtree(path)
|
115 |
|
116 |
|
@@ -133,8 +136,8 @@ async def upload(req: fastapi.Request):
|
|
133 |
"""Receives file uploads and stores them in DATA_PATH."""
|
134 |
form = await req.form()
|
135 |
for file in form.values():
|
136 |
-
file_path =
|
137 |
-
assert file_path.is_relative_to(
|
138 |
with file_path.open("wb") as buffer:
|
139 |
shutil.copyfileobj(file.file, buffer)
|
140 |
return {"status": "ok"}
|
|
|
11 |
import starlette
|
12 |
from lynxkite.core import ops
|
13 |
from lynxkite.core import workspace
|
14 |
+
from . import crdt
|
15 |
|
16 |
|
17 |
def detect_plugins():
|
|
|
45 |
ws: workspace.Workspace
|
46 |
|
47 |
|
48 |
+
data_path = pathlib.Path()
|
49 |
+
|
50 |
+
|
51 |
def save(req: SaveRequest):
|
52 |
+
path = data_path / req.path
|
53 |
+
assert path.is_relative_to(data_path)
|
54 |
workspace.save(req.ws, path)
|
55 |
|
56 |
|
|
|
64 |
|
65 |
@app.post("/api/delete")
|
66 |
async def delete_workspace(req: dict):
|
67 |
+
json_path: pathlib.Path = data_path / req["path"]
|
68 |
+
crdt_path: pathlib.Path = data_path / ".crdt" / f"{req['path']}.crdt"
|
69 |
+
assert json_path.is_relative_to(data_path)
|
|
|
70 |
json_path.unlink()
|
71 |
crdt_path.unlink()
|
72 |
|
73 |
|
74 |
@app.get("/api/load")
|
75 |
def load(path: str):
|
76 |
+
path = data_path / path
|
77 |
+
assert path.is_relative_to(data_path)
|
78 |
if not path.exists():
|
79 |
return workspace.Workspace()
|
80 |
return workspace.load(path)
|
|
|
87 |
|
88 |
@app.get("/api/dir/list")
|
89 |
def list_dir(path: str):
|
90 |
+
path = data_path / path
|
91 |
+
assert path.is_relative_to(data_path)
|
92 |
return sorted(
|
93 |
[
|
94 |
DirectoryEntry(
|
95 |
+
name=str(p.relative_to(data_path)),
|
96 |
type="directory" if p.is_dir() else "workspace",
|
97 |
)
|
98 |
for p in path.iterdir()
|
99 |
+
if not p.name.startswith(".")
|
100 |
],
|
101 |
key=lambda x: x.name,
|
102 |
)
|
|
|
104 |
|
105 |
@app.post("/api/dir/mkdir")
|
106 |
def make_dir(req: dict):
|
107 |
+
path = data_path / req["path"]
|
108 |
+
assert path.is_relative_to(data_path)
|
109 |
assert not path.exists(), f"{path} already exists"
|
110 |
path.mkdir()
|
111 |
|
112 |
|
113 |
@app.post("/api/dir/delete")
|
114 |
def delete_dir(req: dict):
|
115 |
+
path: pathlib.Path = data_path / req["path"]
|
116 |
+
assert all([path.is_relative_to(data_path), path.exists(), path.is_dir()])
|
117 |
shutil.rmtree(path)
|
118 |
|
119 |
|
|
|
136 |
"""Receives file uploads and stores them in DATA_PATH."""
|
137 |
form = await req.form()
|
138 |
for file in form.values():
|
139 |
+
file_path = data_path / "uploads" / file.filename
|
140 |
+
assert file_path.is_relative_to(data_path), "Invalid file path"
|
141 |
with file_path.open("wb") as buffer:
|
142 |
shutil.copyfileobj(file.file, buffer)
|
143 |
return {"status": "ok"}
|
lynxkite-app/tests/test_main.py
CHANGED
@@ -1,7 +1,7 @@
|
|
|
|
1 |
import uuid
|
2 |
from fastapi.testclient import TestClient
|
3 |
from lynxkite_app.main import app, detect_plugins
|
4 |
-
from lynxkite_app.config import DATA_PATH
|
5 |
import os
|
6 |
|
7 |
|
@@ -56,10 +56,9 @@ def test_save_and_load():
|
|
56 |
|
57 |
|
58 |
def test_list_dir():
|
59 |
-
test_dir = str(uuid.uuid4())
|
60 |
-
|
61 |
-
|
62 |
-
test_file = test_dir_full_path / "test_file.txt"
|
63 |
test_file.touch()
|
64 |
response = client.get(f"/api/dir/list?path={str(test_dir)}")
|
65 |
assert response.status_code == 200
|
@@ -67,12 +66,12 @@ def test_list_dir():
|
|
67 |
assert response.json()[0]["name"] == f"{test_dir}/test_file.txt"
|
68 |
assert response.json()[0]["type"] == "workspace"
|
69 |
test_file.unlink()
|
70 |
-
|
71 |
|
72 |
|
73 |
def test_make_dir():
|
74 |
dir_name = str(uuid.uuid4())
|
75 |
response = client.post("/api/dir/mkdir", json={"path": dir_name})
|
76 |
assert response.status_code == 200
|
77 |
-
assert os.path.exists(
|
78 |
-
os.rmdir(
|
|
|
1 |
+
import pathlib
|
2 |
import uuid
|
3 |
from fastapi.testclient import TestClient
|
4 |
from lynxkite_app.main import app, detect_plugins
|
|
|
5 |
import os
|
6 |
|
7 |
|
|
|
56 |
|
57 |
|
58 |
def test_list_dir():
|
59 |
+
test_dir = pathlib.Path() / str(uuid.uuid4())
|
60 |
+
test_dir.mkdir(parents=True, exist_ok=True)
|
61 |
+
test_file = test_dir / "test_file.txt"
|
|
|
62 |
test_file.touch()
|
63 |
response = client.get(f"/api/dir/list?path={str(test_dir)}")
|
64 |
assert response.status_code == 200
|
|
|
66 |
assert response.json()[0]["name"] == f"{test_dir}/test_file.txt"
|
67 |
assert response.json()[0]["type"] == "workspace"
|
68 |
test_file.unlink()
|
69 |
+
test_dir.rmdir()
|
70 |
|
71 |
|
72 |
def test_make_dir():
|
73 |
dir_name = str(uuid.uuid4())
|
74 |
response = client.post("/api/dir/mkdir", json={"path": dir_name})
|
75 |
assert response.status_code == 200
|
76 |
+
assert os.path.exists(dir_name)
|
77 |
+
os.rmdir(dir_name)
|
lynxkite-app/web/playwright.config.ts
CHANGED
@@ -23,7 +23,7 @@ export default defineConfig({
|
|
23 |
},
|
24 |
],
|
25 |
webServer: {
|
26 |
-
command: "cd
|
27 |
url: "http://127.0.0.1:8000",
|
28 |
reuseExistingServer: false,
|
29 |
},
|
|
|
23 |
},
|
24 |
],
|
25 |
webServer: {
|
26 |
+
command: "cd ../../examples && lynxkite",
|
27 |
url: "http://127.0.0.1:8000",
|
28 |
reuseExistingServer: false,
|
29 |
},
|
lynxkite-app/web/src/workspace/Workspace.tsx
CHANGED
@@ -324,7 +324,7 @@ function LynxKiteFlow() {
|
|
324 |
x: e.clientX,
|
325 |
y: e.clientY,
|
326 |
});
|
327 |
-
node.data!.params.file_path = file.name
|
328 |
if (file.name.includes(".csv")) {
|
329 |
node.data!.params.file_format = "csv";
|
330 |
} else if (file.name.includes(".parquet")) {
|
|
|
324 |
x: e.clientX,
|
325 |
y: e.clientY,
|
326 |
});
|
327 |
+
node.data!.params.file_path = `uploads/${file.name}`;
|
328 |
if (file.name.includes(".csv")) {
|
329 |
node.data!.params.file_format = "csv";
|
330 |
} else if (file.name.includes(".parquet")) {
|
lynxkite-core/src/lynxkite/core/workspace.py
CHANGED
@@ -105,7 +105,8 @@ def save(ws: Workspace, path: str):
|
|
105 |
j = ws.model_dump()
|
106 |
j = json.dumps(j, indent=2, sort_keys=True) + "\n"
|
107 |
dirname, basename = os.path.split(path)
|
108 |
-
|
|
|
109 |
# Create temp file in the same directory to make sure it's on the same filesystem.
|
110 |
with tempfile.NamedTemporaryFile(
|
111 |
"w", prefix=f".{basename}.", dir=dirname, delete=False
|
|
|
105 |
j = ws.model_dump()
|
106 |
j = json.dumps(j, indent=2, sort_keys=True) + "\n"
|
107 |
dirname, basename = os.path.split(path)
|
108 |
+
if dirname:
|
109 |
+
os.makedirs(dirname, exist_ok=True)
|
110 |
# Create temp file in the same directory to make sure it's on the same filesystem.
|
111 |
with tempfile.NamedTemporaryFile(
|
112 |
"w", prefix=f".{basename}.", dir=dirname, delete=False
|
lynxkite-lynxscribe/src/lynxkite_lynxscribe/lynxscribe_ops.py
CHANGED
@@ -2,7 +2,6 @@
|
|
2 |
LynxScribe configuration and testing in LynxKite.
|
3 |
"""
|
4 |
|
5 |
-
import os
|
6 |
import pathlib
|
7 |
from lynxscribe.core.llm.base import get_llm_engine
|
8 |
from lynxscribe.core.vector_store.base import get_vector_store
|
@@ -222,11 +221,12 @@ def view(input):
|
|
222 |
return v
|
223 |
|
224 |
|
225 |
-
async def get_chat_api(ws):
|
226 |
from lynxkite.core import workspace
|
227 |
|
228 |
-
|
229 |
-
|
|
|
230 |
assert path.exists(), f"Workspace {path} does not exist"
|
231 |
ws = workspace.load(path)
|
232 |
contexts = await ops.EXECUTORS[ENV](ws)
|
@@ -285,19 +285,16 @@ async def api_service_get(request):
|
|
285 |
return {"error": "Not found"}
|
286 |
|
287 |
|
288 |
-
DATA_PATH = pathlib.Path(os.environ.get("LYNXKITE_DATA", "lynxkite_data"))
|
289 |
-
|
290 |
-
|
291 |
def get_lynxscribe_workspaces():
|
292 |
from lynxkite.core import workspace
|
293 |
|
294 |
workspaces = []
|
295 |
-
for p in
|
296 |
if p.is_file():
|
297 |
try:
|
298 |
ws = workspace.load(p)
|
299 |
if ws.env == ENV:
|
300 |
-
workspaces.append(p
|
301 |
except Exception:
|
302 |
pass # Ignore files that are not valid workspaces.
|
303 |
workspaces.sort()
|
|
|
2 |
LynxScribe configuration and testing in LynxKite.
|
3 |
"""
|
4 |
|
|
|
5 |
import pathlib
|
6 |
from lynxscribe.core.llm.base import get_llm_engine
|
7 |
from lynxscribe.core.vector_store.base import get_vector_store
|
|
|
221 |
return v
|
222 |
|
223 |
|
224 |
+
async def get_chat_api(ws: str):
|
225 |
from lynxkite.core import workspace
|
226 |
|
227 |
+
cwd = pathlib.Path()
|
228 |
+
path = cwd / ws
|
229 |
+
assert path.is_relative_to(cwd)
|
230 |
assert path.exists(), f"Workspace {path} does not exist"
|
231 |
ws = workspace.load(path)
|
232 |
contexts = await ops.EXECUTORS[ENV](ws)
|
|
|
285 |
return {"error": "Not found"}
|
286 |
|
287 |
|
|
|
|
|
|
|
288 |
def get_lynxscribe_workspaces():
|
289 |
from lynxkite.core import workspace
|
290 |
|
291 |
workspaces = []
|
292 |
+
for p in pathlib.Path().glob("**/*"):
|
293 |
if p.is_file():
|
294 |
try:
|
295 |
ws = workspace.load(p)
|
296 |
if ws.env == ENV:
|
297 |
+
workspaces.append(p)
|
298 |
except Exception:
|
299 |
pass # Ignore files that are not valid workspaces.
|
300 |
workspaces.sort()
|