darabos commited on
Commit
289f6da
·
1 Parent(s): c0d2409

File upload with drag & drop.

Browse files
lynxkite-app/src/lynxkite_app/main.py CHANGED
@@ -128,6 +128,18 @@ async def service_post(req: fastapi.Request, module_path: str):
128
  return await module.api_service_post(req)
129
 
130
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  class SPAStaticFiles(StaticFiles):
132
  """Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
133
 
 
128
  return await module.api_service_post(req)
129
 
130
 
131
+ @app.post("/api/upload")
132
+ 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 = config.DATA_PATH / file.filename
137
+ assert file_path.is_relative_to(config.DATA_PATH), "Invalid file path"
138
+ with file_path.open("wb") as buffer:
139
+ shutil.copyfileobj(file.file, buffer)
140
+ return {"status": "ok"}
141
+
142
+
143
  class SPAStaticFiles(StaticFiles):
144
  """Route everything to index.html. https://stackoverflow.com/a/73552966/3318517"""
145
 
lynxkite-app/web/package-lock.json CHANGED
@@ -17,6 +17,7 @@
17
  "@syncedstore/react": "^0.6.0",
18
  "@types/node": "^22.10.1",
19
  "@xyflow/react": "^12.3.5",
 
20
  "daisyui": "^4.12.20",
21
  "echarts": "^5.5.1",
22
  "fuse.js": "^7.0.0",
@@ -2467,6 +2468,12 @@
2467
  "license": "MIT",
2468
  "optional": true
2469
  },
 
 
 
 
 
 
2470
  "node_modules/autoprefixer": {
2471
  "version": "10.4.20",
2472
  "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@@ -2505,6 +2512,17 @@
2505
  "postcss": "^8.1.0"
2506
  }
2507
  },
 
 
 
 
 
 
 
 
 
 
 
2508
  "node_modules/bail": {
2509
  "version": "2.0.2",
2510
  "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -2637,6 +2655,19 @@
2637
  "ieee754": "^1.1.13"
2638
  }
2639
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2640
  "node_modules/callsites": {
2641
  "version": "3.1.0",
2642
  "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2824,6 +2855,18 @@
2824
  "dev": true,
2825
  "license": "MIT"
2826
  },
 
 
 
 
 
 
 
 
 
 
 
 
2827
  "node_modules/comma-separated-tokens": {
2828
  "version": "2.0.3",
2829
  "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@@ -3126,6 +3169,15 @@
3126
  "node": ">=6"
3127
  }
3128
  },
 
 
 
 
 
 
 
 
 
3129
  "node_modules/dequal": {
3130
  "version": "2.0.3",
3131
  "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -3172,6 +3224,20 @@
3172
  "tslib": "^2.0.3"
3173
  }
3174
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3175
  "node_modules/eastasianwidth": {
3176
  "version": "0.2.0",
3177
  "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -3252,6 +3318,51 @@
3252
  "is-arrayish": "^0.2.1"
3253
  }
