@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.
Files changed (282) hide show
  1. package/.env.local.example +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +423 -0
  4. package/README_zh.md +423 -0
  5. package/app/README.md +152 -0
  6. package/app/app/api/ask/route.ts +170 -0
  7. package/app/app/api/ask-sessions/route.ts +90 -0
  8. package/app/app/api/auth/route.ts +37 -0
  9. package/app/app/api/backlinks/route.ts +22 -0
  10. package/app/app/api/bootstrap/route.ts +37 -0
  11. package/app/app/api/extract-pdf/route.ts +82 -0
  12. package/app/app/api/file/route.ts +138 -0
  13. package/app/app/api/files/route.ts +12 -0
  14. package/app/app/api/git/route.ts +42 -0
  15. package/app/app/api/graph/route.ts +113 -0
  16. package/app/app/api/recent-files/route.ts +10 -0
  17. package/app/app/api/search/route.ts +17 -0
  18. package/app/app/api/settings/reset-token/route.ts +21 -0
  19. package/app/app/api/settings/route.ts +123 -0
  20. package/app/app/error.tsx +33 -0
  21. package/app/app/globals.css +368 -0
  22. package/app/app/icon.svg +35 -0
  23. package/app/app/layout.tsx +103 -0
  24. package/app/app/login/page.tsx +120 -0
  25. package/app/app/page.tsx +12 -0
  26. package/app/app/view/[...path]/ViewPageClient.tsx +343 -0
  27. package/app/app/view/[...path]/error.tsx +33 -0
  28. package/app/app/view/[...path]/loading.tsx +15 -0
  29. package/app/app/view/[...path]/page.tsx +93 -0
  30. package/app/components/AskFab.tsx +59 -0
  31. package/app/components/AskModal.tsx +398 -0
  32. package/app/components/Backlinks.tsx +75 -0
  33. package/app/components/Breadcrumb.tsx +31 -0
  34. package/app/components/CsvView.tsx +325 -0
  35. package/app/components/DirView.tsx +138 -0
  36. package/app/components/Editor.tsx +124 -0
  37. package/app/components/EditorWrapper.tsx +17 -0
  38. package/app/components/ErrorBoundary.tsx +53 -0
  39. package/app/components/FileTree.tsx +369 -0
  40. package/app/components/HomeContent.tsx +262 -0
  41. package/app/components/JsonView.tsx +27 -0
  42. package/app/components/MarkdownEditor.tsx +95 -0
  43. package/app/components/MarkdownView.tsx +118 -0
  44. package/app/components/SearchModal.tsx +193 -0
  45. package/app/components/SettingsModal.tsx +237 -0
  46. package/app/components/Sidebar.tsx +136 -0
  47. package/app/components/SidebarLayout.tsx +36 -0
  48. package/app/components/TableOfContents.tsx +150 -0
  49. package/app/components/ThemeToggle.tsx +34 -0
  50. package/app/components/WysiwygEditor.tsx +75 -0
  51. package/app/components/ask/FileChip.tsx +30 -0
  52. package/app/components/ask/MentionPopover.tsx +52 -0
  53. package/app/components/ask/MessageList.tsx +126 -0
  54. package/app/components/ask/SessionHistory.tsx +49 -0
  55. package/app/components/renderers/AgentInspectorRenderer.tsx +277 -0
  56. package/app/components/renderers/BacklinksRenderer.tsx +147 -0
  57. package/app/components/renderers/ConfigRenderer.tsx +236 -0
  58. package/app/components/renderers/CsvRenderer.tsx +77 -0
  59. package/app/components/renderers/DiffRenderer.tsx +310 -0
  60. package/app/components/renderers/GraphRenderer.tsx +428 -0
  61. package/app/components/renderers/SummaryRenderer.tsx +251 -0
  62. package/app/components/renderers/TimelineRenderer.tsx +213 -0
  63. package/app/components/renderers/TodoRenderer.tsx +474 -0
  64. package/app/components/renderers/WorkflowRenderer.tsx +404 -0
  65. package/app/components/renderers/csv/BoardView.tsx +146 -0
  66. package/app/components/renderers/csv/ConfigPanel.tsx +117 -0
  67. package/app/components/renderers/csv/EditableCell.tsx +43 -0
  68. package/app/components/renderers/csv/GalleryView.tsx +40 -0
  69. package/app/components/renderers/csv/TableView.tsx +164 -0
  70. package/app/components/renderers/csv/types.ts +87 -0
  71. package/app/components/settings/AiTab.tsx +111 -0
  72. package/app/components/settings/AppearanceTab.tsx +101 -0
  73. package/app/components/settings/KnowledgeTab.tsx +157 -0
  74. package/app/components/settings/PluginsTab.tsx +82 -0
  75. package/app/components/settings/Primitives.tsx +60 -0
  76. package/app/components/settings/ShortcutsTab.tsx +22 -0
  77. package/app/components/settings/types.ts +41 -0
  78. package/app/components/ui/button.tsx +60 -0
  79. package/app/components/ui/dialog.tsx +157 -0
  80. package/app/components/ui/input.tsx +20 -0
  81. package/app/components/ui/scroll-area.tsx +55 -0
  82. package/app/components/ui/toggle.tsx +44 -0
  83. package/app/components/ui/tooltip.tsx +66 -0
  84. package/app/components.json +25 -0
  85. package/app/data/pages/home-dark.png +0 -0
  86. package/app/data/pages/home-mobile-crop.png +0 -0
  87. package/app/data/pages/home-mobile.png +0 -0
  88. package/app/data/pages/home.png +0 -0
  89. package/app/data/pages/view-dir.png +0 -0
  90. package/app/data/pages/view-file-bot.png +0 -0
  91. package/app/data/pages/view-file-dark-crop.png +0 -0
  92. package/app/data/pages/view-file-dark.png +0 -0
  93. package/app/data/pages/view-file-mobile.png +0 -0
  94. package/app/data/pages/view-file-sm.png +0 -0
  95. package/app/data/pages/view-file-top.png +0 -0
  96. package/app/data/pages/view-file.png +0 -0
  97. package/app/eslint.config.mjs +18 -0
  98. package/app/hooks/useAskSession.ts +181 -0
  99. package/app/hooks/useFileUpload.ts +126 -0
  100. package/app/hooks/useMention.ts +65 -0
  101. package/app/lib/LocaleContext.tsx +40 -0
  102. package/app/lib/actions.ts +40 -0
  103. package/app/lib/agent/index.ts +3 -0
  104. package/app/lib/agent/model.ts +18 -0
  105. package/app/lib/agent/prompt.ts +32 -0
  106. package/app/lib/agent/tools.ts +151 -0
  107. package/app/lib/api.ts +55 -0
  108. package/app/lib/core/backlinks.ts +40 -0
  109. package/app/lib/core/csv.ts +28 -0
  110. package/app/lib/core/fs-ops.ts +118 -0
  111. package/app/lib/core/git.ts +50 -0
  112. package/app/lib/core/index.ts +58 -0
  113. package/app/lib/core/lines.ts +89 -0
  114. package/app/lib/core/search.ts +79 -0
  115. package/app/lib/core/security.ts +43 -0
  116. package/app/lib/core/tree.ts +113 -0
  117. package/app/lib/core/types.ts +40 -0
  118. package/app/lib/fs.ts +467 -0
  119. package/app/lib/i18n.ts +300 -0
  120. package/app/lib/jwt.ts +58 -0
  121. package/app/lib/renderers/index.ts +79 -0
  122. package/app/lib/renderers/registry.ts +70 -0
  123. package/app/lib/settings.ts +150 -0
  124. package/app/lib/types.ts +32 -0
  125. package/app/lib/utils.ts +34 -0
  126. package/app/next-env.d.ts +6 -0
  127. package/app/next.config.ts +10 -0
  128. package/app/package-lock.json +15306 -0
  129. package/app/package.json +71 -0
  130. package/app/postcss.config.mjs +7 -0
  131. package/app/proxy.ts +64 -0
  132. package/app/public/file.svg +1 -0
  133. package/app/public/globe.svg +1 -0
  134. package/app/public/landing/index.html +353 -0
  135. package/app/public/landing/style.css +216 -0
  136. package/app/public/logo-square.svg +37 -0
  137. package/app/public/logo.svg +37 -0
  138. package/app/public/next.svg +1 -0
  139. package/app/public/vercel.svg +1 -0
  140. package/app/public/window.svg +1 -0
  141. package/app/scripts/extract-pdf.cjs +56 -0
  142. package/app/tsconfig.json +34 -0
  143. package/app/vitest.config.ts +14 -0
  144. package/assets/demo-flow-zh.html +622 -0
  145. package/assets/images/demo-flow-dark.png +0 -0
  146. package/assets/images/demo-flow-light.png +0 -0
  147. package/assets/images/demo-flow-zh-dark.png +0 -0
  148. package/assets/images/demo-flow-zh-light.png +0 -0
  149. package/assets/images/gui-sync-cv.png +0 -0
  150. package/assets/logo-square.svg +37 -0
  151. package/bin/cli.js +894 -0
  152. package/mcp/README.md +113 -0
  153. package/mcp/package-lock.json +1717 -0
  154. package/mcp/package.json +18 -0
  155. package/mcp/src/index.ts +494 -0
  156. package/mcp/tsconfig.json +13 -0
  157. package/package.json +49 -0
  158. package/scripts/setup.js +675 -0
  159. package/scripts/upgrade-prompt.md +147 -0
  160. package/skills/mindos/SKILL.md +319 -0
  161. package/skills/mindos-zh/SKILL.md +318 -0
  162. package/templates/README.md +31 -0
  163. package/templates/empty/CHANGELOG.md +9 -0
  164. package/templates/empty/CONFIG.json +197 -0
  165. package/templates/empty/CONFIG.md +73 -0
  166. package/templates/empty/INSTRUCTION.md +177 -0
  167. package/templates/empty/README.md +27 -0
  168. package/templates/en/CHANGELOG.md +9 -0
  169. package/templates/en/CONFIG.json +197 -0
  170. package/templates/en/CONFIG.md +73 -0
  171. package/templates/en/INSTRUCTION.md +177 -0
  172. package/templates/en/README.md +27 -0
  173. package/templates/en/TODO.md +13 -0
  174. package/templates/en//360/237/221/244 Profile/INSTRUCTION.md" +21 -0
  175. package/templates/en//360/237/221/244 Profile/README.md" +15 -0
  176. package/templates/en//360/237/221/244 Profile//342/232/231/357/270/217 Preferences.md" +21 -0
  177. package/templates/en//360/237/221/244 Profile//360/237/216/257 Focus.md" +31 -0
  178. package/templates/en//360/237/221/244 Profile//360/237/221/244 Identity.md" +22 -0
  179. package/templates/en//360/237/223/232 Resources/INSTRUCTION.md" +29 -0
  180. package/templates/en//360/237/223/232 Resources/README.md" +21 -0
  181. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +1 -0
  182. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +1 -0
  183. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +1 -0
  184. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +1 -0
  185. package/templates/en//360/237/223/235 Notes/INSTRUCTION.md" +31 -0
  186. package/templates/en//360/237/223/235 Notes/Ideas/README.md" +8 -0
  187. package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +16 -0
  188. package/templates/en//360/237/223/235 Notes/Inbox/README.md" +8 -0
  189. package/templates/en//360/237/223/235 Notes/Inbox//360/237/247/252_example_quick_capture.md" +14 -0
  190. package/templates/en//360/237/223/235 Notes/Meetings/README.md" +8 -0
  191. package/templates/en//360/237/223/235 Notes/Meetings//360/237/247/252_example_meeting_note.md" +17 -0
  192. package/templates/en//360/237/223/235 Notes/README.md" +24 -0
  193. package/templates/en//360/237/223/235 Notes/Waiting/README.md" +8 -0
  194. package/templates/en//360/237/223/235 Notes/Waiting//360/237/247/252_example_blocked_item.md" +16 -0
  195. package/templates/en//360/237/224/204 Workflows/Configurations/README.md" +3 -0
  196. package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +14 -0
  197. package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +21 -0
  198. package/templates/en//360/237/224/204 Workflows/Information/README.md" +16 -0
  199. package/templates/en//360/237/224/204 Workflows/Information//360/237/247/252_example_info_capture_sop.md" +13 -0
  200. package/templates/en//360/237/224/204 Workflows/Media/README.md" +16 -0
  201. package/templates/en//360/237/224/204 Workflows/Media//360/237/247/252_example_content_publish_sop.md" +13 -0
  202. package/templates/en//360/237/224/204 Workflows/README.md" +22 -0
  203. package/templates/en//360/237/224/204 Workflows/Research/README.md" +16 -0
  204. package/templates/en//360/237/224/204 Workflows/Research//360/237/247/252_example_lit_review_sop.md" +16 -0
  205. package/templates/en//360/237/224/204 Workflows/Startup/README.md" +3 -0
  206. package/templates/en//360/237/224/204 Workflows/Startup//360/237/247/252_example_weekly_founder_ops.md" +22 -0
  207. package/templates/en//360/237/224/227 Connections/Classmates/README.md" +11 -0
  208. package/templates/en//360/237/224/227 Connections/Classmates//360/237/247/252_example_leo_chen.md" +16 -0
  209. package/templates/en//360/237/224/227 Connections/Colleagues/README.md" +11 -0
  210. package/templates/en//360/237/224/227 Connections/Colleagues//360/237/247/252_example_ethan_zhao.md" +16 -0
  211. package/templates/en//360/237/224/227 Connections/Connections Overview.csv" +5 -0
  212. package/templates/en//360/237/224/227 Connections/Family/README.md" +11 -0
  213. package/templates/en//360/237/224/227 Connections/Family//360/237/247/252_example_james_wang.md" +16 -0
  214. package/templates/en//360/237/224/227 Connections/Friends/README.md" +11 -0
  215. package/templates/en//360/237/224/227 Connections/Friends//360/237/247/252_example_lily_lin.md" +16 -0
  216. package/templates/en//360/237/224/227 Connections/INSTRUCTION.md" +56 -0
  217. package/templates/en//360/237/224/227 Connections/README.md" +20 -0
  218. package/templates/en//360/237/232/200 Projects/Archived/README.md" +9 -0
  219. package/templates/en//360/237/232/200 Projects/Archived//360/237/247/252_example_archived_project_note.md" +14 -0
  220. package/templates/en//360/237/232/200 Projects/INSTRUCTION.md" +29 -0
  221. package/templates/en//360/237/232/200 Projects/Products/README.md" +16 -0
  222. package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +20 -0
  223. package/templates/en//360/237/232/200 Projects/README.md" +21 -0
  224. package/templates/en//360/237/232/200 Projects/Research/README.md" +16 -0
  225. package/templates/en//360/237/232/200 Projects/Research//360/237/247/252_example_research_project_brief.md" +16 -0
  226. package/templates/template-generation-skill.md +79 -0
  227. package/templates/zh/CHANGELOG.md +9 -0
  228. package/templates/zh/CONFIG.json +197 -0
  229. package/templates/zh/CONFIG.md +66 -0
  230. package/templates/zh/INSTRUCTION.md +177 -0
  231. package/templates/zh/README.md +27 -0
  232. package/templates/zh/TODO.md +13 -0
  233. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/INSTRUCTION.md" +28 -0
  234. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +20 -0
  235. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/217 Preferences.md" +21 -0
  236. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/257 Focus.md" +31 -0
  237. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/244 Identity.md" +22 -0
  238. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/INSTRUCTION.md" +29 -0
  239. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +21 -0
  240. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +1 -0
  241. 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
  242. 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
  243. 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
  244. package/templates/zh//360/237/223/235 /347/254/224/350/256/260/INSTRUCTION.md" +31 -0
  245. package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +24 -0
  246. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256/README.md" +8 -0
  247. 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
  248. 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
  249. 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
  250. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225/README.md" +8 -0
  251. 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
  252. 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
  253. 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
  254. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/INSTRUCTION.md" +29 -0
  255. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +21 -0
  256. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257/README.md" +16 -0
  257. 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
  258. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223/README.md" +16 -0
  259. 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
  260. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224/README.md" +16 -0
  261. 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
  262. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256/README.md" +3 -0
  263. 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
  264. package/templates/zh//360/237/224/227 /345/205/263/347/263/273/INSTRUCTION.md" +62 -0
  265. package/templates/zh//360/237/224/227 /345/205/263/347/263/273/README.md" +20 -0
  266. 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
  267. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213/README.md" +11 -0
  268. 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
  269. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246/README.md" +11 -0
  270. 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
  271. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272/README.md" +11 -0
  272. 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
  273. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213/README.md" +11 -0
  274. 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
  275. package/templates/zh//360/237/232/200 /351/241/271/347/233/256/INSTRUCTION.md" +31 -0
  276. package/templates/zh//360/237/232/200 /351/241/271/347/233/256/README.md" +21 -0
  277. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201/README.md" +16 -0
  278. 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
  279. 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
  280. 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
  281. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224/README.md" +16 -0
  282. 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,35 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
