@geminilight/mindos 0.6.63 → 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 (268) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +21 -21
  4. package/_standalone/.next/build-manifest.json +2 -2
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  10. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error.html +2 -2
  12. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  25. package/_standalone/.next/server/app/agents/page.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  27. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  28. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
  39. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask/route.js +48 -42
  43. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  44. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  48. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  50. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  52. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  54. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/file/import/route.js +1 -1
  57. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  58. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
  60. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  62. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  64. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  66. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  68. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  71. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  73. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
  75. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  76. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  83. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  85. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
  90. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/settings/route.js +1 -1
  93. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  94. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
  96. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  99. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/setup/route.js +1 -1
  102. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/api/skills/route.js +1 -1
  104. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  105. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  107. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  108. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  111. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  113. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  114. package/_standalone/.next/server/app/changes/page.js +1 -1
  115. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  116. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  117. package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
  118. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  119. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  120. package/_standalone/.next/server/app/echo/page.js +1 -1
  121. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  122. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  123. package/_standalone/.next/server/app/explore/page.js +1 -1
  124. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  125. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  126. package/_standalone/.next/server/app/help/page.js +1 -1
  127. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  128. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  129. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  130. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  131. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  132. package/_standalone/.next/server/app/login/page.js +1 -1
  133. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  134. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  135. package/_standalone/.next/server/app/page.js +1 -1
  136. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  137. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  138. package/_standalone/.next/server/app/setup/page.js +2 -2
  139. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  140. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  141. package/_standalone/.next/server/app/trash/page.js +3 -3
  142. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  143. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  144. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  145. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  146. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  147. package/_standalone/.next/server/app/wiki/page.js +1 -1
  148. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  149. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  150. package/_standalone/.next/server/app-paths-manifest.json +21 -21
  151. package/_standalone/.next/server/chunks/122.js +222 -0
  152. package/_standalone/.next/server/chunks/3113.js +52 -0
  153. package/_standalone/.next/server/chunks/6539.js +1 -1
  154. package/_standalone/.next/server/chunks/8388.js +2 -2
  155. package/_standalone/.next/server/chunks/953.js +3 -3
  156. package/_standalone/.next/server/chunks/9787.js +2 -0
  157. package/_standalone/.next/server/pages/500.html +2 -2
  158. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  159. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
  161. package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
  162. package/_standalone/.next/static/chunks/{5581-82e5db227f8e9393.js → 5581-c671163a2fe1b312.js} +2 -2
  163. package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
  164. package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
  165. package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
  166. package/_standalone/.next/static/chunks/{3674-be69a8b858ceacdd.js → 7294-cac25d97869afadc.js} +1 -1
  167. package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
  168. package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
  169. package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
  170. package/_standalone/.next/static/chunks/app/agents/[agentKey]/{page-b0dabe793500383d.js → page-2f5cf97e03dc1cc9.js} +1 -1
  171. package/_standalone/.next/static/chunks/app/agents/{page-1f1ac330c8177cf6.js → page-50eac58d511dcc6e.js} +1 -1
  172. package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
  173. package/_standalone/.next/static/chunks/app/{layout-50a6b1164ee98ab9.js → layout-2cb7a6602d2e5d5f.js} +62 -58
  174. package/_standalone/.next/static/chunks/app/{page-73802bd31d7f6c9f.js → page-5ab911b2226f6ff7.js} +1 -1
  175. package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
  176. package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
  177. package/_standalone/.next/static/chunks/app/view/[...path]/{page-808f39963bf04715.js → page-26e47dd4c533a58c.js} +2 -2
  178. package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
  179. package/_standalone/.next/trace +65 -65
  180. package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
  181. package/_standalone/__tests__/api/settings.test.ts +16 -12
  182. package/_standalone/__tests__/api/setup.test.ts +11 -9
  183. package/_standalone/__tests__/api/test-key.test.ts +0 -10
  184. package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
  185. package/_standalone/__tests__/core/context.test.ts +48 -426
  186. package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
  187. package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
  188. package/_standalone/__tests__/setup.ts +5 -5
  189. package/_standalone/components/ask/AskContent.tsx +70 -40
  190. package/_standalone/components/ask/AskHeader.tsx +8 -1
  191. package/_standalone/components/ask/MessageList.tsx +37 -3
  192. package/_standalone/components/ask/ProviderModelCapsule.tsx +51 -129
  193. package/_standalone/components/settings/AiTab.tsx +270 -347
  194. package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
  195. package/_standalone/components/settings/CustomProvidersCard.tsx +2 -2
  196. package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
  197. package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
  198. package/_standalone/components/settings/Primitives.tsx +48 -104
  199. package/_standalone/components/settings/ProviderModal.tsx +38 -221
  200. package/_standalone/components/settings/SettingsContent.tsx +5 -12
  201. package/_standalone/components/settings/TestButton.tsx +64 -0
  202. package/_standalone/components/settings/types.ts +3 -12
  203. package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
  204. package/_standalone/components/setup/StepAI.tsx +3 -3
  205. package/_standalone/components/shared/ModelInput.tsx +18 -4
  206. package/_standalone/components/shared/ProviderSelect.tsx +126 -134
  207. package/_standalone/hooks/useAskChat.ts +97 -13
  208. package/_standalone/hooks/useAskPanel.ts +17 -1
  209. package/_standalone/lib/settings-ai-client.ts +17 -8
  210. package/_standalone/tsconfig.tsbuildinfo +1 -1
  211. package/app/app/api/ask/route.ts +124 -44
  212. package/app/app/api/mcp/agents/route.ts +3 -3
  213. package/app/app/api/settings/list-models/route.ts +15 -26
  214. package/app/app/api/settings/route.ts +14 -59
  215. package/app/app/api/settings/test-key/route.ts +47 -12
  216. package/app/app/api/setup/route.ts +36 -18
  217. package/app/app/api/skills/route.ts +1 -1
  218. package/app/app/layout.tsx +5 -3
  219. package/app/components/HomeContent.tsx +11 -0
  220. package/app/components/UpdateToast.tsx +255 -0
  221. package/app/components/ask/AskContent.tsx +70 -40
  222. package/app/components/ask/AskHeader.tsx +8 -1
  223. package/app/components/ask/MessageList.tsx +37 -3
  224. package/app/components/ask/ProviderModelCapsule.tsx +51 -129
  225. package/app/components/settings/AiTab.tsx +270 -347
  226. package/app/components/settings/CustomProviderFields.tsx +121 -0
  227. package/app/components/settings/CustomProvidersCard.tsx +2 -2
  228. package/app/components/settings/KnowledgeTab.tsx +6 -20
  229. package/app/components/settings/McpAgentInstall.tsx +7 -2
  230. package/app/components/settings/Primitives.tsx +48 -104
  231. package/app/components/settings/ProviderModal.tsx +38 -221
  232. package/app/components/settings/SettingsContent.tsx +5 -12
  233. package/app/components/settings/TestButton.tsx +64 -0
  234. package/app/components/settings/types.ts +3 -12
  235. package/app/components/settings/useCustomProviderForm.ts +132 -0
  236. package/app/components/setup/StepAI.tsx +3 -3
  237. package/app/components/shared/ModelInput.tsx +18 -4
  238. package/app/components/shared/ProviderSelect.tsx +126 -134
  239. package/app/hooks/useAskChat.ts +97 -13
  240. package/app/hooks/useAskPanel.ts +17 -1
  241. package/app/lib/agent/context.ts +65 -0
  242. package/app/lib/agent/providers.ts +25 -0
  243. package/app/lib/agent/tools.ts +1 -1
  244. package/app/lib/custom-endpoints.ts +129 -29
  245. package/app/lib/i18n/modules/settings.ts +20 -0
  246. package/app/lib/pi-integration/skills.ts +16 -4
  247. package/app/lib/settings-ai-client.ts +17 -8
  248. package/app/lib/settings.ts +64 -90
  249. package/app/lib/types.ts +4 -0
  250. package/package.json +1 -1
  251. package/_standalone/.next/server/chunks/530.js +0 -218
  252. package/_standalone/.next/server/chunks/9007.js +0 -2
  253. package/_standalone/.next/server/chunks/9137.js +0 -52
  254. package/_standalone/.next/static/chunks/1309-373ade1b40aea186.js +0 -1
  255. package/_standalone/.next/static/chunks/3165-9189a38fd9ebf6f2.js +0 -1
  256. package/_standalone/.next/static/chunks/4587-5d06728133fff222.js +0 -1
  257. package/_standalone/.next/static/chunks/6261-5ce86db54b19ae46.js +0 -1
  258. package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
  259. package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
  260. package/_standalone/.next/static/chunks/8520-84e607f33c409f91.js +0 -22
  261. package/_standalone/.next/static/chunks/9207-9a4a1a1ede4f8e6e.js +0 -1
  262. package/_standalone/.next/static/chunks/app/echo/[segment]/page-bc5e104eb7ae6327.js +0 -11
  263. package/_standalone/.next/static/chunks/app/setup/page-79acb0baf38184c6.js +0 -1
  264. package/_standalone/.next/static/chunks/app/trash/page-d040db56863da504.js +0 -1
  265. package/_standalone/.next/static/css/1287672978833d07.css +0 -1
  266. package/_standalone/lib/agent/context.ts +0 -403
  267. /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
  268. /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