3254
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3255
  "node_modules/esbuild": {
3256
  "version": "0.25.0",
3257
  "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@@ -3639,6 +3750,26 @@
3639
  "dev": true,
3640
  "license": "ISC"
3641
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3642
  "node_modules/foreground-child": {
3643
  "version": "3.3.0",
3644
  "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
@@ -3656,6 +3787,21 @@
3656
  "url": "https://github.com/sponsors/isaacs"
3657
  }
3658
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3659
  "node_modules/fraction.js": {
3660
  "version": "4.3.7",
3661
  "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -3689,7 +3835,6 @@
3689
  "version": "1.1.2",
3690
  "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
3691
  "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
3692
- "dev": true,
3693
  "license": "MIT",
3694
  "funding": {
3695
  "url": "https://github.com/sponsors/ljharb"
@@ -3713,6 +3858,43 @@
3713
  "node": ">=6.9.0"
3714
  }
3715
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3716
  "node_modules/glob": {
3717
  "version": "10.4.5",
3718
  "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -3785,6 +3967,18 @@
3785
  "url": "https://github.com/sponsors/sindresorhus"
3786
  }
3787
  },
 
 
 
 
 
 
 
 
 
 
 
 
3788
  "node_modules/graphemer": {
3789
  "version": "1.4.0",
3790
  "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -3802,11 +3996,37 @@
3802
  "node": ">=8"
3803
  }
3804
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3805
  "node_modules/hasown": {
3806
  "version": "2.0.2",
3807
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
3808
  "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
3809
- "dev": true,
3810
  "license": "MIT",
3811
  "dependencies": {
3812
  "function-bind": "^1.1.2"
@@ -4523,6 +4743,15 @@
4523
  "license": "MIT",
4524
  "optional": true
4525
  },
 
 
 
 
 
 
 
 
 
4526
  "node_modules/mdast-util-from-markdown": {
4527
  "version": "2.0.2",
4528
  "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
@@ -5142,6 +5371,27 @@
5142
  "node": ">=8.6"
5143
  }
5144
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5145
  "node_modules/minimatch": {
5146
  "version": "3.1.2",
5147
  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5766,6 +6016,12 @@
5766
  "url": "https://github.com/sponsors/wooorm"
5767
  }
5768
  },
 
 
 
 
 
 
5769
  "node_modules/prr": {
5770
  "version": "1.0.1",
5771
  "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
 
17
  "@syncedstore/react": "^0.6.0",
18
  "@types/node": "^22.10.1",
19
  "@xyflow/react": "^12.3.5",
20
+ "axios": "^1.8.2",
21
  "daisyui": "^4.12.20",
22
  "echarts": "^5.5.1",
23
  "fuse.js": "^7.0.0",
 
2468
  "license": "MIT",
2469
  "optional": true
2470
  },
2471
+ "node_modules/asynckit": {
2472
+ "version": "0.4.0",
2473
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
2474
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
2475
+ "license": "MIT"
2476
+ },
2477
  "node_modules/autoprefixer": {
2478
  "version": "10.4.20",
2479
  "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
 
2512
  "postcss": "^8.1.0"
2513
  }
2514
  },
2515
+ "node_modules/axios": {
2516
+ "version": "1.8.2",
2517
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
2518
+ "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
2519
+ "license": "MIT",
2520
+ "dependencies": {
2521
+ "follow-redirects": "^1.15.6",
2522
+ "form-data": "^4.0.0",
2523
+ "proxy-from-env": "^1.1.0"
2524
+ }
2525
+ },
2526
  "node_modules/bail": {
2527
  "version": "2.0.2",
2528
  "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
 
2655
  "ieee754": "^1.1.13"
2656
  }
2657
  },
2658
+ "node_modules/call-bind-apply-helpers": {
2659
+ "version": "1.0.2",
2660
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
2661
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
2662
+ "license": "MIT",
2663
+ "dependencies": {
2664
+ "es-errors": "^1.3.0",
2665
+ "function-bind": "^1.1.2"
2666
+ },
2667
+ "engines": {
2668
+ "node": ">= 0.4"
2669
+ }
2670
+ },
2671
  "node_modules/callsites": {
2672
  "version": "3.1.0",
2673
  "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
 
2855
  "dev": true,
2856
  "license": "MIT"
2857
  },
2858
+ "node_modules/combined-stream": {
2859
+ "version": "1.0.8",
2860
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
2861
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
2862
+ "license": "MIT",
2863
+ "dependencies": {
2864
+ "delayed-stream": "~1.0.0"
2865
+ },
2866
+ "engines": {
2867
+ "node": ">= 0.8"
2868
+ }
2869
+ },
2870
  "node_modules/comma-separated-tokens": {
2871
  "version": "2.0.3",
2872
  "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
 
3169
  "node": ">=6"
3170
  }
3171
  },
