neural-loom 0.1.1
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.
- package/.next/BUILD_ID +1 -0
- package/.next/app-path-routes-manifest.json +18 -0
- package/.next/build-manifest.json +20 -0
- package/.next/cache/.previewinfo +1 -0
- package/.next/cache/.rscinfo +1 -0
- package/.next/cache/.tsbuildinfo +1 -0
- package/.next/diagnostics/build-diagnostics.json +6 -0
- package/.next/diagnostics/framework.json +1 -0
- package/.next/diagnostics/route-bundle-stats.json +25 -0
- package/.next/export-marker.json +6 -0
- package/.next/fallback-build-manifest.json +13 -0
- package/.next/images-manifest.json +68 -0
- package/.next/next-minimal-server.js.nft.json +1 -0
- package/.next/next-server.js.nft.json +1 -0
- package/.next/package.json +1 -0
- package/.next/prerender-manifest.json +114 -0
- package/.next/required-server-files.js +332 -0
- package/.next/required-server-files.json +332 -0
- package/.next/routes-manifest.json +141 -0
- package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
- package/.next/server/app/_global-error/page/build-manifest.json +16 -0
- package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
- package/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
- package/.next/server/app/_global-error/page.js +10 -0
- package/.next/server/app/_global-error/page.js.map +5 -0
- package/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
- package/.next/server/app/_global-error.html +1 -0
- package/.next/server/app/_global-error.meta +15 -0
- package/.next/server/app/_global-error.rsc +15 -0
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
- package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -0
- package/.next/server/app/_global-error.segments/_head.segment.rsc +6 -0
- package/.next/server/app/_global-error.segments/_index.segment.rsc +5 -0
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
- package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
- package/.next/server/app/_not-found/page/build-manifest.json +16 -0
- package/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
- package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
- package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
- package/.next/server/app/_not-found/page.js +13 -0
- package/.next/server/app/_not-found/page.js.map +5 -0
- package/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -0
- package/.next/server/app/_not-found.html +1 -0
- package/.next/server/app/_not-found.meta +16 -0
- package/.next/server/app/_not-found.rsc +16 -0
- package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -0
- package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
- package/.next/server/app/_not-found.segments/_index.segment.rsc +5 -0
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +5 -0
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
- package/.next/server/app/api/context/aider/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/context/aider/route/build-manifest.json +9 -0
- package/.next/server/app/api/context/aider/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/context/aider/route.js +6 -0
- package/.next/server/app/api/context/aider/route.js.map +5 -0
- package/.next/server/app/api/context/aider/route.js.nft.json +1 -0
- package/.next/server/app/api/context/aider/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/context/claude/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/context/claude/route/build-manifest.json +9 -0
- package/.next/server/app/api/context/claude/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/context/claude/route.js +6 -0
- package/.next/server/app/api/context/claude/route.js.map +5 -0
- package/.next/server/app/api/context/claude/route.js.nft.json +1 -0
- package/.next/server/app/api/context/claude/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/context/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/context/route/build-manifest.json +9 -0
- package/.next/server/app/api/context/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/context/route.js +6 -0
- package/.next/server/app/api/context/route.js.map +5 -0
- package/.next/server/app/api/context/route.js.nft.json +1 -0
- package/.next/server/app/api/context/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/context/ssh/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/context/ssh/route/build-manifest.json +9 -0
- package/.next/server/app/api/context/ssh/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/context/ssh/route.js +6 -0
- package/.next/server/app/api/context/ssh/route.js.map +5 -0
- package/.next/server/app/api/context/ssh/route.js.nft.json +1 -0
- package/.next/server/app/api/context/ssh/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/files/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/files/route/build-manifest.json +9 -0
- package/.next/server/app/api/files/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/files/route.js +7 -0
- package/.next/server/app/api/files/route.js.map +5 -0
- package/.next/server/app/api/files/route.js.nft.json +1 -0
- package/.next/server/app/api/files/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/git/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/git/route/build-manifest.json +9 -0
- package/.next/server/app/api/git/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/git/route.js +6 -0
- package/.next/server/app/api/git/route.js.map +5 -0
- package/.next/server/app/api/git/route.js.nft.json +1 -0
- package/.next/server/app/api/git/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sessions/inject/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/inject/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/inject/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/inject/route.js +7 -0
- package/.next/server/app/api/sessions/inject/route.js.map +5 -0
- package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/inject/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sessions/input/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/input/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/input/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/input/route.js +7 -0
- package/.next/server/app/api/sessions/input/route.js.map +5 -0
- package/.next/server/app/api/sessions/input/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/input/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sessions/launch/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/launch/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/launch/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/launch/route.js +7 -0
- package/.next/server/app/api/sessions/launch/route.js.map +5 -0
- package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/launch/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sessions/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/route.js +7 -0
- package/.next/server/app/api/sessions/route.js.map +5 -0
- package/.next/server/app/api/sessions/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sessions/stats/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/stats/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/stats/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/stats/route.js +7 -0
- package/.next/server/app/api/sessions/stats/route.js.map +5 -0
- package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/stats/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sessions/stop/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/stop/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/stop/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/stop/route.js +7 -0
- package/.next/server/app/api/sessions/stop/route.js.map +5 -0
- package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/stop/route_client-reference-manifest.js +3 -0
- package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
- package/.next/server/app/favicon.ico/route/build-manifest.json +9 -0
- package/.next/server/app/favicon.ico/route.js +7 -0
- package/.next/server/app/favicon.ico/route.js.map +5 -0
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -0
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/favicon.ico.meta +1 -0
- package/.next/server/app/index.html +1 -0
- package/.next/server/app/index.meta +14 -0
- package/.next/server/app/index.rsc +21 -0
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +10 -0
- package/.next/server/app/index.segments/_full.segment.rsc +21 -0
- package/.next/server/app/index.segments/_head.segment.rsc +6 -0
- package/.next/server/app/index.segments/_index.segment.rsc +5 -0
- package/.next/server/app/index.segments/_tree.segment.rsc +5 -0
- package/.next/server/app/page/app-paths-manifest.json +3 -0
- package/.next/server/app/page/build-manifest.json +16 -0
- package/.next/server/app/page/next-font-manifest.json +11 -0
- package/.next/server/app/page/react-loadable-manifest.json +42 -0
- package/.next/server/app/page/server-reference-manifest.json +4 -0
- package/.next/server/app/page.js +14 -0
- package/.next/server/app/page.js.map +5 -0
- package/.next/server/app/page.js.nft.json +1 -0
- package/.next/server/app/page_client-reference-manifest.js +3 -0
- package/.next/server/app-paths-manifest.json +18 -0
- package/.next/server/chunks/[externals]__0shxiss._.js +3 -0
- package/.next/server/chunks/[externals]__0shxiss._.js.map +1 -0
- package/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +3 -0
- package/.next/server/chunks/[externals]_next_dist_0arv.vj._.js.map +1 -0
- package/.next/server/chunks/[externals]_node-pty_12c-8pf._.js +3 -0
- package/.next/server/chunks/[externals]_node-pty_12c-8pf._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0-k9zyi._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0-k9zyi._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__04kdofw._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__04kdofw._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0c.5qbe._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0c.5qbe._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +28 -0
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0e5cwnx._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0e5cwnx._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0efi5cr._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0efi5cr._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0j8-xkl._.js +13 -0
- package/.next/server/chunks/[root-of-the-server]__0j8-xkl._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0k6w___._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0k6w___._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0l7c-gn._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0l7c-gn._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0l_q72g._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0l_q72g._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0m7-xfc._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0m7-xfc._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0m_~dan._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0m_~dan._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0ntt3om._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ntt3om._.js.map +1 -0
- package/.next/server/chunks/[turbopack]_runtime.js +903 -0
- package/.next/server/chunks/[turbopack]_runtime.js.map +11 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_aider_route_actions_0dszia..js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_aider_route_actions_0dszia..js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_claude_route_actions_12srkdn.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_claude_route_actions_12srkdn.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_route_actions_03ko8ta.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_route_actions_03ko8ta.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_ssh_route_actions_0~0~96d.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_context_ssh_route_actions_0~0~96d.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_files_route_actions_0x8kqqx.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_files_route_actions_0x8kqqx.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_git_route_actions_0xetuf~.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_git_route_actions_0xetuf~.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_inject_route_actions_0y2_m1_.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_inject_route_actions_0y2_m1_.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_input_route_actions_032sdjp.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_input_route_actions_032sdjp.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_launch_route_actions_0_qrmsm.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_launch_route_actions_0_qrmsm.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_route_actions_0y1t9w0.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_route_actions_0y1t9w0.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_stats_route_actions_0gc_vw-.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_stats_route_actions_0gc_vw-.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_stop_route_actions_0xg1t9k.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_stop_route_actions_0xg1t9k.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ev3h.z.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ev3h.z.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0m.429v.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0m.429v.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0.kd7dh._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0.kd7dh._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__089dc4i._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__089dc4i._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_7i5h0._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_7i5h0._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0hw~y-4._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0hw~y-4._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0uk0awy._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0uk0awy._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0~y8ue.._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0~y8ue.._.js.map +1 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js +903 -0
- package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +11 -0
- package/.next/server/chunks/ssr/_0t7oqy6._.js +3 -0
- package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +3 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js.map +1 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +3 -0
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js.map +1 -0
- package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +3 -0
- package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_05l396c._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_05l396c._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_09w7yel._.js +33 -0
- package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_11~q6fv._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_11~q6fv._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0ppctuh._.js +19 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0ppctuh._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0qoh8ry._.js +6 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_0qoh8ry._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03-z2qq.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03-z2qq.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07vh7rm.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07vh7rm.js.map +1 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qp4u6g.js +4 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qp4u6g.js.map +1 -0
- package/.next/server/functions-config-manifest.json +4 -0
- package/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/.next/server/middleware-build-manifest.js +20 -0
- package/.next/server/middleware-manifest.json +6 -0
- package/.next/server/next-font-manifest.js +1 -0
- package/.next/server/next-font-manifest.json +15 -0
- package/.next/server/pages/404.html +1 -0
- package/.next/server/pages/500.html +1 -0
- package/.next/server/pages-manifest.json +4 -0
- package/.next/server/prefetch-hints.json +1 -0
- package/.next/server/server-reference-manifest.js +1 -0
- package/.next/server/server-reference-manifest.json +5 -0
- package/.next/static/chunks/03g6xpslyid3~.js +1 -0
- package/.next/static/chunks/03~yq9q893hmn.js +1 -0
- package/.next/static/chunks/07b7-dl8gh17u.js +5 -0
- package/.next/static/chunks/07lhk_q6pmm3r.js +1 -0
- package/.next/static/chunks/0dbhjjzl8qfwv.js +1 -0
- package/.next/static/chunks/0ewdhgxsa_h.q.js +1 -0
- package/.next/static/chunks/0f4at4_v-tkgj.js +1 -0
- package/.next/static/chunks/0fpki3y6aj230.js +31 -0
- package/.next/static/chunks/0ga14ztvrhau2.css +1 -0
- package/.next/static/chunks/0lnobx4eh3~-_.js +1 -0
- package/.next/static/chunks/0mej.ad_ddvf1.js +1 -0
- package/.next/static/chunks/0q4suv6uexpud.css +1 -0
- package/.next/static/chunks/0xq6bhmghl02_.js +1 -0
- package/.next/static/chunks/139c54uo1w7.4.css +3 -0
- package/.next/static/chunks/140ovxvjat1ch.js +5 -0
- package/.next/static/chunks/16w4~t4h7gk13.js +1 -0
- package/.next/static/chunks/turbopack-08fpdsd.-ns2y.js +1 -0
- package/.next/static/media/4fa387ec64143e14-s.0wkzw~je483f-.woff2 +0 -0
- package/.next/static/media/53b9e256198e5412-s.0-wfv7uh4i7h9.woff2 +0 -0
- package/.next/static/media/5ce348bf30bf5439-s.0zgw-jeven.3w.woff2 +0 -0
- package/.next/static/media/6306c77e7c8268e4-s.0rhz0arwfsn~5.woff2 +0 -0
- package/.next/static/media/7178b3e590c64307-s.0nx0ww8fni_q3.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.08e28id.o-okb.woff2 +0 -0
- package/.next/static/media/7d817b4c03b0c5f1-s.0l76wvqk9d84w.woff2 +0 -0
- package/.next/static/media/8a480f0b521d4e75-s.0jzbimsg8vl84.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.0k4k9394f2q-k.woff2 +0 -0
- package/.next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2 +0 -0
- package/.next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
- package/.next/static/media/fef07dbb0973bf53-s.12tyk43_3sh9u.woff2 +0 -0
- package/.next/static/r-T5V6BKLiZ32Ai9Boe1d/_buildManifest.js +11 -0
- package/.next/static/r-T5V6BKLiZ32Ai9Boe1d/_clientMiddlewareManifest.js +1 -0
- package/.next/static/r-T5V6BKLiZ32Ai9Boe1d/_ssgManifest.js +1 -0
- package/.next/trace +1 -0
- package/.next/trace-build +1 -0
- package/.next/turbopack +0 -0
- package/.next/types/cache-life.d.ts +145 -0
- package/.next/types/routes.d.ts +84 -0
- package/.next/types/validator.ts +178 -0
- package/README.md +125 -0
- package/bin/cli.js +36 -0
- package/next.config.ts +7 -0
- package/package.json +40 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/context/aider/route.ts +113 -0
- package/src/app/api/context/claude/route.ts +64 -0
- package/src/app/api/context/route.ts +37 -0
- package/src/app/api/context/ssh/route.ts +88 -0
- package/src/app/api/files/route.ts +276 -0
- package/src/app/api/git/route.ts +81 -0
- package/src/app/api/sessions/inject/route.ts +21 -0
- package/src/app/api/sessions/input/route.ts +30 -0
- package/src/app/api/sessions/launch/route.ts +25 -0
- package/src/app/api/sessions/route.ts +27 -0
- package/src/app/api/sessions/stats/route.ts +76 -0
- package/src/app/api/sessions/stop/route.ts +26 -0
- package/src/app/components/AiderWizard.tsx +412 -0
- package/src/app/components/ClaudeWizard.tsx +335 -0
- package/src/app/components/ContextEditor.tsx +169 -0
- package/src/app/components/IdeLayout.tsx +2095 -0
- package/src/app/components/SshWizard.tsx +379 -0
- package/src/app/components/TerminalConsole.tsx +228 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +81 -0
- package/src/app/layout.tsx +30 -0
- package/src/app/page.module.css +330 -0
- package/src/app/page.tsx +581 -0
- package/src/lib/agents/AgentRunner.ts +86 -0
- package/src/lib/agents/AiderRunner.ts +261 -0
- package/src/lib/agents/ClaudeRunner.ts +148 -0
- package/src/lib/agents/ContextManager.ts +142 -0
- package/src/lib/agents/DockerRunner.ts +182 -0
- package/src/lib/agents/MockRunner.ts +126 -0
- package/src/lib/agents/SSHRunner.ts +180 -0
- package/src/lib/agents/SessionManager.ts +147 -0
- package/src/lib/agents/SessionPersister.ts +85 -0
- package/src/lib/agents/WebSocketServer.ts +118 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,2095 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import TerminalConsole from "./TerminalConsole";
|
|
5
|
+
|
|
6
|
+
interface FileNode {
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
type: "file" | "directory";
|
|
10
|
+
children?: FileNode[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface WorkspaceData {
|
|
14
|
+
name: string;
|
|
15
|
+
path: string;
|
|
16
|
+
tree: FileNode[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ScopedData {
|
|
20
|
+
name: string;
|
|
21
|
+
path: string;
|
|
22
|
+
tree: FileNode[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GitFile {
|
|
26
|
+
status: string;
|
|
27
|
+
relativePath: string;
|
|
28
|
+
absolutePath: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface SessionStatus {
|
|
33
|
+
type?: string;
|
|
34
|
+
status?: string;
|
|
35
|
+
logs?: string[];
|
|
36
|
+
tokenUsage?: {
|
|
37
|
+
input: number;
|
|
38
|
+
output: number;
|
|
39
|
+
cost: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface SessionStats {
|
|
44
|
+
cpu: string;
|
|
45
|
+
memory: string;
|
|
46
|
+
memoryPercent: string;
|
|
47
|
+
type?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface FlatFile {
|
|
51
|
+
name: string;
|
|
52
|
+
path: string;
|
|
53
|
+
relativePath: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface RunnerStatus {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker';
|
|
60
|
+
status: 'idle' | 'running' | 'stopped';
|
|
61
|
+
createdAt: string;
|
|
62
|
+
logsCount: number;
|
|
63
|
+
workspaceRoot?: string;
|
|
64
|
+
scopedDirs?: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface IdeLayoutProps {
|
|
68
|
+
sessionId: string;
|
|
69
|
+
workspaceRoot: string;
|
|
70
|
+
scopedDirs: string[];
|
|
71
|
+
onClose: () => void;
|
|
72
|
+
sessions: RunnerStatus[];
|
|
73
|
+
onSwitchSession: (id: string) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface OpenFile {
|
|
77
|
+
name: string;
|
|
78
|
+
path: string;
|
|
79
|
+
content: string;
|
|
80
|
+
isModified: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClose, sessions, onSwitchSession }: IdeLayoutProps) {
|
|
84
|
+
const scopedDirsStr = JSON.stringify(scopedDirs);
|
|
85
|
+
const [workspace, setWorkspace] = useState<WorkspaceData | null>(null);
|
|
86
|
+
const [scoped, setScoped] = useState<ScopedData[]>([]);
|
|
87
|
+
|
|
88
|
+
// Track open files and active files per session to preserve state on tab swap
|
|
89
|
+
const [sessionOpenFiles, setSessionOpenFiles] = useState<{ [sessionId: string]: OpenFile[] }>({});
|
|
90
|
+
const [sessionActiveFiles, setSessionActiveFiles] = useState<{ [sessionId: string]: string | null }>({});
|
|
91
|
+
|
|
92
|
+
const openFiles = sessionOpenFiles[sessionId] || [];
|
|
93
|
+
const activeFilePath = sessionActiveFiles[sessionId] || null;
|
|
94
|
+
|
|
95
|
+
const setOpenFiles = useCallback((newFiles: OpenFile[] | ((prev: OpenFile[]) => OpenFile[])) => {
|
|
96
|
+
setSessionOpenFiles(prev => {
|
|
97
|
+
const currentFiles = prev[sessionId] || [];
|
|
98
|
+
const updatedFiles = typeof newFiles === "function" ? newFiles(currentFiles) : newFiles;
|
|
99
|
+
return { ...prev, [sessionId]: updatedFiles };
|
|
100
|
+
});
|
|
101
|
+
}, [sessionId]);
|
|
102
|
+
|
|
103
|
+
const setActiveFilePath = useCallback((path: string | null) => {
|
|
104
|
+
setSessionActiveFiles(prev => ({ ...prev, [sessionId]: path }));
|
|
105
|
+
}, [sessionId]);
|
|
106
|
+
|
|
107
|
+
const [explorerVisible, setExplorerVisible] = useState(true);
|
|
108
|
+
const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor">("explorer");
|
|
109
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
110
|
+
|
|
111
|
+
// Git Diff Panel state
|
|
112
|
+
const [gitFiles, setGitFiles] = useState<GitFile[]>([]);
|
|
113
|
+
const [isGitRepo, setIsGitRepo] = useState(false);
|
|
114
|
+
const [gitLoading, setGitLoading] = useState(false);
|
|
115
|
+
|
|
116
|
+
// Status & Telemetry
|
|
117
|
+
const [sessionStatus, setSessionStatus] = useState<SessionStatus | null>(null);
|
|
118
|
+
const [stats, setStats] = useState<SessionStats | null>(null);
|
|
119
|
+
|
|
120
|
+
// Log Search & Auditor
|
|
121
|
+
const [logSearchQuery, setLogSearchQuery] = useState("");
|
|
122
|
+
|
|
123
|
+
// Custom Commands
|
|
124
|
+
const [customCommands, setCustomCommands] = useState<{ name: string; command: string }[]>([]);
|
|
125
|
+
const [newCmdName, setNewCmdName] = useState("");
|
|
126
|
+
const [newCmdStr, setNewCmdStr] = useState("");
|
|
127
|
+
|
|
128
|
+
// Console split view state
|
|
129
|
+
const [splitSessionId, setSplitSessionId] = useState<string | null>(null);
|
|
130
|
+
|
|
131
|
+
// Load Git status
|
|
132
|
+
const loadGitStatus = useCallback(async () => {
|
|
133
|
+
// Defer state updates to prevent synchronous execution inside useEffect
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
135
|
+
setGitLoading(true);
|
|
136
|
+
try {
|
|
137
|
+
const res = await fetch(`/api/git?action=status&root=${encodeURIComponent(workspaceRoot)}`);
|
|
138
|
+
if (res.ok) {
|
|
139
|
+
const data = await res.json();
|
|
140
|
+
if (data.success) {
|
|
141
|
+
setIsGitRepo(data.isGit);
|
|
142
|
+
setGitFiles(data.files || []);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
console.error("Failed to load git status:", e);
|
|
147
|
+
} finally {
|
|
148
|
+
setGitLoading(false);
|
|
149
|
+
}
|
|
150
|
+
}, [workspaceRoot]);
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
loadGitStatus();
|
|
155
|
+
}, 0);
|
|
156
|
+
const id = setInterval(loadGitStatus, 5000);
|
|
157
|
+
return () => clearInterval(id);
|
|
158
|
+
}, [loadGitStatus]);
|
|
159
|
+
|
|
160
|
+
// Poll Session Status (logs, tokens, type)
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const fetchStatus = async () => {
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetch(`/api/sessions?id=${sessionId}`);
|
|
165
|
+
if (res.ok) {
|
|
166
|
+
const data = await res.json();
|
|
167
|
+
setSessionStatus(data);
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
console.error("Failed to fetch session status:", e);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
fetchStatus();
|
|
174
|
+
const id = setInterval(fetchStatus, 3000);
|
|
175
|
+
return () => clearInterval(id);
|
|
176
|
+
}, [sessionId]);
|
|
177
|
+
|
|
178
|
+
// Poll CPU / Memory Stats
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const fetchStats = async () => {
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetch(`/api/sessions/stats?id=${sessionId}`);
|
|
183
|
+
if (res.ok) {
|
|
184
|
+
const data = await res.json();
|
|
185
|
+
setStats(data);
|
|
186
|
+
}
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.error("Failed to fetch container/process stats:", e);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
fetchStats();
|
|
192
|
+
const id = setInterval(fetchStats, 3000);
|
|
193
|
+
return () => clearInterval(id);
|
|
194
|
+
}, [sessionId]);
|
|
195
|
+
|
|
196
|
+
// Custom Commands Persistence
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
const saved = localStorage.getItem("neural_loom_custom_commands");
|
|
199
|
+
if (saved) {
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(saved);
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
setCustomCommands(parsed);
|
|
204
|
+
}, 0);
|
|
205
|
+
} catch {}
|
|
206
|
+
}
|
|
207
|
+
}, []);
|
|
208
|
+
|
|
209
|
+
const saveCommands = (cmds: { name: string; command: string }[]) => {
|
|
210
|
+
setCustomCommands(cmds);
|
|
211
|
+
localStorage.setItem("neural_loom_custom_commands", JSON.stringify(cmds));
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const handleAddCommand = () => {
|
|
215
|
+
if (!newCmdName.trim() || !newCmdStr.trim()) return;
|
|
216
|
+
const updated = [...customCommands, { name: newCmdName.trim(), command: newCmdStr.trim() }];
|
|
217
|
+
saveCommands(updated);
|
|
218
|
+
setNewCmdName("");
|
|
219
|
+
setNewCmdStr("");
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const handleDeleteCommand = (idx: number) => {
|
|
223
|
+
const updated = customCommands.filter((_, i) => i !== idx);
|
|
224
|
+
saveCommands(updated);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const triggerInject = async (command: string) => {
|
|
228
|
+
try {
|
|
229
|
+
await fetch("/api/sessions/inject", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Content-Type": "application/json" },
|
|
232
|
+
body: JSON.stringify({ id: sessionId, command }),
|
|
233
|
+
});
|
|
234
|
+
} catch (e) {
|
|
235
|
+
console.error("Failed to inject command:", e);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const handleExportLog = () => {
|
|
240
|
+
if (!sessionStatus?.logs) return;
|
|
241
|
+
const fullLog = sessionStatus.logs.join("").replace(/\x1b\[[0-9;]*m/g, "");
|
|
242
|
+
const blob = new Blob([fullLog], { type: "text/plain;charset=utf-8" });
|
|
243
|
+
const url = URL.createObjectURL(blob);
|
|
244
|
+
const a = document.createElement("a");
|
|
245
|
+
a.href = url;
|
|
246
|
+
a.download = `session-${sessionId}-logs.txt`;
|
|
247
|
+
a.click();
|
|
248
|
+
URL.revokeObjectURL(url);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const isAider = sessionStatus?.type?.includes("aider");
|
|
252
|
+
|
|
253
|
+
// Dialog operations state
|
|
254
|
+
const [dialog, setDialog] = useState<{
|
|
255
|
+
type: "create-file" | "create-folder" | "rename" | "delete" | null;
|
|
256
|
+
targetPath: string | null;
|
|
257
|
+
inputValue: string;
|
|
258
|
+
error: string | null;
|
|
259
|
+
}>({ type: null, targetPath: null, inputValue: "", error: null });
|
|
260
|
+
|
|
261
|
+
// Search modal state
|
|
262
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
263
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
264
|
+
const [flatFiles, setFlatFiles] = useState<FlatFile[]>([]);
|
|
265
|
+
const [searchSelectedIndex, setSearchSelectedIndex] = useState(0);
|
|
266
|
+
|
|
267
|
+
// Load directory tree
|
|
268
|
+
const loadFiles = useCallback(async () => {
|
|
269
|
+
// Defer state updates to prevent synchronous execution inside useEffect
|
|
270
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
271
|
+
setIsRefreshing(true);
|
|
272
|
+
try {
|
|
273
|
+
const scopedQuery = scopedDirs && scopedDirs.length > 0 ? `&scoped=${encodeURIComponent(scopedDirs.join(","))}` : "";
|
|
274
|
+
const res = await fetch(`/api/files?action=list&root=${encodeURIComponent(workspaceRoot)}${scopedQuery}`);
|
|
275
|
+
if (res.ok) {
|
|
276
|
+
const data = await res.json();
|
|
277
|
+
if (data.success) {
|
|
278
|
+
setWorkspace(data.workspace);
|
|
279
|
+
setScoped(data.scoped || []);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error("Failed to load file explorer:", err);
|
|
284
|
+
} finally {
|
|
285
|
+
setIsRefreshing(false);
|
|
286
|
+
}
|
|
287
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
288
|
+
}, [workspaceRoot, scopedDirsStr]);
|
|
289
|
+
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
setTimeout(() => {
|
|
292
|
+
loadFiles();
|
|
293
|
+
}, 0);
|
|
294
|
+
}, [loadFiles]);
|
|
295
|
+
|
|
296
|
+
// Trigger creation dialog
|
|
297
|
+
const handleTriggerCreate = (parentPath: string, type: "file" | "directory") => {
|
|
298
|
+
setDialog({
|
|
299
|
+
type: type === "file" ? "create-file" : "create-folder",
|
|
300
|
+
targetPath: parentPath,
|
|
301
|
+
inputValue: "",
|
|
302
|
+
error: null
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Trigger rename dialog
|
|
307
|
+
const handleTriggerRename = (targetPath: string) => {
|
|
308
|
+
const name = targetPath.split(/[/\\]/).pop() || "";
|
|
309
|
+
setDialog({
|
|
310
|
+
type: "rename",
|
|
311
|
+
targetPath,
|
|
312
|
+
inputValue: name,
|
|
313
|
+
error: null
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Trigger delete dialog
|
|
318
|
+
const handleTriggerDelete = (targetPath: string) => {
|
|
319
|
+
setDialog({
|
|
320
|
+
type: "delete",
|
|
321
|
+
targetPath,
|
|
322
|
+
inputValue: "",
|
|
323
|
+
error: null
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const handleDialogSubmit = async (e?: React.FormEvent) => {
|
|
328
|
+
if (e) e.preventDefault();
|
|
329
|
+
if (!dialog.type || !dialog.targetPath) return;
|
|
330
|
+
|
|
331
|
+
setDialog(prev => ({ ...prev, error: null }));
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
if (dialog.type === "create-file" || dialog.type === "create-folder") {
|
|
335
|
+
if (!dialog.inputValue.trim()) {
|
|
336
|
+
setDialog(prev => ({ ...prev, error: "Name cannot be empty" }));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const newPath = `${dialog.targetPath}/${dialog.inputValue.trim()}`;
|
|
341
|
+
const itemType = dialog.type === "create-file" ? "file" : "directory";
|
|
342
|
+
|
|
343
|
+
const res = await fetch("/api/files", {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "Content-Type": "application/json" },
|
|
346
|
+
body: JSON.stringify({
|
|
347
|
+
action: "create",
|
|
348
|
+
path: newPath,
|
|
349
|
+
type: itemType,
|
|
350
|
+
}),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (res.ok) {
|
|
354
|
+
const data = await res.json();
|
|
355
|
+
if (data.success) {
|
|
356
|
+
setDialog({ type: null, targetPath: null, inputValue: "", error: null });
|
|
357
|
+
await loadFiles();
|
|
358
|
+
if (itemType === "file") {
|
|
359
|
+
await handleOpenFile(newPath);
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
setDialog(prev => ({ ...prev, error: data.error || "Failed to create item" }));
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
const data = await res.json();
|
|
366
|
+
setDialog(prev => ({ ...prev, error: data.error || "Failed to create item" }));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (dialog.type === "rename") {
|
|
371
|
+
if (!dialog.inputValue.trim()) {
|
|
372
|
+
setDialog(prev => ({ ...prev, error: "Name cannot be empty" }));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const normPath = dialog.targetPath.replace(/\\/g, "/");
|
|
377
|
+
const parentDir = normPath.substring(0, normPath.lastIndexOf("/"));
|
|
378
|
+
const newPath = parentDir ? `${parentDir}/${dialog.inputValue.trim()}` : dialog.inputValue.trim();
|
|
379
|
+
|
|
380
|
+
const res = await fetch("/api/files", {
|
|
381
|
+
method: "POST",
|
|
382
|
+
headers: { "Content-Type": "application/json" },
|
|
383
|
+
body: JSON.stringify({
|
|
384
|
+
action: "rename",
|
|
385
|
+
oldPath: dialog.targetPath,
|
|
386
|
+
newPath: newPath,
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (res.ok) {
|
|
391
|
+
const data = await res.json();
|
|
392
|
+
if (data.success) {
|
|
393
|
+
// Update openFiles state if renamed file was open
|
|
394
|
+
setOpenFiles(prev => prev.map(f => {
|
|
395
|
+
if (f.path === dialog.targetPath) {
|
|
396
|
+
return {
|
|
397
|
+
...f,
|
|
398
|
+
path: newPath,
|
|
399
|
+
name: dialog.inputValue.trim()
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return f;
|
|
403
|
+
}));
|
|
404
|
+
if (activeFilePath === dialog.targetPath) {
|
|
405
|
+
setActiveFilePath(newPath);
|
|
406
|
+
}
|
|
407
|
+
setDialog({ type: null, targetPath: null, inputValue: "", error: null });
|
|
408
|
+
await loadFiles();
|
|
409
|
+
} else {
|
|
410
|
+
setDialog(prev => ({ ...prev, error: data.error || "Failed to rename item" }));
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
const data = await res.json();
|
|
414
|
+
setDialog(prev => ({ ...prev, error: data.error || "Failed to rename item" }));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (dialog.type === "delete") {
|
|
419
|
+
const res = await fetch("/api/files", {
|
|
420
|
+
method: "POST",
|
|
421
|
+
headers: { "Content-Type": "application/json" },
|
|
422
|
+
body: JSON.stringify({
|
|
423
|
+
action: "delete",
|
|
424
|
+
path: dialog.targetPath,
|
|
425
|
+
}),
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (res.ok) {
|
|
429
|
+
const data = await res.json();
|
|
430
|
+
if (data.success) {
|
|
431
|
+
handleCloseFile(dialog.targetPath);
|
|
432
|
+
setDialog({ type: null, targetPath: null, inputValue: "", error: null });
|
|
433
|
+
await loadFiles();
|
|
434
|
+
} else {
|
|
435
|
+
setDialog(prev => ({ ...prev, error: data.error || "Failed to delete item" }));
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
const data = await res.json();
|
|
439
|
+
setDialog(prev => ({ ...prev, error: data.error || "Failed to delete item" }));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} catch (err) {
|
|
443
|
+
console.error("File operation failed:", err);
|
|
444
|
+
setDialog(prev => ({ ...prev, error: "An unexpected error occurred" }));
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const fetchFlatFiles = useCallback(async () => {
|
|
449
|
+
try {
|
|
450
|
+
const scopedQuery = scopedDirs && scopedDirs.length > 0 ? `&scoped=${encodeURIComponent(scopedDirs.join(","))}` : "";
|
|
451
|
+
const res = await fetch(`/api/files?action=flat_list&root=${encodeURIComponent(workspaceRoot)}${scopedQuery}`);
|
|
452
|
+
if (res.ok) {
|
|
453
|
+
const data = await res.json();
|
|
454
|
+
if (data.success && data.files) {
|
|
455
|
+
setFlatFiles(data.files);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
console.error("Failed to load flat files list:", err);
|
|
460
|
+
}
|
|
461
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
462
|
+
}, [workspaceRoot, scopedDirsStr]);
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (searchOpen) {
|
|
466
|
+
// Defer state updates to prevent synchronous execution inside useEffect
|
|
467
|
+
setTimeout(() => {
|
|
468
|
+
setSearchQuery("");
|
|
469
|
+
setSearchSelectedIndex(0);
|
|
470
|
+
fetchFlatFiles();
|
|
471
|
+
}, 0);
|
|
472
|
+
}
|
|
473
|
+
}, [searchOpen, fetchFlatFiles]);
|
|
474
|
+
|
|
475
|
+
// Handle file click in explorer
|
|
476
|
+
const handleOpenFile = async (filePath: string) => {
|
|
477
|
+
// Check if already open
|
|
478
|
+
const alreadyOpen = openFiles.find((f) => f.path === filePath);
|
|
479
|
+
if (alreadyOpen) {
|
|
480
|
+
setActiveFilePath(filePath);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
const res = await fetch(`/api/files?action=read&path=${encodeURIComponent(filePath)}`);
|
|
486
|
+
if (res.ok) {
|
|
487
|
+
const data = await res.json();
|
|
488
|
+
if (data.success) {
|
|
489
|
+
const newFile: OpenFile = {
|
|
490
|
+
name: filePath.split(/[/\\]/).pop() || "Untitled",
|
|
491
|
+
path: filePath,
|
|
492
|
+
content: data.content,
|
|
493
|
+
isModified: false,
|
|
494
|
+
};
|
|
495
|
+
setOpenFiles([...openFiles, newFile]);
|
|
496
|
+
setActiveFilePath(filePath);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
} catch (err) {
|
|
500
|
+
console.error(`Failed to read file ${filePath}:`, err);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const handleCloseFile = (filePath: string) => {
|
|
505
|
+
const updated = openFiles.filter((f) => f.path !== filePath);
|
|
506
|
+
setOpenFiles(updated);
|
|
507
|
+
if (activeFilePath === filePath) {
|
|
508
|
+
setActiveFilePath(updated.length > 0 ? updated[updated.length - 1].path : null);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const activeFile = openFiles.find((f) => f.path === activeFilePath);
|
|
513
|
+
|
|
514
|
+
const handleContentChange = (newVal: string) => {
|
|
515
|
+
if (!activeFilePath) return;
|
|
516
|
+
setOpenFiles(
|
|
517
|
+
openFiles.map((f) => (f.path === activeFilePath ? { ...f, content: newVal, isModified: true } : f))
|
|
518
|
+
);
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const handleSaveFile = useCallback(async () => {
|
|
522
|
+
if (!activeFile) return;
|
|
523
|
+
try {
|
|
524
|
+
const res = await fetch("/api/files", {
|
|
525
|
+
method: "POST",
|
|
526
|
+
headers: { "Content-Type": "application/json" },
|
|
527
|
+
body: JSON.stringify({
|
|
528
|
+
action: "write",
|
|
529
|
+
path: activeFile.path,
|
|
530
|
+
content: activeFile.content,
|
|
531
|
+
}),
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
if (res.ok) {
|
|
535
|
+
setOpenFiles(prev =>
|
|
536
|
+
prev.map((f) => (f.path === activeFilePath ? { ...f, isModified: false } : f))
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
} catch (err) {
|
|
540
|
+
console.error(`Failed to save file ${activeFile.path}:`, err);
|
|
541
|
+
}
|
|
542
|
+
}, [activeFile, activeFilePath, setOpenFiles]);
|
|
543
|
+
|
|
544
|
+
// Listen for Ctrl+S and Ctrl+P
|
|
545
|
+
useEffect(() => {
|
|
546
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
547
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
handleSaveFile();
|
|
550
|
+
}
|
|
551
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "p") {
|
|
552
|
+
e.preventDefault();
|
|
553
|
+
setSearchOpen(prev => !prev);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
557
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
558
|
+
}, [handleSaveFile]);
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
const filteredFiles = flatFiles.filter(f =>
|
|
562
|
+
f.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
563
|
+
f.relativePath.toLowerCase().includes(searchQuery.toLowerCase())
|
|
564
|
+
).slice(0, 10);
|
|
565
|
+
|
|
566
|
+
const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
567
|
+
if (e.key === "ArrowDown") {
|
|
568
|
+
e.preventDefault();
|
|
569
|
+
setSearchSelectedIndex(prev => (prev + 1) % Math.max(1, filteredFiles.length));
|
|
570
|
+
} else if (e.key === "ArrowUp") {
|
|
571
|
+
e.preventDefault();
|
|
572
|
+
setSearchSelectedIndex(prev => (prev - 1 + filteredFiles.length) % Math.max(1, filteredFiles.length));
|
|
573
|
+
} else if (e.key === "Enter") {
|
|
574
|
+
e.preventDefault();
|
|
575
|
+
if (filteredFiles[searchSelectedIndex]) {
|
|
576
|
+
handleOpenFile(filteredFiles[searchSelectedIndex].path);
|
|
577
|
+
setSearchOpen(false);
|
|
578
|
+
}
|
|
579
|
+
} else if (e.key === "Escape") {
|
|
580
|
+
e.preventDefault();
|
|
581
|
+
setSearchOpen(false);
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
return (
|
|
586
|
+
<div style={ideContainerStyle}>
|
|
587
|
+
{/* Activity Bar (Sidebar leftmost panel icons) */}
|
|
588
|
+
<div style={activityBarStyle}>
|
|
589
|
+
<div style={topIconsStyle}>
|
|
590
|
+
{/* Explorer Tab */}
|
|
591
|
+
<div
|
|
592
|
+
onClick={() => {
|
|
593
|
+
if (activeTab === "explorer") {
|
|
594
|
+
setExplorerVisible(!explorerVisible);
|
|
595
|
+
} else {
|
|
596
|
+
setActiveTab("explorer");
|
|
597
|
+
setExplorerVisible(true);
|
|
598
|
+
}
|
|
599
|
+
}}
|
|
600
|
+
style={{
|
|
601
|
+
...sidebarIconStyle,
|
|
602
|
+
backgroundColor: activeTab === "explorer" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
|
|
603
|
+
borderLeft: activeTab === "explorer" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
|
|
604
|
+
}}
|
|
605
|
+
title="File Explorer (Ctrl+P to search files)"
|
|
606
|
+
>
|
|
607
|
+
📁
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
{/* Git Source Control Tab */}
|
|
611
|
+
<div
|
|
612
|
+
onClick={() => {
|
|
613
|
+
if (activeTab === "git") {
|
|
614
|
+
setExplorerVisible(!explorerVisible);
|
|
615
|
+
} else {
|
|
616
|
+
setActiveTab("git");
|
|
617
|
+
setExplorerVisible(true);
|
|
618
|
+
}
|
|
619
|
+
}}
|
|
620
|
+
style={{
|
|
621
|
+
...sidebarIconStyle,
|
|
622
|
+
backgroundColor: activeTab === "git" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
|
|
623
|
+
borderLeft: activeTab === "git" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
|
|
624
|
+
}}
|
|
625
|
+
title="Source Control (Git Changes)"
|
|
626
|
+
>
|
|
627
|
+
🌿
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
{/* Token & Cost Analytics Tab */}
|
|
631
|
+
<div
|
|
632
|
+
onClick={() => {
|
|
633
|
+
if (activeTab === "analytics") {
|
|
634
|
+
setExplorerVisible(!explorerVisible);
|
|
635
|
+
} else {
|
|
636
|
+
setActiveTab("analytics");
|
|
637
|
+
setExplorerVisible(true);
|
|
638
|
+
}
|
|
639
|
+
}}
|
|
640
|
+
style={{
|
|
641
|
+
...sidebarIconStyle,
|
|
642
|
+
backgroundColor: activeTab === "analytics" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
|
|
643
|
+
borderLeft: activeTab === "analytics" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
|
|
644
|
+
}}
|
|
645
|
+
title="Token Usage & Cost Analytics"
|
|
646
|
+
>
|
|
647
|
+
📊
|
|
648
|
+
</div>
|
|
649
|
+
|
|
650
|
+
{/* Log Auditor Tab */}
|
|
651
|
+
<div
|
|
652
|
+
onClick={() => {
|
|
653
|
+
if (activeTab === "auditor") {
|
|
654
|
+
setExplorerVisible(!explorerVisible);
|
|
655
|
+
} else {
|
|
656
|
+
setActiveTab("auditor");
|
|
657
|
+
setExplorerVisible(true);
|
|
658
|
+
}
|
|
659
|
+
}}
|
|
660
|
+
style={{
|
|
661
|
+
...sidebarIconStyle,
|
|
662
|
+
backgroundColor: activeTab === "auditor" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
|
|
663
|
+
borderLeft: activeTab === "auditor" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
|
|
664
|
+
}}
|
|
665
|
+
title="Session Log Auditor & Search"
|
|
666
|
+
>
|
|
667
|
+
🔍
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<div
|
|
672
|
+
onClick={onClose}
|
|
673
|
+
style={{ ...sidebarIconStyle, color: "var(--danger)", fontSize: "1rem" }}
|
|
674
|
+
title="Exit Session Layout"
|
|
675
|
+
>
|
|
676
|
+
✖
|
|
677
|
+
</div>
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
{/* Primary Sidebar (File Tree Panel) */}
|
|
681
|
+
{explorerVisible && (
|
|
682
|
+
<div style={sidebarPanelStyle}>
|
|
683
|
+
<div style={sidebarHeaderStyle}>
|
|
684
|
+
<span style={{ fontWeight: 700, fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
685
|
+
{activeTab === "explorer" && "Workspace Explorer"}
|
|
686
|
+
{activeTab === "git" && "Source Control"}
|
|
687
|
+
{activeTab === "analytics" && "Telemetry & Cost"}
|
|
688
|
+
{activeTab === "auditor" && "Session Auditor"}
|
|
689
|
+
</span>
|
|
690
|
+
{activeTab === "explorer" && (
|
|
691
|
+
<div style={{ display: "flex", gap: "0.25rem" }}>
|
|
692
|
+
{workspace && (
|
|
693
|
+
<>
|
|
694
|
+
<button
|
|
695
|
+
onClick={() => handleTriggerCreate(workspace.path, "file")}
|
|
696
|
+
style={iconButtonStyle}
|
|
697
|
+
title="New File at Root..."
|
|
698
|
+
>
|
|
699
|
+
📄+
|
|
700
|
+
</button>
|
|
701
|
+
<button
|
|
702
|
+
onClick={() => handleTriggerCreate(workspace.path, "directory")}
|
|
703
|
+
style={iconButtonStyle}
|
|
704
|
+
title="New Folder at Root..."
|
|
705
|
+
>
|
|
706
|
+
📁+
|
|
707
|
+
</button>
|
|
708
|
+
</>
|
|
709
|
+
)}
|
|
710
|
+
<button
|
|
711
|
+
onClick={loadFiles}
|
|
712
|
+
disabled={isRefreshing}
|
|
713
|
+
style={iconButtonStyle}
|
|
714
|
+
title="Refresh tree"
|
|
715
|
+
>
|
|
716
|
+
🔄
|
|
717
|
+
</button>
|
|
718
|
+
</div>
|
|
719
|
+
)}
|
|
720
|
+
{activeTab === "git" && (
|
|
721
|
+
<button
|
|
722
|
+
onClick={loadGitStatus}
|
|
723
|
+
disabled={gitLoading}
|
|
724
|
+
style={iconButtonStyle}
|
|
725
|
+
title="Refresh git status"
|
|
726
|
+
>
|
|
727
|
+
🔄
|
|
728
|
+
</button>
|
|
729
|
+
)}
|
|
730
|
+
</div>
|
|
731
|
+
|
|
732
|
+
<div style={sidebarContentStyle}>
|
|
733
|
+
{activeTab === "explorer" && (
|
|
734
|
+
<>
|
|
735
|
+
{/* Workspace Root Section */}
|
|
736
|
+
{workspace ? (
|
|
737
|
+
<div style={{ marginBottom: "1.5rem" }}>
|
|
738
|
+
<div style={treeSectionHeaderStyle}>
|
|
739
|
+
💼 {workspace.name} <span style={pathBadgeStyle} title={workspace.path}>root</span>
|
|
740
|
+
</div>
|
|
741
|
+
<div style={{ padding: "0.25rem 0.5rem" }}>
|
|
742
|
+
{workspace.tree.map((node) => (
|
|
743
|
+
<FileItemNode
|
|
744
|
+
key={node.path}
|
|
745
|
+
node={node}
|
|
746
|
+
onSelect={handleOpenFile}
|
|
747
|
+
onCreate={handleTriggerCreate}
|
|
748
|
+
onRename={handleTriggerRename}
|
|
749
|
+
onDelete={handleTriggerDelete}
|
|
750
|
+
isReadOnly={false}
|
|
751
|
+
/>
|
|
752
|
+
))}
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
) : (
|
|
756
|
+
<div style={{ padding: "1rem", color: "var(--muted)", fontSize: "0.8rem" }}>
|
|
757
|
+
Scanning Workspace directory...
|
|
758
|
+
</div>
|
|
759
|
+
)}
|
|
760
|
+
|
|
761
|
+
{/* Scoped Directories Section */}
|
|
762
|
+
{scoped.length > 0 && (
|
|
763
|
+
<div>
|
|
764
|
+
<div style={{ ...treeSectionHeaderStyle, borderTop: "1px solid var(--border)", paddingTop: "1rem" }}>
|
|
765
|
+
🔍 Scoped Folders ({scoped.length})
|
|
766
|
+
</div>
|
|
767
|
+
{scoped.map((scope) => (
|
|
768
|
+
<div key={scope.path} style={{ marginTop: "0.75rem", padding: "0 0.5rem" }}>
|
|
769
|
+
<div style={{ ...itemHeaderStyle, display: "flex", gap: "0.25rem", color: "#a855f7", fontSize: "0.75rem", fontWeight: 700, padding: "0.25rem" }} title={scope.path}>
|
|
770
|
+
🔗 {scope.name}/
|
|
771
|
+
</div>
|
|
772
|
+
<div style={{ paddingLeft: "0.25rem" }}>
|
|
773
|
+
{scope.tree.map((node) => (
|
|
774
|
+
<FileItemNode
|
|
775
|
+
key={node.path}
|
|
776
|
+
node={node}
|
|
777
|
+
onSelect={handleOpenFile}
|
|
778
|
+
onCreate={handleTriggerCreate}
|
|
779
|
+
onRename={handleTriggerRename}
|
|
780
|
+
onDelete={handleTriggerDelete}
|
|
781
|
+
isReadOnly={true}
|
|
782
|
+
/>
|
|
783
|
+
))}
|
|
784
|
+
</div>
|
|
785
|
+
</div>
|
|
786
|
+
))}
|
|
787
|
+
</div>
|
|
788
|
+
)}
|
|
789
|
+
</>
|
|
790
|
+
)}
|
|
791
|
+
|
|
792
|
+
{activeTab === "git" && (
|
|
793
|
+
<div style={{ padding: "0.5rem 1rem" }}>
|
|
794
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
795
|
+
Changes
|
|
796
|
+
</div>
|
|
797
|
+
{!isGitRepo ? (
|
|
798
|
+
<div style={{ color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
|
|
799
|
+
Not a git repository.
|
|
800
|
+
</div>
|
|
801
|
+
) : gitFiles.length === 0 ? (
|
|
802
|
+
<div style={{ color: "var(--success)", fontSize: "0.75rem", padding: "0.5rem 0" }}>
|
|
803
|
+
✔ Workspace clean.
|
|
804
|
+
</div>
|
|
805
|
+
) : (
|
|
806
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.25rem" }}>
|
|
807
|
+
{gitFiles.map((file) => {
|
|
808
|
+
let badgeColor = "#eab308";
|
|
809
|
+
if (file.status === "??") badgeColor = "#10b981";
|
|
810
|
+
if (file.status === "A") badgeColor = "#10b981";
|
|
811
|
+
if (file.status === "D") badgeColor = "#ef4444";
|
|
812
|
+
return (
|
|
813
|
+
<div
|
|
814
|
+
key={file.relativePath}
|
|
815
|
+
onClick={async () => {
|
|
816
|
+
try {
|
|
817
|
+
const res = await fetch(`/api/git?action=diff&file=${encodeURIComponent(file.relativePath)}&root=${encodeURIComponent(workspaceRoot)}`);
|
|
818
|
+
if (res.ok) {
|
|
819
|
+
const data = await res.json();
|
|
820
|
+
if (data.success) {
|
|
821
|
+
const diffPath = `diff:${file.relativePath}`;
|
|
822
|
+
if (!openFiles.some(f => f.path === diffPath)) {
|
|
823
|
+
setOpenFiles(prev => [
|
|
824
|
+
...prev,
|
|
825
|
+
{
|
|
826
|
+
name: `Diff: ${file.name}`,
|
|
827
|
+
path: diffPath,
|
|
828
|
+
content: data.diff,
|
|
829
|
+
isModified: false
|
|
830
|
+
}
|
|
831
|
+
]);
|
|
832
|
+
}
|
|
833
|
+
setActiveFilePath(diffPath);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} catch (e) {
|
|
837
|
+
console.error(e);
|
|
838
|
+
}
|
|
839
|
+
}}
|
|
840
|
+
style={{
|
|
841
|
+
display: "flex",
|
|
842
|
+
alignItems: "center",
|
|
843
|
+
justifyContent: "space-between",
|
|
844
|
+
padding: "0.35rem 0.5rem",
|
|
845
|
+
borderRadius: "4px",
|
|
846
|
+
backgroundColor: "rgba(255,255,255,0.02)",
|
|
847
|
+
cursor: "pointer",
|
|
848
|
+
fontSize: "0.75rem",
|
|
849
|
+
fontFamily: "var(--font-mono)",
|
|
850
|
+
}}
|
|
851
|
+
onMouseEnter={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.06)"}
|
|
852
|
+
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.02)"}
|
|
853
|
+
>
|
|
854
|
+
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }} title={file.relativePath}>
|
|
855
|
+
{file.name}
|
|
856
|
+
</span>
|
|
857
|
+
<span style={{ fontSize: "0.6rem", padding: "0.1rem 0.3rem", borderRadius: "3px", backgroundColor: badgeColor + "22", color: badgeColor, fontWeight: 700 }}>
|
|
858
|
+
{file.status}
|
|
859
|
+
</span>
|
|
860
|
+
</div>
|
|
861
|
+
);
|
|
862
|
+
})}
|
|
863
|
+
</div>
|
|
864
|
+
)}
|
|
865
|
+
</div>
|
|
866
|
+
)}
|
|
867
|
+
|
|
868
|
+
{activeTab === "analytics" && (
|
|
869
|
+
<div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1.25rem" }}>
|
|
870
|
+
<div>
|
|
871
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
872
|
+
Token Usage
|
|
873
|
+
</div>
|
|
874
|
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.5rem" }}>
|
|
875
|
+
<div style={analyticsCardStyle}>
|
|
876
|
+
<div style={analyticsCardLabelStyle}>Input</div>
|
|
877
|
+
<div style={analyticsCardValueStyle}>{(sessionStatus?.tokenUsage?.input ?? 0).toLocaleString()}</div>
|
|
878
|
+
</div>
|
|
879
|
+
<div style={analyticsCardStyle}>
|
|
880
|
+
<div style={analyticsCardLabelStyle}>Output</div>
|
|
881
|
+
<div style={analyticsCardValueStyle}>{(sessionStatus?.tokenUsage?.output ?? 0).toLocaleString()}</div>
|
|
882
|
+
</div>
|
|
883
|
+
<div style={{ ...analyticsCardStyle, gridColumn: "span 2" }}>
|
|
884
|
+
<div style={analyticsCardLabelStyle}>Cost</div>
|
|
885
|
+
<div style={{ ...analyticsCardValueStyle, color: "#10b981", fontSize: "1.25rem" }}>
|
|
886
|
+
${(sessionStatus?.tokenUsage?.cost ?? 0).toFixed(4)}
|
|
887
|
+
</div>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
|
|
892
|
+
<div>
|
|
893
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
894
|
+
Resource Telemetry
|
|
895
|
+
</div>
|
|
896
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
|
|
897
|
+
<div>
|
|
898
|
+
<div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.7rem", marginBottom: "0.25rem" }}>
|
|
899
|
+
<span>CPU Usage</span>
|
|
900
|
+
<span style={{ fontWeight: 600, color: "var(--primary)" }}>{stats?.cpu ?? "0.0%"}</span>
|
|
901
|
+
</div>
|
|
902
|
+
<div style={gaugeOuterStyle}>
|
|
903
|
+
<div style={{ ...gaugeInnerStyle, width: stats?.cpu || "0%", backgroundColor: "var(--primary)" }}></div>
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
<div>
|
|
907
|
+
<div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.7rem", marginBottom: "0.25rem" }}>
|
|
908
|
+
<span>Memory Usage</span>
|
|
909
|
+
<span style={{ fontWeight: 600, color: "#eab308" }}>{stats?.memory ?? "0.0MiB"}</span>
|
|
910
|
+
</div>
|
|
911
|
+
<div style={gaugeOuterStyle}>
|
|
912
|
+
<div style={{ ...gaugeInnerStyle, width: stats?.memoryPercent || "0%", backgroundColor: "#eab308" }}></div>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
<div style={{ fontSize: "0.6rem", color: "var(--muted)", marginTop: "0.25rem", fontStyle: "italic" }}>
|
|
916
|
+
{stats?.type?.includes("docker") ? "🐳 Docker container statistics" : "💻 Local process statistics"}
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
919
|
+
</div>
|
|
920
|
+
</div>
|
|
921
|
+
)}
|
|
922
|
+
|
|
923
|
+
{activeTab === "auditor" && (
|
|
924
|
+
<div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1rem", height: "100%" }}>
|
|
925
|
+
<div style={{ display: "flex", gap: "0.25rem" }}>
|
|
926
|
+
<input
|
|
927
|
+
type="text"
|
|
928
|
+
placeholder="Search logs..."
|
|
929
|
+
value={logSearchQuery}
|
|
930
|
+
onChange={(e) => setLogSearchQuery(e.target.value)}
|
|
931
|
+
style={{
|
|
932
|
+
flex: 1,
|
|
933
|
+
backgroundColor: "#05070a",
|
|
934
|
+
border: "1px solid var(--border)",
|
|
935
|
+
borderRadius: "4px",
|
|
936
|
+
padding: "0.25rem 0.5rem",
|
|
937
|
+
color: "#ffffff",
|
|
938
|
+
fontSize: "0.75rem",
|
|
939
|
+
outline: "none"
|
|
940
|
+
}}
|
|
941
|
+
/>
|
|
942
|
+
<button
|
|
943
|
+
onClick={handleExportLog}
|
|
944
|
+
style={{
|
|
945
|
+
backgroundColor: "rgba(59, 130, 246, 0.15)",
|
|
946
|
+
border: "1px solid #3b82f6",
|
|
947
|
+
color: "#3b82f6",
|
|
948
|
+
padding: "0.25rem 0.5rem",
|
|
949
|
+
borderRadius: "4px",
|
|
950
|
+
fontSize: "0.7rem",
|
|
951
|
+
fontWeight: 600,
|
|
952
|
+
cursor: "pointer"
|
|
953
|
+
}}
|
|
954
|
+
>
|
|
955
|
+
Export
|
|
956
|
+
</button>
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
<div style={{
|
|
960
|
+
height: "220px",
|
|
961
|
+
overflowY: "auto",
|
|
962
|
+
backgroundColor: "#05070a",
|
|
963
|
+
border: "1px solid var(--border)",
|
|
964
|
+
borderRadius: "6px",
|
|
965
|
+
padding: "0.5rem",
|
|
966
|
+
fontFamily: "var(--font-mono)",
|
|
967
|
+
fontSize: "0.7rem",
|
|
968
|
+
lineHeight: "1.4",
|
|
969
|
+
whiteSpace: "pre-wrap",
|
|
970
|
+
color: "#cbd5e1"
|
|
971
|
+
}}>
|
|
972
|
+
{(() => {
|
|
973
|
+
if (!sessionStatus?.logs || sessionStatus.logs.length === 0) {
|
|
974
|
+
return <div style={{ color: "var(--muted)", fontStyle: "italic", textAlign: "center" }}>No logs captured.</div>;
|
|
975
|
+
}
|
|
976
|
+
const fullLogString = sessionStatus.logs.join("");
|
|
977
|
+
const cleanLogs = fullLogString.replace(/\x1b\[[0-9;]*m/g, "");
|
|
978
|
+
const lines = cleanLogs.split(/\r?\n/);
|
|
979
|
+
const filteredLines = logSearchQuery.trim()
|
|
980
|
+
? lines.filter((line: string) => line.toLowerCase().includes(logSearchQuery.toLowerCase()))
|
|
981
|
+
: lines;
|
|
982
|
+
if (filteredLines.length === 0) {
|
|
983
|
+
return <div style={{ color: "var(--muted)", fontStyle: "italic", textAlign: "center" }}>No matches.</div>;
|
|
984
|
+
}
|
|
985
|
+
return filteredLines.map((line: string, i: number) => {
|
|
986
|
+
if (!logSearchQuery.trim()) return <div key={i}>{line}</div>;
|
|
987
|
+
const parts = line.split(new RegExp(`(${logSearchQuery})`, "gi"));
|
|
988
|
+
return (
|
|
989
|
+
<div key={i}>
|
|
990
|
+
{parts.map((part: string, j: number) =>
|
|
991
|
+
part.toLowerCase() === logSearchQuery.toLowerCase()
|
|
992
|
+
? <span key={j} style={{ backgroundColor: "#eab308", color: "#000000", fontWeight: 600 }}>{part}</span>
|
|
993
|
+
: part
|
|
994
|
+
)}
|
|
995
|
+
</div>
|
|
996
|
+
);
|
|
997
|
+
});
|
|
998
|
+
})()}
|
|
999
|
+
</div>
|
|
1000
|
+
|
|
1001
|
+
<div style={{ borderTop: "1px solid var(--border)", paddingTop: "0.75rem" }}>
|
|
1002
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
1003
|
+
Custom Buttons
|
|
1004
|
+
</div>
|
|
1005
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
|
|
1006
|
+
<input
|
|
1007
|
+
type="text"
|
|
1008
|
+
placeholder="Button Name (e.g. /undo)"
|
|
1009
|
+
value={newCmdName}
|
|
1010
|
+
onChange={(e) => setNewCmdName(e.target.value)}
|
|
1011
|
+
style={{
|
|
1012
|
+
backgroundColor: "#05070a",
|
|
1013
|
+
border: "1px solid var(--border)",
|
|
1014
|
+
borderRadius: "4px",
|
|
1015
|
+
padding: "0.25rem 0.5rem",
|
|
1016
|
+
color: "#ffffff",
|
|
1017
|
+
fontSize: "0.7rem",
|
|
1018
|
+
outline: "none"
|
|
1019
|
+
}}
|
|
1020
|
+
/>
|
|
1021
|
+
<input
|
|
1022
|
+
type="text"
|
|
1023
|
+
placeholder="Injection Command"
|
|
1024
|
+
value={newCmdStr}
|
|
1025
|
+
onChange={(e) => setNewCmdStr(e.target.value)}
|
|
1026
|
+
style={{
|
|
1027
|
+
backgroundColor: "#05070a",
|
|
1028
|
+
border: "1px solid var(--border)",
|
|
1029
|
+
borderRadius: "4px",
|
|
1030
|
+
padding: "0.25rem 0.5rem",
|
|
1031
|
+
color: "#ffffff",
|
|
1032
|
+
fontSize: "0.7rem",
|
|
1033
|
+
outline: "none"
|
|
1034
|
+
}}
|
|
1035
|
+
/>
|
|
1036
|
+
<button
|
|
1037
|
+
onClick={handleAddCommand}
|
|
1038
|
+
style={{
|
|
1039
|
+
backgroundColor: "rgba(168, 85, 247, 0.15)",
|
|
1040
|
+
border: "1px solid #a855f7",
|
|
1041
|
+
color: "#c084fc",
|
|
1042
|
+
padding: "0.3rem",
|
|
1043
|
+
borderRadius: "4px",
|
|
1044
|
+
fontSize: "0.7rem",
|
|
1045
|
+
fontWeight: 600,
|
|
1046
|
+
cursor: "pointer"
|
|
1047
|
+
}}
|
|
1048
|
+
>
|
|
1049
|
+
+ Add Button
|
|
1050
|
+
</button>
|
|
1051
|
+
{customCommands.length > 0 && (
|
|
1052
|
+
<div style={{ marginTop: "0.25rem", maxHeight: "60px", overflowY: "auto" }}>
|
|
1053
|
+
{customCommands.map((cmd, idx) => (
|
|
1054
|
+
<div key={idx} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "0.65rem", backgroundColor: "rgba(255,255,255,0.02)", padding: "0.1rem 0.25rem", borderRadius: "3px", marginTop: "0.15rem" }}>
|
|
1055
|
+
<span style={{ color: "#c084fc" }}>{cmd.name}</span>
|
|
1056
|
+
<span onClick={() => handleDeleteCommand(idx)} style={{ color: "var(--danger)", cursor: "pointer" }}>✖</span>
|
|
1057
|
+
</div>
|
|
1058
|
+
))}
|
|
1059
|
+
</div>
|
|
1060
|
+
)}
|
|
1061
|
+
</div>
|
|
1062
|
+
</div>
|
|
1063
|
+
</div>
|
|
1064
|
+
)}
|
|
1065
|
+
</div>
|
|
1066
|
+
</div>
|
|
1067
|
+
)}
|
|
1068
|
+
|
|
1069
|
+
{/* Editor & Console Split View */}
|
|
1070
|
+
<div style={mainContentAreaStyle}>
|
|
1071
|
+
{/* Session Tab Bar Switcher */}
|
|
1072
|
+
<div style={sessionTabBarStyle}>
|
|
1073
|
+
<div style={{ display: "flex", gap: "0.25rem", overflowX: "auto" }}>
|
|
1074
|
+
{sessions.map((s) => {
|
|
1075
|
+
const isActive = s.id === sessionId;
|
|
1076
|
+
const statusColor = s.status === "running" ? "#10b981" : "rgba(255,255,255,0.2)";
|
|
1077
|
+
return (
|
|
1078
|
+
<div
|
|
1079
|
+
key={s.id}
|
|
1080
|
+
onClick={() => {
|
|
1081
|
+
// Close split view if switching to that session
|
|
1082
|
+
if (splitSessionId === s.id) {
|
|
1083
|
+
setSplitSessionId(null);
|
|
1084
|
+
}
|
|
1085
|
+
onSwitchSession(s.id);
|
|
1086
|
+
}}
|
|
1087
|
+
style={{
|
|
1088
|
+
...sessionTabItemStyle,
|
|
1089
|
+
backgroundColor: isActive ? "#0b101b" : "rgba(8,12,20,0.6)",
|
|
1090
|
+
borderColor: isActive ? "var(--border-focus)" : "var(--border)",
|
|
1091
|
+
color: isActive ? "var(--foreground)" : "var(--muted)",
|
|
1092
|
+
}}
|
|
1093
|
+
>
|
|
1094
|
+
<span
|
|
1095
|
+
style={{
|
|
1096
|
+
width: "6px",
|
|
1097
|
+
height: "6px",
|
|
1098
|
+
borderRadius: "50%",
|
|
1099
|
+
backgroundColor: statusColor,
|
|
1100
|
+
display: "inline-block",
|
|
1101
|
+
}}
|
|
1102
|
+
/>
|
|
1103
|
+
<span style={{ fontSize: "0.75rem", fontWeight: isActive ? 600 : 500 }}>
|
|
1104
|
+
{s.name}
|
|
1105
|
+
</span>
|
|
1106
|
+
<span style={{ fontSize: "0.6rem", color: "var(--muted)", backgroundColor: "rgba(255,255,255,0.05)", padding: "1px 4px", borderRadius: "3px" }}>
|
|
1107
|
+
{s.type.replace("claude-", "").replace("aider-", "")}
|
|
1108
|
+
</span>
|
|
1109
|
+
</div>
|
|
1110
|
+
);
|
|
1111
|
+
})}
|
|
1112
|
+
</div>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
{/* Editor Panel (top half) */}
|
|
1116
|
+
<div style={editorPanelStyle}>
|
|
1117
|
+
{/* Tabs header */}
|
|
1118
|
+
<div style={tabHeaderStyle}>
|
|
1119
|
+
<div style={tabsWrapperStyle}>
|
|
1120
|
+
{openFiles.map((file) => (
|
|
1121
|
+
<div
|
|
1122
|
+
key={file.path}
|
|
1123
|
+
style={{
|
|
1124
|
+
...tabItemStyle,
|
|
1125
|
+
backgroundColor: activeFilePath === file.path ? "#0b101b" : "rgba(8,12,20,0.4)",
|
|
1126
|
+
borderColor: activeFilePath === file.path ? "var(--border-focus)" : "transparent",
|
|
1127
|
+
color: activeFilePath === file.path ? "var(--foreground)" : "var(--muted)",
|
|
1128
|
+
}}
|
|
1129
|
+
onClick={() => setActiveFilePath(file.path)}
|
|
1130
|
+
>
|
|
1131
|
+
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
|
1132
|
+
{file.name} {file.isModified && <span style={modifiedDotStyle}>●</span>}
|
|
1133
|
+
</span>
|
|
1134
|
+
<span
|
|
1135
|
+
onClick={(e) => {
|
|
1136
|
+
e.stopPropagation();
|
|
1137
|
+
handleCloseFile(file.path);
|
|
1138
|
+
}}
|
|
1139
|
+
style={closeTabStyle}
|
|
1140
|
+
>
|
|
1141
|
+
✖
|
|
1142
|
+
</span>
|
|
1143
|
+
</div>
|
|
1144
|
+
))}
|
|
1145
|
+
</div>
|
|
1146
|
+
|
|
1147
|
+
{activeFile && !activeFile.path.startsWith("diff:") && (
|
|
1148
|
+
<button
|
|
1149
|
+
onClick={handleSaveFile}
|
|
1150
|
+
disabled={!activeFile.isModified}
|
|
1151
|
+
style={{
|
|
1152
|
+
...saveButtonStyle,
|
|
1153
|
+
opacity: activeFile.isModified ? 1 : 0.5,
|
|
1154
|
+
cursor: activeFile.isModified ? "pointer" : "not-allowed",
|
|
1155
|
+
}}
|
|
1156
|
+
>
|
|
1157
|
+
💾 Save (Ctrl+S)
|
|
1158
|
+
</button>
|
|
1159
|
+
)}
|
|
1160
|
+
</div>
|
|
1161
|
+
|
|
1162
|
+
{/* Editor contents */}
|
|
1163
|
+
<div style={editorWorkspaceStyle}>
|
|
1164
|
+
{activeFile ? (
|
|
1165
|
+
activeFile.path.startsWith("diff:") ? (
|
|
1166
|
+
<div style={{ fontFamily: "var(--font-mono)", fontSize: "0.8rem", padding: "1rem", overflow: "auto", height: "100%", backgroundColor: "#0b101b", lineHeight: 1.5 }}>
|
|
1167
|
+
{activeFile.content.split("\n").map((line, i) => {
|
|
1168
|
+
let color = "#e2e8f0";
|
|
1169
|
+
let bg = "transparent";
|
|
1170
|
+
if (line.startsWith("+")) {
|
|
1171
|
+
color = "#4ade80";
|
|
1172
|
+
bg = "rgba(74, 222, 128, 0.08)";
|
|
1173
|
+
} else if (line.startsWith("-")) {
|
|
1174
|
+
color = "#f87171";
|
|
1175
|
+
bg = "rgba(248, 113, 113, 0.08)";
|
|
1176
|
+
} else if (line.startsWith("@@")) {
|
|
1177
|
+
color = "#60a5fa";
|
|
1178
|
+
bg = "rgba(96, 165, 250, 0.05)";
|
|
1179
|
+
}
|
|
1180
|
+
return (
|
|
1181
|
+
<div key={i} style={{ color, backgroundColor: bg, whiteSpace: "pre-wrap", padding: "0.1rem 0.5rem" }}>
|
|
1182
|
+
{line}
|
|
1183
|
+
</div>
|
|
1184
|
+
);
|
|
1185
|
+
})}
|
|
1186
|
+
</div>
|
|
1187
|
+
) : (
|
|
1188
|
+
<textarea
|
|
1189
|
+
value={activeFile.content}
|
|
1190
|
+
onChange={(e) => handleContentChange(e.target.value)}
|
|
1191
|
+
style={textareaStyle}
|
|
1192
|
+
spellCheck={false}
|
|
1193
|
+
/>
|
|
1194
|
+
)
|
|
1195
|
+
) : (
|
|
1196
|
+
<div style={emptyEditorStateStyle}>
|
|
1197
|
+
<div style={{ fontSize: "2rem", marginBottom: "1rem" }}>💻</div>
|
|
1198
|
+
<h4 style={{ margin: 0, fontWeight: 600 }}>NeuralLoom Workspace Editor</h4>
|
|
1199
|
+
<p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.8rem" }}>
|
|
1200
|
+
Select files from the left explorer panel to view and edit. Use Ctrl+S to save changes.
|
|
1201
|
+
</p>
|
|
1202
|
+
<p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
|
|
1203
|
+
Press <kbd style={{ background: "rgba(255,255,255,0.1)", padding: "2px 4px", borderRadius: "3px" }}>Ctrl+P</kbd> to search files.
|
|
1204
|
+
</p>
|
|
1205
|
+
</div>
|
|
1206
|
+
)}
|
|
1207
|
+
</div>
|
|
1208
|
+
</div>
|
|
1209
|
+
|
|
1210
|
+
{/* Console / PTY Bridge Panel (bottom half) */}
|
|
1211
|
+
<div style={terminalPanelStyle}>
|
|
1212
|
+
{/* Dashboard control headers injected above xterm */}
|
|
1213
|
+
<div style={terminalHeaderStyle}>
|
|
1214
|
+
<span style={{ display: "flex", alignItems: "center", gap: "0.5rem", fontWeight: 600, fontSize: "0.8rem" }}>
|
|
1215
|
+
🖥️ Session Console Bridge {splitSessionId && `(Split View Active)`}
|
|
1216
|
+
</span>
|
|
1217
|
+
|
|
1218
|
+
<div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
|
|
1219
|
+
{/* Split dropdown controller */}
|
|
1220
|
+
<select
|
|
1221
|
+
onChange={(e) => {
|
|
1222
|
+
const val = e.target.value;
|
|
1223
|
+
setSplitSessionId(val ? val : null);
|
|
1224
|
+
}}
|
|
1225
|
+
value={splitSessionId || ""}
|
|
1226
|
+
style={{
|
|
1227
|
+
backgroundColor: "#080c14",
|
|
1228
|
+
border: "1px solid var(--border)",
|
|
1229
|
+
borderRadius: "4px",
|
|
1230
|
+
padding: "0.2rem 0.5rem",
|
|
1231
|
+
color: "var(--foreground)",
|
|
1232
|
+
fontSize: "0.7rem",
|
|
1233
|
+
outline: "none",
|
|
1234
|
+
cursor: "pointer",
|
|
1235
|
+
}}
|
|
1236
|
+
>
|
|
1237
|
+
<option value="">🖥️ Single Terminal</option>
|
|
1238
|
+
{sessions
|
|
1239
|
+
.filter(s => s.id !== sessionId && s.status === 'running')
|
|
1240
|
+
.map(s => (
|
|
1241
|
+
<option key={s.id} value={s.id}>🖥️|🖥️ Split: {s.name}</option>
|
|
1242
|
+
))
|
|
1243
|
+
}
|
|
1244
|
+
</select>
|
|
1245
|
+
|
|
1246
|
+
<button
|
|
1247
|
+
onClick={async () => {
|
|
1248
|
+
await fetch("/api/sessions/inject", {
|
|
1249
|
+
method: "POST",
|
|
1250
|
+
headers: { "Content-Type": "application/json" },
|
|
1251
|
+
body: JSON.stringify({ id: sessionId, command: "/ccc" }),
|
|
1252
|
+
});
|
|
1253
|
+
}}
|
|
1254
|
+
style={contextButtonStyle}
|
|
1255
|
+
>
|
|
1256
|
+
🚀 Mount Context (/ccc)
|
|
1257
|
+
</button>
|
|
1258
|
+
<button
|
|
1259
|
+
onClick={async () => {
|
|
1260
|
+
await fetch("/api/sessions/inject", {
|
|
1261
|
+
method: "POST",
|
|
1262
|
+
headers: { "Content-Type": "application/json" },
|
|
1263
|
+
body: JSON.stringify({ id: sessionId, command: "/ccp" }),
|
|
1264
|
+
});
|
|
1265
|
+
}}
|
|
1266
|
+
style={promptButtonStyle}
|
|
1267
|
+
>
|
|
1268
|
+
⚡ Trigger Prompt (/ccp)
|
|
1269
|
+
</button>
|
|
1270
|
+
</div>
|
|
1271
|
+
</div>
|
|
1272
|
+
|
|
1273
|
+
{/* Quick command buttons toolbar */}
|
|
1274
|
+
<div style={quickCommandToolbarStyle}>
|
|
1275
|
+
<span style={{ fontSize: "0.7rem", color: "var(--muted)", textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 700 }}>
|
|
1276
|
+
Quick Commands:
|
|
1277
|
+
</span>
|
|
1278
|
+
<div style={{ display: "flex", gap: "0.35rem", flexWrap: "wrap", alignItems: "center" }}>
|
|
1279
|
+
{isAider ? (
|
|
1280
|
+
<>
|
|
1281
|
+
<button onClick={() => triggerInject("/undo")} style={quickCmdBtnStyle} title="Aider: undo last commit">/undo</button>
|
|
1282
|
+
<button onClick={() => triggerInject("/diff")} style={quickCmdBtnStyle} title="Aider: show git diff">/diff</button>
|
|
1283
|
+
<button onClick={() => triggerInject("/commit")} style={quickCmdBtnStyle} title="Aider: commit edits">/commit</button>
|
|
1284
|
+
<button onClick={() => triggerInject("/test")} style={quickCmdBtnStyle} title="Aider: run tests">/test</button>
|
|
1285
|
+
<button onClick={() => triggerInject("/clear")} style={quickCmdBtnStyle} title="Clear terminal screen">/clear</button>
|
|
1286
|
+
</>
|
|
1287
|
+
) : (
|
|
1288
|
+
<>
|
|
1289
|
+
<button onClick={() => triggerInject("/compact")} style={quickCmdBtnStyle} title="Claude Code: compact history">/compact</button>
|
|
1290
|
+
<button onClick={() => triggerInject("/history")} style={quickCmdBtnStyle} title="Claude Code: view history">/history</button>
|
|
1291
|
+
<button onClick={() => triggerInject("/reset")} style={quickCmdBtnStyle} title="Claude Code: reset conversation">/reset</button>
|
|
1292
|
+
<button onClick={() => triggerInject("/help")} style={quickCmdBtnStyle} title="Claude Code: show help">/help</button>
|
|
1293
|
+
</>
|
|
1294
|
+
)}
|
|
1295
|
+
|
|
1296
|
+
{/* Custom buttons */}
|
|
1297
|
+
{customCommands.map((cmd, i) => (
|
|
1298
|
+
<button
|
|
1299
|
+
key={i}
|
|
1300
|
+
onClick={() => triggerInject(cmd.command)}
|
|
1301
|
+
style={{ ...quickCmdBtnStyle, borderColor: "rgba(168, 85, 247, 0.4)", color: "#c084fc" }}
|
|
1302
|
+
title={`Inject command: ${cmd.command}`}
|
|
1303
|
+
>
|
|
1304
|
+
{cmd.name}
|
|
1305
|
+
</button>
|
|
1306
|
+
))}
|
|
1307
|
+
</div>
|
|
1308
|
+
</div>
|
|
1309
|
+
|
|
1310
|
+
<div style={{ ...terminalConsoleWrapperStyle, display: "flex", width: "100%", height: "100%" }}>
|
|
1311
|
+
<div style={{ flex: 1, height: "100%", borderRight: splitSessionId ? "1px solid var(--border)" : "none", overflow: "hidden" }}>
|
|
1312
|
+
<TerminalConsole sessionId={sessionId} height="100%" />
|
|
1313
|
+
</div>
|
|
1314
|
+
{splitSessionId && (
|
|
1315
|
+
<div style={{ flex: 1, height: "100%", overflow: "hidden", display: "flex", flexDirection: "column" }}>
|
|
1316
|
+
{/* Header for split console */}
|
|
1317
|
+
<div style={{ padding: "0.25rem 0.5rem", backgroundColor: "#030406", fontSize: "0.7rem", borderBottom: "1px solid var(--border)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
1318
|
+
<span style={{ color: "#a855f7", fontWeight: 600 }}>🔗 Focused PTY Connection: {sessions.find(s => s.id === splitSessionId)?.name}</span>
|
|
1319
|
+
<button
|
|
1320
|
+
onClick={() => setSplitSessionId(null)}
|
|
1321
|
+
style={{ background: "none", border: "none", color: "var(--danger)", cursor: "pointer", fontSize: "0.7rem" }}
|
|
1322
|
+
>
|
|
1323
|
+
Close Split ✖
|
|
1324
|
+
</button>
|
|
1325
|
+
</div>
|
|
1326
|
+
<div style={{ flex: 1, overflow: "hidden" }}>
|
|
1327
|
+
<TerminalConsole sessionId={splitSessionId} height="100%" />
|
|
1328
|
+
</div>
|
|
1329
|
+
</div>
|
|
1330
|
+
)}
|
|
1331
|
+
</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
</div>
|
|
1334
|
+
|
|
1335
|
+
{/* Overlay Modal for File Actions */}
|
|
1336
|
+
{dialog.type && (
|
|
1337
|
+
<div style={modalOverlayStyle}>
|
|
1338
|
+
<div style={modalContentStyle}>
|
|
1339
|
+
<h3 style={{ margin: "0 0 1rem 0", fontSize: "1rem", fontWeight: 600 }}>
|
|
1340
|
+
{dialog.type === "create-file" && "Create New File"}
|
|
1341
|
+
{dialog.type === "create-folder" && "Create New Folder"}
|
|
1342
|
+
{dialog.type === "rename" && "Rename Item"}
|
|
1343
|
+
{dialog.type === "delete" && "Confirm Deletion"}
|
|
1344
|
+
</h3>
|
|
1345
|
+
|
|
1346
|
+
{dialog.error && (
|
|
1347
|
+
<div style={{ color: "var(--danger)", fontSize: "0.8rem", marginBottom: "1rem" }}>
|
|
1348
|
+
⚠️ {dialog.error}
|
|
1349
|
+
</div>
|
|
1350
|
+
)}
|
|
1351
|
+
|
|
1352
|
+
{dialog.type !== "delete" ? (
|
|
1353
|
+
<form onSubmit={handleDialogSubmit}>
|
|
1354
|
+
<div style={{ marginBottom: "0.5rem", fontSize: "0.75rem", color: "var(--muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={dialog.targetPath || ""}>
|
|
1355
|
+
Target: {dialog.targetPath}
|
|
1356
|
+
</div>
|
|
1357
|
+
<input
|
|
1358
|
+
type="text"
|
|
1359
|
+
value={dialog.inputValue}
|
|
1360
|
+
onChange={(e) => setDialog({ ...dialog, inputValue: e.target.value })}
|
|
1361
|
+
style={modalInputStyle}
|
|
1362
|
+
autoFocus
|
|
1363
|
+
placeholder={
|
|
1364
|
+
dialog.type === "rename" ? "Enter new name" : "Enter name"
|
|
1365
|
+
}
|
|
1366
|
+
/>
|
|
1367
|
+
<div style={modalActionsStyle}>
|
|
1368
|
+
<button
|
|
1369
|
+
type="button"
|
|
1370
|
+
onClick={() => setDialog({ type: null, targetPath: null, inputValue: "", error: null })}
|
|
1371
|
+
style={modalCancelBtnStyle}
|
|
1372
|
+
>
|
|
1373
|
+
Cancel
|
|
1374
|
+
</button>
|
|
1375
|
+
<button type="submit" style={modalConfirmBtnStyle}>
|
|
1376
|
+
Submit
|
|
1377
|
+
</button>
|
|
1378
|
+
</div>
|
|
1379
|
+
</form>
|
|
1380
|
+
) : (
|
|
1381
|
+
<div>
|
|
1382
|
+
<div style={{ fontSize: "0.85rem", marginBottom: "1.5rem", color: "var(--muted)", overflowWrap: "break-word" }}>
|
|
1383
|
+
Are you sure you want to delete this item? This action is permanent.
|
|
1384
|
+
<div style={{ marginTop: "0.5rem", color: "#e2e8f0", fontFamily: "var(--font-mono)", fontSize: "0.75rem", background: "rgba(255,255,255,0.05)", padding: "0.5rem", borderRadius: "4px" }}>
|
|
1385
|
+
{dialog.targetPath}
|
|
1386
|
+
</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
<div style={modalActionsStyle}>
|
|
1389
|
+
<button
|
|
1390
|
+
type="button"
|
|
1391
|
+
onClick={() => setDialog({ type: null, targetPath: null, inputValue: "", error: null })}
|
|
1392
|
+
style={modalCancelBtnStyle}
|
|
1393
|
+
>
|
|
1394
|
+
Cancel
|
|
1395
|
+
</button>
|
|
1396
|
+
<button
|
|
1397
|
+
onClick={() => handleDialogSubmit()}
|
|
1398
|
+
style={{ ...modalConfirmBtnStyle, backgroundColor: "var(--danger)" }}
|
|
1399
|
+
>
|
|
1400
|
+
Delete Permanently
|
|
1401
|
+
</button>
|
|
1402
|
+
</div>
|
|
1403
|
+
</div>
|
|
1404
|
+
)}
|
|
1405
|
+
</div>
|
|
1406
|
+
</div>
|
|
1407
|
+
)}
|
|
1408
|
+
|
|
1409
|
+
{/* Fuzzy File Finder Modal */}
|
|
1410
|
+
{searchOpen && (
|
|
1411
|
+
<div style={searchOverlayStyle} onClick={() => setSearchOpen(false)}>
|
|
1412
|
+
<div style={searchContentStyle} onClick={(e) => e.stopPropagation()}>
|
|
1413
|
+
<input
|
|
1414
|
+
type="text"
|
|
1415
|
+
value={searchQuery}
|
|
1416
|
+
onChange={(e) => {
|
|
1417
|
+
setSearchQuery(e.target.value);
|
|
1418
|
+
setSearchSelectedIndex(0);
|
|
1419
|
+
}}
|
|
1420
|
+
onKeyDown={handleSearchKeyDown}
|
|
1421
|
+
placeholder="Search files by name or path..."
|
|
1422
|
+
style={searchInputStyle}
|
|
1423
|
+
autoFocus
|
|
1424
|
+
/>
|
|
1425
|
+
<div style={searchResultsListStyle}>
|
|
1426
|
+
{filteredFiles.length > 0 ? (
|
|
1427
|
+
filteredFiles.map((file, idx) => (
|
|
1428
|
+
<div
|
|
1429
|
+
key={file.path}
|
|
1430
|
+
onClick={() => {
|
|
1431
|
+
handleOpenFile(file.path);
|
|
1432
|
+
setSearchOpen(false);
|
|
1433
|
+
}}
|
|
1434
|
+
style={{
|
|
1435
|
+
...searchResultItemStyle,
|
|
1436
|
+
backgroundColor: idx === searchSelectedIndex ? "rgba(168, 85, 247, 0.15)" : "transparent",
|
|
1437
|
+
borderLeft: idx === searchSelectedIndex ? "3px solid var(--primary)" : "3px solid transparent",
|
|
1438
|
+
}}
|
|
1439
|
+
>
|
|
1440
|
+
<span style={{ fontSize: "0.85rem", fontWeight: 600 }}>{file.name}</span>
|
|
1441
|
+
<span style={{ fontSize: "0.7rem", color: "var(--muted)", marginLeft: "0.5rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
|
1442
|
+
{file.relativePath}
|
|
1443
|
+
</span>
|
|
1444
|
+
</div>
|
|
1445
|
+
))
|
|
1446
|
+
) : (
|
|
1447
|
+
<div style={{ padding: "0.75rem 1rem", color: "var(--muted)", fontSize: "0.8rem", textAlign: "center" }}>
|
|
1448
|
+
No matching files found
|
|
1449
|
+
</div>
|
|
1450
|
+
)}
|
|
1451
|
+
</div>
|
|
1452
|
+
<div style={{ padding: "0.5rem 1rem", borderTop: "1px solid var(--border)", fontSize: "0.65rem", color: "var(--muted)", textAlign: "right" }}>
|
|
1453
|
+
Use ↑↓ to navigate, Enter to select, Esc to close
|
|
1454
|
+
</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
</div>
|
|
1457
|
+
)}
|
|
1458
|
+
</div>
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// Styling definitions to create premium dark-mode HSL style
|
|
1463
|
+
const ideContainerStyle: React.CSSProperties = {
|
|
1464
|
+
display: "flex",
|
|
1465
|
+
width: "100%",
|
|
1466
|
+
height: "calc(100vh - 120px)",
|
|
1467
|
+
backgroundColor: "#05070a",
|
|
1468
|
+
border: "1px solid var(--border)",
|
|
1469
|
+
borderRadius: "12px",
|
|
1470
|
+
overflow: "hidden",
|
|
1471
|
+
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.6)",
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
const activityBarStyle: React.CSSProperties = {
|
|
1475
|
+
width: "50px",
|
|
1476
|
+
backgroundColor: "#030406",
|
|
1477
|
+
borderRight: "1px solid var(--border)",
|
|
1478
|
+
display: "flex",
|
|
1479
|
+
flexDirection: "column",
|
|
1480
|
+
justifyContent: "space-between",
|
|
1481
|
+
alignItems: "center",
|
|
1482
|
+
padding: "0.75rem 0",
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
const topIconsStyle: React.CSSProperties = {
|
|
1486
|
+
display: "flex",
|
|
1487
|
+
flexDirection: "column",
|
|
1488
|
+
width: "100%",
|
|
1489
|
+
gap: "0.5rem",
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
const sidebarIconStyle: React.CSSProperties = {
|
|
1493
|
+
width: "100%",
|
|
1494
|
+
height: "45px",
|
|
1495
|
+
display: "flex",
|
|
1496
|
+
alignItems: "center",
|
|
1497
|
+
justifyContent: "center",
|
|
1498
|
+
cursor: "pointer",
|
|
1499
|
+
fontSize: "1.25rem",
|
|
1500
|
+
transition: "all 0.15s ease",
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
const sidebarPanelStyle: React.CSSProperties = {
|
|
1504
|
+
width: "260px",
|
|
1505
|
+
backgroundColor: "#080c14",
|
|
1506
|
+
borderRight: "1px solid var(--border)",
|
|
1507
|
+
display: "flex",
|
|
1508
|
+
flexDirection: "column",
|
|
1509
|
+
overflow: "hidden",
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
const sidebarHeaderStyle: React.CSSProperties = {
|
|
1513
|
+
padding: "0.75rem 1rem",
|
|
1514
|
+
borderBottom: "1px solid var(--border)",
|
|
1515
|
+
display: "flex",
|
|
1516
|
+
alignItems: "center",
|
|
1517
|
+
justifyContent: "space-between",
|
|
1518
|
+
background: "rgba(255,255,255,0.02)",
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
const sidebarContentStyle: React.CSSProperties = {
|
|
1523
|
+
flex: 1,
|
|
1524
|
+
overflowY: "auto",
|
|
1525
|
+
padding: "0.75rem 0",
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
const treeSectionHeaderStyle: React.CSSProperties = {
|
|
1529
|
+
padding: "0.25rem 1rem",
|
|
1530
|
+
fontSize: "0.75rem",
|
|
1531
|
+
fontWeight: 700,
|
|
1532
|
+
color: "var(--muted)",
|
|
1533
|
+
display: "flex",
|
|
1534
|
+
alignItems: "center",
|
|
1535
|
+
justifyContent: "space-between",
|
|
1536
|
+
textTransform: "uppercase",
|
|
1537
|
+
letterSpacing: "0.05em",
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
const pathBadgeStyle: React.CSSProperties = {
|
|
1541
|
+
fontSize: "0.6rem",
|
|
1542
|
+
backgroundColor: "rgba(59, 130, 246, 0.1)",
|
|
1543
|
+
color: "#3b82f6",
|
|
1544
|
+
padding: "0.1rem 0.3rem",
|
|
1545
|
+
borderRadius: "3px",
|
|
1546
|
+
textTransform: "none",
|
|
1547
|
+
letterSpacing: "normal",
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
const itemHeaderStyle: React.CSSProperties = {
|
|
1551
|
+
fontSize: "0.75rem",
|
|
1552
|
+
fontWeight: 600,
|
|
1553
|
+
color: "var(--foreground)",
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
const mainContentAreaStyle: React.CSSProperties = {
|
|
1557
|
+
flex: 1,
|
|
1558
|
+
display: "flex",
|
|
1559
|
+
flexDirection: "column",
|
|
1560
|
+
overflow: "hidden",
|
|
1561
|
+
backgroundColor: "#0b101b",
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
const editorPanelStyle: React.CSSProperties = {
|
|
1565
|
+
flex: 1,
|
|
1566
|
+
display: "flex",
|
|
1567
|
+
flexDirection: "column",
|
|
1568
|
+
borderBottom: "1px solid var(--border)",
|
|
1569
|
+
overflow: "hidden",
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
const tabHeaderStyle: React.CSSProperties = {
|
|
1573
|
+
height: "35px",
|
|
1574
|
+
backgroundColor: "#05070a",
|
|
1575
|
+
borderBottom: "1px solid var(--border)",
|
|
1576
|
+
display: "flex",
|
|
1577
|
+
alignItems: "center",
|
|
1578
|
+
justifyContent: "space-between",
|
|
1579
|
+
paddingRight: "0.75rem",
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
const tabsWrapperStyle: React.CSSProperties = {
|
|
1583
|
+
display: "flex",
|
|
1584
|
+
height: "100%",
|
|
1585
|
+
overflowX: "auto",
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
const tabItemStyle: React.CSSProperties = {
|
|
1589
|
+
padding: "0 1rem",
|
|
1590
|
+
height: "100%",
|
|
1591
|
+
display: "flex",
|
|
1592
|
+
alignItems: "center",
|
|
1593
|
+
gap: "0.75rem",
|
|
1594
|
+
fontSize: "0.75rem",
|
|
1595
|
+
borderRight: "1px solid var(--border)",
|
|
1596
|
+
borderBottom: "2px solid transparent",
|
|
1597
|
+
cursor: "pointer",
|
|
1598
|
+
transition: "all 0.15s ease",
|
|
1599
|
+
maxWidth: "150px",
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
const closeTabStyle: React.CSSProperties = {
|
|
1603
|
+
fontSize: "0.65rem",
|
|
1604
|
+
color: "var(--muted)",
|
|
1605
|
+
cursor: "pointer",
|
|
1606
|
+
padding: "2px",
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
const modifiedDotStyle: React.CSSProperties = {
|
|
1610
|
+
color: "var(--primary)",
|
|
1611
|
+
fontSize: "0.65rem",
|
|
1612
|
+
marginLeft: "2px",
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
const saveButtonStyle: React.CSSProperties = {
|
|
1616
|
+
padding: "0.25rem 0.5rem",
|
|
1617
|
+
borderRadius: "4px",
|
|
1618
|
+
border: "1px solid var(--border-focus)",
|
|
1619
|
+
backgroundColor: "rgba(16, 185, 129, 0.15)",
|
|
1620
|
+
color: "var(--success)",
|
|
1621
|
+
fontSize: "0.7rem",
|
|
1622
|
+
fontWeight: 600,
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
const editorWorkspaceStyle: React.CSSProperties = {
|
|
1626
|
+
flex: 1,
|
|
1627
|
+
position: "relative",
|
|
1628
|
+
overflow: "hidden",
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
const textareaStyle: React.CSSProperties = {
|
|
1632
|
+
width: "100%",
|
|
1633
|
+
height: "100%",
|
|
1634
|
+
backgroundColor: "#0b101b",
|
|
1635
|
+
border: "none",
|
|
1636
|
+
outline: "none",
|
|
1637
|
+
color: "#e2e8f0",
|
|
1638
|
+
fontFamily: "var(--font-mono), monospace",
|
|
1639
|
+
fontSize: "0.85rem",
|
|
1640
|
+
padding: "1rem",
|
|
1641
|
+
resize: "none",
|
|
1642
|
+
lineHeight: 1.5,
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
const emptyEditorStateStyle: React.CSSProperties = {
|
|
1646
|
+
display: "flex",
|
|
1647
|
+
flexDirection: "column",
|
|
1648
|
+
alignItems: "center",
|
|
1649
|
+
justifyContent: "center",
|
|
1650
|
+
height: "100%",
|
|
1651
|
+
color: "var(--muted)",
|
|
1652
|
+
textAlign: "center",
|
|
1653
|
+
padding: "2rem",
|
|
1654
|
+
};
|
|
1655
|
+
|
|
1656
|
+
const terminalPanelStyle: React.CSSProperties = {
|
|
1657
|
+
height: "360px",
|
|
1658
|
+
backgroundColor: "#05070a",
|
|
1659
|
+
display: "flex",
|
|
1660
|
+
flexDirection: "column",
|
|
1661
|
+
overflow: "hidden",
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
const terminalHeaderStyle: React.CSSProperties = {
|
|
1665
|
+
padding: "0.5rem 1rem",
|
|
1666
|
+
borderBottom: "1px solid var(--border)",
|
|
1667
|
+
display: "flex",
|
|
1668
|
+
alignItems: "center",
|
|
1669
|
+
justifyContent: "space-between",
|
|
1670
|
+
backgroundColor: "rgba(255,255,255,0.01)",
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
const contextButtonStyle: React.CSSProperties = {
|
|
1674
|
+
padding: "0.3rem 0.6rem",
|
|
1675
|
+
borderRadius: "4px",
|
|
1676
|
+
border: "1px solid var(--primary)",
|
|
1677
|
+
backgroundColor: "transparent",
|
|
1678
|
+
color: "var(--primary)",
|
|
1679
|
+
fontSize: "0.7rem",
|
|
1680
|
+
fontWeight: 600,
|
|
1681
|
+
cursor: "pointer",
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
const promptButtonStyle: React.CSSProperties = {
|
|
1685
|
+
padding: "0.3rem 0.6rem",
|
|
1686
|
+
borderRadius: "4px",
|
|
1687
|
+
border: "1px solid var(--success)",
|
|
1688
|
+
backgroundColor: "transparent",
|
|
1689
|
+
color: "var(--success)",
|
|
1690
|
+
fontSize: "0.7rem",
|
|
1691
|
+
fontWeight: 600,
|
|
1692
|
+
cursor: "pointer",
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
const terminalConsoleWrapperStyle: React.CSSProperties = {
|
|
1696
|
+
flex: 1,
|
|
1697
|
+
overflow: "hidden",
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
interface FileItemNodeProps {
|
|
1701
|
+
node: FileNode;
|
|
1702
|
+
onSelect: (p: string) => void;
|
|
1703
|
+
onCreate: (parentPath: string, type: "file" | "directory") => void;
|
|
1704
|
+
onRename: (path: string) => void;
|
|
1705
|
+
onDelete: (path: string) => void;
|
|
1706
|
+
isReadOnly?: boolean;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Define outside IdeLayout to prevent component re-creation and auto-collapsing
|
|
1710
|
+
function FileItemNode({
|
|
1711
|
+
node,
|
|
1712
|
+
onSelect,
|
|
1713
|
+
onCreate,
|
|
1714
|
+
onRename,
|
|
1715
|
+
onDelete,
|
|
1716
|
+
isReadOnly = false
|
|
1717
|
+
}: FileItemNodeProps) {
|
|
1718
|
+
const [expanded, setExpanded] = useState(false);
|
|
1719
|
+
const [hovered, setHovered] = useState(false);
|
|
1720
|
+
|
|
1721
|
+
const isFolder = node.type === "directory";
|
|
1722
|
+
|
|
1723
|
+
const itemStyle = {
|
|
1724
|
+
padding: "0.25rem 0.5rem",
|
|
1725
|
+
display: "flex",
|
|
1726
|
+
alignItems: "center",
|
|
1727
|
+
justifyContent: "space-between",
|
|
1728
|
+
fontSize: "0.8rem",
|
|
1729
|
+
fontFamily: "var(--font-mono), monospace",
|
|
1730
|
+
cursor: "pointer",
|
|
1731
|
+
color: "var(--foreground)",
|
|
1732
|
+
borderRadius: "4px",
|
|
1733
|
+
transition: "background 0.1s ease",
|
|
1734
|
+
userSelect: "none" as const,
|
|
1735
|
+
backgroundColor: hovered ? "rgba(255, 255, 255, 0.05)" : "transparent",
|
|
1736
|
+
minWidth: 0,
|
|
1737
|
+
width: "100%",
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
const textContainerStyle = {
|
|
1741
|
+
display: "flex",
|
|
1742
|
+
alignItems: "center",
|
|
1743
|
+
gap: "0.375rem",
|
|
1744
|
+
flex: 1,
|
|
1745
|
+
minWidth: 0,
|
|
1746
|
+
overflow: "hidden",
|
|
1747
|
+
textOverflow: "ellipsis",
|
|
1748
|
+
whiteSpace: "nowrap" as const,
|
|
1749
|
+
};
|
|
1750
|
+
|
|
1751
|
+
const actionsStyle = {
|
|
1752
|
+
display: hovered ? "flex" : "none",
|
|
1753
|
+
alignItems: "center",
|
|
1754
|
+
gap: "0.25rem",
|
|
1755
|
+
paddingLeft: "0.5rem",
|
|
1756
|
+
flexShrink: 0,
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
const actionBtnStyle = {
|
|
1760
|
+
background: "none",
|
|
1761
|
+
border: "none",
|
|
1762
|
+
padding: "2px 4px",
|
|
1763
|
+
cursor: "pointer",
|
|
1764
|
+
fontSize: "0.75rem",
|
|
1765
|
+
color: "var(--muted)",
|
|
1766
|
+
borderRadius: "3px",
|
|
1767
|
+
transition: "all 0.1s ease",
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
const handleAction = (e: React.MouseEvent, act: () => void) => {
|
|
1771
|
+
e.stopPropagation();
|
|
1772
|
+
act();
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1775
|
+
if (!isFolder) {
|
|
1776
|
+
return (
|
|
1777
|
+
<div
|
|
1778
|
+
onClick={() => onSelect(node.path)}
|
|
1779
|
+
style={itemStyle}
|
|
1780
|
+
onMouseEnter={() => setHovered(true)}
|
|
1781
|
+
onMouseLeave={() => setHovered(false)}
|
|
1782
|
+
>
|
|
1783
|
+
<div style={textContainerStyle}>
|
|
1784
|
+
<span style={{ color: "#3b82f6" }}>📄</span>
|
|
1785
|
+
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{node.name}</span>
|
|
1786
|
+
</div>
|
|
1787
|
+
{!isReadOnly && (
|
|
1788
|
+
<div style={actionsStyle}>
|
|
1789
|
+
<button
|
|
1790
|
+
onClick={(e) => handleAction(e, () => onRename(node.path))}
|
|
1791
|
+
style={actionBtnStyle}
|
|
1792
|
+
title="Rename..."
|
|
1793
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--primary)"}
|
|
1794
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
1795
|
+
>
|
|
1796
|
+
✏️
|
|
1797
|
+
</button>
|
|
1798
|
+
<button
|
|
1799
|
+
onClick={(e) => handleAction(e, () => onDelete(node.path))}
|
|
1800
|
+
style={actionBtnStyle}
|
|
1801
|
+
title="Delete File"
|
|
1802
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--danger)"}
|
|
1803
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
1804
|
+
>
|
|
1805
|
+
🗑️
|
|
1806
|
+
</button>
|
|
1807
|
+
</div>
|
|
1808
|
+
)}
|
|
1809
|
+
</div>
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
return (
|
|
1814
|
+
<div>
|
|
1815
|
+
<div
|
|
1816
|
+
onClick={() => setExpanded(!expanded)}
|
|
1817
|
+
style={itemStyle}
|
|
1818
|
+
onMouseEnter={() => setHovered(true)}
|
|
1819
|
+
onMouseLeave={() => setHovered(false)}
|
|
1820
|
+
>
|
|
1821
|
+
<div style={textContainerStyle}>
|
|
1822
|
+
<span style={{ color: "#eab308" }}>{expanded ? "📂" : "📁"}</span>
|
|
1823
|
+
<span style={{ fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{node.name}</span>
|
|
1824
|
+
</div>
|
|
1825
|
+
{!isReadOnly && (
|
|
1826
|
+
<div style={actionsStyle}>
|
|
1827
|
+
<button
|
|
1828
|
+
onClick={(e) => handleAction(e, () => onCreate(node.path, "file"))}
|
|
1829
|
+
style={actionBtnStyle}
|
|
1830
|
+
title="New File inside..."
|
|
1831
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--success)"}
|
|
1832
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
1833
|
+
>
|
|
1834
|
+
📄+
|
|
1835
|
+
</button>
|
|
1836
|
+
<button
|
|
1837
|
+
onClick={(e) => handleAction(e, () => onCreate(node.path, "directory"))}
|
|
1838
|
+
style={actionBtnStyle}
|
|
1839
|
+
title="New Folder inside..."
|
|
1840
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--success)"}
|
|
1841
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
1842
|
+
>
|
|
1843
|
+
📁+
|
|
1844
|
+
</button>
|
|
1845
|
+
<button
|
|
1846
|
+
onClick={(e) => handleAction(e, () => onRename(node.path))}
|
|
1847
|
+
style={actionBtnStyle}
|
|
1848
|
+
title="Rename..."
|
|
1849
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--primary)"}
|
|
1850
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
1851
|
+
>
|
|
1852
|
+
✏️
|
|
1853
|
+
</button>
|
|
1854
|
+
<button
|
|
1855
|
+
onClick={(e) => handleAction(e, () => onDelete(node.path))}
|
|
1856
|
+
style={actionBtnStyle}
|
|
1857
|
+
title="Delete Folder"
|
|
1858
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--danger)"}
|
|
1859
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
1860
|
+
>
|
|
1861
|
+
🗑️
|
|
1862
|
+
</button>
|
|
1863
|
+
</div>
|
|
1864
|
+
)}
|
|
1865
|
+
</div>
|
|
1866
|
+
{expanded && node.children && (
|
|
1867
|
+
<div style={{ paddingLeft: "10px", borderLeft: "1px solid rgba(255, 255, 255, 0.05)", marginLeft: "8px" }}>
|
|
1868
|
+
{node.children.map((child) => (
|
|
1869
|
+
<FileItemNode
|
|
1870
|
+
key={child.path}
|
|
1871
|
+
node={child}
|
|
1872
|
+
onSelect={onSelect}
|
|
1873
|
+
onCreate={onCreate}
|
|
1874
|
+
onRename={onRename}
|
|
1875
|
+
onDelete={onDelete}
|
|
1876
|
+
isReadOnly={isReadOnly}
|
|
1877
|
+
/>
|
|
1878
|
+
))}
|
|
1879
|
+
</div>
|
|
1880
|
+
)}
|
|
1881
|
+
</div>
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// Dialog operation styles
|
|
1886
|
+
const modalOverlayStyle: React.CSSProperties = {
|
|
1887
|
+
position: "fixed",
|
|
1888
|
+
top: 0,
|
|
1889
|
+
left: 0,
|
|
1890
|
+
right: 0,
|
|
1891
|
+
bottom: 0,
|
|
1892
|
+
backgroundColor: "rgba(0,0,0,0.7)",
|
|
1893
|
+
display: "flex",
|
|
1894
|
+
alignItems: "center",
|
|
1895
|
+
justifyContent: "center",
|
|
1896
|
+
zIndex: 1000,
|
|
1897
|
+
backdropFilter: "blur(4px)",
|
|
1898
|
+
};
|
|
1899
|
+
|
|
1900
|
+
const modalContentStyle: React.CSSProperties = {
|
|
1901
|
+
width: "420px",
|
|
1902
|
+
backgroundColor: "#080c14",
|
|
1903
|
+
border: "1px solid var(--border)",
|
|
1904
|
+
borderRadius: "12px",
|
|
1905
|
+
padding: "1.5rem",
|
|
1906
|
+
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.8)",
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
const modalInputStyle: React.CSSProperties = {
|
|
1910
|
+
width: "100%",
|
|
1911
|
+
backgroundColor: "#05070a",
|
|
1912
|
+
border: "1px solid var(--border)",
|
|
1913
|
+
borderRadius: "6px",
|
|
1914
|
+
padding: "0.5rem 0.75rem",
|
|
1915
|
+
color: "#e2e8f0",
|
|
1916
|
+
fontSize: "0.85rem",
|
|
1917
|
+
outline: "none",
|
|
1918
|
+
marginBottom: "1.5rem",
|
|
1919
|
+
fontFamily: "var(--font-mono), monospace",
|
|
1920
|
+
};
|
|
1921
|
+
|
|
1922
|
+
const modalActionsStyle: React.CSSProperties = {
|
|
1923
|
+
display: "flex",
|
|
1924
|
+
justifyContent: "flex-end",
|
|
1925
|
+
gap: "0.75rem",
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
const modalCancelBtnStyle: React.CSSProperties = {
|
|
1929
|
+
backgroundColor: "transparent",
|
|
1930
|
+
border: "1px solid var(--border)",
|
|
1931
|
+
color: "var(--foreground)",
|
|
1932
|
+
padding: "0.5rem 1rem",
|
|
1933
|
+
borderRadius: "6px",
|
|
1934
|
+
fontSize: "0.8rem",
|
|
1935
|
+
fontWeight: 600,
|
|
1936
|
+
cursor: "pointer",
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
const modalConfirmBtnStyle: React.CSSProperties = {
|
|
1940
|
+
backgroundColor: "var(--primary)",
|
|
1941
|
+
border: "none",
|
|
1942
|
+
color: "#ffffff",
|
|
1943
|
+
padding: "0.5rem 1rem",
|
|
1944
|
+
borderRadius: "6px",
|
|
1945
|
+
fontSize: "0.8rem",
|
|
1946
|
+
fontWeight: 600,
|
|
1947
|
+
cursor: "pointer",
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
const iconButtonStyle: React.CSSProperties = {
|
|
1951
|
+
background: "none",
|
|
1952
|
+
border: "none",
|
|
1953
|
+
color: "var(--muted)",
|
|
1954
|
+
cursor: "pointer",
|
|
1955
|
+
fontSize: "0.8rem",
|
|
1956
|
+
padding: "4px",
|
|
1957
|
+
borderRadius: "3px",
|
|
1958
|
+
transition: "all 0.1s ease",
|
|
1959
|
+
};
|
|
1960
|
+
|
|
1961
|
+
// Fuzzy search styles
|
|
1962
|
+
const searchOverlayStyle: React.CSSProperties = {
|
|
1963
|
+
position: "fixed",
|
|
1964
|
+
top: 0,
|
|
1965
|
+
left: 0,
|
|
1966
|
+
right: 0,
|
|
1967
|
+
bottom: 0,
|
|
1968
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
1969
|
+
display: "flex",
|
|
1970
|
+
alignItems: "flex-start",
|
|
1971
|
+
justifyContent: "center",
|
|
1972
|
+
zIndex: 1100,
|
|
1973
|
+
paddingTop: "5vh",
|
|
1974
|
+
backdropFilter: "blur(2px)",
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
const searchContentStyle: React.CSSProperties = {
|
|
1978
|
+
width: "600px",
|
|
1979
|
+
backgroundColor: "#080c14",
|
|
1980
|
+
border: "1px solid var(--border)",
|
|
1981
|
+
borderRadius: "8px",
|
|
1982
|
+
boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.8)",
|
|
1983
|
+
display: "flex",
|
|
1984
|
+
flexDirection: "column",
|
|
1985
|
+
overflow: "hidden",
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1988
|
+
const searchInputStyle: React.CSSProperties = {
|
|
1989
|
+
width: "100%",
|
|
1990
|
+
backgroundColor: "#05070a",
|
|
1991
|
+
border: "none",
|
|
1992
|
+
borderBottom: "1px solid var(--border)",
|
|
1993
|
+
padding: "0.75rem 1rem",
|
|
1994
|
+
color: "#e2e8f0",
|
|
1995
|
+
fontSize: "0.9rem",
|
|
1996
|
+
outline: "none",
|
|
1997
|
+
fontFamily: "var(--font-mono), monospace",
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
const searchResultsListStyle: React.CSSProperties = {
|
|
2001
|
+
maxHeight: "300px",
|
|
2002
|
+
overflowY: "auto",
|
|
2003
|
+
padding: "0.25rem 0",
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
const searchResultItemStyle: React.CSSProperties = {
|
|
2007
|
+
display: "flex",
|
|
2008
|
+
alignItems: "center",
|
|
2009
|
+
padding: "0.5rem 1rem",
|
|
2010
|
+
cursor: "pointer",
|
|
2011
|
+
transition: "all 0.1s ease",
|
|
2012
|
+
justifyContent: "space-between",
|
|
2013
|
+
};
|
|
2014
|
+
|
|
2015
|
+
// Analytics Card & Resource Monitor styles
|
|
2016
|
+
const analyticsCardStyle: React.CSSProperties = {
|
|
2017
|
+
backgroundColor: "rgba(255,255,255,0.02)",
|
|
2018
|
+
border: "1px solid var(--border)",
|
|
2019
|
+
borderRadius: "6px",
|
|
2020
|
+
padding: "0.5rem 0.75rem",
|
|
2021
|
+
display: "flex",
|
|
2022
|
+
flexDirection: "column",
|
|
2023
|
+
gap: "0.25rem",
|
|
2024
|
+
};
|
|
2025
|
+
|
|
2026
|
+
const analyticsCardLabelStyle: React.CSSProperties = {
|
|
2027
|
+
fontSize: "0.6rem",
|
|
2028
|
+
color: "var(--muted)",
|
|
2029
|
+
textTransform: "uppercase",
|
|
2030
|
+
};
|
|
2031
|
+
|
|
2032
|
+
const analyticsCardValueStyle: React.CSSProperties = {
|
|
2033
|
+
fontSize: "1.1rem",
|
|
2034
|
+
fontWeight: 700,
|
|
2035
|
+
fontFamily: "var(--font-mono)",
|
|
2036
|
+
};
|
|
2037
|
+
|
|
2038
|
+
const gaugeOuterStyle: React.CSSProperties = {
|
|
2039
|
+
width: "100%",
|
|
2040
|
+
height: "6px",
|
|
2041
|
+
backgroundColor: "rgba(255,255,255,0.05)",
|
|
2042
|
+
borderRadius: "3px",
|
|
2043
|
+
overflow: "hidden",
|
|
2044
|
+
};
|
|
2045
|
+
|
|
2046
|
+
const gaugeInnerStyle: React.CSSProperties = {
|
|
2047
|
+
height: "100%",
|
|
2048
|
+
borderRadius: "3px",
|
|
2049
|
+
transition: "width 0.5s ease-out",
|
|
2050
|
+
};
|
|
2051
|
+
|
|
2052
|
+
const quickCommandToolbarStyle: React.CSSProperties = {
|
|
2053
|
+
padding: "0.35rem 1rem",
|
|
2054
|
+
borderBottom: "1px solid var(--border)",
|
|
2055
|
+
backgroundColor: "#030406",
|
|
2056
|
+
display: "flex",
|
|
2057
|
+
alignItems: "center",
|
|
2058
|
+
gap: "0.75rem",
|
|
2059
|
+
flexWrap: "nowrap",
|
|
2060
|
+
overflowX: "auto"
|
|
2061
|
+
};
|
|
2062
|
+
|
|
2063
|
+
const quickCmdBtnStyle: React.CSSProperties = {
|
|
2064
|
+
padding: "0.15rem 0.45rem",
|
|
2065
|
+
borderRadius: "4px",
|
|
2066
|
+
border: "1px solid var(--border)",
|
|
2067
|
+
backgroundColor: "rgba(255,255,255,0.02)",
|
|
2068
|
+
color: "var(--muted)",
|
|
2069
|
+
fontSize: "0.7rem",
|
|
2070
|
+
fontFamily: "var(--font-mono)",
|
|
2071
|
+
fontWeight: 600,
|
|
2072
|
+
cursor: "pointer",
|
|
2073
|
+
transition: "all 0.1s ease",
|
|
2074
|
+
};
|
|
2075
|
+
|
|
2076
|
+
const sessionTabBarStyle: React.CSSProperties = {
|
|
2077
|
+
backgroundColor: "#05070a",
|
|
2078
|
+
borderBottom: "1px solid var(--border)",
|
|
2079
|
+
padding: "0.25rem 0.75rem 0 0.75rem",
|
|
2080
|
+
display: "flex",
|
|
2081
|
+
alignItems: "center",
|
|
2082
|
+
justifyContent: "space-between",
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
const sessionTabItemStyle: React.CSSProperties = {
|
|
2086
|
+
padding: "0.35rem 0.75rem",
|
|
2087
|
+
borderRadius: "6px 6px 0 0",
|
|
2088
|
+
border: "1px solid var(--border)",
|
|
2089
|
+
borderBottom: "none",
|
|
2090
|
+
display: "flex",
|
|
2091
|
+
alignItems: "center",
|
|
2092
|
+
gap: "0.5rem",
|
|
2093
|
+
cursor: "pointer",
|
|
2094
|
+
transition: "all 0.15s ease",
|
|
2095
|
+
};
|