@jlongo78/agent-spaces 0.7.3 → 0.7.5
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/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +7 -0
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +19 -19
- package/.next/standalone/.next/routes-manifest.json +42 -0
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.rsc +17 -16
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.html +1 -1
- package/.next/standalone/.next/server/app/admin/users.rsc +17 -16
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.html +1 -1
- package/.next/standalone/.next/server/app/analytics.rsc +17 -16
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/chat/route.js +1 -1
- package/.next/standalone/.next/server/app/api/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/import/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js +7 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/timeline/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/usage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/cortex.html +1 -1
- package/.next/standalone/.next/server/app/cortex.rsc +17 -16
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/login.html +1 -1
- package/.next/standalone/.next/server/app/login.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects.html +1 -1
- package/.next/standalone/.next/server/app/m/projects.rsc +4 -4
- package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions.html +1 -1
- package/.next/standalone/.next/server/app/m/sessions.rsc +4 -4
- package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/settings.html +1 -1
- package/.next/standalone/.next/server/app/m/settings.rsc +4 -4
- package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/terminal.html +1 -1
- package/.next/standalone/.next/server/app/m/terminal.rsc +4 -4
- package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.html +1 -1
- package/.next/standalone/.next/server/app/m.rsc +4 -4
- package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.html +1 -1
- package/.next/standalone/.next/server/app/network.rsc +17 -16
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.html +1 -1
- package/.next/standalone/.next/server/app/projects.rsc +17 -16
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.html +1 -1
- package/.next/standalone/.next/server/app/sessions.rsc +17 -16
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +17 -16
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.html +1 -1
- package/.next/standalone/.next/server/app/terminal.rsc +17 -16
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.html +1 -1
- package/.next/standalone/.next/server/app/workspaces.rsc +17 -16
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
- package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +17 -16
- package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0041efe4._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00bf0ace._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e71d908._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e9142f3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10e47926._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1665dc78._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__175cbabf._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adae357._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d359752._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1e8fabeb._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1f8deca0._.js +8 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__253fdda1._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__28e6434f._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a386564._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2c20fb38._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__309132cd._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__cf3c60c2._.js → [root-of-the-server]__33fec964._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3786d8ae._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3ae92407._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3beda9fe._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4619e9bd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4a051043._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__508002e4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5086c373._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5913e097._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5b5f68d2._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c1f2459._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5ec8c977._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__63cebc6c._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__64d30d4d._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c54fc2e._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6dc1fb7e._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6e568102._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6faa04c0._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__74a34dc3._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e7250a4._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8309e0a4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__86cc0e2b._.js +6 -6
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c2565a._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d178ad9._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__93ee06f3._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e4c154a._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d2e1d3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ae53d343._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b3a04cef._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b4270b77._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b6b6ce60._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b9545dd9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c88b63f7._.js +98 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__eee4c5e8._.js → [root-of-the-server]__cba5f007._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cbf4ceb0._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cefdba2f._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf9e82bb._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d2897392._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d3b2d856._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d73273ca._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8417eb6._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc2a55de._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e0d4690b._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e678dd53._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e9223f55._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ea630076._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__eb8acb65._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f26ca49d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f33e1101._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f515f865._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fceb5d60._.js +98 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fed41403._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff2e98c2._.js +2 -2
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_assess_route_actions_3e8ef2a6.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_publish_route_actions_49238be3.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_refine_route_actions_05675688.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_review_route_actions_5cbb5f6b.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_seed_route_actions_21084bdb.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_marketplace_browse_route_actions_7ed0768c.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_marketplace_preview_route_actions_38d4cc17.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19afc53d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__efef0a06._.js → [root-of-the-server]__ca5beb09._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_7dd2ac82._.js → _078dd64d._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_163d0838._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_a4eeff0d._.js → _2230ad2d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_9303a965._.js → _701606d5._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_72b1de37._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_d39bcfda._.js → _93ef0f79._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_950142a4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_a22b5eb0._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_aeeff784._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_c1cfdd09._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_c2d3f6de._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_8167090e._.js → _db2fec84._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_02bae6e5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_terminal_page_tsx_de5e8d85._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_components_terminal_terminal-pane_tsx_803c5e2c._.js +2 -2
- package/.next/standalone/.next/server/edge/chunks/_d73df637._.js +1 -1
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/25b7a243a404a1a7.js +1 -0
- package/.next/standalone/.next/static/chunks/2b997e211a5d547b.js +1 -0
- package/.next/standalone/.next/static/chunks/5e08abb00653754a.js +1 -0
- package/.next/standalone/.next/static/chunks/61f2ed39b75b1efc.js +1 -0
- package/.next/standalone/.next/static/chunks/6c78a1dfa7ec2959.css +3 -0
- package/.next/standalone/.next/static/chunks/7246f1ee445f7024.js +1 -0
- package/.next/standalone/.next/static/chunks/7424664c6ffa94bd.js +1 -0
- package/.next/standalone/.next/static/chunks/7e0091ab6c5ee8bd.js +1 -0
- package/.next/standalone/.next/static/chunks/8b3f4572fec83caa.js +5 -0
- package/.next/standalone/.next/static/chunks/{a7b2795949a6c63e.js → 9899cf4c2bdbe61d.js} +2 -2
- package/.next/standalone/.next/static/chunks/ac339e970df82fa5.js +5 -0
- package/.next/standalone/.next/static/chunks/{0e61e67b7b8fc3f3.js → c418112e102673ce.js} +1 -1
- package/.next/standalone/.next/static/chunks/e2f0a2330dedff4b.js +85 -0
- package/.next/standalone/.next/static/chunks/f0c8b3f8cc1939ec.js +1 -0
- package/.next/standalone/.next/static/chunks/f9f2628207848ac2.js +1 -0
- package/.next/standalone/bin/cortex-hook.sh +62 -62
- package/.next/standalone/bin/cortex-mcp.js +60 -60
- package/.next/standalone/docs/superpowers/plans/2026-03-13-cortex-wiring.md +1387 -1387
- package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-entity-graph.md +1923 -1923
- package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-knowledge-evolution.md +1113 -1113
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-boundary-engine.md +853 -853
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-context-engine.md +1274 -1274
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-signal-ingestion.md +933 -933
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-lobes.md +1080 -1080
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-gravity-system.md +768 -768
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-ui.md +1108 -1108
- package/.next/standalone/docs/superpowers/plans/2026-03-18-cortex-ui-integration.md +1846 -0
- package/.next/standalone/docs/superpowers/specs/2026-03-13-cortex-wiring-design.md +268 -268
- package/.next/standalone/docs/superpowers/specs/2026-03-14-cortex-v2-design.md +623 -623
- package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-lobes-design.md +263 -263
- package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md +240 -240
- package/.next/standalone/docs/superpowers/specs/2026-03-18-cortex-ui-integration-design.md +341 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +46 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +221 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +1 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +42 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +46 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +221 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +1 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
- package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
- package/.next/standalone/node_modules/@img/{sharp-win32-x64 → sharp-linux-x64}/package.json +46 -39
- package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
- package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
- package/.next/standalone/package.json +102 -102
- package/.next/standalone/server.js +1 -1
- package/.next/standalone/src/app/(desktop)/cortex/page.tsx +78 -75
- package/.next/standalone/src/app/api/cortex/context/route.ts +78 -78
- package/.next/standalone/src/app/api/cortex/curation/assess/route.ts +27 -0
- package/.next/standalone/src/app/api/cortex/curation/publish/route.ts +23 -0
- package/.next/standalone/src/app/api/cortex/curation/refine/route.ts +23 -0
- package/.next/standalone/src/app/api/cortex/curation/review/route.ts +29 -0
- package/.next/standalone/src/app/api/cortex/curation/seed/route.ts +23 -0
- package/.next/standalone/src/app/api/cortex/export/route.ts +40 -40
- package/.next/standalone/src/app/api/cortex/federation/pending/route.ts +20 -20
- package/.next/standalone/src/app/api/cortex/federation/resolve/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/federation/search/route.ts +35 -35
- package/.next/standalone/src/app/api/cortex/federation/teach/route.ts +76 -76
- package/.next/standalone/src/app/api/cortex/graph/edges/route.ts +112 -112
- package/.next/standalone/src/app/api/cortex/graph/entities/[id]/route.ts +73 -73
- package/.next/standalone/src/app/api/cortex/graph/entities/route.ts +75 -75
- package/.next/standalone/src/app/api/cortex/graph/populate/route.ts +203 -203
- package/.next/standalone/src/app/api/cortex/import/route.ts +75 -43
- package/.next/standalone/src/app/api/cortex/import/status/route.ts +15 -15
- package/.next/standalone/src/app/api/cortex/ingest/bootstrap/route.ts +29 -29
- package/.next/standalone/src/app/api/cortex/ingest/status/route.ts +15 -15
- package/.next/standalone/src/app/api/cortex/knowledge/[id]/route.ts +91 -91
- package/.next/standalone/src/app/api/cortex/knowledge/route.ts +93 -93
- package/.next/standalone/src/app/api/cortex/lobes/[id]/route.ts +67 -67
- package/.next/standalone/src/app/api/cortex/lobes/route.ts +22 -22
- package/.next/standalone/src/app/api/cortex/lobes/share/route.ts +80 -80
- package/.next/standalone/src/app/api/cortex/marketplace/browse/route.ts +43 -0
- package/.next/standalone/src/app/api/cortex/marketplace/preview/route.ts +46 -0
- package/.next/standalone/src/app/api/cortex/mcp/call/route.ts +11 -11
- package/.next/standalone/src/app/api/cortex/mcp/tools/route.ts +6 -6
- package/.next/standalone/src/app/api/cortex/search/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/settings/route.ts +33 -33
- package/.next/standalone/src/app/api/cortex/status/route.ts +169 -129
- package/.next/standalone/src/app/api/cortex/timeline/route.ts +42 -42
- package/.next/standalone/src/app/api/cortex/usage/route.ts +31 -31
- package/.next/standalone/src/app/api/cortex/workspace/[id]/context/route.ts +41 -41
- package/.next/standalone/src/components/cortex/constants.ts +29 -0
- package/.next/standalone/src/components/cortex/cortex-dashboard.tsx +304 -228
- package/.next/standalone/src/components/cortex/cortex-indicator.tsx +44 -44
- package/.next/standalone/src/components/cortex/cortex-panel.tsx +140 -140
- package/.next/standalone/src/components/cortex/cortex-settings.tsx +221 -221
- package/.next/standalone/src/components/cortex/curation-tab.tsx +810 -0
- package/.next/standalone/src/components/cortex/entity-detail.tsx +101 -101
- package/.next/standalone/src/components/cortex/entity-graph.tsx +382 -382
- package/.next/standalone/src/components/cortex/import-dialog.tsx +212 -0
- package/.next/standalone/src/components/cortex/injection-badge.tsx +72 -72
- package/.next/standalone/src/components/cortex/knowledge-card.tsx +109 -127
- package/.next/standalone/src/components/cortex/knowledge-tab.tsx +158 -56
- package/.next/standalone/src/components/cortex/lobe-settings.tsx +215 -199
- package/.next/standalone/src/components/cortex/marketplace-card.tsx +126 -0
- package/.next/standalone/src/components/cortex/marketplace-tab.tsx +113 -0
- package/.next/standalone/src/lib/cortex/config.ts +40 -40
- package/.next/standalone/src/lib/cortex/debug.ts +10 -10
- package/.next/standalone/src/lib/cortex/distillation/usage-store.ts +18 -18
- package/.next/standalone/src/lib/cortex/graph/resolver.ts +10 -10
- package/.next/standalone/src/lib/cortex/graph/types.ts +22 -22
- package/.next/standalone/src/lib/cortex/index.ts +56 -56
- package/.next/standalone/src/lib/cortex/ingestion/bootstrap.ts +14 -14
- package/.next/standalone/src/lib/cortex/knowledge/compat.ts +14 -14
- package/.next/standalone/src/lib/cortex/knowledge/contradiction.ts +10 -10
- package/.next/standalone/src/lib/cortex/knowledge/types.ts +67 -67
- package/.next/standalone/src/lib/cortex/lobes/config.ts +16 -16
- package/.next/standalone/src/lib/cortex/lobes/resolver.ts +8 -8
- package/.next/standalone/src/lib/cortex/lobes/shares.ts +14 -14
- package/.next/standalone/src/lib/cortex/mcp/server.ts +8 -8
- package/.next/standalone/src/lib/cortex/portability/exporter.ts +6 -6
- package/.next/standalone/src/lib/cortex/portability/importer.ts +10 -10
- package/.next/standalone/src/lib/cortex/retrieval/context-engine.ts +10 -10
- package/.next/standalone/src/lib/cortex/types.ts +39 -38
- package/.next/standalone/tsconfig.json +34 -34
- package/LICENSE +661 -661
- package/README.md +131 -131
- package/bin/cortex-hook.sh +62 -62
- package/bin/cortex-mcp.js +60 -60
- package/bin/fix-standalone-externals.js +79 -79
- package/bin/lib/auto-setup.js +110 -110
- package/bin/mdns-service.js +171 -171
- package/bin/postinstall.js +35 -35
- package/bin/setup-admin.js +195 -195
- package/bin/spaces-dev.js +208 -208
- package/bin/spaces-install.js +599 -599
- package/bin/spaces-reset-totp.js +50 -50
- package/bin/spaces-service.js +1020 -1020
- package/bin/spaces-setup.js +253 -253
- package/bin/spaces.js +776 -728
- package/bin/ssh-auth-keys.sh +68 -68
- package/bin/terminal-server.js +1649 -1646
- package/package.json +102 -102
- package/.next/standalone/.claude/settings.local.json +0 -55
- package/.next/standalone/.claude/spaces-env.json +0 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ecce08b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_7e63066a._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_a012c43b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_b4db3983._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_cd50a174._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_e7a9e2b0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_ed6c285b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_133a7c8a._.js +0 -3
- package/.next/standalone/.next/static/chunks/17d164c01fa1aaa9.js +0 -5
- package/.next/standalone/.next/static/chunks/19da759dde107f02.js +0 -1
- package/.next/standalone/.next/static/chunks/58d78765e5140dcb.js +0 -1
- package/.next/standalone/.next/static/chunks/596bd56e0ad09173.js +0 -5
- package/.next/standalone/.next/static/chunks/78dd908e70bf8c4b.js +0 -85
- package/.next/standalone/.next/static/chunks/79aacab676df80c4.js +0 -1
- package/.next/standalone/.next/static/chunks/846d6ef408f69390.js +0 -1
- package/.next/standalone/.next/static/chunks/8de5e432a2fc563a.js +0 -1
- package/.next/standalone/.next/static/chunks/9196173f0d081f2c.js +0 -1
- package/.next/standalone/.next/static/chunks/9bd8a119aaeafc9e.js +0 -1
- package/.next/standalone/.next/static/chunks/e35e863c09511a51.js +0 -1
- package/.next/standalone/.next/static/chunks/f644c11ace7a0d9d.css +0 -3
- package/.next/standalone/.spaces/cortex-context.md +0 -70
- package/.next/standalone/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64.node +0 -0
- package/.next/standalone/src/components/cortex/context-tab.tsx +0 -171
- /package/.next/standalone/.next/static/{B207XFa7QvXAYS7Sqq2_4 → 77VYbwIoyxFNr5xevTrCu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{B207XFa7QvXAYS7Sqq2_4 → 77VYbwIoyxFNr5xevTrCu}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{B207XFa7QvXAYS7Sqq2_4 → 77VYbwIoyxFNr5xevTrCu}/_ssgManifest.js +0 -0
- /package/.next/standalone/node_modules/@img/{sharp-win32-x64 → sharp-libvips-linux-x64}/versions.json +0 -0
|
@@ -1,1108 +1,1108 @@
|
|
|
1
|
-
# Cortex v2 UI Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add a dedicated `/cortex` page with interactive entity graph visualization, enhanced knowledge cards with v2 fields, and a context assembly dashboard — plus sidebar navigation and panel enhancements.
|
|
6
|
-
|
|
7
|
-
**Architecture:** New `/app/(desktop)/cortex/page.tsx` with tab navigation (Graph | Knowledge | Context | Settings). Force graph rendered via `force-graph` npm package with `nodeCanvasObject` for custom shapes. Small backend tweaks to expose `listAllEdges()`, entities/conflicts/sourceWeights in context API. Existing right panel gets "Open full view" link.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** React 19, Next.js 16, Tailwind CSS 4, force-graph, Lucide icons, TanStack React Query
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md`
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## File Structure
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
New files:
|
|
19
|
-
├── src/app/(desktop)/cortex/page.tsx — Main /cortex page with tabs
|
|
20
|
-
├── src/components/cortex/entity-graph.tsx — Force graph canvas (dynamic, ssr:false)
|
|
21
|
-
├── src/components/cortex/entity-detail.tsx — Right-side entity detail panel
|
|
22
|
-
├── src/components/cortex/context-tab.tsx — Context assembly inspector tab
|
|
23
|
-
├── src/components/cortex/knowledge-tab.tsx — Knowledge list tab (reuses cards)
|
|
24
|
-
|
|
25
|
-
Modified files:
|
|
26
|
-
├── src/components/cortex/knowledge-card.tsx — Add v2 badges + evidence bar
|
|
27
|
-
├── src/components/cortex/cortex-panel.tsx — Add "Open full view" link
|
|
28
|
-
├── src/components/layout/sidebar.tsx — Add Cortex nav item
|
|
29
|
-
├── src/lib/cortex/graph/entity-graph.ts — Add listAllEdges()
|
|
30
|
-
├── src/app/api/cortex/graph/edges/route.ts — Support all=true
|
|
31
|
-
├── src/lib/cortex/retrieval/context-engine.ts — Expose sourceWeights
|
|
32
|
-
├── src/app/api/cortex/context/route.ts — Return entities, conflicts, sourceWeights
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Chunk 1: Backend Tweaks + Enhanced Knowledge Card
|
|
38
|
-
|
|
39
|
-
### Task 1: Add listAllEdges() and edges `all=true` support
|
|
40
|
-
|
|
41
|
-
**Files:**
|
|
42
|
-
- Modify: `src/lib/cortex/graph/entity-graph.ts`
|
|
43
|
-
- Modify: `src/app/api/cortex/graph/edges/route.ts`
|
|
44
|
-
|
|
45
|
-
- [ ] **Step 1: Add listAllEdges() to EntityGraph**
|
|
46
|
-
|
|
47
|
-
Read `src/lib/cortex/graph/entity-graph.ts`, find the edge methods section, add:
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
listAllEdges(): Edge[] {
|
|
51
|
-
return (this.db.prepare('SELECT * FROM edges ORDER BY source_id').all() as any[])
|
|
52
|
-
.map(r => this.rowToEdge(r));
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
- [ ] **Step 2: Update edges route GET handler**
|
|
57
|
-
|
|
58
|
-
Read `src/app/api/cortex/graph/edges/route.ts`. At the top of the GET handler, before the `from`/`to` check, add:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
const all = url.searchParams.get('all');
|
|
62
|
-
if (all === 'true') {
|
|
63
|
-
const edges = cortex.graph.listAllEdges();
|
|
64
|
-
return NextResponse.json({ edges });
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
- [ ] **Step 3: Commit**
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
git add src/lib/cortex/graph/entity-graph.ts src/app/api/cortex/graph/edges/route.ts
|
|
72
|
-
git commit -m "feat(cortex): add listAllEdges() and edges all=true endpoint"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
### Task 2: Expose sourceWeights in context API
|
|
78
|
-
|
|
79
|
-
**Files:**
|
|
80
|
-
- Modify: `src/lib/cortex/retrieval/context-engine.ts`
|
|
81
|
-
- Modify: `src/app/api/cortex/context/route.ts`
|
|
82
|
-
|
|
83
|
-
- [ ] **Step 1: Add sourceWeights to AssemblyResult**
|
|
84
|
-
|
|
85
|
-
Read `src/lib/cortex/retrieval/context-engine.ts`. Find the `AssemblyResult` interface and add:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
sourceWeights: Array<{ layerKey: string; scopeLevel: string; weight: number }>;
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Find the `assemble()` method. After `computeSourceWeights()` is called (the `sources` variable), map it into the result:
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// In the return object, add:
|
|
95
|
-
sourceWeights: sources.map(s => ({
|
|
96
|
-
layerKey: s.layerKey,
|
|
97
|
-
scopeLevel: s.layerKey === 'personal' ? 'personal' : s.layerKey.startsWith('workspace') ? 'team' : 'organization',
|
|
98
|
-
weight: s.weight,
|
|
99
|
-
})),
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
- [ ] **Step 2: Update context route response**
|
|
103
|
-
|
|
104
|
-
Read `src/app/api/cortex/context/route.ts`. In the `NextResponse.json()` call, change:
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
// From:
|
|
108
|
-
conflicts: result.conflicts.length,
|
|
109
|
-
// To:
|
|
110
|
-
entities: result.entities,
|
|
111
|
-
conflicts: result.conflicts,
|
|
112
|
-
sourceWeights: result.sourceWeights,
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
- [ ] **Step 3: Commit**
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
git add src/lib/cortex/retrieval/context-engine.ts src/app/api/cortex/context/route.ts
|
|
119
|
-
git commit -m "feat(cortex): expose entities, conflicts, sourceWeights in context API"
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
### Task 3: Enhanced knowledge card
|
|
125
|
-
|
|
126
|
-
**Files:**
|
|
127
|
-
- Modify: `src/components/cortex/knowledge-card.tsx`
|
|
128
|
-
|
|
129
|
-
- [ ] **Step 1: Read the current knowledge-card.tsx**
|
|
130
|
-
|
|
131
|
-
- [ ] **Step 2: Extend the props interface and add v2 display**
|
|
132
|
-
|
|
133
|
-
Replace the entire file. The changes:
|
|
134
|
-
- Extend `unit` props with optional v2 fields
|
|
135
|
-
- Add sensitivity badge (color-coded)
|
|
136
|
-
- Add scope badge
|
|
137
|
-
- Add attribution row (creator, source, corroborations)
|
|
138
|
-
- Replace confidence bar with evidence bar when available
|
|
139
|
-
- Add conflict indicator
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
'use client';
|
|
143
|
-
|
|
144
|
-
import { Trash2, AlertTriangle } from 'lucide-react';
|
|
145
|
-
import { api } from '@/lib/api';
|
|
146
|
-
|
|
147
|
-
const TYPE_COLORS: Record<string, string> = {
|
|
148
|
-
decision: 'bg-blue-500/20 text-blue-400',
|
|
149
|
-
preference: 'bg-pink-500/20 text-pink-400',
|
|
150
|
-
pattern: 'bg-green-500/20 text-green-400',
|
|
151
|
-
error_fix: 'bg-amber-500/20 text-amber-400',
|
|
152
|
-
context: 'bg-gray-500/20 text-gray-400',
|
|
153
|
-
code_pattern: 'bg-cyan-500/20 text-cyan-400',
|
|
154
|
-
command: 'bg-orange-500/20 text-orange-400',
|
|
155
|
-
conversation: 'bg-slate-500/20 text-slate-400',
|
|
156
|
-
summary: 'bg-violet-500/20 text-violet-400',
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const SENSITIVITY_COLORS: Record<string, string> = {
|
|
160
|
-
public: 'bg-green-500/20 text-green-400',
|
|
161
|
-
internal: 'bg-indigo-500/20 text-indigo-400',
|
|
162
|
-
restricted: 'bg-amber-500/20 text-amber-400',
|
|
163
|
-
confidential: 'bg-red-500/20 text-red-400',
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
interface KnowledgeCardProps {
|
|
167
|
-
unit: {
|
|
168
|
-
id: string;
|
|
169
|
-
text: string;
|
|
170
|
-
type: string;
|
|
171
|
-
confidence: number;
|
|
172
|
-
created: string;
|
|
173
|
-
session_id?: string | null;
|
|
174
|
-
layer: string;
|
|
175
|
-
stale_score?: number;
|
|
176
|
-
// v2 fields
|
|
177
|
-
scope?: { level: string; entity_id: string };
|
|
178
|
-
sensitivity?: string;
|
|
179
|
-
evidence_score?: number;
|
|
180
|
-
corroborations?: number;
|
|
181
|
-
contradiction_refs?: string[];
|
|
182
|
-
origin?: { source_type: string; source_ref: string; creator_entity_id: string };
|
|
183
|
-
};
|
|
184
|
-
onDelete?: (id: string) => void;
|
|
185
|
-
compact?: boolean;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function KnowledgeCard({ unit, onDelete, compact }: KnowledgeCardProps) {
|
|
189
|
-
const colorClass = TYPE_COLORS[unit.type] || TYPE_COLORS.context;
|
|
190
|
-
const age = getRelativeAge(unit.created);
|
|
191
|
-
const hasConflicts = (unit.contradiction_refs?.length ?? 0) > 0;
|
|
192
|
-
|
|
193
|
-
// Use evidence_score if available, else confidence
|
|
194
|
-
const score = unit.evidence_score ?? unit.confidence;
|
|
195
|
-
const scoreLabel = unit.evidence_score != null
|
|
196
|
-
? `${score.toFixed(2)} evidence`
|
|
197
|
-
: `${Math.round(score * 100)}%`;
|
|
198
|
-
|
|
199
|
-
const handleDelete = async () => {
|
|
200
|
-
await fetch(api(`/api/cortex/knowledge/${unit.id}`), { method: 'DELETE' });
|
|
201
|
-
onDelete?.(unit.id);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<div className="group border border-white/5 rounded-lg p-3 hover:border-white/10 transition-colors">
|
|
206
|
-
{/* Badge row */}
|
|
207
|
-
<div className="flex items-start justify-between gap-2">
|
|
208
|
-
<div className="flex items-center gap-1 flex-wrap">
|
|
209
|
-
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${colorClass}`}>
|
|
210
|
-
{unit.type.replace('_', ' ')}
|
|
211
|
-
</span>
|
|
212
|
-
{unit.sensitivity && (
|
|
213
|
-
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${SENSITIVITY_COLORS[unit.sensitivity] || SENSITIVITY_COLORS.internal}`}>
|
|
214
|
-
{unit.sensitivity}
|
|
215
|
-
</span>
|
|
216
|
-
)}
|
|
217
|
-
{unit.scope && (
|
|
218
|
-
<span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-purple-500/10 text-purple-400">
|
|
219
|
-
{unit.scope.level}
|
|
220
|
-
</span>
|
|
221
|
-
)}
|
|
222
|
-
{(unit.stale_score ?? 0) > 0.3 && (
|
|
223
|
-
<span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-500/20 text-amber-400">
|
|
224
|
-
stale
|
|
225
|
-
</span>
|
|
226
|
-
)}
|
|
227
|
-
{hasConflicts && (
|
|
228
|
-
<span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-500/20 text-amber-400 flex items-center gap-0.5">
|
|
229
|
-
<AlertTriangle className="w-2.5 h-2.5" />
|
|
230
|
-
contested
|
|
231
|
-
</span>
|
|
232
|
-
)}
|
|
233
|
-
</div>
|
|
234
|
-
<div className="flex items-center gap-2 text-[10px] text-gray-500 shrink-0">
|
|
235
|
-
<span>{age}</span>
|
|
236
|
-
{onDelete && (
|
|
237
|
-
<button
|
|
238
|
-
onClick={handleDelete}
|
|
239
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity text-red-400 hover:text-red-300"
|
|
240
|
-
>
|
|
241
|
-
<Trash2 className="w-3 h-3" />
|
|
242
|
-
</button>
|
|
243
|
-
)}
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
{/* Text */}
|
|
248
|
-
<p className="text-xs text-gray-300 mt-1.5 leading-relaxed line-clamp-3">{unit.text}</p>
|
|
249
|
-
|
|
250
|
-
{/* Attribution row (v2) */}
|
|
251
|
-
{!compact && unit.origin && (
|
|
252
|
-
<div className="flex items-center gap-1.5 mt-1.5 text-[10px] text-gray-500 flex-wrap">
|
|
253
|
-
<span>by {unit.origin.creator_entity_id.replace('person-', '')}</span>
|
|
254
|
-
<span>·</span>
|
|
255
|
-
<span>via {unit.origin.source_type.replace('_', ' ')}</span>
|
|
256
|
-
{(unit.corroborations ?? 0) > 0 && (
|
|
257
|
-
<>
|
|
258
|
-
<span>·</span>
|
|
259
|
-
<span className="text-green-400">{unit.corroborations} corr.</span>
|
|
260
|
-
</>
|
|
261
|
-
)}
|
|
262
|
-
</div>
|
|
263
|
-
)}
|
|
264
|
-
|
|
265
|
-
{/* Evidence/confidence bar */}
|
|
266
|
-
<div className="flex items-center gap-2 mt-2">
|
|
267
|
-
<div className="flex-1 h-1 bg-white/5 rounded-full overflow-hidden">
|
|
268
|
-
<div
|
|
269
|
-
className="h-full bg-purple-500/50 rounded-full"
|
|
270
|
-
style={{ width: `${Math.round(score * 100)}%` }}
|
|
271
|
-
/>
|
|
272
|
-
</div>
|
|
273
|
-
<span className="text-[10px] text-gray-500 tabular-nums">{scoreLabel}</span>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function getRelativeAge(iso: string): string {
|
|
280
|
-
const diff = Date.now() - new Date(iso).getTime();
|
|
281
|
-
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
282
|
-
if (days === 0) return 'today';
|
|
283
|
-
if (days === 1) return '1d ago';
|
|
284
|
-
if (days < 30) return `${days}d ago`;
|
|
285
|
-
if (days < 365) return `${Math.floor(days / 30)}mo ago`;
|
|
286
|
-
return `${Math.floor(days / 365)}y ago`;
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
- [ ] **Step 3: Commit**
|
|
291
|
-
|
|
292
|
-
```bash
|
|
293
|
-
git add src/components/cortex/knowledge-card.tsx
|
|
294
|
-
git commit -m "feat(cortex): enhance knowledge card with v2 fields display"
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## Chunk 2: Cortex Page + Sidebar
|
|
300
|
-
|
|
301
|
-
### Task 4: Sidebar navigation
|
|
302
|
-
|
|
303
|
-
**Files:**
|
|
304
|
-
- Modify: `src/components/layout/sidebar.tsx`
|
|
305
|
-
|
|
306
|
-
- [ ] **Step 1: Read sidebar.tsx**
|
|
307
|
-
|
|
308
|
-
- [ ] **Step 2: Add Cortex to nav array and imports**
|
|
309
|
-
|
|
310
|
-
Add `Brain` to the lucide-react import. Add a Cortex entry to the `nav` array (after Network, before Settings):
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
// Add to imports:
|
|
314
|
-
import { ..., Brain } from 'lucide-react';
|
|
315
|
-
|
|
316
|
-
// Add to nav array (before Settings):
|
|
317
|
-
{ href: '/cortex', label: 'Cortex', icon: Brain },
|
|
318
|
-
|
|
319
|
-
// Add to routeNames:
|
|
320
|
-
'/cortex': 'cortex',
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
In the nav filter, gate it like Network:
|
|
324
|
-
```typescript
|
|
325
|
-
if (href === '/cortex') return hasCortex;
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
- [ ] **Step 3: Commit**
|
|
329
|
-
|
|
330
|
-
```bash
|
|
331
|
-
git add src/components/layout/sidebar.tsx
|
|
332
|
-
git commit -m "feat(cortex): add Cortex nav item to sidebar"
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
---
|
|
336
|
-
|
|
337
|
-
### Task 5: Cortex page with tabs
|
|
338
|
-
|
|
339
|
-
**Files:**
|
|
340
|
-
- Create: `src/app/(desktop)/cortex/page.tsx`
|
|
341
|
-
|
|
342
|
-
- [ ] **Step 1: Create the page**
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
'use client';
|
|
346
|
-
|
|
347
|
-
import { useState, useEffect } from 'react';
|
|
348
|
-
import dynamic from 'next/dynamic';
|
|
349
|
-
import { api } from '@/lib/api';
|
|
350
|
-
import { KnowledgeTab } from '@/components/cortex/knowledge-tab';
|
|
351
|
-
import { ContextTab } from '@/components/cortex/context-tab';
|
|
352
|
-
import { CortexSettings } from '@/components/cortex/cortex-settings';
|
|
353
|
-
|
|
354
|
-
const EntityGraphView = dynamic(
|
|
355
|
-
() => import('@/components/cortex/entity-graph').then(m => ({ default: m.EntityGraphView })),
|
|
356
|
-
{ ssr: false, loading: () => <div className="flex-1 flex items-center justify-center text-gray-500 text-sm">Loading graph...</div> }
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
type Tab = 'graph' | 'knowledge' | 'context' | 'settings';
|
|
360
|
-
|
|
361
|
-
export default function CortexPage() {
|
|
362
|
-
const [tab, setTab] = useState<Tab>('graph');
|
|
363
|
-
const [stats, setStats] = useState<any>(null);
|
|
364
|
-
|
|
365
|
-
useEffect(() => {
|
|
366
|
-
fetch(api('/api/cortex/status'))
|
|
367
|
-
.then(r => r.json())
|
|
368
|
-
.then(setStats)
|
|
369
|
-
.catch(() => {});
|
|
370
|
-
}, []);
|
|
371
|
-
|
|
372
|
-
const tabs: { key: Tab; label: string }[] = [
|
|
373
|
-
{ key: 'graph', label: 'Graph' },
|
|
374
|
-
{ key: 'knowledge', label: 'Knowledge' },
|
|
375
|
-
{ key: 'context', label: 'Context' },
|
|
376
|
-
{ key: 'settings', label: 'Settings' },
|
|
377
|
-
];
|
|
378
|
-
|
|
379
|
-
const totalKnowledge = stats
|
|
380
|
-
? Object.values(stats.layers || {}).reduce((sum: number, l: any) => sum + (l.count || 0), 0)
|
|
381
|
-
: 0;
|
|
382
|
-
|
|
383
|
-
return (
|
|
384
|
-
<div className="flex flex-col h-screen bg-gray-950">
|
|
385
|
-
{/* Tab bar */}
|
|
386
|
-
<div className="flex items-center border-b border-white/5 px-4 shrink-0">
|
|
387
|
-
<div className="flex">
|
|
388
|
-
{tabs.map(t => (
|
|
389
|
-
<button
|
|
390
|
-
key={t.key}
|
|
391
|
-
onClick={() => setTab(t.key)}
|
|
392
|
-
className={`px-5 py-3 text-sm font-medium transition-colors border-b-2 ${
|
|
393
|
-
tab === t.key
|
|
394
|
-
? 'text-purple-400 border-purple-400'
|
|
395
|
-
: 'text-gray-500 border-transparent hover:text-gray-300'
|
|
396
|
-
}`}
|
|
397
|
-
>
|
|
398
|
-
{t.label}
|
|
399
|
-
</button>
|
|
400
|
-
))}
|
|
401
|
-
</div>
|
|
402
|
-
<div className="ml-auto text-xs text-gray-600">
|
|
403
|
-
{totalKnowledge} knowledge units
|
|
404
|
-
</div>
|
|
405
|
-
</div>
|
|
406
|
-
|
|
407
|
-
{/* Tab content */}
|
|
408
|
-
<div className="flex-1 overflow-hidden">
|
|
409
|
-
{tab === 'graph' && <EntityGraphView />}
|
|
410
|
-
{tab === 'knowledge' && <KnowledgeTab />}
|
|
411
|
-
{tab === 'context' && <ContextTab />}
|
|
412
|
-
{tab === 'settings' && (
|
|
413
|
-
<div className="p-6 max-w-2xl">
|
|
414
|
-
<CortexSettings />
|
|
415
|
-
</div>
|
|
416
|
-
)}
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
- [ ] **Step 2: Commit**
|
|
424
|
-
|
|
425
|
-
```bash
|
|
426
|
-
git add src/app/(desktop)/cortex/page.tsx
|
|
427
|
-
git commit -m "feat(cortex): add /cortex page with tab navigation"
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
|
-
### Task 6: Panel "Open full view" link
|
|
433
|
-
|
|
434
|
-
**Files:**
|
|
435
|
-
- Modify: `src/components/cortex/cortex-panel.tsx`
|
|
436
|
-
|
|
437
|
-
- [ ] **Step 1: Add link to panel header**
|
|
438
|
-
|
|
439
|
-
Read `src/components/cortex/cortex-panel.tsx`. In the header div (between the "Cortex" heading and the close button), add:
|
|
440
|
-
|
|
441
|
-
```typescript
|
|
442
|
-
import Link from 'next/link';
|
|
443
|
-
import { ExternalLink } from 'lucide-react';
|
|
444
|
-
|
|
445
|
-
// In the header, after the h2:
|
|
446
|
-
<Link
|
|
447
|
-
href="/cortex"
|
|
448
|
-
className="text-[10px] text-purple-400 hover:text-purple-300 flex items-center gap-1"
|
|
449
|
-
onClick={onClose}
|
|
450
|
-
>
|
|
451
|
-
Full view <ExternalLink className="w-2.5 h-2.5" />
|
|
452
|
-
</Link>
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
- [ ] **Step 2: Commit**
|
|
456
|
-
|
|
457
|
-
```bash
|
|
458
|
-
git add src/components/cortex/cortex-panel.tsx
|
|
459
|
-
git commit -m "feat(cortex): add 'Full view' link to cortex panel"
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
## Chunk 3: Graph + Detail Components
|
|
465
|
-
|
|
466
|
-
### Task 7: Entity graph canvas component
|
|
467
|
-
|
|
468
|
-
**Files:**
|
|
469
|
-
- Create: `src/components/cortex/entity-graph.tsx`
|
|
470
|
-
|
|
471
|
-
- [ ] **Step 1: Install force-graph**
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
npm install force-graph
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
- [ ] **Step 2: Create the graph component**
|
|
478
|
-
|
|
479
|
-
```typescript
|
|
480
|
-
'use client';
|
|
481
|
-
|
|
482
|
-
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
483
|
-
import ForceGraph2D from 'force-graph';
|
|
484
|
-
import { api } from '@/lib/api';
|
|
485
|
-
import { EntityDetail } from './entity-detail';
|
|
486
|
-
|
|
487
|
-
const NODE_COLORS: Record<string, string> = {
|
|
488
|
-
person: '#7c3aed',
|
|
489
|
-
team: '#10b981',
|
|
490
|
-
project: '#10b981',
|
|
491
|
-
department: '#3b82f6',
|
|
492
|
-
organization: '#3b82f6',
|
|
493
|
-
system: '#f59e0b',
|
|
494
|
-
module: '#f59e0b',
|
|
495
|
-
topic: '#06b6d4',
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
interface GraphNode {
|
|
499
|
-
id: string;
|
|
500
|
-
name: string;
|
|
501
|
-
type: string;
|
|
502
|
-
metadata: Record<string, unknown>;
|
|
503
|
-
// force-graph adds x, y, vx, vy
|
|
504
|
-
x?: number;
|
|
505
|
-
y?: number;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
interface GraphLink {
|
|
509
|
-
source: string;
|
|
510
|
-
target: string;
|
|
511
|
-
relation: string;
|
|
512
|
-
weight: number;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
export function EntityGraphView() {
|
|
516
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
517
|
-
const graphRef = useRef<any>(null);
|
|
518
|
-
const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
|
|
519
|
-
const [graphData, setGraphData] = useState<{ nodes: GraphNode[]; links: GraphLink[] }>({ nodes: [], links: [] });
|
|
520
|
-
|
|
521
|
-
// Fetch graph data
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
Promise.all([
|
|
524
|
-
fetch(api('/api/cortex/graph/entities')).then(r => r.json()),
|
|
525
|
-
fetch(api('/api/cortex/graph/edges?all=true')).then(r => r.json()),
|
|
526
|
-
]).then(([entData, edgeData]) => {
|
|
527
|
-
const nodes: GraphNode[] = (entData.entities || []).map((e: any) => ({
|
|
528
|
-
id: e.id,
|
|
529
|
-
name: e.name,
|
|
530
|
-
type: e.type,
|
|
531
|
-
metadata: e.metadata || {},
|
|
532
|
-
}));
|
|
533
|
-
const nodeIds = new Set(nodes.map(n => n.id));
|
|
534
|
-
const links: GraphLink[] = (edgeData.edges || [])
|
|
535
|
-
.filter((e: any) => nodeIds.has(e.source_id) && nodeIds.has(e.target_id))
|
|
536
|
-
.map((e: any) => ({
|
|
537
|
-
source: e.source_id,
|
|
538
|
-
target: e.target_id,
|
|
539
|
-
relation: e.relation,
|
|
540
|
-
weight: e.weight,
|
|
541
|
-
}));
|
|
542
|
-
setGraphData({ nodes, links });
|
|
543
|
-
}).catch(() => {});
|
|
544
|
-
}, []);
|
|
545
|
-
|
|
546
|
-
// Initialize force-graph
|
|
547
|
-
useEffect(() => {
|
|
548
|
-
if (!containerRef.current || graphData.nodes.length === 0) return;
|
|
549
|
-
|
|
550
|
-
const width = containerRef.current.clientWidth;
|
|
551
|
-
const height = containerRef.current.clientHeight;
|
|
552
|
-
|
|
553
|
-
const graph = ForceGraph2D()(containerRef.current)
|
|
554
|
-
.width(width)
|
|
555
|
-
.height(height)
|
|
556
|
-
.graphData(graphData)
|
|
557
|
-
.nodeId('id')
|
|
558
|
-
.nodeLabel((node: any) => `${node.name} (${node.type})`)
|
|
559
|
-
.nodeCanvasObject((node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
|
|
560
|
-
const color = NODE_COLORS[node.type] || '#666';
|
|
561
|
-
const size = node.type === 'person' ? 8 : node.type === 'topic' ? 5 : 6;
|
|
562
|
-
const x = node.x ?? 0;
|
|
563
|
-
const y = node.y ?? 0;
|
|
564
|
-
|
|
565
|
-
ctx.beginPath();
|
|
566
|
-
if (node.type === 'system' || node.type === 'module') {
|
|
567
|
-
// Diamond
|
|
568
|
-
ctx.moveTo(x, y - size);
|
|
569
|
-
ctx.lineTo(x + size, y);
|
|
570
|
-
ctx.lineTo(x, y + size);
|
|
571
|
-
ctx.lineTo(x - size, y);
|
|
572
|
-
ctx.closePath();
|
|
573
|
-
} else if (node.type === 'team' || node.type === 'project' || node.type === 'department' || node.type === 'organization') {
|
|
574
|
-
// Rounded rect
|
|
575
|
-
const w = size * 2;
|
|
576
|
-
const h = size * 1.5;
|
|
577
|
-
const r = 2;
|
|
578
|
-
ctx.roundRect(x - w / 2, y - h / 2, w, h, r);
|
|
579
|
-
} else {
|
|
580
|
-
// Circle (person, topic)
|
|
581
|
-
ctx.arc(x, y, size, 0, 2 * Math.PI);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
ctx.fillStyle = color + '33';
|
|
585
|
-
ctx.fill();
|
|
586
|
-
ctx.strokeStyle = color;
|
|
587
|
-
ctx.lineWidth = 1.5 / globalScale;
|
|
588
|
-
ctx.stroke();
|
|
589
|
-
|
|
590
|
-
// Label
|
|
591
|
-
const fontSize = Math.max(10 / globalScale, 3);
|
|
592
|
-
ctx.font = `${fontSize}px sans-serif`;
|
|
593
|
-
ctx.textAlign = 'center';
|
|
594
|
-
ctx.textBaseline = 'middle';
|
|
595
|
-
ctx.fillStyle = color;
|
|
596
|
-
ctx.fillText(node.name, x, y + size + fontSize);
|
|
597
|
-
})
|
|
598
|
-
.nodePointerAreaPaint((node: any, color: string, ctx: CanvasRenderingContext2D) => {
|
|
599
|
-
const size = 10;
|
|
600
|
-
ctx.fillStyle = color;
|
|
601
|
-
ctx.beginPath();
|
|
602
|
-
ctx.arc(node.x ?? 0, node.y ?? 0, size, 0, 2 * Math.PI);
|
|
603
|
-
ctx.fill();
|
|
604
|
-
})
|
|
605
|
-
.linkColor(() => 'rgba(124, 58, 237, 0.2)')
|
|
606
|
-
.linkWidth((link: any) => Math.max(0.5, link.weight * 2))
|
|
607
|
-
.onNodeClick((node: any) => setSelectedNode(node))
|
|
608
|
-
.onBackgroundClick(() => setSelectedNode(null))
|
|
609
|
-
.backgroundColor('#06060c');
|
|
610
|
-
|
|
611
|
-
graphRef.current = graph;
|
|
612
|
-
|
|
613
|
-
const handleResize = () => {
|
|
614
|
-
if (!containerRef.current) return;
|
|
615
|
-
graph.width(containerRef.current.clientWidth);
|
|
616
|
-
graph.height(containerRef.current.clientHeight);
|
|
617
|
-
};
|
|
618
|
-
window.addEventListener('resize', handleResize);
|
|
619
|
-
|
|
620
|
-
return () => {
|
|
621
|
-
window.removeEventListener('resize', handleResize);
|
|
622
|
-
graph._destructor?.();
|
|
623
|
-
};
|
|
624
|
-
}, [graphData]);
|
|
625
|
-
|
|
626
|
-
const handleRecenter = useCallback(() => {
|
|
627
|
-
graphRef.current?.zoomToFit(400, 40);
|
|
628
|
-
}, []);
|
|
629
|
-
|
|
630
|
-
return (
|
|
631
|
-
<div className="flex h-full">
|
|
632
|
-
{/* Graph canvas */}
|
|
633
|
-
<div className="flex-1 relative">
|
|
634
|
-
<div ref={containerRef} className="w-full h-full" />
|
|
635
|
-
|
|
636
|
-
{/* Controls overlay */}
|
|
637
|
-
<div className="absolute bottom-3 left-3 flex gap-2">
|
|
638
|
-
<button
|
|
639
|
-
onClick={handleRecenter}
|
|
640
|
-
className="px-3 py-1.5 text-xs bg-gray-900/80 border border-white/10 rounded text-gray-400 hover:text-gray-200"
|
|
641
|
-
>
|
|
642
|
-
Recenter
|
|
643
|
-
</button>
|
|
644
|
-
</div>
|
|
645
|
-
|
|
646
|
-
{/* Legend overlay */}
|
|
647
|
-
<div className="absolute top-3 right-3 bg-gray-950/90 border border-white/10 rounded-lg p-3 text-[10px] space-y-1">
|
|
648
|
-
{Object.entries(NODE_COLORS).filter(([type]) =>
|
|
649
|
-
['person', 'team', 'system', 'topic'].includes(type)
|
|
650
|
-
).map(([type, color]) => (
|
|
651
|
-
<div key={type} className="flex items-center gap-2">
|
|
652
|
-
<span style={{ color }} className="text-sm">
|
|
653
|
-
{type === 'system' ? '◆' : type === 'team' ? '■' : '●'}
|
|
654
|
-
</span>
|
|
655
|
-
<span className="text-gray-400 capitalize">{type}</span>
|
|
656
|
-
</div>
|
|
657
|
-
))}
|
|
658
|
-
</div>
|
|
659
|
-
|
|
660
|
-
{graphData.nodes.length === 0 && (
|
|
661
|
-
<div className="absolute inset-0 flex items-center justify-center text-gray-600 text-sm">
|
|
662
|
-
No entities yet. Add entities via the API to see the graph.
|
|
663
|
-
</div>
|
|
664
|
-
)}
|
|
665
|
-
</div>
|
|
666
|
-
|
|
667
|
-
{/* Detail panel */}
|
|
668
|
-
<div className="w-72 border-l border-white/5 bg-gray-950 overflow-y-auto">
|
|
669
|
-
<EntityDetail
|
|
670
|
-
node={selectedNode}
|
|
671
|
-
onClose={() => setSelectedNode(null)}
|
|
672
|
-
/>
|
|
673
|
-
</div>
|
|
674
|
-
</div>
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
- [ ] **Step 3: Commit**
|
|
680
|
-
|
|
681
|
-
```bash
|
|
682
|
-
git add src/components/cortex/entity-graph.tsx
|
|
683
|
-
git commit -m "feat(cortex): add force-graph entity visualization component"
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
### Task 8: Entity detail panel
|
|
689
|
-
|
|
690
|
-
**Files:**
|
|
691
|
-
- Create: `src/components/cortex/entity-detail.tsx`
|
|
692
|
-
|
|
693
|
-
- [ ] **Step 1: Create the component**
|
|
694
|
-
|
|
695
|
-
```typescript
|
|
696
|
-
'use client';
|
|
697
|
-
|
|
698
|
-
import { useState, useEffect } from 'react';
|
|
699
|
-
import { api } from '@/lib/api';
|
|
700
|
-
|
|
701
|
-
const RELATION_COLORS: Record<string, string> = {
|
|
702
|
-
member_of: 'text-purple-400',
|
|
703
|
-
expert_in: 'text-cyan-400',
|
|
704
|
-
owns: 'text-green-400',
|
|
705
|
-
contains: 'text-green-400',
|
|
706
|
-
part_of: 'text-blue-400',
|
|
707
|
-
works_on: 'text-purple-400',
|
|
708
|
-
touches: 'text-amber-400',
|
|
709
|
-
depends_on: 'text-red-400',
|
|
710
|
-
relates_to: 'text-cyan-400',
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
interface EntityDetailProps {
|
|
714
|
-
node: { id: string; name: string; type: string; metadata: Record<string, unknown> } | null;
|
|
715
|
-
onClose: () => void;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
export function EntityDetail({ node, onClose }: EntityDetailProps) {
|
|
719
|
-
const [edges, setEdges] = useState<any[]>([]);
|
|
720
|
-
|
|
721
|
-
useEffect(() => {
|
|
722
|
-
if (!node) { setEdges([]); return; }
|
|
723
|
-
|
|
724
|
-
Promise.all([
|
|
725
|
-
fetch(api(`/api/cortex/graph/edges?from=${node.id}`)).then(r => r.json()),
|
|
726
|
-
fetch(api(`/api/cortex/graph/edges?to=${node.id}`)).then(r => r.json()),
|
|
727
|
-
]).then(([fromData, toData]) => {
|
|
728
|
-
const allEdges = [
|
|
729
|
-
...(fromData.edges || []).map((e: any) => ({ ...e, direction: 'out' })),
|
|
730
|
-
...(toData.edges || []).map((e: any) => ({ ...e, direction: 'in' })),
|
|
731
|
-
];
|
|
732
|
-
setEdges(allEdges);
|
|
733
|
-
}).catch(() => {});
|
|
734
|
-
}, [node?.id]);
|
|
735
|
-
|
|
736
|
-
if (!node) {
|
|
737
|
-
return (
|
|
738
|
-
<div className="p-4 text-center text-gray-600 text-xs mt-8">
|
|
739
|
-
Click a node to see details
|
|
740
|
-
</div>
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
const firstLetter = node.name.charAt(0).toUpperCase();
|
|
745
|
-
const color = node.type === 'person' ? '#7c3aed'
|
|
746
|
-
: node.type === 'system' ? '#f59e0b'
|
|
747
|
-
: node.type === 'topic' ? '#06b6d4'
|
|
748
|
-
: '#10b981';
|
|
749
|
-
|
|
750
|
-
return (
|
|
751
|
-
<div className="p-4">
|
|
752
|
-
{/* Header */}
|
|
753
|
-
<div className="flex items-center gap-3 mb-4">
|
|
754
|
-
<div
|
|
755
|
-
className="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
|
|
756
|
-
style={{ backgroundColor: color + '33', color, border: `2px solid ${color}` }}
|
|
757
|
-
>
|
|
758
|
-
{firstLetter}
|
|
759
|
-
</div>
|
|
760
|
-
<div>
|
|
761
|
-
<div className="text-sm font-medium text-gray-200">{node.name}</div>
|
|
762
|
-
<div className="text-[10px] text-gray-500">{node.type}</div>
|
|
763
|
-
</div>
|
|
764
|
-
</div>
|
|
765
|
-
|
|
766
|
-
{/* Metadata */}
|
|
767
|
-
{Object.keys(node.metadata).length > 0 && (
|
|
768
|
-
<div className="mb-4">
|
|
769
|
-
<div className="text-[10px] text-gray-600 uppercase tracking-wider mb-1">Metadata</div>
|
|
770
|
-
{Object.entries(node.metadata).map(([k, v]) => (
|
|
771
|
-
<div key={k} className="text-[11px] text-gray-400">
|
|
772
|
-
<span className="text-gray-600">{k}:</span> {String(v)}
|
|
773
|
-
</div>
|
|
774
|
-
))}
|
|
775
|
-
</div>
|
|
776
|
-
)}
|
|
777
|
-
|
|
778
|
-
{/* Relationships */}
|
|
779
|
-
<div className="text-[10px] text-gray-600 uppercase tracking-wider mb-2">
|
|
780
|
-
Relationships ({edges.length})
|
|
781
|
-
</div>
|
|
782
|
-
<div className="space-y-1 mb-4">
|
|
783
|
-
{edges.map((e, i) => {
|
|
784
|
-
const relColor = RELATION_COLORS[e.relation] || 'text-gray-400';
|
|
785
|
-
const target = e.direction === 'out' ? e.target_id : e.source_id;
|
|
786
|
-
const arrow = e.direction === 'out' ? '→' : '←';
|
|
787
|
-
return (
|
|
788
|
-
<div key={i} className="flex items-center gap-1.5 text-[11px] bg-white/[0.02] rounded px-2 py-1.5">
|
|
789
|
-
<span className={relColor}>{e.relation}</span>
|
|
790
|
-
<span className="text-gray-600">{arrow}</span>
|
|
791
|
-
<span className="text-gray-300 truncate">{target.replace(/^(person|team|system|topic|project|department|organization|module)-/, '')}</span>
|
|
792
|
-
{e.weight < 1 && (
|
|
793
|
-
<span className="text-gray-600 ml-auto text-[9px]">{e.weight.toFixed(2)}</span>
|
|
794
|
-
)}
|
|
795
|
-
</div>
|
|
796
|
-
);
|
|
797
|
-
})}
|
|
798
|
-
{edges.length === 0 && (
|
|
799
|
-
<div className="text-[11px] text-gray-600 py-2">No relationships</div>
|
|
800
|
-
)}
|
|
801
|
-
</div>
|
|
802
|
-
</div>
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
- [ ] **Step 2: Commit**
|
|
808
|
-
|
|
809
|
-
```bash
|
|
810
|
-
git add src/components/cortex/entity-detail.tsx
|
|
811
|
-
git commit -m "feat(cortex): add entity detail panel for graph view"
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
---
|
|
815
|
-
|
|
816
|
-
## Chunk 4: Knowledge Tab, Context Tab, Final Wiring
|
|
817
|
-
|
|
818
|
-
### Task 9: Knowledge tab component
|
|
819
|
-
|
|
820
|
-
**Files:**
|
|
821
|
-
- Create: `src/components/cortex/knowledge-tab.tsx`
|
|
822
|
-
|
|
823
|
-
- [ ] **Step 1: Create the component**
|
|
824
|
-
|
|
825
|
-
A search + list view that reuses `KnowledgeCard`. Fetches from `/api/cortex/search`.
|
|
826
|
-
|
|
827
|
-
```typescript
|
|
828
|
-
'use client';
|
|
829
|
-
|
|
830
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
831
|
-
import { Search } from 'lucide-react';
|
|
832
|
-
import { api } from '@/lib/api';
|
|
833
|
-
import { KnowledgeCard } from './knowledge-card';
|
|
834
|
-
|
|
835
|
-
export function KnowledgeTab() {
|
|
836
|
-
const [query, setQuery] = useState('');
|
|
837
|
-
const [results, setResults] = useState<any[]>([]);
|
|
838
|
-
const [loading, setLoading] = useState(false);
|
|
839
|
-
|
|
840
|
-
const fetchResults = useCallback(async (q?: string) => {
|
|
841
|
-
setLoading(true);
|
|
842
|
-
try {
|
|
843
|
-
const params = new URLSearchParams({ limit: '30' });
|
|
844
|
-
if (q) params.set('q', q);
|
|
845
|
-
const res = await fetch(api(`/api/cortex/search?${params}`));
|
|
846
|
-
if (res.ok) setResults((await res.json()).results || []);
|
|
847
|
-
} catch {}
|
|
848
|
-
setLoading(false);
|
|
849
|
-
}, []);
|
|
850
|
-
|
|
851
|
-
useEffect(() => { fetchResults(); }, [fetchResults]);
|
|
852
|
-
|
|
853
|
-
const handleSearch = () => fetchResults(query.trim() || undefined);
|
|
854
|
-
const handleDelete = (id: string) => setResults(prev => prev.filter(r => r.id !== id));
|
|
855
|
-
|
|
856
|
-
return (
|
|
857
|
-
<div className="flex flex-col h-full">
|
|
858
|
-
<div className="p-4 border-b border-white/5">
|
|
859
|
-
<div className="relative max-w-md">
|
|
860
|
-
<Search className="absolute left-3 top-2.5 w-4 h-4 text-gray-500" />
|
|
861
|
-
<input
|
|
862
|
-
value={query}
|
|
863
|
-
onChange={e => setQuery(e.target.value)}
|
|
864
|
-
onKeyDown={e => e.key === 'Enter' && handleSearch()}
|
|
865
|
-
placeholder="Search knowledge..."
|
|
866
|
-
className="w-full pl-9 pr-4 py-2 text-sm bg-white/5 border border-white/10 rounded-lg text-gray-300 placeholder-gray-600 focus:outline-none focus:border-purple-500/50"
|
|
867
|
-
/>
|
|
868
|
-
</div>
|
|
869
|
-
</div>
|
|
870
|
-
<div className="flex-1 overflow-y-auto p-4">
|
|
871
|
-
<div className="max-w-2xl space-y-2">
|
|
872
|
-
{loading && <p className="text-sm text-gray-500 text-center py-8">Loading...</p>}
|
|
873
|
-
{!loading && results.length === 0 && (
|
|
874
|
-
<p className="text-sm text-gray-500 text-center py-8">No knowledge found</p>
|
|
875
|
-
)}
|
|
876
|
-
{results.map(unit => (
|
|
877
|
-
<KnowledgeCard key={unit.id} unit={unit} onDelete={handleDelete} />
|
|
878
|
-
))}
|
|
879
|
-
</div>
|
|
880
|
-
</div>
|
|
881
|
-
</div>
|
|
882
|
-
);
|
|
883
|
-
}
|
|
884
|
-
```
|
|
885
|
-
|
|
886
|
-
- [ ] **Step 2: Commit**
|
|
887
|
-
|
|
888
|
-
```bash
|
|
889
|
-
git add src/components/cortex/knowledge-tab.tsx
|
|
890
|
-
git commit -m "feat(cortex): add knowledge tab with search and v2 cards"
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
---
|
|
894
|
-
|
|
895
|
-
### Task 10: Context assembly tab
|
|
896
|
-
|
|
897
|
-
**Files:**
|
|
898
|
-
- Create: `src/components/cortex/context-tab.tsx`
|
|
899
|
-
|
|
900
|
-
- [ ] **Step 1: Create the component**
|
|
901
|
-
|
|
902
|
-
```typescript
|
|
903
|
-
'use client';
|
|
904
|
-
|
|
905
|
-
import { useState } from 'react';
|
|
906
|
-
import { Search, ChevronDown, ChevronUp } from 'lucide-react';
|
|
907
|
-
import { api } from '@/lib/api';
|
|
908
|
-
|
|
909
|
-
const INTENT_COLORS: Record<string, string> = {
|
|
910
|
-
debugging: 'text-red-400',
|
|
911
|
-
architecture: 'text-blue-400',
|
|
912
|
-
onboarding: 'text-green-400',
|
|
913
|
-
policy: 'text-purple-400',
|
|
914
|
-
'how-to': 'text-amber-400',
|
|
915
|
-
review: 'text-pink-400',
|
|
916
|
-
security: 'text-red-500',
|
|
917
|
-
general: 'text-gray-400',
|
|
918
|
-
};
|
|
919
|
-
|
|
920
|
-
export function ContextTab() {
|
|
921
|
-
const [query, setQuery] = useState('');
|
|
922
|
-
const [result, setResult] = useState<any>(null);
|
|
923
|
-
const [loading, setLoading] = useState(false);
|
|
924
|
-
const [showRaw, setShowRaw] = useState(false);
|
|
925
|
-
|
|
926
|
-
const handleAnalyze = async () => {
|
|
927
|
-
if (!query.trim()) return;
|
|
928
|
-
setLoading(true);
|
|
929
|
-
try {
|
|
930
|
-
const res = await fetch(api(`/api/cortex/context?q=${encodeURIComponent(query)}&limit=5`));
|
|
931
|
-
if (res.ok) setResult(await res.json());
|
|
932
|
-
} catch {}
|
|
933
|
-
setLoading(false);
|
|
934
|
-
};
|
|
935
|
-
|
|
936
|
-
return (
|
|
937
|
-
<div className="p-6 max-w-3xl mx-auto">
|
|
938
|
-
{/* Query input */}
|
|
939
|
-
<div className="flex gap-3 mb-6">
|
|
940
|
-
<div className="flex-1 relative">
|
|
941
|
-
<Search className="absolute left-3 top-2.5 w-4 h-4 text-gray-500" />
|
|
942
|
-
<input
|
|
943
|
-
value={query}
|
|
944
|
-
onChange={e => setQuery(e.target.value)}
|
|
945
|
-
onKeyDown={e => e.key === 'Enter' && handleAnalyze()}
|
|
946
|
-
placeholder="Enter a query to analyze..."
|
|
947
|
-
className="w-full pl-9 pr-4 py-2 text-sm bg-white/5 border border-white/10 rounded-lg text-gray-300 placeholder-gray-600 focus:outline-none focus:border-purple-500/50"
|
|
948
|
-
/>
|
|
949
|
-
</div>
|
|
950
|
-
<button
|
|
951
|
-
onClick={handleAnalyze}
|
|
952
|
-
disabled={loading}
|
|
953
|
-
className="px-5 py-2 text-sm bg-purple-600 hover:bg-purple-500 text-white rounded-lg disabled:opacity-50"
|
|
954
|
-
>
|
|
955
|
-
{loading ? 'Analyzing...' : 'Analyze'}
|
|
956
|
-
</button>
|
|
957
|
-
</div>
|
|
958
|
-
|
|
959
|
-
{!result && !loading && (
|
|
960
|
-
<p className="text-sm text-gray-600 text-center py-12">
|
|
961
|
-
Enter a query to see how the Context Assembly Engine processes it
|
|
962
|
-
</p>
|
|
963
|
-
)}
|
|
964
|
-
|
|
965
|
-
{result && (
|
|
966
|
-
<>
|
|
967
|
-
{/* Pipeline summary */}
|
|
968
|
-
<div className="grid grid-cols-3 gap-3 mb-6">
|
|
969
|
-
<div className="bg-white/[0.02] border border-white/5 rounded-lg p-3">
|
|
970
|
-
<div className="text-[10px] text-gray-600 uppercase mb-1">Intent</div>
|
|
971
|
-
<div className={`text-lg font-semibold ${INTENT_COLORS[result.intent?.intent] || 'text-gray-400'}`}>
|
|
972
|
-
{result.intent?.intent || '?'}
|
|
973
|
-
</div>
|
|
974
|
-
<div className="text-[10px] text-gray-600">
|
|
975
|
-
confidence: {(result.intent?.confidence ?? 0).toFixed(2)}
|
|
976
|
-
</div>
|
|
977
|
-
</div>
|
|
978
|
-
<div className="bg-white/[0.02] border border-white/5 rounded-lg p-3">
|
|
979
|
-
<div className="text-[10px] text-gray-600 uppercase mb-1">Entities</div>
|
|
980
|
-
<div className="flex flex-wrap gap-1 mt-1">
|
|
981
|
-
{(result.entities || []).map((e: any, i: number) => (
|
|
982
|
-
<span key={i} className="text-[10px] px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-gray-300">
|
|
983
|
-
{e.entity?.type}:{e.entity?.name}
|
|
984
|
-
</span>
|
|
985
|
-
))}
|
|
986
|
-
{(!result.entities || result.entities.length === 0) && (
|
|
987
|
-
<span className="text-[10px] text-gray-600">none detected</span>
|
|
988
|
-
)}
|
|
989
|
-
</div>
|
|
990
|
-
</div>
|
|
991
|
-
<div className="bg-white/[0.02] border border-white/5 rounded-lg p-3">
|
|
992
|
-
<div className="text-[10px] text-gray-600 uppercase mb-1">Timing</div>
|
|
993
|
-
<div className="text-lg font-semibold text-green-400">
|
|
994
|
-
{result.timing?.totalMs ?? '?'}ms
|
|
995
|
-
</div>
|
|
996
|
-
<div className="text-[10px] text-gray-600">
|
|
997
|
-
intent {result.timing?.intentMs}ms · search {result.timing?.searchMs}ms
|
|
998
|
-
</div>
|
|
999
|
-
</div>
|
|
1000
|
-
</div>
|
|
1001
|
-
|
|
1002
|
-
{/* Source weights */}
|
|
1003
|
-
{result.sourceWeights && (
|
|
1004
|
-
<div className="mb-6">
|
|
1005
|
-
<div className="text-[10px] text-gray-600 uppercase mb-2">Source Weights</div>
|
|
1006
|
-
<div className="flex gap-3">
|
|
1007
|
-
{result.sourceWeights.map((sw: any, i: number) => (
|
|
1008
|
-
<div key={i} className="flex-1 bg-white/[0.02] rounded-lg p-3">
|
|
1009
|
-
<div className="flex justify-between text-[11px] mb-1">
|
|
1010
|
-
<span className="text-gray-300 capitalize">{sw.scopeLevel}</span>
|
|
1011
|
-
<span className="text-gray-500">{sw.weight.toFixed(2)}</span>
|
|
1012
|
-
</div>
|
|
1013
|
-
<div className="h-1.5 bg-white/5 rounded-full overflow-hidden">
|
|
1014
|
-
<div
|
|
1015
|
-
className="h-full bg-purple-500/60 rounded-full"
|
|
1016
|
-
style={{ width: `${Math.min(100, sw.weight * 100)}%` }}
|
|
1017
|
-
/>
|
|
1018
|
-
</div>
|
|
1019
|
-
</div>
|
|
1020
|
-
))}
|
|
1021
|
-
</div>
|
|
1022
|
-
</div>
|
|
1023
|
-
)}
|
|
1024
|
-
|
|
1025
|
-
{/* Results */}
|
|
1026
|
-
<div className="mb-6">
|
|
1027
|
-
<div className="text-[10px] text-gray-600 uppercase mb-2">
|
|
1028
|
-
Results ({(result.results || []).length})
|
|
1029
|
-
</div>
|
|
1030
|
-
<div className="space-y-1">
|
|
1031
|
-
{(result.results || []).map((r: any, i: number) => (
|
|
1032
|
-
<div key={i} className="flex items-center gap-2 bg-white/[0.02] border border-white/5 rounded-lg px-3 py-2">
|
|
1033
|
-
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${
|
|
1034
|
-
r.type === 'error_fix' ? 'bg-amber-500/20 text-amber-400'
|
|
1035
|
-
: r.type === 'decision' ? 'bg-blue-500/20 text-blue-400'
|
|
1036
|
-
: 'bg-gray-500/20 text-gray-400'
|
|
1037
|
-
}`}>{r.type?.replace('_', ' ')}</span>
|
|
1038
|
-
<span className="text-xs text-gray-300 truncate flex-1">{r.text}</span>
|
|
1039
|
-
<span className="text-[10px] text-gray-600 tabular-nums shrink-0">
|
|
1040
|
-
{(r.relevance_score ?? 0).toFixed(3)}
|
|
1041
|
-
</span>
|
|
1042
|
-
</div>
|
|
1043
|
-
))}
|
|
1044
|
-
</div>
|
|
1045
|
-
</div>
|
|
1046
|
-
|
|
1047
|
-
{/* Conflicts */}
|
|
1048
|
-
{Array.isArray(result.conflicts) && result.conflicts.length > 0 && (
|
|
1049
|
-
<div className="mb-6 border border-amber-500/20 bg-amber-500/5 rounded-lg p-3">
|
|
1050
|
-
<div className="text-[10px] text-amber-400 uppercase font-medium mb-1">
|
|
1051
|
-
Conflicts ({result.conflicts.length})
|
|
1052
|
-
</div>
|
|
1053
|
-
{result.conflicts.map((c: any, i: number) => (
|
|
1054
|
-
<div key={i} className="text-xs text-gray-400 mt-1">
|
|
1055
|
-
“{c.unitA?.text?.slice(0, 60)}...” vs “{c.unitB?.text?.slice(0, 60)}...”
|
|
1056
|
-
</div>
|
|
1057
|
-
))}
|
|
1058
|
-
</div>
|
|
1059
|
-
)}
|
|
1060
|
-
|
|
1061
|
-
{/* Raw context */}
|
|
1062
|
-
<div>
|
|
1063
|
-
<button
|
|
1064
|
-
onClick={() => setShowRaw(!showRaw)}
|
|
1065
|
-
className="flex items-center gap-1 text-[10px] text-gray-600 hover:text-gray-400 uppercase"
|
|
1066
|
-
>
|
|
1067
|
-
Raw context {showRaw ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
|
1068
|
-
</button>
|
|
1069
|
-
{showRaw && result.context && (
|
|
1070
|
-
<pre className="mt-2 p-3 bg-white/[0.02] border border-white/5 rounded-lg text-[11px] text-gray-400 overflow-x-auto whitespace-pre-wrap">
|
|
1071
|
-
{result.context}
|
|
1072
|
-
</pre>
|
|
1073
|
-
)}
|
|
1074
|
-
</div>
|
|
1075
|
-
</>
|
|
1076
|
-
)}
|
|
1077
|
-
</div>
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
- [ ] **Step 2: Commit**
|
|
1083
|
-
|
|
1084
|
-
```bash
|
|
1085
|
-
git add src/components/cortex/context-tab.tsx
|
|
1086
|
-
git commit -m "feat(cortex): add context assembly inspector tab"
|
|
1087
|
-
```
|
|
1088
|
-
|
|
1089
|
-
---
|
|
1090
|
-
|
|
1091
|
-
## Summary
|
|
1092
|
-
|
|
1093
|
-
| Task | Component | Status |
|
|
1094
|
-
|------|-----------|--------|
|
|
1095
|
-
| 1 | listAllEdges() + edges all=true | |
|
|
1096
|
-
| 2 | Context API expansion (entities, conflicts, sourceWeights) | |
|
|
1097
|
-
| 3 | Enhanced knowledge card (v2 fields) | |
|
|
1098
|
-
| 4 | Sidebar Cortex nav item | |
|
|
1099
|
-
| 5 | /cortex page with tabs | |
|
|
1100
|
-
| 6 | Panel "Open full view" link | |
|
|
1101
|
-
| 7 | Entity graph canvas (force-graph) | |
|
|
1102
|
-
| 8 | Entity detail panel | |
|
|
1103
|
-
| 9 | Knowledge tab | |
|
|
1104
|
-
| 10 | Context assembly tab | |
|
|
1105
|
-
|
|
1106
|
-
**Total: 10 tasks, 4 chunks**
|
|
1107
|
-
|
|
1108
|
-
**No tests in this plan** — these are UI components that render from existing tested APIs. The backend changes (Tasks 1-2) are trivial additions to already-tested modules.
|
|
1
|
+
# Cortex v2 UI Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a dedicated `/cortex` page with interactive entity graph visualization, enhanced knowledge cards with v2 fields, and a context assembly dashboard — plus sidebar navigation and panel enhancements.
|
|
6
|
+
|
|
7
|
+
**Architecture:** New `/app/(desktop)/cortex/page.tsx` with tab navigation (Graph | Knowledge | Context | Settings). Force graph rendered via `force-graph` npm package with `nodeCanvasObject` for custom shapes. Small backend tweaks to expose `listAllEdges()`, entities/conflicts/sourceWeights in context API. Existing right panel gets "Open full view" link.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** React 19, Next.js 16, Tailwind CSS 4, force-graph, Lucide icons, TanStack React Query
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## File Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
New files:
|
|
19
|
+
├── src/app/(desktop)/cortex/page.tsx — Main /cortex page with tabs
|
|
20
|
+
├── src/components/cortex/entity-graph.tsx — Force graph canvas (dynamic, ssr:false)
|
|
21
|
+
├── src/components/cortex/entity-detail.tsx — Right-side entity detail panel
|
|
22
|
+
├── src/components/cortex/context-tab.tsx — Context assembly inspector tab
|
|
23
|
+
├── src/components/cortex/knowledge-tab.tsx — Knowledge list tab (reuses cards)
|
|
24
|
+
|
|
25
|
+
Modified files:
|
|
26
|
+
├── src/components/cortex/knowledge-card.tsx — Add v2 badges + evidence bar
|
|
27
|
+
├── src/components/cortex/cortex-panel.tsx — Add "Open full view" link
|
|
28
|
+
├── src/components/layout/sidebar.tsx — Add Cortex nav item
|
|
29
|
+
├── src/lib/cortex/graph/entity-graph.ts — Add listAllEdges()
|
|
30
|
+
├── src/app/api/cortex/graph/edges/route.ts — Support all=true
|
|
31
|
+
├── src/lib/cortex/retrieval/context-engine.ts — Expose sourceWeights
|
|
32
|
+
├── src/app/api/cortex/context/route.ts — Return entities, conflicts, sourceWeights
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Chunk 1: Backend Tweaks + Enhanced Knowledge Card
|
|
38
|
+
|
|
39
|
+
### Task 1: Add listAllEdges() and edges `all=true` support
|
|
40
|
+
|
|
41
|
+
**Files:**
|
|
42
|
+
- Modify: `src/lib/cortex/graph/entity-graph.ts`
|
|
43
|
+
- Modify: `src/app/api/cortex/graph/edges/route.ts`
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 1: Add listAllEdges() to EntityGraph**
|
|
46
|
+
|
|
47
|
+
Read `src/lib/cortex/graph/entity-graph.ts`, find the edge methods section, add:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
listAllEdges(): Edge[] {
|
|
51
|
+
return (this.db.prepare('SELECT * FROM edges ORDER BY source_id').all() as any[])
|
|
52
|
+
.map(r => this.rowToEdge(r));
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- [ ] **Step 2: Update edges route GET handler**
|
|
57
|
+
|
|
58
|
+
Read `src/app/api/cortex/graph/edges/route.ts`. At the top of the GET handler, before the `from`/`to` check, add:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const all = url.searchParams.get('all');
|
|
62
|
+
if (all === 'true') {
|
|
63
|
+
const edges = cortex.graph.listAllEdges();
|
|
64
|
+
return NextResponse.json({ edges });
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- [ ] **Step 3: Commit**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git add src/lib/cortex/graph/entity-graph.ts src/app/api/cortex/graph/edges/route.ts
|
|
72
|
+
git commit -m "feat(cortex): add listAllEdges() and edges all=true endpoint"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Task 2: Expose sourceWeights in context API
|
|
78
|
+
|
|
79
|
+
**Files:**
|
|
80
|
+
- Modify: `src/lib/cortex/retrieval/context-engine.ts`
|
|
81
|
+
- Modify: `src/app/api/cortex/context/route.ts`
|
|
82
|
+
|
|
83
|
+
- [ ] **Step 1: Add sourceWeights to AssemblyResult**
|
|
84
|
+
|
|
85
|
+
Read `src/lib/cortex/retrieval/context-engine.ts`. Find the `AssemblyResult` interface and add:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
sourceWeights: Array<{ layerKey: string; scopeLevel: string; weight: number }>;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Find the `assemble()` method. After `computeSourceWeights()` is called (the `sources` variable), map it into the result:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// In the return object, add:
|
|
95
|
+
sourceWeights: sources.map(s => ({
|
|
96
|
+
layerKey: s.layerKey,
|
|
97
|
+
scopeLevel: s.layerKey === 'personal' ? 'personal' : s.layerKey.startsWith('workspace') ? 'team' : 'organization',
|
|
98
|
+
weight: s.weight,
|
|
99
|
+
})),
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- [ ] **Step 2: Update context route response**
|
|
103
|
+
|
|
104
|
+
Read `src/app/api/cortex/context/route.ts`. In the `NextResponse.json()` call, change:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// From:
|
|
108
|
+
conflicts: result.conflicts.length,
|
|
109
|
+
// To:
|
|
110
|
+
entities: result.entities,
|
|
111
|
+
conflicts: result.conflicts,
|
|
112
|
+
sourceWeights: result.sourceWeights,
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- [ ] **Step 3: Commit**
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git add src/lib/cortex/retrieval/context-engine.ts src/app/api/cortex/context/route.ts
|
|
119
|
+
git commit -m "feat(cortex): expose entities, conflicts, sourceWeights in context API"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Task 3: Enhanced knowledge card
|
|
125
|
+
|
|
126
|
+
**Files:**
|
|
127
|
+
- Modify: `src/components/cortex/knowledge-card.tsx`
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 1: Read the current knowledge-card.tsx**
|
|
130
|
+
|
|
131
|
+
- [ ] **Step 2: Extend the props interface and add v2 display**
|
|
132
|
+
|
|
133
|
+
Replace the entire file. The changes:
|
|
134
|
+
- Extend `unit` props with optional v2 fields
|
|
135
|
+
- Add sensitivity badge (color-coded)
|
|
136
|
+
- Add scope badge
|
|
137
|
+
- Add attribution row (creator, source, corroborations)
|
|
138
|
+
- Replace confidence bar with evidence bar when available
|
|
139
|
+
- Add conflict indicator
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
'use client';
|
|
143
|
+
|
|
144
|
+
import { Trash2, AlertTriangle } from 'lucide-react';
|
|
145
|
+
import { api } from '@/lib/api';
|
|
146
|
+
|
|
147
|
+
const TYPE_COLORS: Record<string, string> = {
|
|
148
|
+
decision: 'bg-blue-500/20 text-blue-400',
|
|
149
|
+
preference: 'bg-pink-500/20 text-pink-400',
|
|
150
|
+
pattern: 'bg-green-500/20 text-green-400',
|
|
151
|
+
error_fix: 'bg-amber-500/20 text-amber-400',
|
|
152
|
+
context: 'bg-gray-500/20 text-gray-400',
|
|
153
|
+
code_pattern: 'bg-cyan-500/20 text-cyan-400',
|
|
154
|
+
command: 'bg-orange-500/20 text-orange-400',
|
|
155
|
+
conversation: 'bg-slate-500/20 text-slate-400',
|
|
156
|
+
summary: 'bg-violet-500/20 text-violet-400',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const SENSITIVITY_COLORS: Record<string, string> = {
|
|
160
|
+
public: 'bg-green-500/20 text-green-400',
|
|
161
|
+
internal: 'bg-indigo-500/20 text-indigo-400',
|
|
162
|
+
restricted: 'bg-amber-500/20 text-amber-400',
|
|
163
|
+
confidential: 'bg-red-500/20 text-red-400',
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
interface KnowledgeCardProps {
|
|
167
|
+
unit: {
|
|
168
|
+
id: string;
|
|
169
|
+
text: string;
|
|
170
|
+
type: string;
|
|
171
|
+
confidence: number;
|
|
172
|
+
created: string;
|
|
173
|
+
session_id?: string | null;
|
|
174
|
+
layer: string;
|
|
175
|
+
stale_score?: number;
|
|
176
|
+
// v2 fields
|
|
177
|
+
scope?: { level: string; entity_id: string };
|
|
178
|
+
sensitivity?: string;
|
|
179
|
+
evidence_score?: number;
|
|
180
|
+
corroborations?: number;
|
|
181
|
+
contradiction_refs?: string[];
|
|
182
|
+
origin?: { source_type: string; source_ref: string; creator_entity_id: string };
|
|
183
|
+
};
|
|
184
|
+
onDelete?: (id: string) => void;
|
|
185
|
+
compact?: boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function KnowledgeCard({ unit, onDelete, compact }: KnowledgeCardProps) {
|
|
189
|
+
const colorClass = TYPE_COLORS[unit.type] || TYPE_COLORS.context;
|
|
190
|
+
const age = getRelativeAge(unit.created);
|
|
191
|
+
const hasConflicts = (unit.contradiction_refs?.length ?? 0) > 0;
|
|
192
|
+
|
|
193
|
+
// Use evidence_score if available, else confidence
|
|
194
|
+
const score = unit.evidence_score ?? unit.confidence;
|
|
195
|
+
const scoreLabel = unit.evidence_score != null
|
|
196
|
+
? `${score.toFixed(2)} evidence`
|
|
197
|
+
: `${Math.round(score * 100)}%`;
|
|
198
|
+
|
|
199
|
+
const handleDelete = async () => {
|
|
200
|
+
await fetch(api(`/api/cortex/knowledge/${unit.id}`), { method: 'DELETE' });
|
|
201
|
+
onDelete?.(unit.id);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div className="group border border-white/5 rounded-lg p-3 hover:border-white/10 transition-colors">
|
|
206
|
+
{/* Badge row */}
|
|
207
|
+
<div className="flex items-start justify-between gap-2">
|
|
208
|
+
<div className="flex items-center gap-1 flex-wrap">
|
|
209
|
+
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${colorClass}`}>
|
|
210
|
+
{unit.type.replace('_', ' ')}
|
|
211
|
+
</span>
|
|
212
|
+
{unit.sensitivity && (
|
|
213
|
+
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${SENSITIVITY_COLORS[unit.sensitivity] || SENSITIVITY_COLORS.internal}`}>
|
|
214
|
+
{unit.sensitivity}
|
|
215
|
+
</span>
|
|
216
|
+
)}
|
|
217
|
+
{unit.scope && (
|
|
218
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-purple-500/10 text-purple-400">
|
|
219
|
+
{unit.scope.level}
|
|
220
|
+
</span>
|
|
221
|
+
)}
|
|
222
|
+
{(unit.stale_score ?? 0) > 0.3 && (
|
|
223
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-500/20 text-amber-400">
|
|
224
|
+
stale
|
|
225
|
+
</span>
|
|
226
|
+
)}
|
|
227
|
+
{hasConflicts && (
|
|
228
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-500/20 text-amber-400 flex items-center gap-0.5">
|
|
229
|
+
<AlertTriangle className="w-2.5 h-2.5" />
|
|
230
|
+
contested
|
|
231
|
+
</span>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
<div className="flex items-center gap-2 text-[10px] text-gray-500 shrink-0">
|
|
235
|
+
<span>{age}</span>
|
|
236
|
+
{onDelete && (
|
|
237
|
+
<button
|
|
238
|
+
onClick={handleDelete}
|
|
239
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity text-red-400 hover:text-red-300"
|
|
240
|
+
>
|
|
241
|
+
<Trash2 className="w-3 h-3" />
|
|
242
|
+
</button>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{/* Text */}
|
|
248
|
+
<p className="text-xs text-gray-300 mt-1.5 leading-relaxed line-clamp-3">{unit.text}</p>
|
|
249
|
+
|
|
250
|
+
{/* Attribution row (v2) */}
|
|
251
|
+
{!compact && unit.origin && (
|
|
252
|
+
<div className="flex items-center gap-1.5 mt-1.5 text-[10px] text-gray-500 flex-wrap">
|
|
253
|
+
<span>by {unit.origin.creator_entity_id.replace('person-', '')}</span>
|
|
254
|
+
<span>·</span>
|
|
255
|
+
<span>via {unit.origin.source_type.replace('_', ' ')}</span>
|
|
256
|
+
{(unit.corroborations ?? 0) > 0 && (
|
|
257
|
+
<>
|
|
258
|
+
<span>·</span>
|
|
259
|
+
<span className="text-green-400">{unit.corroborations} corr.</span>
|
|
260
|
+
</>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
|
|
265
|
+
{/* Evidence/confidence bar */}
|
|
266
|
+
<div className="flex items-center gap-2 mt-2">
|
|
267
|
+
<div className="flex-1 h-1 bg-white/5 rounded-full overflow-hidden">
|
|
268
|
+
<div
|
|
269
|
+
className="h-full bg-purple-500/50 rounded-full"
|
|
270
|
+
style={{ width: `${Math.round(score * 100)}%` }}
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
<span className="text-[10px] text-gray-500 tabular-nums">{scoreLabel}</span>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getRelativeAge(iso: string): string {
|
|
280
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
281
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
282
|
+
if (days === 0) return 'today';
|
|
283
|
+
if (days === 1) return '1d ago';
|
|
284
|
+
if (days < 30) return `${days}d ago`;
|
|
285
|
+
if (days < 365) return `${Math.floor(days / 30)}mo ago`;
|
|
286
|
+
return `${Math.floor(days / 365)}y ago`;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- [ ] **Step 3: Commit**
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
git add src/components/cortex/knowledge-card.tsx
|
|
294
|
+
git commit -m "feat(cortex): enhance knowledge card with v2 fields display"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Chunk 2: Cortex Page + Sidebar
|
|
300
|
+
|
|
301
|
+
### Task 4: Sidebar navigation
|
|
302
|
+
|
|
303
|
+
**Files:**
|
|
304
|
+
- Modify: `src/components/layout/sidebar.tsx`
|
|
305
|
+
|
|
306
|
+
- [ ] **Step 1: Read sidebar.tsx**
|
|
307
|
+
|
|
308
|
+
- [ ] **Step 2: Add Cortex to nav array and imports**
|
|
309
|
+
|
|
310
|
+
Add `Brain` to the lucide-react import. Add a Cortex entry to the `nav` array (after Network, before Settings):
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Add to imports:
|
|
314
|
+
import { ..., Brain } from 'lucide-react';
|
|
315
|
+
|
|
316
|
+
// Add to nav array (before Settings):
|
|
317
|
+
{ href: '/cortex', label: 'Cortex', icon: Brain },
|
|
318
|
+
|
|
319
|
+
// Add to routeNames:
|
|
320
|
+
'/cortex': 'cortex',
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
In the nav filter, gate it like Network:
|
|
324
|
+
```typescript
|
|
325
|
+
if (href === '/cortex') return hasCortex;
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- [ ] **Step 3: Commit**
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
git add src/components/layout/sidebar.tsx
|
|
332
|
+
git commit -m "feat(cortex): add Cortex nav item to sidebar"
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### Task 5: Cortex page with tabs
|
|
338
|
+
|
|
339
|
+
**Files:**
|
|
340
|
+
- Create: `src/app/(desktop)/cortex/page.tsx`
|
|
341
|
+
|
|
342
|
+
- [ ] **Step 1: Create the page**
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
'use client';
|
|
346
|
+
|
|
347
|
+
import { useState, useEffect } from 'react';
|
|
348
|
+
import dynamic from 'next/dynamic';
|
|
349
|
+
import { api } from '@/lib/api';
|
|
350
|
+
import { KnowledgeTab } from '@/components/cortex/knowledge-tab';
|
|
351
|
+
import { ContextTab } from '@/components/cortex/context-tab';
|
|
352
|
+
import { CortexSettings } from '@/components/cortex/cortex-settings';
|
|
353
|
+
|
|
354
|
+
const EntityGraphView = dynamic(
|
|
355
|
+
() => import('@/components/cortex/entity-graph').then(m => ({ default: m.EntityGraphView })),
|
|
356
|
+
{ ssr: false, loading: () => <div className="flex-1 flex items-center justify-center text-gray-500 text-sm">Loading graph...</div> }
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
type Tab = 'graph' | 'knowledge' | 'context' | 'settings';
|
|
360
|
+
|
|
361
|
+
export default function CortexPage() {
|
|
362
|
+
const [tab, setTab] = useState<Tab>('graph');
|
|
363
|
+
const [stats, setStats] = useState<any>(null);
|
|
364
|
+
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
fetch(api('/api/cortex/status'))
|
|
367
|
+
.then(r => r.json())
|
|
368
|
+
.then(setStats)
|
|
369
|
+
.catch(() => {});
|
|
370
|
+
}, []);
|
|
371
|
+
|
|
372
|
+
const tabs: { key: Tab; label: string }[] = [
|
|
373
|
+
{ key: 'graph', label: 'Graph' },
|
|
374
|
+
{ key: 'knowledge', label: 'Knowledge' },
|
|
375
|
+
{ key: 'context', label: 'Context' },
|
|
376
|
+
{ key: 'settings', label: 'Settings' },
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
const totalKnowledge = stats
|
|
380
|
+
? Object.values(stats.layers || {}).reduce((sum: number, l: any) => sum + (l.count || 0), 0)
|
|
381
|
+
: 0;
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<div className="flex flex-col h-screen bg-gray-950">
|
|
385
|
+
{/* Tab bar */}
|
|
386
|
+
<div className="flex items-center border-b border-white/5 px-4 shrink-0">
|
|
387
|
+
<div className="flex">
|
|
388
|
+
{tabs.map(t => (
|
|
389
|
+
<button
|
|
390
|
+
key={t.key}
|
|
391
|
+
onClick={() => setTab(t.key)}
|
|
392
|
+
className={`px-5 py-3 text-sm font-medium transition-colors border-b-2 ${
|
|
393
|
+
tab === t.key
|
|
394
|
+
? 'text-purple-400 border-purple-400'
|
|
395
|
+
: 'text-gray-500 border-transparent hover:text-gray-300'
|
|
396
|
+
}`}
|
|
397
|
+
>
|
|
398
|
+
{t.label}
|
|
399
|
+
</button>
|
|
400
|
+
))}
|
|
401
|
+
</div>
|
|
402
|
+
<div className="ml-auto text-xs text-gray-600">
|
|
403
|
+
{totalKnowledge} knowledge units
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
{/* Tab content */}
|
|
408
|
+
<div className="flex-1 overflow-hidden">
|
|
409
|
+
{tab === 'graph' && <EntityGraphView />}
|
|
410
|
+
{tab === 'knowledge' && <KnowledgeTab />}
|
|
411
|
+
{tab === 'context' && <ContextTab />}
|
|
412
|
+
{tab === 'settings' && (
|
|
413
|
+
<div className="p-6 max-w-2xl">
|
|
414
|
+
<CortexSettings />
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
- [ ] **Step 2: Commit**
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
git add src/app/(desktop)/cortex/page.tsx
|
|
427
|
+
git commit -m "feat(cortex): add /cortex page with tab navigation"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### Task 6: Panel "Open full view" link
|
|
433
|
+
|
|
434
|
+
**Files:**
|
|
435
|
+
- Modify: `src/components/cortex/cortex-panel.tsx`
|
|
436
|
+
|
|
437
|
+
- [ ] **Step 1: Add link to panel header**
|
|
438
|
+
|
|
439
|
+
Read `src/components/cortex/cortex-panel.tsx`. In the header div (between the "Cortex" heading and the close button), add:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import Link from 'next/link';
|
|
443
|
+
import { ExternalLink } from 'lucide-react';
|
|
444
|
+
|
|
445
|
+
// In the header, after the h2:
|
|
446
|
+
<Link
|
|
447
|
+
href="/cortex"
|
|
448
|
+
className="text-[10px] text-purple-400 hover:text-purple-300 flex items-center gap-1"
|
|
449
|
+
onClick={onClose}
|
|
450
|
+
>
|
|
451
|
+
Full view <ExternalLink className="w-2.5 h-2.5" />
|
|
452
|
+
</Link>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
- [ ] **Step 2: Commit**
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
git add src/components/cortex/cortex-panel.tsx
|
|
459
|
+
git commit -m "feat(cortex): add 'Full view' link to cortex panel"
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Chunk 3: Graph + Detail Components
|
|
465
|
+
|
|
466
|
+
### Task 7: Entity graph canvas component
|
|
467
|
+
|
|
468
|
+
**Files:**
|
|
469
|
+
- Create: `src/components/cortex/entity-graph.tsx`
|
|
470
|
+
|
|
471
|
+
- [ ] **Step 1: Install force-graph**
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
npm install force-graph
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
- [ ] **Step 2: Create the graph component**
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
'use client';
|
|
481
|
+
|
|
482
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
483
|
+
import ForceGraph2D from 'force-graph';
|
|
484
|
+
import { api } from '@/lib/api';
|
|
485
|
+
import { EntityDetail } from './entity-detail';
|
|
486
|
+
|
|
487
|
+
const NODE_COLORS: Record<string, string> = {
|
|
488
|
+
person: '#7c3aed',
|
|
489
|
+
team: '#10b981',
|
|
490
|
+
project: '#10b981',
|
|
491
|
+
department: '#3b82f6',
|
|
492
|
+
organization: '#3b82f6',
|
|
493
|
+
system: '#f59e0b',
|
|
494
|
+
module: '#f59e0b',
|
|
495
|
+
topic: '#06b6d4',
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
interface GraphNode {
|
|
499
|
+
id: string;
|
|
500
|
+
name: string;
|
|
501
|
+
type: string;
|
|
502
|
+
metadata: Record<string, unknown>;
|
|
503
|
+
// force-graph adds x, y, vx, vy
|
|
504
|
+
x?: number;
|
|
505
|
+
y?: number;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
interface GraphLink {
|
|
509
|
+
source: string;
|
|
510
|
+
target: string;
|
|
511
|
+
relation: string;
|
|
512
|
+
weight: number;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function EntityGraphView() {
|
|
516
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
517
|
+
const graphRef = useRef<any>(null);
|
|
518
|
+
const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
|
|
519
|
+
const [graphData, setGraphData] = useState<{ nodes: GraphNode[]; links: GraphLink[] }>({ nodes: [], links: [] });
|
|
520
|
+
|
|
521
|
+
// Fetch graph data
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
Promise.all([
|
|
524
|
+
fetch(api('/api/cortex/graph/entities')).then(r => r.json()),
|
|
525
|
+
fetch(api('/api/cortex/graph/edges?all=true')).then(r => r.json()),
|
|
526
|
+
]).then(([entData, edgeData]) => {
|
|
527
|
+
const nodes: GraphNode[] = (entData.entities || []).map((e: any) => ({
|
|
528
|
+
id: e.id,
|
|
529
|
+
name: e.name,
|
|
530
|
+
type: e.type,
|
|
531
|
+
metadata: e.metadata || {},
|
|
532
|
+
}));
|
|
533
|
+
const nodeIds = new Set(nodes.map(n => n.id));
|
|
534
|
+
const links: GraphLink[] = (edgeData.edges || [])
|
|
535
|
+
.filter((e: any) => nodeIds.has(e.source_id) && nodeIds.has(e.target_id))
|
|
536
|
+
.map((e: any) => ({
|
|
537
|
+
source: e.source_id,
|
|
538
|
+
target: e.target_id,
|
|
539
|
+
relation: e.relation,
|
|
540
|
+
weight: e.weight,
|
|
541
|
+
}));
|
|
542
|
+
setGraphData({ nodes, links });
|
|
543
|
+
}).catch(() => {});
|
|
544
|
+
}, []);
|
|
545
|
+
|
|
546
|
+
// Initialize force-graph
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
if (!containerRef.current || graphData.nodes.length === 0) return;
|
|
549
|
+
|
|
550
|
+
const width = containerRef.current.clientWidth;
|
|
551
|
+
const height = containerRef.current.clientHeight;
|
|
552
|
+
|
|
553
|
+
const graph = ForceGraph2D()(containerRef.current)
|
|
554
|
+
.width(width)
|
|
555
|
+
.height(height)
|
|
556
|
+
.graphData(graphData)
|
|
557
|
+
.nodeId('id')
|
|
558
|
+
.nodeLabel((node: any) => `${node.name} (${node.type})`)
|
|
559
|
+
.nodeCanvasObject((node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
|
|
560
|
+
const color = NODE_COLORS[node.type] || '#666';
|
|
561
|
+
const size = node.type === 'person' ? 8 : node.type === 'topic' ? 5 : 6;
|
|
562
|
+
const x = node.x ?? 0;
|
|
563
|
+
const y = node.y ?? 0;
|
|
564
|
+
|
|
565
|
+
ctx.beginPath();
|
|
566
|
+
if (node.type === 'system' || node.type === 'module') {
|
|
567
|
+
// Diamond
|
|
568
|
+
ctx.moveTo(x, y - size);
|
|
569
|
+
ctx.lineTo(x + size, y);
|
|
570
|
+
ctx.lineTo(x, y + size);
|
|
571
|
+
ctx.lineTo(x - size, y);
|
|
572
|
+
ctx.closePath();
|
|
573
|
+
} else if (node.type === 'team' || node.type === 'project' || node.type === 'department' || node.type === 'organization') {
|
|
574
|
+
// Rounded rect
|
|
575
|
+
const w = size * 2;
|
|
576
|
+
const h = size * 1.5;
|
|
577
|
+
const r = 2;
|
|
578
|
+
ctx.roundRect(x - w / 2, y - h / 2, w, h, r);
|
|
579
|
+
} else {
|
|
580
|
+
// Circle (person, topic)
|
|
581
|
+
ctx.arc(x, y, size, 0, 2 * Math.PI);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
ctx.fillStyle = color + '33';
|
|
585
|
+
ctx.fill();
|
|
586
|
+
ctx.strokeStyle = color;
|
|
587
|
+
ctx.lineWidth = 1.5 / globalScale;
|
|
588
|
+
ctx.stroke();
|
|
589
|
+
|
|
590
|
+
// Label
|
|
591
|
+
const fontSize = Math.max(10 / globalScale, 3);
|
|
592
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
593
|
+
ctx.textAlign = 'center';
|
|
594
|
+
ctx.textBaseline = 'middle';
|
|
595
|
+
ctx.fillStyle = color;
|
|
596
|
+
ctx.fillText(node.name, x, y + size + fontSize);
|
|
597
|
+
})
|
|
598
|
+
.nodePointerAreaPaint((node: any, color: string, ctx: CanvasRenderingContext2D) => {
|
|
599
|
+
const size = 10;
|
|
600
|
+
ctx.fillStyle = color;
|
|
601
|
+
ctx.beginPath();
|
|
602
|
+
ctx.arc(node.x ?? 0, node.y ?? 0, size, 0, 2 * Math.PI);
|
|
603
|
+
ctx.fill();
|
|
604
|
+
})
|
|
605
|
+
.linkColor(() => 'rgba(124, 58, 237, 0.2)')
|
|
606
|
+
.linkWidth((link: any) => Math.max(0.5, link.weight * 2))
|
|
607
|
+
.onNodeClick((node: any) => setSelectedNode(node))
|
|
608
|
+
.onBackgroundClick(() => setSelectedNode(null))
|
|
609
|
+
.backgroundColor('#06060c');
|
|
610
|
+
|
|
611
|
+
graphRef.current = graph;
|
|
612
|
+
|
|
613
|
+
const handleResize = () => {
|
|
614
|
+
if (!containerRef.current) return;
|
|
615
|
+
graph.width(containerRef.current.clientWidth);
|
|
616
|
+
graph.height(containerRef.current.clientHeight);
|
|
617
|
+
};
|
|
618
|
+
window.addEventListener('resize', handleResize);
|
|
619
|
+
|
|
620
|
+
return () => {
|
|
621
|
+
window.removeEventListener('resize', handleResize);
|
|
622
|
+
graph._destructor?.();
|
|
623
|
+
};
|
|
624
|
+
}, [graphData]);
|
|
625
|
+
|
|
626
|
+
const handleRecenter = useCallback(() => {
|
|
627
|
+
graphRef.current?.zoomToFit(400, 40);
|
|
628
|
+
}, []);
|
|
629
|
+
|
|
630
|
+
return (
|
|
631
|
+
<div className="flex h-full">
|
|
632
|
+
{/* Graph canvas */}
|
|
633
|
+
<div className="flex-1 relative">
|
|
634
|
+
<div ref={containerRef} className="w-full h-full" />
|
|
635
|
+
|
|
636
|
+
{/* Controls overlay */}
|
|
637
|
+
<div className="absolute bottom-3 left-3 flex gap-2">
|
|
638
|
+
<button
|
|
639
|
+
onClick={handleRecenter}
|
|
640
|
+
className="px-3 py-1.5 text-xs bg-gray-900/80 border border-white/10 rounded text-gray-400 hover:text-gray-200"
|
|
641
|
+
>
|
|
642
|
+
Recenter
|
|
643
|
+
</button>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
{/* Legend overlay */}
|
|
647
|
+
<div className="absolute top-3 right-3 bg-gray-950/90 border border-white/10 rounded-lg p-3 text-[10px] space-y-1">
|
|
648
|
+
{Object.entries(NODE_COLORS).filter(([type]) =>
|
|
649
|
+
['person', 'team', 'system', 'topic'].includes(type)
|
|
650
|
+
).map(([type, color]) => (
|
|
651
|
+
<div key={type} className="flex items-center gap-2">
|
|
652
|
+
<span style={{ color }} className="text-sm">
|
|
653
|
+
{type === 'system' ? '◆' : type === 'team' ? '■' : '●'}
|
|
654
|
+
</span>
|
|
655
|
+
<span className="text-gray-400 capitalize">{type}</span>
|
|
656
|
+
</div>
|
|
657
|
+
))}
|
|
658
|
+
</div>
|
|
659
|
+
|
|
660
|
+
{graphData.nodes.length === 0 && (
|
|
661
|
+
<div className="absolute inset-0 flex items-center justify-center text-gray-600 text-sm">
|
|
662
|
+
No entities yet. Add entities via the API to see the graph.
|
|
663
|
+
</div>
|
|
664
|
+
)}
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
{/* Detail panel */}
|
|
668
|
+
<div className="w-72 border-l border-white/5 bg-gray-950 overflow-y-auto">
|
|
669
|
+
<EntityDetail
|
|
670
|
+
node={selectedNode}
|
|
671
|
+
onClose={() => setSelectedNode(null)}
|
|
672
|
+
/>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
- [ ] **Step 3: Commit**
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
git add src/components/cortex/entity-graph.tsx
|
|
683
|
+
git commit -m "feat(cortex): add force-graph entity visualization component"
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
---
|
|
687
|
+
|
|
688
|
+
### Task 8: Entity detail panel
|
|
689
|
+
|
|
690
|
+
**Files:**
|
|
691
|
+
- Create: `src/components/cortex/entity-detail.tsx`
|
|
692
|
+
|
|
693
|
+
- [ ] **Step 1: Create the component**
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
'use client';
|
|
697
|
+
|
|
698
|
+
import { useState, useEffect } from 'react';
|
|
699
|
+
import { api } from '@/lib/api';
|
|
700
|
+
|
|
701
|
+
const RELATION_COLORS: Record<string, string> = {
|
|
702
|
+
member_of: 'text-purple-400',
|
|
703
|
+
expert_in: 'text-cyan-400',
|
|
704
|
+
owns: 'text-green-400',
|
|
705
|
+
contains: 'text-green-400',
|
|
706
|
+
part_of: 'text-blue-400',
|
|
707
|
+
works_on: 'text-purple-400',
|
|
708
|
+
touches: 'text-amber-400',
|
|
709
|
+
depends_on: 'text-red-400',
|
|
710
|
+
relates_to: 'text-cyan-400',
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
interface EntityDetailProps {
|
|
714
|
+
node: { id: string; name: string; type: string; metadata: Record<string, unknown> } | null;
|
|
715
|
+
onClose: () => void;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function EntityDetail({ node, onClose }: EntityDetailProps) {
|
|
719
|
+
const [edges, setEdges] = useState<any[]>([]);
|
|
720
|
+
|
|
721
|
+
useEffect(() => {
|
|
722
|
+
if (!node) { setEdges([]); return; }
|
|
723
|
+
|
|
724
|
+
Promise.all([
|
|
725
|
+
fetch(api(`/api/cortex/graph/edges?from=${node.id}`)).then(r => r.json()),
|
|
726
|
+
fetch(api(`/api/cortex/graph/edges?to=${node.id}`)).then(r => r.json()),
|
|
727
|
+
]).then(([fromData, toData]) => {
|
|
728
|
+
const allEdges = [
|
|
729
|
+
...(fromData.edges || []).map((e: any) => ({ ...e, direction: 'out' })),
|
|
730
|
+
...(toData.edges || []).map((e: any) => ({ ...e, direction: 'in' })),
|
|
731
|
+
];
|
|
732
|
+
setEdges(allEdges);
|
|
733
|
+
}).catch(() => {});
|
|
734
|
+
}, [node?.id]);
|
|
735
|
+
|
|
736
|
+
if (!node) {
|
|
737
|
+
return (
|
|
738
|
+
<div className="p-4 text-center text-gray-600 text-xs mt-8">
|
|
739
|
+
Click a node to see details
|
|
740
|
+
</div>
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const firstLetter = node.name.charAt(0).toUpperCase();
|
|
745
|
+
const color = node.type === 'person' ? '#7c3aed'
|
|
746
|
+
: node.type === 'system' ? '#f59e0b'
|
|
747
|
+
: node.type === 'topic' ? '#06b6d4'
|
|
748
|
+
: '#10b981';
|
|
749
|
+
|
|
750
|
+
return (
|
|
751
|
+
<div className="p-4">
|
|
752
|
+
{/* Header */}
|
|
753
|
+
<div className="flex items-center gap-3 mb-4">
|
|
754
|
+
<div
|
|
755
|
+
className="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
|
|
756
|
+
style={{ backgroundColor: color + '33', color, border: `2px solid ${color}` }}
|
|
757
|
+
>
|
|
758
|
+
{firstLetter}
|
|
759
|
+
</div>
|
|
760
|
+
<div>
|
|
761
|
+
<div className="text-sm font-medium text-gray-200">{node.name}</div>
|
|
762
|
+
<div className="text-[10px] text-gray-500">{node.type}</div>
|
|
763
|
+
</div>
|
|
764
|
+
</div>
|
|
765
|
+
|
|
766
|
+
{/* Metadata */}
|
|
767
|
+
{Object.keys(node.metadata).length > 0 && (
|
|
768
|
+
<div className="mb-4">
|
|
769
|
+
<div className="text-[10px] text-gray-600 uppercase tracking-wider mb-1">Metadata</div>
|
|
770
|
+
{Object.entries(node.metadata).map(([k, v]) => (
|
|
771
|
+
<div key={k} className="text-[11px] text-gray-400">
|
|
772
|
+
<span className="text-gray-600">{k}:</span> {String(v)}
|
|
773
|
+
</div>
|
|
774
|
+
))}
|
|
775
|
+
</div>
|
|
776
|
+
)}
|
|
777
|
+
|
|
778
|
+
{/* Relationships */}
|
|
779
|
+
<div className="text-[10px] text-gray-600 uppercase tracking-wider mb-2">
|
|
780
|
+
Relationships ({edges.length})
|
|
781
|
+
</div>
|
|
782
|
+
<div className="space-y-1 mb-4">
|
|
783
|
+
{edges.map((e, i) => {
|
|
784
|
+
const relColor = RELATION_COLORS[e.relation] || 'text-gray-400';
|
|
785
|
+
const target = e.direction === 'out' ? e.target_id : e.source_id;
|
|
786
|
+
const arrow = e.direction === 'out' ? '→' : '←';
|
|
787
|
+
return (
|
|
788
|
+
<div key={i} className="flex items-center gap-1.5 text-[11px] bg-white/[0.02] rounded px-2 py-1.5">
|
|
789
|
+
<span className={relColor}>{e.relation}</span>
|
|
790
|
+
<span className="text-gray-600">{arrow}</span>
|
|
791
|
+
<span className="text-gray-300 truncate">{target.replace(/^(person|team|system|topic|project|department|organization|module)-/, '')}</span>
|
|
792
|
+
{e.weight < 1 && (
|
|
793
|
+
<span className="text-gray-600 ml-auto text-[9px]">{e.weight.toFixed(2)}</span>
|
|
794
|
+
)}
|
|
795
|
+
</div>
|
|
796
|
+
);
|
|
797
|
+
})}
|
|
798
|
+
{edges.length === 0 && (
|
|
799
|
+
<div className="text-[11px] text-gray-600 py-2">No relationships</div>
|
|
800
|
+
)}
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
- [ ] **Step 2: Commit**
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
git add src/components/cortex/entity-detail.tsx
|
|
811
|
+
git commit -m "feat(cortex): add entity detail panel for graph view"
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## Chunk 4: Knowledge Tab, Context Tab, Final Wiring
|
|
817
|
+
|
|
818
|
+
### Task 9: Knowledge tab component
|
|
819
|
+
|
|
820
|
+
**Files:**
|
|
821
|
+
- Create: `src/components/cortex/knowledge-tab.tsx`
|
|
822
|
+
|
|
823
|
+
- [ ] **Step 1: Create the component**
|
|
824
|
+
|
|
825
|
+
A search + list view that reuses `KnowledgeCard`. Fetches from `/api/cortex/search`.
|
|
826
|
+
|
|
827
|
+
```typescript
|
|
828
|
+
'use client';
|
|
829
|
+
|
|
830
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
831
|
+
import { Search } from 'lucide-react';
|
|
832
|
+
import { api } from '@/lib/api';
|
|
833
|
+
import { KnowledgeCard } from './knowledge-card';
|
|
834
|
+
|
|
835
|
+
export function KnowledgeTab() {
|
|
836
|
+
const [query, setQuery] = useState('');
|
|
837
|
+
const [results, setResults] = useState<any[]>([]);
|
|
838
|
+
const [loading, setLoading] = useState(false);
|
|
839
|
+
|
|
840
|
+
const fetchResults = useCallback(async (q?: string) => {
|
|
841
|
+
setLoading(true);
|
|
842
|
+
try {
|
|
843
|
+
const params = new URLSearchParams({ limit: '30' });
|
|
844
|
+
if (q) params.set('q', q);
|
|
845
|
+
const res = await fetch(api(`/api/cortex/search?${params}`));
|
|
846
|
+
if (res.ok) setResults((await res.json()).results || []);
|
|
847
|
+
} catch {}
|
|
848
|
+
setLoading(false);
|
|
849
|
+
}, []);
|
|
850
|
+
|
|
851
|
+
useEffect(() => { fetchResults(); }, [fetchResults]);
|
|
852
|
+
|
|
853
|
+
const handleSearch = () => fetchResults(query.trim() || undefined);
|
|
854
|
+
const handleDelete = (id: string) => setResults(prev => prev.filter(r => r.id !== id));
|
|
855
|
+
|
|
856
|
+
return (
|
|
857
|
+
<div className="flex flex-col h-full">
|
|
858
|
+
<div className="p-4 border-b border-white/5">
|
|
859
|
+
<div className="relative max-w-md">
|
|
860
|
+
<Search className="absolute left-3 top-2.5 w-4 h-4 text-gray-500" />
|
|
861
|
+
<input
|
|
862
|
+
value={query}
|
|
863
|
+
onChange={e => setQuery(e.target.value)}
|
|
864
|
+
onKeyDown={e => e.key === 'Enter' && handleSearch()}
|
|
865
|
+
placeholder="Search knowledge..."
|
|
866
|
+
className="w-full pl-9 pr-4 py-2 text-sm bg-white/5 border border-white/10 rounded-lg text-gray-300 placeholder-gray-600 focus:outline-none focus:border-purple-500/50"
|
|
867
|
+
/>
|
|
868
|
+
</div>
|
|
869
|
+
</div>
|
|
870
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
871
|
+
<div className="max-w-2xl space-y-2">
|
|
872
|
+
{loading && <p className="text-sm text-gray-500 text-center py-8">Loading...</p>}
|
|
873
|
+
{!loading && results.length === 0 && (
|
|
874
|
+
<p className="text-sm text-gray-500 text-center py-8">No knowledge found</p>
|
|
875
|
+
)}
|
|
876
|
+
{results.map(unit => (
|
|
877
|
+
<KnowledgeCard key={unit.id} unit={unit} onDelete={handleDelete} />
|
|
878
|
+
))}
|
|
879
|
+
</div>
|
|
880
|
+
</div>
|
|
881
|
+
</div>
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
- [ ] **Step 2: Commit**
|
|
887
|
+
|
|
888
|
+
```bash
|
|
889
|
+
git add src/components/cortex/knowledge-tab.tsx
|
|
890
|
+
git commit -m "feat(cortex): add knowledge tab with search and v2 cards"
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
---
|
|
894
|
+
|
|
895
|
+
### Task 10: Context assembly tab
|
|
896
|
+
|
|
897
|
+
**Files:**
|
|
898
|
+
- Create: `src/components/cortex/context-tab.tsx`
|
|
899
|
+
|
|
900
|
+
- [ ] **Step 1: Create the component**
|
|
901
|
+
|
|
902
|
+
```typescript
|
|
903
|
+
'use client';
|
|
904
|
+
|
|
905
|
+
import { useState } from 'react';
|
|
906
|
+
import { Search, ChevronDown, ChevronUp } from 'lucide-react';
|
|
907
|
+
import { api } from '@/lib/api';
|
|
908
|
+
|
|
909
|
+
const INTENT_COLORS: Record<string, string> = {
|
|
910
|
+
debugging: 'text-red-400',
|
|
911
|
+
architecture: 'text-blue-400',
|
|
912
|
+
onboarding: 'text-green-400',
|
|
913
|
+
policy: 'text-purple-400',
|
|
914
|
+
'how-to': 'text-amber-400',
|
|
915
|
+
review: 'text-pink-400',
|
|
916
|
+
security: 'text-red-500',
|
|
917
|
+
general: 'text-gray-400',
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
export function ContextTab() {
|
|
921
|
+
const [query, setQuery] = useState('');
|
|
922
|
+
const [result, setResult] = useState<any>(null);
|
|
923
|
+
const [loading, setLoading] = useState(false);
|
|
924
|
+
const [showRaw, setShowRaw] = useState(false);
|
|
925
|
+
|
|
926
|
+
const handleAnalyze = async () => {
|
|
927
|
+
if (!query.trim()) return;
|
|
928
|
+
setLoading(true);
|
|
929
|
+
try {
|
|
930
|
+
const res = await fetch(api(`/api/cortex/context?q=${encodeURIComponent(query)}&limit=5`));
|
|
931
|
+
if (res.ok) setResult(await res.json());
|
|
932
|
+
} catch {}
|
|
933
|
+
setLoading(false);
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
return (
|
|
937
|
+
<div className="p-6 max-w-3xl mx-auto">
|
|
938
|
+
{/* Query input */}
|
|
939
|
+
<div className="flex gap-3 mb-6">
|
|
940
|
+
<div className="flex-1 relative">
|
|
941
|
+
<Search className="absolute left-3 top-2.5 w-4 h-4 text-gray-500" />
|
|
942
|
+
<input
|
|
943
|
+
value={query}
|
|
944
|
+
onChange={e => setQuery(e.target.value)}
|
|
945
|
+
onKeyDown={e => e.key === 'Enter' && handleAnalyze()}
|
|
946
|
+
placeholder="Enter a query to analyze..."
|
|
947
|
+
className="w-full pl-9 pr-4 py-2 text-sm bg-white/5 border border-white/10 rounded-lg text-gray-300 placeholder-gray-600 focus:outline-none focus:border-purple-500/50"
|
|
948
|
+
/>
|
|
949
|
+
</div>
|
|
950
|
+
<button
|
|
951
|
+
onClick={handleAnalyze}
|
|
952
|
+
disabled={loading}
|
|
953
|
+
className="px-5 py-2 text-sm bg-purple-600 hover:bg-purple-500 text-white rounded-lg disabled:opacity-50"
|
|
954
|
+
>
|
|
955
|
+
{loading ? 'Analyzing...' : 'Analyze'}
|
|
956
|
+
</button>
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
{!result && !loading && (
|
|
960
|
+
<p className="text-sm text-gray-600 text-center py-12">
|
|
961
|
+
Enter a query to see how the Context Assembly Engine processes it
|
|
962
|
+
</p>
|
|
963
|
+
)}
|
|
964
|
+
|
|
965
|
+
{result && (
|
|
966
|
+
<>
|
|
967
|
+
{/* Pipeline summary */}
|
|
968
|
+
<div className="grid grid-cols-3 gap-3 mb-6">
|
|
969
|
+
<div className="bg-white/[0.02] border border-white/5 rounded-lg p-3">
|
|
970
|
+
<div className="text-[10px] text-gray-600 uppercase mb-1">Intent</div>
|
|
971
|
+
<div className={`text-lg font-semibold ${INTENT_COLORS[result.intent?.intent] || 'text-gray-400'}`}>
|
|
972
|
+
{result.intent?.intent || '?'}
|
|
973
|
+
</div>
|
|
974
|
+
<div className="text-[10px] text-gray-600">
|
|
975
|
+
confidence: {(result.intent?.confidence ?? 0).toFixed(2)}
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
<div className="bg-white/[0.02] border border-white/5 rounded-lg p-3">
|
|
979
|
+
<div className="text-[10px] text-gray-600 uppercase mb-1">Entities</div>
|
|
980
|
+
<div className="flex flex-wrap gap-1 mt-1">
|
|
981
|
+
{(result.entities || []).map((e: any, i: number) => (
|
|
982
|
+
<span key={i} className="text-[10px] px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-gray-300">
|
|
983
|
+
{e.entity?.type}:{e.entity?.name}
|
|
984
|
+
</span>
|
|
985
|
+
))}
|
|
986
|
+
{(!result.entities || result.entities.length === 0) && (
|
|
987
|
+
<span className="text-[10px] text-gray-600">none detected</span>
|
|
988
|
+
)}
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
991
|
+
<div className="bg-white/[0.02] border border-white/5 rounded-lg p-3">
|
|
992
|
+
<div className="text-[10px] text-gray-600 uppercase mb-1">Timing</div>
|
|
993
|
+
<div className="text-lg font-semibold text-green-400">
|
|
994
|
+
{result.timing?.totalMs ?? '?'}ms
|
|
995
|
+
</div>
|
|
996
|
+
<div className="text-[10px] text-gray-600">
|
|
997
|
+
intent {result.timing?.intentMs}ms · search {result.timing?.searchMs}ms
|
|
998
|
+
</div>
|
|
999
|
+
</div>
|
|
1000
|
+
</div>
|
|
1001
|
+
|
|
1002
|
+
{/* Source weights */}
|
|
1003
|
+
{result.sourceWeights && (
|
|
1004
|
+
<div className="mb-6">
|
|
1005
|
+
<div className="text-[10px] text-gray-600 uppercase mb-2">Source Weights</div>
|
|
1006
|
+
<div className="flex gap-3">
|
|
1007
|
+
{result.sourceWeights.map((sw: any, i: number) => (
|
|
1008
|
+
<div key={i} className="flex-1 bg-white/[0.02] rounded-lg p-3">
|
|
1009
|
+
<div className="flex justify-between text-[11px] mb-1">
|
|
1010
|
+
<span className="text-gray-300 capitalize">{sw.scopeLevel}</span>
|
|
1011
|
+
<span className="text-gray-500">{sw.weight.toFixed(2)}</span>
|
|
1012
|
+
</div>
|
|
1013
|
+
<div className="h-1.5 bg-white/5 rounded-full overflow-hidden">
|
|
1014
|
+
<div
|
|
1015
|
+
className="h-full bg-purple-500/60 rounded-full"
|
|
1016
|
+
style={{ width: `${Math.min(100, sw.weight * 100)}%` }}
|
|
1017
|
+
/>
|
|
1018
|
+
</div>
|
|
1019
|
+
</div>
|
|
1020
|
+
))}
|
|
1021
|
+
</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
)}
|
|
1024
|
+
|
|
1025
|
+
{/* Results */}
|
|
1026
|
+
<div className="mb-6">
|
|
1027
|
+
<div className="text-[10px] text-gray-600 uppercase mb-2">
|
|
1028
|
+
Results ({(result.results || []).length})
|
|
1029
|
+
</div>
|
|
1030
|
+
<div className="space-y-1">
|
|
1031
|
+
{(result.results || []).map((r: any, i: number) => (
|
|
1032
|
+
<div key={i} className="flex items-center gap-2 bg-white/[0.02] border border-white/5 rounded-lg px-3 py-2">
|
|
1033
|
+
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${
|
|
1034
|
+
r.type === 'error_fix' ? 'bg-amber-500/20 text-amber-400'
|
|
1035
|
+
: r.type === 'decision' ? 'bg-blue-500/20 text-blue-400'
|
|
1036
|
+
: 'bg-gray-500/20 text-gray-400'
|
|
1037
|
+
}`}>{r.type?.replace('_', ' ')}</span>
|
|
1038
|
+
<span className="text-xs text-gray-300 truncate flex-1">{r.text}</span>
|
|
1039
|
+
<span className="text-[10px] text-gray-600 tabular-nums shrink-0">
|
|
1040
|
+
{(r.relevance_score ?? 0).toFixed(3)}
|
|
1041
|
+
</span>
|
|
1042
|
+
</div>
|
|
1043
|
+
))}
|
|
1044
|
+
</div>
|
|
1045
|
+
</div>
|
|
1046
|
+
|
|
1047
|
+
{/* Conflicts */}
|
|
1048
|
+
{Array.isArray(result.conflicts) && result.conflicts.length > 0 && (
|
|
1049
|
+
<div className="mb-6 border border-amber-500/20 bg-amber-500/5 rounded-lg p-3">
|
|
1050
|
+
<div className="text-[10px] text-amber-400 uppercase font-medium mb-1">
|
|
1051
|
+
Conflicts ({result.conflicts.length})
|
|
1052
|
+
</div>
|
|
1053
|
+
{result.conflicts.map((c: any, i: number) => (
|
|
1054
|
+
<div key={i} className="text-xs text-gray-400 mt-1">
|
|
1055
|
+
“{c.unitA?.text?.slice(0, 60)}...” vs “{c.unitB?.text?.slice(0, 60)}...”
|
|
1056
|
+
</div>
|
|
1057
|
+
))}
|
|
1058
|
+
</div>
|
|
1059
|
+
)}
|
|
1060
|
+
|
|
1061
|
+
{/* Raw context */}
|
|
1062
|
+
<div>
|
|
1063
|
+
<button
|
|
1064
|
+
onClick={() => setShowRaw(!showRaw)}
|
|
1065
|
+
className="flex items-center gap-1 text-[10px] text-gray-600 hover:text-gray-400 uppercase"
|
|
1066
|
+
>
|
|
1067
|
+
Raw context {showRaw ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
|
1068
|
+
</button>
|
|
1069
|
+
{showRaw && result.context && (
|
|
1070
|
+
<pre className="mt-2 p-3 bg-white/[0.02] border border-white/5 rounded-lg text-[11px] text-gray-400 overflow-x-auto whitespace-pre-wrap">
|
|
1071
|
+
{result.context}
|
|
1072
|
+
</pre>
|
|
1073
|
+
)}
|
|
1074
|
+
</div>
|
|
1075
|
+
</>
|
|
1076
|
+
)}
|
|
1077
|
+
</div>
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
- [ ] **Step 2: Commit**
|
|
1083
|
+
|
|
1084
|
+
```bash
|
|
1085
|
+
git add src/components/cortex/context-tab.tsx
|
|
1086
|
+
git commit -m "feat(cortex): add context assembly inspector tab"
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
## Summary
|
|
1092
|
+
|
|
1093
|
+
| Task | Component | Status |
|
|
1094
|
+
|------|-----------|--------|
|
|
1095
|
+
| 1 | listAllEdges() + edges all=true | |
|
|
1096
|
+
| 2 | Context API expansion (entities, conflicts, sourceWeights) | |
|
|
1097
|
+
| 3 | Enhanced knowledge card (v2 fields) | |
|
|
1098
|
+
| 4 | Sidebar Cortex nav item | |
|
|
1099
|
+
| 5 | /cortex page with tabs | |
|
|
1100
|
+
| 6 | Panel "Open full view" link | |
|
|
1101
|
+
| 7 | Entity graph canvas (force-graph) | |
|
|
1102
|
+
| 8 | Entity detail panel | |
|
|
1103
|
+
| 9 | Knowledge tab | |
|
|
1104
|
+
| 10 | Context assembly tab | |
|
|
1105
|
+
|
|
1106
|
+
**Total: 10 tasks, 4 chunks**
|
|
1107
|
+
|
|
1108
|
+
**No tests in this plan** — these are UI components that render from existing tested APIs. The backend changes (Tasks 1-2) are trivial additions to already-tested modules.
|