@@ -17,7 +17,7 @@ export async function GET() {
17
17
  try {
18
18
  const settings = readSettings();
19
19
  const disabledSkills = settings.disabledSkills ?? [];
20
- const skills = scanSkillDirs({
20
+ const skills = await scanSkillDirs({
21
21
  projectRoot: PROJECT_ROOT,
22
22
  mindRoot: getMindRoot(),
23
23
  disabledSkills,
@@ -9,6 +9,7 @@ import ErrorBoundary from '@/components/ErrorBoundary';
9
9
  import Toaster from '@/components/ui/Toaster';
10
10
  import RegisterSW from './register-sw';
11
11
  import UpdateOverlay from '@/components/UpdateOverlay';
12
+ import UpdateToast from '@/components/UpdateToast';
12
13
  import { cookies, headers } from 'next/headers';
13
14
  import type { Locale } from '@/lib/i18n';
14
15
  import '@/lib/renderers/index'; // globally register built-in renderers once
@@ -120,9 +121,10 @@ export default async function RootLayout({
120
121
  </ShellLayout>
121
122
  </ErrorBoundary>
122
123
  </TooltipProvider>
123
- <Toaster />
124
- <RegisterSW />
125
- <UpdateOverlay />
124
+ <Toaster />
125
+ <RegisterSW />
126
+ <UpdateOverlay />
127
+ <UpdateToast />
126
128
  </body>
127
129
  </html>
128
130
  );
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useCallback } from 'react';
4
+ import { useRouter } from 'next/navigation';
4
5
  import { useLocale } from '@/lib/stores/locale-store';
5
6
  import { FolderSync, PenLine, BarChart3, Sparkles, ArrowUpRight } from 'lucide-react';
6
7
  import OnboardingView from './OnboardingView';
@@ -22,6 +23,7 @@ const TAB_ICONS = [FolderSync, PenLine, BarChart3, Sparkles];
22
23
 
23
24
  export default function HomeContent({ recent, existingFiles, spaces }: { recent: RecentFile[]; existingFiles?: string[]; spaces?: SpaceInfo[] }) {
24
25
  const { t } = useLocale();
26
+ const router = useRouter();
25
27
  const [activeTab, setActiveTab] = useState(0);
26
28
  const [maximized, setMaximized] = useState(false);
27
29
 
@@ -32,6 +34,14 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
32
34
  setMaximized(true);
33
35
  }, []);
34
36
 
37
+ // Navigate to editor with right-side Ask panel open
38
+ const handleDockToPanel = useCallback(() => {
39
+ const target = recent.length > 0 ? `/view/${recent[0].path}` : '/';
40
+ // Signal the already-mounted SidebarLayout to open the Ask panel
41
+ window.dispatchEvent(new CustomEvent('mindos:open-ask-panel'));
42
+ router.push(target);
43
+ }, [recent, router]);
44
+
35
45
  if (recent.length === 0) {
36
46
  return <OnboardingView />;
37
47
  }
@@ -98,6 +108,7 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
98
108
  maximized={maximized}
99
109
  onMaximize={toggleMaximize}
100
110
  onFirstMessage={handleFirstMessage}
111
+ onDockToPanel={handleDockToPanel}
101
112
  />
102
113
  </div>
103
114
  </div>
@@ -0,0 +1,255 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState, useCallback, useRef } from 'react';
4
+ import { X } from 'lucide-react';
5
+ import { useLocale } from '@/lib/stores/locale-store';
6
+
7
+ /* ── Bridge interface ──────────────────────────────────────────────── */
8
+
9
+ interface MindosDesktopBridge {
10
+ checkUpdate?: () => Promise<{ available: boolean; version?: string }>;
11
+ onUpdateAvailable?: (cb: (info: { version?: string }) => void) => () => void;
12
+ checkCoreUpdate?: () => Promise<{
13
+ available: boolean;
14
+ currentVersion: string;
15
+ latestVersion: string;
16
+ }>;
17
+ onCoreUpdateAvailable?: (
18
+ cb: (info: { current: string; latest: string; ready?: boolean }) => void,
19
+ ) => () => void;
20
+ }
21
+
22
+ function getDesktopBridge(): MindosDesktopBridge | null {
23
+ if (typeof window === 'undefined') return null;
24
+ const w = window as unknown as { mindos?: MindosDesktopBridge };
25
+ return w.mindos?.checkUpdate ? (w.mindos as MindosDesktopBridge) : null;
26
+ }
27
+
28
+ /* ── Types ─────────────────────────────────────────────────────────── */
29
+
30
+ interface PendingUpdate {
31
+ type: 'desktop' | 'core';
32
+ version: string;
33
+ }
34
+
35
+ type ToastVisibility = 'hidden' | 'visible' | 'dismissing';
36
+
37
+ /* ── Constants ─────────────────────────────────────────────────────── */
38
+
39
+ const SKIP_DESKTOP_KEY = 'mindos_update_skip_desktop';
40
+ const SKIP_CORE_KEY = 'mindos_update_skip_core';
41
+ const SHOW_DELAY_MS = 10_000; // Wait 10 s after startup before showing
42
+ const DISMISS_MS = 200; // Match the CSS transition duration
43
+
44
+ /* ── Helpers ───────────────────────────────────────────────────────── */
45
+
46
+ /** Proper semantic-version comparison: returns true when `a` is strictly newer than `b`. */
47
+ function isNewer(a: string, b: string): boolean {
48
+ const pa = a.split('.').map(Number);
49
+ const pb = b.split('.').map(Number);
50
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
51
+ const va = pa[i] ?? 0;
52
+ const vb = pb[i] ?? 0;
53
+ if (va > vb) return true;
54
+ if (va < vb) return false;
55
+ }
56
+ return false;
57
+ }
58
+
59
+ /* ── Component ─────────────────────────────────────────────────────── */
60
+
61
+ /**
62
+ * Desktop-only update notification toast.
63
+ *
64
+ * Appears in the bottom-right corner when the Electron bridge reports a new
65
+ * Desktop shell or MindOS Core update. Persists until the user clicks
66
+ * "View Details" (→ Settings > Update tab) or "Skip Version" (→ stored in
67
+ * localStorage so it won't re-appear for that version).
68
+ *
69
+ * Renders `null` in browser/CLI mode (no bridge).
70
+ */
71
+ export default function UpdateToast() {
72
+ const { t } = useLocale();
73
+ const ut = t.settings.update.updateToast;
74
+
75
+ const [visibility, setVisibility] = useState<ToastVisibility>('hidden');
76
+ const [updates, setUpdates] = useState<{
77
+ desktop?: PendingUpdate;
78
+ core?: PendingUpdate;
79
+ }>({});
80
+
81
+ // Stable ref for the bridge — avoids re-running the effect every render.
82
+ const bridgeRef = useRef<MindosDesktopBridge | null>(null);
83
+ const [isDesktop, setIsDesktop] = useState(false);
84
+
85
+ // Timeout bookkeeping — prevents stale setState after dismiss / unmount.
86
+ const timers = useRef(new Set<ReturnType<typeof setTimeout>>());
87
+ const queued = useRef<{ desktop?: string; core?: string }>({});
88
+
89
+ // ── Timeout helpers ─────────────────────────────────────────────────
90
+
91
+ const clearTimers = useCallback(() => {
92
+ timers.current.forEach(clearTimeout);
93
+ timers.current.clear();
94
+ }, []);
95
+
96
+ const schedule = useCallback((state: ToastVisibility, ms: number) => {
97
+ const id = setTimeout(() => {
98
+ timers.current.delete(id);
99
+ setVisibility(state);
100
+ }, ms);
101
+ timers.current.add(id);
102
+ }, []);
103
+
104
+ // ── Detect Desktop bridge once on mount ─────────────────────────────
105
+
106
+ useEffect(() => {
107
+ const b = getDesktopBridge();
108
+ bridgeRef.current = b;
109
+ setIsDesktop(!!b);
110
+ }, []);
111
+
112
+ // ── Subscribe to IPC update events ──────────────────────────────────
113
+
114
+ useEffect(() => {
115
+ const bridge = bridgeRef.current;
116
+ if (!bridge) return;
117
+
118
+ clearTimers();
119
+ const teardowns: Array<() => void> = [];
120
+
121
+ // Helper: queue an update unless already queued for this version.
122
+ const enqueue = (
123
+ key: 'desktop' | 'core',
124
+ version: string,
125
+ skipKey: string,
126
+ ) => {
127
+ const skipped = localStorage.getItem(skipKey);
128
+ if (skipped && !isNewer(version, skipped)) return;
129
+ if (queued.current[key] === version) return; // de-dup
130
+
131
+ queued.current[key] = version;
132
+ setUpdates(prev => ({ ...prev, [key]: { type: key, version } }));
133
+ schedule('visible', SHOW_DELAY_MS);
134
+ };
135
+
136
+ if (bridge.onUpdateAvailable) {
137
+ teardowns.push(
138
+ bridge.onUpdateAvailable(info => {
139
+ if (info?.version) enqueue('desktop', info.version, SKIP_DESKTOP_KEY);
140
+ }),
141
+ );
142
+ }
143
+
144
+ if (bridge.onCoreUpdateAvailable) {
145
+ teardowns.push(
146
+ bridge.onCoreUpdateAvailable(info => {
147
+ if (info?.latest && !info.ready) {
148
+ enqueue('core', info.latest, SKIP_CORE_KEY);
149
+ }
150
+ }),
151
+ );
152
+ }
153
+
154
+ return () => {
155
+ teardowns.forEach(fn => fn());
156
+ clearTimers();
157
+ };
158
+ }, [isDesktop, clearTimers, schedule]); // eslint-disable-line react-hooks/exhaustive-deps
159
+
160
+ // Clean up on unmount
161
+ useEffect(() => () => clearTimers(), [clearTimers]);
162
+
163
+ // ── Actions ─────────────────────────────────────────────────────────
164
+
165
+ const handleViewDetails = useCallback(() => {
166
+ clearTimers();
167
+ window.dispatchEvent(
168
+ new CustomEvent('mindos:open-settings', { detail: { tab: 'update' } }),
169
+ );
170
+ setVisibility('dismissing');
171
+ schedule('hidden', DISMISS_MS);
172
+ }, [clearTimers, schedule]);
173
+
174
+ const handleSkip = useCallback(() => {
175
+ clearTimers();
176
+ if (updates.desktop) localStorage.setItem(SKIP_DESKTOP_KEY, updates.desktop.version);
177
+ if (updates.core) localStorage.setItem(SKIP_CORE_KEY, updates.core.version);
178
+ setVisibility('dismissing');
179
+ schedule('hidden', DISMISS_MS);
180
+ }, [updates, clearTimers, schedule]);
181
+
182
+ // ── Render ──────────────────────────────────────────────────────────
183
+
184
+ if (!isDesktop || visibility === 'hidden') return null;
185
+
186
+ const hasBoth = !!(updates.desktop && updates.core);
187
+ const title = hasBoth
188
+ ? ut.titleMultiple
189
+ : updates.desktop
190
+ ? ut.titleSingle(ut.desktopLabel, updates.desktop.version)
191
+ : updates.core
192
+ ? ut.titleSingle(ut.coreLabel, updates.core.version)
193
+ : '';
194
+
195
+ const subtitle = hasBoth
196
+ ? `${ut.desktopLabel} v${updates.desktop!.version} \u00B7 ${ut.coreLabel} v${updates.core!.version}`
197
+ : '';
198
+
199
+ const show = visibility === 'visible';
200
+
201
+ return (
202
+ <div
203
+ role="status"
204
+ aria-live="polite"
205
+ className={`
206
+ fixed bottom-14 right-4 z-40 pointer-events-none
207
+ transition-all duration-200
208
+ ${show ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}
209
+ `}
210
+ >
211
+ <div className="pointer-events-auto flex flex-col gap-2.5 bg-card border border-border rounded-xl shadow-lg px-4 py-3 w-[290px]">
212
+ {/* ── Title row ── */}
213
+ <div className="flex items-start gap-2">
214
+ {/* Amber indicator dot */}
215
+ <span className="mt-[5px] w-2 h-2 rounded-full bg-[var(--amber)] shrink-0" />
216
+
217
+ <div className="flex-1 min-w-0">
218
+ <p className="text-sm font-medium text-foreground leading-snug">{title}</p>
219
+ {subtitle && (
220
+ <p className="text-xs text-muted-foreground mt-0.5 truncate">{subtitle}</p>
221
+ )}
222
+ </div>
223
+
224
+ {/* Close = same as skip */}
225
+ <button
226
+ type="button"
227
+ onClick={handleSkip}
228
+ className="shrink-0 p-0.5 rounded text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
229
+ aria-label="Dismiss"
230
+ >
231
+ <X size={13} />
232
+ </button>
233
+ </div>
234
+
235
+ {/* ── Actions ── */}
236
+ <div className="flex gap-2">
237
+ <button
238
+ type="button"
239
+ onClick={handleViewDetails}
240
+ className="flex-1 px-3 py-1.5 text-xs font-medium rounded-lg text-[var(--amber-foreground)] bg-[var(--amber)] hover:opacity-90 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
241
+ >
242
+ {ut.viewDetails}
243
+ </button>
244
+ <button
245
+ type="button"
246
+ onClick={handleSkip}
247
+ className="flex-1 px-3 py-1.5 text-xs rounded-lg text-muted-foreground border border-border hover:text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
248
+ >
249
+ {hasBoth ? ut.skipAll : ut.skipVersion}
250
+ </button>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ );
255
+ }
@@ -3,7 +3,7 @@
3
3
  import { useEffect, useLayoutEffect, useRef, useState, useCallback, useMemo } from 'react';
4
4
  import { Send, StopCircle, X, Plus, FileText, ImageIcon } from 'lucide-react';
5
5
  import { useLocale } from '@/lib/stores/locale-store';
6
- import type { AskMode } from '@/lib/types';
6
+ import type { AskMode, Message } from '@/lib/types';
7
7
  import ModeCapsule, { getPersistedMode } from '@/components/ask/ModeCapsule';
8
8
  import { useAskSession } from '@/hooks/useAskSession';
9
9
  import { useFileUpload } from '@/hooks/useFileUpload';
@@ -74,9 +74,11 @@ interface AskContentProps {
74
74
  askMode?: 'panel' | 'popup';
75
75
  /** Switch between panel ↔ popup */
76
76
  onModeSwitch?: () => void;
77
+ /** Navigate from fullscreen to right-side panel mode */
78
+ onDockToPanel?: () => void;
77
79
  }
78
80
 
79
- export default function AskContent({ visible, currentFile, initialMessage, initialAcpAgent, onFirstMessage, variant, onClose, maximized, onMaximize, askMode, onModeSwitch }: AskContentProps) {
81
+ export default function AskContent({ visible, currentFile, initialMessage, initialAcpAgent, onFirstMessage, variant, onClose, maximized, onMaximize, askMode, onModeSwitch, onDockToPanel }: AskContentProps) {
80
82
  const isPanel = variant === 'panel';
81
83
  const isHome = variant === 'home';
82
84
 
@@ -103,7 +105,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
103
105
  const selectedAcpAgentRef = useRef(selectedAcpAgent);
104
106
  selectedAcpAgentRef.current = selectedAcpAgent;
105
107
  const [chatMode, setChatMode] = useState<AskMode>('agent');
106
- const [providerOverride, setProviderOverride] = useState<ProviderId | `cp_${string}` | null>(null);
108
+ const [providerOverride, setProviderOverride] = useState<ProviderId | `p_${string}` | null>(null);
107
109
  const [modelOverride, setModelOverride] = useState<string | null>(null);
108
110
 
109
111
  useEffect(() => {
@@ -136,7 +138,30 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
136
138
  setSelectedSkill(null);
137
139
  setSelectedAcpAgent(null);
138
140
  setAttachedFiles(currentFile ? [currentFile] : []);
139
- }, [currentFile]);
141
+ upload.clearAttachments();
142
+ }, [currentFile, upload]);
143
+
144
+
145
+ const handleRestoreInput = useCallback((userMessage: Message) => {
146
+ setInput(userMessage.content);
147
+ // Restore images if they exist
148
+ if (userMessage.images && userMessage.images.length > 0) {
149
+ // Reconstruct the images state from the message images
150
+ imageUpload.clearImages();
151
+ // Note: we can't directly set images without going through the upload flow
152
+ // So we just clear them for now - in practice, images are usually small content
153
+ // and the user can re-add them if needed
154
+ }
155
+ if (userMessage.attachedFiles) setAttachedFiles(userMessage.attachedFiles);
156
+ // Restore skill selection if it was set
157
+ if (userMessage.skillName) {
158
+ // The skill is already in the slash command system, just mark as selected
159
+ // This will be handled through the UI state
160
+ slash.resetSlash(); // Clear any active slash query
161
+ }
162
+ // Focus back to input
163
+ setTimeout(() => inputRef.current?.focus(), 50);
164
+ }, [imageUpload, slash]);
140
165
 
141
166
  const chatRefs = useRef({ inputValueRef, mentionRef, slashRef, imageUploadRef, sessionRef, uploadRef, selectedSkillRef, selectedAcpAgentRef, attachedFilesRef });
142
167
  const chat = useAskChat({
@@ -148,6 +173,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
148
173
  refs: chatRefs.current,
149
174
  errorLabels: { noResponse: t.ask.errorNoResponse, stopped: t.ask.stopped },
150
175
  resetInputState,
176
+ onRestoreInput: handleRestoreInput,
151
177
  });
152
178
  const { isLoading, loadingPhase, reconnectAttempt, reconnectMaxRef } = chat;
153
179
  const handleSubmit = chat.submit;
@@ -482,7 +508,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
482
508
  }), [t, reconnectAttempt]);
