@geminilight/mindos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.local.example +38 -0
- package/LICENSE +21 -0
- package/README.md +423 -0
- package/README_zh.md +423 -0
- package/app/README.md +152 -0
- package/app/app/api/ask/route.ts +170 -0
- package/app/app/api/ask-sessions/route.ts +90 -0
- package/app/app/api/auth/route.ts +37 -0
- package/app/app/api/backlinks/route.ts +22 -0
- package/app/app/api/bootstrap/route.ts +37 -0
- package/app/app/api/extract-pdf/route.ts +82 -0
- package/app/app/api/file/route.ts +138 -0
- package/app/app/api/files/route.ts +12 -0
- package/app/app/api/git/route.ts +42 -0
- package/app/app/api/graph/route.ts +113 -0
- package/app/app/api/recent-files/route.ts +10 -0
- package/app/app/api/search/route.ts +17 -0
- package/app/app/api/settings/reset-token/route.ts +21 -0
- package/app/app/api/settings/route.ts +123 -0
- package/app/app/error.tsx +33 -0
- package/app/app/globals.css +368 -0
- package/app/app/icon.svg +35 -0
- package/app/app/layout.tsx +103 -0
- package/app/app/login/page.tsx +120 -0
- package/app/app/page.tsx +12 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +343 -0
- package/app/app/view/[...path]/error.tsx +33 -0
- package/app/app/view/[...path]/loading.tsx +15 -0
- package/app/app/view/[...path]/page.tsx +93 -0
- package/app/components/AskFab.tsx +59 -0
- package/app/components/AskModal.tsx +398 -0
- package/app/components/Backlinks.tsx +75 -0
- package/app/components/Breadcrumb.tsx +31 -0
- package/app/components/CsvView.tsx +325 -0
- package/app/components/DirView.tsx +138 -0
- package/app/components/Editor.tsx +124 -0
- package/app/components/EditorWrapper.tsx +17 -0
- package/app/components/ErrorBoundary.tsx +53 -0
- package/app/components/FileTree.tsx +369 -0
- package/app/components/HomeContent.tsx +262 -0
- package/app/components/JsonView.tsx +27 -0
- package/app/components/MarkdownEditor.tsx +95 -0
- package/app/components/MarkdownView.tsx +118 -0
- package/app/components/SearchModal.tsx +193 -0
- package/app/components/SettingsModal.tsx +237 -0
- package/app/components/Sidebar.tsx +136 -0
- package/app/components/SidebarLayout.tsx +36 -0
- package/app/components/TableOfContents.tsx +150 -0
- package/app/components/ThemeToggle.tsx +34 -0
- package/app/components/WysiwygEditor.tsx +75 -0
- package/app/components/ask/FileChip.tsx +30 -0
- package/app/components/ask/MentionPopover.tsx +52 -0
- package/app/components/ask/MessageList.tsx +126 -0
- package/app/components/ask/SessionHistory.tsx +49 -0
- package/app/components/renderers/AgentInspectorRenderer.tsx +277 -0
- package/app/components/renderers/BacklinksRenderer.tsx +147 -0
- package/app/components/renderers/ConfigRenderer.tsx +236 -0
- package/app/components/renderers/CsvRenderer.tsx +77 -0
- package/app/components/renderers/DiffRenderer.tsx +310 -0
- package/app/components/renderers/GraphRenderer.tsx +428 -0
- package/app/components/renderers/SummaryRenderer.tsx +251 -0
- package/app/components/renderers/TimelineRenderer.tsx +213 -0
- package/app/components/renderers/TodoRenderer.tsx +474 -0
- package/app/components/renderers/WorkflowRenderer.tsx +404 -0
- package/app/components/renderers/csv/BoardView.tsx +146 -0
- package/app/components/renderers/csv/ConfigPanel.tsx +117 -0
- package/app/components/renderers/csv/EditableCell.tsx +43 -0
- package/app/components/renderers/csv/GalleryView.tsx +40 -0
- package/app/components/renderers/csv/TableView.tsx +164 -0
- package/app/components/renderers/csv/types.ts +87 -0
- package/app/components/settings/AiTab.tsx +111 -0
- package/app/components/settings/AppearanceTab.tsx +101 -0
- package/app/components/settings/KnowledgeTab.tsx +157 -0
- package/app/components/settings/PluginsTab.tsx +82 -0
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/ShortcutsTab.tsx +22 -0
- package/app/components/settings/types.ts +41 -0
- package/app/components/ui/button.tsx +60 -0
- package/app/components/ui/dialog.tsx +157 -0
- package/app/components/ui/input.tsx +20 -0
- package/app/components/ui/scroll-area.tsx +55 -0
- package/app/components/ui/toggle.tsx +44 -0
- package/app/components/ui/tooltip.tsx +66 -0
- package/app/components.json +25 -0
- package/app/data/pages/home-dark.png +0 -0
- package/app/data/pages/home-mobile-crop.png +0 -0
- package/app/data/pages/home-mobile.png +0 -0
- package/app/data/pages/home.png +0 -0
- package/app/data/pages/view-dir.png +0 -0
- package/app/data/pages/view-file-bot.png +0 -0
- package/app/data/pages/view-file-dark-crop.png +0 -0
- package/app/data/pages/view-file-dark.png +0 -0
- package/app/data/pages/view-file-mobile.png +0 -0
- package/app/data/pages/view-file-sm.png +0 -0
- package/app/data/pages/view-file-top.png +0 -0
- package/app/data/pages/view-file.png +0 -0
- package/app/eslint.config.mjs +18 -0
- package/app/hooks/useAskSession.ts +181 -0
- package/app/hooks/useFileUpload.ts +126 -0
- package/app/hooks/useMention.ts +65 -0
- package/app/lib/LocaleContext.tsx +40 -0
- package/app/lib/actions.ts +40 -0
- package/app/lib/agent/index.ts +3 -0
- package/app/lib/agent/model.ts +18 -0
- package/app/lib/agent/prompt.ts +32 -0
- package/app/lib/agent/tools.ts +151 -0
- package/app/lib/api.ts +55 -0
- package/app/lib/core/backlinks.ts +40 -0
- package/app/lib/core/csv.ts +28 -0
- package/app/lib/core/fs-ops.ts +118 -0
- package/app/lib/core/git.ts +50 -0
- package/app/lib/core/index.ts +58 -0
- package/app/lib/core/lines.ts +89 -0
- package/app/lib/core/search.ts +79 -0
- package/app/lib/core/security.ts +43 -0
- package/app/lib/core/tree.ts +113 -0
- package/app/lib/core/types.ts +40 -0
- package/app/lib/fs.ts +467 -0
- package/app/lib/i18n.ts +300 -0
- package/app/lib/jwt.ts +58 -0
- package/app/lib/renderers/index.ts +79 -0
- package/app/lib/renderers/registry.ts +70 -0
- package/app/lib/settings.ts +150 -0
- package/app/lib/types.ts +32 -0
- package/app/lib/utils.ts +34 -0
- package/app/next-env.d.ts +6 -0
- package/app/next.config.ts +10 -0
- package/app/package-lock.json +15306 -0
- package/app/package.json +71 -0
- package/app/postcss.config.mjs +7 -0
- package/app/proxy.ts +64 -0
- package/app/public/file.svg +1 -0
- package/app/public/globe.svg +1 -0
- package/app/public/landing/index.html +353 -0
- package/app/public/landing/style.css +216 -0
- package/app/public/logo-square.svg +37 -0
- package/app/public/logo.svg +37 -0
- package/app/public/next.svg +1 -0
- package/app/public/vercel.svg +1 -0
- package/app/public/window.svg +1 -0
- package/app/scripts/extract-pdf.cjs +56 -0
- package/app/tsconfig.json +34 -0
- package/app/vitest.config.ts +14 -0
- package/assets/demo-flow-zh.html +622 -0
- package/assets/images/demo-flow-dark.png +0 -0
- package/assets/images/demo-flow-light.png +0 -0
- package/assets/images/demo-flow-zh-dark.png +0 -0
- package/assets/images/demo-flow-zh-light.png +0 -0
- package/assets/images/gui-sync-cv.png +0 -0
- package/assets/logo-square.svg +37 -0
- package/bin/cli.js +894 -0
- package/mcp/README.md +113 -0
- package/mcp/package-lock.json +1717 -0
- package/mcp/package.json +18 -0
- package/mcp/src/index.ts +494 -0
- package/mcp/tsconfig.json +13 -0
- package/package.json +49 -0
- package/scripts/setup.js +675 -0
- package/scripts/upgrade-prompt.md +147 -0
- package/skills/mindos/SKILL.md +319 -0
- package/skills/mindos-zh/SKILL.md +318 -0
- package/templates/README.md +31 -0
- package/templates/empty/CHANGELOG.md +9 -0
- package/templates/empty/CONFIG.json +197 -0
- package/templates/empty/CONFIG.md +73 -0
- package/templates/empty/INSTRUCTION.md +177 -0
- package/templates/empty/README.md +27 -0
- package/templates/en/CHANGELOG.md +9 -0
- package/templates/en/CONFIG.json +197 -0
- package/templates/en/CONFIG.md +73 -0
- package/templates/en/INSTRUCTION.md +177 -0
- package/templates/en/README.md +27 -0
- package/templates/en/TODO.md +13 -0
- package/templates/en//360/237/221/244 Profile/INSTRUCTION.md" +21 -0
- package/templates/en//360/237/221/244 Profile/README.md" +15 -0
- package/templates/en//360/237/221/244 Profile//342/232/231/357/270/217 Preferences.md" +21 -0
- package/templates/en//360/237/221/244 Profile//360/237/216/257 Focus.md" +31 -0
- package/templates/en//360/237/221/244 Profile//360/237/221/244 Identity.md" +22 -0
- package/templates/en//360/237/223/232 Resources/INSTRUCTION.md" +29 -0
- package/templates/en//360/237/223/232 Resources/README.md" +21 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +1 -0
- package/templates/en//360/237/223/235 Notes/INSTRUCTION.md" +31 -0
- package/templates/en//360/237/223/235 Notes/Ideas/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +16 -0
- package/templates/en//360/237/223/235 Notes/Inbox/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Inbox//360/237/247/252_example_quick_capture.md" +14 -0
- package/templates/en//360/237/223/235 Notes/Meetings/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Meetings//360/237/247/252_example_meeting_note.md" +17 -0
- package/templates/en//360/237/223/235 Notes/README.md" +24 -0
- package/templates/en//360/237/223/235 Notes/Waiting/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Waiting//360/237/247/252_example_blocked_item.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Configurations/README.md" +3 -0
- package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +14 -0
- package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +21 -0
- package/templates/en//360/237/224/204 Workflows/Information/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Information//360/237/247/252_example_info_capture_sop.md" +13 -0
- package/templates/en//360/237/224/204 Workflows/Media/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Media//360/237/247/252_example_content_publish_sop.md" +13 -0
- package/templates/en//360/237/224/204 Workflows/README.md" +22 -0
- package/templates/en//360/237/224/204 Workflows/Research/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Research//360/237/247/252_example_lit_review_sop.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Startup/README.md" +3 -0
- package/templates/en//360/237/224/204 Workflows/Startup//360/237/247/252_example_weekly_founder_ops.md" +22 -0
- package/templates/en//360/237/224/227 Connections/Classmates/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Classmates//360/237/247/252_example_leo_chen.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Colleagues/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Colleagues//360/237/247/252_example_ethan_zhao.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Connections Overview.csv" +5 -0
- package/templates/en//360/237/224/227 Connections/Family/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Family//360/237/247/252_example_james_wang.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Friends/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Friends//360/237/247/252_example_lily_lin.md" +16 -0
- package/templates/en//360/237/224/227 Connections/INSTRUCTION.md" +56 -0
- package/templates/en//360/237/224/227 Connections/README.md" +20 -0
- package/templates/en//360/237/232/200 Projects/Archived/README.md" +9 -0
- package/templates/en//360/237/232/200 Projects/Archived//360/237/247/252_example_archived_project_note.md" +14 -0
- package/templates/en//360/237/232/200 Projects/INSTRUCTION.md" +29 -0
- package/templates/en//360/237/232/200 Projects/Products/README.md" +16 -0
- package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +20 -0
- package/templates/en//360/237/232/200 Projects/README.md" +21 -0
- package/templates/en//360/237/232/200 Projects/Research/README.md" +16 -0
- package/templates/en//360/237/232/200 Projects/Research//360/237/247/252_example_research_project_brief.md" +16 -0
- package/templates/template-generation-skill.md +79 -0
- package/templates/zh/CHANGELOG.md +9 -0
- package/templates/zh/CONFIG.json +197 -0
- package/templates/zh/CONFIG.md +66 -0
- package/templates/zh/INSTRUCTION.md +177 -0
- package/templates/zh/README.md +27 -0
- package/templates/zh/TODO.md +13 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/INSTRUCTION.md" +28 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +20 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/217 Preferences.md" +21 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/257 Focus.md" +31 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/244 Identity.md" +22 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/INSTRUCTION.md" +29 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +21 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/INSTRUCTION.md" +31 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +24 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256//360/237/247/252_example_/344/274/232/350/256/256/347/272/252/350/246/201.md" +17 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210//360/237/247/252_example_/345/276/205/345/217/215/351/246/210/344/272/213/351/241/271.md" +16 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +16 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261//360/237/247/252_example_/344/270/264/346/227/266/351/200/237/350/256/260.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/INSTRUCTION.md" +29 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +21 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257//360/237/247/252_example_/344/277/241/346/201/257/351/207/207/351/233/206/346/265/201/347/250/213.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223//360/237/247/252_example_/345/206/205/345/256/271/345/217/221/345/270/203/346/265/201/347/250/213.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224//360/237/247/252_example_/346/226/207/347/214/256/347/273/274/350/277/260/346/265/201/347/250/213.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256/README.md" +3 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +26 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273/INSTRUCTION.md" +62 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273/README.md" +20 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/205/263/347/263/273/346/200/273/350/247/210.csv" +5 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213//360/237/247/252_example_/345/220/214/344/272/213/350/265/265/344/270/200/350/276/260.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246//360/237/247/252_example_/345/220/214/345/255/246/351/231/210/347/253/213/346/254/247.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272//360/237/247/252_example_/345/256/266/344/272/272/347/216/213/345/273/272/345/233/275.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213//360/237/247/252_example_/346/234/213/345/217/213/346/236/227/345/260/217/344/270/275.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256/INSTRUCTION.md" +31 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256/README.md" +21 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201/README.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +20 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243/README.md" +9 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243//360/237/247/252_example_/345/275/222/346/241/243/351/241/271/347/233/256/350/256/260/345/275/225.md" +15 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224/README.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224//360/237/247/252_example_/347/247/221/347/240/224/351/241/271/347/233/256/347/256/200/346/212/245.md" +16 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Trash2 } from 'lucide-react';
|
|
4
|
+
import type { ChatSession } from '@/lib/types';
|
|
5
|
+
import { sessionTitle } from '@/hooks/useAskSession';
|
|
6
|
+
|
|
7
|
+
interface SessionHistoryProps {
|
|
8
|
+
sessions: ChatSession[];
|
|
9
|
+
activeSessionId: string | null;
|
|
10
|
+
onLoad: (id: string) => void;
|
|
11
|
+
onDelete: (id: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function SessionHistory({ sessions, activeSessionId, onLoad, onDelete }: SessionHistoryProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="border-b border-border px-4 py-2.5 max-h-[190px] overflow-y-auto">
|
|
17
|
+
<div className="text-[11px] text-muted-foreground mb-2">Session History</div>
|
|
18
|
+
<div className="flex flex-col gap-1.5">
|
|
19
|
+
{sessions.length === 0 && (
|
|
20
|
+
<div className="text-xs text-muted-foreground/70">No saved sessions.</div>
|
|
21
|
+
)}
|
|
22
|
+
{sessions.map((s) => (
|
|
23
|
+
<div key={s.id} className="flex items-center gap-1.5">
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
onClick={() => onLoad(s.id)}
|
|
27
|
+
className={`flex-1 text-left px-2 py-1.5 rounded text-xs transition-colors ${
|
|
28
|
+
activeSessionId === s.id
|
|
29
|
+
? 'bg-accent text-foreground'
|
|
30
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
31
|
+
}`}
|
|
32
|
+
>
|
|
33
|
+
<div className="truncate">{sessionTitle(s)}</div>
|
|
34
|
+
<div className="text-[10px] opacity-60">{new Date(s.updatedAt).toLocaleString()}</div>
|
|
35
|
+
</button>
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={() => onDelete(s.id)}
|
|
39
|
+
className="p-1 rounded text-muted-foreground hover:text-red-400 hover:bg-muted"
|
|
40
|
+
title="Delete session"
|
|
41
|
+
>
|
|
42
|
+
<Trash2 size={12} />
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { Terminal, FileEdit, FilePlus, Trash2, Search, Clock, ChevronDown, AlertCircle, CheckCircle2 } from 'lucide-react';
|
|
6
|
+
import type { RendererContext } from '@/lib/renderers/registry';
|
|
7
|
+
|
|
8
|
+
// ─── Log entry format ─────────────────────────────────────────────────────────
|
|
9
|
+
// Each entry is a fenced JSON block in the markdown:
|
|
10
|
+
//
|
|
11
|
+
// ```agent-op
|
|
12
|
+
// { "ts": "2025-01-15T10:30:00Z", "tool": "mindos_write_file",
|
|
13
|
+
// "params": { "path": "...", "content": "..." },
|
|
14
|
+
// "result": "ok" | "error", "message": "..." }
|
|
15
|
+
// ```
|
|
16
|
+
|
|
17
|
+
interface AgentOp {
|
|
18
|
+
ts: string;
|
|
19
|
+
tool: string;
|
|
20
|
+
params: Record<string, unknown>;
|
|
21
|
+
result: 'ok' | 'error';
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
function parseOps(content: string): AgentOp[] {
|
|
28
|
+
const ops: AgentOp[] = [];
|
|
29
|
+
const re = /```agent-op\n([\s\S]*?)```/g;
|
|
30
|
+
let m: RegExpExecArray | null;
|
|
31
|
+
while ((m = re.exec(content)) !== null) {
|
|
32
|
+
try {
|
|
33
|
+
const op = JSON.parse(m[1].trim()) as AgentOp;
|
|
34
|
+
if (op.tool && op.ts) ops.push(op);
|
|
35
|
+
} catch { /* skip malformed */ }
|
|
36
|
+
}
|
|
37
|
+
// newest first
|
|
38
|
+
return ops.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Tool metadata ────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
type OpKind = 'read' | 'write' | 'create' | 'delete' | 'search' | 'other';
|
|
44
|
+
|
|
45
|
+
function opKind(tool: string): OpKind {
|
|
46
|
+
if (/read|list|get|search/.test(tool)) {
|
|
47
|
+
if (/search/.test(tool)) return 'search';
|
|
48
|
+
return 'read';
|
|
49
|
+
}
|
|
50
|
+
if (/create/.test(tool)) return 'create';
|
|
51
|
+
if (/delete/.test(tool)) return 'delete';
|
|
52
|
+
if (/write|update|insert|append/.test(tool)) return 'write';
|
|
53
|
+
return 'other';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const KIND_STYLE: Record<OpKind, { bg: string; text: string; border: string }> = {
|
|
57
|
+
read: { bg: 'rgba(138,180,216,0.10)', text: '#8ab4d8', border: 'rgba(138,180,216,0.25)' },
|
|
58
|
+
write: { bg: 'rgba(200,135,58,0.10)', text: 'var(--amber)', border: 'rgba(200,135,58,0.25)' },
|
|
59
|
+
create: { bg: 'rgba(122,173,128,0.10)', text: '#7aad80', border: 'rgba(122,173,128,0.25)' },
|
|
60
|
+
delete: { bg: 'rgba(200,80,80,0.10)', text: '#c85050', border: 'rgba(200,80,80,0.25)' },
|
|
61
|
+
search: { bg: 'rgba(200,160,216,0.10)', text: '#c8a0d8', border: 'rgba(200,160,216,0.25)' },
|
|
62
|
+
other: { bg: 'var(--muted)', text: 'var(--muted-foreground)', border: 'var(--border)' },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function OpIcon({ kind, size = 13 }: { kind: OpKind; size?: number }) {
|
|
66
|
+
if (kind === 'read') return <Clock size={size} />;
|
|
67
|
+
if (kind === 'write') return <FileEdit size={size} />;
|
|
68
|
+
if (kind === 'create') return <FilePlus size={size} />;
|
|
69
|
+
if (kind === 'delete') return <Trash2 size={size} />;
|
|
70
|
+
if (kind === 'search') return <Search size={size} />;
|
|
71
|
+
return <Terminal size={size} />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
function formatTs(ts: string): string {
|
|
77
|
+
try {
|
|
78
|
+
const d = new Date(ts);
|
|
79
|
+
return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
80
|
+
} catch { return ts; }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function relativeTs(ts: string): string {
|
|
84
|
+
const diff = Date.now() - new Date(ts).getTime();
|
|
85
|
+
const m = Math.floor(diff / 60000);
|
|
86
|
+
const h = Math.floor(diff / 3600000);
|
|
87
|
+
const d = Math.floor(diff / 86400000);
|
|
88
|
+
if (m < 1) return 'just now';
|
|
89
|
+
if (m < 60) return `${m}m ago`;
|
|
90
|
+
if (h < 24) return `${h}h ago`;
|
|
91
|
+
return `${d}d ago`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getFilePath(params: Record<string, unknown>): string | null {
|
|
95
|
+
return typeof params.path === 'string' ? params.path : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function truncateContent(v: unknown, max = 120): string {
|
|
99
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
100
|
+
return s.length > max ? s.slice(0, max) + '…' : s;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Single op card ───────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function OpCard({ op }: { op: AgentOp }) {
|
|
106
|
+
const router = useRouter();
|
|
107
|
+
const [expanded, setExpanded] = useState(false);
|
|
108
|
+
const kind = opKind(op.tool);
|
|
109
|
+
const style = KIND_STYLE[kind];
|
|
110
|
+
const filePath = getFilePath(op.params);
|
|
111
|
+
|
|
112
|
+
const toolShort = op.tool.replace('mindos_', '');
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div style={{
|
|
116
|
+
background: 'var(--card)',
|
|
117
|
+
border: `1px solid var(--border)`,
|
|
118
|
+
borderRadius: 10,
|
|
119
|
+
overflow: 'hidden',
|
|
120
|
+
marginBottom: 8,
|
|
121
|
+
}}>
|
|
122
|
+
{/* Header row */}
|
|
123
|
+
<div
|
|
124
|
+
style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', cursor: 'pointer', userSelect: 'none' }}
|
|
125
|
+
onClick={() => setExpanded(v => !v)}
|
|
126
|
+
>
|
|
127
|
+
{/* kind badge */}
|
|
128
|
+
<span style={{
|
|
129
|
+
display: 'inline-flex', alignItems: 'center', gap: 4,
|
|
130
|
+
padding: '2px 8px', borderRadius: 999, fontSize: '0.68rem',
|
|
131
|
+
fontFamily: "'IBM Plex Mono',monospace", fontWeight: 600,
|
|
132
|
+
background: style.bg, color: style.text, border: `1px solid ${style.border}`,
|
|
133
|
+
flexShrink: 0,
|
|
134
|
+
}}>
|
|
135
|
+
<OpIcon kind={kind} size={10} />
|
|
136
|
+
{kind}
|
|
137
|
+
</span>
|
|
138
|
+
|
|
139
|
+
{/* tool name */}
|
|
140
|
+
<span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.78rem', color: 'var(--foreground)', fontWeight: 600, flexShrink: 0 }}>
|
|
141
|
+
{toolShort}
|
|
142
|
+
</span>
|
|
143
|
+
|
|
144
|
+
{/* file path */}
|
|
145
|
+
{filePath && (
|
|
146
|
+
<span
|
|
147
|
+
style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', color: 'var(--amber)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' }}
|
|
148
|
+
onClick={e => { e.stopPropagation(); router.push('/view/' + filePath.split('/').map(encodeURIComponent).join('/')); }}
|
|
149
|
+
title={filePath}
|
|
150
|
+
>
|
|
151
|
+
{filePath}
|
|
152
|
+
</span>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
|
|
156
|
+
{/* result */}
|
|
157
|
+
{op.result === 'ok'
|
|
158
|
+
? <CheckCircle2 size={13} style={{ color: '#7aad80' }} />
|
|
159
|
+
: <AlertCircle size={13} style={{ color: '#c85050' }} />
|
|
160
|
+
}
|
|
161
|
+
{/* timestamp */}
|
|
162
|
+
<span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6 }} title={formatTs(op.ts)}>
|
|
163
|
+
{relativeTs(op.ts)}
|
|
164
|
+
</span>
|
|
165
|
+
{/* chevron */}
|
|
166
|
+
<ChevronDown size={12} style={{ color: 'var(--muted-foreground)', transform: expanded ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Expanded params */}
|
|
171
|
+
{expanded && (
|
|
172
|
+
<div style={{ borderTop: '1px solid var(--border)', padding: '10px 14px', background: 'var(--background)' }}>
|
|
173
|
+
{/* params table */}
|
|
174
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: op.message ? 8 : 0 }}>
|
|
175
|
+
{Object.entries(op.params).map(([k, v]) => (
|
|
176
|
+
<div key={k} style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
|
|
177
|
+
<span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.7, flexShrink: 0, minWidth: 80 }}>
|
|
178
|
+
{k}
|
|
179
|
+
</span>
|
|
180
|
+
<span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', color: 'var(--foreground)', wordBreak: 'break-all', lineHeight: 1.5 }}>
|
|
181
|
+
{truncateContent(v)}
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
))}
|
|
185
|
+
</div>
|
|
186
|
+
{/* result message */}
|
|
187
|
+
{op.message && (
|
|
188
|
+
<div style={{ marginTop: 6, padding: '5px 9px', borderRadius: 5, fontSize: '0.72rem', fontFamily: "'IBM Plex Mono',monospace",
|
|
189
|
+
background: op.result === 'error' ? 'rgba(200,80,80,0.08)' : 'rgba(122,173,128,0.08)',
|
|
190
|
+
color: op.result === 'error' ? '#c85050' : '#7aad80',
|
|
191
|
+
border: `1px solid ${op.result === 'error' ? 'rgba(200,80,80,0.2)' : 'rgba(122,173,128,0.2)'}`,
|
|
192
|
+
}}>
|
|
193
|
+
{op.message}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
{/* absolute timestamp */}
|
|
197
|
+
<div style={{ marginTop: 6, fontSize: '0.65rem', fontFamily: "'IBM Plex Mono',monospace", color: 'var(--muted-foreground)', opacity: 0.5 }}>
|
|
198
|
+
{formatTs(op.ts)}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── Filter bar ───────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
const KINDS: Array<OpKind | 'all'> = ['all', 'write', 'create', 'delete', 'read', 'search'];
|
|
209
|
+
|
|
210
|
+
// ─── Main renderer ────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
export function AgentInspectorRenderer({ content }: RendererContext) {
|
|
213
|
+
const [filter, setFilter] = useState<OpKind | 'all'>('all');
|
|
214
|
+
const ops = useMemo(() => parseOps(content), [content]);
|
|
215
|
+
|
|
216
|
+
const filtered = useMemo(() =>
|
|
217
|
+
filter === 'all' ? ops : ops.filter(op => opKind(op.tool) === filter),
|
|
218
|
+
[ops, filter]);
|
|
219
|
+
|
|
220
|
+
const counts = useMemo(() => {
|
|
221
|
+
const c: Record<string, number> = { all: ops.length };
|
|
222
|
+
for (const op of ops) {
|
|
223
|
+
const k = opKind(op.tool);
|
|
224
|
+
c[k] = (c[k] ?? 0) + 1;
|
|
225
|
+
}
|
|
226
|
+
return c;
|
|
227
|
+
}, [ops]);
|
|
228
|
+
|
|
229
|
+
if (ops.length === 0) {
|
|
230
|
+
return (
|
|
231
|
+
<div style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace", fontSize: 12 }}>
|
|
232
|
+
<Terminal size={28} style={{ margin: '0 auto 10px', opacity: 0.3 }} />
|
|
233
|
+
<p>No agent operations logged yet.</p>
|
|
234
|
+
<p style={{ marginTop: 6, opacity: 0.6, fontSize: 11 }}>
|
|
235
|
+
Agent writes appear here as <code style={{ background: 'var(--muted)', padding: '1px 5px', borderRadius: 4 }}>```agent-op</code> blocks.
|
|
236
|
+
</p>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div style={{ maxWidth: 760, margin: '0 auto', padding: '1.5rem 0' }}>
|
|
243
|
+
{/* filter bar */}
|
|
244
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: '1.2rem', flexWrap: 'wrap' }}>
|
|
245
|
+
{KINDS.map(k => {
|
|
246
|
+
const cnt = counts[k] ?? 0;
|
|
247
|
+
if (k !== 'all' && !cnt) return null;
|
|
248
|
+
const style = k !== 'all' ? KIND_STYLE[k] : undefined;
|
|
249
|
+
const active = filter === k;
|
|
250
|
+
return (
|
|
251
|
+
<button
|
|
252
|
+
key={k}
|
|
253
|
+
onClick={() => setFilter(k)}
|
|
254
|
+
style={{
|
|
255
|
+
display: 'inline-flex', alignItems: 'center', gap: 4,
|
|
256
|
+
padding: '3px 10px', borderRadius: 999, fontSize: '0.7rem',
|
|
257
|
+
fontFamily: "'IBM Plex Mono',monospace", cursor: 'pointer', border: 'none',
|
|
258
|
+
background: active ? (style?.bg ?? 'var(--accent)') : 'var(--muted)',
|
|
259
|
+
color: active ? (style?.text ?? 'var(--foreground)') : 'var(--muted-foreground)',
|
|
260
|
+
outline: active ? `1px solid ${style?.border ?? 'var(--border)'}` : 'none',
|
|
261
|
+
transition: 'all .1s',
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
{k !== 'all' && <OpIcon kind={k} size={10} />}
|
|
265
|
+
{k} <span style={{ opacity: 0.6 }}>({cnt})</span>
|
|
266
|
+
</button>
|
|
267
|
+
);
|
|
268
|
+
})}
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* ops list */}
|
|
272
|
+
<div>
|
|
273
|
+
{filtered.map((op, i) => <OpCard key={i} op={op} />)}
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { FileText, ExternalLink } from 'lucide-react';
|
|
6
|
+
import { encodePath } from '@/lib/utils';
|
|
7
|
+
import type { RendererContext } from '@/lib/renderers/registry';
|
|
8
|
+
import { apiFetch } from '@/lib/api';
|
|
9
|
+
|
|
10
|
+
interface BacklinkItem {
|
|
11
|
+
filePath: string;
|
|
12
|
+
snippets: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function basename(p: string) {
|
|
16
|
+
return p.split('/').pop()?.replace(/\.md$/, '') ?? p;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function dirname(p: string) {
|
|
20
|
+
const parts = p.split('/');
|
|
21
|
+
return parts.length > 1 ? parts.slice(0, -1).join('/') : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Highlight [[...]] and [text](url) references in snippet
|
|
25
|
+
function SnippetLine({ text }: { text: string }) {
|
|
26
|
+
// Replace wikilinks and md links with styled spans
|
|
27
|
+
const parts = text.split(/(\[\[[^\]]+\]\]|\[[^\]]+\]\([^)]+\))/g);
|
|
28
|
+
return (
|
|
29
|
+
<span>
|
|
30
|
+
{parts.map((part, i) => {
|
|
31
|
+
if (/^\[\[/.test(part) || /^\[/.test(part)) {
|
|
32
|
+
return <span key={i} style={{ color: 'var(--amber)', fontWeight: 500 }}>{part}</span>;
|
|
33
|
+
}
|
|
34
|
+
return <span key={i}>{part}</span>;
|
|
35
|
+
})}
|
|
36
|
+
</span>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function BacklinksRenderer({ filePath }: RendererContext) {
|
|
41
|
+
const router = useRouter();
|
|
42
|
+
const [backlinks, setBacklinks] = useState<BacklinkItem[] | null>(null);
|
|
43
|
+
const [loading, setLoading] = useState(true);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
apiFetch<BacklinkItem[]>(`/api/backlinks?path=${encodeURIComponent(filePath)}`)
|
|
48
|
+
.then((data) => { setBacklinks(data); setLoading(false); })
|
|
49
|
+
.catch(() => setLoading(false));
|
|
50
|
+
}, [filePath]);
|
|
51
|
+
|
|
52
|
+
if (loading) {
|
|
53
|
+
return (
|
|
54
|
+
<div style={{ padding: '3rem 1rem', textAlign: 'center', fontFamily: "'IBM Plex Mono',monospace", fontSize: 12, color: 'var(--muted-foreground)' }}>
|
|
55
|
+
Scanning backlinks…
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const items = backlinks ?? [];
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div style={{ maxWidth: 720, margin: '0 auto', padding: '1.5rem 0' }}>
|
|
64
|
+
{/* header */}
|
|
65
|
+
<div style={{ marginBottom: '1.5rem', display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
66
|
+
<span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 11, color: 'var(--muted-foreground)' }}>
|
|
67
|
+
{items.length === 0 ? 'No backlinks found' : `${items.length} file${items.length === 1 ? '' : 's'} link here`}
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{items.length === 0 ? (
|
|
72
|
+
<div style={{
|
|
73
|
+
border: '1px dashed var(--border)',
|
|
74
|
+
borderRadius: 10,
|
|
75
|
+
padding: '2.5rem 1.5rem',
|
|
76
|
+
textAlign: 'center',
|
|
77
|
+
color: 'var(--muted-foreground)',
|
|
78
|
+
fontSize: 13,
|
|
79
|
+
}}>
|
|
80
|
+
<FileText size={28} style={{ margin: '0 auto 10px', opacity: 0.3 }} />
|
|
81
|
+
<p style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 12 }}>
|
|
82
|
+
No other files link to <strong style={{ color: 'var(--foreground)' }}>{basename(filePath)}</strong> yet.
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
) : (
|
|
86
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
87
|
+
{items.map(({ filePath: src, snippets }) => {
|
|
88
|
+
const name = basename(src);
|
|
89
|
+
const dir = dirname(src);
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
key={src}
|
|
93
|
+
style={{
|
|
94
|
+
background: 'var(--card)',
|
|
95
|
+
border: '1px solid var(--border)',
|
|
96
|
+
borderRadius: 10,
|
|
97
|
+
overflow: 'hidden',
|
|
98
|
+
cursor: 'pointer',
|
|
99
|
+
transition: 'border-color .15s',
|
|
100
|
+
}}
|
|
101
|
+
onClick={() => router.push('/view/' + encodePath(src))}
|
|
102
|
+
onMouseEnter={e => (e.currentTarget.style.borderColor = 'rgba(200,135,58,0.4)')}
|
|
103
|
+
onMouseLeave={e => (e.currentTarget.style.borderColor = 'var(--border)')}
|
|
104
|
+
>
|
|
105
|
+
{/* file header */}
|
|
106
|
+
<div style={{
|
|
107
|
+
display: 'flex',
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
gap: 8,
|
|
110
|
+
padding: '10px 14px',
|
|
111
|
+
borderBottom: snippets.length > 0 ? '1px solid var(--border)' : 'none',
|
|
112
|
+
background: 'var(--muted)',
|
|
113
|
+
}}>
|
|
114
|
+
<FileText size={13} style={{ color: 'var(--muted-foreground)', flexShrink: 0 }} />
|
|
115
|
+
<span style={{ fontFamily: "'IBM Plex Sans',sans-serif", fontWeight: 600, fontSize: '0.85rem', color: 'var(--foreground)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
116
|
+
{name}
|
|
117
|
+
</span>
|
|
118
|
+
{dir && (
|
|
119
|
+
<span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6, flexShrink: 0 }}>
|
|
120
|
+
{dir}
|
|
121
|
+
</span>
|
|
122
|
+
)}
|
|
123
|
+
<ExternalLink size={11} style={{ color: 'var(--muted-foreground)', opacity: 0.5, flexShrink: 0 }} />
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* snippets */}
|
|
127
|
+
{snippets.map((snippet: string, i: number) => (
|
|
128
|
+
<div key={i} style={{
|
|
129
|
+
padding: '8px 14px',
|
|
130
|
+
borderBottom: i < snippets.length - 1 ? '1px solid var(--border)' : 'none',
|
|
131
|
+
background: 'var(--background)',
|
|
132
|
+
}}>
|
|
133
|
+
{snippet.split('\n').map((line: string, j: number) => (
|
|
134
|
+
<div key={j} style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', color: 'var(--muted-foreground)', lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
135
|
+
<SnippetLine text={line} />
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
))}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
})}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|