darabos commited on
Commit
62d700f
·
2 Parent(s): bfeadab ce899aa

Merge pull request #132 from biggraph/darabos-execute-button

Browse files
.github/workflows/test.yaml CHANGED
@@ -8,6 +8,8 @@ on:
8
  jobs:
9
  test:
10
  runs-on: ubuntu-latest
 
 
11
  steps:
12
  - uses: actions/checkout@v4
13
 
@@ -24,6 +26,8 @@ jobs:
24
  run: |
25
  eval `ssh-agent -s`
26
  ssh-add - <<< '${{ secrets.LYNXSCRIBE_DEPLOY_KEY }}'
 
 
27
  uv pip install \
28
  -e lynxkite-core/[dev] \
29
  -e lynxkite-app/[dev] \
@@ -31,15 +35,11 @@ jobs:
31
  -e lynxkite-bio \
32
  -e lynxkite-lynxscribe/ \
33
  -e lynxkite-pillow-example/
34
- env:
35
- UV_SYSTEM_PYTHON: 1
36
 
37
  - name: Run pre-commits
38
  run: |
39
  uv pip install pre-commit
40
  pre-commit run --all-files
41
- env:
42
- UV_SYSTEM_PYTHON: 1
43
 
44
  - name: Run core tests
45
  run: |
@@ -65,8 +65,6 @@ jobs:
65
  run: |
66
  uv pip install mkdocs-material mkdocstrings[python]
67
  mkdocs build
68
- env:
69
- UV_SYSTEM_PYTHON: 1
70
 
71
  - uses: actions/setup-node@v4
72
  with:
 
8
  jobs:
9
  test:
10
  runs-on: ubuntu-latest
11
+ env:
12
+ UV_SYSTEM_PYTHON: 1
13
  steps:
14
  - uses: actions/checkout@v4
15
 
 
26
  run: |
27
  eval `ssh-agent -s`
28
  ssh-add - <<< '${{ secrets.LYNXSCRIBE_DEPLOY_KEY }}'
29
+ uv venv
30
+ . .venv/bin/activate
31
  uv pip install \
32
  -e lynxkite-core/[dev] \
33
  -e lynxkite-app/[dev] \
 
35
  -e lynxkite-bio \
36
  -e lynxkite-lynxscribe/ \
37
  -e lynxkite-pillow-example/
 
 
38
 
39
  - name: Run pre-commits
40
  run: |
41
  uv pip install pre-commit
42
  pre-commit run --all-files
 
 
43
 
44
  - name: Run core tests
45
  run: |
 
65
  run: |
66
  uv pip install mkdocs-material mkdocstrings[python]
67
  mkdocs build
 
 
68
 
69
  - uses: actions/setup-node@v4
70
  with:
examples/Image processing.lynxkite.json CHANGED
@@ -8,31 +8,31 @@
8
  "targetHandle": "image"
9
  },
10
  {
11
- "id": "xy-edge__Open image 1output-Flip verically 1image",
12
- "source": "Open image 1",
13
  "sourceHandle": "output",
14
- "target": "Flip verically 1",
15
  "targetHandle": "image"
16
  },
17
  {
18
- "id": "xy-edge__To grayscale 1output-View image 2image",
19
- "source": "To grayscale 1",
20
  "sourceHandle": "output",
21
- "target": "View image 2",
22
  "targetHandle": "image"
23
  },
24
  {
25
- "id": "xy-edge__Flip verically 1output-Blur 1image",
26
- "source": "Flip verically 1",
27
  "sourceHandle": "output",
28
- "target": "Blur 1",
29
  "targetHandle": "image"
30
  },
31
  {
32
- "id": "xy-edge__Blur 1output-To grayscale 1image",
33
- "source": "Blur 1",
34
  "sourceHandle": "output",
35
- "target": "To grayscale 1",
36
  "targetHandle": "image"
37
  }
38
  ],
@@ -120,51 +120,6 @@
120
  "type": "image",
121
  "width": 265.0
122
  },