3172
+ "node_modules/delayed-stream": {
3173
+ "version": "1.0.0",
3174
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
3175
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
3176
+ "license": "MIT",
3177
+ "engines": {
3178
+ "node": ">=0.4.0"
3179
+ }
3180
+ },
3181
  "node_modules/dequal": {
3182
  "version": "2.0.3",
3183
  "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
 
3224
  "tslib": "^2.0.3"
3225
  }
3226
  },
3227
+ "node_modules/dunder-proto": {
3228
+ "version": "1.0.1",
3229
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
3230
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
3231
+ "license": "MIT",
3232
+ "dependencies": {
3233
+ "call-bind-apply-helpers": "^1.0.1",
3234
+ "es-errors": "^1.3.0",
3235
+ "gopd": "^1.2.0"
3236
+ },
3237
+ "engines": {
3238
+ "node": ">= 0.4"
3239
+ }
3240
+ },
3241
  "node_modules/eastasianwidth": {
3242
  "version": "0.2.0",
3243
  "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
 
3318
  "is-arrayish": "^0.2.1"
3319
  }
3320
  },
3321
+ "node_modules/es-define-property": {
3322
+ "version": "1.0.1",
3323
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
3324
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
3325
+ "license": "MIT",
3326
+ "engines": {
3327
+ "node": ">= 0.4"
3328
+ }
3329
+ },
3330
+ "node_modules/es-errors": {
3331
+ "version": "1.3.0",
3332
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
3333
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
3334
+ "license": "MIT",
3335
+ "engines": {
3336
+ "node": ">= 0.4"
3337
+ }
3338
+ },
3339
+ "node_modules/es-object-atoms": {
3340
+ "version": "1.1.1",
3341
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
3342
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
3343
+ "license": "MIT",
3344
+ "dependencies": {
3345
+ "es-errors": "^1.3.0"
3346
+ },
3347
+ "engines": {
3348
+ "node": ">= 0.4"
3349
+ }
3350
+ },
3351
+ "node_modules/es-set-tostringtag": {
3352
+ "version": "2.1.0",
3353
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
3354
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
3355
+ "license": "MIT",
3356
+ "dependencies": {
3357
+ "es-errors": "^1.3.0",
3358
+ "get-intrinsic": "^1.2.6",
3359
+ "has-tostringtag": "^1.0.2",
3360
+ "hasown": "^2.0.2"
3361
+ },
3362
+ "engines": {
3363
+ "node": ">= 0.4"
3364
+ }
3365
+ },
3366
  "node_modules/esbuild": {
3367
  "version": "0.25.0",
3368
  "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
 
3750
  "dev": true,
3751
  "license": "ISC"
3752
  },
3753
+ "node_modules/follow-redirects": {
3754
+ "version": "1.15.9",
3755
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
3756
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
3757
+ "funding": [
3758
+ {
3759
+ "type": "individual",
3760
+ "url": "https://github.com/sponsors/RubenVerborgh"
3761
+ }
3762
+ ],
3763
+ "license": "MIT",
3764
+ "engines": {
3765
+ "node": ">=4.0"
3766
+ },
3767
+ "peerDependenciesMeta": {
3768
+ "debug": {
3769
+ "optional": true
3770
+ }
3771
+ }
3772
+ },
3773
  "node_modules/foreground-child": {
3774
  "version": "3.3.0",
3775
  "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
 
3787
  "url": "https://github.com/sponsors/isaacs"
3788
  }
3789
  },
3790
+ "node_modules/form-data": {
3791
+ "version": "4.0.2",
3792
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
3793
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
3794
+ "license": "MIT",
3795
+ "dependencies": {
3796
+ "asynckit": "^0.4.0",
3797
+ "combined-stream": "^1.0.8",
3798
+ "es-set-tostringtag": "^2.1.0",
3799
+ "mime-types": "^2.1.12"
3800
+ },
3801
+ "engines": {
3802
+ "node": ">= 6"
3803
+ }
3804
+ },
3805
  "node_modules/fraction.js": {
3806
  "version": "4.3.7",
3807
  "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
 
3835
  "version": "1.1.2",
3836
  "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
3837
  "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
 
3838
  "license": "MIT",
3839
  "funding": {
3840
  "url": "https://github.com/sponsors/ljharb"
 
3858
  "node": ">=6.9.0"
3859
  }
3860
  },
