Spaces:
Running
Running
Run SQL on GPU with Polars.
Browse files- requirements.txt +15 -6
- run.sh +1 -0
- server/lynxkite_ops.py +86 -12
- web/package-lock.json +177 -4
- web/package.json +5 -0
- web/src/workspace/Workspace.tsx +2 -1
- web/src/workspace/nodes/LynxKiteNode.tsx +9 -12
- web/src/workspace/nodes/NodeWithTableView.tsx +3 -3
requirements.txt
CHANGED
@@ -1,15 +1,24 @@
|
|
|
|
1 |
fastapi
|
2 |
-
matplotlib
|
3 |
-
networkx
|
4 |
-
numpy
|
5 |
orjson
|
6 |
-
pandas
|
7 |
pydantic-to-typescript
|
8 |
-
scipy
|
9 |
uvicorn[standard]
|
10 |
pycrdt
|
11 |
pycrdt-websocket
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
chromadb
|
14 |
Jinja2
|
15 |
openai
|
|
|
1 |
+
# Dependencies for basic operation.
|
2 |
fastapi
|
|
|
|
|
|
|
3 |
orjson
|
|
|
4 |
pydantic-to-typescript
|
|
|
5 |
uvicorn[standard]
|
6 |
pycrdt
|
7 |
pycrdt-websocket
|
8 |
+
|
9 |
+
# For lynxkite_ops.
|
10 |
+
matplotlib
|
11 |
+
networkx
|
12 |
+
numpy
|
13 |
+
pandas
|
14 |
+
scipy
|
15 |
+
|
16 |
+
# GPU-accelerated graph analytics.
|
17 |
+
cugraph-cu12
|
18 |
+
cudf-cu12
|
19 |
+
nx-cugraph-cu12
|
20 |
+
|
21 |
+
# For llm_ops.
|
22 |
chromadb
|
23 |
Jinja2
|
24 |
openai
|
run.sh
CHANGED
@@ -1,2 +1,3 @@
|
|
1 |
#!/bin/bash -xue
|
|
|
2 |
uvicorn server.main:app --reload
|
|
|
1 |
#!/bin/bash -xue
|
2 |
+
export NX_CUGRAPH_AUTOCONFIG=True
|
3 |
uvicorn server.main:app --reload
|
server/lynxkite_ops.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
"""
|
2 |
|
|
|
3 |
from . import ops
|
4 |
from collections import deque
|
5 |
import dataclasses
|
@@ -7,6 +8,7 @@ import functools
|
|
7 |
import matplotlib
|
8 |
import networkx as nx
|
9 |
import pandas as pd
|
|
|
10 |
import traceback
|
11 |
import typing
|
12 |
|
@@ -63,13 +65,27 @@ class Bundle:
|
|
63 |
],
|
64 |
)
|
65 |
|
|
|
|
|
|
|
|
|
66 |
def to_nx(self):
|
|
|
67 |
graph = nx.from_pandas_edgelist(self.dfs["edges"])
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
71 |
return graph
|
72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
def nx_node_attribute_func(name):
|
75 |
"""Decorator for wrapping a function that adds a NetworkX node attribute."""
|
@@ -99,7 +115,6 @@ def disambiguate_edges(ws):
|
|
99 |
@ops.register_executor("LynxKite")
|
100 |
async def execute(ws):
|
101 |
catalog = ops.CATALOGS["LynxKite"]
|
102 |
-
# Nodes are responsible for interpreting/executing their child nodes.
|
103 |
disambiguate_edges(ws)
|
104 |
outputs = {}
|
105 |
failed = 0
|
@@ -115,12 +130,14 @@ async def execute(ws):
|
|
115 |
op = catalog[data.title]
|
116 |
params = {**data.params}
|
117 |
# Convert inputs.
|
118 |
-
for i, (x, p) in enumerate(zip(inputs, op.inputs.values())):
|
119 |
-
if p.type == nx.Graph and isinstance(x, Bundle):
|
120 |
-
inputs[i] = x.to_nx()
|
121 |
-
elif p.type == Bundle and isinstance(x, nx.Graph):
|
122 |
-
inputs[i] = Bundle.from_nx(x)
|
123 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
output = op(*inputs, **params)
|
125 |
except Exception as e:
|
126 |
traceback.print_exc()
|
@@ -142,10 +159,24 @@ async def execute(ws):
|
|
142 |
|
143 |
@op("Import Parquet")
|
144 |
def import_parquet(*, filename: str):
|
145 |
-
"""Imports a
|
146 |
return pd.read_parquet(filename)
|
147 |
|
148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
149 |
@op("Create scale-free graph")
|
150 |
def create_scale_free_graph(*, nodes: int = 10):
|
151 |
"""Creates a scale-free graph with the given number of nodes."""
|
@@ -158,6 +189,12 @@ def compute_pagerank(graph: nx.Graph, *, damping=0.85, iterations=100):
|
|
158 |
return nx.pagerank(graph, alpha=damping, max_iter=iterations)
|
159 |
|
160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
@op("Discard loop edges")
|
162 |
def discard_loop_edges(graph: nx.Graph):
|
163 |
graph = graph.copy()
|
@@ -165,6 +202,35 @@ def discard_loop_edges(graph: nx.Graph):
|
|
165 |
return graph
|
166 |
|
167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
@op("Sample graph")
|
169 |
def sample_graph(graph: nx.Graph, *, nodes: int = 100):
|
170 |
"""Takes a (preferably connected) subgraph."""
|
@@ -237,13 +303,21 @@ def visualize_graph(graph: Bundle, *, color_nodes_by: ops.NodeAttribute = None):
|
|
237 |
return v
|
238 |
|
239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
@op("View tables", view="table_view")
|
241 |
def view_tables(bundle: Bundle):
|
242 |
v = {
|
243 |
"dataframes": {
|
244 |
name: {
|
245 |
"columns": [str(c) for c in df.columns],
|
246 |
-
"data": df
|
247 |
}
|
248 |
for name, df in bundle.dfs.items()
|
249 |
},
|
|
|
1 |
+
"""Graph analytics operations. To be split into separate files when we have more."""
|
2 |
|
3 |
+
import os
|
4 |
from . import ops
|
5 |
from collections import deque
|
6 |
import dataclasses
|
|
|
8 |
import matplotlib
|
9 |
import networkx as nx
|
10 |
import pandas as pd
|
11 |
+
import polars as pl
|
12 |
import traceback
|
13 |
import typing
|
14 |
|
|
|
65 |
],
|
66 |
)
|
67 |
|
68 |
+
@classmethod
|
69 |
+
def from_df(cls, df: pd.DataFrame):
|
70 |
+
return cls(dfs={"df": df})
|
71 |
+
|
72 |
def to_nx(self):
|
73 |
+
# TODO: Use relations.
|
74 |
graph = nx.from_pandas_edgelist(self.dfs["edges"])
|
75 |
+
if "nodes" in self.dfs:
|
76 |
+
nx.set_node_attributes(
|
77 |
+
graph, self.dfs["nodes"].set_index("id").to_dict("index")
|
78 |
+
)
|
79 |
return graph
|
80 |
|
81 |
+
def copy(self):
|
82 |
+
"""Returns a medium depth copy of the bundle. The Bundle is completely new, but the DataFrames and RelationDefinitions are shared."""
|
83 |
+
return Bundle(
|
84 |
+
dfs=dict(self.dfs),
|
85 |
+
relations=list(self.relations),
|
86 |
+
other=dict(self.other) if self.other else None,
|
87 |
+
)
|
88 |
+
|
89 |
|
90 |
def nx_node_attribute_func(name):
|
91 |
"""Decorator for wrapping a function that adds a NetworkX node attribute."""
|
|
|
115 |
@ops.register_executor("LynxKite")
|
116 |
async def execute(ws):
|
117 |
catalog = ops.CATALOGS["LynxKite"]
|
|
|
118 |
disambiguate_edges(ws)
|
119 |
outputs = {}
|
120 |
failed = 0
|
|
|
130 |
op = catalog[data.title]
|
131 |
params = {**data.params}
|
132 |
# Convert inputs.
|
|
|
|
|
|
|
|
|
|
|
133 |
try:
|
134 |
+
for i, (x, p) in enumerate(zip(inputs, op.inputs.values())):
|
135 |
+
if p.type == nx.Graph and isinstance(x, Bundle):
|
136 |
+
inputs[i] = x.to_nx()
|
137 |
+
elif p.type == Bundle and isinstance(x, nx.Graph):
|
138 |
+
inputs[i] = Bundle.from_nx(x)
|
139 |
+
elif p.type == Bundle and isinstance(x, pd.DataFrame):
|
140 |
+
inputs[i] = Bundle.from_df(x)
|
141 |
output = op(*inputs, **params)
|
142 |
except Exception as e:
|
143 |
traceback.print_exc()
|
|
|
159 |
|
160 |
@op("Import Parquet")
|
161 |
def import_parquet(*, filename: str):
|
162 |
+
"""Imports a Parquet file."""
|
163 |
return pd.read_parquet(filename)
|
164 |
|
165 |
|
166 |
+
@op("Import CSV")
|
167 |
+
def import_csv(
|
168 |
+
*, filename: str, columns: str = "<from file>", separator: str = "<auto>"
|
169 |
+
):
|
170 |
+
"""Imports a CSV file."""
|
171 |
+
return pd.read_csv(
|
172 |
+
filename,
|
173 |
+
names=pd.api.extensions.no_default
|
174 |
+
if columns == "<from file>"
|
175 |
+
else columns.split(","),
|
176 |
+
sep=pd.api.extensions.no_default if separator == "<auto>" else separator,
|
177 |
+
)
|
178 |
+
|
179 |
+
|
180 |
@op("Create scale-free graph")
|
181 |
def create_scale_free_graph(*, nodes: int = 10):
|
182 |
"""Creates a scale-free graph with the given number of nodes."""
|
|
|
189 |
return nx.pagerank(graph, alpha=damping, max_iter=iterations)
|
190 |
|
191 |
|
192 |
+
@op("Compute betweenness centrality")
|
193 |
+
@nx_node_attribute_func("betweenness_centrality")
|
194 |
+
def compute_betweenness_centrality(graph: nx.Graph, *, k=10):
|
195 |
+
return nx.betweenness_centrality(graph, k=k, backend="cugraph")
|
196 |
+
|
197 |
+
|
198 |
@op("Discard loop edges")
|
199 |
def discard_loop_edges(graph: nx.Graph):
|
200 |
graph = graph.copy()
|
|
|
202 |
return graph
|
203 |
|
204 |
|
205 |
+
@op("SQL")
|
206 |
+
def sql(bundle: Bundle, *, query: ops.LongStr, save_as: str = "result"):
|
207 |
+
"""Run a SQL query on the DataFrames in the bundle. Save the results as a new DataFrame."""
|
208 |
+
bundle = bundle.copy()
|
209 |
+
if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true":
|
210 |
+
with pl.Config() as cfg:
|
211 |
+
cfg.set_verbose(True)
|
212 |
+
res = (
|
213 |
+
pl.SQLContext(bundle.dfs)
|
214 |
+
.execute(query)
|
215 |
+
.collect(engine="gpu")
|
216 |
+
.to_pandas()
|
217 |
+
)
|
218 |
+
# TODO: Currently `collect()` moves the data from cuDF to Polars. Then we convert it to Pandas,
|
219 |
+
# which (hopefully) puts it back into cuDF. Hopefully we will be able to keep it in cuDF.
|
220 |
+
else:
|
221 |
+
res = pl.SQLContext(bundle.dfs).execute(query)
|
222 |
+
bundle.dfs[save_as] = res
|
223 |
+
return bundle
|
224 |
+
|
225 |
+
|
226 |
+
@op("Organize bundle")
|
227 |
+
def organize_bundle(bundle: Bundle, *, code: ops.LongStr):
|
228 |
+
"""Lets you rename/copy/delete DataFrames, and modify relations. TODO: Use a declarative solution instead of Python code. Add UI."""
|
229 |
+
bundle = bundle.copy()
|
230 |
+
exec(code, globals(), {"bundle": bundle})
|
231 |
+
return bundle
|
232 |
+
|
233 |
+
|
234 |
@op("Sample graph")
|
235 |
def sample_graph(graph: nx.Graph, *, nodes: int = 100):
|
236 |
"""Takes a (preferably connected) subgraph."""
|
|
|
303 |
return v
|
304 |
|
305 |
|
306 |
+
def collect(df: pd.DataFrame):
|
307 |
+
if isinstance(df, pl.LazyFrame):
|
308 |
+
df = df.collect()
|
309 |
+
if isinstance(df, pl.DataFrame):
|
310 |
+
return [[d[c] for c in df.columns] for d in df.to_dicts()]
|
311 |
+
return df.values.tolist()
|
312 |
+
|
313 |
+
|
314 |
@op("View tables", view="table_view")
|
315 |
def view_tables(bundle: Bundle):
|
316 |
v = {
|
317 |
"dataframes": {
|
318 |
name: {
|
319 |
"columns": [str(c) for c in df.columns],
|
320 |
+
"data": collect(df),
|
321 |
}
|
322 |
for name, df in bundle.dfs.items()
|
323 |
},
|
web/package-lock.json
CHANGED
@@ -8,9 +8,11 @@
|
|
8 |
"name": "lynxkite",
|
9 |
"version": "0.0.0",
|
10 |
"dependencies": {
|
|
|
11 |
"@iconify-json/tabler": "^1.2.10",
|
12 |
"@svgr/core": "^8.1.0",
|
13 |
"@svgr/plugin-jsx": "^8.1.0",
|
|
|
14 |
"@syncedstore/core": "^0.6.0",
|
15 |
"@syncedstore/react": "^0.6.0",
|
16 |
"@types/node": "^22.10.1",
|
@@ -42,6 +44,9 @@
|
|
42 |
"typescript": "~5.6.2",
|
43 |
"typescript-eslint": "^8.15.0",
|
44 |
"vite": "^6.0.1"
|
|
|
|
|
|
|
45 |
}
|
46 |
},
|
47 |
"node_modules/@alloc/quick-lru": {
|
@@ -351,6 +356,21 @@
|
|
351 |
"node": ">=18"
|
352 |
}
|
353 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
354 |
"node_modules/@eslint-community/eslint-utils": {
|
355 |
"version": "4.4.1",
|
356 |
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
|
@@ -748,6 +768,19 @@
|
|
748 |
"darwin"
|
749 |
]
|
750 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
751 |
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
752 |
"version": "8.0.0",
|
753 |
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
@@ -965,7 +998,6 @@
|
|
965 |
"version": "1.10.1",
|
966 |
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz",
|
967 |
"integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==",
|
968 |
-
"dev": true,
|
969 |
"hasInstallScript": true,
|
970 |
"license": "Apache-2.0",
|
971 |
"dependencies": {
|
@@ -1007,7 +1039,6 @@
|
|
1007 |
"cpu": [
|
1008 |
"arm64"
|
1009 |
],
|
1010 |
-
"dev": true,
|
1011 |
"license": "Apache-2.0 AND MIT",
|
1012 |
"optional": true,
|
1013 |
"os": [
|
@@ -1017,18 +1048,160 @@
|
|
1017 |
"node": ">=10"
|
1018 |
}
|
1019 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1020 |
"node_modules/@swc/counter": {
|
1021 |
"version": "0.1.3",
|
1022 |
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
1023 |
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
1024 |
-
"dev": true,
|
1025 |
"license": "Apache-2.0"
|
1026 |
},
|
1027 |
"node_modules/@swc/types": {
|
1028 |
"version": "0.1.17",
|
1029 |
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz",
|
1030 |
"integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==",
|
1031 |
-
"dev": true,
|
1032 |
"license": "Apache-2.0",
|
1033 |
"dependencies": {
|
1034 |
"@swc/counter": "^0.1.3"
|
|
|
8 |
"name": "lynxkite",
|
9 |
"version": "0.0.0",
|
10 |
"dependencies": {
|
11 |
+
"@esbuild/linux-x64": "^0.24.0",
|
12 |
"@iconify-json/tabler": "^1.2.10",
|
13 |
"@svgr/core": "^8.1.0",
|
14 |
"@svgr/plugin-jsx": "^8.1.0",
|
15 |
+
"@swc/core": "^1.10.1",
|
16 |
"@syncedstore/core": "^0.6.0",
|
17 |
"@syncedstore/react": "^0.6.0",
|
18 |
"@types/node": "^22.10.1",
|
|
|
44 |
"typescript": "~5.6.2",
|
45 |
"typescript-eslint": "^8.15.0",
|
46 |
"vite": "^6.0.1"
|
47 |
+
},
|
48 |
+
"optionalDependencies": {
|
49 |
+
"@rollup/rollup-linux-x64-gnu": "^4.28.1"
|
50 |
}
|
51 |
},
|
52 |
"node_modules/@alloc/quick-lru": {
|
|
|
356 |
"node": ">=18"
|
357 |
}
|
358 |
},
|
359 |
+
"node_modules/@esbuild/linux-x64": {
|
360 |
+
"version": "0.24.0",
|
361 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
|
362 |
+
"integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
|
363 |
+
"cpu": [
|
364 |
+
"x64"
|
365 |
+
],
|
366 |
+
"license": "MIT",
|
367 |
+
"os": [
|
368 |
+
"linux"
|
369 |
+
],
|
370 |
+
"engines": {
|
371 |
+
"node": ">=18"
|
372 |
+
}
|
373 |
+
},
|
374 |
"node_modules/@eslint-community/eslint-utils": {
|
375 |
"version": "4.4.1",
|
376 |
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
|
|
|
768 |
"darwin"
|
769 |
]
|
770 |
},
|
771 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
772 |
+
"version": "4.28.1",
|
773 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
|
774 |
+
"integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
|
775 |
+
"cpu": [
|
776 |
+
"x64"
|
777 |
+
],
|
778 |
+
"license": "MIT",
|
779 |
+
"optional": true,
|
780 |
+
"os": [
|
781 |
+
"linux"
|
782 |
+
]
|
783 |
+
},
|
784 |
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
785 |
"version": "8.0.0",
|
786 |
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
|
|
998 |
"version": "1.10.1",
|
999 |
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz",
|
1000 |
"integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==",
|
|
|
1001 |
"hasInstallScript": true,
|
1002 |
"license": "Apache-2.0",
|
1003 |
"dependencies": {
|
|
|
1039 |
"cpu": [
|
1040 |
"arm64"
|
1041 |
],
|
|
|
1042 |
"license": "Apache-2.0 AND MIT",
|
1043 |
"optional": true,
|
1044 |
"os": [
|
|
|
1048 |
"node": ">=10"
|
1049 |
}
|
1050 |
},
|
1051 |
+
"node_modules/@swc/core-darwin-x64": {
|
1052 |
+
"version": "1.10.1",
|
1053 |
+
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz",
|
1054 |
+
"integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==",
|
1055 |
+
"cpu": [
|
1056 |
+
"x64"
|
1057 |
+
],
|
1058 |
+
"license": "Apache-2.0 AND MIT",
|
1059 |
+
"optional": true,
|
1060 |
+
"os": [
|
1061 |
+
"darwin"
|
1062 |
+
],
|
1063 |
+
"engines": {
|
1064 |
+
"node": ">=10"
|
1065 |
+
}
|
1066 |
+
},
|
1067 |
+
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
1068 |
+
"version": "1.10.1",
|
1069 |
+
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz",
|
1070 |
+
"integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==",
|
1071 |
+
"cpu": [
|
1072 |
+
"arm"
|
1073 |
+
],
|
1074 |
+
"license": "Apache-2.0",
|
1075 |
+
"optional": true,
|
1076 |
+
"os": [
|
1077 |
+
"linux"
|
1078 |
+
],
|
1079 |
+
"engines": {
|
1080 |
+
"node": ">=10"
|
1081 |
+
}
|
1082 |
+
},
|
1083 |
+
"node_modules/@swc/core-linux-arm64-gnu": {
|
1084 |
+
"version": "1.10.1",
|
1085 |
+
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz",
|
1086 |
+
"integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==",
|
1087 |
+
"cpu": [
|
1088 |
+
"arm64"
|
1089 |
+
],
|
1090 |
+
"license": "Apache-2.0 AND MIT",
|
1091 |
+
"optional": true,
|
1092 |
+
"os": [
|
1093 |
+
"linux"
|
1094 |
+
],
|
1095 |
+
"engines": {
|
1096 |
+
"node": ">=10"
|
1097 |
+
}
|
1098 |
+
},
|
1099 |
+
"node_modules/@swc/core-linux-arm64-musl": {
|
1100 |
+
"version": "1.10.1",
|
1101 |
+
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz",
|
1102 |
+
"integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==",
|
1103 |
+
"cpu": [
|
1104 |
+
"arm64"
|
1105 |
+
],
|
1106 |
+
"license": "Apache-2.0 AND MIT",
|
1107 |
+
"optional": true,
|
1108 |
+
"os": [
|
1109 |
+
"linux"
|
1110 |
+
],
|
1111 |
+
"engines": {
|
1112 |
+
"node": ">=10"
|
1113 |
+
}
|
1114 |
+
},
|
1115 |
+
"node_modules/@swc/core-linux-x64-gnu": {
|
1116 |
+
"version": "1.10.1",
|
1117 |
+
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz",
|
1118 |
+
"integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==",
|
1119 |
+
"cpu": [
|
1120 |
+
"x64"
|
1121 |
+
],
|
1122 |
+
"license": "Apache-2.0 AND MIT",
|
1123 |
+
"optional": true,
|
1124 |
+
"os": [
|
1125 |
+
"linux"
|
1126 |
+
],
|
1127 |
+
"engines": {
|
1128 |
+
"node": ">=10"
|
1129 |
+
}
|
1130 |
+
},
|
1131 |
+
"node_modules/@swc/core-linux-x64-musl": {
|
1132 |
+
"version": "1.10.1",
|
1133 |
+
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz",
|
1134 |
+
"integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==",
|
1135 |
+
"cpu": [
|
1136 |
+
"x64"
|
1137 |
+
],
|
1138 |
+
"license": "Apache-2.0 AND MIT",
|
1139 |
+
"optional": true,
|
1140 |
+
"os": [
|
1141 |
+
"linux"
|
1142 |
+
],
|
1143 |
+
"engines": {
|
1144 |
+
"node": ">=10"
|
1145 |
+
}
|
1146 |
+
},
|
1147 |
+
"node_modules/@swc/core-win32-arm64-msvc": {
|
1148 |
+
"version": "1.10.1",
|
1149 |
+
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz",
|
1150 |
+
"integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==",
|
1151 |
+
"cpu": [
|
1152 |
+
"arm64"
|
1153 |
+
],
|
1154 |
+
"license": "Apache-2.0 AND MIT",
|
1155 |
+
"optional": true,
|
1156 |
+
"os": [
|
1157 |
+
"win32"
|
1158 |
+
],
|
1159 |
+
"engines": {
|
1160 |
+
"node": ">=10"
|
1161 |
+
}
|
1162 |
+
},
|
1163 |
+
"node_modules/@swc/core-win32-ia32-msvc": {
|
1164 |
+
"version": "1.10.1",
|
1165 |
+
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz",
|
1166 |
+
"integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==",
|
1167 |
+
"cpu": [
|
1168 |
+
"ia32"
|
1169 |
+
],
|
1170 |
+
"license": "Apache-2.0 AND MIT",
|
1171 |
+
"optional": true,
|
1172 |
+
"os": [
|
1173 |
+
"win32"
|
1174 |
+
],
|
1175 |
+
"engines": {
|
1176 |
+
"node": ">=10"
|
1177 |
+
}
|
1178 |
+
},
|
1179 |
+
"node_modules/@swc/core-win32-x64-msvc": {
|
1180 |
+
"version": "1.10.1",
|
1181 |
+
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz",
|
1182 |
+
"integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==",
|
1183 |
+
"cpu": [
|
1184 |
+
"x64"
|
1185 |
+
],
|
1186 |
+
"license": "Apache-2.0 AND MIT",
|
1187 |
+
"optional": true,
|
1188 |
+
"os": [
|
1189 |
+
"win32"
|
1190 |
+
],
|
1191 |
+
"engines": {
|
1192 |
+
"node": ">=10"
|
1193 |
+
}
|
1194 |
+
},
|
1195 |
"node_modules/@swc/counter": {
|
1196 |
"version": "0.1.3",
|
1197 |
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
1198 |
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
|
|
1199 |
"license": "Apache-2.0"
|
1200 |
},
|
1201 |
"node_modules/@swc/types": {
|
1202 |
"version": "0.1.17",
|
1203 |
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz",
|
1204 |
"integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==",
|
|
|
1205 |
"license": "Apache-2.0",
|
1206 |
"dependencies": {
|
1207 |
"@swc/counter": "^0.1.3"
|
web/package.json
CHANGED
@@ -10,9 +10,11 @@
|
|
10 |
"preview": "vite preview"
|
11 |
},
|
12 |
"dependencies": {
|
|
|
13 |
"@iconify-json/tabler": "^1.2.10",
|
14 |
"@svgr/core": "^8.1.0",
|
15 |
"@svgr/plugin-jsx": "^8.1.0",
|
|
|
16 |
"@syncedstore/core": "^0.6.0",
|
17 |
"@syncedstore/react": "^0.6.0",
|
18 |
"@types/node": "^22.10.1",
|
@@ -44,5 +46,8 @@
|
|
44 |
"typescript": "~5.6.2",
|
45 |
"typescript-eslint": "^8.15.0",
|
46 |
"vite": "^6.0.1"
|
|
|
|
|
|
|
47 |
}
|
48 |
}
|
|
|
10 |
"preview": "vite preview"
|
11 |
},
|
12 |
"dependencies": {
|
13 |
+
"@esbuild/linux-x64": "^0.24.0",
|
14 |
"@iconify-json/tabler": "^1.2.10",
|
15 |
"@svgr/core": "^8.1.0",
|
16 |
"@svgr/plugin-jsx": "^8.1.0",
|
17 |
+
"@swc/core": "^1.10.1",
|
18 |
"@syncedstore/core": "^0.6.0",
|
19 |
"@syncedstore/react": "^0.6.0",
|
20 |
"@types/node": "^22.10.1",
|
|
|
46 |
"typescript": "~5.6.2",
|
47 |
"typescript-eslint": "^8.15.0",
|
48 |
"vite": "^6.0.1"
|
49 |
+
},
|
50 |
+
"optionalDependencies": {
|
51 |
+
"@rollup/rollup-linux-x64-gnu": "^4.28.1"
|
52 |
}
|
53 |
}
|
web/src/workspace/Workspace.tsx
CHANGED
@@ -57,7 +57,7 @@ function LynxKiteFlow() {
|
|
57 |
const state = syncedStore({ workspace: {} as Workspace });
|
58 |
setState(state);
|
59 |
const doc = getYjsDoc(state);
|
60 |
-
const wsProvider = new WebsocketProvider("ws://localhost:
|
61 |
const onChange = (_update: any, origin: any, _doc: any, _tr: any) => {
|
62 |
if (origin === wsProvider) {
|
63 |
// An update from the CRDT. Apply it to the local state.
|
@@ -125,6 +125,7 @@ function LynxKiteFlow() {
|
|
125 |
const edgeIndex = wedges.findIndex((e) => e.id === ch.id);
|
126 |
if (ch.type === 'remove') {
|
127 |
wedges.splice(edgeIndex, 1);
|
|
|
128 |
} else {
|
129 |
console.log('Unknown edge change', ch);
|
130 |
}
|
|
|
57 |
const state = syncedStore({ workspace: {} as Workspace });
|
58 |
setState(state);
|
59 |
const doc = getYjsDoc(state);
|
60 |
+
const wsProvider = new WebsocketProvider("ws://localhost:5173/ws/crdt", path!, doc);
|
61 |
const onChange = (_update: any, origin: any, _doc: any, _tr: any) => {
|
62 |
if (origin === wsProvider) {
|
63 |
// An update from the CRDT. Apply it to the local state.
|
|
|
125 |
const edgeIndex = wedges.findIndex((e) => e.id === ch.id);
|
126 |
if (ch.type === 'remove') {
|
127 |
wedges.splice(edgeIndex, 1);
|
128 |
+
} else if (ch.type === 'select') {
|
129 |
} else {
|
130 |
console.log('Unknown edge change', ch);
|
131 |
}
|
web/src/workspace/nodes/LynxKiteNode.tsx
CHANGED
@@ -45,9 +45,6 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
|
|
45 |
const data = props.data;
|
46 |
const expanded = !data.collapsed;
|
47 |
const handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {});
|
48 |
-
function asPx(n: number | undefined) {
|
49 |
-
return (n ? n + 'px' : undefined) || '200px';
|
50 |
-
}
|
51 |
function titleClicked() {
|
52 |
reactFlow.updateNodeData(props.id, { collapsed: expanded });
|
53 |
}
|
@@ -55,7 +52,7 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
|
|
55 |
|
56 |
return (
|
57 |
<div className={'node-container ' + (expanded ? 'expanded' : 'collapsed')}
|
58 |
-
style={{ width:
|
59 |
<div className="lynxkite-node" style={props.nodeStyle}>
|
60 |
<div className="title bg-primary" onClick={titleClicked}>
|
61 |
{data.title}
|
@@ -67,14 +64,6 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
|
|
67 |
<div className="error">{data.error}</div>
|
68 |
}
|
69 |
{props.children}
|
70 |
-
{handles.map(handle => (
|
71 |
-
<Handle
|
72 |
-
key={handle.name}
|
73 |
-
id={handle.name} type={handle.type} position={handle.position as Position}
|
74 |
-
style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
|
75 |
-
{handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
|
76 |
-
</Handle >
|
77 |
-
))}
|
78 |
<NodeResizeControl
|
79 |
minWidth={100}
|
80 |
minHeight={50}
|
@@ -83,6 +72,14 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
|
|
83 |
<ChevronDownRight className="node-resizer" />
|
84 |
</NodeResizeControl>
|
85 |
</>}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
</div>
|
87 |
</div>
|
88 |
);
|
|
|
45 |
const data = props.data;
|
46 |
const expanded = !data.collapsed;
|
47 |
const handles = getHandles(data.meta?.inputs || {}, data.meta?.outputs || {});
|
|
|
|
|
|
|
48 |
function titleClicked() {
|
49 |
reactFlow.updateNodeData(props.id, { collapsed: expanded });
|
50 |
}
|
|
|
52 |
|
53 |
return (
|
54 |
<div className={'node-container ' + (expanded ? 'expanded' : 'collapsed')}
|
55 |
+
style={{ width: props.width || 200, height: expanded ? props.height || 200 : undefined }}>
|
56 |
<div className="lynxkite-node" style={props.nodeStyle}>
|
57 |
<div className="title bg-primary" onClick={titleClicked}>
|
58 |
{data.title}
|
|
|
64 |
<div className="error">{data.error}</div>
|
65 |
}
|
66 |
{props.children}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
<NodeResizeControl
|
68 |
minWidth={100}
|
69 |
minHeight={50}
|
|
|
72 |
<ChevronDownRight className="node-resizer" />
|
73 |
</NodeResizeControl>
|
74 |
</>}
|
75 |
+
{handles.map(handle => (
|
76 |
+
<Handle
|
77 |
+
key={handle.name}
|
78 |
+
id={handle.name} type={handle.type} position={handle.position as Position}
|
79 |
+
style={{ [handleOffsetDirection[handle.position]]: handle.offsetPercentage + '%' }}>
|
80 |
+
{handle.showLabel && <span className="handle-name">{handle.name.replace(/_/g, " ")}</span>}
|
81 |
+
</Handle >
|
82 |
+
))}
|
83 |
</div>
|
84 |
</div>
|
85 |
);
|
web/src/workspace/nodes/NodeWithTableView.tsx
CHANGED
@@ -22,12 +22,12 @@ export default function NodeWithTableView(props: any) {
|
|
22 |
<LynxKiteNode {...props}>
|
23 |
{display && [
|
24 |
Object.entries(display.dataframes || {}).map(([name, df]: [string, any]) => <React.Fragment key={name}>
|
25 |
-
{!single && <div key={name} className="df-head" onClick={() => setOpen({ ...open, [name]: !open[name] })}>{name}</div>}
|
26 |
{(single || open[name]) &&
|
27 |
(df.data.length > 1 ?
|
28 |
-
<Table key={name} columns={df.columns} data={df.data} />
|
29 |
:
|
30 |
-
<dl key={name}>
|
31 |
{df.columns.map((c: string, i: number) =>
|
32 |
<React.Fragment key={name + '-' + c}>
|
33 |
<dt>{c}</dt>
|
|
|
22 |
<LynxKiteNode {...props}>
|
23 |
{display && [
|
24 |
Object.entries(display.dataframes || {}).map(([name, df]: [string, any]) => <React.Fragment key={name}>
|
25 |
+
{!single && <div key={name + '-header'} className="df-head" onClick={() => setOpen({ ...open, [name]: !open[name] })}>{name}</div>}
|
26 |
{(single || open[name]) &&
|
27 |
(df.data.length > 1 ?
|
28 |
+
<Table key={name + '-table'} columns={df.columns} data={df.data} />
|
29 |
:
|
30 |
+
<dl key={name + '-dl'}>
|
31 |
{df.columns.map((c: string, i: number) =>
|
32 |
<React.Fragment key={name + '-' + c}>
|
33 |
<dt>{c}</dt>
|