darabos commited on
Commit
cd2383d
Β·
1 Parent(s): c5aaf53

Open code files from directory + related stuff.

Browse files
biome.json CHANGED
@@ -1,4 +1,7 @@
1
  {
 
 
 
2
  "formatter": {
3
  "ignore": ["**/node_modules/**", "**/dist/**"],
4
  "lineWidth": 100,
@@ -21,6 +24,7 @@
21
  "useKeyWithClickEvents": "off",
22
  "useValidAnchor": "off",
23
  "useButtonType": "off",
 
24
  "noNoninteractiveTabindex": "off"
25
  }
26
  }
 
1
  {
2
+ "files": {
3
+ "ignore": ["**/*.lynxkite.json"]
4
+ },
5
  "formatter": {
6
  "ignore": ["**/node_modules/**", "**/dist/**"],
7
  "lineWidth": 100,
 
24
  "useKeyWithClickEvents": "off",
25
  "useValidAnchor": "off",
26
  "useButtonType": "off",
27
+ "noAutofocus": "off",
28
  "noNoninteractiveTabindex": "off"
29
  }
30
  }
examples/{Airlines demo β†’ Airlines demo.lynxkite.json} RENAMED
File without changes
examples/{Image processing β†’ Image processing.lynxkite.json} RENAMED
File without changes
examples/{Model definition β†’ Model definition.lynxkite.json} RENAMED
File without changes
examples/{Model use β†’ Model use.lynxkite.json} RENAMED
File without changes
examples/{NetworkX demo β†’ NetworkX demo.lynxkite.json} RENAMED
File without changes
examples/ODE-GNN experiment DELETED
@@ -1,466 +0,0 @@
1
- {
2
- "edges": [
3
- {
4
- "id": "Import CSV 1 Train/test split 1",
5
- "source": "Import CSV 1",
6
- "sourceHandle": "output",
7
- "target": "Train/test split 1",
8
- "targetHandle": "bundle"
9
- },
10
- {
11
- "id": "Train/test split 1 Create graph 1",
12
- "source": "Train/test split 1",
13
- "sourceHandle": "output",
14
- "target": "Create graph 1",
15
- "targetHandle": "bundle"
16
- },
17
- {
18
- "id": "Biomedical foundation graph (PLACEHOLDER) 1 Create graph 1",
19
- "source": "Biomedical foundation graph (PLACEHOLDER) 1",
20
- "sourceHandle": "output",
21
- "target": "Create graph 1",
22
- "targetHandle": "bundle"
23
- },
24
- {
25
- "id": "Define model 1 Create graph 1",
26
- "source": "Define model 1",
27
- "sourceHandle": "output",
28
- "target": "Create graph 1",
29
- "targetHandle": "bundle"
30
- },
31
- {
32
- "id": "Create graph 1 Train model 1",
33
- "source": "Create graph 1",
34
- "sourceHandle": "output",
35
- "target": "Train model 1",
36
- "targetHandle": "bundle"
37
- },
38
- {
39
- "id": "Train model 1 Model inference 1",
40
- "source": "Train model 1",
41
- "sourceHandle": "output",
42
- "target": "Model inference 1",
43
- "targetHandle": "bundle"
44
- }
45
- ],
46
- "env": "LynxKite Graph Analytics",
47
- "nodes": [
48
- {
49
- "data": {
50
- "__execution_delay": 0.0,
51
- "collapsed": null,
52
- "display": null,
53
- "error": null,
54
- "meta": {
55
- "inputs": {},
56
- "name": "Biomedical foundation graph (PLACEHOLDER)",
57
- "outputs": {
58
- "output": {
59
- "name": "output",
60
- "position": "right",
61
- "type": {
62
- "type": "None"
63
- }
64
- }
65
- },
66
- "params": {
67
- "filter_nodes": {
68
- "default": null,
69
- "name": "filter_nodes",
70
- "type": {
71
- "type": "<class 'str'>"
72
- }
73
- }
74
- },
75
- "type": "basic"
76
- },
77
- "params": {
78
- "filter_nodes": "drug,gene,disease"
79
- },
80
- "status": "done",
81
- "title": "Biomedical foundation graph (PLACEHOLDER)"
82
- },
83
- "dragHandle": ".bg-primary",
84
- "height": 200.0,
85
- "id": "Biomedical foundation graph (PLACEHOLDER) 1",
86
- "position": {
87
- "x": 230.1082040835347,
88
- "y": 643.2454063689602
89
- },
90
- "type": "basic",
91
- "width": 200.0
92
- },
93
- {
94
- "data": {
95
- "__execution_delay": null,
96
- "collapsed": true,
97
- "display": null,
98
- "error": "Missing input: bundle",
99
- "meta": {
100
- "inputs": {
101
- "bundle": {
102
- "name": "bundle",
103
- "position": "left",
104
- "type": {
105
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
106
- }
107
- }
108
- },
109
- "name": "Train/test split",
110
- "outputs": {
111
- "output": {
112
- "name": "output",
113
- "position": "right",
114
- "type": {
115
- "type": "None"
116
- }
117
- }
118
- },
119
- "params": {
120
- "table_name": {
121
- "default": null,
122
- "name": "table_name",
123
- "type": {
124
- "type": "<class 'str'>"
125
- }
126
- },
127
- "test_ratio": {
128
- "default": 0.1,
129
- "name": "test_ratio",
130
- "type": {
131
- "type": "<class 'float'>"
132
- }
133
- }
134
- },
135
- "type": "basic"
136
- },
137
- "params": {
138
- "table_name": null,
139
- "test_ratio": 0.1
140
- },
141
- "status": "planned",
142
- "title": "Train/test split"
143
- },
144
- "dragHandle": ".bg-primary",
145
- "height": 200.0,
146
- "id": "Train/test split 1",
147
- "position": {
148
- "x": 313.3745540124723,
149
- "y": 412.5466021460861
150
- },
151
- "type": "basic",
152
- "width": 200.0
153
- },
154
- {
155
- "data": {
156
- "display": null,
157
- "error": "[Errno 2] No such file or directory: ''",
158
- "meta": {
159
- "inputs": {},
160
- "name": "Import CSV",
161
- "outputs": {
162
- "output": {
163
- "name": "output",
164
- "position": "right",
165
- "type": {
166
- "type": "None"
167
- }
168
- }
169
- },
170
- "params": {
171
- "columns": {
172
- "default": "<from file>",
173
- "name": "columns",
174
- "type": {
175
- "type": "<class 'str'>"
176
- }
177
- },
178
- "filename": {
179
- "default": null,
180
- "name": "filename",
181
- "type": {
182
- "type": "<class 'str'>"
183
- }
184
- },
185
- "separator": {
186
- "default": "<auto>",
187
- "name": "separator",
188
- "type": {
189
- "type": "<class 'str'>"
190
- }
191
- }
192
- },
193
- "type": "basic"
194
- },
195
- "params": {
196
- "columns": "<from file>",
197
- "filename": null,
198
- "separator": "<auto>"
199
- },
200
- "status": "done",
201
- "title": "Import CSV"
202
- },
203
- "dragHandle": ".bg-primary",
204
- "height": 200.0,
205
- "id": "Import CSV 1",
206
- "position": {
207
- "x": -2.1743215714344757,
208
- "y": 346.06014722935214
209
- },
210
- "type": "basic",
211
- "width": 200.0
212
- },
213
- {
214
- "data": {
215
- "__execution_delay": 0.0,
216
- "collapsed": null,
217
- "display": null,
218
- "error": "Missing input: bundle",
219
- "meta": {
220
- "inputs": {
221
- "bundle": {
222
- "name": "bundle",
223
- "position": "left",
224
- "type": {
225
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
226
- }
227
- }
228
- },
229
- "name": "Model inference",
230
- "outputs": {
231
- "output": {
232
- "name": "output",
233
- "position": "right",
234
- "type": {
235
- "type": "None"
236
- }
237
- }
238
- },
239
- "params": {
240
- "model_mapping": {
241
- "default": null,
242
- "name": "model_mapping",
243
- "type": {
244
- "type": "<class 'str'>"
245
- }
246
- },
247
- "model_name": {
248
- "default": null,
249
- "name": "model_name",
250
- "type": {
251
- "type": "<class 'str'>"
252
- }
253
- },
254
- "save_output_as": {
255
- "default": "prediction",
256
- "name": "save_output_as",
257
- "type": {
258
- "type": "<class 'str'>"
259
- }
260
- }
261
- },
262
- "type": "basic"
263
- },
264
- "params": {
265
- "model_mapping": "input: data_test",
266
- "model_name": "model",
267
- "save_output_as": "prediction"
268
- },
269
- "status": "done",
270
- "title": "Model inference"
271
- },
272
- "dragHandle": ".bg-primary",
273
- "height": 339.0,
274
- "id": "Model inference 1",
275
- "position": {
276
- "x": 1736.5697434242886,
277
- "y": 357.0743204289906
278
- },
279
- "type": "basic",
280
- "width": 281.0
281
- },
282
- {
283
- "data": {
284
- "__execution_delay": null,
285
- "collapsed": true,
286
- "display": null,
287
- "error": "Missing input: bundle",
288
- "meta": {
289
- "inputs": {
290
- "bundle": {
291
- "name": "bundle",
292
- "position": "left",
293
- "type": {
294
- "type": "list[lynxkite_graph_analytics.core.Bundle]"
295
- }
296
- }
297
- },
298
- "name": "Organize",
299
- "outputs": {
300
- "output": {
301
- "name": "output",
302
- "position": "right",
303
- "type": {
304
- "type": "None"
305
- }
306
- }
307
- },
308
- "params": {
309
- "relations": {
310
- "default": null,
311
- "name": "relations",
312
- "type": {
313
- "type": "<class 'str'>"
314
- }
315
- }
316
- },
317
- "type": "graph_creation_view"
318
- },
319
- "params": {
320
- "relations": null
321
- },
322
- "status": "planned",
323
- "title": "Organize"
324
- },
325
- "dragHandle": ".bg-primary",
326
- "height": 322.0,
327
- "id": "Create graph 1",
328
- "position": {
329
- "x": 846.6882598271658,
330
- "y": 480.6258932907771
331
- },
332
- "type": "graph_creation_view",
333
- "width": 313.0
334
- },
335
- {
336
- "data": {
337
- "__execution_delay": 0.0,
338
- "collapsed": null,
339
- "display": null,
340
- "error": null,
341
- "meta": {
342
- "inputs": {},
343
- "name": "Define model",
344
- "outputs": {
345
- "output": {
346
- "name": "output",
347
- "position": "right",
348
- "type": {
349
- "type": "None"
350
- }
351
- }
352
- },
353
- "params": {
354
- "model_workspace": {
355
- "default": null,
356
- "name": "model_workspace",
357
- "type": {
358
- "type": "<class 'str'>"
359
- }
360
- },
361
- "save_as": {
362
- "default": "model",
363
- "name": "save_as",
364
- "type": {
365
- "type": "<class 'str'>"
366
- }
367
- }
368
- },
369
- "position": {
370
- "x": 286.0,
371
- "y": 208.0
372
- },
373
- "type": "basic"
374
- },
375
- "params": {
376
- "model_workspace": "ODE-GNN",
377
- "save_as": "model"
378
- },
379
- "status": "done",
380
- "title": "Define model"
381
- },
382
- "dragHandle": ".bg-primary",
383
- "height": 200.0,
384
- "id": "Define model 1",
385
- "position": {
386
- "x": 311.976524267066,
387
- "y": 146.99006795914332
388
- },
389
- "type": "basic",
390
- "width": 200.0
391
- },
392
- {
393
- "data": {
394
- "__execution_delay": 0.0,
395
- "collapsed": null,
396
- "display": null,
397
- "error": "Missing input: bundle",
398
- "meta": {
399
- "inputs": {
400
- "bundle": {
401
- "name": "bundle",
402
- "position": "left",
403
- "type": {
404
- "type": "<class 'lynxkite_graph_analytics.core.Bundle'>"
405
- }
406
- }
407
- },
408
- "name": "Train model",
409
- "outputs": {
410
- "output": {
411
- "name": "output",
412
- "position": "right",
413
- "type": {
414
- "type": "None"
415
- }
416
- }
417
- },
418
- "params": {
419
- "epochs": {
420
- "default": 1.0,
421
- "name": "epochs",
422
- "type": {
423
- "type": "<class 'int'>"
424
- }
425
- },
426
- "model_mapping": {
427
- "default": null,
428
- "name": "model_mapping",
429
- "type": {
430
- "type": "<class 'str'>"
431
- }
432
- },
433
- "model_name": {
434
- "default": null,
435
- "name": "model_name",
436
- "type": {
437
- "type": "<class 'str'>"
438
- }
439
- }
440
- },
441
- "position": {
442
- "x": 995.0,
443
- "y": 350.0
444
- },
445
- "type": "basic"
446
- },
447
- "params": {
448
- "epochs": 1.0,
449
- "model_mapping": "input: data_train",
450
- "model_name": "model"
451
- },
452
- "status": "planned",
453
- "title": "Train model"
454
- },
455
- "dragHandle": ".bg-primary",
456
- "height": 342.0,
457
- "id": "Train model 1",
458
- "position": {
459
- "x": 1358.7213662492159,
460
- "y": 352.03096133771896
461
- },
462
- "type": "basic",
463
- "width": 296.0
464
- }
465
- ]
466
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/{PyTorch demo β†’ PyTorch demo.lynxkite.json} RENAMED
File without changes
examples/Word2vec.lynxkite.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/{sql β†’ sql.lynxkite.json} RENAMED
File without changes
examples/word2vec.py CHANGED
@@ -5,17 +5,14 @@ import pandas as pd
5
  ENV = "LynxKite Graph Analytics"
6
 
7
 
8
- @op(ENV, "Word2vec for the top 1000 words", cache=True)
9
  def word2vec_1000():
10
  model = staticvectors.StaticVectors("neuml/word2vec-quantized")
11
- with open("wordlist.txt") as f:
12
- words = [w.strip() for w in f.read().strip().split("\n")]
13
- df = pd.DataFrame(
14
- {
15
- "word": words,
16
- "embedding": model.embeddings(words).tolist(),
17
- }
18
  )
 
19
  return df
20
 
21
 
 
5
  ENV = "LynxKite Graph Analytics"
6
 
7
 
8
+ @op(ENV, "Word2vec for the top 1000 words", slow=True)
9
  def word2vec_1000():
10
  model = staticvectors.StaticVectors("neuml/word2vec-quantized")
11
+ df = pd.read_csv(
12
+ "https://gist.githubusercontent.com/deekayen/4148741/raw/98d35708fa344717d8eee15d11987de6c8e26d7d/1-1000.txt",
13
+ names=["word"],
 
 
 
 
14
  )
15
+ df["embedding"] = model.embeddings(df.word.tolist()).tolist()
16
  return df
17
 
18
 
lynxkite-app/src/lynxkite_app/main.py CHANGED
@@ -84,6 +84,15 @@ class DirectoryEntry(pydantic.BaseModel):
84
  type: str
85
 
86
 
 
 
 
 
 
 
 
 
 
87
  @app.get("/api/dir/list")
88
  def list_dir(path: str):
89
  path = data_path / path
@@ -92,7 +101,7 @@ def list_dir(path: str):
92
  [
93
  DirectoryEntry(
94
  name=str(p.relative_to(data_path)),
95
- type="directory" if p.is_dir() else "workspace",
96
  )
97
  for p in path.iterdir()
98
  if not p.name.startswith(".")
 
84
  type: str
85
 
86
 
87
+ def _get_path_type(path: pathlib.Path) -> str:
88
+ if path.is_dir():
89
+ return "directory"
90
+ elif path.suffixes[-2:] == [".lynxkite", ".json"]:
91
+ return "workspace"
92
+ else:
93
+ return "file"
94
+
95
+
96
  @app.get("/api/dir/list")
97
  def list_dir(path: str):
98
  path = data_path / path
 
101
  [
102
  DirectoryEntry(
103
  name=str(p.relative_to(data_path)),
104
+ type=_get_path_type(p),
105
  )
106
  for p in path.iterdir()
107
  if not p.name.startswith(".")
lynxkite-app/web/index.html CHANGED
@@ -4,7 +4,6 @@
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>LynxKite 2025</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
 
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
7
  </head>
8
  <body>
9
  <div id="root"></div>
lynxkite-app/web/src/Directory.tsx CHANGED
@@ -15,9 +15,46 @@ 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 () {
@@ -27,48 +64,41 @@ export default function () {
27
  dedupingInterval: 0,
28
  });
29
  const navigate = useNavigate();
30
- const [isCreatingDir, setIsCreatingDir] = useState(false);
31
- const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
32
 
33
  function link(item: DirectoryEntry) {
34
  if (item.type === "directory") {
35
  return `/dir/${item.name}`;
36
  }
37
- return `/edit/${item.name}`;
 
 
 
38
  }
39
 
40
  function shortName(item: DirectoryEntry) {
41
- return item.name.split("/").pop();
 
 
 
42
  }
43
 
44
- function newName(list: DirectoryEntry[], baseName = "Untitled") {
45
- let i = 0;
46
- while (true) {
47
- const name = `${baseName}${i ? ` ${i}` : ""}`;
48
- if (!list.find((item) => item.name === name)) {
49
- return name;
50
- }
51
- i++;
52
- }
53
  }
54
-
55
- function newWorkspaceIn(path: string, list: DirectoryEntry[], workspaceName?: string) {
56
  const pathSlash = path ? `${path}/` : "";
57
- const name = workspaceName || newName(list);
58
- navigate(`/edit/${pathSlash}${name}`, { replace: true });
59
  }
60
-
61
- async function newFolderIn(path: string, list: DirectoryEntry[], folderName?: string) {
62
- const name = folderName || newName(list, "New Folder");
63
  const pathSlash = path ? `${path}/` : "";
64
-
65
  const res = await fetch("/api/dir/mkdir", {
66
  method: "POST",
67
  headers: { "Content-Type": "application/json" },
68
- body: JSON.stringify({ path: pathSlash + name }),
69
  });
70
  if (res.ok) {
71
- navigate(`/dir/${pathSlash}${name}`);
72
  } else {
73
  alert("Failed to create folder.");
74
  }
@@ -105,84 +135,66 @@ export default function () {
105
  {list.data && (
106
  <>
107
  <div className="actions">
108
- <div className="new-workspace">
109
- {isCreatingWorkspace && (
110
- // @ts-ignore
111
- <form
112
- onSubmit={(e) => {
113
- e.preventDefault();
114
- newWorkspaceIn(
115
- path || "",
116
- list.data,
117
- (e.target as HTMLFormElement).workspaceName.value.trim(),
118
- );
119
- }}
120
- >
121
- <input
122
- type="text"
123
- name="workspaceName"
124
- defaultValue={newName(list.data)}
125
- placeholder={newName(list.data)}
126
- />
127
- </form>
128
- )}
129
- <button type="button" onClick={() => setIsCreatingWorkspace(true)}>
130
- <FolderPlus /> New workspace
131
- </button>
132
- </div>
133
-
134
- <div className="new-folder">
135
- {isCreatingDir && (
136
- // @ts-ignore
137
- <form
138
- onSubmit={(e) => {
139
- e.preventDefault();
140
- newFolderIn(
141
- path || "",
142
- list.data,
143
- (e.target as HTMLFormElement).folderName.value.trim(),
144
- );
145
- }}
146
- >
147
- <input
148
- type="text"
149
- name="folderName"
150
- defaultValue={newName(list.data)}
151
- placeholder={newName(list.data)}
152
- />
153
- </form>
154
- )}
155
- <button type="button" onClick={() => setIsCreatingDir(true)}>
156
- <FolderPlus /> New folder
157
- </button>
158
- </div>
159
  </div>
160
 
161
- {path && (
162
  <div className="breadcrumbs">
163
  <Link to="/dir/">
164
  <Home />
165
  </Link>{" "}
166
  <span className="current-folder">{path}</span>
 
167
  </div>
 
 
168
  )}
169
 
170
- {list.data.map((item: DirectoryEntry) => (
171
- <div key={item.name} className="entry">
172
- <Link key={link(item)} to={link(item)}>
173
- {item.type === "directory" ? <Folder /> : <File />}
174
- {shortName(item)}
175
- </Link>
176
- <button
177
- type="button"
178
- onClick={() => {
179
- deleteItem(item);
180
- }}
181
- >
182
- <Trash />
183
- </button>
184
- </div>
185
- ))}
 
 
 
 
 
 
 
 
 
186
  </>
187
  )}
188
  </div>{" "}
 
15
  // @ts-ignore
16
  import Home from "~icons/tabler/home";
17
  // @ts-ignore
18
+ import LayoutGrid from "~icons/tabler/layout-grid";
19
+ // @ts-ignore
20
+ import LayoutGridAdd from "~icons/tabler/layout-grid-add";
21
+ // @ts-ignore
22
  import Trash from "~icons/tabler/trash";
23
  import logo from "./assets/logo.png";
24
 
25
+ function EntryCreator(props: {
26
+ label: string;
27
+ icon: JSX.Element;
28
+ onCreate: (name: string) => void;
29
+ }) {
30
+ const [isCreating, setIsCreating] = useState(false);
31
+ return (
32
+ <>
33
+ {isCreating ? (
34
+ <form
35
+ onSubmit={(e) => {
36
+ e.preventDefault();
37
+ props.onCreate((e.target as HTMLFormElement).entryName.value.trim());
38
+ }}
39
+ >
40
+ <input
41
+ className="input input-ghost w-full"
42
+ autoFocus
43
+ type="text"
44
+ name="entryName"
45
+ onBlur={() => setIsCreating(false)}
46
+ placeholder={`${props.label} name`}
47
+ />
48
+ </form>
49
+ ) : (
50
+ <button type="button" onClick={() => setIsCreating(true)}>
51
+ {props.icon} {props.label}
52
+ </button>
53
+ )}
54
+ </>
55
+ );
56
+ }
57
+
58
  const fetcher = (url: string) => fetch(url).then((res) => res.json());
59
 
60
  export default function () {
 
64
  dedupingInterval: 0,
65
  });
66
  const navigate = useNavigate();
 
 
67
 
68
  function link(item: DirectoryEntry) {
69
  if (item.type === "directory") {
70
  return `/dir/${item.name}`;
71
  }
72
+ if (item.type === "workspace") {
73
+ return `/edit/${item.name}`;
74
+ }
75
+ return `/code/${item.name}`;
76
  }
77
 
78
  function shortName(item: DirectoryEntry) {
79
+ return item.name
80
+ .split("/")
81
+ .pop()
82
+ ?.replace(/[.]lynxkite[.]json$/, "");
83
  }
84
 
85
+ function newWorkspaceIn(path: string, workspaceName: string) {
86
+ const pathSlash = path ? `${path}/` : "";
87
+ navigate(`/edit/${pathSlash}${workspaceName}.lynxkite.json`, { replace: true });
 
 
 
 
 
 
88
  }
89
+ function newCodeFile(path: string, name: string) {
 
90
  const pathSlash = path ? `${path}/` : "";
91
+ navigate(`/code/${pathSlash}${name}`, { replace: true });
 
92
  }
93
+ async function newFolderIn(path: string, folderName: string) {
 
 
94
  const pathSlash = path ? `${path}/` : "";
 
95
  const res = await fetch("/api/dir/mkdir", {
96
  method: "POST",
97
  headers: { "Content-Type": "application/json" },
98
+ body: JSON.stringify({ path: pathSlash + folderName }),
99
  });
100
  if (res.ok) {
101
+ navigate(`/dir/${pathSlash}${folderName}`);
102
  } else {
103
  alert("Failed to create folder.");
104
  }
 
135
  {list.data && (
136
  <>
137
  <div className="actions">
138
+ <EntryCreator
139
+ onCreate={(name) => {
140
+ newWorkspaceIn(path || "", name);
141
+ }}
142
+ icon={<LayoutGridAdd />}
143
+ label="New workspace"
144
+ />
145
+ <EntryCreator
146
+ onCreate={(name) => {
147
+ newCodeFile(path || "", name);
148
+ }}
149
+ icon={<FilePlus />}
150
+ label="New code file"
151
+ />
152
+ <EntryCreator
153
+ onCreate={(name: string) => {
154
+ newFolderIn(path || "", name);
155
+ }}
156
+ icon={<FolderPlus />}
157
+ label="New folder"
158
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  </div>
160
 
161
+ {path ? (
162
  <div className="breadcrumbs">
163
  <Link to="/dir/">
164
  <Home />
165
  </Link>{" "}
166
  <span className="current-folder">{path}</span>
167
+ <title>{path}</title>
168
  </div>
169
+ ) : (
170
+ <title>LynxKite 2000:MM</title>
171
  )}
172
 
173
+ {list.data.map(
174
+ (item: DirectoryEntry) =>
175
+ !shortName(item)?.startsWith("__") && (
176
+ <div key={item.name} className="entry">
177
+ <Link key={link(item)} to={link(item)}>
178
+ {item.type === "directory" ? (
179
+ <Folder />
180
+ ) : item.type === "workspace" ? (
181
+ <LayoutGrid />
182
+ ) : (
183
+ <File />
184
+ )}
185
+ {shortName(item)}
186
+ </Link>
187
+ <button
188
+ type="button"
189
+ onClick={() => {
190
+ deleteItem(item);
191
+ }}
192
+ >
193
+ <Trash />
194
+ </button>
195
+ </div>
196
+ ),
197
+ )}
198
  </>
199
  )}
200
  </div>{" "}
lynxkite-app/web/src/index.css CHANGED
@@ -326,7 +326,14 @@ body {
326
  .actions {
327
  display: flex;
328
  justify-content: space-evenly;
 
 
329
  padding: 5px;
 
 
 
 
 
330
  }
331
 
332
  .actions a {
 
326
  .actions {
327
  display: flex;
328
  justify-content: space-evenly;
329
+ align-items: center;
330
+ height: 50px;
331
  padding: 5px;
332
+
333
+ form,
334
+ button {
335
+ flex: 1;
336
+ }
337
  }
338
 
339
  .actions a {
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -53,6 +53,10 @@ function LynxKiteFlow() {
53
  const [nodes, setNodes] = useState([] as Node[]);
54
  const [edges, setEdges] = useState([] as Edge[]);
55
  const { path } = useParams();
 
 
 
 
56
  const [state, setState] = useState({ workspace: {} as Workspace });
57
  const [message, setMessage] = useState(null as string | null);
58
  useEffect(() => {
@@ -320,7 +324,8 @@ function LynxKiteFlow() {
320
  <a className="logo" href="">
321
  <img alt="" src={favicon} />
322
  </a>
323
- <div className="ws-name">{path}</div>
 
324
  <EnvironmentSelector
325
  options={Object.keys(catalog.data || {})}
326
  value={state.workspace.env!}
 
53
  const [nodes, setNodes] = useState([] as Node[]);
54
  const [edges, setEdges] = useState([] as Edge[]);
55
  const { path } = useParams();
56
+ const shortPath = path!
57
+ .split("/")
58
+ .pop()!
59
+ .replace(/[.]lynxkite[.]json$/, "");
60
  const [state, setState] = useState({ workspace: {} as Workspace });
61
  const [message, setMessage] = useState(null as string | null);
62
  useEffect(() => {
 
324
  <a className="logo" href="">
325
  <img alt="" src={favicon} />
326
  </a>
327
+ <div className="ws-name">{shortPath}</div>
328
+ <title>{shortPath}</title>
329
  <EnvironmentSelector
330
  options={Object.keys(catalog.data || {})}
331
  value={state.workspace.env!}
lynxkite-core/src/lynxkite/core/ops.py CHANGED
@@ -196,13 +196,14 @@ class Op(BaseConfig):
196
  return res
197
 
198
 
199
- def op(env: str, name: str, *, view="basic", outputs=None, params=None, cache=False):
200
  """Decorator for defining an operation."""
201
 
202
  def decorator(func):
203
- if cache:
204
- func = mem.cache(func)
205
  sig = inspect.signature(func)
 
 
 
206
  # Positional arguments are inputs.
207
  inputs = {
208
  name: Input(name=name, type=param.annotation)
@@ -319,6 +320,7 @@ def slow(func):
319
  return wrapper
320
 
321
 
 
322
  CATALOGS_SNAPSHOTS: dict[str, Catalogs] = {}
323
 
324
 
 
196
  return res
197
 
198
 
199
+ def op(env: str, name: str, *, view="basic", outputs=None, params=None, slow=False):
200
  """Decorator for defining an operation."""
201
 
202
  def decorator(func):
 
 
203
  sig = inspect.signature(func)
204
+ if slow:
205
+ func = mem.cache(func)
206
+ func = _global_slow(func)
207
  # Positional arguments are inputs.
208
  inputs = {
209
  name: Input(name=name, type=param.annotation)
 
320
  return wrapper
321
 
322
 
323
+ _global_slow = slow # For access inside op().
324
  CATALOGS_SNAPSHOTS: dict[str, Catalogs] = {}
325
 
326