3861
+ "node_modules/get-intrinsic": {
3862
+ "version": "1.3.0",
3863
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
3864
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
3865
+ "license": "MIT",
3866
+ "dependencies": {
3867
+ "call-bind-apply-helpers": "^1.0.2",
3868
+ "es-define-property": "^1.0.1",
3869
+ "es-errors": "^1.3.0",
3870
+ "es-object-atoms": "^1.1.1",
3871
+ "function-bind": "^1.1.2",
3872
+ "get-proto": "^1.0.1",
3873
+ "gopd": "^1.2.0",
3874
+ "has-symbols": "^1.1.0",
3875
+ "hasown": "^2.0.2",
3876
+ "math-intrinsics": "^1.1.0"
3877
+ },
3878
+ "engines": {
3879
+ "node": ">= 0.4"
3880
+ },
3881
+ "funding": {
3882
+ "url": "https://github.com/sponsors/ljharb"
3883
+ }
3884
+ },
3885
+ "node_modules/get-proto": {
3886
+ "version": "1.0.1",
3887
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
3888
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
3889
+ "license": "MIT",
3890
+ "dependencies": {
3891
+ "dunder-proto": "^1.0.1",
3892
+ "es-object-atoms": "^1.0.0"
3893
+ },
3894
+ "engines": {
3895
+ "node": ">= 0.4"
3896
+ }
3897
+ },
3898
  "node_modules/glob": {
3899
  "version": "10.4.5",
3900
  "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
 
3967
  "url": "https://github.com/sponsors/sindresorhus"
3968
  }
3969
  },
3970
+ "node_modules/gopd": {
3971
+ "version": "1.2.0",
3972
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
3973
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
3974
+ "license": "MIT",
3975
+ "engines": {
3976
+ "node": ">= 0.4"
3977
+ },
3978
+ "funding": {
3979
+ "url": "https://github.com/sponsors/ljharb"
3980
+ }
3981
+ },
3982
  "node_modules/graphemer": {
3983
  "version": "1.4.0",
3984
  "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
 
3996
  "node": ">=8"
3997
  }
3998
  },
3999
+ "node_modules/has-symbols": {
4000
+ "version": "1.1.0",
4001
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
4002
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
4003
+ "license": "MIT",
4004
+ "engines": {
4005
+ "node": ">= 0.4"
4006
+ },
4007
+ "funding": {
4008
+ "url": "https://github.com/sponsors/ljharb"
4009
+ }
4010
+ },
4011
+ "node_modules/has-tostringtag": {
4012
+ "version": "1.0.2",
4013
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
4014
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
4015
+ "license": "MIT",
4016
+ "dependencies": {
4017
+ "has-symbols": "^1.0.3"
4018
+ },
4019
+ "engines": {
4020
+ "node": ">= 0.4"
4021
+ },
4022
+ "funding": {
4023
+ "url": "https://github.com/sponsors/ljharb"
4024
+ }
4025
+ },
4026
  "node_modules/hasown": {
4027
  "version": "2.0.2",
4028
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
4029
  "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
 
4030
  "license": "MIT",
4031
  "dependencies": {
4032
  "function-bind": "^1.1.2"
 
4743
  "license": "MIT",
4744
  "optional": true
4745
  },
4746
+ "node_modules/math-intrinsics": {
4747
+ "version": "1.1.0",
4748
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
4749
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
4750
+ "license": "MIT",
4751
+ "engines": {
4752
+ "node": ">= 0.4"
4753
+ }
4754
+ },
4755
  "node_modules/mdast-util-from-markdown": {
4756
  "version": "2.0.2",
4757
  "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
 
5371
  "node": ">=8.6"
5372
  }
5373
  },