2
+ <defs>
3
+ <linearGradient id="g-human" x1="14" y1="16" x2="2" y2="16" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0%" stop-color="#c8873a" stop-opacity="0.8"/>
5
+ <stop offset="100%" stop-color="#c8873a" stop-opacity="0.3"/>
6
+ </linearGradient>
7
+ <linearGradient id="g-agent" x1="14" y1="16" x2="30" y2="16" gradientUnits="userSpaceOnUse">
8
+ <stop offset="0%" stop-color="#c8873a" stop-opacity="0.8"/>
9
+ <stop offset="100%" stop-color="#c8873a" stop-opacity="1"/>
10
+ </linearGradient>
11
+ </defs>
12
+
13
+ <!-- Human Loop (Left) — dashed, lighter -->
14
+ <path
15
+ d="M14,16 C10,23 3.2,23 3.2,16 C3.2,9 10,9 14,16"
16
+ stroke="url(#g-human)"
17
+ stroke-width="1.8"
18
+ stroke-dasharray="1.2 2.4"
19
+ stroke-linecap="round"
20
+ />
21
+
22
+ <!-- Agent Loop (Right) — solid, bolder -->
23
+ <path
24
+ d="M14,16 C18,7 28.8,7 28.8,16 C28.8,25 18,25 14,16"
25
+ stroke="url(#g-agent)"
26
+ stroke-width="2.6"
27
+ stroke-linecap="round"
28
+ />
29
+
30
+ <!-- Spark center -->
31
+ <path
32
+ d="M14,14.5 Q14,16 15.5,16 Q14,16 14,17.5 Q14,16 12.5,16 Q14,16 14,14.5 Z"
33
+ fill="#FEF3C7"
34
+ />
35
+ </svg>
@@ -0,0 +1,103 @@
1
+ import type { Metadata } from 'next';
2
+ import { Geist, Geist_Mono, IBM_Plex_Mono, IBM_Plex_Sans, Lora } from 'next/font/google';
3
+ import { headers } from 'next/headers';
4
+ import './globals.css';
5
+ import { getFileTree } from '@/lib/fs';
6
+ import SidebarLayout from '@/components/SidebarLayout';
7
+ import { TooltipProvider } from '@/components/ui/tooltip';
8
+ import { LocaleProvider } from '@/lib/LocaleContext';
9
+ import ErrorBoundary from '@/components/ErrorBoundary';
10
+
11
+ const geistSans = Geist({
12
+ variable: '--font-geist-sans',
13
+ subsets: ['latin'],
14
+ });
15
+
16
+ const geistMono = Geist_Mono({
17
+ variable: '--font-geist-mono',
18
+ subsets: ['latin'],
19
+ });
20
+
21
+ const ibmPlexMono = IBM_Plex_Mono({
22
+ variable: '--font-ibm-plex-mono',
23
+ weight: ['400', '500', '600'],
24
+ subsets: ['latin'],
25
+ });
26
+
27
+ const ibmPlexSans = IBM_Plex_Sans({
28
+ variable: '--font-ibm-plex-sans',
29
+ weight: ['300', '400', '500', '600'],
30
+ subsets: ['latin'],
31
+ });
32
+
33
+ const lora = Lora({
34
+ variable: '--font-lora',
35
+ weight: ['400', '500', '600'],
36
+ style: ['normal', 'italic'],
37
+ subsets: ['latin'],
38
+ });
39
+
40
+ export const metadata: Metadata = {
41
+ title: 'MindOS',
42
+ description: 'Personal knowledge base',
43
+ icons: { icon: '/logo-square.svg' },
44
+ };
45
+
46
+ export const viewport = {
47
+ width: 'device-width',
48
+ initialScale: 1,
49
+ viewportFit: 'cover' as const,
50
+ };
51
+
52
+ export default async function RootLayout({
53
+ children,
54
+ }: Readonly<{
55
+ children: React.ReactNode;
56
+ }>) {
57
+ let fileTree: import('@/lib/types').FileNode[] = [];
58
+ try {
59
+ fileTree = getFileTree();
60
+ } catch (err) {
61
+ console.error('[RootLayout] Failed to load file tree:', err);
62
+ }
63
+
64
+ const headersList = await headers();
65
+ const isLoginPage = headersList.get('x-pathname') === '/login';
66
+
67
+ return (
68
+ <html lang="en" suppressHydrationWarning>
69
+ <head>
70
+ {/* Patch Node.removeChild/insertBefore to swallow errors caused by browser
71
+ extensions (translators, Grammarly, etc.) that mutate the DOM between SSR
72
+ and hydration. See: https://github.com/facebook/react/issues/17256 */}
73
+ <script
74
+ dangerouslySetInnerHTML={{
75
+ __html: `(function(){if(typeof Node!=='undefined'){var o=Node.prototype.removeChild;Node.prototype.removeChild=function(c){if(c.parentNode!==this){try{return o.call(c.parentNode,c)}catch(e){return c}}return o.call(this,c)};var i=Node.prototype.insertBefore;Node.prototype.insertBefore=function(n,r){if(r&&r.parentNode!==this){try{return i.call(r.parentNode,n,r)}catch(e){return i.call(this,n,null)}}return i.call(this,n,r)}}})();`,
76
+ }}
77
+ />
78
+ {/* Apply user appearance settings before first paint, preventing flash */}
79
+ <script
80
+ dangerouslySetInnerHTML={{
81
+ __html: `(function(){try{var s=localStorage.getItem('theme');var dark=s?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.classList.toggle('dark',dark);var cw=localStorage.getItem('content-width');if(cw)document.documentElement.style.setProperty('--content-width-override',cw);var pf=localStorage.getItem('prose-font');var fm={lora:'"Lora", Georgia, serif','ibm-plex-sans':'"IBM Plex Sans", sans-serif',geist:'var(--font-geist-sans), sans-serif','ibm-plex-mono':'"IBM Plex Mono", monospace'};if(pf&&fm[pf])document.documentElement.style.setProperty('--prose-font-override',fm[pf]);}catch(e){}})();`,
82
+ }}
83
+ />
84
+ </head>
85
+ <body
86
+ className={`${geistSans.variable} ${geistMono.variable} ${ibmPlexMono.variable} ${ibmPlexSans.variable} ${lora.variable} antialiased bg-background text-foreground`}
87
+ suppressHydrationWarning
88
+ >
89
+ <LocaleProvider>
90
+ <TooltipProvider delay={300}>
91
+ <ErrorBoundary>
92
+ {isLoginPage ? children : (
93
+ <SidebarLayout fileTree={fileTree}>
94
+ {children}
95
+ </SidebarLayout>
96
+ )}
97
+ </ErrorBoundary>
98
+ </TooltipProvider>
99
+ </LocaleProvider>
100
+ </body>
101
+ </html>
102
+ );
103
+ }
@@ -0,0 +1,120 @@
1
+ 'use client';
2
+
3
+ import { useState, Suspense } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import { Loader2, Lock } from 'lucide-react';
6
+
7
+ function LoginForm() {
8
+ const router = useRouter();
9
+ const searchParams = useSearchParams();
10
+ const [password, setPassword] = useState('');
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState('');
13
+
14
+ async function handleSubmit(e: React.FormEvent) {
15
+ e.preventDefault();
16
+ setLoading(true);
17
+ setError('');
18
+ try {
19
+ const res = await fetch('/api/auth', {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({ password }),
23
+ });
24
+ if (res.ok) {
25
+ const rawRedirect = searchParams.get('redirect') ?? '/';
26
+ // Safety: only allow relative paths starting with / to prevent open redirect
27
+ const safe = rawRedirect.startsWith('/') && !rawRedirect.startsWith('//')
28
+ ? rawRedirect
29
+ : '/';
30
+ router.replace(safe);
31
+ } else {
32
+ setError('Incorrect password. Please try again.');
33
+ setPassword('');
34
+ }
35
+ } catch {
36
+ setError('Connection error. Please try again.');
37
+ } finally {
38
+ setLoading(false);
39
+ }
40
+ }
41
+
42
+ return (
43
+ <div className="min-h-screen bg-background flex items-center justify-center px-4">
44
+ <div className="bg-card border border-border rounded-xl shadow-2xl w-full max-w-sm p-8">
45
+ {/* Logo + title */}
46
+ <div className="flex flex-col items-center gap-3 mb-8">
47
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" width={40} height={40}>
48
+ <defs>
49
+ <linearGradient id="lp-grad-human" x1="35" y1="40" x2="5" y2="40" gradientUnits="userSpaceOnUse">
50
+ <stop offset="0%" stopColor="#c8873a" stopOpacity="0.8"/>
51
+ <stop offset="100%" stopColor="#c8873a" stopOpacity="0.3"/>
52
+ </linearGradient>
53
+ <linearGradient id="lp-grad-agent" x1="35" y1="40" x2="75" y2="40" gradientUnits="userSpaceOnUse">
54
+ <stop offset="0%" stopColor="#c8873a" stopOpacity="0.8"/>
55
+ <stop offset="100%" stopColor="#c8873a" stopOpacity="1"/>
56
+ </linearGradient>
57
+ </defs>
58
+ <g transform="translate(0, 20)">
59
+ <path d="M35,20 C25,35 8,35 8,20 C8,5 25,5 35,20" stroke="url(#lp-grad-human)" strokeWidth="3" strokeDasharray="2 4" strokeLinecap="round"/>
60
+ <path d="M35,20 C45,2 75,2 75,20 C75,38 45,38 35,20" stroke="url(#lp-grad-agent)" strokeWidth="4.5" strokeLinecap="round"/>
61
+ <path d="M35,17.5 Q35,20 37.5,20 Q35,20 35,22.5 Q35,20 32.5,20 Q35,20 35,17.5 Z" fill="#FEF3C7"/>
62
+ </g>
63
+ </svg>
64
+ <h1
65
+ className="text-xl font-semibold text-foreground tracking-tight"
66
+ style={{ fontFamily: "'IBM Plex Mono', monospace" }}
67
+ >
68
+ MindOS
69
+ </h1>
70
+ <p className="text-sm text-muted-foreground">Enter your password to continue</p>
71
+ </div>
72
+
73
+ <form onSubmit={handleSubmit} className="space-y-4">
74
+ <div className="space-y-1.5">
75
+ <label className="text-sm font-medium text-foreground" htmlFor="password">
76
+ Password
77
+ </label>
78
+ <input
79
+ id="password"
80
+ type="password"
81
+ value={password}
82
+ onChange={e => setPassword(e.target.value)}
83
+ placeholder="Enter password"
84
+ autoFocus
85
+ autoComplete="current-password"
86
+ required
87
+ className="w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:ring-1 focus:ring-ring disabled:opacity-50"
88
+ />
89
+ </div>
90
+
91
+ {error && (
92
+ <p className="text-xs text-destructive">{error}</p>
93
+ )}
94
+
95
+ <button
96
+ type="submit"
97
+ disabled={loading || !password}
98
+ className="w-full flex items-center justify-center gap-2 py-2 px-4 rounded-lg text-sm font-medium transition-opacity disabled:opacity-50 disabled:cursor-not-allowed mt-2"
99
+ style={{ background: 'var(--amber)', color: '#131210' }}
100
+ >
101
+ {loading ? (
102
+ <Loader2 size={14} className="animate-spin" />
103
+ ) : (
104
+ <Lock size={14} />
105
+ )}
106
+ {loading ? 'Signing in…' : 'Sign in'}
107
+ </button>
108
+ </form>
109
+ </div>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ export default function LoginPage() {
115
+ return (
116
+ <Suspense>
117
+ <LoginForm />
118
+ </Suspense>
119
+ );
120
+ }
@@ -0,0 +1,12 @@
1
+ import { getRecentlyModified } from '@/lib/fs';
2
+ import HomeContent from '@/components/HomeContent';
3
+
4
+ export default function HomePage() {
5
+ let recent: { path: string; mtime: number }[] = [];
6
+ try {
7
+ recent = getRecentlyModified(15);
8
+ } catch (err) {
9
+ console.error('[HomePage] Failed to load recent files:', err);
10
+ }
11
+ return <HomeContent recent={recent} />;
12
+ }
@@ -0,0 +1,343 @@
1
+ 'use client';
2
+
3
+ import { useState, useTransition, useCallback, useEffect, useSyncExternalStore } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { Edit3, Save, X, Loader2, LayoutTemplate } from 'lucide-react';
6
+ import MarkdownView from '@/components/MarkdownView';
7
+ import JsonView from '@/components/JsonView';
8
+ import CsvView from '@/components/CsvView';
9
+ import Backlinks from '@/components/Backlinks';
10
+ import Breadcrumb from '@/components/Breadcrumb';
11
+ import MarkdownEditor, { MdViewMode } from '@/components/MarkdownEditor';
12
+ import TableOfContents from '@/components/TableOfContents';
13
+ import { resolveRenderer } from '@/lib/renderers/registry';
14
+ import { encodePath } from '@/lib/utils';
15
+ import '@/lib/renderers/index'; // registers all renderers
16
+
17
+ interface ViewPageClientProps {
18
+ filePath: string;
19
+ content: string;
20
+ extension: string;
21
+ saveAction: (content: string) => Promise<void>;
22
+ appendRowAction?: (newRow: string[]) => Promise<{ newContent: string }>;
23
+ initialEditing?: boolean;
24
+ isDraft?: boolean;
25
+ draftDirectories?: string[];
26
+ createDraftAction?: (targetPath: string, content: string) => Promise<void>;
27
+ }
28
+
29
+ export default function ViewPageClient({
30
+ filePath,
31
+ content,
32
+ extension,
33
+ saveAction,
34
+ appendRowAction,
35
+ initialEditing = false,
36
+ isDraft = false,
37
+ draftDirectories = [],
38
+ createDraftAction,
39
+ }: ViewPageClientProps) {
40
+ const hydrated = useSyncExternalStore(
41
+ () => () => {},
42
+ () => true,
43
+ () => false,
44
+ );
45
+
46
+ const useRaw = useSyncExternalStore(
47
+ (onStoreChange) => {
48
+ const listener = () => onStoreChange();
49
+ window.addEventListener('storage', listener);
50
+ window.addEventListener('mindos-use-raw-change', listener);
51
+ return () => {
52
+ window.removeEventListener('storage', listener);
53
+ window.removeEventListener('mindos-use-raw-change', listener);
54
+ };
55
+ },
56
+ () => {
57
+ const saved = localStorage.getItem('mindos-use-raw');
58
+ return saved !== null ? saved === 'true' : true;
59
+ },
60
+ () => false,
61
+ );
62
+ const router = useRouter();
63
+ const [editing, setEditing] = useState(initialEditing || content === '');
64
+ const [editContent, setEditContent] = useState(content);
65
+ const [savedContent, setSavedContent] = useState(content);
66
+ const [isPending, startTransition] = useTransition();
67
+ const [saveError, setSaveError] = useState<string | null>(null);
68
+ const [saveSuccess, setSaveSuccess] = useState(false);
69
+ const [mdViewMode, setMdViewMode] = useState<MdViewMode>('wysiwyg');
70
+
71
+ const inferredName = filePath.split('/').pop() || 'Untitled.md';
72
+ const [showSaveAs, setShowSaveAs] = useState(isDraft);
73
+ const [saveDir, setSaveDir] = useState('');
74
+ const [saveName, setSaveName] = useState(inferredName);
75
+
76
+ // Keep first paint deterministic between server and client to avoid hydration mismatch.
77
+ const effectiveUseRaw = hydrated ? useRaw : false;
78
+
79
+ const handleToggleRaw = useCallback(() => {
80
+ const next = !useRaw;
81
+ localStorage.setItem('mindos-use-raw', String(next));
82
+ window.dispatchEvent(new Event('mindos-use-raw-change'));
83
+ }, [useRaw]);
84
+
85
+ const renderer = resolveRenderer(filePath, extension);
86
+ const isCsv = extension === 'csv';
87
+ const showRenderer = !editing && !effectiveUseRaw && !!renderer;
88
+
89
+ const handleEdit = useCallback(() => {
90
+ setEditContent(savedContent);
91
+ setEditing(true);
92
+ setSaveError(null);
93
+ setSaveSuccess(false);
94
+ }, [savedContent]);
95
+
96
+ const handleCancel = useCallback(() => {
97
+ if (isDraft) {
98
+ router.push('/');
99
+ return;
100
+ }
101
+ setEditing(false);
102
+ setSaveError(null);
103
+ }, [isDraft, router]);
104
+
105
+ const handleConfirmDraftSave = useCallback(() => {
106
+ const trimmed = saveName.trim();
107
+ if (!trimmed) {
108
+ setSaveError('Please enter a file name');
109
+ return;
110
+ }
111
+ if (!createDraftAction) {
112
+ setSaveError('Draft save is not available');
113
+ return;
114
+ }
115
+
116
+ const finalName = trimmed.endsWith('.md') || trimmed.endsWith('.csv') ? trimmed : `${trimmed}.md`;
117
+ const targetPath = saveDir ? `${saveDir}/${finalName}` : finalName;
118
+
119
+ setSaveError(null);
120
+ startTransition(async () => {
121
+ try {
122
+ await createDraftAction(targetPath, editContent);
123
+ setSavedContent(editContent);
124
+ setEditing(false);
125
+ setShowSaveAs(false);
126
+ setSaveSuccess(true);
127
+ setTimeout(() => setSaveSuccess(false), 2500);
128
+ router.push(`/view/${encodePath(targetPath)}`);
129
+ router.refresh();
130
+ } catch (err) {
131
+ setSaveError(err instanceof Error ? err.message : 'Failed to save');
132
+ }
133
+ });
134
+ }, [saveName, createDraftAction, saveDir, editContent, router]);
135
+
136
+ const handleSave = useCallback(() => {
137
+ if (isCsv) {
138
+ setEditing(false);
139
+ setSaveSuccess(true);
140
+ setTimeout(() => setSaveSuccess(false), 2500);
141
+ return;
142
+ }
143
+
144
+ if (isDraft) {
145
+ setShowSaveAs(true);
146
+ return;
147
+ }
148
+
149
+ setSaveError(null);
150
+ startTransition(async () => {
151
+ try {
152
+ await saveAction(editContent);
153
+ setSavedContent(editContent);
154
+ setEditing(false);
155
+ setSaveSuccess(true);
156
+ setTimeout(() => setSaveSuccess(false), 2500);
157
+ } catch (err) {
158
+ setSaveError(err instanceof Error ? err.message : 'Failed to save');
159
+ }
160
+ });
161
+ }, [isCsv, isDraft, saveAction, editContent]);
162
+
163
+ // Renderer's inline save — updates local savedContent without entering edit mode
164
+ const handleRendererSave = useCallback(async (newContent: string) => {
165
+ await saveAction(newContent);
166
+ setSavedContent(newContent);
167
+ }, [saveAction]);
168
+
169
+ useEffect(() => {
170
+ const handler = (e: KeyboardEvent) => {
171
+ if ((e.metaKey || e.ctrlKey) && e.key === 's') {
172
+ e.preventDefault();
173
+ if (editing) handleSave();
174
+ }
175
+ if (e.key === 'e' && !editing && document.activeElement?.tagName === 'BODY') {
176
+ handleEdit();
177
+ }
178
+ if (e.key === 'Escape' && editing) handleCancel();
179
+ };
180
+ window.addEventListener('keydown', handler);
181
+ return () => window.removeEventListener('keydown', handler);
182
+ }, [editing, handleSave, handleEdit, handleCancel]);
183
+
184
+ return (
185
+ <div className="flex flex-col min-h-screen">
186
+ {/* Top bar */}
187
+ <div className="sticky top-[52px] md:top-0 z-20 border-b border-border px-4 md:px-6 py-2.5" style={{ background: 'var(--background)' }}>
188
+ <div className="content-width xl:mr-[220px] flex items-center justify-between gap-2">
189
+ <div className="min-w-0 flex-1">
190
+ <Breadcrumb filePath={filePath} />
191
+ </div>
192
+
193
+ <div className="flex items-center gap-1.5 md:gap-2 shrink-0">
194
+ {saveSuccess && (
195
+ <span className="text-xs flex items-center gap-1.5" style={{ color: '#7aad80', fontFamily: "'IBM Plex Mono', monospace" }}>
196
+ <span className="w-1.5 h-1.5 rounded-full" style={{ background: '#7aad80' }} />
197
+ <span className="hidden sm:inline">saved</span>
198
+ </span>
199
+ )}
200
+ {saveError && (
201
+ <span className="text-xs text-red-400 hidden sm:inline">{saveError}</span>
202
+ )}
203
+
204
+ {/* Renderer toggle — only shown when a custom renderer exists */}
205
+ {renderer && !editing && !isDraft && (
206
+ <button
207
+ onClick={handleToggleRaw}
208
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors"
209
+ style={{
210
+ background: effectiveUseRaw ? 'var(--muted)' : `${'var(--amber)'}22`,
211
+ color: effectiveUseRaw ? 'var(--muted-foreground)' : 'var(--amber)',
212
+ fontFamily: "'IBM Plex Mono', monospace",
213
+ }}
214
+ title={effectiveUseRaw ? `Switch to ${renderer.name}` : 'View raw'}
215
+ >
216
+ <LayoutTemplate size={13} />
217
+ <span className="hidden sm:inline">{effectiveUseRaw ? renderer.name : 'Raw'}</span>
218
+ </button>
219
+ )}
220
+
221
+ {!editing && !showRenderer && !isDraft && (
222
+ <button
223
+ onClick={handleEdit}
224
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors"
225
+ style={{ background: 'var(--muted)', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono', monospace" }}
226
+ onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--foreground)'; e.currentTarget.style.background = 'var(--accent)'; }}
227
+ onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--muted-foreground)'; e.currentTarget.style.background = 'var(--muted)'; }}
228
+ >
229
+ <Edit3 size={13} />
230
+ <span className="hidden sm:inline">Edit</span>
231
+ </button>
232
+ )}
233
+ {editing && (
234
+ <>
235
+ <button
236
+ onClick={handleCancel}
237
+ disabled={isPending}
238
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors disabled:opacity-50"
239
+ style={{ background: 'var(--muted)', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono', monospace" }}
240
+ onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--accent)'; }}
241
+ onMouseLeave={(e) => { e.currentTarget.style.background = 'var(--muted)'; }}
242
+ >
243
+ <X size={13} />
244
+ <span className="hidden sm:inline">Cancel</span>
245
+ </button>
246
+ <button
247
+ onClick={isDraft && showSaveAs ? handleConfirmDraftSave : handleSave}
248
+ disabled={isPending}
249
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium disabled:opacity-50"
250
+ style={{ background: 'var(--amber)', color: '#131210', fontFamily: "'IBM Plex Mono', monospace" }}
251
+ >
252
+ {isPending ? <Loader2 size={13} className="animate-spin" /> : <Save size={13} />}
253
+ <span className="hidden sm:inline">Save</span>
254
+ </button>
255
+ </>
256
+ )}
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ {/* Content */}
262
+ <div className="flex-1 px-4 md:px-6 py-6 md:py-8">
263
+ {editing ? (
264
+ <div className="content-width xl:mr-[220px]">
265
+ {isDraft && showSaveAs && (
266
+ <div className="mb-3 rounded-lg border border-border bg-card p-3 flex flex-col md:flex-row md:items-end gap-2">
267
+ <div className="flex-1 min-w-[180px]">
268
+ <label className="text-xs text-muted-foreground">Directory</label>
269
+ <select
270
+ value={saveDir}
271
+ onChange={(e) => setSaveDir(e.target.value)}
272
+ className="mt-1 w-full px-2 py-1.5 text-sm bg-background border border-border rounded text-foreground"
273
+ >
274
+ <option value="">/</option>
275
+ {draftDirectories.map((dir) => (
276
+ <option key={dir} value={dir}>{dir}</option>
277
+ ))}
278
+ </select>
279
+ </div>
280
+ <div className="flex-1 min-w-[200px]">
281
+ <label className="text-xs text-muted-foreground">File name</label>
282
+ <input
283
+ value={saveName}
284
+ onChange={(e) => setSaveName(e.target.value)}
285
+ onKeyDown={(e) => { if (e.key === 'Enter') handleConfirmDraftSave(); }}
286
+ className="mt-1 w-full px-2 py-1.5 text-sm bg-background border border-border rounded text-foreground"
287
+ placeholder="Untitled.md"
288
+ />
289
+ </div>
290
+ </div>
291
+ )}
292
+ {isCsv ? (
293
+ <CsvView
294
+ content={editContent}
295
+ filePath={filePath}
296
+ appendAction={appendRowAction}
297
+ saveAction={async (c) => {
298
+ await saveAction(c);
299
+ setEditContent(c);
300
+ setSavedContent(c);
301
+ }}
302
+ />
303
+ ) : (
304
+ <MarkdownEditor
305
+ value={editContent}
306
+ onChange={setEditContent}
307
+ viewMode={mdViewMode}
308
+ onViewModeChange={setMdViewMode}
309
+ />
310
+ )}
311
+ </div>
312
+ ) : showRenderer ? (
313
+ <div className="content-width xl:mr-[220px]">
314
+ <renderer.component
315
+ filePath={filePath}
316
+ content={savedContent}
317
+ extension={extension}
318
+ saveAction={handleRendererSave}
319
+ />
320
+ <Backlinks filePath={filePath} />
321
+ </div>
322
+ ) : (
323
+ <div className="content-width xl:mr-[220px]">
324
+ {extension === 'csv' ? (
325
+ <CsvView
326
+ content={savedContent}
327
+ filePath={filePath}
328
+ />
329
+ ) : extension === 'json' ? (
330
+ <JsonView content={savedContent} />
331
+ ) : (
332
+ <>
333
+ <MarkdownView content={savedContent} />
334
+ <TableOfContents content={savedContent} />
335
+ </>
336
+ )}
337
+ <Backlinks filePath={filePath} />
338
+ </div>
339
+ )}
340
+ </div>
341
+ </div>
342
+ );
343
+ }
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ export default function ViewError({
6
+ error,
7
+ reset,
8
+ }: {
9
+ error: Error & { digest?: string };
10
+ reset: () => void;
11
+ }) {
12
+ useEffect(() => {
13
+ console.error('View error:', error);
14
+ }, [error]);
15
+
16
+ return (
17
+ <div className="flex flex-col items-center justify-center min-h-[50vh] gap-4 px-6">
18
+ <div className="text-center">
19
+ <h2 className="text-lg font-semibold text-foreground mb-2">Failed to load file</h2>
20
+ <p className="text-sm text-muted-foreground max-w-md">
21
+ {error.message || 'The file could not be read or rendered.'}
22
+ </p>
23
+ </div>
24
+ <button
25
+ onClick={reset}
26
+ className="px-4 py-2 rounded-md text-sm font-medium transition-colors"
27
+ style={{ background: 'var(--muted)', color: 'var(--foreground)' }}
28
+ >
29
+ Try again
30
+ </button>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,15 @@
1
+ export default function Loading() {
2
+ return (
3
+ <div className="flex items-center justify-center min-h-[50vh]">
4
+ <div className="flex flex-col items-center gap-3">
5
+ <div
6
+ className="w-6 h-6 border-2 rounded-full animate-spin"
7
+ style={{ borderColor: 'var(--border)', borderTopColor: 'var(--amber)' }}
8
+ />
9
+ <span className="text-xs text-muted-foreground" style={{ fontFamily: "'IBM Plex Mono', monospace" }}>
10
+ Loading...
11
+ </span>
12
+ </div>
13
+ </div>
14
+ );
15
+ }