123
- {
124
- "data": {
125
- "__execution_delay": null,
126
- "collapsed": true,
127
- "display": null,
128
- "error": null,
129
- "input_metadata": null,
130
- "meta": {
131
- "inputs": {
132
- "image": {
133
- "name": "image",
134
- "position": "left",
135
- "type": {
136
- "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
137
- }
138
- }
139
- },
140
- "name": "Flip verically",
141
- "outputs": {
142
- "output": {
143
- "name": "output",
144
- "position": "right",
145
- "type": {
146
- "type": "None"
147
- }
148
- }
149
- },
150
- "params": {},
151
- "type": "basic"
152
- },
153
- "params": {},
154
- "status": "done",
155
- "title": "Flip verically"
156
- },
157
- "dragHandle": ".bg-primary",
158
- "height": 200.0,
159
- "id": "Flip verically 1",
160
- "parentId": null,
161
- "position": {
162
- "x": 228.3853393986406,
163
- "y": 245.68255477059915
164
- },
165
- "type": "basic",
166
- "width": 200.0
167
- },
168
  {
169
  "data": {
170
  "display": "data:image/jpeg;base64,UklGRoADAABXRUJQVlA4IHQDAAAwNQCdASrIAMgAPm0ukkYyMaGhrtt5UkANiWluu7AObIPJl9NfJmVCwA06FIbr8ZA3WaSJ7/dHH5er4u683qcKWcW7qn4VGE7XCnSEtZUZsarT1ox9y8J2EionbqsxfkAAIOk7JTtvXiLVAeVPQlEW3R4aJSLoxXCJTuGOqcp6fJ7+tzWWmOSq1IqFDN7536TfOvEoiw/K8AQcP06J13RTpwyFow3MFoK9BWZDv0mPqMmHjuC7Hb5MkHc6QnQihSek5PyYdUN4+yG0NpM3NK0+lOSMpu5jaeY5YrFUKke5nLaJ0Mu/3it1akkzebF4fRj8xdIkJDrC4xD4RcyC8aQ1zI4MjffvaCX8X0jbNJ5n5xzGOOU9kFhpry6Uy30xVtGdrEYwbGflOPdNp1u0tiKemkGylZmdj/9rbvWo0IYnCFLs1tAQ1fpkGVQYVo4FA68IbkKvB2KNxrQ17ySt96mzi6ODrNuVzsgisAS/q2ILoOlZL2nfdCNWb523fz7hEM319hb+aWTmo2zExKfWXzUZRGWy09rt8rQZXW5qBKTs5vsYabOjubwNNfFoX7dv32XwAAD++LH6dpCJbVSQouNoBCx0XMzcYw+6o+G//GpIy1eD9A/1cvAp/zhr1mDURDqx2xBgY79tNgsjX9AArhyCNX/BRq/st/uDXRqUv7Wx+Pl/40TsVhng3/XrKZXhDYKnD2aAAyOHG8yS8FTlr5aM6Nh//ra74YvdX+3/j/RLZDm6wRAbHmTF1ZWhK3HdcgOeU+RMXPeCEL8zqEcm3nMeTpuJhH7GTnjejYZx6OrdHAWBghyiaVrgH9f2nXLm1VZ4AgOokXq16NNjRz3FgLYzr7qd94Y2TRJW/cGAcaH5CN5+iXZy4LY2JaCo5I4//pJ/TthQ8SBbQdOIDZPayUXSmAo7cTuD/rk97N3QBGdx5oIzTeSYL8FZfiH/nJLQARX7PafEYZBW9a/ZMjDSPg9GFTPL4BL42F32uL8ZcYLrpkRGRW6jbjmj7GuGSwV5+YuKOhEAf2ZVwU55UjaxRygygrjlYc8FoCe+HyL6rJ5V3jfZvZy1Nw3CdfX4CWQ5/gUjrgONEGb7g2SL/nS9dGgl2gAHKKCzEneCdL38wwsnQYPGdrerT/4uEWM1T24cHLkgBmT+zH6YiwG7DkqEmAbjF4AAAA==",
@@ -299,6 +254,50 @@
299
  },
300
  "type": "basic",
301
  "width": 200.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
303
  ]
304
  }
 
8
  "targetHandle": "image"
9
  },
10
  {
11
+ "id": "xy-edge__To grayscale 1output-View image 2image",
12
+ "source": "To grayscale 1",
13
  "sourceHandle": "output",
14
+ "target": "View image 2",
15
  "targetHandle": "image"
16
  },
17
  {
18
+ "id": "xy-edge__Blur 1output-To grayscale 1image",
19
+ "source": "Blur 1",
20
  "sourceHandle": "output",
21
+ "target": "To grayscale 1",
22
  "targetHandle": "image"
23
  },
24
  {
25
+ "id": "Open image 1 Flip vertically 1",
26
+ "source": "Open image 1",
27
  "sourceHandle": "output",
28
+ "target": "Flip vertically 1",
29
  "targetHandle": "image"
30
  },
31
  {
32
+ "id": "Flip vertically 1 Blur 1",
33
+ "source": "Flip vertically 1",
34
  "sourceHandle": "output",
35
+ "target": "Blur 1",
36
  "targetHandle": "image"
37
  }
38
  ],
 
120
  "type": "image",
121
  "width": 265.0
