@geminilight/mindos 0.6.61 → 0.6.65

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 (313) hide show
  1. package/_standalone/.antigravity/mcp_config.json +14 -0
  2. package/_standalone/.mindos-build-version +1 -1
  3. package/_standalone/.next/BUILD_ID +1 -1
  4. package/_standalone/.next/app-path-routes-manifest.json +23 -23
  5. package/_standalone/.next/build-manifest.json +2 -2
  6. package/_standalone/.next/cache/.previewinfo +1 -1
  7. package/_standalone/.next/cache/.rscinfo +1 -1
  8. package/_standalone/.next/cache/config.json +3 -3
  9. package/_standalone/.next/prerender-manifest.json +3 -3
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +1 -1
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/registry/route.js +1 -1
  38. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
  42. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/ask/route.js +53 -47
  46. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  47. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  51. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  53. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  55. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  57. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/file/import/route.js +1 -1
  60. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  61. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
  63. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  65. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  67. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  69. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  71. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  74. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  76. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
  78. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  79. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  86. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  90. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  91. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
  93. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/settings/route.js +1 -1
  96. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  97. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
  99. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  102. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/api/setup/route.js +1 -1
  105. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/api/skills/route.js +1 -1
  107. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  108. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  110. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  111. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  113. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  114. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  115. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  116. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  117. package/_standalone/.next/server/app/changes/page.js +1 -1
  118. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  119. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  120. package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
  121. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  122. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  123. package/_standalone/.next/server/app/echo/page.js +1 -1
  124. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  125. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  126. package/_standalone/.next/server/app/explore/page.js +1 -1
  127. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  128. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  129. package/_standalone/.next/server/app/help/page.js +1 -1
  130. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  131. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  132. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  133. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  134. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  135. package/_standalone/.next/server/app/login/page.js +1 -1
  136. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  137. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  138. package/_standalone/.next/server/app/page.js +1 -1
  139. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  140. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/_standalone/.next/server/app/setup/page.js +2 -2
  142. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  143. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  144. package/_standalone/.next/server/app/trash/page.js +3 -3
  145. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  146. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  147. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  148. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  149. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  150. package/_standalone/.next/server/app/wiki/page.js +1 -1
  151. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  152. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  153. package/_standalone/.next/server/app-paths-manifest.json +23 -23
  154. package/_standalone/.next/server/chunks/122.js +222 -0
  155. package/_standalone/.next/server/chunks/1550.js +1 -1
  156. package/_standalone/.next/server/chunks/1750.js +1 -1
  157. package/_standalone/.next/server/chunks/3113.js +52 -0
  158. package/_standalone/.next/server/chunks/6539.js +1 -1
  159. package/_standalone/.next/server/chunks/8388.js +3 -3
  160. package/_standalone/.next/server/chunks/953.js +3 -3
  161. package/_standalone/.next/server/pages/500.html +2 -2
  162. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  163. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  164. package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
  165. package/_standalone/.next/static/chunks/1088-77544af0a50cb7a4.js +1 -0
  166. package/_standalone/.next/static/chunks/1467-87dde7eed498806f.js +1 -0
  167. package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
  168. package/_standalone/.next/static/chunks/5581-c671163a2fe1b312.js +29 -0
  169. package/_standalone/.next/static/chunks/{7266-bb7be1128eccd48e.js → 5718-3837c3210a0e175f.js} +2 -2
  170. package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
  171. package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
  172. package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
  173. package/_standalone/.next/static/chunks/7294-cac25d97869afadc.js +1 -0
  174. package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
  175. package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
  176. package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
  177. package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-2f5cf97e03dc1cc9.js +1 -0
  178. package/_standalone/.next/static/chunks/app/agents/page-50eac58d511dcc6e.js +1 -0
  179. package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
  180. package/_standalone/.next/static/chunks/app/layout-2cb7a6602d2e5d5f.js +168 -0
  181. package/_standalone/.next/static/chunks/app/{page-6a1f8d21c12b829e.js → page-5ab911b2226f6ff7.js} +1 -1
  182. package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
  183. package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
  184. package/_standalone/.next/static/chunks/app/view/[...path]/page-26e47dd4c533a58c.js +12 -0
  185. package/_standalone/.next/static/chunks/app/wiki/page-dce495b9048022fb.js +1 -0
  186. package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
  187. package/_standalone/.next/trace +65 -65
  188. package/_standalone/__tests__/acp/registry.test.ts +30 -20
  189. package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
  190. package/_standalone/__tests__/api/mcp-install.test.ts +49 -2
  191. package/_standalone/__tests__/api/settings.test.ts +16 -12
  192. package/_standalone/__tests__/api/setup.test.ts +11 -9
  193. package/_standalone/__tests__/api/test-key.test.ts +0 -10
  194. package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
  195. package/_standalone/__tests__/core/context.test.ts +48 -426
  196. package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
  197. package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
  198. package/_standalone/__tests__/setup.ts +5 -5
  199. package/_standalone/app/globals.css +4 -4
  200. package/_standalone/components/ActivityBar.tsx +17 -6
  201. package/_standalone/components/Panel.tsx +24 -6
  202. package/_standalone/components/SidebarLayout.tsx +36 -8
  203. package/_standalone/components/agents/AgentsMcpSection.tsx +2 -2
  204. package/_standalone/components/agents/AgentsOverviewSection.tsx +5 -1
  205. package/_standalone/components/agents/AgentsPanelA2aTab.tsx +173 -113
  206. package/_standalone/components/agents/AgentsSkillsSection.tsx +2 -2
  207. package/_standalone/components/ask/AskContent.tsx +83 -44
  208. package/_standalone/components/ask/AskHeader.tsx +8 -1
  209. package/_standalone/components/ask/MessageList.tsx +37 -3
  210. package/_standalone/components/ask/ProviderModelCapsule.tsx +444 -174
  211. package/_standalone/components/home/InboxSection.tsx +25 -25
  212. package/_standalone/components/settings/AiTab.tsx +353 -298
  213. package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
  214. package/_standalone/components/settings/CustomProvidersCard.tsx +154 -0
  215. package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
  216. package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
  217. package/_standalone/components/settings/Primitives.tsx +48 -104
  218. package/_standalone/components/settings/ProviderModal.tsx +87 -0
  219. package/_standalone/components/settings/SettingsContent.tsx +2 -5
  220. package/_standalone/components/settings/TestButton.tsx +64 -0
  221. package/_standalone/components/settings/types.ts +3 -9
  222. package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
  223. package/_standalone/components/setup/StepAI.tsx +12 -5
  224. package/_standalone/components/shared/ModelInput.tsx +220 -0
  225. package/_standalone/components/shared/ProviderSelect.tsx +126 -36
  226. package/_standalone/hooks/useAskChat.ts +100 -13
  227. package/_standalone/hooks/useAskPanel.ts +17 -1
  228. package/_standalone/lib/settings-ai-client.ts +17 -8
  229. package/_standalone/tsconfig.tsbuildinfo +1 -1
  230. package/app/.antigravity/mcp_config.json +14 -0
  231. package/app/app/api/ask/route.ts +154 -45
  232. package/app/app/api/mcp/agents/route.ts +3 -3
  233. package/app/app/api/settings/list-models/route.ts +36 -9
  234. package/app/app/api/settings/route.ts +14 -42
  235. package/app/app/api/settings/test-key/route.ts +78 -2
  236. package/app/app/api/setup/route.ts +36 -18
  237. package/app/app/api/skills/route.ts +1 -1
  238. package/app/app/globals.css +4 -4
  239. package/app/app/layout.tsx +5 -3
  240. package/app/app/view/[...path]/page.tsx +5 -0
  241. package/app/components/ActivityBar.tsx +17 -6
  242. package/app/components/HomeContent.tsx +11 -0
  243. package/app/components/InboxView.tsx +656 -0
  244. package/app/components/Panel.tsx +24 -6
  245. package/app/components/SidebarLayout.tsx +36 -8
  246. package/app/components/UpdateToast.tsx +255 -0
  247. package/app/components/agents/AgentDetailContent.tsx +8 -8
  248. package/app/components/agents/AgentsMcpSection.tsx +2 -2
  249. package/app/components/agents/AgentsOverviewSection.tsx +5 -1
  250. package/app/components/agents/AgentsPanelA2aTab.tsx +173 -113
  251. package/app/components/agents/AgentsSkillsSection.tsx +2 -2
  252. package/app/components/ask/AskContent.tsx +83 -44
  253. package/app/components/ask/AskHeader.tsx +8 -1
  254. package/app/components/ask/MessageList.tsx +37 -3
  255. package/app/components/ask/ProviderModelCapsule.tsx +444 -174
  256. package/app/components/home/InboxSection.tsx +25 -25
  257. package/app/components/settings/AiTab.tsx +353 -298
  258. package/app/components/settings/CustomProviderFields.tsx +121 -0
  259. package/app/components/settings/CustomProvidersCard.tsx +154 -0
  260. package/app/components/settings/KnowledgeTab.tsx +6 -20
  261. package/app/components/settings/McpAgentInstall.tsx +7 -2
  262. package/app/components/settings/Primitives.tsx +48 -104
  263. package/app/components/settings/ProviderModal.tsx +87 -0
  264. package/app/components/settings/SettingsContent.tsx +2 -5
  265. package/app/components/settings/TestButton.tsx +64 -0
  266. package/app/components/settings/types.ts +3 -9
  267. package/app/components/settings/useCustomProviderForm.ts +132 -0
  268. package/app/components/setup/StepAI.tsx +12 -5
  269. package/app/components/shared/ModelInput.tsx +220 -0
  270. package/app/components/shared/ProviderSelect.tsx +126 -36
  271. package/app/hooks/useAskChat.ts +100 -13
  272. package/app/hooks/useAskPanel.ts +17 -1
  273. package/app/lib/acp/registry.ts +92 -10
  274. package/app/lib/agent/context.ts +65 -0
  275. package/app/lib/agent/providers.ts +25 -0
  276. package/app/lib/agent/tools.ts +1 -1
  277. package/app/lib/custom-endpoints.ts +160 -0
  278. package/app/lib/fs.ts +8 -1
  279. package/app/lib/i18n/modules/ai-chat.ts +6 -0
  280. package/app/lib/i18n/modules/knowledge.ts +16 -0
  281. package/app/lib/i18n/modules/onboarding.ts +4 -0
  282. package/app/lib/i18n/modules/settings.ts +88 -2
  283. package/app/lib/mcp-agents.ts +11 -0
  284. package/app/lib/pi-integration/skills.ts +16 -4
  285. package/app/lib/settings-ai-client.ts +17 -8
  286. package/app/lib/settings.ts +68 -72
  287. package/app/lib/types.ts +4 -0
  288. package/bin/lib/mcp-agents.js +11 -0
  289. package/bin/lib/mcp-install.js +71 -7
  290. package/package.json +1 -1
  291. package/_standalone/.next/server/chunks/530.js +0 -218
  292. package/_standalone/.next/server/chunks/8955.js +0 -52
  293. package/_standalone/.next/static/chunks/1369-7d0ac5d1564eed1e.js +0 -1
  294. package/_standalone/.next/static/chunks/3427-2e61a5df1f5e55fb.js +0 -1
  295. package/_standalone/.next/static/chunks/5581-0c700c20718bd916.js +0 -29
  296. package/_standalone/.next/static/chunks/6297-085daa21037d5f81.js +0 -1
  297. package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
  298. package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
  299. package/_standalone/.next/static/chunks/8520-76d1b05072178b43.js +0 -22
  300. package/_standalone/.next/static/chunks/8658-16ff58b75ae37fbb.js +0 -1
  301. package/_standalone/.next/static/chunks/9905-a19d379cb225246e.js +0 -1
  302. package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-0ea3571c8fbae823.js +0 -1
  303. package/_standalone/.next/static/chunks/app/agents/page-66858acbcd1d4bf8.js +0 -1
  304. package/_standalone/.next/static/chunks/app/echo/[segment]/page-bf5c290fa3ccff09.js +0 -11
  305. package/_standalone/.next/static/chunks/app/layout-a5d5925b47e87cc3.js +0 -164
  306. package/_standalone/.next/static/chunks/app/setup/page-821714e7477be46c.js +0 -1
  307. package/_standalone/.next/static/chunks/app/trash/page-40bc7316806acd62.js +0 -1
  308. package/_standalone/.next/static/chunks/app/view/[...path]/page-6fbb14b8f322d0f0.js +0 -12
  309. package/_standalone/.next/static/chunks/app/wiki/page-ba36eccf4fe62cfe.js +0 -1
  310. package/_standalone/.next/static/css/b57c4eb3cc88308b.css +0 -1
  311. package/_standalone/lib/agent/context.ts +0 -403
  312. /package/_standalone/.next/static/{5GmVArEG8OX03azKICsGq → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
  313. /package/_standalone/.next/static/{5GmVArEG8OX03azKICsGq → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
@@ -0,0 +1,656 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
4
+ import Link from 'next/link';
5
+ import { useRouter } from 'next/navigation';
6
+ import {
7
+ Inbox,
8
+ Sparkles,
9
+ FileText,
10
+ FileCode,
11
+ Table,
12
+ AlertCircle,
13
+ Loader2,
14
+ Upload,
15
+ FolderInput,
16
+ Check,
17
+ ChevronDown,
18
+ X,
19
+ ExternalLink,
20
+ Copy,
21
+ Trash2,
22
+ ArrowLeft,
23
+ History,
24
+ } from 'lucide-react';
25
+ import { toast } from '@/lib/toast';
26
+ import { useLocale } from '@/lib/stores/locale-store';
27
+ import { encodePath } from '@/lib/utils';
28
+ import { quickDropToInbox } from '@/lib/inbox-upload';
29
+ import { loadHistory, type OrganizeHistoryEntry, type OrganizeSource } from '@/lib/organize-history';
30
+
31
+ interface InboxFile {
32
+ name: string;
33
+ path: string;
34
+ size: number;
35
+ modifiedAt: string;
36
+ isAging: boolean;
37
+ }
38
+
39
+ const HISTORY_VISIBLE = 5;
40
+
41
+ const EXT_STYLES: Record<string, { bg: string; text: string }> = {
42
+ md: { bg: 'bg-blue-500/10', text: 'text-blue-500/70' },
43
+ txt: { bg: 'bg-muted/50', text: 'text-muted-foreground/60' },
44
+ csv: { bg: 'bg-emerald-500/10', text: 'text-emerald-500/70' },
45
+ json: { bg: 'bg-violet-500/10', text: 'text-violet-500/70' },
46
+ pdf: { bg: 'bg-red-500/10', text: 'text-red-500/60' },
47
+ };
48
+
49
+ function getFileExt(name: string): string {
50
+ const dot = name.lastIndexOf('.');
51
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
52
+ }
53
+
54
+ function getFileBaseName(name: string): string {
55
+ const dot = name.lastIndexOf('.');
56
+ return dot > 0 ? name.slice(0, dot) : name;
57
+ }
58
+
59
+ export default function InboxView() {
60
+ const { t } = useLocale();
61
+ const router = useRouter();
62
+ const [files, setFiles] = useState<InboxFile[]>([]);
63
+ const [loading, setLoading] = useState(true);
64
+ const [organizing, setOrganizing] = useState(false);
65
+ const [dragOver, setDragOver] = useState(false);
66
+ const [history, setHistory] = useState<OrganizeHistoryEntry[]>([]);
67
+ const fileInputRef = useRef<HTMLInputElement>(null);
68
+ const dragCounterRef = useRef(0);
69
+
70
+ const fetchInbox = useCallback(async () => {
71
+ try {
72
+ const res = await fetch('/api/inbox');
73
+ if (!res.ok) return;
74
+ const data = await res.json();
75
+ if (Array.isArray(data.files)) setFiles(data.files);
76
+ } catch (err) {
77
+ console.warn('[InboxView] fetch failed:', err);
78
+ } finally {
79
+ setLoading(false);
80
+ }
81
+ }, []);
82
+
83
+ const refreshHistory = useCallback(() => {
84
+ setHistory(loadHistory());
85
+ }, []);
86
+
87
+ const refreshTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
88
+ const debouncedRefresh = useCallback(() => {
89
+ clearTimeout(refreshTimerRef.current);
90
+ refreshTimerRef.current = setTimeout(() => {
91
+ fetchInbox();
92
+ refreshHistory();
93
+ }, 80);
94
+ }, [fetchInbox, refreshHistory]);
95
+
96
+ useEffect(() => {
97
+ fetchInbox();
98
+ refreshHistory();
99
+
100
+ const onOrganizeDone = () => { setOrganizing(false); debouncedRefresh(); };
101
+ const resetDrag = () => { dragCounterRef.current = 0; setDragOver(false); };
102
+
103
+ window.addEventListener('mindos:files-changed', debouncedRefresh);
104
+ window.addEventListener('mindos:inbox-updated', debouncedRefresh);
105
+ window.addEventListener('mindos:organize-done', onOrganizeDone);
106
+ window.addEventListener('mindos:organize-history-update', refreshHistory);
107
+ window.addEventListener('drop', resetDrag, true);
108
+ window.addEventListener('dragend', resetDrag, true);
109
+ return () => {
110
+ clearTimeout(refreshTimerRef.current);
111
+ window.removeEventListener('mindos:files-changed', debouncedRefresh);
112
+ window.removeEventListener('mindos:inbox-updated', debouncedRefresh);
113
+ window.removeEventListener('mindos:organize-done', onOrganizeDone);
114
+ window.removeEventListener('mindos:organize-history-update', refreshHistory);
115
+ window.removeEventListener('drop', resetDrag, true);
116
+ window.removeEventListener('dragend', resetDrag, true);
117
+ };
118
+ }, [fetchInbox, debouncedRefresh, refreshHistory]);
119
+
120
+ const handleOrganize = useCallback(() => {
121
+ if (files.length === 0 || organizing) return;
122
+ setOrganizing(true);
123
+ window.dispatchEvent(
124
+ new CustomEvent('mindos:inbox-organize', { detail: { files } }),
125
+ );
126
+ }, [files, organizing]);
127
+
128
+ const handleDeleteFile = useCallback(async (name: string) => {
129
+ try {
130
+ const res = await fetch('/api/inbox', {
131
+ method: 'DELETE',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify({ names: [name] }),
134
+ });
135
+ if (!res.ok) throw new Error('Failed to delete');
136
+ setFiles(prev => prev.filter(f => f.name !== name));
137
+ window.dispatchEvent(new Event('mindos:inbox-updated'));
138
+ toast.success(t.inbox.fileRemoved);
139
+ } catch {
140
+ toast.error(t.inbox.fileRemoveFailed);
141
+ }
142
+ }, [t]);
143
+
144
+ const handleUpload = useCallback((selected: FileList | null) => {
145
+ if (!selected || selected.length === 0) return;
146
+ quickDropToInbox(Array.from(selected), t);
147
+ if (fileInputRef.current) fileInputRef.current.value = '';
148
+ }, [t]);
149
+
150
+ const handleDrop = useCallback((e: React.DragEvent) => {
151
+ e.preventDefault();
152
+ e.stopPropagation();
153
+ dragCounterRef.current = 0;
154
+ setDragOver(false);
155
+ if (e.dataTransfer.files.length > 0) {
156
+ quickDropToInbox(Array.from(e.dataTransfer.files), t);
157
+ }
158
+ }, [t]);
159
+
160
+ const agingCount = useMemo(() => files.filter(f => f.isAging).length, [files]);
161
+ const hasFiles = files.length > 0;
162
+ const visibleHistory = useMemo(() => history.slice(0, HISTORY_VISIBLE), [history]);
163
+ const [animateList, setAnimateList] = useState(true);
164
+ const prevFileCountRef = useRef(0);
165
+ useEffect(() => {
166
+ if (prevFileCountRef.current > 0 && files.length > 0) setAnimateList(false);
167
+ prevFileCountRef.current = files.length;
168
+ }, [files.length]);
169
+
170
+ if (loading) {
171
+ return (
172
+ <div className="flex flex-col min-h-screen">
173
+ <div className="sticky top-[52px] md:top-0 z-20 border-b border-border px-4 md:px-6 py-3 bg-background">
174
+ <div className="max-w-[780px] mx-auto">
175
+ <div className="h-5 w-32 bg-muted rounded animate-pulse" />
176
+ </div>
177
+ </div>
178
+ <div className="flex-1 px-4 md:px-6 py-8">
179
+ <div className="max-w-[780px] mx-auto space-y-3">
180
+ {[...Array(4)].map((_, i) => (
181
+ <div key={i} className="h-12 bg-muted/40 rounded-lg animate-pulse" />
182
+ ))}
183
+ </div>
184
+ </div>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ return (
190
+ <div className="flex flex-col min-h-screen">
191
+ {/* Hidden file input */}
192
+ <input
193
+ ref={fileInputRef}
194
+ type="file"
195
+ multiple
196
+ accept=".md,.txt,.csv,.json,.pdf"
197
+ className="hidden"
198
+ onChange={(e) => handleUpload(e.target.files)}
199
+ />
200
+
201
+ {/* ─── Sticky Top Bar ─── */}
202
+ <div className="sticky top-[52px] md:top-0 z-20 border-b border-border bg-background">
203
+ <div className="max-w-[780px] mx-auto px-4 md:px-6 py-3">
204
+ <div className="flex items-center gap-3">
205
+ {/* Back */}
206
+ <button
207
+ onClick={() => router.push('/wiki')}
208
+ className="p-1.5 -ml-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
209
+ title="Back"
210
+ >
211
+ <ArrowLeft size={16} />
212
+ </button>
213
+
214
+ {/* Title area */}
215
+ <div className="flex items-center gap-2.5 flex-1 min-w-0">
216
+ <div className="flex items-center justify-center w-7 h-7 rounded-lg bg-[var(--amber-subtle)] text-[var(--amber)]">
217
+ <Inbox size={15} />
218
+ </div>
219
+ <div className="min-w-0">
220
+ <h1 className="text-sm font-semibold text-foreground tracking-tight leading-tight">
221
+ {t.inbox.title}
222
+ </h1>
223
+ {hasFiles && (
224
+ <p className="text-2xs text-muted-foreground/60 leading-tight mt-0.5">
225
+ {t.inbox.fileCount(files.length)}
226
+ {agingCount > 0 && (
227
+ <span className="text-[var(--amber)]/70"> · {agingCount} {t.inbox.agingHint}</span>
228
+ )}
229
+ </p>
230
+ )}
231
+ </div>
232
+ </div>
233
+
234
+ {/* Actions */}
235
+ <div className="flex items-center gap-2 shrink-0">
236
+ <button
237
+ type="button"
238
+ onClick={() => fileInputRef.current?.click()}
239
+ className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-lg transition-colors text-muted-foreground hover:text-foreground hover:bg-muted"
240
+ title={t.inbox.uploadButton}
241
+ >
242
+ <Upload size={13} />
243
+ <span className="hidden sm:inline">{t.inbox.uploadButton}</span>
244
+ </button>
245
+ {hasFiles && (
246
+ <button
247
+ onClick={handleOrganize}
248
+ disabled={organizing}
249
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg transition-all bg-[var(--amber)] text-[var(--amber-foreground)] hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed"
250
+ title={organizing ? t.inbox.organizing : t.inbox.organizeButton}
251
+ >
252
+ {organizing ? (
253
+ <Loader2 size={13} className="animate-spin" />
254
+ ) : (
255
+ <Sparkles size={13} />
256
+ )}
257
+ <span>{organizing ? t.inbox.organizing : t.inbox.organizeButton}</span>
258
+ </button>
259
+ )}
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </div>
264
+
265
+ {/* ─── Main Content ─── */}
266
+ <div className="flex-1 px-4 md:px-6 py-6">
267
+ <div className="max-w-[780px] mx-auto">
268
+
269
+ {/* ─── Drop Zone ─── */}
270
+ <div
271
+ className={`rounded-xl border-2 border-dashed transition-all duration-200 cursor-pointer mb-6 ${
272
+ dragOver
273
+ ? 'border-[var(--amber)] bg-[var(--amber-subtle)] scale-[1.01]'
274
+ : hasFiles
275
+ ? 'border-border/40 hover:border-[var(--amber)]/30 bg-[var(--amber-subtle)]/30 hover:bg-[var(--amber-subtle)]/50 px-4 py-3.5'
276
+ : 'border-border hover:border-[var(--amber)]/40 px-4 py-10'
277
+ } ${dragOver ? 'px-4 py-6' : ''}`}
278
+ role="button"
279
+ tabIndex={0}
280
+ aria-label={t.inbox.uploadButton}
281
+ onClick={() => fileInputRef.current?.click()}
282
+ onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fileInputRef.current?.click(); } }}
283
+ onDragEnter={(e) => {
284
+ if (!e.dataTransfer.types.includes('Files')) return;
285
+ e.preventDefault();
286
+ e.stopPropagation();
287
+ dragCounterRef.current++;
288
+ if (dragCounterRef.current === 1) setDragOver(true);
289
+ }}
290
+ onDragOver={(e) => {
291
+ if (!e.dataTransfer.types.includes('Files')) return;
292
+ e.preventDefault();
293
+ e.stopPropagation();
294
+ }}
295
+ onDragLeave={(e) => {
296
+ e.stopPropagation();
297
+ dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
298
+ if (dragCounterRef.current === 0) setDragOver(false);
299
+ }}
300
+ onDrop={handleDrop}
301
+ >
302
+ {hasFiles ? (
303
+ <div className="flex items-center justify-center gap-2 text-center">
304
+ <FolderInput size={16} className={`shrink-0 ${dragOver ? 'text-[var(--amber)]' : 'text-muted-foreground/30'}`} />
305
+ <p className="text-xs text-muted-foreground/60">
306
+ {t.fileImport.dropzoneCompact}{' '}
307
+ <span className="text-[var(--amber)] hover:underline">{t.fileImport.dropzoneCompactButton}</span>
308
+ </p>
309
+ </div>
310
+ ) : (
311
+ <div className="flex flex-col items-center gap-4 text-center">
312
+ <div className={`flex items-center justify-center w-14 h-14 rounded-2xl transition-colors duration-200 ${
313
+ dragOver ? 'bg-[var(--amber)]/15' : 'bg-[var(--amber-subtle)]'
314
+ }`}>
315
+ <FolderInput size={26} className={`transition-colors duration-200 ${dragOver ? 'text-[var(--amber)]' : 'text-[var(--amber)]/35'}`} />
316
+ </div>
317
+ <div>
318
+ <p className="text-sm font-medium text-foreground/70">
319
+ {t.inbox.emptyTitle}
320
+ </p>
321
+ <p className="text-xs text-muted-foreground/50 mt-1.5 max-w-[280px] mx-auto leading-relaxed">
322
+ {t.inbox.emptyDesc}
323
+ </p>
324
+ </div>
325
+ <div className="flex items-center gap-1.5">
326
+ {['md', 'txt', 'pdf', 'csv', 'json'].map(e => {
327
+ const s = EXT_STYLES[e] ?? { bg: 'bg-muted/50', text: 'text-muted-foreground/50' };
328
+ return <span key={e} className={`text-2xs font-mono px-1.5 py-px rounded ${s.bg} ${s.text}`}>.{e}</span>;
329
+ })}
330
+ </div>
331
+ </div>
332
+ )}
333
+ </div>
334
+
335
+ {/* ─── File List ─── */}
336
+ {hasFiles && (
337
+ <div className="mb-8">
338
+ <div className="rounded-xl border border-border overflow-hidden divide-y divide-border/50">
339
+ {files.map((file, idx) => (
340
+ <InboxFileRow
341
+ key={file.path}
342
+ file={file}
343
+ index={idx}
344
+ animate={animateList}
345
+ onDelete={handleDeleteFile}
346
+ />
347
+ ))}
348
+ </div>
349
+ </div>
350
+ )}
351
+
352
+ {/* ─── History Section ─── */}
353
+ {visibleHistory.length > 0 && (
354
+ <div>
355
+ <div className="flex items-center gap-2 mb-3">
356
+ <History size={12} className="text-muted-foreground/40" />
357
+ <span className="text-2xs font-medium text-muted-foreground/50 uppercase tracking-wider">
358
+ {t.importHistory.title}
359
+ </span>
360
+ {history.length > HISTORY_VISIBLE && (
361
+ <Link
362
+ href="/inbox/history"
363
+ className="ml-auto text-2xs text-muted-foreground/50 hover:text-[var(--amber)] transition-colors"
364
+ >
365
+ {t.inbox.viewAllHistory(history.length)}
366
+ </Link>
367
+ )}
368
+ </div>
369
+ <div className="space-y-2">
370
+ {visibleHistory.map((entry) => (
371
+ <HistoryRow key={entry.id} entry={entry} />
372
+ ))}
373
+ </div>
374
+ </div>
375
+ )}
376
+
377
+ {/* ─── Tip: when files exist but no history yet ─── */}
378
+ {hasFiles && visibleHistory.length === 0 && (
379
+ <div className="flex items-start gap-2.5 px-3.5 py-3 rounded-lg bg-[var(--amber-subtle)]/50 border border-[var(--amber)]/10 animate-[fadeSlideUp_0.22s_ease_both]">
380
+ <Sparkles size={14} className="text-[var(--amber)]/60 shrink-0 mt-0.5" />
381
+ <p className="text-xs text-foreground/60 leading-relaxed">
382
+ {t.inbox.viewOrganizeTip}
383
+ </p>
384
+ </div>
385
+ )}
386
+
387
+ {/* ─── Empty + No History — additional guidance ─── */}
388
+ {!hasFiles && visibleHistory.length === 0 && (
389
+ <div className="flex flex-col items-center gap-1.5 py-2 text-center">
390
+ <p className="text-2xs text-muted-foreground/30">
391
+ {t.inbox.viewEmptyGuide}
392
+ </p>
393
+ </div>
394
+ )}
395
+ </div>
396
+ </div>
397
+ </div>
398
+ );
399
+ }
400
+
401
+ /* ─── File Row ─── */
402
+
403
+ function InboxFileRow({ file, onDelete, index, animate }: { file: InboxFile; onDelete: (name: string) => void; index: number; animate: boolean }) {
404
+ const { t } = useLocale();
405
+ const router = useRouter();
406
+ const ext = getFileExt(file.name);
407
+ const baseName = getFileBaseName(file.name);
408
+ const extStyle = EXT_STYLES[ext];
409
+ const age = formatRelativeTime(file.modifiedAt, t.home.relativeTime);
410
+ const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null);
411
+ const sizeLabel = formatSize(file.size);
412
+
413
+ const FileIcon = ext === 'csv' ? Table
414
+ : ext === 'json' ? FileCode
415
+ : FileText;
416
+ const iconColor = ext === 'csv' ? 'text-emerald-500/70'
417
+ : ext === 'json' ? 'text-violet-500/70'
418
+ : ext === 'pdf' ? 'text-red-500/60'
419
+ : 'text-muted-foreground/60';
420
+
421
+ return (
422
+ <>
423
+ <div
424
+ role="button"
425
+ tabIndex={0}
426
+ onClick={() => router.push(`/view/${encodePath(file.path)}`)}
427
+ onKeyDown={(e) => { if (e.key === 'Enter') router.push(`/view/${encodePath(file.path)}`); }}
428
+ onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); setCtxMenu({ x: e.clientX, y: e.clientY }); }}
429
+ className={`flex items-center gap-3 px-4 py-3 bg-card hover:bg-accent transition-colors duration-100 cursor-pointer group${animate ? ' animate-[fadeSlideUp_0.22s_ease_both]' : ''}`}
430
+ style={animate ? { animationDelay: `${index * 30}ms` } : undefined}
431
+ >
432
+ {/* File icon */}
433
+ <FileIcon size={15} className={`shrink-0 ${iconColor}`} />
434
+
435
+ {/* Name + meta */}
436
+ <div className="flex-1 min-w-0">
437
+ <div className="flex items-center gap-1.5">
438
+ <span className="text-sm text-foreground truncate" title={file.name}>
439
+ {baseName}
440
+ </span>
441
+ {extStyle && (
442
+ <span className={`text-2xs font-mono px-1.5 py-px rounded shrink-0 ${extStyle.bg} ${extStyle.text}`}>
443
+ .{ext}
444
+ </span>
445
+ )}
446
+ {file.isAging && (
447
+ <span className="text-2xs px-1.5 py-px rounded shrink-0 bg-[var(--amber)]/10 text-[var(--amber)]/70" title={t.inbox.agingHint}>
448
+ {t.inbox.agingHint}
449
+ </span>
450
+ )}
451
+ </div>
452
+ <div className="flex items-center gap-2 mt-0.5">
453
+ <span className="text-2xs text-muted-foreground/40 tabular-nums">{sizeLabel}</span>
454
+ <span className="text-2xs text-muted-foreground/30">·</span>
455
+ <span className="text-2xs text-muted-foreground/40 tabular-nums">{age}</span>
456
+ </div>
457
+ </div>
458
+
459
+ {/* Hover: delete */}
460
+ <button
461
+ type="button"
462
+ onClick={(e) => { e.stopPropagation(); e.preventDefault(); onDelete(file.name); }}
463
+ className="hidden group-hover:flex items-center justify-center w-7 h-7 rounded-md shrink-0 text-muted-foreground/40 hover:text-destructive hover:bg-destructive/10 transition-colors"
464
+ title={t.inbox.removeFile}
465
+ >
466
+ <X size={14} />
467
+ </button>
468
+ </div>
469
+
470
+ {ctxMenu && (
471
+ <FileContextMenu
472
+ x={ctxMenu.x}
473
+ y={ctxMenu.y}
474
+ file={file}
475
+ onDelete={() => { setCtxMenu(null); onDelete(file.name); }}
476
+ onClose={() => setCtxMenu(null)}
477
+ />
478
+ )}
479
+ </>
480
+ );
481
+ }
482
+
483
+ /* ─── Context Menu ─── */
484
+
485
+ function FileContextMenu({ x, y, file, onDelete, onClose }: {
486
+ x: number; y: number; file: InboxFile; onDelete: () => void; onClose: () => void;
487
+ }) {
488
+ const menuRef = useRef<HTMLDivElement>(null);
489
+ const router = useRouter();
490
+ const { t } = useLocale();
491
+
492
+ useEffect(() => {
493
+ const handleClick = (e: MouseEvent) => {
494
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) onClose();
495
+ };
496
+ const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
497
+ document.addEventListener('mousedown', handleClick);
498
+ document.addEventListener('keydown', handleKey);
499
+ return () => { document.removeEventListener('mousedown', handleClick); document.removeEventListener('keydown', handleKey); };
500
+ }, [onClose]);
501
+
502
+ const adjX = typeof window !== 'undefined' ? Math.min(x, window.innerWidth - 200) : x;
503
+ const adjY = typeof window !== 'undefined' ? Math.min(y, window.innerHeight - 120) : y;
504
+ const itemCls = 'w-full flex items-center gap-2 px-3 py-1.5 text-sm text-foreground hover:bg-muted transition-colors text-left';
505
+
506
+ return (
507
+ <div
508
+ ref={menuRef}
509
+ className="fixed z-50 min-w-[160px] bg-card border border-border rounded-lg shadow-lg py-1"
510
+ style={{ top: adjY, left: adjX }}
511
+ >
512
+ <button className={itemCls} onClick={() => { router.push(`/view/${encodePath(file.path)}`); onClose(); }}>
513
+ <ExternalLink size={14} className="shrink-0" /> {t.inbox.openFile}
514
+ </button>
515
+ <button className={itemCls} onClick={() => { navigator.clipboard.writeText(file.name); toast.copy(); onClose(); }}>
516
+ <Copy size={14} className="shrink-0" /> {t.inbox.copyName}
517
+ </button>
518
+ <div className="border-t border-border my-1" />
519
+ <button className={`${itemCls} text-destructive hover:text-destructive`} onClick={onDelete}>
520
+ <Trash2 size={14} className="shrink-0" /> {t.inbox.removeFile}
521
+ </button>
522
+ </div>
523
+ );
524
+ }
525
+
526
+ /* ─── History Row ─── */
527
+
528
+ function HistoryRow({ entry }: { entry: OrganizeHistoryEntry }) {
529
+ const { t } = useLocale();
530
+ const [expanded, setExpanded] = useState(false);
531
+ const isUndone = entry.status === 'undone';
532
+ const sourceBadge = getSourceBadge(entry.source);
533
+ const duration = entry.durationMs ? formatDuration(entry.durationMs) : null;
534
+ const age = formatRelativeTime(new Date(entry.timestamp).toISOString(), t.home.relativeTime);
535
+ const successCount = entry.files.filter(f => f.ok && !f.undone).length;
536
+
537
+ return (
538
+ <div className="rounded-lg border border-border/30 overflow-hidden">
539
+ <button
540
+ type="button"
541
+ onClick={() => setExpanded(v => !v)}
542
+ aria-expanded={expanded}
543
+ className="w-full flex items-center gap-2.5 px-3.5 py-2.5 text-left hover:bg-muted/20 transition-colors"
544
+ >
545
+ {isUndone ? (
546
+ <AlertCircle size={13} className="text-muted-foreground/40 shrink-0" />
547
+ ) : (
548
+ <Check size={13} className="text-success/70 shrink-0" />
549
+ )}
550
+ <div className="flex-1 min-w-0 flex items-center gap-1.5">
551
+ <span className={`text-xs truncate ${isUndone ? 'text-muted-foreground/50 line-through' : 'text-foreground/80'}`}>
552
+ {entry.sourceFiles.length === 1 ? entry.sourceFiles[0] : t.importHistory.nFiles(entry.sourceFiles.length)}
553
+ </span>
554
+ {sourceBadge && (
555
+ <span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${sourceBadge.className}`}>
556
+ {sourceBadge.label}
557
+ </span>
558
+ )}
559
+ {successCount > 0 && (
560
+ <span className="text-2xs text-muted-foreground/40 shrink-0">
561
+ {t.importHistory.changesSummary(successCount)}
562
+ </span>
563
+ )}
564
+ </div>
565
+ <span className="text-2xs text-muted-foreground/40 tabular-nums shrink-0">
566
+ {duration && `${duration} · `}{age}
567
+ </span>
568
+ {entry.files.length > 0 && (
569
+ <ChevronDown
570
+ size={10}
571
+ className={`text-muted-foreground/30 shrink-0 transition-transform duration-150 ${expanded ? 'rotate-180' : ''}`}
572
+ />
573
+ )}
574
+ </button>
575
+
576
+ {expanded && entry.files.length > 0 && (
577
+ <div className="border-t border-border/20 px-3.5 py-2 space-y-0.5">
578
+ {entry.files.map((f, idx) => {
579
+ const parts = f.path.split('/');
580
+ const fileName = parts.pop() ?? f.path;
581
+ const dirPath = parts.length > 0 ? parts.join('/') : null;
582
+ const isClickable = !f.undone && f.ok;
583
+ const rowClass = `flex items-center gap-2 py-1 text-2xs${f.undone ? ' opacity-40' : ''}${isClickable ? ' rounded -mx-1 px-1 hover:bg-muted/20 transition-colors' : ''}`;
584
+ const rowContent = (
585
+ <>
586
+ <span className={`w-1 h-1 rounded-full shrink-0 ${f.ok && !f.undone ? 'bg-success/60' : 'bg-muted-foreground/30'}`} />
587
+ <span className={`truncate flex-1 min-w-0 ${f.undone ? 'line-through text-muted-foreground' : ''}`}>
588
+ {dirPath && <span className="text-muted-foreground/30">{dirPath}/</span>}
589
+ <span className={f.undone ? '' : 'text-foreground/70'}>{fileName}</span>
590
+ </span>
591
+ <span className="text-muted-foreground/40 shrink-0">
592
+ {f.undone ? t.importHistory.statusUndone : f.action === 'create' ? t.importHistory.statusCreated : t.importHistory.statusUpdated}
593
+ </span>
594
+ </>
595
+ );
596
+ return isClickable ? (
597
+ <Link key={`${f.path}-${idx}`} href={`/view/${encodePath(f.path)}`} className={rowClass}>
598
+ {rowContent}
599
+ </Link>
600
+ ) : (
601
+ <div key={`${f.path}-${idx}`} className={rowClass}>
602
+ {rowContent}
603
+ </div>
604
+ );
605
+ })}
606
+ </div>
607
+ )}
608
+ </div>
609
+ );
610
+ }
611
+
612
+ /* ─── Helpers ─── */
613
+
614
+ function getSourceBadge(source?: OrganizeSource): { label: string; className: string } | null {
615
+ switch (source) {
616
+ case 'drag-drop': return { label: 'drop', className: 'bg-muted/50 text-muted-foreground/50' };
617
+ case 'inbox-organize': return { label: 'inbox', className: 'bg-[var(--amber)]/10 text-[var(--amber)]/70' };
618
+ case 'import-modal': return { label: 'import', className: 'bg-blue-500/10 text-blue-500/70' };
619
+ case 'plugin': return { label: 'plugin', className: 'bg-violet-500/10 text-violet-500/70' };
620
+ case 'upload': return { label: 'upload', className: 'bg-teal-500/10 text-teal-500/70' };
621
+ case 'web-clipper': return { label: 'clip', className: 'bg-emerald-500/10 text-emerald-500/70' };
622
+ default: return null;
623
+ }
624
+ }
625
+
626
+ function formatDuration(ms: number): string {
627
+ const s = Math.round(ms / 1000);
628
+ if (s < 60) return `${s}s`;
629
+ const m = Math.floor(s / 60);
630
+ const rem = s % 60;
631
+ return `${m}m${rem > 0 ? `${rem}s` : ''}`;
632
+ }
633
+
634
+ function formatSize(bytes: number): string {
635
+ if (bytes < 1024) return `${bytes} B`;
636
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
637
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
638
+ }
639
+
640
+ interface RelativeTimeStrings {
641
+ justNow: string;
642
+ minutesAgo: (n: number) => string;
643
+ hoursAgo: (n: number) => string;
644
+ daysAgo: (n: number) => string;
645
+ }
646
+
647
+ function formatRelativeTime(isoString: string, rt: RelativeTimeStrings): string {
648
+ const diff = Date.now() - new Date(isoString).getTime();
649
+ const minutes = Math.floor(diff / 60000);
650
+ if (minutes < 1) return rt.justNow;
651
+ if (minutes < 60) return rt.minutesAgo(minutes);
652
+ const hours = Math.floor(minutes / 60);
653
+ if (hours < 24) return rt.hoursAgo(hours);
654
+ const days = Math.floor(hours / 24);
655
+ return rt.daysAgo(days);
656
+ }