5374
+ "node_modules/mime-db": {
5375
+ "version": "1.52.0",
5376
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
5377
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
5378
+ "license": "MIT",
5379
+ "engines": {
5380
+ "node": ">= 0.6"
5381
+ }
5382
+ },
5383
+ "node_modules/mime-types": {
5384
+ "version": "2.1.35",
5385
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
5386
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
5387
+ "license": "MIT",
5388
+ "dependencies": {
5389
+ "mime-db": "1.52.0"
5390
+ },
5391
+ "engines": {
5392
+ "node": ">= 0.6"
5393
+ }
5394
+ },
5395
  "node_modules/minimatch": {
5396
  "version": "3.1.2",
5397
  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
 
6016
  "url": "https://github.com/sponsors/wooorm"
6017
  }
6018
  },
6019
+ "node_modules/proxy-from-env": {
6020
+ "version": "1.1.0",
6021
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
6022
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
6023
+ "license": "MIT"
6024
+ },
6025
  "node_modules/prr": {
6026
  "version": "1.0.1",
6027
  "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
lynxkite-app/web/package.json CHANGED
@@ -20,6 +20,7 @@
20
  "@syncedstore/react": "^0.6.0",
21
  "@types/node": "^22.10.1",
22
  "@xyflow/react": "^12.3.5",
 
23
  "daisyui": "^4.12.20",
24
  "echarts": "^5.5.1",
25
  "fuse.js": "^7.0.0",
@@ -35,6 +36,8 @@
35
  },
36
  "devDependencies": {
37
  "@eslint/js": "^9.15.0",
 
 
38
  "@types/react": "^18.3.14",
39
  "@types/react-dom": "^18.3.2",
40
  "@vitejs/plugin-react-swc": "^3.5.0",
@@ -47,9 +50,7 @@
47
  "tailwindcss": "^3.4.16",
48
  "typescript": "~5.6.2",
49
  "typescript-eslint": "^8.15.0",
50
- "vite": "^6.2.0",
51
- "@playwright/test": "^1.50.1",
52
- "@types/node": "^22.13.1"
53
  },
54
  "optionalDependencies": {
55
  "@rollup/rollup-linux-x64-gnu": "^4.28.1"
 
20
  "@syncedstore/react": "^0.6.0",
21
  "@types/node": "^22.10.1",
22
  "@xyflow/react": "^12.3.5",
23
+ "axios": "^1.8.2",
24
  "daisyui": "^4.12.20",
25
  "echarts": "^5.5.1",
26
  "fuse.js": "^7.0.0",
 
36
  },
37
  "devDependencies": {
38
  "@eslint/js": "^9.15.0",
39
+ "@playwright/test": "^1.50.1",
40
+ "@types/node": "^22.13.1",
41
  "@types/react": "^18.3.14",
42
  "@types/react-dom": "^18.3.2",
43
  "@vitejs/plugin-react-swc": "^3.5.0",
 
50
  "tailwindcss": "^3.4.16",
51
  "typescript": "~5.6.2",
52
  "typescript-eslint": "^8.15.0",
53
+ "vite": "^6.2.0"
 
 
54
  },