122
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  {
124
  "data": {
125
  "display": "data:image/jpeg;base64,UklGRoADAABXRUJQVlA4IHQDAAAwNQCdASrIAMgAPm0ukkYyMaGhrtt5UkANiWluu7AObIPJl9NfJmVCwA06FIbr8ZA3WaSJ7/dHH5er4u683qcKWcW7qn4VGE7XCnSEtZUZsarT1ox9y8J2EionbqsxfkAAIOk7JTtvXiLVAeVPQlEW3R4aJSLoxXCJTuGOqcp6fJ7+tzWWmOSq1IqFDN7536TfOvEoiw/K8AQcP06J13RTpwyFow3MFoK9BWZDv0mPqMmHjuC7Hb5MkHc6QnQihSek5PyYdUN4+yG0NpM3NK0+lOSMpu5jaeY5YrFUKke5nLaJ0Mu/3it1akkzebF4fRj8xdIkJDrC4xD4RcyC8aQ1zI4MjffvaCX8X0jbNJ5n5xzGOOU9kFhpry6Uy30xVtGdrEYwbGflOPdNp1u0tiKemkGylZmdj/9rbvWo0IYnCFLs1tAQ1fpkGVQYVo4FA68IbkKvB2KNxrQ17ySt96mzi6ODrNuVzsgisAS/q2ILoOlZL2nfdCNWb523fz7hEM319hb+aWTmo2zExKfWXzUZRGWy09rt8rQZXW5qBKTs5vsYabOjubwNNfFoX7dv32XwAAD++LH6dpCJbVSQouNoBCx0XMzcYw+6o+G//GpIy1eD9A/1cvAp/zhr1mDURDqx2xBgY79tNgsjX9AArhyCNX/BRq/st/uDXRqUv7Wx+Pl/40TsVhng3/XrKZXhDYKnD2aAAyOHG8yS8FTlr5aM6Nh//ra74YvdX+3/j/RLZDm6wRAbHmTF1ZWhK3HdcgOeU+RMXPeCEL8zqEcm3nMeTpuJhH7GTnjejYZx6OrdHAWBghyiaVrgH9f2nXLm1VZ4AgOokXq16NNjRz3FgLYzr7qd94Y2TRJW/cGAcaH5CN5+iXZy4LY2JaCo5I4//pJ/TthQ8SBbQdOIDZPayUXSmAo7cTuD/rk97N3QBGdx5oIzTeSYL8FZfiH/nJLQARX7PafEYZBW9a/ZMjDSPg9GFTPL4BL42F32uL8ZcYLrpkRGRW6jbjmj7GuGSwV5+YuKOhEAf2ZVwU55UjaxRygygrjlYc8FoCe+HyL6rJ5V3jfZvZy1Nw3CdfX4CWQ5/gUjrgONEGb7g2SL/nS9dGgl2gAHKKCzEneCdL38wwsnQYPGdrerT/4uEWM1T24cHLkgBmT+zH6YiwG7DkqEmAbjF4AAAA==",
 
254
  },
255
  "type": "basic",
256
  "width": 200.0
257
+ },
258
+ {
259
+ "data": {
260
+ "__execution_delay": null,
261
+ "collapsed": true,
262
+ "display": null,
263
+ "error": null,
264
+ "input_metadata": null,
265
+ "meta": {
266
+ "inputs": {
267
+ "image": {
268
+ "name": "image",
269
+ "position": "left",
270
+ "type": {
271
+ "type": "<module 'PIL.Image' from '/media/nvme/darabos/lynxkite-2024/.venv/lib/python3.11/site-packages/PIL/Image.py'>"
272
+ }
273
+ }
274
+ },
275
+ "name": "Flip vertically",
276
+ "outputs": {
277
+ "output": {
278
+ "name": "output",
279
+ "position": "right",
280
+ "type": {
281
+ "type": "None"
282
+ }
283
+ }
284
+ },
285
+ "params": {},
286
+ "type": "basic"
287
+ },
288
+ "params": {},
289
+ "status": "done",
290
+ "title": "Flip vertically"
291
+ },
292
+ "dragHandle": ".bg-primary",
293
+ "height": 200.0,
294
+ "id": "Flip vertically 1",
295
+ "position": {
296
+ "x": 148.51544517498044,
297
+ "y": 288.98657171134255
298
+ },
299
+ "type": "basic",
300
+ "width": 200.0
301
  }
302
  ]
303
  }
examples/Model use.lynxkite.json CHANGED
@@ -421,7 +421,7 @@
421
  "type": "basic"
422
  },
423
  "params": {
424
- "model_workspace": "Model definition.lynxkite.json",
425
  "save_as": "model"
426
  },
427
  "status": "done",
 
421
  "type": "basic"
422
  },
423
  "params": {
424
+ "model_workspace": "Model definition",
425
  "save_as": "model"
426
  },
427
  "status": "done",
