@tyroneross/navgator 0.2.0 → 0.2.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/dist/cli/index.js +1 -1
- package/package.json +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -0
- package/web/.next/standalone/web/.next/app-path-routes-manifest.json +13 -0
- package/web/.next/standalone/web/.next/build-manifest.json +20 -0
- package/web/.next/standalone/web/.next/package.json +1 -0
- package/web/.next/standalone/web/.next/prerender-manifest.json +88 -0
- package/web/.next/standalone/web/.next/required-server-files.json +317 -0
- package/web/.next/standalone/web/.next/routes-manifest.json +110 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +17 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js +10 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.meta +15 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +12 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +12 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page/build-manifest.json +17 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js +13 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +17 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +17 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +8 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +8 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/components/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/connections/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/graph/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/projects/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route.js +7 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/prompts/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/scan/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/settings/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route/build-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route.js +6 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/api/status/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/index.html +1 -0
- package/web/.next/standalone/web/.next/server/app/index.meta +14 -0
- package/web/.next/standalone/web/.next/server/app/index.rsc +23 -0
- package/web/.next/standalone/web/.next/server/app/index.segments/__PAGE__.segment.rsc +9 -0
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +23 -0
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +8 -0
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +8 -0
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/page/build-manifest.json +17 -0
- package/web/.next/standalone/web/.next/server/app/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/page.js +15 -0
- package/web/.next/standalone/web/.next/server/app/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app-paths-manifest.json +13 -0
- package/web/.next/standalone/web/.next/server/chunks/2374f_next_dist_esm_build_templates_app-route_0bb4e66a.js +19 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__006b837d._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__1ab91221._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__2e09fec9._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__38d0390f._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__539ef30d._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__594bcf20._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__b888fadf._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__f3450c22._.js +21 -0
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__fa2ec862._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/[turbopack]_runtime.js +770 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_e6039567._.js +6 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_09351209._.js +6 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_222be7ae._.js +6 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_2346e1b3._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_cec86455._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_client_components_9c5d1a14._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_client_components_builtin_forbidden_8eae0c85.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_client_components_builtin_global-error_81159d60.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_client_components_builtin_unauthorized_7d34a31c.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_e03afa0e._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_esm_build_templates_app-page_2c8d71b9.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_next_dist_server_route-modules_app-page_vendored_ssr_react-dom_8910f04c.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__1a0663e6._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__4306eafc._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__44903626._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__60278e3f._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__61942f24._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__8c45c3c9._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9a2f110d._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__ed07bd88._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f2db61af._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[turbopack]_runtime.js +770 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_0103e631._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_171de0df._.js +14 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app__global-error_page_actions_2a1e94d4.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app__not-found_page_actions_9eaa9845.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_page_actions_ec26bf28.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_components_route_actions_c88bf2a6.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_connections_route_actions_b1d5e95a.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_graph_route_actions_e2dd052c.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_projects_route_actions_3f671cbb.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_prompts_route_actions_85b56166.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_scan_route_actions_861cde8d.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_settings_route_actions_e19026ae.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/web__next-internal_server_app_api_status_route_actions_8b8c6c89.js +3 -0
- package/web/.next/standalone/web/.next/server/functions-config-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +21 -0
- package/web/.next/standalone/web/.next/server/middleware-manifest.json +6 -0
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -0
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +15 -0
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -0
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -0
- package/web/.next/standalone/web/.next/server/pages-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -0
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +5 -0
- package/web/.next/standalone/web/.next/static/WH0NvTGUEDs9QVUjcPJKx/_buildManifest.js +11 -0
- package/web/.next/standalone/web/.next/static/WH0NvTGUEDs9QVUjcPJKx/_clientMiddlewareManifest.json +1 -0
- package/web/.next/standalone/web/.next/static/WH0NvTGUEDs9QVUjcPJKx/_ssgManifest.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/062ae79751df2759.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/159889e17b2cf1f8.js +2 -0
- package/web/.next/standalone/web/.next/static/chunks/458d6f37339fc069.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/6d3d39425a878d7f.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/8a80e7184ad3a13f.css +2 -0
- package/web/.next/standalone/web/.next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/c056475f5f4424b6.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/c57fee8cce8d7cb9.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/cb3513192b63e480.js +12 -0
- package/web/.next/standalone/web/.next/static/chunks/dd22b5f2beb2cc31.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/e0affeef0ddb9a97.js +4 -0
- package/web/.next/standalone/web/.next/static/chunks/f74a6859e1c4d5e3.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/turbopack-c0c89f9e6f0a38c4.js +3 -0
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/web/.next/standalone/web/app/api/components/route.ts +252 -0
- package/web/.next/standalone/web/app/api/connections/route.ts +319 -0
- package/web/.next/standalone/web/app/api/graph/route.ts +235 -0
- package/web/.next/standalone/web/app/api/projects/route.ts +221 -0
- package/web/.next/standalone/web/app/api/prompts/route.ts +328 -0
- package/web/.next/standalone/web/app/api/scan/route.ts +108 -0
- package/web/.next/standalone/web/app/api/settings/route.ts +198 -0
- package/web/.next/standalone/web/app/api/status/route.ts +169 -0
- package/web/.next/standalone/web/app/globals.css +99 -0
- package/web/.next/standalone/web/app/layout.tsx +49 -0
- package/web/.next/standalone/web/app/page.tsx +94 -0
- package/web/.next/standalone/web/components/components-panel.tsx +240 -0
- package/web/.next/standalone/web/components/connections-panel.tsx +220 -0
- package/web/.next/standalone/web/components/diagram-view.tsx +735 -0
- package/web/.next/standalone/web/components/header.tsx +225 -0
- package/web/.next/standalone/web/components/impact-analysis.tsx +358 -0
- package/web/.next/standalone/web/components/llm-tracking-panel.tsx +1483 -0
- package/web/.next/standalone/web/components/settings-panel.tsx +671 -0
- package/web/.next/standalone/web/components/sidebar.tsx +74 -0
- package/web/.next/standalone/web/components/status-overview.tsx +326 -0
- package/web/.next/standalone/web/components/theme-provider.tsx +11 -0
- package/web/.next/standalone/web/components/ui/accordion.tsx +66 -0
- package/web/.next/standalone/web/components/ui/alert-dialog.tsx +157 -0
- package/web/.next/standalone/web/components/ui/alert.tsx +66 -0
- package/web/.next/standalone/web/components/ui/aspect-ratio.tsx +11 -0
- package/web/.next/standalone/web/components/ui/avatar.tsx +53 -0
- package/web/.next/standalone/web/components/ui/badge.tsx +46 -0
- package/web/.next/standalone/web/components/ui/breadcrumb.tsx +109 -0
- package/web/.next/standalone/web/components/ui/button-group.tsx +83 -0
- package/web/.next/standalone/web/components/ui/button.tsx +60 -0
- package/web/.next/standalone/web/components/ui/calendar.tsx +213 -0
- package/web/.next/standalone/web/components/ui/card.tsx +92 -0
- package/web/.next/standalone/web/components/ui/carousel.tsx +241 -0
- package/web/.next/standalone/web/components/ui/chart.tsx +353 -0
- package/web/.next/standalone/web/components/ui/checkbox.tsx +32 -0
- package/web/.next/standalone/web/components/ui/collapsible.tsx +33 -0
- package/web/.next/standalone/web/components/ui/command.tsx +184 -0
- package/web/.next/standalone/web/components/ui/context-menu.tsx +252 -0
- package/web/.next/standalone/web/components/ui/dialog.tsx +143 -0
- package/web/.next/standalone/web/components/ui/drawer.tsx +135 -0
- package/web/.next/standalone/web/components/ui/dropdown-menu.tsx +257 -0
- package/web/.next/standalone/web/components/ui/empty.tsx +104 -0
- package/web/.next/standalone/web/components/ui/field.tsx +244 -0
- package/web/.next/standalone/web/components/ui/form.tsx +167 -0
- package/web/.next/standalone/web/components/ui/hover-card.tsx +44 -0
- package/web/.next/standalone/web/components/ui/input-group.tsx +169 -0
- package/web/.next/standalone/web/components/ui/input-otp.tsx +77 -0
- package/web/.next/standalone/web/components/ui/input.tsx +21 -0
- package/web/.next/standalone/web/components/ui/item.tsx +193 -0
- package/web/.next/standalone/web/components/ui/kbd.tsx +28 -0
- package/web/.next/standalone/web/components/ui/label.tsx +24 -0
- package/web/.next/standalone/web/components/ui/menubar.tsx +276 -0
- package/web/.next/standalone/web/components/ui/navigation-menu.tsx +166 -0
- package/web/.next/standalone/web/components/ui/pagination.tsx +127 -0
- package/web/.next/standalone/web/components/ui/popover.tsx +48 -0
- package/web/.next/standalone/web/components/ui/progress.tsx +31 -0
- package/web/.next/standalone/web/components/ui/radio-group.tsx +45 -0
- package/web/.next/standalone/web/components/ui/resizable.tsx +56 -0
- package/web/.next/standalone/web/components/ui/scroll-area.tsx +58 -0
- package/web/.next/standalone/web/components/ui/select.tsx +185 -0
- package/web/.next/standalone/web/components/ui/separator.tsx +28 -0
- package/web/.next/standalone/web/components/ui/sheet.tsx +139 -0
- package/web/.next/standalone/web/components/ui/sidebar.tsx +726 -0
- package/web/.next/standalone/web/components/ui/skeleton.tsx +13 -0
- package/web/.next/standalone/web/components/ui/slider.tsx +63 -0
- package/web/.next/standalone/web/components/ui/sonner.tsx +25 -0
- package/web/.next/standalone/web/components/ui/spinner.tsx +16 -0
- package/web/.next/standalone/web/components/ui/switch.tsx +31 -0
- package/web/.next/standalone/web/components/ui/table.tsx +116 -0
- package/web/.next/standalone/web/components/ui/tabs.tsx +66 -0
- package/web/.next/standalone/web/components/ui/textarea.tsx +18 -0
- package/web/.next/standalone/web/components/ui/toast.tsx +129 -0
- package/web/.next/standalone/web/components/ui/toaster.tsx +35 -0
- package/web/.next/standalone/web/components/ui/toggle-group.tsx +73 -0
- package/web/.next/standalone/web/components/ui/toggle.tsx +47 -0
- package/web/.next/standalone/web/components/ui/tooltip.tsx +61 -0
- package/web/.next/standalone/web/components/ui/use-mobile.tsx +19 -0
- package/web/.next/standalone/web/components/ui/use-toast.ts +191 -0
- package/web/.next/standalone/web/components.json +21 -0
- package/web/.next/standalone/web/hooks/use-mobile.ts +19 -0
- package/web/.next/standalone/web/hooks/use-toast.ts +191 -0
- package/web/.next/standalone/web/lib/hooks/index.ts +8 -0
- package/web/.next/standalone/web/lib/hooks/use-components.ts +83 -0
- package/web/.next/standalone/web/lib/hooks/use-connections.ts +83 -0
- package/web/.next/standalone/web/lib/hooks/use-projects.ts +116 -0
- package/web/.next/standalone/web/lib/hooks/use-prompts.ts +122 -0
- package/web/.next/standalone/web/lib/hooks/use-settings.ts +135 -0
- package/web/.next/standalone/web/lib/hooks/use-status.ts +80 -0
- package/web/.next/standalone/web/lib/project-context.tsx +46 -0
- package/web/.next/standalone/web/lib/transform.ts +625 -0
- package/web/.next/standalone/web/lib/types.ts +198 -0
- package/web/.next/standalone/web/lib/utils.ts +6 -0
- package/web/.next/standalone/web/next.config.mjs +12 -0
- package/web/.next/standalone/web/package-lock.json +3987 -0
- package/web/.next/standalone/web/package.json +73 -0
- package/web/.next/standalone/web/pnpm-lock.yaml +5 -0
- package/web/.next/standalone/web/postcss.config.mjs +8 -0
- package/web/.next/standalone/web/public/apple-icon.png +0 -0
- package/web/.next/standalone/web/public/icon-dark-32x32.png +0 -0
- package/web/.next/standalone/web/public/icon-light-32x32.png +0 -0
- package/web/.next/standalone/web/public/icon.svg +26 -0
- package/web/.next/standalone/web/public/navgator-logo.png +0 -0
- package/web/.next/standalone/web/public/placeholder-logo.png +0 -0
- package/web/.next/standalone/web/public/placeholder-logo.svg +1 -0
- package/web/.next/standalone/web/public/placeholder-user.jpg +0 -0
- package/web/.next/standalone/web/public/placeholder.jpg +0 -0
- package/web/.next/standalone/web/public/placeholder.svg +1 -0
- package/web/.next/standalone/web/public/public/apple-icon.png +0 -0
- package/web/.next/standalone/web/public/public/icon-dark-32x32.png +0 -0
- package/web/.next/standalone/web/public/public/icon-light-32x32.png +0 -0
- package/web/.next/standalone/web/public/public/icon.svg +26 -0
- package/web/.next/standalone/web/public/public/navgator-logo.png +0 -0
- package/web/.next/standalone/web/public/public/placeholder-logo.png +0 -0
- package/web/.next/standalone/web/public/public/placeholder-logo.svg +1 -0
- package/web/.next/standalone/web/public/public/placeholder-user.jpg +0 -0
- package/web/.next/standalone/web/public/public/placeholder.jpg +0 -0
- package/web/.next/standalone/web/public/public/placeholder.svg +1 -0
- package/web/.next/standalone/web/server.js +38 -0
- package/web/.next/standalone/web/styles/globals.css +125 -0
- package/web/.next/standalone/web/tsconfig.json +41 -0
- package/web/.next/static/WH0NvTGUEDs9QVUjcPJKx/_buildManifest.js +11 -0
- package/web/.next/static/WH0NvTGUEDs9QVUjcPJKx/_clientMiddlewareManifest.json +1 -0
- package/web/.next/static/WH0NvTGUEDs9QVUjcPJKx/_ssgManifest.js +1 -0
- package/web/.next/static/chunks/062ae79751df2759.js +1 -0
- package/web/.next/static/chunks/159889e17b2cf1f8.js +2 -0
- package/web/.next/static/chunks/458d6f37339fc069.js +1 -0
- package/web/.next/static/chunks/6d3d39425a878d7f.js +1 -0
- package/web/.next/static/chunks/8a80e7184ad3a13f.css +2 -0
- package/web/.next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/web/.next/static/chunks/c056475f5f4424b6.css +1 -0
- package/web/.next/static/chunks/c57fee8cce8d7cb9.js +1 -0
- package/web/.next/static/chunks/cb3513192b63e480.js +12 -0
- package/web/.next/static/chunks/dd22b5f2beb2cc31.js +1 -0
- package/web/.next/static/chunks/e0affeef0ddb9a97.js +4 -0
- package/web/.next/static/chunks/f74a6859e1c4d5e3.js +1 -0
- package/web/.next/static/chunks/turbopack-c0c89f9e6f0a38c4.js +3 -0
- package/web/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/web/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/web/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/web/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
|
@@ -0,0 +1,1483 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from "react";
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
9
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
10
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
|
11
|
+
import {
|
|
12
|
+
Search,
|
|
13
|
+
Brain,
|
|
14
|
+
MessageSquare,
|
|
15
|
+
FileText,
|
|
16
|
+
Copy,
|
|
17
|
+
Check,
|
|
18
|
+
ChevronDown,
|
|
19
|
+
ChevronRight,
|
|
20
|
+
Zap,
|
|
21
|
+
AlertTriangle,
|
|
22
|
+
Filter,
|
|
23
|
+
RefreshCw,
|
|
24
|
+
Loader2,
|
|
25
|
+
Info,
|
|
26
|
+
GitBranch,
|
|
27
|
+
ArrowDown,
|
|
28
|
+
Circle,
|
|
29
|
+
Layers,
|
|
30
|
+
X,
|
|
31
|
+
} from "lucide-react";
|
|
32
|
+
import { usePrompts } from "@/lib/hooks";
|
|
33
|
+
import type { LLMCall, Prompt } from "@/lib/types";
|
|
34
|
+
|
|
35
|
+
const categoryColors: Record<string, string> = {
|
|
36
|
+
chat: "bg-info/20 text-info border-info/30",
|
|
37
|
+
completion: "bg-primary/20 text-primary border-primary/30",
|
|
38
|
+
embedding: "bg-chart-3/20 text-chart-3 border-chart-3/30",
|
|
39
|
+
function: "bg-chart-4/20 text-chart-4 border-chart-4/30",
|
|
40
|
+
agent: "bg-warning/20 text-warning border-warning/30",
|
|
41
|
+
image: "bg-chart-3/20 text-chart-3 border-chart-3/30",
|
|
42
|
+
audio: "bg-chart-4/20 text-chart-4 border-chart-4/30",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const typeColors: Record<Prompt["type"], string> = {
|
|
46
|
+
system: "bg-info/20 text-info border-info/30",
|
|
47
|
+
user: "bg-primary/20 text-primary border-primary/30",
|
|
48
|
+
assistant: "bg-chart-3/20 text-chart-3 border-chart-3/30",
|
|
49
|
+
function: "bg-chart-4/20 text-chart-4 border-chart-4/30",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function LLMTrackingPanel() {
|
|
53
|
+
// Fetch data from API (defaults to demo mode until real scan data exists)
|
|
54
|
+
const { calls, prompts, summary, isLoading, error, source, refresh, scan } = usePrompts({
|
|
55
|
+
autoFetch: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
59
|
+
const [selectedCall, setSelectedCall] = useState<LLMCall | null>(null);
|
|
60
|
+
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
|
61
|
+
const [expandedCalls, setExpandedCalls] = useState<Set<string>>(new Set());
|
|
62
|
+
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
63
|
+
const [categoryFilter, setCategoryFilter] = useState<string>("all");
|
|
64
|
+
const [providerFilter, setProviderFilter] = useState<string>("all");
|
|
65
|
+
const [activeTab, setActiveTab] = useState<string>("calls");
|
|
66
|
+
const [sheetType, setSheetType] = useState<"calls" | "prompts" | null>(null);
|
|
67
|
+
const [expandedProviders, setExpandedProviders] = useState<Set<string>>(new Set());
|
|
68
|
+
const [expandedModels, setExpandedModels] = useState<Set<string>>(new Set());
|
|
69
|
+
const [fileFilter, setFileFilter] = useState<string | null>(null);
|
|
70
|
+
|
|
71
|
+
// Navigate to a specific file's prompts
|
|
72
|
+
const navigateToFile = (file: string) => {
|
|
73
|
+
setFileFilter(file);
|
|
74
|
+
setActiveTab("prompts");
|
|
75
|
+
// Auto-select first prompt in this file
|
|
76
|
+
const firstPrompt = prompts.find((p) => p.file === file);
|
|
77
|
+
if (firstPrompt) {
|
|
78
|
+
setSelectedPrompt(firstPrompt);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const filteredCalls = calls.filter((call) => {
|
|
83
|
+
const matchesSearch =
|
|
84
|
+
call.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
85
|
+
call.model.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
86
|
+
call.file.toLowerCase().includes(searchQuery.toLowerCase());
|
|
87
|
+
const matchesCategory = categoryFilter === "all" || call.category === categoryFilter;
|
|
88
|
+
const matchesProvider = providerFilter === "all" || call.provider === providerFilter;
|
|
89
|
+
return matchesSearch && matchesCategory && matchesProvider;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const filteredPrompts = prompts.filter((prompt) => {
|
|
93
|
+
const matchesSearch =
|
|
94
|
+
prompt.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
95
|
+
prompt.content.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
96
|
+
prompt.file.toLowerCase().includes(searchQuery.toLowerCase());
|
|
97
|
+
const matchesFile = !fileFilter || prompt.file === fileFilter;
|
|
98
|
+
return matchesSearch && matchesFile;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const toggleExpanded = (id: string) => {
|
|
102
|
+
const next = new Set(expandedCalls);
|
|
103
|
+
if (next.has(id)) {
|
|
104
|
+
next.delete(id);
|
|
105
|
+
} else {
|
|
106
|
+
next.add(id);
|
|
107
|
+
}
|
|
108
|
+
setExpandedCalls(next);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const toggleProvider = (provider: string) => {
|
|
112
|
+
const next = new Set(expandedProviders);
|
|
113
|
+
if (next.has(provider)) {
|
|
114
|
+
next.delete(provider);
|
|
115
|
+
} else {
|
|
116
|
+
next.add(provider);
|
|
117
|
+
}
|
|
118
|
+
setExpandedProviders(next);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const toggleModel = (key: string) => {
|
|
122
|
+
const next = new Set(expandedModels);
|
|
123
|
+
if (next.has(key)) {
|
|
124
|
+
next.delete(key);
|
|
125
|
+
} else {
|
|
126
|
+
next.add(key);
|
|
127
|
+
}
|
|
128
|
+
setExpandedModels(next);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const copyToClipboard = (text: string, id: string) => {
|
|
132
|
+
navigator.clipboard.writeText(text);
|
|
133
|
+
setCopiedId(id);
|
|
134
|
+
setTimeout(() => setCopiedId(null), 2000);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Calculate stats from real data
|
|
138
|
+
const providers = [...new Set(calls.map((c) => c.provider))];
|
|
139
|
+
const categories = [...new Set(calls.map((c) => c.category))];
|
|
140
|
+
const filesWithAI = new Set(calls.map((c) => c.file)).size;
|
|
141
|
+
|
|
142
|
+
// Enhancement 3: Compute issues
|
|
143
|
+
const issues = useMemo(() => {
|
|
144
|
+
const items: Array<{ type: string; message: string; id: string; tab: "calls" | "prompts" }> = [];
|
|
145
|
+
for (const call of calls) {
|
|
146
|
+
if (call.provider === "unknown") items.push({ type: "unknown-provider", message: `${call.name}: unknown provider`, id: call.id, tab: "calls" });
|
|
147
|
+
if (call.model === "unknown") items.push({ type: "unknown-model", message: `${call.name}: unknown model`, id: call.id, tab: "calls" });
|
|
148
|
+
if (!call.promptTemplate && !call.systemPrompt) items.push({ type: "no-prompt", message: `${call.name}: no visible prompt`, id: call.id, tab: "calls" });
|
|
149
|
+
}
|
|
150
|
+
for (const prompt of prompts) {
|
|
151
|
+
if (prompt.usedBy.length === 0) items.push({ type: "orphan", message: `${prompt.name}: defined but never called`, id: prompt.id, tab: "prompts" });
|
|
152
|
+
}
|
|
153
|
+
return items;
|
|
154
|
+
}, [calls, prompts]);
|
|
155
|
+
|
|
156
|
+
const issuesByType = useMemo(() => {
|
|
157
|
+
const counts: Record<string, number> = {};
|
|
158
|
+
for (const issue of issues) {
|
|
159
|
+
counts[issue.type] = (counts[issue.type] || 0) + 1;
|
|
160
|
+
}
|
|
161
|
+
return counts;
|
|
162
|
+
}, [issues]);
|
|
163
|
+
|
|
164
|
+
const issueTextSummary = useMemo(() => {
|
|
165
|
+
const parts: string[] = [];
|
|
166
|
+
if (issuesByType["unknown-provider"]) parts.push(`${issuesByType["unknown-provider"]} unknown provider${issuesByType["unknown-provider"] > 1 ? "s" : ""}`);
|
|
167
|
+
if (issuesByType["unknown-model"]) parts.push(`${issuesByType["unknown-model"]} unknown model${issuesByType["unknown-model"] > 1 ? "s" : ""}`);
|
|
168
|
+
if (issuesByType["no-prompt"]) parts.push(`${issuesByType["no-prompt"]} call${issuesByType["no-prompt"] > 1 ? "s" : ""} without prompt`);
|
|
169
|
+
if (issuesByType["orphan"]) parts.push(`${issuesByType["orphan"]} orphan prompt${issuesByType["orphan"] > 1 ? "s" : ""}`);
|
|
170
|
+
return parts.join(", ");
|
|
171
|
+
}, [issuesByType]);
|
|
172
|
+
|
|
173
|
+
// Enhancement 1: Provider tree computation
|
|
174
|
+
const providerTree = useMemo(() => {
|
|
175
|
+
const tree: Record<string, Record<string, LLMCall[]>> = {};
|
|
176
|
+
for (const call of calls) {
|
|
177
|
+
if (!tree[call.provider]) tree[call.provider] = {};
|
|
178
|
+
if (!tree[call.provider][call.model]) tree[call.provider][call.model] = [];
|
|
179
|
+
tree[call.provider][call.model].push(call);
|
|
180
|
+
}
|
|
181
|
+
return tree;
|
|
182
|
+
}, [calls]);
|
|
183
|
+
|
|
184
|
+
const providerStats = useMemo(() => {
|
|
185
|
+
const stats: Record<string, { modelCount: number; callSiteCount: number }> = {};
|
|
186
|
+
for (const provider in providerTree) {
|
|
187
|
+
const models = providerTree[provider];
|
|
188
|
+
const modelCount = Object.keys(models).length;
|
|
189
|
+
const callSiteCount = Object.values(models).flat().length;
|
|
190
|
+
stats[provider] = { modelCount, callSiteCount };
|
|
191
|
+
}
|
|
192
|
+
return stats;
|
|
193
|
+
}, [providerTree]);
|
|
194
|
+
|
|
195
|
+
const modelStats = useMemo(() => {
|
|
196
|
+
const stats: Record<string, number> = {};
|
|
197
|
+
for (const provider in providerTree) {
|
|
198
|
+
for (const model in providerTree[provider]) {
|
|
199
|
+
const key = `${provider}:${model}`;
|
|
200
|
+
stats[key] = providerTree[provider][model].length;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return stats;
|
|
204
|
+
}, [providerTree]);
|
|
205
|
+
|
|
206
|
+
// Find provider and model for a call
|
|
207
|
+
const getCallProviderModel = (callId: string) => {
|
|
208
|
+
const call = calls.find(c => c.id === callId);
|
|
209
|
+
return call ? { provider: call.provider, model: call.model } : null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Sheet handlers
|
|
213
|
+
const openSheet = (type: "calls" | "prompts") => {
|
|
214
|
+
setSheetType(type);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const closeSheet = () => {
|
|
218
|
+
setSheetType(null);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const handleSheetItemClick = (type: "calls" | "prompts", item: LLMCall | Prompt) => {
|
|
222
|
+
if (type === "calls") {
|
|
223
|
+
setSelectedCall(item as LLMCall);
|
|
224
|
+
setActiveTab("calls");
|
|
225
|
+
} else {
|
|
226
|
+
setSelectedPrompt(item as Prompt);
|
|
227
|
+
setActiveTab("prompts");
|
|
228
|
+
}
|
|
229
|
+
closeSheet();
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Helper to check if call has issue
|
|
233
|
+
const callHasIssue = (callId: string) => {
|
|
234
|
+
return issues.some(issue => issue.id === callId && issue.tab === "calls");
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const promptHasIssue = (promptId: string) => {
|
|
238
|
+
return issues.some(issue => issue.id === promptId && issue.tab === "prompts");
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div className="flex flex-col gap-6">
|
|
243
|
+
{/* Data Source Indicator */}
|
|
244
|
+
{source === "mock" && (
|
|
245
|
+
<div className="flex items-center gap-2 rounded-lg border border-info/30 bg-info/10 p-3">
|
|
246
|
+
<Info className="h-4 w-4 text-info" />
|
|
247
|
+
<p className="text-sm text-info">
|
|
248
|
+
Showing demo data. Run <code className="rounded bg-info/20 px-1">navgator scan --prompts</code> to scan your project.
|
|
249
|
+
</p>
|
|
250
|
+
<Button
|
|
251
|
+
variant="outline"
|
|
252
|
+
size="sm"
|
|
253
|
+
className="ml-auto"
|
|
254
|
+
onClick={() => scan()}
|
|
255
|
+
disabled={isLoading}
|
|
256
|
+
>
|
|
257
|
+
{isLoading ? (
|
|
258
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
259
|
+
) : (
|
|
260
|
+
<RefreshCw className="h-4 w-4" />
|
|
261
|
+
)}
|
|
262
|
+
<span className="ml-2">Scan Now</span>
|
|
263
|
+
</Button>
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{error && (
|
|
268
|
+
<div className="flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/10 p-3">
|
|
269
|
+
<AlertTriangle className="h-4 w-4 text-destructive" />
|
|
270
|
+
<p className="text-sm text-destructive">{error}</p>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
{/* Summary Stats - Enhancement 2: Make clickable */}
|
|
275
|
+
<div className="grid grid-cols-3 gap-4">
|
|
276
|
+
<Card
|
|
277
|
+
className="border-border bg-card cursor-pointer transition-colors hover:bg-secondary/50"
|
|
278
|
+
onClick={() => openSheet("calls")}
|
|
279
|
+
>
|
|
280
|
+
<CardContent className="p-4">
|
|
281
|
+
<div className="flex items-center gap-3">
|
|
282
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
|
283
|
+
{isLoading ? (
|
|
284
|
+
<Loader2 className="h-5 w-5 animate-spin text-primary" />
|
|
285
|
+
) : (
|
|
286
|
+
<Brain className="h-5 w-5 text-primary" />
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
<div>
|
|
290
|
+
<p className="text-sm text-muted-foreground">LLM Call Sites</p>
|
|
291
|
+
<p className="text-2xl font-semibold text-foreground">
|
|
292
|
+
{calls.length}
|
|
293
|
+
</p>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
</CardContent>
|
|
297
|
+
</Card>
|
|
298
|
+
|
|
299
|
+
<Card
|
|
300
|
+
className="border-border bg-card cursor-pointer transition-colors hover:bg-secondary/50"
|
|
301
|
+
onClick={() => openSheet("prompts")}
|
|
302
|
+
>
|
|
303
|
+
<CardContent className="p-4">
|
|
304
|
+
<div className="flex items-center gap-3">
|
|
305
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-info/10">
|
|
306
|
+
<FileText className="h-5 w-5 text-info" />
|
|
307
|
+
</div>
|
|
308
|
+
<div>
|
|
309
|
+
<p className="text-sm text-muted-foreground">Prompts</p>
|
|
310
|
+
<p className="text-2xl font-semibold text-foreground">
|
|
311
|
+
{prompts.length}
|
|
312
|
+
</p>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</CardContent>
|
|
316
|
+
</Card>
|
|
317
|
+
|
|
318
|
+
<Card className="border-border bg-card">
|
|
319
|
+
<CardContent className="p-4">
|
|
320
|
+
<div className="flex items-center gap-3">
|
|
321
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-warning/10">
|
|
322
|
+
<Layers className="h-5 w-5 text-warning" />
|
|
323
|
+
</div>
|
|
324
|
+
<div>
|
|
325
|
+
<p className="text-sm text-muted-foreground">Providers</p>
|
|
326
|
+
<p className="text-2xl font-semibold text-foreground">
|
|
327
|
+
{providers.length}
|
|
328
|
+
</p>
|
|
329
|
+
<p className="text-xs text-muted-foreground">
|
|
330
|
+
{filesWithAI} file{filesWithAI !== 1 ? "s" : ""} with AI
|
|
331
|
+
</p>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</CardContent>
|
|
335
|
+
</Card>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{/* Enhancement 3: Issues summary bar */}
|
|
339
|
+
{issues.length > 0 && (
|
|
340
|
+
<div className="flex items-center gap-2">
|
|
341
|
+
<AlertTriangle className="h-4 w-4 text-warning" />
|
|
342
|
+
<p className="text-sm text-warning">
|
|
343
|
+
{issues.length} item{issues.length !== 1 ? "s" : ""} need attention: {issueTextSummary}
|
|
344
|
+
</p>
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
|
|
348
|
+
{/* Enhancement 2: Sheet overlay */}
|
|
349
|
+
<Sheet open={sheetType !== null} onOpenChange={(open) => !open && closeSheet()}>
|
|
350
|
+
<SheetContent side="right" className="w-[400px] sm:w-[540px]">
|
|
351
|
+
<SheetHeader>
|
|
352
|
+
<SheetTitle>
|
|
353
|
+
{sheetType === "calls" && "All LLM Call Sites"}
|
|
354
|
+
{sheetType === "prompts" && "All Prompts"}
|
|
355
|
+
</SheetTitle>
|
|
356
|
+
</SheetHeader>
|
|
357
|
+
<ScrollArea className="h-[calc(100vh-80px)] mt-6">
|
|
358
|
+
<div className="rounded-lg border border-border">
|
|
359
|
+
{sheetType === "calls" && calls.map((call, idx) => (
|
|
360
|
+
<button
|
|
361
|
+
key={`${call.id}-sheet-${idx}`}
|
|
362
|
+
type="button"
|
|
363
|
+
onClick={() => handleSheetItemClick("calls", call)}
|
|
364
|
+
className={`w-full text-left p-4 transition-colors hover:bg-secondary/50 ${
|
|
365
|
+
idx !== calls.length - 1 ? "border-b border-border" : ""
|
|
366
|
+
}`}
|
|
367
|
+
>
|
|
368
|
+
<div className="font-medium text-sm">{call.name}</div>
|
|
369
|
+
<div className="text-xs text-muted-foreground mt-1">
|
|
370
|
+
{call.model} · {call.provider}
|
|
371
|
+
</div>
|
|
372
|
+
<div className="font-mono text-xs text-muted-foreground mt-1">
|
|
373
|
+
{call.file}:{call.line}
|
|
374
|
+
</div>
|
|
375
|
+
</button>
|
|
376
|
+
))}
|
|
377
|
+
{sheetType === "prompts" && prompts.map((prompt, idx) => (
|
|
378
|
+
<button
|
|
379
|
+
key={`${prompt.id}-sheet-${idx}`}
|
|
380
|
+
type="button"
|
|
381
|
+
onClick={() => handleSheetItemClick("prompts", prompt)}
|
|
382
|
+
className={`w-full text-left p-4 transition-colors hover:bg-secondary/50 ${
|
|
383
|
+
idx !== prompts.length - 1 ? "border-b border-border" : ""
|
|
384
|
+
}`}
|
|
385
|
+
>
|
|
386
|
+
<div className="font-medium text-sm">{prompt.name}</div>
|
|
387
|
+
<div className="text-xs text-muted-foreground mt-1">{prompt.type}</div>
|
|
388
|
+
<div className="font-mono text-xs text-muted-foreground mt-1">
|
|
389
|
+
{prompt.file}:{prompt.line}
|
|
390
|
+
</div>
|
|
391
|
+
</button>
|
|
392
|
+
))}
|
|
393
|
+
</div>
|
|
394
|
+
</ScrollArea>
|
|
395
|
+
</SheetContent>
|
|
396
|
+
</Sheet>
|
|
397
|
+
|
|
398
|
+
{/* Main Content - Enhancement 1: Add activeTab control */}
|
|
399
|
+
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
400
|
+
<div className="flex items-center justify-between gap-4">
|
|
401
|
+
<TabsList className="bg-secondary">
|
|
402
|
+
<TabsTrigger value="calls" className="gap-2">
|
|
403
|
+
<Zap className="h-4 w-4" />
|
|
404
|
+
LLM Call Sites
|
|
405
|
+
</TabsTrigger>
|
|
406
|
+
<TabsTrigger value="prompts" className="gap-2">
|
|
407
|
+
<MessageSquare className="h-4 w-4" />
|
|
408
|
+
Prompts
|
|
409
|
+
</TabsTrigger>
|
|
410
|
+
<TabsTrigger value="flow" className="gap-2">
|
|
411
|
+
<GitBranch className="h-4 w-4" />
|
|
412
|
+
AI Flow
|
|
413
|
+
</TabsTrigger>
|
|
414
|
+
<TabsTrigger value="providers" className="gap-2">
|
|
415
|
+
<Layers className="h-4 w-4" />
|
|
416
|
+
By Provider
|
|
417
|
+
</TabsTrigger>
|
|
418
|
+
</TabsList>
|
|
419
|
+
|
|
420
|
+
<div className="flex items-center gap-2">
|
|
421
|
+
<div className="relative">
|
|
422
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
423
|
+
<Input
|
|
424
|
+
placeholder="Search..."
|
|
425
|
+
value={searchQuery}
|
|
426
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
427
|
+
className="w-64 bg-secondary pl-9"
|
|
428
|
+
/>
|
|
429
|
+
</div>
|
|
430
|
+
<Button
|
|
431
|
+
variant="outline"
|
|
432
|
+
size="sm"
|
|
433
|
+
onClick={() => refresh()}
|
|
434
|
+
disabled={isLoading}
|
|
435
|
+
title="Refresh data"
|
|
436
|
+
>
|
|
437
|
+
{isLoading ? (
|
|
438
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
439
|
+
) : (
|
|
440
|
+
<RefreshCw className="h-4 w-4" />
|
|
441
|
+
)}
|
|
442
|
+
</Button>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<TabsContent value="calls" className="mt-4">
|
|
447
|
+
{/* Filters */}
|
|
448
|
+
<div className="mb-4 flex items-center gap-3">
|
|
449
|
+
<Filter className="h-4 w-4 text-muted-foreground" />
|
|
450
|
+
<div className="flex gap-2">
|
|
451
|
+
<Button
|
|
452
|
+
variant={categoryFilter === "all" ? "default" : "outline"}
|
|
453
|
+
size="sm"
|
|
454
|
+
onClick={() => setCategoryFilter("all")}
|
|
455
|
+
>
|
|
456
|
+
All Types
|
|
457
|
+
</Button>
|
|
458
|
+
{categories.map((cat) => (
|
|
459
|
+
<Button
|
|
460
|
+
key={cat}
|
|
461
|
+
variant={categoryFilter === cat ? "default" : "outline"}
|
|
462
|
+
size="sm"
|
|
463
|
+
onClick={() => setCategoryFilter(cat)}
|
|
464
|
+
className="capitalize"
|
|
465
|
+
>
|
|
466
|
+
{cat}
|
|
467
|
+
</Button>
|
|
468
|
+
))}
|
|
469
|
+
</div>
|
|
470
|
+
<div className="ml-4 flex gap-2">
|
|
471
|
+
<Button
|
|
472
|
+
variant={providerFilter === "all" ? "default" : "outline"}
|
|
473
|
+
size="sm"
|
|
474
|
+
onClick={() => setProviderFilter("all")}
|
|
475
|
+
>
|
|
476
|
+
All Providers
|
|
477
|
+
</Button>
|
|
478
|
+
{providers.map((prov) => (
|
|
479
|
+
<Button
|
|
480
|
+
key={prov}
|
|
481
|
+
variant={providerFilter === prov ? "default" : "outline"}
|
|
482
|
+
size="sm"
|
|
483
|
+
onClick={() => setProviderFilter(prov)}
|
|
484
|
+
className="capitalize"
|
|
485
|
+
>
|
|
486
|
+
{prov}
|
|
487
|
+
</Button>
|
|
488
|
+
))}
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<div className="grid grid-cols-2 gap-4">
|
|
493
|
+
{/* LLM Calls List - Enhancement 3: Add issue indicators */}
|
|
494
|
+
<Card className="border-border bg-card">
|
|
495
|
+
<CardHeader className="pb-3">
|
|
496
|
+
<CardTitle className="text-base font-medium">
|
|
497
|
+
Detected Call Sites ({filteredCalls.length})
|
|
498
|
+
</CardTitle>
|
|
499
|
+
</CardHeader>
|
|
500
|
+
<CardContent className="p-0">
|
|
501
|
+
<ScrollArea className="h-[500px]">
|
|
502
|
+
<div className="flex flex-col">
|
|
503
|
+
{filteredCalls.map((call, idx) => (
|
|
504
|
+
<div key={`${call.id}-${idx}`}>
|
|
505
|
+
<button
|
|
506
|
+
type="button"
|
|
507
|
+
onClick={() => {
|
|
508
|
+
setSelectedCall(call);
|
|
509
|
+
toggleExpanded(call.id);
|
|
510
|
+
}}
|
|
511
|
+
className={`flex w-full items-start gap-3 border-b border-border p-4 text-left transition-colors hover:bg-secondary/50 ${
|
|
512
|
+
selectedCall?.id === call.id ? "bg-secondary" : ""
|
|
513
|
+
}`}
|
|
514
|
+
>
|
|
515
|
+
<div className="mt-0.5">
|
|
516
|
+
{expandedCalls.has(call.id) ? (
|
|
517
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
518
|
+
) : (
|
|
519
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
<div className="flex-1">
|
|
523
|
+
<div className="flex items-center gap-2">
|
|
524
|
+
<span className="font-mono text-sm text-foreground">
|
|
525
|
+
{call.name}
|
|
526
|
+
</span>
|
|
527
|
+
{callHasIssue(call.id) && (
|
|
528
|
+
<AlertTriangle className="h-3.5 w-3.5 text-warning" />
|
|
529
|
+
)}
|
|
530
|
+
<Badge
|
|
531
|
+
variant="outline"
|
|
532
|
+
className={`text-xs ${categoryColors[call.category]}`}
|
|
533
|
+
>
|
|
534
|
+
{call.category}
|
|
535
|
+
</Badge>
|
|
536
|
+
</div>
|
|
537
|
+
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
|
|
538
|
+
<span>{call.model}</span>
|
|
539
|
+
<span>·</span>
|
|
540
|
+
<span>{call.provider}</span>
|
|
541
|
+
{call.method && (
|
|
542
|
+
<>
|
|
543
|
+
<span>·</span>
|
|
544
|
+
<span className="font-mono">{call.method}</span>
|
|
545
|
+
</>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
<span
|
|
549
|
+
role="button"
|
|
550
|
+
tabIndex={0}
|
|
551
|
+
onClick={(e) => {
|
|
552
|
+
e.stopPropagation();
|
|
553
|
+
navigateToFile(call.file);
|
|
554
|
+
}}
|
|
555
|
+
onKeyDown={(e) => {
|
|
556
|
+
if (e.key === "Enter") {
|
|
557
|
+
e.stopPropagation();
|
|
558
|
+
navigateToFile(call.file);
|
|
559
|
+
}
|
|
560
|
+
}}
|
|
561
|
+
className="mt-1 font-mono text-xs text-muted-foreground hover:text-primary hover:underline transition-colors cursor-pointer"
|
|
562
|
+
>
|
|
563
|
+
{call.file}:{call.line}
|
|
564
|
+
</span>
|
|
565
|
+
</div>
|
|
566
|
+
</button>
|
|
567
|
+
|
|
568
|
+
{expandedCalls.has(call.id) && (
|
|
569
|
+
<div className="border-b border-border bg-secondary/30 p-4">
|
|
570
|
+
<div className="grid grid-cols-3 gap-4 text-sm">
|
|
571
|
+
{call.method && (
|
|
572
|
+
<div>
|
|
573
|
+
<p className="text-muted-foreground">SDK Method</p>
|
|
574
|
+
<p className="font-mono text-foreground">{call.method}</p>
|
|
575
|
+
</div>
|
|
576
|
+
)}
|
|
577
|
+
{call.sdk && (
|
|
578
|
+
<div>
|
|
579
|
+
<p className="text-muted-foreground">SDK</p>
|
|
580
|
+
<p className="font-mono text-foreground">{call.sdk}</p>
|
|
581
|
+
</div>
|
|
582
|
+
)}
|
|
583
|
+
<div>
|
|
584
|
+
<p className="text-muted-foreground">Confidence</p>
|
|
585
|
+
<p className="font-mono text-foreground">
|
|
586
|
+
{(call.confidence * 100).toFixed(0)}%
|
|
587
|
+
</p>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
{call.configExtracted && (
|
|
591
|
+
<div className="mt-3 grid grid-cols-3 gap-4 text-sm">
|
|
592
|
+
{call.configExtracted.temperature !== undefined && (
|
|
593
|
+
<div>
|
|
594
|
+
<p className="text-muted-foreground">Temperature</p>
|
|
595
|
+
<p className="font-mono text-foreground">{call.configExtracted.temperature}</p>
|
|
596
|
+
</div>
|
|
597
|
+
)}
|
|
598
|
+
{call.configExtracted.maxTokens !== undefined && (
|
|
599
|
+
<div>
|
|
600
|
+
<p className="text-muted-foreground">Max Tokens</p>
|
|
601
|
+
<p className="font-mono text-foreground">{call.configExtracted.maxTokens}</p>
|
|
602
|
+
</div>
|
|
603
|
+
)}
|
|
604
|
+
{call.configExtracted.stream !== undefined && (
|
|
605
|
+
<div>
|
|
606
|
+
<p className="text-muted-foreground">Streaming</p>
|
|
607
|
+
<p className="font-mono text-foreground">{call.configExtracted.stream ? "Yes" : "No"}</p>
|
|
608
|
+
</div>
|
|
609
|
+
)}
|
|
610
|
+
</div>
|
|
611
|
+
)}
|
|
612
|
+
{call.promptVariables.length > 0 && (
|
|
613
|
+
<div className="mt-3">
|
|
614
|
+
<p className="text-xs text-muted-foreground">
|
|
615
|
+
Variables:{" "}
|
|
616
|
+
{call.promptVariables.map((v) => (
|
|
617
|
+
<code
|
|
618
|
+
key={v}
|
|
619
|
+
className="mx-1 rounded bg-secondary px-1 py-0.5"
|
|
620
|
+
>
|
|
621
|
+
{`{{${v}}}`}
|
|
622
|
+
</code>
|
|
623
|
+
))}
|
|
624
|
+
</p>
|
|
625
|
+
</div>
|
|
626
|
+
)}
|
|
627
|
+
</div>
|
|
628
|
+
)}
|
|
629
|
+
</div>
|
|
630
|
+
))}
|
|
631
|
+
</div>
|
|
632
|
+
</ScrollArea>
|
|
633
|
+
</CardContent>
|
|
634
|
+
</Card>
|
|
635
|
+
|
|
636
|
+
{/* Call Details */}
|
|
637
|
+
<Card className="border-border bg-card">
|
|
638
|
+
<CardHeader className="pb-3">
|
|
639
|
+
<CardTitle className="text-base font-medium">Call Site Details</CardTitle>
|
|
640
|
+
</CardHeader>
|
|
641
|
+
<CardContent>
|
|
642
|
+
{selectedCall ? (
|
|
643
|
+
<div className="flex flex-col gap-4">
|
|
644
|
+
<div className="flex items-center justify-between">
|
|
645
|
+
<div>
|
|
646
|
+
<h3 className="font-mono text-lg text-foreground">
|
|
647
|
+
{selectedCall.name}
|
|
648
|
+
</h3>
|
|
649
|
+
<button
|
|
650
|
+
type="button"
|
|
651
|
+
onClick={() => navigateToFile(selectedCall.file)}
|
|
652
|
+
className="text-sm text-muted-foreground hover:text-primary hover:underline transition-colors text-left"
|
|
653
|
+
>
|
|
654
|
+
{selectedCall.file}:{selectedCall.line}
|
|
655
|
+
</button>
|
|
656
|
+
</div>
|
|
657
|
+
<Badge
|
|
658
|
+
variant="outline"
|
|
659
|
+
className={categoryColors[selectedCall.category]}
|
|
660
|
+
>
|
|
661
|
+
{selectedCall.category}
|
|
662
|
+
</Badge>
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<div className="grid grid-cols-2 gap-4">
|
|
666
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
667
|
+
<p className="text-xs text-muted-foreground">Model</p>
|
|
668
|
+
<p className="font-mono text-sm text-foreground">
|
|
669
|
+
{selectedCall.model}
|
|
670
|
+
</p>
|
|
671
|
+
{selectedCall.model === "unknown" && (
|
|
672
|
+
<p className="text-xs text-warning mt-1">Model name not detected</p>
|
|
673
|
+
)}
|
|
674
|
+
</div>
|
|
675
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
676
|
+
<p className="text-xs text-muted-foreground">Provider</p>
|
|
677
|
+
<p className="font-mono text-sm capitalize text-foreground">
|
|
678
|
+
{selectedCall.provider}
|
|
679
|
+
</p>
|
|
680
|
+
{selectedCall.provider === "unknown" && (
|
|
681
|
+
<p className="text-xs text-warning mt-1">Provider could not be identified from code patterns</p>
|
|
682
|
+
)}
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
|
|
686
|
+
{(selectedCall.method || selectedCall.sdk) && (
|
|
687
|
+
<div className="grid grid-cols-2 gap-4">
|
|
688
|
+
{selectedCall.method && (
|
|
689
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
690
|
+
<p className="text-xs text-muted-foreground">SDK Method</p>
|
|
691
|
+
<p className="font-mono text-sm text-foreground">{selectedCall.method}</p>
|
|
692
|
+
</div>
|
|
693
|
+
)}
|
|
694
|
+
{selectedCall.sdk && (
|
|
695
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
696
|
+
<p className="text-xs text-muted-foreground">SDK Package</p>
|
|
697
|
+
<p className="font-mono text-sm text-foreground">{selectedCall.sdk}</p>
|
|
698
|
+
</div>
|
|
699
|
+
)}
|
|
700
|
+
</div>
|
|
701
|
+
)}
|
|
702
|
+
|
|
703
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
704
|
+
<p className="text-xs text-muted-foreground">Confidence</p>
|
|
705
|
+
<p className="font-mono text-sm text-foreground">
|
|
706
|
+
{(selectedCall.confidence * 100).toFixed(0)}%
|
|
707
|
+
</p>
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
{selectedCall.systemPrompt && (
|
|
711
|
+
<div>
|
|
712
|
+
<div className="mb-2 flex items-center justify-between">
|
|
713
|
+
<p className="text-sm font-medium text-foreground">
|
|
714
|
+
System Prompt
|
|
715
|
+
</p>
|
|
716
|
+
<Button
|
|
717
|
+
variant="ghost"
|
|
718
|
+
size="sm"
|
|
719
|
+
onClick={() =>
|
|
720
|
+
copyToClipboard(
|
|
721
|
+
selectedCall.systemPrompt || "",
|
|
722
|
+
`sys-${selectedCall.id}`
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
>
|
|
726
|
+
{copiedId === `sys-${selectedCall.id}` ? (
|
|
727
|
+
<Check className="h-4 w-4 text-primary" />
|
|
728
|
+
) : (
|
|
729
|
+
<Copy className="h-4 w-4" />
|
|
730
|
+
)}
|
|
731
|
+
</Button>
|
|
732
|
+
</div>
|
|
733
|
+
<pre className="rounded-lg bg-secondary p-3 font-mono text-xs text-muted-foreground">
|
|
734
|
+
{selectedCall.systemPrompt}
|
|
735
|
+
</pre>
|
|
736
|
+
</div>
|
|
737
|
+
)}
|
|
738
|
+
|
|
739
|
+
<div>
|
|
740
|
+
<div className="mb-2 flex items-center justify-between">
|
|
741
|
+
<p className="text-sm font-medium text-foreground">
|
|
742
|
+
Prompt Template
|
|
743
|
+
</p>
|
|
744
|
+
<Button
|
|
745
|
+
variant="ghost"
|
|
746
|
+
size="sm"
|
|
747
|
+
onClick={() =>
|
|
748
|
+
copyToClipboard(
|
|
749
|
+
selectedCall.promptTemplate,
|
|
750
|
+
`tpl-${selectedCall.id}`
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
>
|
|
754
|
+
{copiedId === `tpl-${selectedCall.id}` ? (
|
|
755
|
+
<Check className="h-4 w-4 text-primary" />
|
|
756
|
+
) : (
|
|
757
|
+
<Copy className="h-4 w-4" />
|
|
758
|
+
)}
|
|
759
|
+
</Button>
|
|
760
|
+
</div>
|
|
761
|
+
<pre className="rounded-lg bg-secondary p-3 font-mono text-xs text-muted-foreground">
|
|
762
|
+
{selectedCall.promptTemplate}
|
|
763
|
+
</pre>
|
|
764
|
+
</div>
|
|
765
|
+
|
|
766
|
+
</div>
|
|
767
|
+
) : (
|
|
768
|
+
<div className="flex h-[400px] items-center justify-center text-muted-foreground">
|
|
769
|
+
<p>Select an LLM call to view details</p>
|
|
770
|
+
</div>
|
|
771
|
+
)}
|
|
772
|
+
</CardContent>
|
|
773
|
+
</Card>
|
|
774
|
+
</div>
|
|
775
|
+
</TabsContent>
|
|
776
|
+
|
|
777
|
+
<TabsContent value="prompts" className="mt-4">
|
|
778
|
+
{fileFilter && (
|
|
779
|
+
<div className="mb-3 flex items-center gap-2">
|
|
780
|
+
<span className="text-xs text-muted-foreground">Filtered by file:</span>
|
|
781
|
+
<button
|
|
782
|
+
type="button"
|
|
783
|
+
onClick={() => {
|
|
784
|
+
setFileFilter(null);
|
|
785
|
+
setSelectedPrompt(null);
|
|
786
|
+
}}
|
|
787
|
+
className="inline-flex items-center gap-1.5 rounded-md bg-secondary px-2.5 py-1 text-xs font-mono text-foreground hover:bg-secondary/80 transition-colors"
|
|
788
|
+
>
|
|
789
|
+
{fileFilter.split("/").pop()}
|
|
790
|
+
<X className="h-3 w-3 text-muted-foreground" />
|
|
791
|
+
</button>
|
|
792
|
+
</div>
|
|
793
|
+
)}
|
|
794
|
+
<div className="grid grid-cols-2 gap-4">
|
|
795
|
+
{/* Prompts List - Enhancement 1 & 3: Add provider/model info and issue indicators */}
|
|
796
|
+
<Card className="border-border bg-card">
|
|
797
|
+
<CardHeader className="pb-3">
|
|
798
|
+
<CardTitle className="text-base font-medium">
|
|
799
|
+
Prompt Library ({filteredPrompts.length})
|
|
800
|
+
</CardTitle>
|
|
801
|
+
</CardHeader>
|
|
802
|
+
<CardContent className="p-0">
|
|
803
|
+
<ScrollArea className="h-[500px]">
|
|
804
|
+
<div className="flex flex-col">
|
|
805
|
+
{filteredPrompts.map((prompt, idx) => {
|
|
806
|
+
// Find calls that use this prompt
|
|
807
|
+
const relatedCall = calls.find(call =>
|
|
808
|
+
call.promptTemplate?.includes(prompt.name) ||
|
|
809
|
+
call.systemPrompt?.includes(prompt.name) ||
|
|
810
|
+
prompt.usedBy.includes(call.name)
|
|
811
|
+
);
|
|
812
|
+
return (
|
|
813
|
+
<button
|
|
814
|
+
key={`${prompt.id}-${idx}`}
|
|
815
|
+
type="button"
|
|
816
|
+
onClick={() => setSelectedPrompt(prompt)}
|
|
817
|
+
className={`flex w-full items-start gap-3 border-b border-border p-4 text-left transition-colors hover:bg-secondary/50 ${
|
|
818
|
+
selectedPrompt?.id === prompt.id ? "bg-secondary" : ""
|
|
819
|
+
}`}
|
|
820
|
+
>
|
|
821
|
+
<div className="flex-1">
|
|
822
|
+
<div className="flex items-center gap-2">
|
|
823
|
+
<span className="font-mono text-sm text-foreground">
|
|
824
|
+
{prompt.name}
|
|
825
|
+
</span>
|
|
826
|
+
{promptHasIssue(prompt.id) && (
|
|
827
|
+
<AlertTriangle className="h-3.5 w-3.5 text-warning" />
|
|
828
|
+
)}
|
|
829
|
+
<Badge
|
|
830
|
+
variant="outline"
|
|
831
|
+
className={`text-xs ${typeColors[prompt.type]}`}
|
|
832
|
+
>
|
|
833
|
+
{prompt.type}
|
|
834
|
+
</Badge>
|
|
835
|
+
{relatedCall && (
|
|
836
|
+
<span className="text-xs text-muted-foreground">
|
|
837
|
+
{relatedCall.provider} · {relatedCall.model}
|
|
838
|
+
</span>
|
|
839
|
+
)}
|
|
840
|
+
</div>
|
|
841
|
+
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
|
842
|
+
{prompt.content}
|
|
843
|
+
</p>
|
|
844
|
+
<div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground">
|
|
845
|
+
<span>{prompt.tokenCount} tokens</span>
|
|
846
|
+
<span>·</span>
|
|
847
|
+
<span>v{prompt.version}</span>
|
|
848
|
+
<span>·</span>
|
|
849
|
+
<span>{prompt.lastModified}</span>
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
</button>
|
|
853
|
+
);
|
|
854
|
+
})}
|
|
855
|
+
</div>
|
|
856
|
+
</ScrollArea>
|
|
857
|
+
</CardContent>
|
|
858
|
+
</Card>
|
|
859
|
+
|
|
860
|
+
{/* Prompt Details - Enhancement 1: Add provider/model and clickable provider */}
|
|
861
|
+
<Card className="border-border bg-card">
|
|
862
|
+
<CardHeader className="pb-3">
|
|
863
|
+
<CardTitle className="text-base font-medium">Prompt Details</CardTitle>
|
|
864
|
+
</CardHeader>
|
|
865
|
+
<CardContent>
|
|
866
|
+
{selectedPrompt ? (
|
|
867
|
+
<div className="flex flex-col gap-4">
|
|
868
|
+
<div className="flex items-center justify-between">
|
|
869
|
+
<div>
|
|
870
|
+
<h3 className="font-mono text-lg text-foreground">
|
|
871
|
+
{selectedPrompt.name}
|
|
872
|
+
</h3>
|
|
873
|
+
<button
|
|
874
|
+
type="button"
|
|
875
|
+
onClick={() => navigateToFile(selectedPrompt.file)}
|
|
876
|
+
className="text-sm text-muted-foreground hover:text-primary hover:underline transition-colors text-left"
|
|
877
|
+
>
|
|
878
|
+
{selectedPrompt.file}:{selectedPrompt.line}
|
|
879
|
+
</button>
|
|
880
|
+
</div>
|
|
881
|
+
<div className="flex items-center gap-2">
|
|
882
|
+
<Badge variant="outline" className="text-xs">
|
|
883
|
+
v{selectedPrompt.version}
|
|
884
|
+
</Badge>
|
|
885
|
+
<Badge
|
|
886
|
+
variant="outline"
|
|
887
|
+
className={typeColors[selectedPrompt.type]}
|
|
888
|
+
>
|
|
889
|
+
{selectedPrompt.type}
|
|
890
|
+
</Badge>
|
|
891
|
+
</div>
|
|
892
|
+
</div>
|
|
893
|
+
|
|
894
|
+
{(() => {
|
|
895
|
+
const relatedCall = calls.find(call =>
|
|
896
|
+
call.promptTemplate?.includes(selectedPrompt.name) ||
|
|
897
|
+
call.systemPrompt?.includes(selectedPrompt.name) ||
|
|
898
|
+
selectedPrompt.usedBy.includes(call.name)
|
|
899
|
+
);
|
|
900
|
+
return relatedCall ? (
|
|
901
|
+
<div className="grid grid-cols-2 gap-4">
|
|
902
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
903
|
+
<p className="text-xs text-muted-foreground">Provider</p>
|
|
904
|
+
<button
|
|
905
|
+
type="button"
|
|
906
|
+
onClick={() => {
|
|
907
|
+
setActiveTab("providers");
|
|
908
|
+
setExpandedProviders(new Set([relatedCall.provider]));
|
|
909
|
+
}}
|
|
910
|
+
className={`font-mono text-sm text-foreground hover:text-primary transition-colors ${
|
|
911
|
+
relatedCall.provider === "unknown" ? "text-warning" : ""
|
|
912
|
+
}`}
|
|
913
|
+
>
|
|
914
|
+
{relatedCall.provider}
|
|
915
|
+
</button>
|
|
916
|
+
</div>
|
|
917
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
918
|
+
<p className="text-xs text-muted-foreground">Model</p>
|
|
919
|
+
<p className="font-mono text-sm text-foreground">
|
|
920
|
+
{relatedCall.model}
|
|
921
|
+
</p>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
) : null;
|
|
925
|
+
})()}
|
|
926
|
+
|
|
927
|
+
<div className="grid grid-cols-2 gap-4">
|
|
928
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
929
|
+
<p className="text-xs text-muted-foreground">Token Count</p>
|
|
930
|
+
<p className="font-mono text-sm text-foreground">
|
|
931
|
+
{selectedPrompt.tokenCount}
|
|
932
|
+
</p>
|
|
933
|
+
</div>
|
|
934
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
935
|
+
<p className="text-xs text-muted-foreground">Last Modified</p>
|
|
936
|
+
<p className="font-mono text-sm text-foreground">
|
|
937
|
+
{selectedPrompt.lastModified}
|
|
938
|
+
</p>
|
|
939
|
+
</div>
|
|
940
|
+
</div>
|
|
941
|
+
|
|
942
|
+
<div>
|
|
943
|
+
<div className="mb-2 flex items-center justify-between">
|
|
944
|
+
<p className="text-sm font-medium text-foreground">Content</p>
|
|
945
|
+
<Button
|
|
946
|
+
variant="ghost"
|
|
947
|
+
size="sm"
|
|
948
|
+
onClick={() =>
|
|
949
|
+
copyToClipboard(
|
|
950
|
+
selectedPrompt.content,
|
|
951
|
+
`content-${selectedPrompt.id}`
|
|
952
|
+
)
|
|
953
|
+
}
|
|
954
|
+
>
|
|
955
|
+
{copiedId === `content-${selectedPrompt.id}` ? (
|
|
956
|
+
<Check className="h-4 w-4 text-primary" />
|
|
957
|
+
) : (
|
|
958
|
+
<Copy className="h-4 w-4" />
|
|
959
|
+
)}
|
|
960
|
+
</Button>
|
|
961
|
+
</div>
|
|
962
|
+
<pre className="max-h-[200px] overflow-auto rounded-lg bg-secondary p-3 font-mono text-xs text-muted-foreground">
|
|
963
|
+
{selectedPrompt.content}
|
|
964
|
+
</pre>
|
|
965
|
+
</div>
|
|
966
|
+
|
|
967
|
+
{selectedPrompt.variables.length > 0 && (
|
|
968
|
+
<div>
|
|
969
|
+
<p className="mb-2 text-sm font-medium text-foreground">
|
|
970
|
+
Variables
|
|
971
|
+
</p>
|
|
972
|
+
<div className="flex flex-wrap gap-2">
|
|
973
|
+
{selectedPrompt.variables.map((v) => (
|
|
974
|
+
<code
|
|
975
|
+
key={v}
|
|
976
|
+
className="rounded bg-secondary px-2 py-1 font-mono text-xs text-muted-foreground"
|
|
977
|
+
>
|
|
978
|
+
{`{{${v}}}`}
|
|
979
|
+
</code>
|
|
980
|
+
))}
|
|
981
|
+
</div>
|
|
982
|
+
</div>
|
|
983
|
+
)}
|
|
984
|
+
|
|
985
|
+
<div>
|
|
986
|
+
<p className="mb-2 text-sm font-medium text-foreground">Used By</p>
|
|
987
|
+
<div className="flex flex-wrap gap-2">
|
|
988
|
+
{selectedPrompt.usedBy.length > 0 ? (
|
|
989
|
+
selectedPrompt.usedBy.map((fn) => (
|
|
990
|
+
<Badge key={fn} variant="outline" className="font-mono">
|
|
991
|
+
{fn}()
|
|
992
|
+
</Badge>
|
|
993
|
+
))
|
|
994
|
+
) : (
|
|
995
|
+
<p className="text-xs text-warning">Not used by any detected calls</p>
|
|
996
|
+
)}
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
</div>
|
|
1000
|
+
) : (
|
|
1001
|
+
<div className="flex h-[400px] items-center justify-center text-muted-foreground">
|
|
1002
|
+
<p>Select a prompt to view details</p>
|
|
1003
|
+
</div>
|
|
1004
|
+
)}
|
|
1005
|
+
</CardContent>
|
|
1006
|
+
</Card>
|
|
1007
|
+
</div>
|
|
1008
|
+
</TabsContent>
|
|
1009
|
+
|
|
1010
|
+
<TabsContent value="flow" className="mt-4">
|
|
1011
|
+
<AIFlowDiagram prompts={prompts} calls={calls} onFileClick={navigateToFile} />
|
|
1012
|
+
</TabsContent>
|
|
1013
|
+
|
|
1014
|
+
{/* Enhancement 1: By Provider tab */}
|
|
1015
|
+
<TabsContent value="providers" className="mt-4">
|
|
1016
|
+
<div className="grid grid-cols-2 gap-4">
|
|
1017
|
+
<Card className="border-border bg-card">
|
|
1018
|
+
<CardHeader className="pb-3">
|
|
1019
|
+
<CardTitle className="text-base font-medium">
|
|
1020
|
+
By Provider ({Object.keys(providerTree).length})
|
|
1021
|
+
</CardTitle>
|
|
1022
|
+
</CardHeader>
|
|
1023
|
+
<CardContent className="p-0">
|
|
1024
|
+
<ScrollArea className="h-[500px]">
|
|
1025
|
+
<div className="flex flex-col">
|
|
1026
|
+
{Object.entries(providerTree).map(([provider, models], providerIdx) => (
|
|
1027
|
+
<div key={provider}>
|
|
1028
|
+
<button
|
|
1029
|
+
type="button"
|
|
1030
|
+
onClick={() => toggleProvider(provider)}
|
|
1031
|
+
className="flex w-full items-start gap-3 border-b border-border p-4 text-left transition-colors hover:bg-secondary/50"
|
|
1032
|
+
>
|
|
1033
|
+
<div className="mt-0.5">
|
|
1034
|
+
{expandedProviders.has(provider) ? (
|
|
1035
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
1036
|
+
) : (
|
|
1037
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
1038
|
+
)}
|
|
1039
|
+
</div>
|
|
1040
|
+
<div className="flex-1">
|
|
1041
|
+
<div className="flex items-center gap-2">
|
|
1042
|
+
<span className={`font-medium text-sm ${provider === "unknown" ? "text-warning" : "text-foreground"}`}>
|
|
1043
|
+
{provider}
|
|
1044
|
+
</span>
|
|
1045
|
+
</div>
|
|
1046
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
1047
|
+
{providerStats[provider].modelCount} model{providerStats[provider].modelCount !== 1 ? "s" : ""} · {providerStats[provider].callSiteCount} call site{providerStats[provider].callSiteCount !== 1 ? "s" : ""}
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</button>
|
|
1051
|
+
|
|
1052
|
+
{expandedProviders.has(provider) && (
|
|
1053
|
+
<div className="border-b border-border bg-secondary/30">
|
|
1054
|
+
{Object.entries(models).map(([model, modelCalls]) => {
|
|
1055
|
+
const modelKey = `${provider}:${model}`;
|
|
1056
|
+
return (
|
|
1057
|
+
<div key={modelKey}>
|
|
1058
|
+
<button
|
|
1059
|
+
type="button"
|
|
1060
|
+
onClick={() => toggleModel(modelKey)}
|
|
1061
|
+
className="flex w-full items-start gap-3 border-b border-border p-4 pl-12 text-left transition-colors hover:bg-secondary/50"
|
|
1062
|
+
>
|
|
1063
|
+
<div className="mt-0.5">
|
|
1064
|
+
{expandedModels.has(modelKey) ? (
|
|
1065
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
1066
|
+
) : (
|
|
1067
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
1068
|
+
)}
|
|
1069
|
+
</div>
|
|
1070
|
+
<div className="flex-1">
|
|
1071
|
+
<div className="flex items-center gap-2">
|
|
1072
|
+
<span className="font-mono text-sm text-foreground">
|
|
1073
|
+
{model}
|
|
1074
|
+
</span>
|
|
1075
|
+
</div>
|
|
1076
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
1077
|
+
{modelStats[modelKey]} call site{modelStats[modelKey] !== 1 ? "s" : ""}
|
|
1078
|
+
</div>
|
|
1079
|
+
</div>
|
|
1080
|
+
</button>
|
|
1081
|
+
|
|
1082
|
+
{expandedModels.has(modelKey) && (
|
|
1083
|
+
<div className="border-b border-border bg-secondary/50">
|
|
1084
|
+
{modelCalls.map((call, callIdx) => (
|
|
1085
|
+
<button
|
|
1086
|
+
key={`${call.id}-prov-${callIdx}`}
|
|
1087
|
+
type="button"
|
|
1088
|
+
onClick={() => {
|
|
1089
|
+
setSelectedCall(call);
|
|
1090
|
+
}}
|
|
1091
|
+
className={`flex w-full items-start gap-3 border-b border-border p-4 pl-20 text-left transition-colors hover:bg-secondary/70 ${
|
|
1092
|
+
selectedCall?.id === call.id ? "bg-secondary" : ""
|
|
1093
|
+
}`}
|
|
1094
|
+
>
|
|
1095
|
+
<div className="flex-1">
|
|
1096
|
+
<div className="flex items-center gap-2">
|
|
1097
|
+
<span className="font-mono text-sm text-foreground">
|
|
1098
|
+
{call.name}
|
|
1099
|
+
</span>
|
|
1100
|
+
</div>
|
|
1101
|
+
<p className="mt-1 font-mono text-xs text-muted-foreground">
|
|
1102
|
+
{call.file}:{call.line}
|
|
1103
|
+
</p>
|
|
1104
|
+
</div>
|
|
1105
|
+
</button>
|
|
1106
|
+
))}
|
|
1107
|
+
</div>
|
|
1108
|
+
)}
|
|
1109
|
+
</div>
|
|
1110
|
+
);
|
|
1111
|
+
})}
|
|
1112
|
+
</div>
|
|
1113
|
+
)}
|
|
1114
|
+
</div>
|
|
1115
|
+
))}
|
|
1116
|
+
</div>
|
|
1117
|
+
</ScrollArea>
|
|
1118
|
+
</CardContent>
|
|
1119
|
+
</Card>
|
|
1120
|
+
|
|
1121
|
+
{/* Call Site Details (providers tab) */}
|
|
1122
|
+
<Card className="border-border bg-card">
|
|
1123
|
+
<CardHeader className="pb-3">
|
|
1124
|
+
<CardTitle className="text-base font-medium">Call Site Details</CardTitle>
|
|
1125
|
+
</CardHeader>
|
|
1126
|
+
<CardContent>
|
|
1127
|
+
{selectedCall ? (
|
|
1128
|
+
<div className="flex flex-col gap-4">
|
|
1129
|
+
<div className="flex items-center justify-between">
|
|
1130
|
+
<div>
|
|
1131
|
+
<h3 className="font-mono text-lg text-foreground">
|
|
1132
|
+
{selectedCall.name}
|
|
1133
|
+
</h3>
|
|
1134
|
+
<button
|
|
1135
|
+
type="button"
|
|
1136
|
+
onClick={() => navigateToFile(selectedCall.file)}
|
|
1137
|
+
className="text-sm text-muted-foreground hover:text-primary hover:underline transition-colors text-left"
|
|
1138
|
+
>
|
|
1139
|
+
{selectedCall.file}:{selectedCall.line}
|
|
1140
|
+
</button>
|
|
1141
|
+
</div>
|
|
1142
|
+
<Badge
|
|
1143
|
+
variant="outline"
|
|
1144
|
+
className={categoryColors[selectedCall.category]}
|
|
1145
|
+
>
|
|
1146
|
+
{selectedCall.category}
|
|
1147
|
+
</Badge>
|
|
1148
|
+
</div>
|
|
1149
|
+
|
|
1150
|
+
<div className="grid grid-cols-2 gap-4">
|
|
1151
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
1152
|
+
<p className="text-xs text-muted-foreground">Model</p>
|
|
1153
|
+
<p className="font-mono text-sm text-foreground">{selectedCall.model}</p>
|
|
1154
|
+
</div>
|
|
1155
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
1156
|
+
<p className="text-xs text-muted-foreground">Provider</p>
|
|
1157
|
+
<p className="font-mono text-sm capitalize text-foreground">{selectedCall.provider}</p>
|
|
1158
|
+
</div>
|
|
1159
|
+
</div>
|
|
1160
|
+
|
|
1161
|
+
{(selectedCall.method || selectedCall.sdk) && (
|
|
1162
|
+
<div className="grid grid-cols-2 gap-4">
|
|
1163
|
+
{selectedCall.method && (
|
|
1164
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
1165
|
+
<p className="text-xs text-muted-foreground">SDK Method</p>
|
|
1166
|
+
<p className="font-mono text-sm text-foreground">{selectedCall.method}</p>
|
|
1167
|
+
</div>
|
|
1168
|
+
)}
|
|
1169
|
+
{selectedCall.sdk && (
|
|
1170
|
+
<div className="rounded-lg bg-secondary p-3">
|
|
1171
|
+
<p className="text-xs text-muted-foreground">SDK Package</p>
|
|
1172
|
+
<p className="font-mono text-sm text-foreground">{selectedCall.sdk}</p>
|
|
1173
|
+
</div>
|
|
1174
|
+
)}
|
|
1175
|
+
</div>
|
|
1176
|
+
)}
|
|
1177
|
+
|
|
1178
|
+
{selectedCall.systemPrompt && (
|
|
1179
|
+
<div>
|
|
1180
|
+
<p className="mb-2 text-sm font-medium text-foreground">System Prompt</p>
|
|
1181
|
+
<pre className="rounded-lg bg-secondary p-3 font-mono text-xs text-muted-foreground">
|
|
1182
|
+
{selectedCall.systemPrompt}
|
|
1183
|
+
</pre>
|
|
1184
|
+
</div>
|
|
1185
|
+
)}
|
|
1186
|
+
|
|
1187
|
+
<div>
|
|
1188
|
+
<p className="mb-2 text-sm font-medium text-foreground">Prompt Template</p>
|
|
1189
|
+
<pre className="rounded-lg bg-secondary p-3 font-mono text-xs text-muted-foreground">
|
|
1190
|
+
{selectedCall.promptTemplate}
|
|
1191
|
+
</pre>
|
|
1192
|
+
</div>
|
|
1193
|
+
|
|
1194
|
+
</div>
|
|
1195
|
+
) : (
|
|
1196
|
+
<div className="flex h-[400px] items-center justify-center text-muted-foreground">
|
|
1197
|
+
<p>Select a call site to view details</p>
|
|
1198
|
+
</div>
|
|
1199
|
+
)}
|
|
1200
|
+
</CardContent>
|
|
1201
|
+
</Card>
|
|
1202
|
+
</div>
|
|
1203
|
+
</TabsContent>
|
|
1204
|
+
</Tabs>
|
|
1205
|
+
</div>
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// =============================================================================
|
|
1210
|
+
// AI FLOW DIAGRAM COMPONENT
|
|
1211
|
+
// =============================================================================
|
|
1212
|
+
|
|
1213
|
+
interface AIFlowDiagramProps {
|
|
1214
|
+
prompts: Prompt[];
|
|
1215
|
+
calls: LLMCall[];
|
|
1216
|
+
onFileClick?: (file: string) => void;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
interface FlowNode {
|
|
1220
|
+
id: string;
|
|
1221
|
+
name: string;
|
|
1222
|
+
file: string;
|
|
1223
|
+
type: "input" | "process" | "output";
|
|
1224
|
+
category?: string;
|
|
1225
|
+
purpose?: string;
|
|
1226
|
+
prompts: Prompt[];
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function AIFlowDiagram({ prompts, calls, onFileClick }: AIFlowDiagramProps) {
|
|
1230
|
+
// Group prompts by file and categorize them
|
|
1231
|
+
const groupedByFile = prompts.reduce((acc, prompt) => {
|
|
1232
|
+
const file = prompt.file;
|
|
1233
|
+
if (!acc[file]) {
|
|
1234
|
+
acc[file] = [];
|
|
1235
|
+
}
|
|
1236
|
+
acc[file].push(prompt);
|
|
1237
|
+
return acc;
|
|
1238
|
+
}, {} as Record<string, Prompt[]>);
|
|
1239
|
+
|
|
1240
|
+
// Categorize files into flow stages based on naming patterns and prompt types
|
|
1241
|
+
const categorizeFile = (file: string, filePrompts: Prompt[]): "input" | "process" | "output" => {
|
|
1242
|
+
const lowerFile = file.toLowerCase();
|
|
1243
|
+
|
|
1244
|
+
// Input/routing patterns
|
|
1245
|
+
if (
|
|
1246
|
+
lowerFile.includes("router") ||
|
|
1247
|
+
lowerFile.includes("query") ||
|
|
1248
|
+
lowerFile.includes("classify") ||
|
|
1249
|
+
lowerFile.includes("input") ||
|
|
1250
|
+
lowerFile.includes("parse")
|
|
1251
|
+
) {
|
|
1252
|
+
return "input";
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// Output patterns
|
|
1256
|
+
if (
|
|
1257
|
+
lowerFile.includes("summar") ||
|
|
1258
|
+
lowerFile.includes("output") ||
|
|
1259
|
+
lowerFile.includes("response") ||
|
|
1260
|
+
lowerFile.includes("format") ||
|
|
1261
|
+
lowerFile.includes("render")
|
|
1262
|
+
) {
|
|
1263
|
+
return "output";
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// Check prompt purposes/categories
|
|
1267
|
+
const hasInput = filePrompts.some(p =>
|
|
1268
|
+
p.purpose?.toLowerCase().includes("classif") ||
|
|
1269
|
+
p.purpose?.toLowerCase().includes("rout") ||
|
|
1270
|
+
p.category?.toLowerCase().includes("input")
|
|
1271
|
+
);
|
|
1272
|
+
if (hasInput) return "input";
|
|
1273
|
+
|
|
1274
|
+
const hasOutput = filePrompts.some(p =>
|
|
1275
|
+
p.purpose?.toLowerCase().includes("summar") ||
|
|
1276
|
+
p.purpose?.toLowerCase().includes("format") ||
|
|
1277
|
+
p.category?.toLowerCase().includes("output")
|
|
1278
|
+
);
|
|
1279
|
+
if (hasOutput) return "output";
|
|
1280
|
+
|
|
1281
|
+
return "process";
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
// Build flow nodes
|
|
1285
|
+
const flowNodes: FlowNode[] = Object.entries(groupedByFile).map(([file, filePrompts]) => {
|
|
1286
|
+
const type = categorizeFile(file, filePrompts);
|
|
1287
|
+
const fileName = file.split("/").pop() || file;
|
|
1288
|
+
|
|
1289
|
+
return {
|
|
1290
|
+
id: file,
|
|
1291
|
+
name: fileName.replace(/\.(ts|js|tsx|jsx|py)$/, ""),
|
|
1292
|
+
file,
|
|
1293
|
+
type,
|
|
1294
|
+
category: filePrompts[0]?.category,
|
|
1295
|
+
purpose: filePrompts[0]?.purpose,
|
|
1296
|
+
prompts: filePrompts,
|
|
1297
|
+
};
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
// Sort by type order: input -> process -> output
|
|
1301
|
+
const typeOrder = { input: 0, process: 1, output: 2 };
|
|
1302
|
+
flowNodes.sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);
|
|
1303
|
+
|
|
1304
|
+
// Group by type for display
|
|
1305
|
+
const inputNodes = flowNodes.filter(n => n.type === "input");
|
|
1306
|
+
const processNodes = flowNodes.filter(n => n.type === "process");
|
|
1307
|
+
const outputNodes = flowNodes.filter(n => n.type === "output");
|
|
1308
|
+
|
|
1309
|
+
const typeColors = {
|
|
1310
|
+
input: "border-info bg-info/10 text-info",
|
|
1311
|
+
process: "border-primary bg-primary/10 text-primary",
|
|
1312
|
+
output: "border-chart-3 bg-chart-3/10 text-chart-3",
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
const typeLabels = {
|
|
1316
|
+
input: "Input / Routing",
|
|
1317
|
+
process: "Processing / Analysis",
|
|
1318
|
+
output: "Output / Summary",
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
if (prompts.length === 0) {
|
|
1322
|
+
return (
|
|
1323
|
+
<Card className="border-border bg-card">
|
|
1324
|
+
<CardContent className="flex flex-col items-center justify-center py-16">
|
|
1325
|
+
<GitBranch className="h-12 w-12 text-muted-foreground/50" />
|
|
1326
|
+
<p className="mt-4 text-muted-foreground">No AI prompts detected</p>
|
|
1327
|
+
<p className="mt-1 text-xs text-muted-foreground/80">
|
|
1328
|
+
Run <code className="rounded bg-secondary px-1.5">navgator scan --prompts</code> to detect AI flows
|
|
1329
|
+
</p>
|
|
1330
|
+
</CardContent>
|
|
1331
|
+
</Card>
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return (
|
|
1336
|
+
<div className="space-y-6">
|
|
1337
|
+
{/* Flow Legend */}
|
|
1338
|
+
<div className="flex items-center gap-6 text-xs">
|
|
1339
|
+
<div className="flex items-center gap-2">
|
|
1340
|
+
<div className="h-3 w-3 rounded-full border-2 border-info bg-info/30" />
|
|
1341
|
+
<span className="text-muted-foreground">Input/Routing</span>
|
|
1342
|
+
</div>
|
|
1343
|
+
<div className="flex items-center gap-2">
|
|
1344
|
+
<div className="h-3 w-3 rounded-full border-2 border-primary bg-primary/30" />
|
|
1345
|
+
<span className="text-muted-foreground">Processing</span>
|
|
1346
|
+
</div>
|
|
1347
|
+
<div className="flex items-center gap-2">
|
|
1348
|
+
<div className="h-3 w-3 rounded-full border-2 border-chart-3 bg-chart-3/30" />
|
|
1349
|
+
<span className="text-muted-foreground">Output/Summary</span>
|
|
1350
|
+
</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
|
|
1353
|
+
{/* Flow Diagram */}
|
|
1354
|
+
<Card className="border-border bg-card">
|
|
1355
|
+
<CardContent className="p-6">
|
|
1356
|
+
<div className="relative flex flex-col items-center gap-2">
|
|
1357
|
+
{/* User Input */}
|
|
1358
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-secondary px-4 py-2">
|
|
1359
|
+
<Circle className="h-4 w-4 text-muted-foreground" />
|
|
1360
|
+
<span className="text-sm font-medium text-foreground">User Input</span>
|
|
1361
|
+
</div>
|
|
1362
|
+
|
|
1363
|
+
{inputNodes.length > 0 && (
|
|
1364
|
+
<>
|
|
1365
|
+
<ArrowDown className="h-5 w-5 text-muted-foreground" />
|
|
1366
|
+
<FlowStage
|
|
1367
|
+
label={typeLabels.input}
|
|
1368
|
+
nodes={inputNodes}
|
|
1369
|
+
colorClass={typeColors.input}
|
|
1370
|
+
onNodeClick={onFileClick}
|
|
1371
|
+
/>
|
|
1372
|
+
</>
|
|
1373
|
+
)}
|
|
1374
|
+
|
|
1375
|
+
{processNodes.length > 0 && (
|
|
1376
|
+
<>
|
|
1377
|
+
<ArrowDown className="h-5 w-5 text-muted-foreground" />
|
|
1378
|
+
<FlowStage
|
|
1379
|
+
label={typeLabels.process}
|
|
1380
|
+
nodes={processNodes}
|
|
1381
|
+
colorClass={typeColors.process}
|
|
1382
|
+
onNodeClick={onFileClick}
|
|
1383
|
+
/>
|
|
1384
|
+
</>
|
|
1385
|
+
)}
|
|
1386
|
+
|
|
1387
|
+
{outputNodes.length > 0 && (
|
|
1388
|
+
<>
|
|
1389
|
+
<ArrowDown className="h-5 w-5 text-muted-foreground" />
|
|
1390
|
+
<FlowStage
|
|
1391
|
+
label={typeLabels.output}
|
|
1392
|
+
nodes={outputNodes}
|
|
1393
|
+
colorClass={typeColors.output}
|
|
1394
|
+
onNodeClick={onFileClick}
|
|
1395
|
+
/>
|
|
1396
|
+
</>
|
|
1397
|
+
)}
|
|
1398
|
+
|
|
1399
|
+
<ArrowDown className="h-5 w-5 text-muted-foreground" />
|
|
1400
|
+
|
|
1401
|
+
{/* Response */}
|
|
1402
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-secondary px-4 py-2">
|
|
1403
|
+
<Circle className="h-4 w-4 text-muted-foreground" />
|
|
1404
|
+
<span className="text-sm font-medium text-foreground">Response</span>
|
|
1405
|
+
</div>
|
|
1406
|
+
</div>
|
|
1407
|
+
</CardContent>
|
|
1408
|
+
</Card>
|
|
1409
|
+
|
|
1410
|
+
{/* Detailed File List */}
|
|
1411
|
+
<Card className="border-border bg-card">
|
|
1412
|
+
<CardHeader className="pb-3">
|
|
1413
|
+
<CardTitle className="text-base font-medium">
|
|
1414
|
+
Files with AI Prompts ({Object.keys(groupedByFile).length})
|
|
1415
|
+
</CardTitle>
|
|
1416
|
+
</CardHeader>
|
|
1417
|
+
<CardContent>
|
|
1418
|
+
<ScrollArea className="h-[300px]">
|
|
1419
|
+
<div className="space-y-2">
|
|
1420
|
+
{flowNodes.map((node) => (
|
|
1421
|
+
<button
|
|
1422
|
+
type="button"
|
|
1423
|
+
key={node.id}
|
|
1424
|
+
onClick={() => onFileClick?.(node.file)}
|
|
1425
|
+
className={`w-full text-left rounded-lg border p-3 transition-colors hover:opacity-80 ${typeColors[node.type]}`}
|
|
1426
|
+
>
|
|
1427
|
+
<div className="flex items-center justify-between">
|
|
1428
|
+
<div className="flex items-center gap-2">
|
|
1429
|
+
<span className="font-mono text-sm font-medium">{node.name}</span>
|
|
1430
|
+
<Badge variant="outline" className="text-xs">
|
|
1431
|
+
{node.prompts.length} prompt{node.prompts.length !== 1 ? "s" : ""}
|
|
1432
|
+
</Badge>
|
|
1433
|
+
</div>
|
|
1434
|
+
<Badge variant="outline" className="text-xs capitalize">
|
|
1435
|
+
{node.type}
|
|
1436
|
+
</Badge>
|
|
1437
|
+
</div>
|
|
1438
|
+
<p className="mt-1 font-mono text-xs opacity-70">{node.file}</p>
|
|
1439
|
+
{node.purpose && (
|
|
1440
|
+
<p className="mt-2 text-xs">{node.purpose}</p>
|
|
1441
|
+
)}
|
|
1442
|
+
</button>
|
|
1443
|
+
))}
|
|
1444
|
+
</div>
|
|
1445
|
+
</ScrollArea>
|
|
1446
|
+
</CardContent>
|
|
1447
|
+
</Card>
|
|
1448
|
+
</div>
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
interface FlowStageProps {
|
|
1453
|
+
label: string;
|
|
1454
|
+
nodes: FlowNode[];
|
|
1455
|
+
colorClass: string;
|
|
1456
|
+
onNodeClick?: (file: string) => void;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function FlowStage({ label, nodes, colorClass, onNodeClick }: FlowStageProps) {
|
|
1460
|
+
return (
|
|
1461
|
+
<div className="w-full max-w-2xl">
|
|
1462
|
+
<div className="mb-2 text-center text-xs font-medium text-muted-foreground">
|
|
1463
|
+
{label}
|
|
1464
|
+
</div>
|
|
1465
|
+
<div className={`rounded-lg border-2 p-4 ${colorClass}`}>
|
|
1466
|
+
<div className="flex flex-wrap justify-center gap-3">
|
|
1467
|
+
{nodes.map((node) => (
|
|
1468
|
+
<button
|
|
1469
|
+
type="button"
|
|
1470
|
+
key={node.id}
|
|
1471
|
+
onClick={() => onNodeClick?.(node.file)}
|
|
1472
|
+
className="flex items-center gap-2 rounded-md bg-background/50 px-3 py-1.5 transition-colors hover:bg-background/80"
|
|
1473
|
+
>
|
|
1474
|
+
<FileText className="h-3.5 w-3.5" />
|
|
1475
|
+
<span className="font-mono text-xs">{node.name}</span>
|
|
1476
|
+
<span className="text-xs opacity-60">({node.prompts.length})</span>
|
|
1477
|
+
</button>
|
|
1478
|
+
))}
|
|
1479
|
+
</div>
|
|
1480
|
+
</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
);
|
|
1483
|
+
}
|