55
  "optionalDependencies": {
56
  "@rollup/rollup-linux-x64-gnu": "^4.28.1"
lynxkite-app/web/src/index.css CHANGED
@@ -240,6 +240,22 @@ body {
240
  background: transparent;
241
  color: #39bcf3;
242
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
244
 
245
  .params-expander {
 
240
  background: transparent;
241
  color: #39bcf3;
242
  }
243
+
244
+ .workspace-message {
245
+ position: absolute;
246
+ left: 50%;
247
+ bottom: 20px;
248
+ transform: translateX(-50%);
249
+ box-shadow: 0 5px 50px 0px #8008;
250
+ padding: 10px 40px 10px 20px;
251
+ border-radius: 5px;
252
+
253
+ .close {
254
+ position: absolute;
255
+ right: 10px;
256
+ cursor: pointer;
257
+ }
258
+ }
259
  }
260
 
261
  .params-expander {
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -14,6 +14,7 @@ import {
14
  useReactFlow,
15
  useUpdateNodeInternals,
16
  } from "@xyflow/react";
 
17
  import {
18
  type MouseEvent,
19
  useCallback,
@@ -62,6 +63,7 @@ function LynxKiteFlow() {
62
  const [edges, setEdges] = useState([] as Edge[]);
63
  const { path } = useParams();
64
  const [state, setState] = useState({ workspace: {} as Workspace });
 
65
  useEffect(() => {
66
  const state = syncedStore({ workspace: {} as Workspace });
67
  setState(state);
@@ -236,33 +238,44 @@ function LynxKiteFlow() {
236
  },
237
  [catalog, state, nodeSearchSettings, suppressSearchUntil, closeNodeSearch],
238
  );
239
- const addNode = useCallback(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  (meta: OpsOp) => {
241
- const node: Partial<WorkspaceNode> = {
242
- type: meta.type,
243
- data: {
244
- meta: meta,
245
- title: meta.name,
246
- params: Object.fromEntries(
247
- Object.values(meta.params).map((p) => [p.name, p.default]),
248
- ),
249
- },
250
- };
251
  const nss = nodeSearchSettings!;
252
  node.position = reactFlow.screenToFlowPosition({
253
  x: nss.pos.x,
254
  y: nss.pos.y,
255
  });
256
- const title = meta.name;
257
- let i = 1;
258
- node.id = `${title} ${i}`;
259
- const wnodes = state.workspace.nodes!;
260
- while (wnodes.find((x) => x.id === node.id)) {
261
- i += 1;
262
- node.id = `${title} ${i}`;
263
- }
264
- wnodes.push(node as WorkspaceNode);
265
- setNodes([...nodes, node as WorkspaceNode]);
266
  closeNodeSearch();
267
  },
268
  [nodeSearchSettings, state, reactFlow, nodes, closeNodeSearch],
@@ -284,6 +297,39 @@ function LynxKiteFlow() {
284
  [state],
285
  );
286
  const parentDir = path!.split("/").slice(0, -1).join("/");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  return (
288
  <div className="workspace">
289
  <div className="top-bar bg-neutral">
@@ -310,7 +356,11 @@ function LynxKiteFlow() {
310
  </a>
311
  </div>
312
  </div>
313
- <div style={{ height: "100%", width: "100vw" }}>
 
 
 
 
314
  <LynxKiteState.Provider value={state}>
315
  <ReactFlow
316
  nodes={nodes}
@@ -344,11 +394,19 @@ function LynxKiteFlow() {
344
  pos={nodeSearchSettings.pos}
345
  boxes={nodeSearchSettings.boxes}
346
  onCancel={closeNodeSearch}
347
- onAdd={addNode}
348
  />
349
  )}
350
  </ReactFlow>
351
  </LynxKiteState.Provider>
 
 
 
 
 
 
 
 
352
  </div>
353
  </div>
354
  );
 
14
  useReactFlow,
15
  useUpdateNodeInternals,
16
  } from "@xyflow/react";
17
+ import axios from "axios";
18
  import {
19
  type MouseEvent,
20
  useCallback,
 
63
  const [edges, setEdges] = useState([] as Edge[]);
64
  const { path } = useParams();
65
  const [state, setState] = useState({ workspace: {} as Workspace });
66
+ const [message, setMessage] = useState(null as string | null);
67
  useEffect(() => {
68
  const state = syncedStore({ workspace: {} as Workspace });
69
  setState(state);
 
238
  },
239
  [catalog, state, nodeSearchSettings, suppressSearchUntil, closeNodeSearch],
240
  );
241
+ function addNode(
242
+ node: Partial<WorkspaceNode>,
243
+ state: { workspace: Workspace },
244
+ nodes: Node[],
245
+ ) {
246
+ const title = node.title;
247
+ let i = 1;
248
+ node.id = `${title} ${i}`;
249
+ const wnodes = state.workspace.nodes!;
250
+ while (wnodes.find((x) => x.id === node.id)) {
251
+ i += 1;
252
+ node.id = `${title} ${i}`;
253
+ }
254
+ wnodes.push(node as WorkspaceNode);
255
+ setNodes([...nodes, node as WorkspaceNode]);
256
+ }
257
+ function nodeFromMeta(meta: OpsOp): Partial<WorkspaceNode> {
258
+ const node: Partial<WorkspaceNode> = {
259
+ type: meta.type,
260
+ data: {
261
+ meta: meta,
262
+ title: meta.name,
263
+ params: Object.fromEntries(
264
+ Object.values(meta.params).map((p) => [p.name, p.default]),
265
+ ),
266
+ },
267
+ };
268
+ return node;
269
+ }
270
+ const addNodeFromSearch = useCallback(
271
  (meta: OpsOp) => {
272
+ const node = nodeFromMeta(meta);
 
 
 
 
 
 
 
 
 
273
  const nss = nodeSearchSettings!;
274
  node.position = reactFlow.screenToFlowPosition({
275
  x: nss.pos.x,
276
  y: nss.pos.y,
277
  });
278
+ addNode(node, state, nodes);
 
 
 
 
 
 
 
 
 
279
  closeNodeSearch();
280
  },
281
  [nodeSearchSettings, state, reactFlow, nodes, closeNodeSearch],
 
297
  [state],
298
  );
299
  const parentDir = path!.split("/").slice(0, -1).join("/");
300
+ function onDragOver(e: React.DragEvent<HTMLDivElement>) {
301
+ e.stopPropagation();
302
+ e.preventDefault();
303
+ }
304
+ async function onDrop(e: React.DragEvent<HTMLDivElement>) {
305
+ e.stopPropagation();
306
+ e.preventDefault();
307
+ const file = e.dataTransfer.files[0];
308
+ const formData = new FormData();
309
+ formData.append("file", file);
310
+ try {
311
+ await axios.post("/api/upload", formData, {
312
+ onUploadProgress: (progressEvent) => {
313
+ const percentCompleted = Math.round(
314
+ (100 * progressEvent.loaded) / progressEvent.total!,
315
+ );
316
+ if (percentCompleted === 100) setMessage("Processing file...");
317
+ else setMessage(`Uploading ${percentCompleted}%`);
318
+ },
319
+ });
320
+ setMessage(null);
321
+ const cat = catalog.data![state.workspace.env!];
322
+ const node = nodeFromMeta(cat["Import file"]);
323
+ node.position = reactFlow.screenToFlowPosition({
324
+ x: e.clientX,
325
+ y: e.clientY,
326
+ });
327
+ node.data!.params.file_path = file.name;
328
+ addNode(node, state, nodes);
329
+ } catch (error) {
330
+ setMessage("File upload failed.");
331
+ }
332
+ }
333
  return (
334
  <div className="workspace">
335
  <div className="top-bar bg-neutral">
 
356
  </a>
357
  </div>
358
  </div>
359
+ <div
360
+ style={{ height: "100%", width: "100vw" }}
361
+ onDragOver={onDragOver}
362
+ onDrop={onDrop}
363
+ >
364
  <LynxKiteState.Provider value={state}>
365
  <ReactFlow
366
  nodes={nodes}
 
394
  pos={nodeSearchSettings.pos}
395
  boxes={nodeSearchSettings.boxes}
396
  onCancel={closeNodeSearch}
397
+ onAdd={addNodeFromSearch}
398
  />
399
  )}
400
  </ReactFlow>
401
  </LynxKiteState.Provider>
402
+ {message && (
403
+ <div className="workspace-message">
404
+ <span className="close" onClick={() => setMessage(null)}>
405
+ <Close />
406
+ </span>
407
+ {message}
408
+ </div>
409
+ )}
410
  </div>
411
  </div>
412
  );