483
509
 
484
510
  return (
485
- <>
511
+ <div className="flex min-h-0 w-full flex-col h-full">
486
512
  {/* Header — home variant shows session switcher + new/history/fullscreen buttons */}
487
513
  <AskHeader
488
514
  isPanel={isPanel || isHome}
@@ -495,6 +521,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
495
521
  askMode={isHome ? undefined : askMode}
496
522
  onModeSwitch={isHome ? undefined : onModeSwitch}
497
523
  onClose={isHome ? undefined : onClose}
524
+ onDockToPanel={maximized ? onDockToPanel : undefined}
498
525
  sessions={session.sessions}
499
526
  activeSessionId={session.activeSessionId}
500
527
  onLoadSession={handleLoadSession}
@@ -522,32 +549,33 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
522
549
  />
523
550
  )}
524
551
 
525
- {/* Messages */}
526
552
  {/* Messages — home variant hides empty state (suggestions rendered externally) */}
527
- {!isHome && (
528
- <MessageList
529
- messages={session.messages}
530
- isLoading={isLoading}
531
- loadingPhase={loadingPhase}
532
- emptyPrompt={t.ask.emptyPrompt}
533
- emptyHint={t.ask.emptyHint}
534
- suggestions={t.ask.suggestions}
535
- onSuggestionClick={setInput}
536
- labels={messageLabels}
537
- />
538
- )}
539
- {isHome && session.messages.length > 0 && (
540
- <MessageList
541
- messages={session.messages}
542
- isLoading={isLoading}
543
- loadingPhase={loadingPhase}
544
- emptyPrompt={t.ask.emptyPrompt}
545
- emptyHint={t.ask.emptyHint}
546
- suggestions={[]}
547
- onSuggestionClick={setInput}
548
- labels={messageLabels}
549
- />
550
- )}
553
+ <div className="flex-1 min-h-0 flex flex-col">
554
+ {!isHome && (
555
+ <MessageList
556
+ messages={session.messages}
557
+ isLoading={isLoading}
558
+ loadingPhase={loadingPhase}
559
+ emptyPrompt={t.ask.emptyPrompt}
560
+ emptyHint={t.ask.emptyHint}
561
+ suggestions={t.ask.suggestions}
562
+ onSuggestionClick={setInput}
563
+ labels={messageLabels}
564
+ />
565
+ )}
566
+ {isHome && session.messages.length > 0 && (
567
+ <MessageList
568
+ messages={session.messages}
569
+ isLoading={isLoading}
570
+ loadingPhase={loadingPhase}
571
+ emptyPrompt={t.ask.emptyPrompt}
572
+ emptyHint={t.ask.emptyHint}
573
+ suggestions={[]}
574
+ onSuggestionClick={setInput}
575
+ labels={messageLabels}
576
+ />
577
+ )}
578
+ </div>
551
579
 
