@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
package/app/lib/i18n.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
export type Locale = 'en' | 'zh';
|
|
2
|
+
|
|
3
|
+
export const messages = {
|
|
4
|
+
en: {
|
|
5
|
+
common: {
|
|
6
|
+
relatedFiles: 'Related Files',
|
|
7
|
+
},
|
|
8
|
+
app: {
|
|
9
|
+
tagline: 'Personal knowledge OS — browse, edit, and query your second brain.',
|
|
10
|
+
footer: 'MindOS · personal knowledge OS',
|
|
11
|
+
},
|
|
12
|
+
home: {
|
|
13
|
+
recentlyModified: 'Recently Modified',
|
|
14
|
+
continueEditing: 'Continue editing',
|
|
15
|
+
newNote: 'New Notes',
|
|
16
|
+
plugins: 'Plugins',
|
|
17
|
+
showMore: 'Show more',
|
|
18
|
+
showLess: 'Show less',
|
|
19
|
+
shortcuts: {
|
|
20
|
+
searchFiles: 'Search files',
|
|
21
|
+
askAI: 'Ask AI',
|
|
22
|
+
editFile: 'Edit file',
|
|
23
|
+
save: 'Save',
|
|
24
|
+
settings: 'Settings',
|
|
25
|
+
},
|
|
26
|
+
relativeTime: {
|
|
27
|
+
justNow: 'just now',
|
|
28
|
+
minutesAgo: (n: number) => `${n}m ago`,
|
|
29
|
+
hoursAgo: (n: number) => `${n}h ago`,
|
|
30
|
+
daysAgo: (n: number) => `${n}d ago`,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
sidebar: {
|
|
34
|
+
searchTitle: 'Search (⌘K)',
|
|
35
|
+
askTitle: 'Ask AI (⌘/)',
|
|
36
|
+
settingsTitle: 'Settings (⌘,)',
|
|
37
|
+
collapseTitle: 'Collapse sidebar',
|
|
38
|
+
expandTitle: 'Expand sidebar',
|
|
39
|
+
},
|
|
40
|
+
search: {
|
|
41
|
+
placeholder: 'Search files...',
|
|
42
|
+
noResults: 'No results found',
|
|
43
|
+
prompt: 'Type to search across all files',
|
|
44
|
+
navigate: 'navigate',
|
|
45
|
+
open: 'open',
|
|
46
|
+
close: 'close',
|
|
47
|
+
},
|
|
48
|
+
ask: {
|
|
49
|
+
title: 'MindOS Agent',
|
|
50
|
+
placeholder: 'Ask a question... or type @ to attach a file',
|
|
51
|
+
emptyPrompt: 'Ask anything about your knowledge base',
|
|
52
|
+
send: 'send',
|
|
53
|
+
attachFile: 'attach file',
|
|
54
|
+
attachCurrent: 'attach current file',
|
|
55
|
+
stopTitle: 'Stop',
|
|
56
|
+
connecting: 'Thinking with your mind...',
|
|
57
|
+
thinking: 'Thinking...',
|
|
58
|
+
searching: 'Searching knowledge base...',
|
|
59
|
+
generating: 'Generating response...',
|
|
60
|
+
stopped: 'Generation stopped.',
|
|
61
|
+
errorNoResponse: 'No response from AI. Please check your API key and provider settings.',
|
|
62
|
+
suggestions: [
|
|
63
|
+
'Summarize this document',
|
|
64
|
+
'List all action items and TODOs',
|
|
65
|
+
'What are the key points?',
|
|
66
|
+
'Find related notes on this topic',
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
fileTree: {
|
|
70
|
+
newFileTitle: 'New file in this directory',
|
|
71
|
+
rename: 'Rename',
|
|
72
|
+
delete: 'Delete',
|
|
73
|
+
create: 'Create',
|
|
74
|
+
enterFileName: 'Enter a file name',
|
|
75
|
+
failed: 'Failed',
|
|
76
|
+
confirmDelete: (name: string) => `Delete "${name}"? This cannot be undone.`,
|
|
77
|
+
},
|
|
78
|
+
dirView: {
|
|
79
|
+
gridView: 'Grid view',
|
|
80
|
+
listView: 'List view',
|
|
81
|
+
emptyFolder: 'This folder is empty.',
|
|
82
|
+
fileCount: (n: number) => `${n} files`,
|
|
83
|
+
},
|
|
84
|
+
settings: {
|
|
85
|
+
title: 'Settings',
|
|
86
|
+
tabs: { ai: 'AI', appearance: 'Appearance', knowledge: 'Knowledge Base', plugins: 'Plugins', shortcuts: 'Shortcuts' },
|
|
87
|
+
ai: {
|
|
88
|
+
provider: 'Provider',
|
|
89
|
+
model: 'Model',
|
|
90
|
+
apiKey: 'API Key',
|
|
91
|
+
baseUrl: 'Base URL',
|
|
92
|
+
baseUrlHint: 'Optional. Use for proxies or OpenAI-compatible APIs.',
|
|
93
|
+
keyHint: 'Stored locally in ~/.mindos/config.json',
|
|
94
|
+
envHint: 'Fields marked env have a value from environment variables. You can override them here, or restore all to env defaults.',
|
|
95
|
+
envFieldNote: (key: string) => `Env var ${key} is set. Clear this field to fall back to it.`,
|
|
96
|
+
resetToEnv: 'Reset to env value',
|
|
97
|
+
restoreFromEnv: 'Restore from env',
|
|
98
|
+
noApiKey: 'API key is not set. AI features will be unavailable until you add one.',
|
|
99
|
+
},
|
|
100
|
+
appearance: {
|
|
101
|
+
readingFont: 'Reading font',
|
|
102
|
+
contentWidth: 'Content width',
|
|
103
|
+
colorTheme: 'Color theme',
|
|
104
|
+
dark: 'Dark',
|
|
105
|
+
light: 'Light',
|
|
106
|
+
language: 'Language',
|
|
107
|
+
browserNote: 'Appearance settings are saved in your browser.',
|
|
108
|
+
fontPreview: 'The quick brown fox jumps over the lazy dog.',
|
|
109
|
+
},
|
|
110
|
+
knowledge: {
|
|
111
|
+
sopRoot: 'Knowledge base root',
|
|
112
|
+
sopRootHint: 'Absolute path to your notes directory. Requires server restart to take effect.',
|
|
113
|
+
sopRootEnvHint: (key: string) => `MIND_ROOT env var is set to "${key}". Fill in above to override it.`,
|
|
114
|
+
envNote: 'MIND_ROOT env var is set. Fill in above to override it — your value takes priority.',
|
|
115
|
+
webPassword: 'Web UI password',
|
|
116
|
+
webPasswordHint: 'Protect the browser UI with a password. Leave empty to disable. Changes take effect on next page load.',
|
|
117
|
+
webPasswordClear: 'Clear password',
|
|
118
|
+
authToken: 'MCP / API auth token',
|
|
119
|
+
authTokenHint: 'Bearer token for Agent and MCP clients. Not required for browser access.',
|
|
120
|
+
authTokenNone: 'No token set — API is open to all requests.',
|
|
121
|
+
authTokenCopy: 'Copy',
|
|
122
|
+
authTokenCopied: 'Copied!',
|
|
123
|
+
authTokenReset: 'Regenerate',
|
|
124
|
+
authTokenClear: 'Clear token',
|
|
125
|
+
authTokenResetConfirm: 'Regenerate token? All existing MCP clients will need to update their config.',
|
|
126
|
+
authTokenMcpPort: 'MCP port',
|
|
127
|
+
},
|
|
128
|
+
plugins: {
|
|
129
|
+
title: 'Installed Renderers',
|
|
130
|
+
builtinBadge: 'built-in',
|
|
131
|
+
enabled: 'Enabled',
|
|
132
|
+
disabled: 'Disabled',
|
|
133
|
+
matchHint: 'Auto-activates on',
|
|
134
|
+
noPlugins: 'No renderers installed.',
|
|
135
|
+
comingSoon: 'Plugin marketplace coming soon.',
|
|
136
|
+
},
|
|
137
|
+
save: 'Save',
|
|
138
|
+
saved: 'Saved',
|
|
139
|
+
saveFailed: 'Save failed',
|
|
140
|
+
},
|
|
141
|
+
shortcuts: [
|
|
142
|
+
{ keys: ['⌘', 'K'], description: 'Search' },
|
|
143
|
+
{ keys: ['⌘', '/'], description: 'Ask AI' },
|
|
144
|
+
{ keys: ['⌘', ','], description: 'Settings' },
|
|
145
|
+
{ keys: ['E'], description: 'Edit current file' },
|
|
146
|
+
{ keys: ['⌘', 'S'], description: 'Save' },
|
|
147
|
+
{ keys: ['Esc'], description: 'Cancel edit / close modal' },
|
|
148
|
+
{ keys: ['@'], description: 'Attach file in Ask AI' },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
zh: {
|
|
152
|
+
common: {
|
|
153
|
+
relatedFiles: '关联视图',
|
|
154
|
+
},
|
|
155
|
+
app: {
|
|
156
|
+
tagline: '个人知识操作系统 — 浏览、编辑并查询你的第二大脑。',
|
|
157
|
+
footer: 'MindOS · 个人知识操作系统',
|
|
158
|
+
},
|
|
159
|
+
home: {
|
|
160
|
+
recentlyModified: '最近修改',
|
|
161
|
+
continueEditing: '继续编辑',
|
|
162
|
+
newNote: '新建笔记',
|
|
163
|
+
plugins: '插件',
|
|
164
|
+
showMore: '查看更多',
|
|
165
|
+
showLess: '收起',
|
|
166
|
+
shortcuts: {
|
|
167
|
+
searchFiles: '搜索文件',
|
|
168
|
+
askAI: '问 AI',
|
|
169
|
+
editFile: '编辑文件',
|
|
170
|
+
save: '保存',
|
|
171
|
+
settings: '设置',
|
|
172
|
+
},
|
|
173
|
+
relativeTime: {
|
|
174
|
+
justNow: '刚刚',
|
|
175
|
+
minutesAgo: (n: number) => `${n} 分钟前`,
|
|
176
|
+
hoursAgo: (n: number) => `${n} 小时前`,
|
|
177
|
+
daysAgo: (n: number) => `${n} 天前`,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
sidebar: {
|
|
181
|
+
searchTitle: '搜索 (⌘K)',
|
|
182
|
+
askTitle: '问 AI (⌘/)',
|
|
183
|
+
settingsTitle: '设置 (⌘,)',
|
|
184
|
+
collapseTitle: '收起侧栏',
|
|
185
|
+
expandTitle: '展开侧栏',
|
|
186
|
+
},
|
|
187
|
+
search: {
|
|
188
|
+
placeholder: '搜索文件...',
|
|
189
|
+
noResults: '没有找到结果',
|
|
190
|
+
prompt: '输入关键词搜索所有文件',
|
|
191
|
+
navigate: '上下导航',
|
|
192
|
+
open: '打开',
|
|
193
|
+
close: '关闭',
|
|
194
|
+
},
|
|
195
|
+
ask: {
|
|
196
|
+
title: 'MindOS Agent',
|
|
197
|
+
placeholder: '输入问题,或输入 @ 添加附件文件',
|
|
198
|
+
emptyPrompt: '可以问任何关于知识库的问题',
|
|
199
|
+
send: '发送',
|
|
200
|
+
attachFile: '附加文件',
|
|
201
|
+
attachCurrent: '附加当前文件',
|
|
202
|
+
stopTitle: '停止',
|
|
203
|
+
connecting: '正在与你的心智一起思考...' ,
|
|
204
|
+
thinking: '思考中...',
|
|
205
|
+
searching: '正在搜索知识库...',
|
|
206
|
+
generating: '正在生成回复...',
|
|
207
|
+
stopped: '已停止生成。',
|
|
208
|
+
errorNoResponse: 'AI 未返回响应,请检查 API Key 和服务商设置。',
|
|
209
|
+
suggestions: [
|
|
210
|
+
'总结这篇文档',
|
|
211
|
+
'列出所有待办事项',
|
|
212
|
+
'这篇文档的核心要点是什么?',
|
|
213
|
+
'查找与这个主题相关的笔记',
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
fileTree: {
|
|
217
|
+
newFileTitle: '在此目录新建文件',
|
|
218
|
+
rename: '重命名',
|
|
219
|
+
delete: '删除',
|
|
220
|
+
create: '创建',
|
|
221
|
+
enterFileName: '请输入文件名',
|
|
222
|
+
failed: '操作失败',
|
|
223
|
+
confirmDelete: (name: string) => `确定删除「${name}」?此操作不可撤销。`,
|
|
224
|
+
},
|
|
225
|
+
dirView: {
|
|
226
|
+
gridView: '网格视图',
|
|
227
|
+
listView: '列表视图',
|
|
228
|
+
emptyFolder: '此目录为空。',
|
|
229
|
+
fileCount: (n: number) => `${n} 个文件`,
|
|
230
|
+
},
|
|
231
|
+
settings: {
|
|
232
|
+
title: '设置',
|
|
233
|
+
tabs: { ai: 'AI', appearance: '外观', knowledge: '知识库', plugins: '插件', shortcuts: '快捷键' },
|
|
234
|
+
ai: {
|
|
235
|
+
provider: '服务商',
|
|
236
|
+
model: '模型',
|
|
237
|
+
apiKey: 'API 密钥',
|
|
238
|
+
baseUrl: '接口地址',
|
|
239
|
+
baseUrlHint: '可选。用于代理或 OpenAI 兼容 API。',
|
|
240
|
+
keyHint: '本地存储于 ~/.mindos/config.json',
|
|
241
|
+
envHint: '标有 env 的字段存在环境变量值。你可以在此覆盖,或一键恢复为环境变量默认值。',
|
|
242
|
+
envFieldNote: (key: string) => `环境变量 ${key} 已设置。清空此字段将回退到环境变量值。`,
|
|
243
|
+
resetToEnv: '恢复为环境变量',
|
|
244
|
+
restoreFromEnv: '从环境变量恢复',
|
|
245
|
+
noApiKey: 'API 密钥未设置,AI 功能暂不可用,请在此填写。',
|
|
246
|
+
},
|
|
247
|
+
appearance: {
|
|
248
|
+
readingFont: '正文字体',
|
|
249
|
+
contentWidth: '内容宽度',
|
|
250
|
+
colorTheme: '颜色主题',
|
|
251
|
+
dark: '深色',
|
|
252
|
+
light: '浅色',
|
|
253
|
+
language: '语言',
|
|
254
|
+
browserNote: '外观设置保存在浏览器本地。',
|
|
255
|
+
fontPreview: '人之初,性本善;性相近,习相远。',
|
|
256
|
+
},
|
|
257
|
+
knowledge: {
|
|
258
|
+
sopRoot: '知识库根目录',
|
|
259
|
+
sopRootHint: '笔记目录的绝对路径,修改后需要重启服务器。',
|
|
260
|
+
sopRootEnvHint: (key: string) => `MIND_ROOT 环境变量已设置为 "${key}",填写上方路径可覆盖它。`,
|
|
261
|
+
envNote: 'MIND_ROOT 环境变量已设置。填写上方路径后,你的值优先生效。',
|
|
262
|
+
webPassword: '网页访问密码',
|
|
263
|
+
webPasswordHint: '设置后访问浏览器 UI 需要登录,留空则关闭密码保护。下次刷新页面后生效。',
|
|
264
|
+
webPasswordClear: '清除密码',
|
|
265
|
+
authToken: 'MCP / API 访问令牌',
|
|
266
|
+
authTokenHint: 'Agent 和 MCP 客户端使用的 Bearer Token,浏览器访问无需此项。',
|
|
267
|
+
authTokenNone: '未设置令牌 — API 对所有请求开放。',
|
|
268
|
+
authTokenCopy: '复制',
|
|
269
|
+
authTokenCopied: '已复制!',
|
|
270
|
+
authTokenReset: '重新生成',
|
|
271
|
+
authTokenClear: '清除令牌',
|
|
272
|
+
authTokenResetConfirm: '重新生成令牌?所有 MCP 客户端配置都需要更新。',
|
|
273
|
+
authTokenMcpPort: 'MCP 端口',
|
|
274
|
+
},
|
|
275
|
+
plugins: {
|
|
276
|
+
title: '已安装渲染器',
|
|
277
|
+
builtinBadge: '内置',
|
|
278
|
+
enabled: '已启用',
|
|
279
|
+
disabled: '已禁用',
|
|
280
|
+
matchHint: '自动匹配',
|
|
281
|
+
noPlugins: '暂无渲染器。',
|
|
282
|
+
comingSoon: '插件市场即将上线。',
|
|
283
|
+
},
|
|
284
|
+
save: '保存',
|
|
285
|
+
saved: '已保存',
|
|
286
|
+
saveFailed: '保存失败',
|
|
287
|
+
},
|
|
288
|
+
shortcuts: [
|
|
289
|
+
{ keys: ['⌘', 'K'], description: '搜索' },
|
|
290
|
+
{ keys: ['⌘', '/'], description: '问 AI' },
|
|
291
|
+
{ keys: ['⌘', ','], description: '设置' },
|
|
292
|
+
{ keys: ['E'], description: '编辑当前文件' },
|
|
293
|
+
{ keys: ['⌘', 'S'], description: '保存' },
|
|
294
|
+
{ keys: ['Esc'], description: '取消编辑 / 关闭弹窗' },
|
|
295
|
+
{ keys: ['@'], description: '在 AI 对话中添加附件' },
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
} as const;
|
|
299
|
+
|
|
300
|
+
export type Messages = typeof messages['en'];
|
package/app/lib/jwt.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JWT implementation using Web Crypto (HMAC-SHA256).
|
|
3
|
+
* Compatible with both Next.js Edge runtime (proxy) and Node.js API routes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ALG = { name: 'HMAC', hash: 'SHA-256' };
|
|
7
|
+
|
|
8
|
+
function b64url(buf: ArrayBuffer): string {
|
|
9
|
+
return btoa(String.fromCharCode(...new Uint8Array(buf)))
|
|
10
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function b64urlEncode(str: string): string {
|
|
14
|
+
return btoa(unescape(encodeURIComponent(str)))
|
|
15
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function b64urlDecode(str: string): string {
|
|
19
|
+
return decodeURIComponent(escape(atob(str.replace(/-/g, '+').replace(/_/g, '/'))));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function importKey(secret: string): Promise<CryptoKey> {
|
|
23
|
+
return crypto.subtle.importKey(
|
|
24
|
+
'raw',
|
|
25
|
+
new TextEncoder().encode(secret),
|
|
26
|
+
ALG,
|
|
27
|
+
false,
|
|
28
|
+
['sign', 'verify'],
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function signJwt(payload: Record<string, unknown>, secret: string): Promise<string> {
|
|
33
|
+
const header = b64urlEncode(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
|
|
34
|
+
const body = b64urlEncode(JSON.stringify(payload));
|
|
35
|
+
const key = await importKey(secret);
|
|
36
|
+
const sig = await crypto.subtle.sign(ALG.name, key, new TextEncoder().encode(`${header}.${body}`));
|
|
37
|
+
return `${header}.${body}.${b64url(sig)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function verifyJwt(token: string, secret: string): Promise<Record<string, unknown> | null> {
|
|
41
|
+
const parts = token.split('.');
|
|
42
|
+
if (parts.length !== 3) return null;
|
|
43
|
+
|
|
44
|
+
const [header, body, sig] = parts;
|
|
45
|
+
const key = await importKey(secret);
|
|
46
|
+
const valid = await crypto.subtle.verify(
|
|
47
|
+
ALG.name,
|
|
48
|
+
key,
|
|
49
|
+
Uint8Array.from(atob(sig.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)),
|
|
50
|
+
new TextEncoder().encode(`${header}.${body}`),
|
|
51
|
+
);
|
|
52
|
+
if (!valid) return null;
|
|
53
|
+
|
|
54
|
+
const payload = JSON.parse(b64urlDecode(body)) as Record<string, unknown>;
|
|
55
|
+
if (typeof payload.exp === 'number' && Date.now() / 1000 > payload.exp) return null;
|
|
56
|
+
|
|
57
|
+
return payload;
|
|
58
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { registerRenderer } from './registry';
|
|
2
|
+
import { TodoRenderer } from '@/components/renderers/TodoRenderer';
|
|
3
|
+
import { CsvRenderer } from '@/components/renderers/CsvRenderer';
|
|
4
|
+
import { GraphRenderer } from '@/components/renderers/GraphRenderer';
|
|
5
|
+
import { TimelineRenderer } from '@/components/renderers/TimelineRenderer';
|
|
6
|
+
import { SummaryRenderer } from '@/components/renderers/SummaryRenderer';
|
|
7
|
+
import { ConfigRenderer } from '@/components/renderers/ConfigRenderer';
|
|
8
|
+
|
|
9
|
+
registerRenderer({
|
|
10
|
+
id: 'todo',
|
|
11
|
+
name: 'TODO Board',
|
|
12
|
+
description: 'Renders TODO.md/TODO.csv as an interactive kanban board grouped by section. Check items off directly — changes are written back to the source file.',
|
|
13
|
+
author: 'MindOS',
|
|
14
|
+
icon: '✅',
|
|
15
|
+
tags: ['productivity', 'tasks', 'markdown'],
|
|
16
|
+
builtin: true,
|
|
17
|
+
match: ({ filePath }) => /\bTODO\b.*\.(md|csv)$/i.test(filePath),
|
|
18
|
+
component: TodoRenderer,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registerRenderer({
|
|
22
|
+
id: 'csv',
|
|
23
|
+
name: 'CSV Views',
|
|
24
|
+
description: 'Renders any CSV file as Table, Gallery, or Board. Each view is independently configurable — choose which columns map to title, description, tag, and group.',
|
|
25
|
+
author: 'MindOS',
|
|
26
|
+
icon: '📊',
|
|
27
|
+
tags: ['csv', 'table', 'gallery', 'board', 'data'],
|
|
28
|
+
builtin: true,
|
|
29
|
+
match: ({ extension, filePath }) => extension === 'csv' && !/\bTODO\b/i.test(filePath),
|
|
30
|
+
component: CsvRenderer,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
registerRenderer({
|
|
34
|
+
id: 'config-panel',
|
|
35
|
+
name: 'Config Panel',
|
|
36
|
+
description: 'Renders CONFIG.json as an editable control panel based on uiSchema/keySpecs. Changes are written back to the JSON file directly.',
|
|
37
|
+
author: 'MindOS',
|
|
38
|
+
icon: '🧩',
|
|
39
|
+
tags: ['config', 'json', 'settings', 'schema'],
|
|
40
|
+
builtin: true,
|
|
41
|
+
match: ({ filePath, extension }) => extension === 'json' && /(^|\/)CONFIG\.json$/i.test(filePath),
|
|
42
|
+
component: ConfigRenderer,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
registerRenderer({
|
|
46
|
+
id: 'graph',
|
|
47
|
+
name: 'Wiki Graph',
|
|
48
|
+
description: 'Force-directed graph of wikilink references across all markdown files. Supports Global and Local (2-hop) scope filters.',
|
|
49
|
+
author: 'MindOS',
|
|
50
|
+
icon: '🕸️',
|
|
51
|
+
tags: ['graph', 'wiki', 'links', 'visualization'],
|
|
52
|
+
builtin: true,
|
|
53
|
+
match: ({ extension }) => extension === 'md',
|
|
54
|
+
component: GraphRenderer,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
registerRenderer({
|
|
58
|
+
id: 'timeline',
|
|
59
|
+
name: 'Timeline',
|
|
60
|
+
description: 'Renders changelog and journal files as a vertical timeline. Any markdown with ## date headings (e.g. ## 2025-01-15) becomes a card in the feed.',
|
|
61
|
+
author: 'MindOS',
|
|
62
|
+
icon: '📅',
|
|
63
|
+
tags: ['timeline', 'changelog', 'journal', 'history'],
|
|
64
|
+
builtin: true,
|
|
65
|
+
match: ({ filePath }) => /\b(CHANGELOG|changelog|TIMELINE|timeline|journal|Journal|diary|Diary)\b.*\.md$/i.test(filePath),
|
|
66
|
+
component: TimelineRenderer,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
registerRenderer({
|
|
70
|
+
id: 'summary',
|
|
71
|
+
name: 'AI Briefing',
|
|
72
|
+
description: 'Streams an AI-generated daily briefing summarizing your most recently modified files — key changes, recurring themes, and suggested next actions.',
|
|
73
|
+
author: 'MindOS',
|
|
74
|
+
icon: '✨',
|
|
75
|
+
tags: ['ai', 'summary', 'briefing', 'daily'],
|
|
76
|
+
builtin: true,
|
|
77
|
+
match: ({ filePath }) => /\b(SUMMARY|summary|Summary|BRIEFING|briefing|Briefing|DAILY|daily|Daily)\b.*\.md$/i.test(filePath),
|
|
78
|
+
component: SummaryRenderer,
|
|
79
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface RendererContext {
|
|
4
|
+
filePath: string;
|
|
5
|
+
content: string;
|
|
6
|
+
extension: string;
|
|
7
|
+
saveAction: (content: string) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RendererDefinition {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
author: string;
|
|
15
|
+
icon: string; // emoji or short string
|
|
16
|
+
tags: string[];
|
|
17
|
+
builtin: boolean; // true = ships with MindOS; false = user-installed (future)
|
|
18
|
+
match: (ctx: Pick<RendererContext, 'filePath' | 'extension'>) => boolean;
|
|
19
|
+
component: ComponentType<RendererContext>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const registry: RendererDefinition[] = [];
|
|
23
|
+
|
|
24
|
+
// Disabled plugin IDs — persisted to localStorage on client
|
|
25
|
+
let _disabledIds: Set<string> = new Set();
|
|
26
|
+
|
|
27
|
+
export function loadDisabledState() {
|
|
28
|
+
if (typeof window === 'undefined') return;
|
|
29
|
+
try {
|
|
30
|
+
const raw = localStorage.getItem('mindos-disabled-renderers');
|
|
31
|
+
_disabledIds = new Set(raw ? JSON.parse(raw) : []);
|
|
32
|
+
} catch {
|
|
33
|
+
_disabledIds = new Set();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setRendererEnabled(id: string, enabled: boolean) {
|
|
38
|
+
if (enabled) {
|
|
39
|
+
_disabledIds.delete(id);
|
|
40
|
+
} else {
|
|
41
|
+
_disabledIds.add(id);
|
|
42
|
+
}
|
|
43
|
+
if (typeof window !== 'undefined') {
|
|
44
|
+
localStorage.setItem('mindos-disabled-renderers', JSON.stringify([..._disabledIds]));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isRendererEnabled(id: string): boolean {
|
|
49
|
+
return !_disabledIds.has(id);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function registerRenderer(def: RendererDefinition) {
|
|
53
|
+
if (!registry.find(r => r.id === def.id)) registry.push(def);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resolveRenderer(
|
|
57
|
+
filePath: string,
|
|
58
|
+
extension: string,
|
|
59
|
+
forceId?: string,
|
|
60
|
+
): RendererDefinition | undefined {
|
|
61
|
+
if (forceId) {
|
|
62
|
+
const r = registry.find(d => d.id === forceId);
|
|
63
|
+
return r && isRendererEnabled(r.id) ? r : undefined;
|
|
64
|
+
}
|
|
65
|
+
return registry.find(r => isRendererEnabled(r.id) && r.match({ filePath, extension }));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getAllRenderers(): RendererDefinition[] {
|
|
69
|
+
return registry;
|
|
70
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const SETTINGS_PATH = path.join(os.homedir(), '.mindos', 'config.json');
|
|
6
|
+
|
|
7
|
+
export interface ProviderConfig {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
model: string;
|
|
10
|
+
baseUrl?: string; // only for openai-compatible providers
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AiConfig {
|
|
14
|
+
provider: 'anthropic' | 'openai';
|
|
15
|
+
providers: {
|
|
16
|
+
anthropic: ProviderConfig;
|
|
17
|
+
openai: ProviderConfig;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ServerSettings {
|
|
22
|
+
ai: AiConfig;
|
|
23
|
+
mindRoot: string; // empty = use env var / default
|
|
24
|
+
// Fields managed by CLI only (not edited via GUI, preserved on write)
|
|
25
|
+
port?: number;
|
|
26
|
+
mcpPort?: number;
|
|
27
|
+
authToken?: string;
|
|
28
|
+
webPassword?: string;
|
|
29
|
+
startMode?: 'dev' | 'start';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULTS: ServerSettings = {
|
|
33
|
+
ai: {
|
|
34
|
+
provider: 'anthropic',
|
|
35
|
+
providers: {
|
|
36
|
+
anthropic: { apiKey: '', model: 'claude-sonnet-4-6' },
|
|
37
|
+
openai: { apiKey: '', model: 'gpt-5.4', baseUrl: '' },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
mindRoot: '',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Safely extract a string field from an unknown object, returning fallback if missing or wrong type */
|
|
44
|
+
function str(obj: unknown, key: string, fallback: string): string {
|
|
45
|
+
if (obj && typeof obj === 'object') {
|
|
46
|
+
const val = (obj as Record<string, unknown>)[key];
|
|
47
|
+
if (typeof val === 'string' && val.trim() !== '') return val;
|
|
48
|
+
}
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Parse a provider config from unknown input, filling missing/invalid fields with defaults */
|
|
53
|
+
function parseProvider(raw: unknown, defaults: ProviderConfig): ProviderConfig {
|
|
54
|
+
return {
|
|
55
|
+
apiKey: str(raw, 'apiKey', defaults.apiKey),
|
|
56
|
+
model: str(raw, 'model', defaults.model),
|
|
57
|
+
...(defaults.baseUrl !== undefined
|
|
58
|
+
? { baseUrl: str(raw, 'baseUrl', defaults.baseUrl) }
|
|
59
|
+
: {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Migrate old flat ai structure to new providers dict, if needed */
|
|
64
|
+
function migrateAi(parsed: Record<string, unknown>): AiConfig {
|
|
65
|
+
const ai = parsed.ai as Record<string, unknown> | undefined;
|
|
66
|
+
if (!ai) return { ...DEFAULTS.ai };
|
|
67
|
+
|
|
68
|
+
const providerField = ai.provider;
|
|
69
|
+
const provider: 'anthropic' | 'openai' =
|
|
70
|
+
providerField === 'anthropic' || providerField === 'openai' ? providerField : 'anthropic';
|
|
71
|
+
|
|
72
|
+
// Already new format
|
|
73
|
+
if (ai.providers && typeof ai.providers === 'object') {
|
|
74
|
+
const p = ai.providers as Record<string, unknown>;
|
|
75
|
+
return {
|
|
76
|
+
provider,
|
|
77
|
+
providers: {
|
|
78
|
+
anthropic: parseProvider(p.anthropic, DEFAULTS.ai.providers.anthropic),
|
|
79
|
+
openai: parseProvider(p.openai, DEFAULTS.ai.providers.openai),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Old flat format — migrate
|
|
85
|
+
return {
|
|
86
|
+
provider,
|
|
87
|
+
providers: {
|
|
88
|
+
anthropic: {
|
|
89
|
+
apiKey: str(ai, 'anthropicApiKey', ''),
|
|
90
|
+
model: str(ai, 'anthropicModel', 'claude-sonnet-4-6'),
|
|
91
|
+
},
|
|
92
|
+
openai: {
|
|
93
|
+
apiKey: str(ai, 'openaiApiKey', ''),
|
|
94
|
+
model: str(ai, 'openaiModel', 'gpt-5.4'),
|
|
95
|
+
baseUrl: str(ai, 'openaiBaseUrl', ''),
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function readSettings(): ServerSettings {
|
|
102
|
+
try {
|
|
103
|
+
const raw = fs.readFileSync(SETTINGS_PATH, 'utf-8');
|
|
104
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
105
|
+
return {
|
|
106
|
+
ai: migrateAi(parsed),
|
|
107
|
+
mindRoot: (parsed.mindRoot ?? parsed.sopRoot ?? DEFAULTS.mindRoot) as string,
|
|
108
|
+
webPassword: typeof parsed.webPassword === 'string' ? parsed.webPassword : undefined,
|
|
109
|
+
authToken: typeof parsed.authToken === 'string' ? parsed.authToken : undefined,
|
|
110
|
+
mcpPort: typeof parsed.mcpPort === 'number' ? parsed.mcpPort : undefined,
|
|
111
|
+
};
|
|
112
|
+
} catch {
|
|
113
|
+
return { ...DEFAULTS, ai: { ...DEFAULTS.ai, providers: { ...DEFAULTS.ai.providers } } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function writeSettings(settings: ServerSettings): void {
|
|
118
|
+
const dir = path.dirname(SETTINGS_PATH);
|
|
119
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
120
|
+
// Merge into existing config to preserve fields like port, authToken, mcpPort
|
|
121
|
+
let existing: Record<string, unknown> = {};
|
|
122
|
+
try { existing = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf-8')); } catch { /* ignore */ }
|
|
123
|
+
const merged: Record<string, unknown> = { ...existing, ai: settings.ai, mindRoot: settings.mindRoot };
|
|
124
|
+
if (settings.webPassword !== undefined) merged.webPassword = settings.webPassword;
|
|
125
|
+
if (settings.authToken !== undefined) merged.authToken = settings.authToken;
|
|
126
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Effective AI config — settings file overrides env vars when non-empty */
|
|
130
|
+
export function effectiveAiConfig() {
|
|
131
|
+
const s = readSettings();
|
|
132
|
+
const provider = (s.ai.provider || process.env.AI_PROVIDER || 'anthropic') as 'anthropic' | 'openai';
|
|
133
|
+
const anthropic = s.ai.providers.anthropic;
|
|
134
|
+
const openai = s.ai.providers.openai;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
provider,
|
|
138
|
+
anthropicApiKey: anthropic.apiKey || process.env.ANTHROPIC_API_KEY || '',
|
|
139
|
+
anthropicModel: anthropic.model || process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-6',
|
|
140
|
+
openaiApiKey: openai.apiKey || process.env.OPENAI_API_KEY || '',
|
|
141
|
+
openaiModel: openai.model || process.env.OPENAI_MODEL || 'gpt-5.4',
|
|
142
|
+
openaiBaseUrl: openai.baseUrl || process.env.OPENAI_BASE_URL || '',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Effective MIND_ROOT — settings file can override, env var is fallback */
|
|
147
|
+
export function effectiveSopRoot(): string {
|
|
148
|
+
const s = readSettings();
|
|
149
|
+
return s.mindRoot || process.env.MIND_ROOT || path.join(os.homedir(), '.mindos', 'my-mind');
|
|
150
|
+
}
|
package/app/lib/types.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Re-export core types as single source of truth
|
|
2
|
+
export type { FileNode, SearchResult, BacklinkEntry } from './core/types';
|
|
3
|
+
|
|
4
|
+
export interface SearchMatch {
|
|
5
|
+
indices: [number, number][];
|
|
6
|
+
value: string;
|
|
7
|
+
key: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Frontend-facing backlink shape returned by /api/backlinks (transformed from core BacklinkEntry) */
|
|
11
|
+
export interface BacklinkItem {
|
|
12
|
+
filePath: string;
|
|
13
|
+
snippets: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Message {
|
|
17
|
+
role: 'user' | 'assistant';
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LocalAttachment {
|
|
22
|
+
name: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ChatSession {
|
|
27
|
+
id: string;
|
|
28
|
+
currentFile?: string;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
updatedAt: number;
|
|
31
|
+
messages: Message[];
|
|
32
|
+
}
|