prompt_objects 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/CLAUDE.md +112 -44
- data/Gemfile.lock +31 -29
- data/README.md +5 -0
- data/frontend/index.html +5 -1
- data/frontend/package-lock.json +123 -0
- data/frontend/package.json +4 -0
- data/frontend/src/App.tsx +70 -71
- data/frontend/src/canvas/CanvasView.tsx +113 -0
- data/frontend/src/canvas/ForceLayout.ts +115 -0
- data/frontend/src/canvas/SceneManager.ts +587 -0
- data/frontend/src/canvas/canvasStore.ts +47 -0
- data/frontend/src/canvas/constants.ts +95 -0
- data/frontend/src/canvas/controls/CameraControls.ts +98 -0
- data/frontend/src/canvas/edges/MessageArc.ts +149 -0
- data/frontend/src/canvas/inspector/InspectorPanel.tsx +31 -0
- data/frontend/src/canvas/inspector/POInspector.tsx +262 -0
- data/frontend/src/canvas/inspector/ToolCallInspector.tsx +67 -0
- data/frontend/src/canvas/nodes/PONode.ts +249 -0
- data/frontend/src/canvas/nodes/ToolCallNode.ts +116 -0
- data/frontend/src/canvas/types.ts +24 -0
- data/frontend/src/components/ContextMenu.tsx +5 -4
- data/frontend/src/components/Inspector.tsx +232 -0
- data/frontend/src/components/MarkdownMessage.tsx +22 -20
- data/frontend/src/components/MethodList.tsx +90 -0
- data/frontend/src/components/ModelSelector.tsx +13 -14
- data/frontend/src/components/NotificationPanel.tsx +29 -33
- data/frontend/src/components/ObjectList.tsx +78 -0
- data/frontend/src/components/PaneSlot.tsx +30 -0
- data/frontend/src/components/SourcePane.tsx +202 -0
- data/frontend/src/components/SystemBar.tsx +74 -0
- data/frontend/src/components/Transcript.tsx +76 -0
- data/frontend/src/components/UsagePanel.tsx +27 -27
- data/frontend/src/components/Workspace.tsx +260 -0
- data/frontend/src/components/index.ts +10 -9
- data/frontend/src/hooks/useResize.ts +55 -0
- data/frontend/src/hooks/useWebSocket.ts +274 -189
- data/frontend/src/index.css +69 -3
- data/frontend/src/store/index.ts +23 -0
- data/frontend/src/types/index.ts +5 -0
- data/frontend/tailwind.config.js +28 -9
- data/lib/prompt_objects/capability.rb +23 -1
- data/lib/prompt_objects/connectors/mcp.rb +5 -22
- data/lib/prompt_objects/environment.rb +8 -0
- data/lib/prompt_objects/llm/openai_adapter.rb +22 -0
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +1 -2
- data/lib/prompt_objects/mcp/tools/inspect_po.rb +1 -31
- data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +2 -8
- data/lib/prompt_objects/primitives/list_files.rb +1 -2
- data/lib/prompt_objects/prompt_object.rb +150 -6
- data/lib/prompt_objects/server/api/routes.rb +3 -48
- data/lib/prompt_objects/server/app.rb +9 -0
- data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +4345 -0
- data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +1 -0
- data/lib/prompt_objects/server/public/index.html +7 -3
- data/lib/prompt_objects/server/websocket_handler.rb +23 -100
- data/lib/prompt_objects/server.rb +6 -62
- data/prompt_objects.gemspec +1 -1
- data/templates/arc-agi-1/primitives/find_objects.rb +1 -1
- data/templates/arc-agi-1/primitives/grid_diff.rb +2 -2
- data/templates/arc-agi-1/primitives/grid_info.rb +1 -1
- data/templates/arc-agi-1/primitives/grid_transform.rb +1 -1
- data/templates/arc-agi-1/primitives/render_grid.rb +1 -0
- data/templates/arc-agi-1/primitives/test_solution.rb +3 -0
- metadata +26 -14
- data/frontend/src/components/CapabilitiesPanel.tsx +0 -141
- data/frontend/src/components/ChatPanel.tsx +0 -288
- data/frontend/src/components/Dashboard.tsx +0 -83
- data/frontend/src/components/Header.tsx +0 -141
- data/frontend/src/components/MessageBus.tsx +0 -56
- data/frontend/src/components/POCard.tsx +0 -56
- data/frontend/src/components/PODetail.tsx +0 -124
- data/frontend/src/components/PromptPanel.tsx +0 -156
- data/frontend/src/components/SessionsPanel.tsx +0 -174
- data/frontend/src/components/ThreadsSidebar.tsx +0 -163
- data/lib/prompt_objects/server/public/assets/index-Bkme6COu.css +0 -1
- data/lib/prompt_objects/server/public/assets/index-CQ7lVDF_.js +0 -77
data/frontend/package-lock.json
CHANGED
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
12
12
|
"clsx": "^2.1.1",
|
|
13
|
+
"d3-force": "^3.0.0",
|
|
13
14
|
"react": "^18.3.1",
|
|
14
15
|
"react-dom": "^18.3.1",
|
|
15
16
|
"react-markdown": "^10.1.0",
|
|
16
17
|
"react-syntax-highlighter": "^16.1.0",
|
|
17
18
|
"remark-gfm": "^4.0.1",
|
|
19
|
+
"three": "^0.182.0",
|
|
18
20
|
"zustand": "^5.0.0"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
23
|
+
"@types/d3-force": "^3.0.10",
|
|
21
24
|
"@types/react": "^18.3.12",
|
|
22
25
|
"@types/react-dom": "^18.3.1",
|
|
26
|
+
"@types/three": "^0.182.0",
|
|
23
27
|
"@vitejs/plugin-react": "^4.3.4",
|
|
24
28
|
"autoprefixer": "^10.4.20",
|
|
25
29
|
"postcss": "^8.4.49",
|
|
@@ -332,6 +336,13 @@
|
|
|
332
336
|
"node": ">=6.9.0"
|
|
333
337
|
}
|
|
334
338
|
},
|
|
339
|
+
"node_modules/@dimforge/rapier3d-compat": {
|
|
340
|
+
"version": "0.12.0",
|
|
341
|
+
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
|
342
|
+
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
|
343
|
+
"dev": true,
|
|
344
|
+
"license": "Apache-2.0"
|
|
345
|
+
},
|
|
335
346
|
"node_modules/@esbuild/aix-ppc64": {
|
|
336
347
|
"version": "0.25.12",
|
|
337
348
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
|
@@ -1219,6 +1230,13 @@
|
|
|
1219
1230
|
"win32"
|
|
1220
1231
|
]
|
|
1221
1232
|
},
|
|
1233
|
+
"node_modules/@tweenjs/tween.js": {
|
|
1234
|
+
"version": "23.1.3",
|
|
1235
|
+
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
|
1236
|
+
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
|
1237
|
+
"dev": true,
|
|
1238
|
+
"license": "MIT"
|
|
1239
|
+
},
|
|
1222
1240
|
"node_modules/@types/babel__core": {
|
|
1223
1241
|
"version": "7.20.5",
|
|
1224
1242
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
|
@@ -1264,6 +1282,13 @@
|
|
|
1264
1282
|
"@babel/types": "^7.28.2"
|
|
1265
1283
|
}
|
|
1266
1284
|
},
|
|
1285
|
+
"node_modules/@types/d3-force": {
|
|
1286
|
+
"version": "3.0.10",
|
|
1287
|
+
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
|
|
1288
|
+
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
|
|
1289
|
+
"dev": true,
|
|
1290
|
+
"license": "MIT"
|
|
1291
|
+
},
|
|
1267
1292
|
"node_modules/@types/debug": {
|
|
1268
1293
|
"version": "4.1.12",
|
|
1269
1294
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
|
@@ -1353,12 +1378,42 @@
|
|
|
1353
1378
|
"@types/react": "*"
|
|
1354
1379
|
}
|
|
1355
1380
|
},
|
|
1381
|
+
"node_modules/@types/stats.js": {
|
|
1382
|
+
"version": "0.17.4",
|
|
1383
|
+
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
|
1384
|
+
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
|
1385
|
+
"dev": true,
|
|
1386
|
+
"license": "MIT"
|
|
1387
|
+
},
|
|
1388
|
+
"node_modules/@types/three": {
|
|
1389
|
+
"version": "0.182.0",
|
|
1390
|
+
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz",
|
|
1391
|
+
"integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==",
|
|
1392
|
+
"dev": true,
|
|
1393
|
+
"license": "MIT",
|
|
1394
|
+
"dependencies": {
|
|
1395
|
+
"@dimforge/rapier3d-compat": "~0.12.0",
|
|
1396
|
+
"@tweenjs/tween.js": "~23.1.3",
|
|
1397
|
+
"@types/stats.js": "*",
|
|
1398
|
+
"@types/webxr": ">=0.5.17",
|
|
1399
|
+
"@webgpu/types": "*",
|
|
1400
|
+
"fflate": "~0.8.2",
|
|
1401
|
+
"meshoptimizer": "~0.22.0"
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1356
1404
|
"node_modules/@types/unist": {
|
|
1357
1405
|
"version": "3.0.3",
|
|
1358
1406
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
|
1359
1407
|
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
|
1360
1408
|
"license": "MIT"
|
|
1361
1409
|
},
|
|
1410
|
+
"node_modules/@types/webxr": {
|
|
1411
|
+
"version": "0.5.24",
|
|
1412
|
+
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
|
1413
|
+
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
|
1414
|
+
"dev": true,
|
|
1415
|
+
"license": "MIT"
|
|
1416
|
+
},
|
|
1362
1417
|
"node_modules/@ungap/structured-clone": {
|
|
1363
1418
|
"version": "1.3.0",
|
|
1364
1419
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
|
@@ -1386,6 +1441,13 @@
|
|
|
1386
1441
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
1387
1442
|
}
|
|
1388
1443
|
},
|
|
1444
|
+
"node_modules/@webgpu/types": {
|
|
1445
|
+
"version": "0.1.69",
|
|
1446
|
+
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
|
|
1447
|
+
"integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==",
|
|
1448
|
+
"dev": true,
|
|
1449
|
+
"license": "BSD-3-Clause"
|
|
1450
|
+
},
|
|
1389
1451
|
"node_modules/any-promise": {
|
|
1390
1452
|
"version": "1.3.0",
|
|
1391
1453
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
|
@@ -1705,6 +1767,47 @@
|
|
|
1705
1767
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
|
1706
1768
|
"license": "MIT"
|
|
1707
1769
|
},
|
|
1770
|
+
"node_modules/d3-dispatch": {
|
|
1771
|
+
"version": "3.0.1",
|
|
1772
|
+
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
|
1773
|
+
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
|
1774
|
+
"license": "ISC",
|
|
1775
|
+
"engines": {
|
|
1776
|
+
"node": ">=12"
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
"node_modules/d3-force": {
|
|
1780
|
+
"version": "3.0.0",
|
|
1781
|
+
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
|
|
1782
|
+
"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
|
|
1783
|
+
"license": "ISC",
|
|
1784
|
+
"dependencies": {
|
|
1785
|
+
"d3-dispatch": "1 - 3",
|
|
1786
|
+
"d3-quadtree": "1 - 3",
|
|
1787
|
+
"d3-timer": "1 - 3"
|
|
1788
|
+
},
|
|
1789
|
+
"engines": {
|
|
1790
|
+
"node": ">=12"
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
"node_modules/d3-quadtree": {
|
|
1794
|
+
"version": "3.0.1",
|
|
1795
|
+
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
|
|
1796
|
+
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
|
|
1797
|
+
"license": "ISC",
|
|
1798
|
+
"engines": {
|
|
1799
|
+
"node": ">=12"
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
"node_modules/d3-timer": {
|
|
1803
|
+
"version": "3.0.1",
|
|
1804
|
+
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
|
1805
|
+
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
|
1806
|
+
"license": "ISC",
|
|
1807
|
+
"engines": {
|
|
1808
|
+
"node": ">=12"
|
|
1809
|
+
}
|
|
1810
|
+
},
|
|
1708
1811
|
"node_modules/debug": {
|
|
1709
1812
|
"version": "4.4.3",
|
|
1710
1813
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
|
@@ -1911,6 +2014,13 @@
|
|
|
1911
2014
|
"url": "https://github.com/sponsors/wooorm"
|
|
1912
2015
|
}
|
|
1913
2016
|
},
|
|
2017
|
+
"node_modules/fflate": {
|
|
2018
|
+
"version": "0.8.2",
|
|
2019
|
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
|
2020
|
+
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
|
2021
|
+
"dev": true,
|
|
2022
|
+
"license": "MIT"
|
|
2023
|
+
},
|
|
1914
2024
|
"node_modules/fill-range": {
|
|
1915
2025
|
"version": "7.1.1",
|
|
1916
2026
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
@@ -2624,6 +2734,13 @@
|
|
|
2624
2734
|
"node": ">= 8"
|
|
2625
2735
|
}
|
|
2626
2736
|
},
|
|
2737
|
+
"node_modules/meshoptimizer": {
|
|
2738
|
+
"version": "0.22.0",
|
|
2739
|
+
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz",
|
|
2740
|
+
"integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==",
|
|
2741
|
+
"dev": true,
|
|
2742
|
+
"license": "MIT"
|
|
2743
|
+
},
|
|
2627
2744
|
"node_modules/micromark": {
|
|
2628
2745
|
"version": "4.0.2",
|
|
2629
2746
|
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
|
|
@@ -4006,6 +4123,12 @@
|
|
|
4006
4123
|
"node": ">=0.8"
|
|
4007
4124
|
}
|
|
4008
4125
|
},
|
|
4126
|
+
"node_modules/three": {
|
|
4127
|
+
"version": "0.182.0",
|
|
4128
|
+
"resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz",
|
|
4129
|
+
"integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==",
|
|
4130
|
+
"license": "MIT"
|
|
4131
|
+
},
|
|
4009
4132
|
"node_modules/tinyglobby": {
|
|
4010
4133
|
"version": "0.2.15",
|
|
4011
4134
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
data/frontend/package.json
CHANGED
|
@@ -12,16 +12,20 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
14
14
|
"clsx": "^2.1.1",
|
|
15
|
+
"d3-force": "^3.0.0",
|
|
15
16
|
"react": "^18.3.1",
|
|
16
17
|
"react-dom": "^18.3.1",
|
|
17
18
|
"react-markdown": "^10.1.0",
|
|
18
19
|
"react-syntax-highlighter": "^16.1.0",
|
|
19
20
|
"remark-gfm": "^4.0.1",
|
|
21
|
+
"three": "^0.182.0",
|
|
20
22
|
"zustand": "^5.0.0"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
25
|
+
"@types/d3-force": "^3.0.10",
|
|
23
26
|
"@types/react": "^18.3.12",
|
|
24
27
|
"@types/react-dom": "^18.3.1",
|
|
28
|
+
"@types/three": "^0.182.0",
|
|
25
29
|
"@vitejs/plugin-react": "^4.3.4",
|
|
26
30
|
"autoprefixer": "^10.4.20",
|
|
27
31
|
"postcss": "^8.4.49",
|
data/frontend/src/App.tsx
CHANGED
|
@@ -1,96 +1,95 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
1
|
import { useWebSocket } from './hooks/useWebSocket'
|
|
3
2
|
import { useStore, useSelectedPO } from './store'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { useResize } from './hooks/useResize'
|
|
4
|
+
import { SystemBar } from './components/SystemBar'
|
|
5
|
+
import { ObjectList } from './components/ObjectList'
|
|
6
|
+
import { Inspector } from './components/Inspector'
|
|
7
|
+
import { Transcript } from './components/Transcript'
|
|
8
8
|
import { NotificationPanel } from './components/NotificationPanel'
|
|
9
|
-
import { ThreadsSidebar } from './components/ThreadsSidebar'
|
|
10
9
|
import { UsagePanel } from './components/UsagePanel'
|
|
10
|
+
import { CanvasView } from './canvas/CanvasView'
|
|
11
11
|
|
|
12
12
|
export default function App() {
|
|
13
13
|
const { sendMessage, respondToNotification, createSession, switchSession, switchLLM, createThread, updatePrompt, requestUsage, exportThread } =
|
|
14
14
|
useWebSocket()
|
|
15
|
-
const { selectedPO, busOpen, notifications, usageData, clearUsageData } = useStore()
|
|
15
|
+
const { selectedPO, busOpen, notifications, usageData, clearUsageData, currentView } = useStore()
|
|
16
16
|
const selectedPOData = useSelectedPO()
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
const objectListResize = useResize({
|
|
19
|
+
direction: 'horizontal',
|
|
20
|
+
initialSize: 192,
|
|
21
|
+
minSize: 120,
|
|
22
|
+
maxSize: 320,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const transcriptResize = useResize({
|
|
26
|
+
direction: 'vertical',
|
|
27
|
+
initialSize: 180,
|
|
28
|
+
minSize: 80,
|
|
29
|
+
maxSize: 400,
|
|
30
|
+
inverted: true,
|
|
31
|
+
})
|
|
18
32
|
|
|
19
33
|
return (
|
|
20
34
|
<div className="h-screen flex flex-col bg-po-bg">
|
|
21
|
-
<
|
|
35
|
+
<SystemBar switchLLM={switchLLM} />
|
|
22
36
|
|
|
23
|
-
<div className="flex-1 flex overflow-hidden">
|
|
24
|
-
{/*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
className="text-xs text-gray-500 hover:text-white"
|
|
34
|
-
title="Hide sidebar"
|
|
35
|
-
>
|
|
36
|
-
✕
|
|
37
|
-
</button>
|
|
38
|
-
</div>
|
|
39
|
-
<div className="flex-1 overflow-auto">
|
|
40
|
-
<Dashboard compact />
|
|
37
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
38
|
+
{/* Main view area */}
|
|
39
|
+
<div className="flex-1 flex overflow-hidden">
|
|
40
|
+
{currentView === 'canvas' ? (
|
|
41
|
+
<CanvasView />
|
|
42
|
+
) : (
|
|
43
|
+
<>
|
|
44
|
+
{/* Object List - resizable */}
|
|
45
|
+
<div style={{ width: objectListResize.size }} className="flex-shrink-0">
|
|
46
|
+
<ObjectList />
|
|
41
47
|
</div>
|
|
42
|
-
</aside>
|
|
43
|
-
|
|
44
|
-
{/* Threads List for selected PO */}
|
|
45
|
-
{selectedPOData && (
|
|
46
|
-
<aside className="w-56 border-r border-po-border bg-po-bg overflow-hidden">
|
|
47
|
-
<ThreadsSidebar
|
|
48
|
-
po={selectedPOData}
|
|
49
|
-
switchSession={switchSession}
|
|
50
|
-
createThread={createThread}
|
|
51
|
-
requestUsage={requestUsage}
|
|
52
|
-
exportThread={exportThread}
|
|
53
|
-
/>
|
|
54
|
-
</aside>
|
|
55
|
-
)}
|
|
56
|
-
</>
|
|
57
|
-
)}
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
onClick={() => setSplitView(true)}
|
|
65
|
-
className="absolute left-2 top-16 z-10 bg-po-surface border border-po-border rounded px-2 py-1 text-xs text-gray-400 hover:text-white hover:border-po-accent transition-colors"
|
|
66
|
-
title="Show dashboard sidebar"
|
|
67
|
-
>
|
|
68
|
-
☰ POs
|
|
69
|
-
</button>
|
|
70
|
-
)}
|
|
49
|
+
{/* Resize handle */}
|
|
50
|
+
<div
|
|
51
|
+
className="resize-handle"
|
|
52
|
+
onMouseDown={objectListResize.onMouseDown}
|
|
53
|
+
/>
|
|
71
54
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
55
|
+
{/* Main content */}
|
|
56
|
+
<main className="flex-1 overflow-hidden flex flex-col">
|
|
57
|
+
{selectedPO && selectedPOData ? (
|
|
58
|
+
<Inspector
|
|
59
|
+
po={selectedPOData}
|
|
60
|
+
sendMessage={sendMessage}
|
|
61
|
+
createSession={createSession}
|
|
62
|
+
switchSession={switchSession}
|
|
63
|
+
createThread={createThread}
|
|
64
|
+
updatePrompt={updatePrompt}
|
|
65
|
+
requestUsage={requestUsage}
|
|
66
|
+
exportThread={exportThread}
|
|
67
|
+
/>
|
|
68
|
+
) : (
|
|
69
|
+
<div className="h-full flex items-center justify-center text-po-text-ghost">
|
|
70
|
+
<span className="font-mono text-xs">Select an object</span>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</main>
|
|
74
|
+
</>
|
|
82
75
|
)}
|
|
83
|
-
</
|
|
76
|
+
</div>
|
|
84
77
|
|
|
85
|
-
{/*
|
|
78
|
+
{/* Transcript - resizable bottom pane, visible in both views */}
|
|
86
79
|
{busOpen && (
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
80
|
+
<>
|
|
81
|
+
<div
|
|
82
|
+
className="resize-handle-h"
|
|
83
|
+
onMouseDown={transcriptResize.onMouseDown}
|
|
84
|
+
/>
|
|
85
|
+
<div style={{ height: transcriptResize.size }} className="flex-shrink-0">
|
|
86
|
+
<Transcript />
|
|
87
|
+
</div>
|
|
88
|
+
</>
|
|
90
89
|
)}
|
|
91
90
|
</div>
|
|
92
91
|
|
|
93
|
-
{/* Notification panel */}
|
|
92
|
+
{/* Notification panel - floating */}
|
|
94
93
|
{notifications.length > 0 && (
|
|
95
94
|
<NotificationPanel respondToNotification={respondToNotification} />
|
|
96
95
|
)}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback } from 'react'
|
|
2
|
+
import { useStore } from '../store'
|
|
3
|
+
import { useCanvasStore } from './canvasStore'
|
|
4
|
+
import { SceneManager } from './SceneManager'
|
|
5
|
+
import { InspectorPanel } from './inspector/InspectorPanel'
|
|
6
|
+
|
|
7
|
+
export function CanvasView() {
|
|
8
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
9
|
+
const sceneRef = useRef<SceneManager | null>(null)
|
|
10
|
+
const syncScheduled = useRef(false)
|
|
11
|
+
|
|
12
|
+
// Schedule a throttled sync via requestAnimationFrame
|
|
13
|
+
const scheduleSync = useCallback(() => {
|
|
14
|
+
if (syncScheduled.current) return
|
|
15
|
+
syncScheduled.current = true
|
|
16
|
+
requestAnimationFrame(() => {
|
|
17
|
+
syncScheduled.current = false
|
|
18
|
+
const scene = sceneRef.current
|
|
19
|
+
if (!scene) return
|
|
20
|
+
const state = useStore.getState()
|
|
21
|
+
scene.syncPromptObjects(state.promptObjects)
|
|
22
|
+
scene.syncBusMessages(state.busMessages)
|
|
23
|
+
scene.syncNotifications(state.notifications)
|
|
24
|
+
})
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
// Mount/unmount SceneManager
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const container = containerRef.current
|
|
30
|
+
if (!container) return
|
|
31
|
+
|
|
32
|
+
const scene = new SceneManager(container)
|
|
33
|
+
sceneRef.current = scene
|
|
34
|
+
|
|
35
|
+
// Initial sync
|
|
36
|
+
const state = useStore.getState()
|
|
37
|
+
scene.syncPromptObjects(state.promptObjects)
|
|
38
|
+
scene.syncBusMessages(state.busMessages)
|
|
39
|
+
scene.syncNotifications(state.notifications)
|
|
40
|
+
scene.start()
|
|
41
|
+
|
|
42
|
+
// Fit all after a short delay to let force layout settle
|
|
43
|
+
const fitTimer = setTimeout(() => scene.fitAll(), 500)
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
clearTimeout(fitTimer)
|
|
47
|
+
scene.dispose()
|
|
48
|
+
sceneRef.current = null
|
|
49
|
+
}
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
// Subscribe to store changes (non-React API to avoid re-renders)
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const unsub = useStore.subscribe(scheduleSync)
|
|
55
|
+
return unsub
|
|
56
|
+
}, [scheduleSync])
|
|
57
|
+
|
|
58
|
+
// Keyboard shortcuts
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const handler = (e: KeyboardEvent) => {
|
|
61
|
+
if (e.key === 'f' && !e.ctrlKey && !e.metaKey) {
|
|
62
|
+
// Don't intercept when typing in an input
|
|
63
|
+
if ((e.target as HTMLElement).tagName === 'INPUT' || (e.target as HTMLElement).tagName === 'TEXTAREA') return
|
|
64
|
+
sceneRef.current?.fitAll()
|
|
65
|
+
}
|
|
66
|
+
if (e.key === 'Escape') {
|
|
67
|
+
useCanvasStore.getState().selectNode(null)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
window.addEventListener('keydown', handler)
|
|
71
|
+
return () => window.removeEventListener('keydown', handler)
|
|
72
|
+
}, [])
|
|
73
|
+
|
|
74
|
+
const showLabels = useCanvasStore((s) => s.showLabels)
|
|
75
|
+
const toggleLabels = useCanvasStore((s) => s.toggleLabels)
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="flex-1 flex overflow-hidden relative">
|
|
79
|
+
{/* Three.js container */}
|
|
80
|
+
<div ref={containerRef} className="flex-1 relative" />
|
|
81
|
+
|
|
82
|
+
{/* Toolbar overlay */}
|
|
83
|
+
<div className="absolute top-3 left-3 flex gap-2 z-10">
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => sceneRef.current?.fitAll()}
|
|
86
|
+
className="px-2.5 py-1 text-xs bg-po-surface-2/80 backdrop-blur border border-po-border rounded hover:border-po-accent transition-colors duration-150 text-po-text-secondary hover:text-po-text-primary"
|
|
87
|
+
title="Fit all nodes (F)"
|
|
88
|
+
>
|
|
89
|
+
Fit All
|
|
90
|
+
</button>
|
|
91
|
+
<button
|
|
92
|
+
onClick={toggleLabels}
|
|
93
|
+
className={`px-2.5 py-1 text-xs backdrop-blur border rounded transition-colors duration-150 ${
|
|
94
|
+
showLabels
|
|
95
|
+
? 'bg-po-accent-wash border-po-accent text-po-accent'
|
|
96
|
+
: 'bg-po-surface-2/80 border-po-border text-po-text-secondary hover:text-po-text-primary'
|
|
97
|
+
}`}
|
|
98
|
+
title="Toggle labels"
|
|
99
|
+
>
|
|
100
|
+
Labels
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Help hint */}
|
|
105
|
+
<div className="absolute bottom-3 left-3 text-2xs text-po-text-ghost z-10 font-mono">
|
|
106
|
+
Scroll to zoom · Shift+drag to pan · F to fit · Click node to inspect
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Inspector panel */}
|
|
110
|
+
<InspectorPanel />
|
|
111
|
+
</div>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forceSimulation,
|
|
3
|
+
forceManyBody,
|
|
4
|
+
forceCenter,
|
|
5
|
+
forceCollide,
|
|
6
|
+
forceLink,
|
|
7
|
+
type Simulation,
|
|
8
|
+
type SimulationNodeDatum,
|
|
9
|
+
type SimulationLinkDatum,
|
|
10
|
+
} from 'd3-force'
|
|
11
|
+
import { FORCE } from './constants'
|
|
12
|
+
|
|
13
|
+
export interface ForceNode extends SimulationNodeDatum {
|
|
14
|
+
id: string
|
|
15
|
+
type: 'po' | 'toolcall'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ForceLink extends SimulationLinkDatum<ForceNode> {
|
|
19
|
+
id: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ForceLayout {
|
|
23
|
+
private simulation: Simulation<ForceNode, ForceLink>
|
|
24
|
+
private nodes: ForceNode[] = []
|
|
25
|
+
private links: ForceLink[] = []
|
|
26
|
+
private dirty = false
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
this.simulation = forceSimulation<ForceNode, ForceLink>()
|
|
30
|
+
.alphaDecay(FORCE.alphaDecay)
|
|
31
|
+
.velocityDecay(FORCE.velocityDecay)
|
|
32
|
+
.force('charge', forceManyBody<ForceNode>().strength(FORCE.chargeStrength))
|
|
33
|
+
.force('center', forceCenter<ForceNode>(0, 0).strength(FORCE.centerStrength))
|
|
34
|
+
.force(
|
|
35
|
+
'collision',
|
|
36
|
+
forceCollide<ForceNode>().radius(FORCE.collisionRadius)
|
|
37
|
+
)
|
|
38
|
+
.force(
|
|
39
|
+
'link',
|
|
40
|
+
forceLink<ForceNode, ForceLink>()
|
|
41
|
+
.id((d) => d.id)
|
|
42
|
+
.distance(FORCE.linkDistance)
|
|
43
|
+
)
|
|
44
|
+
.stop() // Manual tick mode — we call tick() from animation loop
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
addNode(id: string, type: 'po' | 'toolcall'): void {
|
|
48
|
+
if (this.nodes.find((n) => n.id === id)) return
|
|
49
|
+
this.nodes.push({ id, type })
|
|
50
|
+
this.dirty = true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
removeNode(id: string): void {
|
|
54
|
+
const idx = this.nodes.findIndex((n) => n.id === id)
|
|
55
|
+
if (idx === -1) return
|
|
56
|
+
this.nodes.splice(idx, 1)
|
|
57
|
+
// Also remove any links referencing this node
|
|
58
|
+
this.links = this.links.filter((l) => {
|
|
59
|
+
const src = typeof l.source === 'object' ? (l.source as ForceNode).id : l.source
|
|
60
|
+
const tgt = typeof l.target === 'object' ? (l.target as ForceNode).id : l.target
|
|
61
|
+
return src !== id && tgt !== id
|
|
62
|
+
})
|
|
63
|
+
this.dirty = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
addLink(id: string, sourceId: string, targetId: string): void {
|
|
67
|
+
if (this.links.find((l) => l.id === id)) return
|
|
68
|
+
this.links.push({ id, source: sourceId, target: targetId })
|
|
69
|
+
this.dirty = true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
removeLink(id: string): void {
|
|
73
|
+
const idx = this.links.findIndex((l) => l.id === id)
|
|
74
|
+
if (idx === -1) return
|
|
75
|
+
this.links.splice(idx, 1)
|
|
76
|
+
this.dirty = true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
tick(): void {
|
|
80
|
+
if (this.dirty) {
|
|
81
|
+
this.rebuild()
|
|
82
|
+
this.dirty = false
|
|
83
|
+
}
|
|
84
|
+
this.simulation.tick()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getPositions(): Map<string, { x: number; y: number }> {
|
|
88
|
+
const positions = new Map<string, { x: number; y: number }>()
|
|
89
|
+
for (const node of this.nodes) {
|
|
90
|
+
positions.set(node.id, { x: node.x ?? 0, y: node.y ?? 0 })
|
|
91
|
+
}
|
|
92
|
+
return positions
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
reheat(): void {
|
|
96
|
+
this.simulation.alpha(0.8).restart().stop()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private rebuild(): void {
|
|
100
|
+
this.simulation.nodes(this.nodes)
|
|
101
|
+
const linkForce = this.simulation.force('link') as ReturnType<
|
|
102
|
+
typeof forceLink<ForceNode, ForceLink>
|
|
103
|
+
>
|
|
104
|
+
if (linkForce) {
|
|
105
|
+
linkForce.links(this.links)
|
|
106
|
+
}
|
|
107
|
+
this.reheat()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
dispose(): void {
|
|
111
|
+
this.simulation.stop()
|
|
112
|
+
this.nodes = []
|
|
113
|
+
this.links = []
|
|
114
|
+
}
|
|
115
|
+
}
|