552
580
  {/* Popovers — flex children so they stay within overflow boundary (absolute positioning would be clipped by RightAskPanel's overflow-hidden) */}
553
581
  {mention.mentionQuery !== null && mention.mentionResults.length > 0 && (
@@ -573,7 +601,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
573
601
  )}
574
602
 
575
603
  {/* Composer card — unified input area */}
576
- <div className="shrink-0 px-3 pb-2.5 pt-1">
604
+ <div className={cn('shrink-0', isHome ? 'px-2 pb-2 pt-0.5' : 'px-3 pb-2.5 pt-1')}>
577
605
  <div
578
606
  className={cn(
579
607
  'rounded-xl bg-muted/40 transition-all focus-within:bg-muted/60',
@@ -621,7 +649,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
621
649
  <form
622
650
  ref={formRef}
623
651
  onSubmit={handleSubmit}
624
- className="flex items-end gap-1.5 px-3 py-2"
652
+ className={cn('flex items-end gap-1.5', isHome ? 'px-2 py-1.5' : 'px-3 py-2')}
625
653
  >
626
654
  {/* + attach button with mini menu */}
627
655
  <div className="relative shrink-0">
@@ -690,7 +718,7 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
690
718
  onPaste={handlePaste}
691
719
  placeholder={t.ask.placeholder}
692
720
  rows={1}
693
- className="min-w-0 flex-1 resize-none overflow-y-hidden bg-transparent py-2 text-sm leading-relaxed text-foreground placeholder:text-muted-foreground/50 outline-none focus-visible:ring-0"
721
+ className={cn('min-w-0 flex-1 resize-none overflow-y-hidden bg-transparent py-2 leading-relaxed text-foreground placeholder:text-muted-foreground/50 outline-none focus-visible:ring-0', isHome ? 'text-xs' : 'text-sm')}
694
722
  />
695
723
 
696
724
  {isLoading ? (
@@ -705,8 +733,8 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
705
733
  </form>
706
734
 
707
735
  {/* Mode + Agent + Provider selector row + keyboard hint */}
708
- <div className="flex items-center justify-between px-3 pb-2 pt-1.5 border-t border-border/10">
709
- <div className="flex items-center gap-2">
736
+ <div className={cn('flex items-center justify-between border-t border-border/10', isPanel ? 'px-2 pb-1.5 pt-1 gap-1' : 'px-3 pb-2 pt-1.5')}>
737
+ <div className={cn('flex items-center flex-wrap', isPanel ? 'gap-1' : 'gap-2')}>
710
738
  <ModeCapsule mode={chatMode} onChange={setChatMode} disabled={isLoading} />
711
739
  {mounted && acpDetection.installedAgents.length > 0 && (
712
740
  <AgentSelectorCapsule
@@ -729,13 +757,15 @@ export default function AskContent({ visible, currentFile, initialMessage, initi
729
757
  />
730
758
  )}
731
759
  </div>
732
- {/* Keyboard hint */}
733
- <span className="hidden md:inline text-2xs text-muted-foreground/40 select-none">
734
- <kbd className="font-mono">Enter</kbd> {t.ask.send} · <kbd className="font-mono">Shift+Enter</kbd> {t.ask.newlineHint}
735
- </span>
760
+ {/* Keyboard hint — hidden in panel (too narrow) and home (compact) */}
761
+ {!isPanel && !isHome && (
762
+ <span className="hidden md:inline text-2xs text-muted-foreground/40 select-none shrink-0">
763
+ <kbd className="font-mono">Enter</kbd> {t.ask.send} · <kbd className="font-mono">Shift+Enter</kbd> {t.ask.newlineHint}
764
+ </span>
765
+ )}
736
766
  </div>
737
767
  </div>
738
768
  </div>
739
- </>
769
+ </div>
740
770
  );
741
771
  }
@@ -16,6 +16,8 @@ interface AskHeaderProps {
16
16
  askMode?: 'panel' | 'popup';
17
17
  onModeSwitch?: () => void;
18
18
  onClose?: () => void;
19
+ /** Navigate from fullscreen to right-side panel mode */
20
+ onDockToPanel?: () => void;
19
21
  hideTitle?: boolean;
20
22
  /** Session switching — inline in header when >=2 sessions */
21
23
  sessions?: ChatSession[];
@@ -28,7 +30,7 @@ interface AskHeaderProps {
28
30
 
29
31
  export default memo(function AskHeader({
30
32
  isPanel, showHistory, onToggleHistory, onReset, isLoading,
31
- maximized, onMaximize, askMode, onModeSwitch, onClose, hideTitle,
33
+ maximized, onMaximize, askMode, onModeSwitch, onClose, onDockToPanel, hideTitle,
32
34
  sessions, activeSessionId, onLoadSession, onDeleteSession, onRenameSession, onTogglePinSession,
33
35
  }: AskHeaderProps) {
34
36
  const { t } = useLocale();
@@ -244,6 +246,11 @@ export default memo(function AskHeader({
244
246
  {maximized ? <Minimize2 size={iconSize} /> : <Maximize2 size={iconSize} />}
245
247
  </button>
246
248
  )}
249
+ {onDockToPanel && (
250
+ <button type="button" onClick={(e) => { e.stopPropagation(); onDockToPanel(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={t.hints.dockToSide ?? 'Dock to side panel'}>
251
+ <PanelRight size={iconSize} />
252
+ </button>
253
+ )}
247
254
  {onModeSwitch && (
248
255
  <button type="button" onClick={(e) => { e.stopPropagation(); onModeSwitch(); }} className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? t.hints.dockToSide : t.hints.openAsPopup}>
249
256
  {askMode === 'popup' ? <PanelRight size={iconSize} /> : <AppWindow size={iconSize} />}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useRef, useEffect, memo, useState, useCallback } from 'react';
4
- import { Sparkles, Loader2, AlertCircle, Wrench, WifiOff, Zap, Copy, Check, ArrowDown, FolderInput, Search, PenLine, Lightbulb } from 'lucide-react';
4
+ import { Sparkles, Loader2, AlertCircle, Wrench, WifiOff, Zap, Copy, Check, ArrowDown, FolderInput, Search, PenLine, Lightbulb, FileText, Paperclip } from 'lucide-react';
5
5
  import ReactMarkdown from 'react-markdown';
6
6
  import remarkGfm from 'remark-gfm';
7
7
  import type { Message, ImagePart } from '@/lib/types';
@@ -35,10 +35,17 @@ function CopyMessageButton({ text, label }: { text: string; label?: string }) {
35
35
  );
36
36
  }
37
37
 
38
- function UserMessageContent({ content, skillName, images }: { content: string; skillName?: string; images?: ImagePart[] }) {
38
+ function UserMessageContent({ content, skillName, images, attachedFiles, uploadedFileNames }: { content: string; skillName?: string; images?: ImagePart[]; attachedFiles?: string[]; uploadedFileNames?: string[] }) {
39
39
  const resolved = skillName ?? content.match(SKILL_PREFIX_RE)?.[1];
40
40
  const prefixMatch = content.match(SKILL_PREFIX_RE);
41
41
  const rest = prefixMatch ? content.slice(prefixMatch[0].length) : content;
42
+
43
+ // Deduplicate: uploaded files already shown shouldn't repeat as attached
44
+ const uploadedSet = new Set(uploadedFileNames ?? []);
45
+ const dedupedAttached = attachedFiles?.filter(fp => !uploadedSet.has(fp.split('/').pop() ?? fp));
46
+ const hasContext = (dedupedAttached && dedupedAttached.length > 0)
47
+ || (uploadedFileNames && uploadedFileNames.length > 0);
48
+
42
49
  return (
43
50
  <>
44
51
  {/* Images */}
@@ -68,6 +75,33 @@ function UserMessageContent({ content, skillName, images }: { content: string; s
68
75
  </span>
69
76
  )}
70
77
  {resolved ? rest : content}
78
+ {/* File context chips */}
79
+ {hasContext && (
80
+ <div className="mt-2 pt-1.5 border-t border-white/15 flex flex-wrap gap-1 whitespace-normal" role="list" aria-label="Attached files">
81
+ {dedupedAttached?.map(fp => (
82
+ <span
83
+ key={fp}
84
+ role="listitem"
85
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] bg-white/10 text-white/80 min-w-0"
86
+ title={fp}
87
+ >
88
+ <FileText size={9} className="shrink-0 opacity-70" />
89
+ <span className="truncate max-w-[120px]">{fp.split('/').pop()}</span>
90
+ </span>
91
+ ))}
92
+ {uploadedFileNames?.map(name => (
93
+ <span
94
+ key={name}
95
+ role="listitem"
96
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] bg-white/10 text-white/80 min-w-0"
97
+ title={name}
98
+ >
99
+ <Paperclip size={9} className="shrink-0 opacity-70" />
100
+ <span className="truncate max-w-[120px]">{name}</span>
101
+ </span>
102
+ ))}
103
+ </div>
104
+ )}
71
105
  </>
72
106
  );
73
107
  }
@@ -253,7 +287,7 @@ export default memo(function MessageList({
253
287
  <div
254
288
  className="max-w-[85%] px-3.5 py-2.5 rounded-2xl rounded-br-lg text-sm leading-relaxed whitespace-pre-wrap bg-[var(--amber)] text-[var(--amber-foreground)] shadow-sm shadow-[var(--amber)]/10"
255
289
  >
256
- <UserMessageContent content={m.content} skillName={m.skillName} images={m.images} />
290
+ <UserMessageContent content={m.content} skillName={m.skillName} images={m.images} attachedFiles={m.attachedFiles} uploadedFileNames={m.uploadedFileNames} />
257
291
  </div>
258
292
  ) : m.content.startsWith('__error__') ? (
259
293
  <div className="max-w-[85%] px-3.5 py-3 rounded-2xl rounded-bl-md border border-error/30 bg-error/10 text-sm shadow-sm">