examples/PyTorch demo.lynxkite.json DELETED
@@ -1,623 +0,0 @@
1
- {
2
- "env": "PyTorch model",
3
- "nodes": [
4
- {
5
- "id": "Input: features 1",
6
- "type": "basic",
7
- "data": {
8
- "title": "Input: features",
9
- "params": {},
10
- "display": null,
11
- "error": null,
12
- "meta": {
13
- "name": "Input: features",
14
- "type": "basic",
15
- "outputs": {
16
- "x": {
17
- "position": "top",
18
- "name": "x",
19
- "type": {
20
- "type": "tensor"
21
- }
22
- }
23
- },
24
- "params": {},
25
- "inputs": {}
26
- },
27
- "collapsed": true,
28
- "__execution_delay": null
29
- },
30
- "position": {
31
- "x": -108.60604658638658,
32
- "y": 63.96065124378427
33
- },
34
- "width": 200.0,
35
- "height": 200.0,
36
- "parentId": null
37
- },
38
- {
39
- "id": "Input: graph edges 1",
40
- "type": "basic",
41
- "data": {
42
- "title": "Input: graph edges",
43
- "params": {},
44
- "display": null,
45
- "error": null,
46
- "__execution_delay": null,
47
- "collapsed": true,
48
- "meta": {
49
- "name": "Input: graph edges",
50
- "inputs": {},
51
- "params": {},
52
- "type": "basic",
53
- "outputs": {
54
- "edges": {
55
- "name": "edges",
56
- "type": {
57
- "type": "tensor"
58
- },
59
- "position": "top"
60
- }
61
- }
62
- }
63
- },
64
- "position": {
65
- "x": 180.7373888617958,
66
- "y": 58.54904654355781
67
- },
68
- "width": 200.0,
69
- "parentId": null,
70
- "height": 200.0
71
- },
72
- {
73
- "id": "Linear 1",
74
- "type": "basic",
75
- "data": {
76
- "title": "Linear",
77
- "params": {
78
- "output_dim": "same"
79
- },
80
- "display": null,
81
- "error": null,
82
- "meta": {
83
- "inputs": {
84
- "x": {
85
- "type": {
86
- "type": "tensor"
87
- },
88
- "position": "bottom",
89
- "name": "x"
90
- }
91
- },
92
- "type": "basic",
93
- "name": "Linear",
94
- "outputs": {
95
- "x": {
96
- "type": {
97
- "type": "tensor"
98
- },
99
- "position": "top",
100
- "name": "x"
101
- }
102
- },
103
- "params": {
104
- "output_dim": {
105
- "name": "output_dim",
106
- "type": {
107
- "type": "<class 'str'>"
108
- },
109
- "default": "same"
110
- }
111
- }
112
- }
113
- },
114
- "position": {
115
- "x": 78.37881963123723,
116
- "y": -528.3012263817914
117
- },
118
- "parentId": null,
119
- "width": 204.0,
120
- "height": 140.0
121
- },
122
- {
123
- "id": "Activation 1",
124
- "type": "basic",
125
- "data": {
126
- "title": "Activation",
127
- "params": {
128
- "type": "ReLU"
129
- },
130
- "display": null,
131
- "error": null,
132
- "meta": {
133
- "outputs": {
134
- "x": {
135
- "position": "top",
136
- "type": {
137
- "type": "tensor"
138
- },
139
- "name": "x"
140
- }
141
- },
142
- "name": "Activation",
143
- "type": "basic",
144
- "inputs": {
145
- "x": {
146
- "position": "bottom",
147
- "name": "x",
148
- "type": {
149
- "type": "tensor"
150
- }
151
- }
152
- },
153
- "params": {
154
- "type": {
155
- "default": "OptionsFor_type.ReLU",
156
- "name": "type",
157
- "type": {
158
- "enum": [
159
- "ReLU",
160
- "LeakyReLU",
161
- "Tanh",
162
- "Mish"
163
- ]
164
- }
165
- }
166
- }
167
- }
168
- },
169
- "position": {
170
- "x": 98.44658319023353,
171
- "y": -741.1411130550297
172
- },
173
- "height": 154.0,
174
- "parentId": null,
175
- "width": 173.0
176
- },
177
- {
178
- "id": "Dropout 1",
179
- "type": "basic",
180
- "data": {
181
- "title": "Dropout",
182
- "params": {
183
- "p": 0.5
184
- },
185
- "display": null,
186
- "error": null,
187
- "meta": {
188
- "type": "basic",
189
- "inputs": {
190
- "x": {
191
- "name": "x",
192
- "type": {
193
- "type": "tensor"
194
- },
195
- "position": "bottom"
196
- }
197
- },
198
- "name": "Dropout",
199
- "params": {
200
- "p": {
201
- "default": 0.5,
202
- "type": {
203
- "type": "<class 'float'>"
204
- },
205
- "name": "p"
206
- }
207
- },
208
- "outputs": {
209
- "x": {
210
- "position": "top",
211
- "name": "x",
212
- "type": {
213
- "type": "tensor"
214
- }
215
- }
216
- }
217
- }
218
- },
219
- "position": {
220
- "x": 73.61437458187964,
221
- "y": -929.9824215609918
222
- },
223
- "width": 207.0,
224
- "parentId": null,
225
- "height": 159.0
226
- },
227
- {
228
- "id": "Graph conv 1",
229
- "type": "basic",
230
- "data": {
231
- "title": "Graph conv",
232
- "params": {
233
- "type": "SAGEConv"
234
- },
235
- "display": null,
236
- "error": null,
237
- "meta": {
238
- "outputs": {
239
- "x": {
240
- "type": {
241
- "type": "tensor"
242
- },
243
- "name": "x",
244
- "position": "top"
245
- }
246
- },
247
- "type": "basic",
248
- "name": "Graph conv",
249
- "params": {
250
- "type": {
251
- "name": "type",
252
- "default": "OptionsFor_type.GCNConv",
253
- "type": {
254
- "enum": [
255
- "GCNConv",
256
- "GATConv",
257
- "GATv2Conv",
258
- "SAGEConv"
259
- ]
260
- }
261
- }
262
- },
263
- "inputs": {
264
- "x": {
265
- "type": {
266
- "type": "tensor"
267
- },
268
- "name": "x",
269
- "position": "bottom"
270
- },
271
- "edges": {
272
- "name": "edges",
273
- "type": {
274
- "type": "tensor"
275
- },
276
- "position": "bottom"
277
- }
278
- }
279
- }
280
- },
281
- "position": {
282
- "x": 64.08886242755246,
283
- "y": -269.43023573181557
284
- },
285
- "height": 200.0,
286
- "width": 200.0,
287
- "parentId": null
288
- },
289
- {
290
- "id": "Supervised loss 1",
291
- "type": "basic",
292
- "data": {
293
- "title": "Supervised loss",
294
- "params": {},
295
- "display": null,
296
- "error": null,
297
- "collapsed": true,
298
- "__execution_delay": null,
299
- "meta": {
300
- "type": "basic",
301
- "params": {},
302
- "name": "Supervised loss",
303
- "inputs": {
304
- "y": {
305
- "name": "y",
306
- "type": {
307
- "type": "tensor"
308
- },
309
- "position": "bottom"
310
- },
311
- "x": {
312
- "name": "x",
313
- "type": {
314
- "type": "tensor"
315
- },
316
- "position": "bottom"
317
- }
318
- },
319
- "outputs": {
320
- "loss": {
321
- "position": "top",
322
- "type": {
323
- "type": "tensor"
324
- },
325
- "name": "loss"
326
- }
327
- }
328
- }
329
- },
330
- "position": {
331
- "x": 110.53693593362718,
332
- "y": -1123.9976567905628
333
- },
334
- "parentId": null,
335
- "height": 80.0,
336
- "width": 204.0
337
- },
338
- {
339
- "id": "Input: label 1",
340
- "type": "basic",
341
- "data": {
342
- "title": "Input: label",
343
- "params": {},
344
- "display": null,
345
- "error": null,
346
- "collapsed": true,
347
- "__execution_delay": null,
348
- "meta": {
349
- "inputs": {},
350
- "outputs": {
351
- "y": {
352
- "type": {
353
- "type": "tensor"
354
- },
355
- "position": "top",
356
- "name": "y"
357
- }
358
- },
359
- "name": "Input: label",
360
- "type": "basic",
361
- "params": {}
362
- }
363
- },
364
- "position": {
365
- "x": 666.110498676668,
366
- "y": -898.6721561114967
367
- },
368
- "width": 200.0,
369
- "height": 73.0,
370
- "parentId": null
371
- },
372
- {
373
- "id": "Optimizer 1",
374
- "type": "basic",
375
- "data": {
376
- "title": "Optimizer",
377
- "params": {
378
- "lr": 0.001,
379
- "type": "AdamW"
380
- },
381
- "display": null,
382
- "error": null,
383
- "meta": {
384
- "name": "Optimizer",
385
- "outputs": {},
386
- "params": {
387
- "lr": {
388
- "default": 0.001,
389
- "name": "lr",
390
- "type": {
391
- "type": "<class 'float'>"
392
- }
393
- },
394
- "type": {
395
- "type": {
396
- "enum": [
397
- "AdamW",
398
- "Adafactor",
399
- "Adagrad",
400
- "SGD",
401
- "Lion",
402
- "Paged AdamW",
403
- "Galore AdamW"
404
- ]
405
- },
406
- "name": "type",
407
- "default": "OptionsFor_type.AdamW"
408
- }
409
- },
410
- "inputs": {
411
- "loss": {
412
- "type": {
413
- "type": "tensor"
414
- },
415
- "name": "loss",
416
- "position": "bottom"
417
- }
418
- },
419
- "type": "basic"
420
- }
421
- },
422
- "position": {
423
- "x": 115.25730958703494,
424
- "y": -1431.5320892753364
425
- },
426
- "width": 200.0,
427
- "height": 233.0,
428
- "parentId": null
429
- },
430
- {
431
- "id": "Repeat 3",
432
- "type": "basic",
433
- "data": {
434
- "title": "Repeat",
435
- "params": {
436
- "times": 1.0
437
- },
438
- "display": null,
439
- "error": null,
440
- "meta": {
441
- "type": "basic",
442
- "position": {
443
- "y": 340.0,
444
- "x": 371.0
445
- },
446
- "outputs": {
447
- "output": {
448
- "name": "output",
449
- "type": {
450
- "type": "tensor"
451
- },
452
- "position": "bottom"
453
- }
454
- },
455
- "name": "Repeat",
456
- "params": {
457
- "times": {
458
- "name": "times",
459
- "default": 1.0,
460
- "type": {
461
- "type": "<class 'int'>"
462
- }
463
- }
464
- },
465
- "inputs": {
466
- "input": {
467
- "type": {
468
- "type": "tensor"
469
- },
470
- "position": "top",
471
- "name": "input"
472
- }
473
- }
474
- }
475
- },
476
- "position": {
477
- "x": -245.1288628776232,
478
- "y": -276.90661040974317
479
- },
480
- "width": 200.0,
481
- "height": 200.0
482
- },
483
- {
484
- "id": "Repeat 1",
485
- "type": "basic",
486
- "data": {
487
- "title": "Repeat",
488
- "params": {
489
- "times": 1.0
490
- },
491
- "display": null,
492
- "error": null,
493
- "meta": {
494
- "outputs": {
495
- "output": {
496
- "position": "bottom",
497
- "type": {
498
- "type": "tensor"
499
- },
500
- "name": "output"
501
- }
502
- },
503
- "params": {
504
- "times": {
505
- "default": 1.0,
506
- "type": {
507
- "type": "<class 'int'>"
508
- },
509
- "name": "times"
510
- }
511
- },
512
- "position": {
513
- "x": 387.0,
514
- "y": 337.0
515
- },
516
- "type": "basic",
517
- "name": "Repeat",
518
- "inputs": {
519
- "input": {
520
- "name": "input",
521
- "position": "top",
522
- "type": {
523
- "type": "tensor"
524
- }
525
- }
526
- }
527
- }
528
- },
529
- "position": {
530
- "x": -258.0088683218416,
531
- "y": -737.3822225246788
532
- },
533
- "width": 200.0,
534
- "height": 200.0
535
- }
536
- ],
537
- "edges": [
538
- {
539
- "id": "xy-edge__Linear 1x-Activation 1x",
540
- "source": "Linear 1",
541
- "target": "Activation 1",
542
- "sourceHandle": "x",
543
- "targetHandle": "x"
544
- },
545
- {
546
- "id": "xy-edge__Activation 1x-Dropout 1x",
547
- "source": "Activation 1",
548
- "target": "Dropout 1",
549
- "sourceHandle": "x",
550
- "targetHandle": "x"
551
- },
552
- {
553
- "id": "xy-edge__Input: features 1x-Graph conv 1x",
554
- "source": "Input: features 1",
555
- "target": "Graph conv 1",
556
- "sourceHandle": "x",
557
- "targetHandle": "x"
558
- },
559
- {
560
- "id": "xy-edge__Input: graph edges 1edges-Graph conv 1edges",
561
- "source": "Input: graph edges 1",
562
- "target": "Graph conv 1",
563
- "sourceHandle": "edges",
564
- "targetHandle": "edges"
565
- },
566
- {
567
- "id": "xy-edge__Graph conv 1x-Linear 1x",
568
- "source": "Graph conv 1",
569
- "target": "Linear 1",
570
- "sourceHandle": "x",
571
- "targetHandle": "x"
572
- },
573
- {
574
- "id": "xy-edge__Input: label 1y-Supervised loss 1y",
575
- "source": "Input: label 1",
576
- "target": "Supervised loss 1",
577
- "sourceHandle": "y",
578
- "targetHandle": "y"
579
- },
580
- {
581
- "id": "xy-edge__Dropout 1x-Supervised loss 1x",
582
- "source": "Dropout 1",
583
- "target": "Supervised loss 1",
584
- "sourceHandle": "x",
585
- "targetHandle": "x"
586
- },
587
- {
588
- "id": "xy-edge__Supervised loss 1loss-Optimizer 1loss",
589
- "source": "Supervised loss 1",
590
- "target": "Optimizer 1",
591
- "sourceHandle": "loss",
592
- "targetHandle": "loss"
593
- },
594
- {
595
- "id": "Graph conv 1 Repeat 3",
596
- "source": "Graph conv 1",
597
- "target": "Repeat 3",
598
- "sourceHandle": "x",
599
- "targetHandle": "input"
600
- },
601
- {
602
- "id": "Repeat 3 Graph conv 1",
603
- "source": "Repeat 3",
604
- "target": "Graph conv 1",
605
- "sourceHandle": "output",
606
- "targetHandle": "x"
607
- },
608
- {
609
- "id": "Dropout 1 Repeat 1",
610
- "source": "Dropout 1",
611
- "target": "Repeat 1",
612
- "sourceHandle": "x",
613
- "targetHandle": "input"
614
- },
615
- {
616
- "id": "Repeat 1 Linear 1",
617
- "source": "Repeat 1",
618
- "target": "Linear 1",
619
- "sourceHandle": "output",
620
- "targetHandle": "x"
621
- }
622
- ]
623
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/requirements.txt CHANGED
@@ -1 +1,2 @@
1
- Faker
 
 
1
+ # Example of a requirements.txt file. LynxKite will automatically install anything you put here.
2
+ faker
examples/word2vec.py CHANGED
@@ -1,5 +1,4 @@
1
  from lynxkite.core.ops import op
2
- import staticvectors
3
  import pandas as pd
4
 
5
  ENV = "LynxKite Graph Analytics"
@@ -7,6 +6,8 @@ ENV = "LynxKite Graph Analytics"
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",
 
1
  from lynxkite.core.ops import op
 
2
  import pandas as pd
3
 
4
  ENV = "LynxKite Graph Analytics"
 
6
 
7
  @op(ENV, "Word2vec for the top 1000 words", slow=True)
8
  def word2vec_1000():
9
+ import staticvectors
10
+
11
  model = staticvectors.StaticVectors("neuml/word2vec-quantized")
12
  df = pd.read_csv(
13
  "https://gist.githubusercontent.com/deekayen/4148741/raw/98d35708fa344717d8eee15d11987de6c8e26d7d/1-1000.txt",
lynxkite-app/src/lynxkite_app/main.py CHANGED
@@ -151,6 +151,14 @@ async def upload(req: fastapi.Request):
151
  return {"status": "ok"}
152
 
153
 
 
 
 
 
 
 
 
 
154
  class SPAStaticFiles(StaticFiles):
155
  """Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
156
 
 
151
  return {"status": "ok"}
152
 
153
 
154
+ @app.post("/api/execute_workspace")
155
+ async def execute_workspace(name: str):
156
+ """Trigger and await the execution of a workspace."""
157
+ room = await crdt.ws_websocket_server.get_room(name)
158
+ ws_pyd = workspace.Workspace.model_validate(room.ws.to_py())
159
+ await crdt.execute(name, room.ws, ws_pyd)
160
+
161
+
162
  class SPAStaticFiles(StaticFiles):
163
  """Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
164
 
lynxkite-app/web/src/Code.tsx CHANGED
@@ -71,13 +71,13 @@ export default function Code() {
71
  </a>
72
  <div className="ws-name">{path}</div>
73
  <div className="tools text-secondary">
74
- <a href="">
75
  <Atom />
76
- </a>
77
- <a href="">
78
  <Backspace />
79
- </a>
80
- <a href={`/dir/${parentDir}`}>
81
  <Close />
82
  </a>
83
  </div>
 
71
  </a>
72
  <div className="ws-name">{path}</div>
73
  <div className="tools text-secondary">
74
+ <button className="btn btn-link">
75
  <Atom />
76
+ </button>
77
+ <button className="btn btn-link">
78
  <Backspace />
79
+ </button>
80
+ <a href={`/dir/${parentDir}`} className="btn btn-link">
81
  <Close />
82
  </a>
83
  </div>
lynxkite-app/web/src/index.css CHANGED
@@ -54,7 +54,7 @@ body {
54
  display: flex;
55
  align-items: center;
56
 
57
- a {
58
  color: oklch(75% 0.13 230);
59
  font-size: 1.5em;
60
  padding: 0 10px;
 
54
  display: flex;
55
  align-items: center;
56
 
57
+ .btn {
58
  color: oklch(75% 0.13 230);
59
  font-size: 1.5em;
60
  padding: 0 10px;
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -26,6 +26,8 @@ import Atom from "~icons/tabler/atom.jsx";
26
  // @ts-ignore
27
  import Backspace from "~icons/tabler/backspace.jsx";
28
  // @ts-ignore
 
 
29
  import Close from "~icons/tabler/x.jsx";
30
  import type { Workspace, WorkspaceNode } from "../apiTypes.ts";
31
  import favicon from "../assets/favicon.ico";
@@ -181,12 +183,16 @@ function LynxKiteFlow() {
181
  useEffect(() => {
182
  const handleKeyDown = (event: KeyboardEvent) => {
183
  // Show the node search dialog on "/".
184
- if (event.key === "/" && !nodeSearchSettings && !isTypingInFormElement()) {
 
185
  event.preventDefault();
186
  setNodeSearchSettings({
187
  pos: { x: 100, y: 100 },
188
  boxes: catalog.data![state.workspace.env!],
189
  });
 
 
 
190
  }
191
  };
192
  // TODO: Switch to keydown once https://github.com/xyflow/xyflow/pull/5055 is merged.
@@ -318,6 +324,12 @@ function LynxKiteFlow() {
318
  setMessage("File upload failed.");
319
  }
320
  }
 
 
 
 
 
 
321
  return (
322
  <div className="workspace">
323
  <div className="top-bar bg-neutral">
@@ -334,13 +346,16 @@ function LynxKiteFlow() {
334
  }}
335
  />
336
  <div className="tools text-secondary">
337
- <a href="">
338
  <Atom />
339
- </a>
340
- <a href="">
341
  <Backspace />
342
- </a>
343
- <a href={`/dir/${parentDir}`}>
 
 
 
344
  <Close />
345
  </a>
346
  </div>
 
26
  // @ts-ignore
27
  import Backspace from "~icons/tabler/backspace.jsx";
28
  // @ts-ignore
29
+ import Restart from "~icons/tabler/rotate-clockwise.jsx";
30
+ // @ts-ignore
31
  import Close from "~icons/tabler/x.jsx";
32
  import type { Workspace, WorkspaceNode } from "../apiTypes.ts";
33
  import favicon from "../assets/favicon.ico";
 
183
  useEffect(() => {
184
  const handleKeyDown = (event: KeyboardEvent) => {
185
  // Show the node search dialog on "/".
186
+ if (nodeSearchSettings || isTypingInFormElement()) return;
187
+ if (event.key === "/") {
188
  event.preventDefault();
189
  setNodeSearchSettings({
190
  pos: { x: 100, y: 100 },
191
  boxes: catalog.data![state.workspace.env!],
192
  });
193
+ } else if (event.key === "r") {
194
+ event.preventDefault();
195
+ executeWorkspace();
196
  }
197
  };
198
  // TODO: Switch to keydown once https://github.com/xyflow/xyflow/pull/5055 is merged.
 
324
  setMessage("File upload failed.");
325
  }
326
  }
327
+ async function executeWorkspace() {
328
+ const response = await axios.post(`/api/execute_workspace?name=${path}`);
329
+ if (response.status !== 200) {
330
+ setMessage("Workspace execution failed.");
331
+ }
332
+ }
333
  return (
334
  <div className="workspace">
335
  <div className="top-bar bg-neutral">
 
346
  }}
347
  />
348
  <div className="tools text-secondary">
349
+ <button className="btn btn-link">
350
  <Atom />
351
+ </button>
352
+ <button className="btn btn-link">
353
  <Backspace />
354
+ </button>
355
+ <button className="btn btn-link" onClick={executeWorkspace}>
356
+ <Restart />
357
+ </button>
358
+ <a className="btn btn-link" href={`/dir/${parentDir}`}>
359
  <Close />
360
  </a>
361
  </div>
lynxkite-app/web/tests/examples.spec.ts CHANGED
@@ -2,44 +2,21 @@
2
  import { expect, test } from "@playwright/test";
3
  import { Workspace } from "./lynxkite";
4
 
5
- test("LynxKite Graph Analytics example", async ({ page }) => {
6
- const ws = await Workspace.open(page, "NetworkX demo");
7
- await ws.expectErrorFree(process.env.CI ? 2000 : 1000);
8
- });
9
-
10
- test("Bio example", async ({ page }) => {
11
- const ws = await Workspace.open(page, "Bio demo");
12
- await ws.expectErrorFree();
13
- });
14
-
15
- test("Pytorch example", async ({ page }) => {
16
- const ws = await Workspace.open(page, "PyTorch demo");
17
- await ws.expectErrorFree();
18
- });
19
-
20
- test("AIMO example", async ({ page }) => {
21
- const ws = await Workspace.open(page, "AIMO");
22
- await ws.expectErrorFree();
23
- });
24
-
25
- test("LynxScribe example", async ({ page }) => {
26
- // Fails because of missing OPENAI_API_KEY
27
- const ws = await Workspace.open(page, "LynxScribe demo");
28
- await ws.expectErrorFree();
29
- });
30
-
31
- test("Graph RAG", async ({ page }) => {
32
- // Fails due to some issue with ChromaDB
33
- const ws = await Workspace.open(page, "Graph RAG");
34
- await ws.expectErrorFree(process.env.CI ? 2000 : 500);
35
- });
36
-
37
- test("Airlines demo", async ({ page }) => {
38
- const ws = await Workspace.open(page, "Airlines demo");
39
- await ws.expectErrorFree(process.env.CI ? 10000 : 500);
40
- });
41
-
42
- test("Pillow example", async ({ page }) => {
43
- const ws = await Workspace.open(page, "Image processing");
44
- await ws.expectErrorFree();
45
- });
 
2
  import { expect, test } from "@playwright/test";
3
  import { Workspace } from "./lynxkite";
4
 
5
+ const WORKSPACES = [
6
+ // "AIMO",
7
+ "Airlines demo",
8
+ "Bio Cypher demo",
9
+ // "Graph RAG",
10
+ "Image processing",
11
+ // "LynxScribe demo",
12
+ "NetworkX demo",
13
+ "Model use",
14
+ ];
15
+
16
+ for (const name of WORKSPACES) {
17
+ test(name, async ({ page }) => {
18
+ const ws = await Workspace.open(page, name);
19
+ await ws.execute();
20
+ await ws.expectErrorFree();
21
+ });
22
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lynxkite-app/web/tests/lynxkite.ts CHANGED
@@ -144,8 +144,14 @@ export class Workspace {
144
  await this.page.mouse.up();
145
  }
146
 
 
 
 
 
 
 
147
  async expectErrorFree(executionWaitTime?) {
148
- await expect(this.getBoxes().locator(".error").first()).not.toBeVisible();
149
  }
150
 
151
  async close() {
 
144
  await this.page.mouse.up();
145
  }
146
 
147
+ async execute() {
148
+ const request = this.page.waitForResponse(/api[/]execute_workspace/);
149
+ await this.page.keyboard.press("r");
150
+ await request;
151
+ }
152
+
153
  async expectErrorFree(executionWaitTime?) {
154
+ await expect(this.getBoxes().locator("text=⚠️").first()).not.toBeVisible();
155
  }
156
 
157
  async close() {
lynxkite-core/src/lynxkite/core/ops.py CHANGED
@@ -344,29 +344,27 @@ def load_user_scripts(workspace: str):
344
  assert path.is_relative_to(cwd), "Provided workspace path is invalid"
345
  for p in path.parents:
346
  print("checking user scripts in", p)
347
- for f in p.glob("*.py"):
348
- try:
349
- run_user_script(f)
350
- except Exception:
351
- traceback.print_exc()
352
  req = p / "requirements.txt"
353
  if req.exists():
354
  try:
355
  install_requirements(req)
356
  except Exception:
357
  traceback.print_exc()
 
 
 
 
 
358
  if p == cwd:
359
  break
360
 
361
 
362
  def install_requirements(req: pathlib.Path):
363
- cmd = ["uv", "pip", "install", "-r", str(req)]
364
- print(f"Running {' '.join(cmd)}")
365
  subprocess.check_call(cmd)
366
 
367
 
368
  def run_user_script(script_path: pathlib.Path):
369
- print(f"Running {script_path}...")
370
  spec = importlib.util.spec_from_file_location(script_path.stem, str(script_path))
371
  module = importlib.util.module_from_spec(spec)
372
  spec.loader.exec_module(module)
 
344
  assert path.is_relative_to(cwd), "Provided workspace path is invalid"
345
  for p in path.parents:
346
  print("checking user scripts in", p)
 
 
 
 
 
347
  req = p / "requirements.txt"
348
  if req.exists():
349
  try:
350
  install_requirements(req)
351
  except Exception:
352
  traceback.print_exc()
353
+ for f in p.glob("*.py"):
354
+ try:
355
+ run_user_script(f)
356
+ except Exception:
357
+ traceback.print_exc()
358
  if p == cwd:
359
  break
360
 
361
 
362
  def install_requirements(req: pathlib.Path):
363
+ cmd = ["uv", "pip", "install", "-q", "-r", str(req)]
 
364
  subprocess.check_call(cmd)
365
 
366
 
367
  def run_user_script(script_path: pathlib.Path):
 
368
  spec = importlib.util.spec_from_file_location(script_path.stem, str(script_path))
369
  module = importlib.util.module_from_spec(spec)
370
  spec.loader.exec_module(module)
lynxkite-graph-analytics/src/lynxkite_graph_analytics/networkx_ops.py CHANGED
@@ -152,11 +152,11 @@ def types_from_doc(doc: str) -> dict[str, type]:
152
 
153
  def wrapped(name: str, func):
154
  @functools.wraps(func)
155
- def wrapper(*args, **kwargs):
156
  for k, v in kwargs.items():
157
  if v == "None":
158
  kwargs[k] = None
159
- res = func(*args, **kwargs)
160
  # Figure out what the returned value is.
161
  if isinstance(res, nx.Graph):
162
  return res
 
152
 
153
  def wrapped(name: str, func):
154
  @functools.wraps(func)
155
+ async def wrapper(*args, **kwargs):
156
  for k, v in kwargs.items():
157
  if v == "None":
158
  kwargs[k] = None
159
+ res = await ops.slow(func)(*args, **kwargs)
160
  # Figure out what the returned value is.
161
  if isinstance(res, nx.Graph):
162
  return res