@pheem49/mint 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1162) hide show
  1. package/Cargo.lock +1012 -69
  2. package/Cargo.toml +5 -2
  3. package/LICENSE +21 -654
  4. package/README.md +92 -817
  5. package/Release_Note.md +71 -579
  6. package/apps/mint-companion/src-tauri/Cargo.toml +20 -0
  7. package/apps/mint-companion/src-tauri/build.rs +3 -0
  8. package/apps/mint-companion/src-tauri/src/compact.rs +69 -0
  9. package/apps/mint-companion/src-tauri/src/main.rs +675 -0
  10. package/apps/mint-companion/tsconfig.json +20 -0
  11. package/apps/mint-companion/vite.config.ts +4 -0
  12. package/components.json +19 -0
  13. package/crates/mint-cli/Cargo.toml +6 -0
  14. package/crates/mint-cli/src/agent/live_status.rs +230 -41
  15. package/crates/mint-cli/src/agent.rs +476 -108
  16. package/crates/mint-cli/src/commands/agent.rs +294 -86
  17. package/crates/mint-cli/src/commands/browser.rs +127 -0
  18. package/crates/mint-cli/src/commands/config.rs +14 -1
  19. package/crates/mint-cli/src/commands/integrations.rs +7 -0
  20. package/crates/mint-cli/src/commands/knowledge.rs +28 -34
  21. package/crates/mint-cli/src/commands/mod.rs +59 -1
  22. package/crates/mint-cli/src/commands/tailnet_web.rs +2 -7
  23. package/crates/mint-cli/src/companion_navigation.rs +268 -0
  24. package/crates/mint-cli/src/image.rs +31 -33
  25. package/crates/mint-cli/src/interactive/chat_tui.rs +4977 -864
  26. package/crates/mint-cli/src/interactive/command_ui.rs +39 -0
  27. package/crates/mint-cli/src/interactive/input_box.rs +56 -6
  28. package/crates/mint-cli/src/interactive/slash_commands.rs +321 -61
  29. package/crates/mint-cli/src/interactive/tool_activity.rs +667 -0
  30. package/crates/mint-cli/src/interactive.rs +93 -37
  31. package/crates/mint-cli/src/main.rs +36 -9
  32. package/crates/mint-cli/src/mcp.rs +5 -0
  33. package/crates/mint-cli/src/mod_host.rs +6 -0
  34. package/crates/mint-cli/src/mods_cli.rs +62 -0
  35. package/crates/mint-cli/src/mods_ui.rs +187 -0
  36. package/crates/mint-cli/src/onboard.rs +3492 -1360
  37. package/crates/mint-cli/src/terminal_theme.rs +3 -1
  38. package/crates/mint-cli/src/updater.rs +56 -2
  39. package/crates/mint-cli/tests/library_intake.rs +77 -0
  40. package/crates/mint-cli/tests/mods_testing.rs +137 -0
  41. package/crates/mint-companion-protocol/Cargo.toml +10 -0
  42. package/crates/mint-companion-protocol/src/lib.rs +276 -0
  43. package/crates/mint-core/Cargo.toml +6 -3
  44. package/crates/mint-core/build.rs +26 -0
  45. package/crates/mint-core/src/agent/chat.rs +406 -39
  46. package/crates/mint-core/src/agent/code_tools.rs +15 -0
  47. package/crates/mint-core/src/agent/memory/sources.rs +240 -0
  48. package/crates/mint-core/src/agent/memory.rs +220 -19
  49. package/crates/mint-core/src/agent/safety.rs +56 -4
  50. package/crates/mint-core/src/agent/skill_inventory.rs +327 -0
  51. package/crates/mint-core/src/agent/skills.rs +438 -90
  52. package/crates/mint-core/src/agent.rs +1 -0
  53. package/crates/mint-core/src/api_server/routes/auth.rs +1 -4
  54. package/crates/mint-core/src/api_server/routes/background_jobs.rs +310 -0
  55. package/crates/mint-core/src/api_server/routes/chat.rs +245 -214
  56. package/crates/mint-core/src/api_server/routes/html_preview.rs +77 -0
  57. package/crates/mint-core/src/api_server/routes/library.rs +97 -0
  58. package/crates/mint-core/src/api_server/routes/media_gen.rs +10 -57
  59. package/crates/mint-core/src/api_server/routes/misc.rs +220 -107
  60. package/crates/mint-core/src/api_server/routes/mod.rs +6 -0
  61. package/crates/mint-core/src/api_server/routes/mods.rs +31 -0
  62. package/crates/mint-core/src/api_server/routes/profile_oauth.rs +43 -26
  63. package/crates/mint-core/src/api_server/routes/sessions.rs +127 -69
  64. package/crates/mint-core/src/api_server/routes/skills_subagents.rs +16 -42
  65. package/crates/mint-core/src/api_server/routes/slash.rs +50 -1
  66. package/crates/mint-core/src/api_server/routes/sources.rs +101 -0
  67. package/crates/mint-core/src/api_server/routes/status_health.rs +18 -8
  68. package/crates/mint-core/src/api_server.rs +327 -72
  69. package/crates/mint-core/src/avatar_bridge.rs +15 -3
  70. package/crates/mint-core/src/browser/cdp.rs +82 -26
  71. package/crates/mint-core/src/browser/lifecycle.rs +143 -43
  72. package/crates/mint-core/src/browser/mod.rs +5 -4
  73. package/crates/mint-core/src/browser/navigate.rs +82 -9
  74. package/crates/mint-core/src/browser/runtime.rs +284 -0
  75. package/crates/mint-core/src/browser/session.rs +315 -7
  76. package/crates/mint-core/src/browser/workflow.rs +386 -0
  77. package/crates/mint-core/src/companion/navigation.rs +115 -0
  78. package/crates/mint-core/src/companion.rs +824 -0
  79. package/crates/mint-core/src/cron/scheduler.rs +34 -2
  80. package/crates/mint-core/src/integrations/channels.rs +87 -345
  81. package/crates/mint-core/src/integrations/mcp.rs +397 -50
  82. package/crates/mint-core/src/integrations/mcp_calls.rs +823 -0
  83. package/crates/mint-core/src/integrations/mcp_catalog.rs +267 -0
  84. package/crates/mint-core/src/integrations/mcp_device.rs +319 -0
  85. package/crates/mint-core/src/integrations/mcp_result.rs +401 -0
  86. package/crates/mint-core/src/integrations/webhooks.rs +539 -0
  87. package/crates/mint-core/src/integrations.rs +2 -0
  88. package/crates/mint-core/src/lib.rs +95 -32
  89. package/crates/mint-core/src/media/gemini_live.rs +134 -14
  90. package/crates/mint-core/src/media/image_models.rs +3 -27
  91. package/crates/mint-core/src/media/image_proxy.rs +563 -0
  92. package/crates/mint-core/src/media/image_workflow.rs +216 -0
  93. package/crates/mint-core/src/media/library/intake.rs +144 -0
  94. package/crates/mint-core/src/media/library/migration.rs +56 -0
  95. package/crates/mint-core/src/media/library/removal.rs +55 -0
  96. package/crates/mint-core/src/media/library/tests.rs +730 -0
  97. package/crates/mint-core/src/media/library/thumbnails.rs +72 -0
  98. package/crates/mint-core/src/media/library.rs +545 -0
  99. package/crates/mint-core/src/media/model_catalog.rs +375 -0
  100. package/crates/mint-core/src/media/pictures.rs +79 -222
  101. package/crates/mint-core/src/media/tool_images.rs +196 -0
  102. package/crates/mint-core/src/media/tts/playback.rs +609 -0
  103. package/crates/mint-core/src/media/tts.rs +1 -0
  104. package/crates/mint-core/src/media/video_models.rs +3 -22
  105. package/crates/mint-core/src/media.rs +5 -0
  106. package/crates/mint-core/src/mods/manager.rs +1124 -0
  107. package/crates/mint-core/src/mods/mod.rs +569 -0
  108. package/crates/mint-core/src/mods/operations.rs +204 -0
  109. package/crates/mint-core/src/mods/session.rs +146 -0
  110. package/crates/mint-core/src/mods/slash.rs +59 -0
  111. package/crates/mint-core/src/mods/telemetry.rs +306 -0
  112. package/crates/mint-core/src/orchestration/approvals.rs +344 -0
  113. package/crates/mint-core/src/orchestration/context_render.rs +553 -32
  114. package/crates/mint-core/src/orchestration/decision_parsing.rs +5 -23
  115. package/crates/mint-core/src/orchestration/diagnostics.rs +307 -0
  116. package/crates/mint-core/src/orchestration/image_regressions.rs +379 -0
  117. package/crates/mint-core/src/orchestration/loop_guard.rs +334 -0
  118. package/crates/mint-core/src/orchestration/mcp_review.rs +153 -0
  119. package/crates/mint-core/src/orchestration/memory_skill.rs +104 -32
  120. package/crates/mint-core/src/orchestration/mod.rs +3194 -755
  121. package/crates/mint-core/src/orchestration/mod_authoring_regressions.rs +209 -0
  122. package/crates/mint-core/src/orchestration/progress_clock.rs +80 -0
  123. package/crates/mint-core/src/orchestration/reliability.rs +541 -0
  124. package/crates/mint-core/src/orchestration/repeated_failures.rs +124 -0
  125. package/crates/mint-core/src/orchestration/review_regressions.rs +595 -0
  126. package/crates/mint-core/src/orchestration/run_control.rs +196 -0
  127. package/crates/mint-core/src/orchestration/tools/files.rs +191 -16
  128. package/crates/mint-core/src/orchestration/tools/media.rs +37 -41
  129. package/crates/mint-core/src/orchestration/tools/misc.rs +3 -2
  130. package/crates/mint-core/src/orchestration/tools/mod.rs +2 -0
  131. package/crates/mint-core/src/orchestration/tools/mods.rs +161 -0
  132. package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +321 -74
  133. package/crates/mint-core/src/orchestration/tools/project.rs +1 -1
  134. package/crates/mint-core/src/orchestration/tools/shell.rs +124 -7
  135. package/crates/mint-core/src/orchestration/tools/web.rs +5 -1
  136. package/crates/mint-core/src/orchestration/trajectory_schema.rs +353 -0
  137. package/crates/mint-core/src/orchestration/truncation.rs +211 -0
  138. package/crates/mint-core/src/orchestration/verification.rs +36 -6
  139. package/crates/mint-core/src/orchestration/workspace_helpers.rs +57 -23
  140. package/crates/mint-core/src/prompts/agent.rs +47 -41
  141. package/crates/mint-core/src/prompts/tool_catalog.rs +158 -38
  142. package/crates/mint-core/src/search/knowledge.rs +7 -1
  143. package/crates/mint-core/src/search/linked_folders/index.rs +177 -13
  144. package/crates/mint-core/src/search/linked_folders.rs +83 -2
  145. package/crates/mint-core/src/search/web_search.rs +44 -3
  146. package/crates/mint-core/src/slash/catalog.rs +1 -1
  147. package/crates/mint-core/src/slash/mod.rs +638 -12
  148. package/crates/mint-core/src/slash/mods.rs +155 -0
  149. package/crates/mint-core/src/swarm/batch.rs +643 -0
  150. package/crates/mint-core/src/swarm/dossier.rs +152 -0
  151. package/crates/mint-core/src/swarm/labels.rs +100 -0
  152. package/crates/mint-core/src/swarm/mod.rs +158 -0
  153. package/crates/mint-core/src/swarm/permissions.rs +167 -0
  154. package/crates/mint-core/src/system/auth.rs +156 -7
  155. package/crates/mint-core/src/system/bg_shell.rs +1197 -95
  156. package/crates/mint-core/src/system/config.rs +97 -13
  157. package/crates/mint-core/src/system/html_preview.rs +398 -0
  158. package/crates/mint-core/src/system/mint_web.rs +185 -0
  159. package/crates/mint-core/src/system/release_notes.rs +18 -0
  160. package/crates/mint-core/src/system/shell.rs +200 -16
  161. package/crates/mint-core/src/system/workspace.rs +276 -8
  162. package/crates/mint-core/src/system/workspace_history.rs +604 -0
  163. package/crates/mint-core/src/system.rs +4 -0
  164. package/crates/mint-core/tests/background_retention.rs +34 -0
  165. package/crates/mint-core/tests/background_shutdown.rs +24 -0
  166. package/crates/mint-core/tests/library_http.rs +209 -0
  167. package/crates/mint-core/tests/mcp_contract.rs +283 -0
  168. package/crates/mint-core/tests/mcp_stdio.rs +11 -10
  169. package/crates/mint-core/tests/media_model_catalog_http.rs +403 -0
  170. package/crates/mint-core/tests/memory_persistence.rs +27 -0
  171. package/crates/mint-core/tests/mods_http.rs +215 -0
  172. package/crates/mint-core/tests/terminal_shutdown.rs +109 -0
  173. package/crates/mint-mod-runtime/Cargo.toml +18 -0
  174. package/crates/mint-mod-runtime/src/lib.rs +507 -0
  175. package/crates/mint-mod-runtime/src/sdk.js +56 -0
  176. package/crates/mint-mod-runtime/src/test_sdk.js +32 -0
  177. package/crates/mint-mod-runtime/src/testing.rs +94 -0
  178. package/crates/mint-mod-runtime/tests/runtime.rs +156 -0
  179. package/docs/mods/AUTHORING.md +61 -0
  180. package/docs/mods/README.md +139 -0
  181. package/docs/mods/TESTING.md +56 -0
  182. package/docs/mods/mint-mods.d.ts +66 -0
  183. package/examples/device-mcp/README.md +22 -0
  184. package/examples/device-mcp/contract.md +49 -0
  185. package/examples/device-mcp/mock-device.mjs +66 -0
  186. package/examples/device-mcp/server.mjs +48 -0
  187. package/examples/device-mcp/server.test.mjs +78 -0
  188. package/examples/mods/task-checklist/README.md +1 -0
  189. package/examples/mods/task-checklist/main.ts +17 -0
  190. package/examples/mods/task-checklist/manifest.json +1 -0
  191. package/examples/mods/task-checklist/tests/checklist.test.ts +15 -0
  192. package/examples/mods/token-meter/README.md +1 -0
  193. package/examples/mods/token-meter/main.ts +8 -0
  194. package/examples/mods/token-meter/manifest.json +1 -0
  195. package/examples/mods/tool-guard/README.md +1 -0
  196. package/examples/mods/tool-guard/main.ts +9 -0
  197. package/examples/mods/tool-guard/manifest.json +1 -0
  198. package/out/web/assets/ArtifactPreviewPanel-DAT_Y7i0.css +1 -0
  199. package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +2 -0
  200. package/out/web/assets/CalculationCard-CRjP3E1j.js +1 -0
  201. package/out/web/assets/CliSessionsView-C3Br2v5Y.css +1 -0
  202. package/out/web/assets/CliSessionsView-MI8kXMAb.js +1 -0
  203. package/out/web/assets/CodeReviewPage-CUJceQme.css +1 -0
  204. package/out/web/assets/CodeReviewPage-VCUgPla_.js +2 -0
  205. package/out/web/assets/CommandPalette-BD8SlTE5.js +1 -0
  206. package/out/web/assets/ImageGenCard-BCXxkVJX.js +1 -0
  207. package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +1 -0
  208. package/out/web/assets/ImageStudioPanel-BP7N4rLr.js +1 -0
  209. package/out/web/assets/LibraryPage-DAxUTAg7.css +1 -0
  210. package/out/web/assets/LibraryPage-WlviRdR8.js +2 -0
  211. package/out/web/assets/LinkedFoldersView-CKRgroWK.js +1 -0
  212. package/out/web/assets/McpRegistryPicker-CoS83vtc.js +1 -0
  213. package/out/web/assets/McpServersView-D3wHnuHg.js +1 -0
  214. package/out/web/assets/MermaidCard-NqrHdyJW.js +2 -0
  215. package/out/web/assets/MintDashboard-B2DJsDDO.css +1 -0
  216. package/out/web/assets/MintDashboard-COw_xol7.js +63 -0
  217. package/out/web/assets/PluginsView-jBYmcKh4.js +1 -0
  218. package/out/web/assets/RewindModal-DempuDod.js +1 -0
  219. package/out/web/assets/ScheduledTasksView-5uw9-V9H.js +1 -0
  220. package/out/web/assets/SelectField-DXBQtrUK.js +1 -0
  221. package/out/web/assets/SelectField-lFEkkWCU.css +1 -0
  222. package/out/web/assets/SettingsWindow-2YP3yj7s.js +4 -0
  223. package/out/web/assets/SettingsWindow-Dz8LQVgD.css +1 -0
  224. package/out/web/assets/SkillsView-C-gp9J7m.js +2 -0
  225. package/out/web/assets/StockCard-BDMCe7mx.js +1 -0
  226. package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +38 -0
  227. package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +1 -0
  228. package/out/web/assets/UiFeatureCard-6BwxTOCe.js +1 -0
  229. package/out/web/assets/UiGridCard-DvZcLNxb.js +1 -0
  230. package/out/web/assets/UiMockupWidget-DD73QWg1.js +1 -0
  231. package/out/web/assets/VeoStudioPanel-DfsOJKv0.js +4 -0
  232. package/out/web/assets/WeatherCard-CPucIfq8.js +1 -0
  233. package/out/web/assets/WorkspacePanel-BfHKkulU.js +1 -0
  234. package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +2 -0
  235. package/out/web/assets/abnfDiagram-VCTEODGH-BOo3Ckng.js +1 -0
  236. package/out/web/assets/architecture-7GRP2DOG-ZOwheW3n.js +1 -0
  237. package/out/web/assets/architectureDiagram-5GKGNRK7-DC2FDzVd.js +36 -0
  238. package/out/web/assets/blockDiagram-I7D4REHJ-BrKFWdAH.js +129 -0
  239. package/out/web/assets/c4Diagram-7LVT6UL2-BHTHK1Yd.js +38 -0
  240. package/out/web/assets/chunk-2Q5K7J3B-D2JLPQzF.js +1 -0
  241. package/out/web/assets/chunk-4HAMMTFA-BYFkZwNj.js +62 -0
  242. package/out/web/assets/chunk-5VM5RSS4-BZ2oBPYQ.js +15 -0
  243. package/out/web/assets/chunk-75Z2AOVW-BSKfSKSA.js +2 -0
  244. package/out/web/assets/chunk-DU6HZSFF-vIhsiW6n.js +128 -0
  245. package/out/web/assets/chunk-F27PBJKO-BlYpW9xq.js +1 -0
  246. package/out/web/assets/chunk-FOHPRMQF-JXEeeJ9r.js +161 -0
  247. package/out/web/assets/chunk-GMAD6QVW-BIBz_Ug5.js +72 -0
  248. package/out/web/assets/chunk-GVQU2GXP-NPSHxeqC.js +1 -0
  249. package/out/web/assets/chunk-IMKFNOWR-YlJK8_Fp.js +231 -0
  250. package/out/web/assets/chunk-JWPE2WC7-BeEMUFDw.js +1 -0
  251. package/out/web/assets/chunk-L3NEJ4N5-DMYi4-af.js +1 -0
  252. package/out/web/assets/chunk-OSK3NFVY-Begd3-jA.js +10 -0
  253. package/out/web/assets/chunk-P2QGCYS3-D1I4xFHN.js +1 -0
  254. package/out/web/assets/chunk-POPQ4Y6H-CafZZzNE.js +1 -0
  255. package/out/web/assets/chunk-PWAF6VOD-Dwh6_Lnt.js +1 -0
  256. package/out/web/assets/chunk-SHT3W25Y-B0XrCSEb.js +168 -0
  257. package/out/web/assets/chunk-SVP7TREG-CxLsddxC.js +88 -0
  258. package/out/web/assets/chunk-TICWLB2K-y156QInb.js +206 -0
  259. package/out/web/assets/chunk-XXDRQBXY-B9YlhOkE.js +1 -0
  260. package/out/web/assets/chunk-Y2CYZVJY-BxCWjNVG.js +1 -0
  261. package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +1 -0
  262. package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +1 -0
  263. package/out/web/assets/config-CvIO5hkn.js +2 -0
  264. package/out/web/assets/config-DqXJXeZM.css +1 -0
  265. package/out/web/assets/core-Mi3s5D4_.js +1 -0
  266. package/out/web/assets/cose-bilkent-JH36ORCC-qlna31ty.js +1 -0
  267. package/out/web/assets/cynefin-OW5HDTMX-SMVvrLf-.js +1 -0
  268. package/out/web/assets/cynefinDiagram-5FMLGOSQ-rZznN24T.js +62 -0
  269. package/out/web/assets/cytoscape.esm-DWtAUG0r.js +321 -0
  270. package/out/web/assets/dagre-CnWJJM5f.js +1 -0
  271. package/out/web/assets/dagre-GXQ25YYZ-BOToydFu.js +4 -0
  272. package/out/web/assets/diagram-S7CK7UJ4-TMVaGQEJ.js +30 -0
  273. package/out/web/assets/diagram-UQ7AKVKN-C2JK-GP2.js +41 -0
  274. package/out/web/assets/diagram-VSXAHHWV-Cw8VSi2K.js +3 -0
  275. package/out/web/assets/diagram-VX7I27RA-DXp4fKk9.js +24 -0
  276. package/out/web/assets/diagram-Z3DM3KII-CzSicYiW.js +24 -0
  277. package/out/web/assets/dist-9ISJ7r-g.js +1 -0
  278. package/out/web/assets/dist-js-BV3mI7uw.js +1 -0
  279. package/out/web/assets/ebnfDiagram-PWID7BFC-Dqj-zqPu.js +1 -0
  280. package/out/web/assets/erDiagram-RLTQ6QDP-DZwFZAuD.js +99 -0
  281. package/out/web/assets/event-D7D-EBy9.js +1 -0
  282. package/out/web/assets/eventmodeling-NTZA5JFV-C0O-Lvgp.js +1 -0
  283. package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +1 -0
  284. package/out/web/assets/ganttDiagram-EL5Y4UJY-Da4ULXTl.js +292 -0
  285. package/out/web/assets/gitGraph-4MIJSDKK-B9wNFZIP.js +1 -0
  286. package/out/web/assets/gitGraphDiagram-WWUBYQGX-RjrhB0MP.js +106 -0
  287. package/out/web/assets/graphlib-Bh9gHply.js +1 -0
  288. package/out/web/assets/index-BmpulIkg.js +168 -0
  289. package/out/web/assets/index-CkvBwhX_.css +1 -0
  290. package/out/web/assets/info-A6RAGUB7-B3dfCpeY.js +1 -0
  291. package/out/web/assets/infoDiagram-27XIBGKW-QVWkLVx7.js +2 -0
  292. package/out/web/assets/ishikawaDiagram-5VMMS53U-CLFNIRI9.js +70 -0
  293. package/out/web/assets/journeyDiagram-3NMN7TZE-fgOQadYF.js +139 -0
  294. package/out/web/assets/jsx-runtime-DmUwWVTj.js +1 -0
  295. package/out/web/assets/kanban-definition-UXKFOSKX-G3Q_9g9O.js +89 -0
  296. package/out/web/assets/katex-MCdQaEAD.js +257 -0
  297. package/out/web/assets/mermaid-parser.core-v3Jm7nco.js +7 -0
  298. package/out/web/assets/mermaid.core-DTEjLUA1.js +44 -0
  299. package/out/web/assets/mindmap-definition-YA3MSWOX-C_D42_fF.js +96 -0
  300. package/out/web/assets/modelManager-BPLycQrN.js +2 -0
  301. package/out/web/assets/oauthManager-BUK7ic7q.js +1 -0
  302. package/out/web/assets/packet-AYTQ26CC-B-R-SHlF.js +1 -0
  303. package/out/web/assets/pdf-Bcahc2yC.js +56 -0
  304. package/out/web/assets/pdf.worker.min-Dkey6ZUl.mjs +27 -0
  305. package/out/web/assets/pegDiagram-XKGWAZYB-Da2q_giz.js +1 -0
  306. package/out/web/assets/pie-WAS4IAKB-BC8F0N7-.js +1 -0
  307. package/out/web/assets/pieDiagram-E7YTZNPT-Iy9REJNC.js +39 -0
  308. package/out/web/assets/quadrantDiagram-AXDQQJYC-ztsiYw7h.js +7 -0
  309. package/out/web/assets/radar-RG4KPBEZ-B_1P8Z_z.js +1 -0
  310. package/out/web/assets/railroad-74A4TZTK-DuTqczSo.js +1 -0
  311. package/out/web/assets/railroad-abnf-HS5TGJTU-Df29WLmp.js +1 -0
  312. package/out/web/assets/railroad-ebnf-LZEXJU2U-ePFMo-qp.js +1 -0
  313. package/out/web/assets/railroad-peg-WCYAUIDC-DYXLW4_E.js +1 -0
  314. package/out/web/assets/railroadDiagram-O6MQD6OU-LStnZf2S.js +1 -0
  315. package/out/web/assets/requirementDiagram-BXWQKSXE-DmzonMH-.js +84 -0
  316. package/out/web/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
  317. package/out/web/assets/rough.esm-CNnFptCR.js +1 -0
  318. package/out/web/assets/sankeyDiagram-P5KCCOFB-B8vjmUU1.js +40 -0
  319. package/out/web/assets/sequenceDiagram-WJ2MYXX4-CTnplOAX.js +162 -0
  320. package/out/web/assets/sizeCapture-INFHLROL-C8Q8DbV5.js +1 -0
  321. package/out/web/assets/src-CRDfRlHM.js +1 -0
  322. package/out/web/assets/stateDiagram-D77RDMKH-BFrIaLdI.js +1 -0
  323. package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +1 -0
  324. package/out/web/assets/studio-BBZQ_-QK.js +1 -0
  325. package/out/web/assets/swimlanes-42K2YHIH-BXGngUNU.js +1 -0
  326. package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +8 -0
  327. package/out/web/assets/timeline-definition-24CTP7MA-CegH7n43.js +120 -0
  328. package/out/web/assets/treeView-Q6P3EWNA-DTpJv2Tw.js +1 -0
  329. package/out/web/assets/treemap-WGGIJYW6-i8M9iJU6.js +1 -0
  330. package/out/web/assets/vennDiagram-4TSXK5OY-Dpzo1S8U.js +34 -0
  331. package/out/web/assets/wardley-WFR3VGLG-BrNsgtfw.js +1 -0
  332. package/out/web/assets/wardleyDiagram-VM6X3IG4-lQ92LFE1.js +78 -0
  333. package/out/web/assets/window-CkUOP7t0.js +1 -0
  334. package/out/web/assets/workspaces-SYPrmbgC.js +1 -0
  335. package/out/web/assets/xychartDiagram-S5SC5T6Z-BaOaEoBd.js +7 -0
  336. package/out/web/index-web.html +4 -3
  337. package/out/web/index.html +4 -3
  338. package/out/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  339. package/out/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  340. package/out/web/pdfjs/cmaps/78-H.bcmap +0 -0
  341. package/out/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  342. package/out/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  343. package/out/web/pdfjs/cmaps/78-V.bcmap +0 -0
  344. package/out/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  345. package/out/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  346. package/out/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  347. package/out/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  348. package/out/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  349. package/out/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  350. package/out/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  351. package/out/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  352. package/out/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  353. package/out/web/pdfjs/cmaps/Add-H.bcmap +0 -0
  354. package/out/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  355. package/out/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  356. package/out/web/pdfjs/cmaps/Add-V.bcmap +0 -0
  357. package/out/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  358. package/out/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  359. package/out/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  360. package/out/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  361. package/out/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  362. package/out/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  363. package/out/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  364. package/out/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  365. package/out/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  366. package/out/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  367. package/out/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  368. package/out/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  369. package/out/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  370. package/out/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  371. package/out/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  372. package/out/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  373. package/out/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  374. package/out/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  375. package/out/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  376. package/out/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  377. package/out/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  378. package/out/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  379. package/out/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  380. package/out/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  381. package/out/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  382. package/out/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  383. package/out/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  384. package/out/web/pdfjs/cmaps/B5-H.bcmap +0 -0
  385. package/out/web/pdfjs/cmaps/B5-V.bcmap +0 -0
  386. package/out/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  387. package/out/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  388. package/out/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  389. package/out/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  390. package/out/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  391. package/out/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  392. package/out/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  393. package/out/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  394. package/out/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  395. package/out/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  396. package/out/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  397. package/out/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  398. package/out/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  399. package/out/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  400. package/out/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
  401. package/out/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
  402. package/out/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
  403. package/out/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  404. package/out/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  405. package/out/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
  406. package/out/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  407. package/out/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  408. package/out/web/pdfjs/cmaps/GB-H.bcmap +4 -0
  409. package/out/web/pdfjs/cmaps/GB-V.bcmap +0 -0
  410. package/out/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  411. package/out/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  412. package/out/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  413. package/out/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  414. package/out/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  415. package/out/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  416. package/out/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  417. package/out/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  418. package/out/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
  419. package/out/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
  420. package/out/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  421. package/out/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  422. package/out/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  423. package/out/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  424. package/out/web/pdfjs/cmaps/H.bcmap +0 -0
  425. package/out/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  426. package/out/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  427. package/out/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  428. package/out/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  429. package/out/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  430. package/out/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  431. package/out/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  432. package/out/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  433. package/out/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  434. package/out/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  435. package/out/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  436. package/out/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  437. package/out/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
  438. package/out/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
  439. package/out/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  440. package/out/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  441. package/out/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
  442. package/out/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  443. package/out/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  444. package/out/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
  445. package/out/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  446. package/out/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  447. package/out/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  448. package/out/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  449. package/out/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  450. package/out/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  451. package/out/web/pdfjs/cmaps/Katakana.bcmap +0 -0
  452. package/out/web/pdfjs/cmaps/LICENSE +36 -0
  453. package/out/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
  454. package/out/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
  455. package/out/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  456. package/out/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  457. package/out/web/pdfjs/cmaps/Roman.bcmap +0 -0
  458. package/out/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  459. package/out/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  460. package/out/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  461. package/out/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  462. package/out/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  463. package/out/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  464. package/out/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  465. package/out/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  466. package/out/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  467. package/out/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  468. package/out/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  469. package/out/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  470. package/out/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  471. package/out/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  472. package/out/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  473. package/out/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  474. package/out/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  475. package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  476. package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  477. package/out/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  478. package/out/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  479. package/out/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  480. package/out/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  481. package/out/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  482. package/out/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  483. package/out/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  484. package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  485. package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  486. package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  487. package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  488. package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  489. package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  490. package/out/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  491. package/out/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  492. package/out/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  493. package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  494. package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  495. package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  496. package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  497. package/out/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  498. package/out/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  499. package/out/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  500. package/out/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  501. package/out/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  502. package/out/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  503. package/out/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  504. package/out/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  505. package/out/web/pdfjs/cmaps/V.bcmap +0 -0
  506. package/out/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  507. package/out/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  508. package/out/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  509. package/out/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  510. package/out/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  511. package/out/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  512. package/out/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  513. package/out/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  514. package/out/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  515. package/out/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  516. package/out/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  517. package/out/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  518. package/out/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
  519. package/out/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  520. package/out/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  521. package/out/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  522. package/out/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  523. package/out/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
  524. package/out/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
  525. package/out/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  526. package/out/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  527. package/out/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
  528. package/out/web/pdfjs/wasm/LICENSE_QCMS +21 -0
  529. package/out/web/pdfjs/wasm/jbig2.wasm +0 -0
  530. package/out/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
  531. package/out/web/pdfjs/wasm/openjpeg.wasm +0 -0
  532. package/out/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
  533. package/out/web/pdfjs/wasm/qcms_bg.wasm +0 -0
  534. package/out/web/pdfjs/wasm/quickjs-eval.js +16 -0
  535. package/out/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
  536. package/package.json +74 -23
  537. package/shared-modules/.github/workflows/ci.yml +120 -0
  538. package/shared-modules/.github/workflows/update.yaml +28 -0
  539. package/slash-commands.json +14 -3
  540. package/src/bin/auto-build.js +66 -0
  541. package/src/bin/desktop.js +113 -0
  542. package/src/bin/desktop.test.js +113 -0
  543. package/src/bin/index.js +21 -25
  544. package/src/bin/index.test.js +89 -0
  545. package/src/bin/measure-effects.mjs +53 -0
  546. package/src/bin/package.test.js +28 -0
  547. package/src/bin/pdfjs-assets.mjs +31 -0
  548. package/src/bin/release-notes.test.js +84 -0
  549. package/src/bin/styles.test.js +20 -0
  550. package/src/bin/web.js +5 -0
  551. package/src/bin/web.test.js +39 -0
  552. package/src/renderer/index.html +0 -2
  553. package/src/renderer/node_modules/.vite/vitest/CACHEDIR.TAG +3 -0
  554. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  555. package/src/renderer/shared/agentProgress.ts +42 -0
  556. package/src/renderer/shared/components/Settings/AgentsTab.tsx +76 -11
  557. package/src/renderer/shared/components/Settings/AudioTab.tsx +8 -7
  558. package/src/renderer/shared/components/Settings/AutomationTab.tsx +59 -2
  559. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +18 -12
  560. package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.test.tsx +65 -0
  561. package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.tsx +456 -0
  562. package/src/renderer/shared/components/Settings/GeneralTab.tsx +72 -113
  563. package/src/renderer/shared/components/Settings/MemoryTab.test.tsx +37 -0
  564. package/src/renderer/shared/components/Settings/MemoryTab.tsx +71 -31
  565. package/src/renderer/shared/components/Settings/NotificationsTab.test.tsx +15 -0
  566. package/src/renderer/shared/components/Settings/NotificationsTab.tsx +84 -0
  567. package/src/renderer/shared/components/Settings/PluginsTab.tsx +101 -24
  568. package/src/renderer/shared/components/Settings/ProfileTab.tsx +1 -1
  569. package/src/renderer/shared/components/Settings/SettingsWindow.tsx +796 -0
  570. package/src/renderer/shared/components/Settings/ThemeTab.tsx +31 -11
  571. package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +220 -0
  572. package/src/renderer/shared/components/activity/AgentActivityTable.tsx +340 -0
  573. package/src/renderer/shared/components/activity/McpToolResults.tsx +118 -0
  574. package/src/renderer/shared/components/activity/PlanChecklistWidget.tsx +77 -0
  575. package/src/renderer/shared/components/activity/RunSummaryDashboard.test.tsx +127 -0
  576. package/src/renderer/shared/components/activity/RunSummaryDashboard.tsx +222 -0
  577. package/src/renderer/shared/components/activity/SubagentDagView.test.ts +123 -0
  578. package/src/renderer/shared/components/activity/SubagentDagView.tsx +420 -0
  579. package/src/renderer/shared/components/activity/SubagentStatusStrip.test.tsx +82 -0
  580. package/src/renderer/shared/components/activity/SubagentStatusStrip.tsx +107 -0
  581. package/src/renderer/shared/components/activity/ViewedImages.tsx +140 -0
  582. package/src/renderer/shared/components/app/AuthGate.tsx +227 -0
  583. package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +186 -0
  584. package/src/renderer/shared/components/app/CommandPalette.tsx +593 -0
  585. package/src/renderer/shared/components/app/DashboardSidebar.tsx +1230 -0
  586. package/src/renderer/shared/components/app/DesktopTitlebar.tsx +354 -0
  587. package/src/renderer/shared/components/app/ErrorNotice.tsx +29 -0
  588. package/src/renderer/shared/components/app/MintDashboard.tsx +2182 -0
  589. package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +127 -0
  590. package/src/renderer/shared/components/cards/CalculationCard.tsx +86 -0
  591. package/src/renderer/shared/components/cards/ImageGenCard.tsx +146 -0
  592. package/src/renderer/shared/components/cards/ImageSearchCard.tsx +145 -0
  593. package/src/renderer/shared/components/cards/MermaidCard.tsx +283 -0
  594. package/src/renderer/shared/components/cards/StockCard.tsx +152 -0
  595. package/src/renderer/shared/components/cards/ToolResultCards.tsx +86 -0
  596. package/src/renderer/shared/components/cards/UiFeatureCard.tsx +267 -0
  597. package/src/renderer/shared/components/cards/UiGridCard.tsx +188 -0
  598. package/src/renderer/shared/components/cards/WeatherCard.tsx +224 -0
  599. package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +24 -0
  600. package/src/renderer/shared/components/chat/ApprovalCard.tsx +347 -0
  601. package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +109 -0
  602. package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +145 -0
  603. package/src/renderer/shared/components/chat/ChatMessageItem.tsx +242 -0
  604. package/src/renderer/shared/components/chat/ChatPanel.test.tsx +31 -0
  605. package/src/renderer/shared/components/chat/ChatPanel.tsx +1999 -0
  606. package/src/renderer/shared/components/chat/CollapsiblePasteInput.tsx +90 -0
  607. package/src/renderer/shared/components/chat/ContextCompactionStatus.tsx +17 -0
  608. package/src/renderer/shared/components/chat/DiffReviewModal.tsx +565 -0
  609. package/src/renderer/shared/components/chat/GeminiLiveOverlay.css +86 -0
  610. package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +24 -0
  611. package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +112 -0
  612. package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +936 -0
  613. package/src/renderer/shared/components/chat/QueuedMessagesBanner.test.tsx +89 -0
  614. package/src/renderer/shared/components/chat/QueuedMessagesBanner.tsx +201 -0
  615. package/src/renderer/shared/components/chat/RewindModal.tsx +414 -0
  616. package/src/renderer/shared/components/chat/SlashChoicePanel.tsx +88 -0
  617. package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +33 -0
  618. package/src/renderer/shared/components/chat/SlashSuggestions.tsx +346 -0
  619. package/src/renderer/shared/components/chat/SourcesBlock.tsx +435 -0
  620. package/src/renderer/shared/components/chat/ThinkingBlock.tsx +102 -0
  621. package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +86 -0
  622. package/src/renderer/shared/components/chat/TurnFileChanges.tsx +430 -0
  623. package/src/renderer/shared/components/chat/VirtualChatHistory.tsx +82 -0
  624. package/src/renderer/shared/components/chat/VoicePill.css +85 -0
  625. package/src/renderer/shared/components/chat/VoicePill.d.ts +12 -0
  626. package/src/renderer/shared/components/chat/VoicePill.jsx +191 -0
  627. package/src/renderer/shared/components/chat/VoicePill.test.tsx +22 -0
  628. package/src/renderer/shared/components/extensions/CliSessionsView.test.tsx +125 -0
  629. package/src/renderer/shared/components/extensions/CliSessionsView.tsx +545 -0
  630. package/src/renderer/shared/components/extensions/LinkedFoldersView.tsx +614 -0
  631. package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +302 -0
  632. package/src/renderer/shared/components/extensions/McpServersView.tsx +1040 -0
  633. package/src/renderer/shared/components/extensions/McpToolAllowlist.tsx +182 -0
  634. package/src/renderer/shared/components/extensions/ModUi.test.tsx +70 -0
  635. package/src/renderer/shared/components/extensions/ModsPanel.tsx +208 -0
  636. package/src/renderer/shared/components/extensions/PluginsView.tsx +325 -0
  637. package/src/renderer/shared/components/extensions/ScheduledTasksView.tsx +614 -0
  638. package/src/renderer/shared/components/extensions/SkillsView.test.tsx +31 -0
  639. package/src/renderer/shared/components/extensions/SkillsView.tsx +363 -0
  640. package/src/renderer/shared/components/studio/ImageStudioPanel.tsx +740 -0
  641. package/src/renderer/shared/components/studio/LibraryItemCard.tsx +72 -0
  642. package/src/renderer/shared/components/studio/LibraryPage.tsx +165 -0
  643. package/src/renderer/shared/components/studio/LibraryPreview.tsx +59 -0
  644. package/src/renderer/shared/components/studio/VeoStudioPanel.tsx +708 -0
  645. package/src/renderer/shared/components/ui/GlideSelect.css +139 -0
  646. package/src/renderer/shared/components/ui/ProviderBrandIcon.test.tsx +67 -0
  647. package/src/renderer/shared/components/ui/ProviderBrandIcon.tsx +167 -0
  648. package/src/renderer/shared/components/ui/README.md +52 -0
  649. package/src/renderer/shared/components/ui/SelectField.test.tsx +27 -0
  650. package/src/renderer/shared/components/ui/SelectField.tsx +506 -0
  651. package/src/renderer/shared/components/ui/button.tsx +37 -0
  652. package/src/renderer/shared/components/ui/error-recovery.css +35 -0
  653. package/src/renderer/shared/components/ui/error-recovery.tsx +35 -0
  654. package/src/renderer/shared/components/ui/voice-powered-orb.css +6 -0
  655. package/src/renderer/shared/components/ui/voice-powered-orb.tsx +374 -0
  656. package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +700 -0
  657. package/src/renderer/shared/components/workspace/BackgroundTerminals.test.tsx +63 -0
  658. package/src/renderer/shared/components/workspace/BackgroundTerminals.tsx +212 -0
  659. package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +707 -0
  660. package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +442 -0
  661. package/src/renderer/shared/components/workspace/SessionSources.tsx +113 -0
  662. package/src/renderer/shared/components/workspace/TerminalDock.tsx +244 -0
  663. package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +68 -0
  664. package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +819 -0
  665. package/src/renderer/shared/components/workspace/ToolsPanelToggle.tsx +13 -0
  666. package/src/renderer/shared/components/workspace/WorkspaceFileInfo.tsx +28 -0
  667. package/src/renderer/shared/components/workspace/WorkspaceMediaPreview.tsx +17 -0
  668. package/src/renderer/shared/components/workspace/WorkspacePaneResizer.tsx +92 -0
  669. package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +89 -0
  670. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +89 -0
  671. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.tsx +19 -0
  672. package/src/renderer/shared/components/workspace/WorkspaceSelector.tsx +246 -0
  673. package/src/renderer/shared/components/workspace/background-terminals.css +292 -0
  674. package/src/renderer/shared/components/workspace/workspace-selector.css +330 -0
  675. package/src/renderer/shared/constants/config.ts +37 -1
  676. package/src/renderer/shared/constants/slashCommands.ts +1 -1
  677. package/src/renderer/shared/conversation/coordinator.test.ts +46 -0
  678. package/src/renderer/shared/conversation/coordinator.ts +50 -2
  679. package/src/renderer/shared/conversation/lifecycle.test.ts +524 -0
  680. package/src/renderer/shared/conversation/lifecycle.ts +575 -0
  681. package/src/renderer/shared/conversation/projectNavigation.test.ts +123 -0
  682. package/src/renderer/shared/conversation/projectNavigation.ts +50 -0
  683. package/src/renderer/shared/conversation/sourcesTransport.ts +27 -0
  684. package/src/renderer/shared/conversation/syncView.test.ts +1 -34
  685. package/src/renderer/shared/conversation/syncView.ts +0 -37
  686. package/src/renderer/shared/conversation/transport.test.ts +183 -0
  687. package/src/renderer/shared/conversation/transport.ts +201 -0
  688. package/src/renderer/shared/conversation/useConversationCoordinator.ts +67 -79
  689. package/src/renderer/shared/css/agent-observability.css +90 -30
  690. package/src/renderer/shared/css/auth-gate.css +112 -13
  691. package/src/renderer/shared/css/chat-ui-cards.css +35 -35
  692. package/src/renderer/shared/css/code-review.css +119 -86
  693. package/src/renderer/shared/css/code-sessions.css +37 -10
  694. package/src/renderer/shared/css/collapsed-paste.css +35 -0
  695. package/src/renderer/shared/css/effects.css +15 -0
  696. package/src/renderer/shared/css/error-notice.css +60 -0
  697. package/src/renderer/shared/css/file-changes.css +167 -5
  698. package/src/renderer/shared/css/library.css +476 -0
  699. package/src/renderer/shared/css/management-views.css +216 -37
  700. package/src/renderer/shared/css/mcp-results.css +54 -0
  701. package/src/renderer/shared/css/model-selector.css +163 -105
  702. package/src/renderer/shared/css/mods.css +77 -0
  703. package/src/renderer/shared/css/notification-notice.css +28 -0
  704. package/src/renderer/shared/css/queued-messages.css +221 -0
  705. package/src/renderer/shared/css/session-sources.css +28 -0
  706. package/src/renderer/shared/css/settings/agents.css +2 -2
  707. package/src/renderer/shared/css/settings/base.css +1260 -0
  708. package/src/renderer/shared/css/settings/general.css +384 -92
  709. package/src/renderer/shared/css/settings/memory.css +154 -0
  710. package/src/renderer/shared/css/settings/notifications.css +11 -0
  711. package/src/renderer/shared/css/settings/plugins.css +8 -8
  712. package/src/renderer/shared/css/settings/theme.css +49 -32
  713. package/src/renderer/shared/css/slash-choice.css +95 -0
  714. package/src/renderer/shared/css/slash-suggestions.css +68 -0
  715. package/src/renderer/shared/css/sources-block.css +91 -0
  716. package/src/renderer/shared/css/thinking-status.css +46 -0
  717. package/src/renderer/shared/css/tools-panel.css +7 -0
  718. package/src/renderer/shared/css/ui-tailwind.css +19 -0
  719. package/src/renderer/shared/css/viewed-images.css +104 -0
  720. package/src/renderer/shared/css/workspace-file-info.css +12 -0
  721. package/src/renderer/shared/css/workspace-media-preview.css +13 -0
  722. package/src/renderer/shared/env.d.ts +9 -0
  723. package/src/renderer/shared/fonts.css +46 -0
  724. package/src/renderer/shared/fonts.ts +3 -12
  725. package/src/renderer/shared/lib/utils.ts +6 -0
  726. package/src/renderer/shared/library/controller.test.ts +61 -0
  727. package/src/renderer/shared/library/controller.ts +57 -0
  728. package/src/renderer/shared/library/transport.test.ts +26 -0
  729. package/src/renderer/shared/library/transport.ts +60 -0
  730. package/src/renderer/shared/library/useLibraryFile.ts +38 -0
  731. package/src/renderer/shared/mods.ts +58 -0
  732. package/src/renderer/shared/platform.test.ts +200 -1
  733. package/src/renderer/shared/platform.ts +55 -4
  734. package/src/renderer/shared/speech/player.ts +160 -0
  735. package/src/renderer/shared/speech/types.ts +11 -0
  736. package/src/renderer/shared/theme/themeManager.test.ts +63 -0
  737. package/src/renderer/shared/theme/themeManager.ts +20 -5
  738. package/src/renderer/shared/theme/themes.ts +17 -4
  739. package/src/renderer/shared/theme/tokens.css +448 -51
  740. package/src/renderer/shared/types.ts +111 -2
  741. package/src/renderer/shared/utils/agentActivity.test.ts +234 -0
  742. package/src/renderer/shared/utils/agentActivity.ts +221 -32
  743. package/src/renderer/shared/utils/approval.tsx +7 -0
  744. package/src/renderer/shared/utils/artifactBrowser.test.ts +80 -0
  745. package/src/renderer/shared/utils/artifactBrowser.ts +41 -0
  746. package/src/renderer/shared/utils/artifacts.ts +55 -0
  747. package/src/renderer/shared/utils/contextCompaction.test.tsx +48 -0
  748. package/src/renderer/shared/utils/contextCompaction.ts +21 -0
  749. package/src/renderer/shared/utils/effectVisibility.ts +9 -0
  750. package/src/renderer/shared/utils/externalLinks.ts +91 -0
  751. package/src/renderer/shared/utils/lazyWithRetry.ts +13 -0
  752. package/src/renderer/shared/utils/markdown.tsx +172 -38
  753. package/src/renderer/shared/utils/markdown.webImages.test.tsx +124 -2
  754. package/src/renderer/shared/utils/mcpResults.test.tsx +114 -0
  755. package/src/renderer/shared/utils/modViewport.ts +7 -0
  756. package/src/renderer/shared/utils/nativeRecordingSession.test.ts +61 -0
  757. package/src/renderer/shared/utils/nativeRecordingSession.ts +46 -0
  758. package/src/renderer/shared/utils/notificationSettings.test.ts +14 -0
  759. package/src/renderer/shared/utils/notificationSettings.ts +23 -0
  760. package/src/renderer/shared/utils/notifications.test.ts +51 -0
  761. package/src/renderer/shared/utils/notifications.ts +54 -0
  762. package/src/renderer/shared/utils/pastedText.test.ts +20 -0
  763. package/src/renderer/shared/utils/pastedText.ts +13 -0
  764. package/src/renderer/shared/utils/pdfRange.ts +16 -0
  765. package/src/renderer/shared/utils/previewObjectUrls.ts +20 -0
  766. package/src/renderer/shared/utils/progressCache.test.ts +32 -0
  767. package/src/renderer/shared/utils/sessionSources.test.ts +71 -0
  768. package/src/renderer/shared/utils/sessionSources.ts +36 -0
  769. package/src/renderer/shared/utils/slashCommandProcessor.ts +5 -2
  770. package/src/renderer/shared/utils/sourceAttachments.ts +28 -0
  771. package/src/renderer/shared/utils/speech.ts +65 -9
  772. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +22 -0
  773. package/src/renderer/shared/utils/syntaxHighlight.tsx +26 -0
  774. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +49 -5
  775. package/src/renderer/shared/utils/useLatestCallback.ts +9 -0
  776. package/src/renderer/shared/utils/useNativeVoiceInput.ts +95 -73
  777. package/src/renderer/shared/utils/useSessionSources.ts +37 -0
  778. package/src/renderer/shared/utils/viewedImages.test.ts +129 -0
  779. package/src/renderer/shared/utils/viewedImages.ts +97 -0
  780. package/src/renderer/shared/utils/workspaceDrag.ts +30 -0
  781. package/src/renderer/shared/utils/workspaceImage.ts +17 -0
  782. package/src/renderer/shared/utils/workspaceListPreferences.test.ts +64 -0
  783. package/src/renderer/shared/utils/workspaceListPreferences.ts +54 -0
  784. package/src/renderer/shared/utils/workspaceMedia.ts +41 -0
  785. package/src/renderer/shared/utils/workspacePane.test.ts +33 -0
  786. package/src/renderer/shared/utils/workspacePane.ts +20 -0
  787. package/src/renderer/shared/utils/workspacePreview.test.ts +169 -0
  788. package/src/renderer/shared/utils/workspacePreview.ts +47 -0
  789. package/src/renderer/shared/utils/workspaceSnapshot.test.ts +49 -0
  790. package/src/renderer/shared/utils/workspaceSnapshot.ts +26 -0
  791. package/src/renderer/shared/utils/workspaces.ts +11 -0
  792. package/src/renderer/shared/workspaceUndo.ts +88 -0
  793. package/src/renderer/src/App.tsx +15 -9
  794. package/src/renderer/src/components/ChatPanel.tsx +1 -1
  795. package/src/renderer/src/components/DashboardSidebar.tsx +2 -2
  796. package/src/renderer/src/components/ImageStudioPanel.tsx +1 -1
  797. package/src/renderer/src/components/LiveTranslateControls.tsx +65 -0
  798. package/src/renderer/src/components/MintDashboard.tsx +1 -1
  799. package/src/renderer/src/components/ProactiveGlow.tsx +1 -1
  800. package/src/renderer/src/components/ScreenPicker.css +67 -30
  801. package/src/renderer/src/components/ScreenPicker.tsx +201 -60
  802. package/src/renderer/src/components/SettingsWindow.tsx +6 -825
  803. package/src/renderer/src/components/SpotlightWindow.tsx +4 -32
  804. package/src/renderer/src/components/VeoStudioPanel.tsx +1 -1
  805. package/src/renderer/src/components/WorkspacePanel.tsx +261 -38
  806. package/src/renderer/src/components/browser-shell.css +27 -27
  807. package/src/renderer/src/components/browser-suggestions-popup.css +9 -9
  808. package/src/renderer/src/components/liveTranslateFrames.test.ts +26 -5
  809. package/src/renderer/src/components/liveTranslateFrames.ts +18 -7
  810. package/src/renderer/src/components/liveTranslateLanguage.ts +6 -0
  811. package/src/renderer/src/css/app-shell.css +69 -234
  812. package/src/renderer/src/css/chat.css +207 -494
  813. package/src/renderer/src/css/desktop-tools.css +6 -139
  814. package/src/renderer/src/css/misc.css +0 -52
  815. package/src/renderer/src/css/sidebar-workspace.css +171 -94
  816. package/src/renderer/src/css/spotlight.css +2 -2
  817. package/src/renderer/src/css/tool-surfaces.css +387 -61
  818. package/src/renderer/src/css/widget.css +7 -6
  819. package/src/renderer/src/env.d.ts +14 -9
  820. package/src/renderer/src/index.css +16 -64
  821. package/src/renderer/src/main.tsx +2 -1
  822. package/src/renderer/src/proactiveSuggestions.ts +29 -0
  823. package/src/renderer/src/tauri.ts +195 -337
  824. package/src/renderer/src/voiceInput.ts +5 -2
  825. package/src/renderer/src-web/App.tsx +6 -8
  826. package/src/renderer/src-web/components/ChatPanel.tsx +1 -1
  827. package/src/renderer/src-web/components/DashboardSidebar.tsx +2 -2
  828. package/src/renderer/src-web/components/ImageStudioPanel.tsx +1 -1
  829. package/src/renderer/src-web/components/MintDashboard.tsx +1 -1
  830. package/src/renderer/src-web/components/PullToRefresh.tsx +1 -1
  831. package/src/renderer/src-web/components/SettingsWindow.tsx +6 -846
  832. package/src/renderer/src-web/components/VeoStudioPanel.tsx +1 -1
  833. package/src/renderer/src-web/css/app-shell.css +40 -221
  834. package/src/renderer/src-web/css/chat.css +207 -494
  835. package/src/renderer/src-web/css/misc.css +0 -52
  836. package/src/renderer/src-web/css/mobile.css +15 -14
  837. package/src/renderer/src-web/css/sidebar-workspace.css +29 -95
  838. package/src/renderer/src-web/env.d.ts +15 -12
  839. package/src/renderer/src-web/index.css +16 -64
  840. package/src/renderer/src-web/main.tsx +2 -1
  841. package/src/renderer/src-web/proactiveSuggestions.ts +3 -0
  842. package/src/renderer/src-web/tauri.ts +194 -361
  843. package/src/renderer/tests/backgroundTerminals.browser.html +81 -0
  844. package/src/renderer/tests/browserArtifacts.browser.html +1 -0
  845. package/src/renderer/tests/browserArtifacts.browser.tsx +68 -0
  846. package/src/renderer/tests/chatEscape.browser.html +1 -0
  847. package/src/renderer/tests/chatEscape.browser.tsx +25 -0
  848. package/src/renderer/tests/chatPanelFixture.ts +23 -0
  849. package/src/renderer/tests/chatPerformance.browser.html +71 -0
  850. package/src/renderer/tests/cliSessions.browser.html +82 -0
  851. package/src/renderer/tests/collapsedPaste.browser.html +70 -0
  852. package/src/renderer/tests/composerMenus.browser.html +116 -0
  853. package/src/renderer/tests/contextCompaction.browser.html +17 -0
  854. package/src/renderer/tests/conversationLifecycle.browser.html +70 -0
  855. package/src/renderer/tests/effectsPerformance.browser.html +13 -0
  856. package/src/renderer/tests/errorRecovery.browser.html +21 -0
  857. package/src/renderer/tests/fixtures/preview.webm +0 -0
  858. package/src/renderer/tests/glideSelect.browser.html +70 -0
  859. package/src/renderer/tests/glideSelect.preview.html +27 -0
  860. package/src/renderer/tests/library.browser.html +1 -0
  861. package/src/renderer/tests/library.browser.tsx +78 -0
  862. package/src/renderer/tests/liveOrb.browser.html +49 -0
  863. package/src/renderer/tests/liveVoice.browser.html +29 -0
  864. package/src/renderer/tests/mediaFixtures.ts +32 -0
  865. package/src/renderer/tests/modInput.browser.html +22 -0
  866. package/src/renderer/tests/sessionSources.browser.html +1 -0
  867. package/src/renderer/tests/sessionSources.browser.tsx +168 -0
  868. package/src/renderer/tests/skills.browser.html +24 -0
  869. package/src/renderer/tests/toolsPanel.browser.html +1 -0
  870. package/src/renderer/tests/toolsPanel.browser.tsx +113 -0
  871. package/src/renderer/tests/viewedImages.browser.html +1 -0
  872. package/src/renderer/tests/viewedImages.browser.tsx +93 -0
  873. package/src/renderer/tests/voiceDictation.browser.html +101 -0
  874. package/src/renderer/tests/voicePill.browser.html +80 -0
  875. package/src/renderer/tests/voicePill.preview.html +34 -0
  876. package/src/renderer/tests/webSources.browser.html +1 -0
  877. package/src/renderer/tests/webSources.browser.tsx +2 -0
  878. package/src/renderer/tests/workspaceDrop.browser.html +1 -0
  879. package/src/renderer/tests/workspaceDrop.browser.tsx +183 -0
  880. package/src/renderer/tests/workspaceExpansion.browser.html +1 -0
  881. package/src/renderer/tests/workspaceExpansion.browser.tsx +66 -0
  882. package/src/renderer/tests/workspaceRemoval.browser.html +66 -0
  883. package/src/renderer/tests/workspaceUndo.browser.html +100 -0
  884. package/src-tauri/Cargo.toml +40 -0
  885. package/src-tauri/build.rs +3 -0
  886. package/src-tauri/src/app/lifecycle.rs +320 -0
  887. package/src-tauri/src/app/mod.rs +4 -0
  888. package/src-tauri/src/app/shortcuts.rs +40 -0
  889. package/src-tauri/src/app/tray.rs +52 -0
  890. package/src-tauri/src/background/events.rs +70 -0
  891. package/src-tauri/src/background/headless.rs +230 -0
  892. package/src-tauri/src/background/mod.rs +4 -0
  893. package/src-tauri/src/background/proactive.rs +262 -0
  894. package/src-tauri/src/commands/approvals.rs +20 -0
  895. package/src-tauri/src/commands/auth.rs +127 -0
  896. package/src-tauri/src/commands/conversation.rs +668 -0
  897. package/src-tauri/src/commands/integrations.rs +163 -0
  898. package/src-tauri/src/commands/media.rs +186 -0
  899. package/src-tauri/src/commands/mod.rs +11 -0
  900. package/src-tauri/src/commands/mods.rs +6 -0
  901. package/src-tauri/src/commands/settings.rs +141 -0
  902. package/src-tauri/src/commands/voice.rs +153 -0
  903. package/src-tauri/src/commands/workspace.rs +539 -0
  904. package/src-tauri/src/lib.rs +10 -0
  905. package/src-tauri/src/main.rs +3 -0
  906. package/src-tauri/src/native/actions.rs +206 -0
  907. package/src-tauri/src/native/browser.rs +252 -0
  908. package/src-tauri/src/native/capture.rs +695 -0
  909. package/src-tauri/src/native/discord_rpc.rs +99 -0
  910. package/src-tauri/src/native/integrations.rs +126 -0
  911. package/src-tauri/src/native/mod.rs +12 -0
  912. package/src-tauri/src/native/notifications.rs +51 -0
  913. package/src-tauri/src/native/plugins.rs +14 -0
  914. package/src-tauri/src/native/system.rs +250 -0
  915. package/src-tauri/src/native/terminal.rs +201 -0
  916. package/src-tauri/src/native/updater.rs +148 -0
  917. package/src-tauri/src/native/windows.rs +143 -0
  918. package/tsconfig.json +17 -1
  919. package/vite.config.ts +5 -1
  920. package/vite.config.web.ts +6 -0
  921. package/.cargo/config.toml +0 -13
  922. package/.claude/settings.local.json +0 -182
  923. package/.codex +0 -0
  924. package/CLAUDE.md +0 -102
  925. package/ROADMAP.md +0 -30
  926. package/mint-presentation-demo.mp4 +0 -0
  927. package/out/web/Live2DCubismCore.js +0 -9
  928. package/out/web/assets/CliSessionsView-BxUVWEEL.js +0 -1
  929. package/out/web/assets/CliSessionsView-CBDTXuQv.css +0 -1
  930. package/out/web/assets/CodeReviewPage-DkMgfcos.css +0 -1
  931. package/out/web/assets/CodeReviewPage-jKlmLJwT.js +0 -3
  932. package/out/web/assets/CommandPalette-DnwPA5hy.js +0 -1
  933. package/out/web/assets/ImageStudioPanel-BR1XFwhR.js +0 -1
  934. package/out/web/assets/LinkedFoldersView-bi-CWQ-I.js +0 -1
  935. package/out/web/assets/McpRegistryPicker-D5tEFnyq.js +0 -1
  936. package/out/web/assets/McpServersView-BGG1dhFM.js +0 -1
  937. package/out/web/assets/MintDashboard-BIglDEWX.js +0 -20
  938. package/out/web/assets/MintDashboard-DSMywSgs.css +0 -1
  939. package/out/web/assets/ModelPanel-CbJ1PFoZ.js +0 -1
  940. package/out/web/assets/PicturesLibrary-CQP0fLP1.css +0 -1
  941. package/out/web/assets/PicturesLibrary-DoYN7T1p.js +0 -1
  942. package/out/web/assets/PluginsView-CU1HWXGk.js +0 -1
  943. package/out/web/assets/ScheduledTasksView-Bnl36y2N.js +0 -1
  944. package/out/web/assets/SettingsWindow-DhWLAn9T.css +0 -1
  945. package/out/web/assets/SettingsWindow-Dw8L01pw.js +0 -2
  946. package/out/web/assets/SkillsView-cnl8aY5p.js +0 -2
  947. package/out/web/assets/ToolSurfacePage-CgsQw6l5.css +0 -1
  948. package/out/web/assets/ToolSurfacePage-Cz2VJAPZ.js +0 -37
  949. package/out/web/assets/VeoStudioPanel-D4JnERWR.js +0 -4
  950. package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +0 -1
  951. package/out/web/assets/abnfDiagram-VCTEODGH-ByMyfanq.js +0 -1
  952. package/out/web/assets/architecture-7GRP2DOG-CLq2kEOB.js +0 -1
  953. package/out/web/assets/architectureDiagram-5GKGNRK7-6PoVSmNG.js +0 -36
  954. package/out/web/assets/blockDiagram-NRAW4CY4-cKw9mAKi.js +0 -129
  955. package/out/web/assets/c4Diagram-UCG6FXSJ-Cqy8N5Ge.js +0 -38
  956. package/out/web/assets/chunk-2Q5K7J3B-Dmqrq8Tp.js +0 -1
  957. package/out/web/assets/chunk-5VM5RSS4-BiPuE0io.js +0 -15
  958. package/out/web/assets/chunk-75Z2AOVW-3qybijF5.js +0 -2
  959. package/out/web/assets/chunk-CKBBP62Z-cIrXq6jN.js +0 -2
  960. package/out/web/assets/chunk-CLGD4ZFX-CndDQkww.js +0 -1
  961. package/out/web/assets/chunk-CacB07dV.js +0 -1
  962. package/out/web/assets/chunk-DSNCTWBM-Cc4NxvgD.js +0 -2
  963. package/out/web/assets/chunk-DU6HZSFF-drzh6GsU.js +0 -128
  964. package/out/web/assets/chunk-F27PBJKO-FiY35l4v.js +0 -1
  965. package/out/web/assets/chunk-FOHPRMQF-BaVC1uA7.js +0 -161
  966. package/out/web/assets/chunk-G27WJ6UU-Bjt9Il4A.js +0 -231
  967. package/out/web/assets/chunk-GMAD6QVW-Bppi3Ctd.js +0 -72
  968. package/out/web/assets/chunk-JQ64N6SF-DGg5kcEV.js +0 -1
  969. package/out/web/assets/chunk-JWPE2WC7-BwtYOHqU.js +0 -1
  970. package/out/web/assets/chunk-LCL6LL3I-CCYcjd7p.js +0 -206
  971. package/out/web/assets/chunk-LNGE3PJU-AdBoOWw2.js +0 -34
  972. package/out/web/assets/chunk-OBVCFTLP-DJeNil5M.js +0 -1
  973. package/out/web/assets/chunk-P2QGCYS3-lRzkRjBB.js +0 -1
  974. package/out/web/assets/chunk-POPQ4Y6H-DouASMBW.js +0 -1
  975. package/out/web/assets/chunk-PWAF6VOD-DMmD1vVf.js +0 -1
  976. package/out/web/assets/chunk-R7TYR2AO-CYLA5UC5.js +0 -10
  977. package/out/web/assets/chunk-RHFEMEQ7-BiXwEcUT.js +0 -168
  978. package/out/web/assets/chunk-SVP7TREG-BV-zPslF.js +0 -88
  979. package/out/web/assets/chunk-TEH6E4GO-B2RGEohZ.js +0 -62
  980. package/out/web/assets/chunk-XXDRQBXY-DnJkbUaw.js +0 -1
  981. package/out/web/assets/chunk-Y2CYZVJY-C9o-s6OY.js +0 -1
  982. package/out/web/assets/classDiagram-DTDB5LWJ-Dq0smRWk.js +0 -1
  983. package/out/web/assets/classDiagram-v2-JRS7N3AN-Dq0smRWk.js +0 -1
  984. package/out/web/assets/config-C-ZFtipU.css +0 -1
  985. package/out/web/assets/config-eEDngJXO.js +0 -1
  986. package/out/web/assets/core-hQ2VA9Ti.js +0 -1
  987. package/out/web/assets/cose-bilkent-JH36ORCC--UN4aw5-.js +0 -1
  988. package/out/web/assets/cynefin-OW5HDTMX-Cas4csHM.js +0 -1
  989. package/out/web/assets/cynefinDiagram-5FMLGOSQ-Bbpj66Y3.js +0 -62
  990. package/out/web/assets/cytoscape.esm-Df1UN4Y1.js +0 -321
  991. package/out/web/assets/dagre-3AP2YEHR-B0-2RDEd.js +0 -4
  992. package/out/web/assets/dagre-BWadQu2F.js +0 -1
  993. package/out/web/assets/diagram-S7CK7UJ4-BdzEjUyH.js +0 -30
  994. package/out/web/assets/diagram-UQ7AKVKN-CJDwyPxF.js +0 -41
  995. package/out/web/assets/diagram-VSXAHHWV-BxaG1r_B.js +0 -3
  996. package/out/web/assets/diagram-VX7I27RA-BMmUqm8f.js +0 -24
  997. package/out/web/assets/diagram-Z3DM3KII-CFtd03eE.js +0 -24
  998. package/out/web/assets/dist-BoTOASLF.js +0 -1
  999. package/out/web/assets/dist-js-Bf2bhoku.js +0 -1
  1000. package/out/web/assets/ebnfDiagram-PWID7BFC-DwAen1hO.js +0 -1
  1001. package/out/web/assets/erDiagram-SSCWMZ5O-BCZSHACj.js +0 -99
  1002. package/out/web/assets/event-ChBVpRy0.js +0 -1
  1003. package/out/web/assets/eventmodeling-NTZA5JFV-XHStE4E_.js +0 -1
  1004. package/out/web/assets/flowDiagram-A5DVABFB-BLg7nEcd.js +0 -1
  1005. package/out/web/assets/ganttDiagram-EL5Y4UJY-BAEu1Cql.js +0 -292
  1006. package/out/web/assets/gitGraph-4MIJSDKK-r32Vyper.js +0 -1
  1007. package/out/web/assets/gitGraphDiagram-WWUBYQGX-z9o9qt6V.js +0 -106
  1008. package/out/web/assets/graphlib-CTR5ZhLf.js +0 -1
  1009. package/out/web/assets/index-Btsf4L8M.css +0 -1
  1010. package/out/web/assets/index-D8D9MeRA.js +0 -11
  1011. package/out/web/assets/info-A6RAGUB7-BvLaC2cw.js +0 -1
  1012. package/out/web/assets/infoDiagram-RXCK75RN-Be6WAXXw.js +0 -2
  1013. package/out/web/assets/ishikawaDiagram-5VMMS53U-DZjoHJHC.js +0 -70
  1014. package/out/web/assets/journeyDiagram-EYS64GPL-CtVkjkGm.js +0 -139
  1015. package/out/web/assets/kanban-definition-3QL26DDD-CW5FMv1w.js +0 -89
  1016. package/out/web/assets/katex-O3jvqgnU.js +0 -257
  1017. package/out/web/assets/lucide-react-Ip3L6Jtn.js +0 -1
  1018. package/out/web/assets/markdown-BSeeUbJH.js +0 -38
  1019. package/out/web/assets/mermaid-parser.core-aUgCqsZ5.js +0 -7
  1020. package/out/web/assets/mermaid.core-D57sEiSL.js +0 -11
  1021. package/out/web/assets/mindmap-definition-FBJOCRG2-DAmSpM3a.js +0 -96
  1022. package/out/web/assets/modelManager-tMVmJVAW.js +0 -2
  1023. package/out/web/assets/oauthManager-9jxicDWJ.js +0 -1
  1024. package/out/web/assets/packet-AYTQ26CC-DJ27iIoJ.js +0 -1
  1025. package/out/web/assets/pegDiagram-XKGWAZYB-Bj3ii-gS.js +0 -1
  1026. package/out/web/assets/pie-WAS4IAKB-DG5mCUbc.js +0 -1
  1027. package/out/web/assets/pieDiagram-E7YTZNPT-BrJLPnDA.js +0 -39
  1028. package/out/web/assets/prompt-latin-400-normal-BQ9zjSN8.woff2 +0 -0
  1029. package/out/web/assets/prompt-latin-400-normal-CxU4ec_r.woff +0 -0
  1030. package/out/web/assets/prompt-latin-500-normal-B1edBDQ9.woff +0 -0
  1031. package/out/web/assets/prompt-latin-500-normal-CxzxEHZc.woff2 +0 -0
  1032. package/out/web/assets/prompt-latin-600-normal-CSDDLorv.woff +0 -0
  1033. package/out/web/assets/prompt-latin-600-normal-hKZWXsc1.woff2 +0 -0
  1034. package/out/web/assets/prompt-latin-700-normal-BfbftDSq.woff +0 -0
  1035. package/out/web/assets/prompt-latin-700-normal-I2gc831J.woff2 +0 -0
  1036. package/out/web/assets/prompt-thai-400-normal-BrkKv8cO.woff2 +0 -0
  1037. package/out/web/assets/prompt-thai-400-normal-DJypnfr3.woff +0 -0
  1038. package/out/web/assets/prompt-thai-500-normal-C18pDUoL.woff2 +0 -0
  1039. package/out/web/assets/prompt-thai-500-normal-D7YNqUJD.woff +0 -0
  1040. package/out/web/assets/prompt-thai-600-normal-CYPViXds.woff +0 -0
  1041. package/out/web/assets/prompt-thai-600-normal-MrdfU7zR.woff2 +0 -0
  1042. package/out/web/assets/prompt-thai-700-normal-Cb5ilR-c.woff +0 -0
  1043. package/out/web/assets/prompt-thai-700-normal-Cg4aQ0Nn.woff2 +0 -0
  1044. package/out/web/assets/quadrantDiagram-AXDQQJYC-3x_TmF3j.js +0 -7
  1045. package/out/web/assets/radar-RG4KPBEZ-CrXaTTuV.js +0 -1
  1046. package/out/web/assets/railroad-74A4TZTK-BpqMH9BE.js +0 -1
  1047. package/out/web/assets/railroad-abnf-HS5TGJTU-DHI0uLlV.js +0 -1
  1048. package/out/web/assets/railroad-ebnf-LZEXJU2U-CQCb316y.js +0 -1
  1049. package/out/web/assets/railroad-peg-WCYAUIDC-Ph1giKkH.js +0 -1
  1050. package/out/web/assets/railroadDiagram-O6MQD6OU-BRq6wIxx.js +0 -1
  1051. package/out/web/assets/requirementDiagram-EFPCY7ZU-DIJj6G1g.js +0 -84
  1052. package/out/web/assets/rough.esm-DFbTUy8a.js +0 -1
  1053. package/out/web/assets/sankeyDiagram-P5KCCOFB-DZajSryq.js +0 -40
  1054. package/out/web/assets/sequenceDiagram-WJ2MYXX4-DxJCWyZS.js +0 -162
  1055. package/out/web/assets/sizeCapture-X5ZJPWSS-DKeOCaN2.js +0 -1
  1056. package/out/web/assets/src-DnfeXQPr.js +0 -1
  1057. package/out/web/assets/stateDiagram-HBIQ2CUA-k6voi8Vf.js +0 -1
  1058. package/out/web/assets/stateDiagram-v2-4QOOHH4V-DeEc0lGZ.js +0 -1
  1059. package/out/web/assets/studio-B1mk40wD.js +0 -1
  1060. package/out/web/assets/swimlanes-XN3QIQJK-igYhTjXB.js +0 -1
  1061. package/out/web/assets/swimlanesDiagram-VK2B7HYN-DpVik9v7.js +0 -8
  1062. package/out/web/assets/timeline-definition-24CTP7MA-Dke2p93p.js +0 -120
  1063. package/out/web/assets/treeView-Q6P3EWNA-hNELnXUs.js +0 -1
  1064. package/out/web/assets/treemap-WGGIJYW6-D2B6wDsx.js +0 -1
  1065. package/out/web/assets/vennDiagram-4TSXK5OY-CDkW7rUP.js +0 -34
  1066. package/out/web/assets/wardley-WFR3VGLG-BECdXETi.js +0 -1
  1067. package/out/web/assets/wardleyDiagram-VM6X3IG4-B5BycpQ4.js +0 -78
  1068. package/out/web/assets/window-5qYXKoW7.js +0 -1
  1069. package/out/web/assets/xychartDiagram-S5SC5T6Z-CtJS3b--.js +0 -7
  1070. package/skills-lock.json +0 -77
  1071. package/src/renderer/public/Live2DCubismCore.js +0 -9
  1072. package/src/renderer/shared/components/AgentActivityDrawer.tsx +0 -203
  1073. package/src/renderer/shared/components/AgentActivityTable.tsx +0 -301
  1074. package/src/renderer/shared/components/ApprovalCard.tsx +0 -340
  1075. package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +0 -505
  1076. package/src/renderer/shared/components/AuthGate.tsx +0 -191
  1077. package/src/renderer/shared/components/CalculationCard.tsx +0 -100
  1078. package/src/renderer/shared/components/ChatCodeBlock.tsx +0 -105
  1079. package/src/renderer/shared/components/ChatMessageItem.tsx +0 -306
  1080. package/src/renderer/shared/components/ChatPanel.tsx +0 -2226
  1081. package/src/renderer/shared/components/ChunkErrorBoundary.tsx +0 -284
  1082. package/src/renderer/shared/components/CliSessionsView.tsx +0 -495
  1083. package/src/renderer/shared/components/CodeReviewPage.tsx +0 -565
  1084. package/src/renderer/shared/components/CommandPalette.tsx +0 -593
  1085. package/src/renderer/shared/components/DashboardSidebar.tsx +0 -1341
  1086. package/src/renderer/shared/components/DesktopTitlebar.tsx +0 -345
  1087. package/src/renderer/shared/components/DiffReviewModal.tsx +0 -565
  1088. package/src/renderer/shared/components/GeminiLiveOverlay.tsx +0 -128
  1089. package/src/renderer/shared/components/GitBranchSelector.tsx +0 -442
  1090. package/src/renderer/shared/components/ImageGenCard.tsx +0 -159
  1091. package/src/renderer/shared/components/ImageSearchCard.tsx +0 -168
  1092. package/src/renderer/shared/components/ImageStudioPanel.tsx +0 -739
  1093. package/src/renderer/shared/components/InlineThoughtNote.tsx +0 -27
  1094. package/src/renderer/shared/components/LinkedFoldersView.tsx +0 -614
  1095. package/src/renderer/shared/components/McpRegistryPicker.tsx +0 -302
  1096. package/src/renderer/shared/components/McpServersView.tsx +0 -952
  1097. package/src/renderer/shared/components/McpToolAllowlist.tsx +0 -184
  1098. package/src/renderer/shared/components/MermaidCard.tsx +0 -267
  1099. package/src/renderer/shared/components/MintDashboard.tsx +0 -2665
  1100. package/src/renderer/shared/components/ModalErrorBoundary.tsx +0 -127
  1101. package/src/renderer/shared/components/ModelSelectorPopover.tsx +0 -929
  1102. package/src/renderer/shared/components/PlanChecklistWidget.tsx +0 -77
  1103. package/src/renderer/shared/components/PluginsView.tsx +0 -325
  1104. package/src/renderer/shared/components/RewindModal.tsx +0 -421
  1105. package/src/renderer/shared/components/RunSummaryDashboard.tsx +0 -149
  1106. package/src/renderer/shared/components/ScheduledTasksView.tsx +0 -613
  1107. package/src/renderer/shared/components/SkillsView.tsx +0 -347
  1108. package/src/renderer/shared/components/SlashSuggestions.tsx +0 -318
  1109. package/src/renderer/shared/components/SourcesBlock.tsx +0 -450
  1110. package/src/renderer/shared/components/StockCard.tsx +0 -168
  1111. package/src/renderer/shared/components/SubagentDagView.tsx +0 -366
  1112. package/src/renderer/shared/components/TerminalDock.tsx +0 -241
  1113. package/src/renderer/shared/components/ThinkingBlock.tsx +0 -102
  1114. package/src/renderer/shared/components/ToolSurfacePage.tsx +0 -520
  1115. package/src/renderer/shared/components/UiFeatureCard.tsx +0 -267
  1116. package/src/renderer/shared/components/UiGridCard.tsx +0 -188
  1117. package/src/renderer/shared/components/VeoStudioPanel.tsx +0 -707
  1118. package/src/renderer/shared/components/WeatherCard.tsx +0 -224
  1119. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +0 -133
  1120. package/src/renderer/shared/components/WorkspaceSelector.tsx +0 -149
  1121. package/src/renderer/src/companionWidget.ts +0 -95
  1122. package/src/renderer/src/components/CalculationCard.tsx +0 -3
  1123. package/src/renderer/src/components/ImageGenCard.tsx +0 -2
  1124. package/src/renderer/src/components/ImageSearchCard.tsx +0 -2
  1125. package/src/renderer/src/components/Live2DStage.tsx +0 -412
  1126. package/src/renderer/src/components/MermaidCard.tsx +0 -1
  1127. package/src/renderer/src/components/ModelPanel.tsx +0 -155
  1128. package/src/renderer/src/components/PicturesLibrary.tsx +0 -288
  1129. package/src/renderer/src/components/Settings/AgentsTab.tsx +0 -4
  1130. package/src/renderer/src/components/Settings/AudioTab.tsx +0 -3
  1131. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -3
  1132. package/src/renderer/src/components/Settings/GeneralTab.tsx +0 -3
  1133. package/src/renderer/src/components/Settings/MemoryTab.tsx +0 -3
  1134. package/src/renderer/src/components/Settings/PluginsTab.tsx +0 -3
  1135. package/src/renderer/src/components/Settings/ProfileTab.tsx +0 -3
  1136. package/src/renderer/src/components/Settings/ShortcutsTab.tsx +0 -3
  1137. package/src/renderer/src/components/Settings/ThemeTab.tsx +0 -3
  1138. package/src/renderer/src/components/StockCard.tsx +0 -3
  1139. package/src/renderer/src/components/WeatherCard.tsx +0 -3
  1140. package/src/renderer/src/css/model.css +0 -628
  1141. package/src/renderer/src/css/pictures.css +0 -415
  1142. package/src/renderer/src/css/settings/base.css +0 -1263
  1143. package/src/renderer/src-web/companionWidget.ts +0 -37
  1144. package/src/renderer/src-web/components/ModelPanel.tsx +0 -12
  1145. package/src/renderer/src-web/components/PicturesLibrary.tsx +0 -285
  1146. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +0 -4
  1147. package/src/renderer/src-web/components/Settings/AudioTab.tsx +0 -3
  1148. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -3
  1149. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +0 -3
  1150. package/src/renderer/src-web/components/Settings/MemoryTab.tsx +0 -3
  1151. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +0 -3
  1152. package/src/renderer/src-web/components/Settings/ProfileTab.tsx +0 -3
  1153. package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +0 -3
  1154. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +0 -3
  1155. package/src/renderer/src-web/css/model.css +0 -628
  1156. package/src/renderer/src-web/css/pictures.css +0 -462
  1157. package/src/renderer/src-web/css/settings/base.css +0 -1263
  1158. package/src/renderer/src-web/css/spotlight.css +0 -102
  1159. package/src/renderer/src-web/css/widget.css +0 -213
  1160. /package/src/renderer/shared/components/{UiMockupWidget.tsx → cards/UiMockupWidget.tsx} +0 -0
  1161. /package/src/renderer/shared/components/{ScreenCaptureDialog.css → studio/ScreenCaptureDialog.css} +0 -0
  1162. /package/src/renderer/shared/components/{ScreenCaptureDialog.tsx → studio/ScreenCaptureDialog.tsx} +0 -0
@@ -0,0 +1,2182 @@
1
+ import { createWorkspacePreviewOpener } from '../../utils/workspacePreview'
2
+ import { shouldNotify } from '../../utils/notificationSettings'
3
+ import { deliverNotification, receiveProactiveNotification } from '../../utils/notifications'
4
+ import '../../css/notification-notice.css'
5
+ import { lazy, Suspense, type ChangeEvent, type CSSProperties, type FormEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
6
+ import {
7
+ mergeFileChanges,
8
+ parseFileChangesFromProgress,
9
+ } from '../../agentProgress'
10
+ import { catalogPlatform, conversationPlatform, mediaPlatform, runtimePlatform, workspacePlatform, type SlashResponse } from '../../platform'
11
+ import type { ChatSession, DocumentAttachment, RuntimeStatus } from '../../types'
12
+
13
+ const {
14
+ clearChatHistory, deleteChatSession, renameChatSession,
15
+ saveSystemInteraction, updateChatSessionWorkspace,
16
+ listen, readClipboardImage, listChatSessions, pendingCompanionChat,
17
+ } = conversationPlatform
18
+ const { getRuntimeStatus, setActiveModel, selectWorkspaceDirectory, selectLinkedFolderPath, isTauriRuntime } = runtimePlatform
19
+ const { convertFileSrc } = mediaPlatform
20
+ const {
21
+ listLearnedSkills, addLearnedSkill, deleteLearnedSkill, detectSystemTools, reauthMcpServer,
22
+ listMcpServerTools, setProfileValue, listCronJobs, addCronJob, removeCronJob,
23
+ setCronJobEnabled, listLinkedFolders, addLinkedFolder, removeLinkedFolder, runSlashCommand,
24
+ linkedFolderStatus, refreshLinkedFolder, listLinkedFolderNotes, readLinkedFolderNote, openLinkedFolderNote,
25
+ } = catalogPlatform
26
+
27
+ import ChatPanel, { type ConversationActions, type ConversationViewModel } from '../chat/ChatPanel'
28
+ import ScreenCaptureDialog from '../studio/ScreenCaptureDialog'
29
+ import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
30
+ import DesktopTitlebar from './DesktopTitlebar'
31
+ import ErrorNotice from './ErrorNotice'
32
+ import type { ToolSurface } from '../workspace/ToolSurfacePage'
33
+ import SessionSources from '../workspace/SessionSources'
34
+ import { shouldPollSessionSources, useSessionSources } from '../../utils/useSessionSources'
35
+ import { useLatestCallback } from '../../utils/useLatestCallback'
36
+ import { PreviewObjectUrls } from '../../utils/previewObjectUrls'
37
+ import {
38
+ errorMessage,
39
+ readImage,
40
+ readDocument,
41
+ createTrimmedImagePreview,
42
+ createObjectUrlPreview,
43
+ applyThemeStyles,
44
+ } from '../../utils/ui'
45
+ import { workspacePaths } from '../../utils/workspaces'
46
+ import { workspaceListPreferences } from '../../utils/workspaceListPreferences'
47
+ import { executeSlashCommand } from '../../utils/slashCommandProcessor'
48
+ import { captureScreenForChat } from '../../utils/screenCapture'
49
+ import { useConversationCoordinator } from '../../conversation/useConversationCoordinator'
50
+ import { isPersistedConversation, navigateToSavedConversation } from '../../conversation/projectNavigation'
51
+ import type { PromptOptions } from '../../conversation/lifecycle'
52
+
53
+
54
+
55
+ import { DEFAULT_CONFIG, migrateFontFamily, migrateTypographyScale } from '../../constants/config'
56
+ import { BackgroundTerminals } from '../workspace/BackgroundTerminals'
57
+
58
+ const LAST_WORKSPACE_PATH_KEY = 'mint:last-workspace-path'
59
+ const RECENT_WORKSPACE_PATHS_KEY = 'mint:recent-workspace-paths'
60
+ const MAX_RECENT_WORKSPACES = 8
61
+ const ACTIVE_CONVERSATION_ID_KEY = 'mint:active-conversation-id'
62
+ const LAST_ACTIVE_TIME_KEY = 'mint:last-active-timestamp'
63
+ const DESKTOP_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 // 30 minutes
64
+
65
+ function readRecentWorkspacePaths(): string[] {
66
+ if (typeof window === 'undefined') return []
67
+ try {
68
+ const parsed: unknown = JSON.parse(window.localStorage.getItem(RECENT_WORKSPACE_PATHS_KEY) || '[]')
69
+ return Array.isArray(parsed)
70
+ ? [...new Set(parsed.filter((path): path is string => typeof path === 'string' && Boolean(path.trim())))].slice(0, MAX_RECENT_WORKSPACES)
71
+ : []
72
+ } catch {
73
+ return []
74
+ }
75
+ }
76
+
77
+ function touchActiveTimestamp() {
78
+ if (typeof window === 'undefined') return
79
+ try {
80
+ window.localStorage.setItem(LAST_ACTIVE_TIME_KEY, Date.now().toString())
81
+ } catch {
82
+ /* ignore */
83
+ }
84
+ }
85
+
86
+ function isDesktopRecentlyActive(): boolean {
87
+ if (typeof window === 'undefined') return false
88
+ if (!isTauriRuntime()) return false
89
+ const lastActiveStr = window.localStorage.getItem(LAST_ACTIVE_TIME_KEY)
90
+ if (!lastActiveStr) return false
91
+ const lastActive = parseInt(lastActiveStr, 10)
92
+ if (isNaN(lastActive) || lastActive <= 0) return false
93
+
94
+ const now = Date.now()
95
+ const diff = now - lastActive
96
+ if (diff < 0 || diff > DESKTOP_INACTIVITY_THRESHOLD_MS) {
97
+ return false
98
+ }
99
+
100
+ // Also verify same calendar day
101
+ const lastDate = new Date(lastActive).toDateString()
102
+ const today = new Date(now).toDateString()
103
+ return lastDate === today
104
+ }
105
+
106
+ // Unsent composer text, stashed per conversation so switching chats or
107
+ // reloading the page doesn't lose an in-progress message.
108
+ const DRAFT_KEY_PREFIX = 'mint:draft:'
109
+ function readDraft(id: string | null | undefined): string {
110
+ if (!id) return ''
111
+ try {
112
+ return window.localStorage.getItem(`${DRAFT_KEY_PREFIX}${id}`) || ''
113
+ } catch {
114
+ return ''
115
+ }
116
+ }
117
+ const SIDEBAR_DEFAULT_WIDTH = 264
118
+ const SIDEBAR_MIN_WIDTH = 200
119
+ const SIDEBAR_MAX_WIDTH = 420
120
+ /** Drag the sidebar edge past this width and it snaps fully closed on release. */
121
+ const SIDEBAR_COLLAPSE_THRESHOLD = 170
122
+
123
+ function createConversationId() {
124
+ const random = Math.random().toString(36).slice(2, 10)
125
+ return `conversation-${Date.now().toString(36)}-${random}`
126
+ }
127
+
128
+ function getConversationIdFromUrl(): string | null {
129
+ if (typeof window === 'undefined') return null
130
+ const pathname = window.location.pathname || ''
131
+ const hash = (window.location.hash || '').replace(/^#/, '')
132
+ const target = pathname || hash
133
+
134
+ const match = target.match(/^\/chat\/(.+)$/i) || target.match(/^\/c\/(.+)$/i)
135
+ if (match && match[1]) {
136
+ return decodeURIComponent(match[1])
137
+ }
138
+
139
+ const searchParams = new URLSearchParams(window.location.search)
140
+ const queryId = searchParams.get('id')
141
+ if (queryId) return queryId
142
+
143
+ return null
144
+ }
145
+
146
+ function isRootOrNewChatRoute(): boolean {
147
+ if (typeof window === 'undefined') return true
148
+ const pathname = (window.location.pathname || '').replace(/\/+$/, '')
149
+ const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
150
+ const target = (pathname || hash).toLowerCase()
151
+
152
+ // Explicit conversation ID specified in URL
153
+ if (getConversationIdFromUrl()) {
154
+ return false
155
+ }
156
+
157
+ // Explicit other panel view (skills, mcp, plugins, etc.)
158
+ if (
159
+ target.includes('skills') ||
160
+ target.includes('mcp') ||
161
+ target.includes('plugins') ||
162
+ target.includes('picture') ||
163
+ target.includes('image-studio') ||
164
+ target.includes('imagine') ||
165
+ target.includes('veo-studio') ||
166
+ target.includes('veo') ||
167
+ target.includes('code') ||
168
+ target === '/cli' ||
169
+ target === 'cli' ||
170
+ target.includes('cli-sessions') ||
171
+ target.includes('settings')
172
+ ) {
173
+ return false
174
+ }
175
+
176
+ // Root or generic chat route without specific conversation ID
177
+ return !target || target === '' || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html'
178
+ }
179
+
180
+ function activeConversationId() {
181
+ const fromUrl = getConversationIdFromUrl()
182
+ if (fromUrl) {
183
+ window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, fromUrl)
184
+ touchActiveTimestamp()
185
+ return fromUrl
186
+ }
187
+
188
+ // Desktop Smart Inactivity:
189
+ // On Desktop App (Tauri), if the user was active recently (within 30 mins and on the same calendar day),
190
+ // resume the previous conversation so a quick window close/reopen or system restart doesn't drop context.
191
+ if (isTauriRuntime() && isDesktopRecentlyActive()) {
192
+ const existing = window.localStorage.getItem(ACTIVE_CONVERSATION_ID_KEY)
193
+ if (existing) {
194
+ touchActiveTimestamp()
195
+ return existing === 'conversation-default' ? 'cli' : existing
196
+ }
197
+ }
198
+
199
+ // When visiting Root URL ('/' or '/chat' without specific ID), always start a fresh New chat
200
+ // (matching ChatGPT / Claude / Gemini industry-standard UX).
201
+ // Applies to Web UI always, and Desktop App upon cold launch / inactivity / new day.
202
+ if (isRootOrNewChatRoute()) {
203
+ const next = createConversationId()
204
+ touchActiveTimestamp()
205
+ return next
206
+ }
207
+
208
+ const existing = window.localStorage.getItem(ACTIVE_CONVERSATION_ID_KEY)
209
+ if (existing === 'conversation-default') {
210
+ window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, 'cli')
211
+ touchActiveTimestamp()
212
+ return 'cli'
213
+ }
214
+ if (existing) {
215
+ touchActiveTimestamp()
216
+ return existing
217
+ }
218
+ const next = createConversationId()
219
+ touchActiveTimestamp()
220
+ return next
221
+ }
222
+
223
+ const MOCK_WELCOME_INTERACTION = {
224
+ id: -1,
225
+ userText: '',
226
+ aiText: `Hi there! I'm Mint, your AI assistant! 🎯✨ I'm here and ready to help you with whatever you need. I've been organizing some background data to make things smoother for you. 💖\n\nBut the moment you start chatting with me, I'll put everything aside and focus on you! Is there something I can help you with today, or would you like to chat about something special? Let's do this! 🚀💪`,
227
+ provider: 'gemini',
228
+ model: 'gemini-3-flash-preview',
229
+ createdAt: new Date().toISOString(),
230
+ }
231
+
232
+
233
+ import { isSupportedDocument } from '../../utils/documentTypes'
234
+ import { useProactiveSuggestions } from '@/proactiveSuggestions'
235
+
236
+ const WorkspacePanel = lazy(() => import('@/components/WorkspacePanel'))
237
+ const ToolSurfacePage = lazy(() => import('../workspace/ToolSurfacePage'))
238
+ const SkillsView = lazy(() => import('../extensions/SkillsView'))
239
+ const ScheduledTasksView = lazy(() => import('../extensions/ScheduledTasksView'))
240
+ const LinkedFoldersView = lazy(() => import('../extensions/LinkedFoldersView'))
241
+ const McpServersView = lazy(() => import('../extensions/McpServersView'))
242
+ const PluginsView = lazy(() => import('../extensions/PluginsView'))
243
+ const CliSessionsView = lazy(() => import('../extensions/CliSessionsView'))
244
+ const LibraryPage = lazy(() => import('../studio/LibraryPage'))
245
+ const ImageStudioPanel = lazy(() => import('../studio/ImageStudioPanel'))
246
+ const VeoStudioPanel = lazy(() => import('../studio/VeoStudioPanel'))
247
+ const CommandPalette = lazy(() => import('./CommandPalette').then(({ CommandPalette: Component }) => ({ default: Component })))
248
+
249
+ function LazyPanelFallback() {
250
+ return <div className="auth-gate-loading" style={{ minHeight: 160 }}>Loading panel…</div>
251
+ }
252
+
253
+ function getInitialViewFromUrl(): DashboardView {
254
+ if (typeof window === 'undefined') return 'chat'
255
+ const hash = (window.location.hash || '').toLowerCase().replace(/^#/, '')
256
+ const pathname = (window.location.pathname || '').toLowerCase()
257
+ const target = hash || pathname
258
+
259
+ if (target.includes('skills')) return 'skills'
260
+ if (target.includes('mcp')) return 'mcp'
261
+ if (target.includes('plugins')) return 'plugins'
262
+ if (target.includes('picture') || target.includes('library')) return 'pictures'
263
+ if (target.includes('image-studio') || target.includes('imagine')) return 'imagine'
264
+ if (target.includes('veo-studio') || target.includes('veo')) return 'veo'
265
+ if (target.includes('code') || target === '/cli' || target === 'cli' || target.includes('cli-sessions')) return 'code'
266
+ return 'chat'
267
+ }
268
+
269
+ function getCleanPathForView(v: string, activeId?: string): string {
270
+ if (v === 'skills') return '/skills'
271
+ if (v === 'mcp') return '/mcp'
272
+ if (v === 'plugins') return '/plugins'
273
+ if (v === 'pictures') return '/library'
274
+ if (v === 'imagine') return '/image-studio'
275
+ if (v === 'veo' || v === 'veo_studio') return '/veo-studio'
276
+ if (v === 'code') return '/code'
277
+ if (v === 'settings') return '/settings'
278
+ if (activeId) return `/chat/${encodeURIComponent(activeId)}`
279
+ return '/chat'
280
+ }
281
+
282
+ /** Keeps a desktop/browser notification body to one readable line. */
283
+ function truncateForNotification(text: string, maxChars = 120): string {
284
+ const clean = text.replace(/\s+/g, ' ').trim()
285
+ return clean.length > maxChars ? `${clean.slice(0, maxChars)}…` : clean
286
+ }
287
+
288
+ /** One-line plain-text summary of an AgentApproval for a notification body
289
+ * — ApprovalCard's own renderApprovalDetails returns JSX, not text, so
290
+ * this is a separate, notification-only summary. AgentApproval serializes
291
+ * as a single-key-tagged object per variant, e.g. {"WriteFile": {path, ...}}. */
292
+ function describeApproval(payload: any): string {
293
+ const entry = Object.entries(payload?.approval ?? {})[0]
294
+ const kind = entry?.[0]
295
+ const detail: any = entry?.[1]
296
+ switch (kind) {
297
+ case 'WriteFile': return `Write to ${detail?.path}?`
298
+ case 'ApplyPatch': return `Edit ${detail?.path}?`
299
+ case 'RunShell': return `Run: ${detail?.command}`
300
+ case 'NoteWrite': return `Save note to ${detail?.path}?`
301
+ case 'RunPlugin': return `Run plugin: ${detail?.name}?`
302
+ case 'McpTool': return `Use ${detail?.tool} on ${detail?.server}?`
303
+ case 'UserApproval': return detail?.title ?? 'Approval needed'
304
+ case 'AskUser': return detail?.question ?? 'Mint has a question'
305
+ case 'ExitPlanMode':
306
+ case 'EnterPlanMode':
307
+ return 'Plan mode change needs approval'
308
+ default: return 'Mint needs your approval to continue'
309
+ }
310
+ }
311
+
312
+ export default function MintDashboard() {
313
+ const isDesktopApp = isTauriRuntime()
314
+ const [view, setViewState] = useState<DashboardView>(getInitialViewFromUrl)
315
+ const [initialConversationId] = useState(activeConversationId)
316
+ // Declared here (not further down with the rest of the workspace-related
317
+ // state) so the URL-change effect below can read it.
318
+ const { conversation, actions: conversationActions } = useConversationCoordinator(
319
+ {
320
+ workspacePath: window.localStorage.getItem(LAST_WORKSPACE_PATH_KEY) || '',
321
+ message: readDraft(initialConversationId),
322
+ },
323
+ {
324
+ conversationId: initialConversationId,
325
+ draftKeyPrefix: DRAFT_KEY_PREFIX,
326
+ workspaceStorageKey: LAST_WORKSPACE_PATH_KEY,
327
+ historyVisible: view === 'chat',
328
+ onError: reason => setError(errorMessage(reason)),
329
+ onApproval: (payload, chatId) => notifyPendingApproval(payload, chatId),
330
+ onReply: (response, chatId) => {
331
+ if (document.hidden || !document.hasFocus()) return sendNotification(response.text || 'Mint finished your request.', chatId)
332
+ },
333
+ onActivityPersisted: id => {
334
+ const key = String(id)
335
+ setThinkingExpanded(current => ({ ...current,
336
+ ...(liveThinkingOpenRef.current ? { [key]: true } : {}),
337
+ ...(liveExtendedThinkingOpenRef.current ? { [`extended-${key}`]: true } : {}),
338
+ }))
339
+ },
340
+ onRunCompleted: async () => {
341
+ getRuntimeStatus().then(setStatus).catch(() => {})
342
+ },
343
+ onVisibleRunCompleted: chatId => {
344
+ const pathname = (window.location.pathname || '').replace(/\/+$/, '')
345
+ const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
346
+ const target = (pathname || hash).toLowerCase()
347
+ if (!target || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html') {
348
+ window.history.replaceState({}, '', `/chat/${encodeURIComponent(chatId)}`)
349
+ }
350
+ },
351
+ },
352
+ )
353
+ const {
354
+ conversationId, workspacePath, message, imageAttachments, videoAttachments, documentAttachment,
355
+ queuedPrompts, queuePaused,
356
+ sending, sendingMessage, sendingImageCount, sendingVideoCount, streamedReply,
357
+ streamedResponse, agentProgress, interactions, hasOlderInteractions,
358
+ streamingConversationId, activeTurnId, agentActivitySnapshots, pendingApprovals,
359
+ sessionAutoApproved, chatSessions,
360
+ } = conversation
361
+ const activateConversation = (id: string) => {
362
+ conversationActions.activate(id)
363
+ }
364
+ // Web only in practice (desktop's window has no mobile-width breakpoint,
365
+ // so nothing ever sets this true there) — declared unconditionally so
366
+ // `changeView` can close it on every navigation without branching.
367
+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
368
+ const [screenCaptureOpen, setScreenCaptureOpen] = useState(false)
369
+
370
+ useEffect(() => {
371
+ if (!mobileSidebarOpen) return
372
+ const closeOnEscape = (event: KeyboardEvent) => {
373
+ if (event.key === 'Escape') setMobileSidebarOpen(false)
374
+ }
375
+ window.addEventListener('keydown', closeOnEscape)
376
+ return () => window.removeEventListener('keydown', closeOnEscape)
377
+ }, [mobileSidebarOpen])
378
+ const [workspaceRefreshRevision, setWorkspaceRefreshRevision] = useState(0)
379
+ const [recentWorkspacePaths, setRecentWorkspacePaths] = useState<string[]>(readRecentWorkspacePaths)
380
+
381
+ function rememberWorkspace(path: string) {
382
+ const recent = workspacePaths([path, ...readRecentWorkspacePaths()]).slice(0, MAX_RECENT_WORKSPACES)
383
+ try {
384
+ window.localStorage.setItem(RECENT_WORKSPACE_PATHS_KEY, JSON.stringify(recent))
385
+ } catch {
386
+ /* Keep the in-memory recent list usable when storage is unavailable. */
387
+ }
388
+ setRecentWorkspacePaths(recent)
389
+ }
390
+
391
+ useEffect(() => {
392
+ if (workspacePath.trim()) rememberWorkspace(workspacePath)
393
+ }, [workspacePath])
394
+
395
+ async function selectCodeHubFolder() {
396
+ const selected = await selectLinkedFolderPath()
397
+ if (selected) rememberWorkspace(selected)
398
+ return selected
399
+ }
400
+
401
+ const changeView = (newView: any, targetConversationId?: string) => {
402
+ setMobileSidebarOpen(false)
403
+ setViewState((prev) => {
404
+ const next = typeof newView === 'function' ? newView(prev) : newView
405
+ if (next === 'settings') {
406
+ if ((window as any).api?.openSettings) {
407
+ (window as any).api.openSettings()
408
+ } else {
409
+ if (window.location.pathname !== '/settings') {
410
+ window.history.pushState({}, '', '/settings')
411
+ }
412
+ }
413
+ return prev
414
+ }
415
+ const requestedView: DashboardView = (next === 'veo_studio' ? 'veo' : next) as DashboardView
416
+ // The Web app can select a project for chat context, but its local file
417
+ // browser is Desktop-only. Keep any Workspace navigation on Web in chat.
418
+ const mappedView: DashboardView = !isDesktopApp && requestedView === 'workspace' ? 'chat' : requestedView
419
+ const targetPath = getCleanPathForView(mappedView, targetConversationId || conversationId)
420
+ if (typeof window !== 'undefined' && window.location.pathname !== targetPath) {
421
+ window.history.pushState({}, '', targetPath)
422
+ }
423
+ return mappedView
424
+ })
425
+ }
426
+
427
+ const savedNavigationEpoch = useRef(0)
428
+ useEffect(() => () => { savedNavigationEpoch.current += 1 }, [])
429
+ const savedNavigationRef = useRef<(id: string) => Promise<void>>(async () => {})
430
+ savedNavigationRef.current = async (id: string) => {
431
+ const epoch = ++savedNavigationEpoch.current
432
+ try {
433
+ const opened = await navigateToSavedConversation(id, {
434
+ listSessions: listChatSessions,
435
+ openSession: (session, view) => selectConversation(session.id, session, view),
436
+ isCurrent: () => savedNavigationEpoch.current === epoch,
437
+ })
438
+ if (!opened && savedNavigationEpoch.current === epoch) {
439
+ setError('This conversation no longer exists. Choose another conversation.')
440
+ changeView('chat')
441
+ setMobileSidebarOpen(true)
442
+ }
443
+ } catch (reason) { if (savedNavigationEpoch.current === epoch) setError(errorMessage(reason)) }
444
+ }
445
+
446
+ useEffect(() => {
447
+ const handleUrlChange = () => {
448
+ savedNavigationEpoch.current += 1
449
+ if (window.location.hash === '#' || window.location.hash === '#/') {
450
+ window.history.replaceState({}, '', window.location.pathname + window.location.search)
451
+ }
452
+ const pathname = (window.location.pathname || '').toLowerCase()
453
+ const hash = (window.location.hash || '').toLowerCase()
454
+ if (pathname.includes('/settings') || hash.includes('/settings')) return
455
+ const nextView = getInitialViewFromUrl()
456
+ setViewState(nextView)
457
+
458
+ const urlSessionId = getConversationIdFromUrl()
459
+ if (urlSessionId && urlSessionId !== conversationId) {
460
+ void savedNavigationRef.current(urlSessionId)
461
+ } else if (!urlSessionId && isRootOrNewChatRoute()) {
462
+ const next = createConversationId()
463
+ activateConversation(next)
464
+ }
465
+ }
466
+ window.addEventListener('popstate', handleUrlChange)
467
+ window.addEventListener('hashchange', handleUrlChange)
468
+ return () => {
469
+ window.removeEventListener('popstate', handleUrlChange)
470
+ window.removeEventListener('hashchange', handleUrlChange)
471
+ }
472
+ }, [conversationId, workspacePath])
473
+
474
+ useEffect(() => {
475
+ touchActiveTimestamp()
476
+ const handleActivity = () => touchActiveTimestamp()
477
+ window.addEventListener('beforeunload', handleActivity)
478
+ document.addEventListener('visibilitychange', handleActivity)
479
+ window.addEventListener('focus', handleActivity)
480
+ return () => {
481
+ window.removeEventListener('beforeunload', handleActivity)
482
+ document.removeEventListener('visibilitychange', handleActivity)
483
+ window.removeEventListener('focus', handleActivity)
484
+ }
485
+ }, [])
486
+ const [status, setStatus] = useState<RuntimeStatus | null>(null)
487
+ const [error, setError] = useState('')
488
+ const [notificationNotice, setNotificationNotice] = useState<{ message: string; chatId?: string; type?: string } | null>(null)
489
+ const notificationNavigationRef = useRef(selectConversation)
490
+ notificationNavigationRef.current = selectConversation
491
+ const sendNotification = (body: string, chatId?: string, kind: 'replies' | 'approvals' | 'system' = 'replies') => deliverNotification(
492
+ () => shouldNotify(notificationConfigRef.current?.notificationSettings, kind, 'os')
493
+ ? window.api?.notifyAiResponse?.(truncateForNotification(body), chatId) : undefined,
494
+ setError,
495
+ )
496
+ const notifyPendingApproval = (payload: any, chatId?: string) => {
497
+ if (document.hidden || !document.hasFocus()) {
498
+ void sendNotification(describeApproval(payload), chatId ?? payload.chatId, 'approvals')
499
+ }
500
+ }
501
+ useEffect(() => {
502
+ let disposed = false
503
+ const cleanups: Array<() => void> = []
504
+ const register = (subscription: Promise<any> | undefined) => {
505
+ subscription?.then(unlisten => {
506
+ if (typeof unlisten !== 'function') return
507
+ if (disposed) unlisten()
508
+ else cleanups.push(unlisten)
509
+ }).catch(reason => { if (!disposed) setError(errorMessage(reason)) })
510
+ }
511
+ const openChat = (chatId?: string) => {
512
+ if (chatId) void notificationNavigationRef.current(chatId).catch(reason => setError(errorMessage(reason)))
513
+ }
514
+ const clicked = (event: Event) => openChat((event as CustomEvent).detail?.chatId)
515
+ window.addEventListener('mint:notification-click', clicked)
516
+ const openCompanionChat = async () => {
517
+ try {
518
+ const chatId = await pendingCompanionChat()
519
+ if (!chatId || disposed) return
520
+ initialRouteValidated.current = true
521
+ await savedNavigationRef.current(chatId)
522
+ if (!disposed) await pendingCompanionChat(chatId)
523
+ } catch (reason) { if (!disposed) setError(errorMessage(reason)) }
524
+ }
525
+ if (isTauriRuntime()) {
526
+ register(listen('companion-open-chat', () => { void openCompanionChat() }).then(unlisten => {
527
+ if (!disposed) void openCompanionChat()
528
+ return unlisten
529
+ }))
530
+ }
531
+ register(listen('notification-clicked', (event: { payload: { chatId?: string } }) => openChat(event.payload?.chatId)))
532
+ register(Promise.resolve(window.api?.onProactiveNotification?.((payload: any) => {
533
+ if (disposed) return
534
+ void receiveProactiveNotification(payload, document.hidden || !document.hasFocus(),
535
+ notice => { if (shouldNotify(notificationConfigRef.current?.notificationSettings, 'system', 'inApp')) setNotificationNotice(notice) },
536
+ (body, chatId) => sendNotification(body, chatId, 'system'))
537
+ .catch(reason => setError(errorMessage(reason)))
538
+ })))
539
+ const visible = () => { if (!document.hidden) window.api?.clearAiNotifications?.() }
540
+ document.addEventListener('visibilitychange', visible)
541
+ return () => {
542
+ disposed = true
543
+ cleanups.forEach(unlisten => unlisten())
544
+ window.removeEventListener('mint:notification-click', clicked)
545
+ document.removeEventListener('visibilitychange', visible)
546
+ }
547
+ }, [])
548
+
549
+ const [thinkingExpanded, setThinkingExpanded] = useState<Record<string, boolean>>({})
550
+ const liveThinkingOpenRef = useRef(true)
551
+ const liveExtendedThinkingOpenRef = useRef(true)
552
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(() => window.localStorage.getItem('mint:sidebar-collapsed') === 'true')
553
+ const [toolSurfaces, setToolSurfaces] = useState<ToolSurface[]>([])
554
+ const toolSurfacesRef = useRef(toolSurfaces)
555
+ toolSurfacesRef.current = toolSurfaces
556
+ const workspacePreviewOpener = useRef<ReturnType<typeof createWorkspacePreviewOpener> | null>(null)
557
+ const previewObjectUrls = useRef(new PreviewObjectUrls())
558
+ useEffect(() => {
559
+ previewObjectUrls.current = new PreviewObjectUrls()
560
+ return () => previewObjectUrls.current.dispose()
561
+ }, [])
562
+ useEffect(() => {
563
+ previewObjectUrls.current.update(toolSurfaces.flatMap(surface => surface.kind === 'preview' ? [surface.artifact] : []))
564
+ }, [toolSurfaces])
565
+ const [activeSurfaceId, setActiveSurfaceId] = useState<string | null>(null)
566
+ const [toolsPanelOpen, setToolsPanelOpen] = useState(false)
567
+ const sessionSources = useSessionSources(
568
+ conversationId,
569
+ streamingConversationId === conversationId ? agentProgress : [],
570
+ shouldPollSessionSources(toolsPanelOpen, activeSurfaceId),
571
+ )
572
+ const [modsPanelTarget, setModsPanelTarget] = useState<HTMLDivElement | null>(null)
573
+ const [backgroundPanelTarget, setBackgroundPanelTarget] = useState<HTMLDivElement | null>(null)
574
+ const [backgroundTaskCount, setBackgroundTaskCount] = useState(0)
575
+ const [terminalRightWidth, setTerminalRightWidth] = useState(() => {
576
+ const saved = Number(window.localStorage.getItem('mint:terminal-right-width'))
577
+ return saved >= 280 && saved <= 900 ? saved : 440
578
+ })
579
+ const terminalSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
580
+ const [sidebarWidth, setSidebarWidth] = useState(() => {
581
+ const saved = Number(window.localStorage.getItem('mint:sidebar-width'))
582
+ return saved >= SIDEBAR_MIN_WIDTH && saved <= SIDEBAR_MAX_WIDTH ? saved : SIDEBAR_DEFAULT_WIDTH
583
+ })
584
+
585
+ const [agentMode, setAgentMode] = useState(() => window.localStorage.getItem('mint:agent-mode') === 'true')
586
+ const [planMode, setPlanMode] = useState(() => window.localStorage.getItem('mint:plan-mode') === 'true')
587
+ const [toastMessage, setToastMessage] = useState('')
588
+ const [dashboardDataReady, setDashboardDataReady] = useState(false)
589
+ const initialRouteValidated = useRef(false)
590
+ useEffect(() => {
591
+ if (!dashboardDataReady || initialRouteValidated.current) return
592
+ initialRouteValidated.current = true
593
+ const chatId = getConversationIdFromUrl()
594
+ if (chatId) void savedNavigationRef.current(chatId)
595
+ }, [dashboardDataReady])
596
+ const [startupTimedOut, setStartupTimedOut] = useState(false)
597
+ const [settingsConfig, setSettingsConfig] = useState<any>(null)
598
+ const notificationConfigRef = useRef(settingsConfig)
599
+ notificationConfigRef.current = settingsConfig
600
+ useEffect(() => {
601
+ if (!shouldNotify(settingsConfig?.notificationSettings, 'system', 'inApp')) setNotificationNotice(null)
602
+ if (settingsConfig?.notificationSettings?.enabled === false || settingsConfig?.notificationSettings?.os === false) window.api?.clearAiNotifications?.()
603
+ }, [settingsConfig])
604
+ const availableWorkspacePaths = useMemo(() => workspacePaths(
605
+ recentWorkspacePaths, workspacePath, chatSessions.map(session => session.workspacePath),
606
+ ), [recentWorkspacePaths, workspacePath, chatSessions])
607
+ const chatEnd = useRef<HTMLDivElement | null>(null)
608
+ const lastNativePasteTimeRef = useRef(0)
609
+ const { proactiveSuggestion, dismissProactiveSuggestion, handleProactiveAction } = useProactiveSuggestions((message) => setError(message))
610
+ const startupReady = dashboardDataReady || startupTimedOut
611
+
612
+ const handleUpdateSettingsField = async (field: string, value: any) => {
613
+ const currentConfig = notificationConfigRef.current || settingsConfig || DEFAULT_CONFIG
614
+ const nextConfig = { ...currentConfig, [field]: value }
615
+ notificationConfigRef.current = nextConfig
616
+ setSettingsConfig(nextConfig)
617
+
618
+ if ((window as any).settingsApi) {
619
+ await (window as any).settingsApi.saveSettings(nextConfig)
620
+ } else {
621
+ try {
622
+ await setProfileValue('user-settings', JSON.stringify(nextConfig))
623
+ } catch (e) {
624
+ console.error('Failed to save settings field:', e)
625
+ }
626
+ }
627
+ }
628
+
629
+ const handleConnectPlugin = (_plugin: string) => {}
630
+
631
+ const [isSearchOpen, setIsSearchOpen] = useState(false)
632
+ // A pending `needs_choice` from the shared slash engine — rendered as a chip bar.
633
+ const [pendingSlashChoice, setPendingSlashChoice] = useState<
634
+ { command: string; title: string; options: { label: string; value: string }[] } | null
635
+ >(null)
636
+
637
+ useEffect(() => {
638
+ function handleKeyDown(event: KeyboardEvent) {
639
+ if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
640
+ event.preventDefault()
641
+ setIsSearchOpen((prev) => !prev)
642
+ } else if (event.key === 'Escape') {
643
+ setIsSearchOpen(false)
644
+ }
645
+ }
646
+ window.addEventListener('keydown', handleKeyDown)
647
+ return () => window.removeEventListener('keydown', handleKeyDown)
648
+ }, [])
649
+
650
+
651
+ const refreshHistory = conversationActions.refreshHistory
652
+ const refreshChatSessions = useCallback(async () => {
653
+ try { await conversationActions.refreshSessions() }
654
+ catch (error) { console.error('Failed to refresh chat sessions:', error) }
655
+ }, [conversationActions])
656
+ const loadOlderInteractions = conversationActions.loadOlder
657
+
658
+ const workspaceUpdateQueues = useRef(new Map<string, Promise<void>>())
659
+ const handleUpdateSessionWorkspace = useCallback(async (sessionId: string, targetPath: string | null) => {
660
+ // Preserve selection order if the user switches folders quickly.
661
+ const previous = workspaceUpdateQueues.current.get(sessionId) ?? Promise.resolve()
662
+ const update = previous.catch(() => {}).then(async () => {
663
+ await updateChatSessionWorkspace(sessionId, targetPath)
664
+ await refreshChatSessions()
665
+ })
666
+ workspaceUpdateQueues.current.set(sessionId, update)
667
+ try {
668
+ await update
669
+ } catch (e) {
670
+ console.error('Failed to update session workspace:', e)
671
+ setError(errorMessage(e))
672
+ } finally {
673
+ if (workspaceUpdateQueues.current.get(sessionId) === update) {
674
+ workspaceUpdateQueues.current.delete(sessionId)
675
+ }
676
+ }
677
+ }, [refreshChatSessions])
678
+
679
+ const handleBrowseFolderForMove = useCallback(async () => {
680
+ try {
681
+ const selected = await selectWorkspaceDirectory()
682
+ return selected || null
683
+ } catch {
684
+ return null
685
+ }
686
+ }, [])
687
+
688
+ useEffect(() => {
689
+ Promise.allSettled([
690
+ getRuntimeStatus().then(setStatus),
691
+ refreshHistory(),
692
+ refreshChatSessions(),
693
+ window.settingsApi?.getSettings()
694
+ .then((loaded: any) => {
695
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
696
+ setSettingsConfig(migrated)
697
+ applyThemeStyles({ ...DEFAULT_CONFIG, ...migrated })
698
+ }),
699
+ ]).then((results) => {
700
+ const failure = results.find((result) => result.status === 'rejected')
701
+ if (failure?.status === 'rejected') setError(errorMessage(failure.reason))
702
+ setDashboardDataReady(true)
703
+ })
704
+ const unlistenSpotlight = window.api.onSpotlightToChat((query) => {
705
+ changeView('chat')
706
+ conversationActions.compose(query)
707
+ })
708
+ const handleWindowFocus = () => {
709
+ getRuntimeStatus().then(setStatus).catch(() => {})
710
+ window.settingsApi?.getSettings?.().then((loaded: any) => {
711
+ if (loaded) {
712
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
713
+ setSettingsConfig(migrated)
714
+ applyThemeStyles(migrated)
715
+ }
716
+ }).catch(() => {})
717
+ window.api?.clearAiNotifications?.()
718
+ }
719
+ window.addEventListener('focus', handleWindowFocus)
720
+
721
+ window.api?.onSettingsChanged?.((loaded: any) => {
722
+ const migrated = migrateFontFamily(migrateTypographyScale(loaded))
723
+ setSettingsConfig(migrated)
724
+ applyThemeStyles(migrated)
725
+ getRuntimeStatus().then(setStatus).catch(() => {})
726
+ })
727
+
728
+ const unlistenPromise = listen('tool-approval-requested', (event: { payload: any }) => {
729
+ const chatId = event.payload.chatId as string | undefined
730
+ if (chatId) conversationActions.receiveApproval(chatId, event.payload)
731
+ })
732
+ return () => {
733
+ window.removeEventListener('focus', handleWindowFocus)
734
+ unlistenPromise?.then?.((unlisten) => unlisten?.())
735
+ unlistenSpotlight?.then?.((unlisten) => unlisten?.())
736
+ }
737
+ }, [])
738
+
739
+ useEffect(() => {
740
+ const timer = window.setTimeout(() => setStartupTimedOut(true), 10000)
741
+ return () => window.clearTimeout(timer)
742
+ }, [])
743
+
744
+ useEffect(() => {
745
+ if (view === 'workspace' && !agentMode) updateAgentMode(true)
746
+ }, [view, agentMode])
747
+
748
+ const showToast = (nextMessage: string) => {
749
+ setToastMessage(nextMessage)
750
+ setTimeout(() => setToastMessage((current) => current === nextMessage ? '' : current), 3000)
751
+ }
752
+
753
+ const openMintBrowser = async (url: string, throwOnError = false) => {
754
+ if (!isDesktopApp) {
755
+ window.open(url, '_blank', 'noopener,noreferrer')
756
+ return
757
+ }
758
+ try {
759
+ showToast('Opening Co-browsing browser (Mint Auto)...')
760
+ const { invoke } = await import('@tauri-apps/api/core')
761
+ await invoke('open_mint_browser', { url })
762
+ } catch (error) {
763
+ if (throwOnError) throw error
764
+ showToast(`Could not open browser: ${String(error)}`)
765
+ }
766
+ }
767
+
768
+ const openHtmlFile = async (root: string, relativePath: string, mode: 'external' | 'mint') => {
769
+ if (!isDesktopApp && !['localhost', '127.0.0.1', '[::1]', '::1'].includes(window.location.hostname)) {
770
+ throw new Error('HTML preview requires a browser on the same machine as the Mint backend.')
771
+ }
772
+ const popup = !isDesktopApp && mode === 'external' ? window.open('about:blank', '_blank') : null
773
+ if (popup) popup.opener = null
774
+ try {
775
+ const preview = await workspacePlatform.startHtmlPreview(root, relativePath, !isDesktopApp && mode === 'mint')
776
+ if (mode === 'mint') {
777
+ if (isDesktopApp) await openMintBrowser(preview.url, true)
778
+ } else if (isDesktopApp) {
779
+ const { invoke } = await import('@tauri-apps/api/core')
780
+ await invoke('run_desktop_action', { action: { type: 'open_url', target: preview.url } })
781
+ } else {
782
+ if (!popup) throw new Error('Allow browser popups, then open the HTML file again.')
783
+ popup.location.replace(preview.url)
784
+ }
785
+ showToast('HTML preview is running. Open Tools → Background tasks to view or stop the server.')
786
+ } catch (error) {
787
+ popup?.close()
788
+ throw error
789
+ }
790
+ }
791
+
792
+ const openTerminalSurface = () => {
793
+ setToolsPanelOpen(true)
794
+ const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
795
+ setToolSurfaces((current) => {
796
+ const terminalCount = current.filter((surface) => surface.kind === 'terminal').length
797
+ let baseTitle = 'terminal'
798
+ if (workspacePath && workspacePath.trim()) {
799
+ const cleanPath = workspacePath.trim().replace(/[\\/]+$/, '')
800
+ const folderName = cleanPath.split(/[\\/]/).pop()
801
+ if (folderName) baseTitle = folderName
802
+ }
803
+ const title = terminalCount === 0 ? baseTitle : `${baseTitle} (${terminalCount + 1})`
804
+ const next = { id, kind: 'terminal' as const, title }
805
+ setActiveSurfaceId(id)
806
+ return [...current, next]
807
+ })
808
+ }
809
+
810
+ const openUtilitySurface = (kind: 'sources' | 'mods' | 'background') => {
811
+ setToolsPanelOpen(true)
812
+ setToolSurfaces(current => current.some(surface => surface.id === kind) ? current : [...current, { id: kind, kind, title: kind === 'sources' ? 'Sources' : kind === 'mods' ? 'Mods' : 'Background tasks' }])
813
+ setActiveSurfaceId(kind)
814
+ }
815
+
816
+ const closeToolsPanel = () => {
817
+ setToolsPanelOpen(false)
818
+ requestAnimationFrame(() => document.querySelector<HTMLButtonElement>('.tools-panel-toggle')?.focus())
819
+ }
820
+
821
+ const openNativeBrowser = (url = 'https://www.google.com') => {
822
+ void openMintBrowser(url)
823
+ }
824
+
825
+ const duplicateToolSurface = (id: string) => {
826
+ setToolSurfaces((current) => {
827
+ const source = current.find((surface) => surface.id === id)
828
+ if (!source) return current
829
+ if (source.kind === 'sources' || source.kind === 'mods' || source.kind === 'background') return current
830
+ const duplicateId = `${source.kind}:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
831
+ const duplicate = { ...source, id: duplicateId, title: `${source.title} copy` } as ToolSurface
832
+ setActiveSurfaceId(duplicateId)
833
+ return [...current, duplicate]
834
+ })
835
+ }
836
+
837
+ const renameToolSurface = (id: string) => {
838
+ const source = toolSurfaces.find((surface) => surface.id === id)
839
+ if (!source) return
840
+ const nextTitle = window.prompt('Rename tab', source.title)?.trim()
841
+ if (!nextTitle) return
842
+ setToolSurfaces((current) => current.map((surface) => surface.id === id ? { ...surface, title: nextTitle } : surface))
843
+ }
844
+
845
+ const closeOtherToolSurfaces = (id: string) => {
846
+ setToolSurfaces((current) => current.filter((surface) => surface.id === id))
847
+ setActiveSurfaceId(id)
848
+ }
849
+
850
+ const toggleTerminalSurface = () => {
851
+ setToolSurfaces((current) => {
852
+ const terminal = current.find((surface) => surface.kind === 'terminal')
853
+ if (!terminal) {
854
+ const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
855
+ setActiveSurfaceId(id)
856
+ return [...current, { id, kind: 'terminal' as const, title: 'Terminal' }]
857
+ }
858
+ setActiveSurfaceId((active) => active === terminal.id ? null : terminal.id)
859
+ return current
860
+ })
861
+ }
862
+
863
+ const checkForUpdatesFromMenu = async () => {
864
+ try {
865
+ const update = await window.settingsApi.checkForUpdates()
866
+ showToast(update?.available ? `Mint ${update.version} is available.` : 'Mint is up to date.')
867
+ } catch (error) {
868
+ showToast(`Could not check for updates: ${String(error)}`)
869
+ }
870
+ }
871
+
872
+ const openArtifactSurface = (artifact: import('../../utils/artifacts').ArtifactFile) => {
873
+ if (!previewObjectUrls.current.accept(artifact)) return
874
+ setToolsPanelOpen(true)
875
+ const fileName = artifact.path.replace(/\\/g, '/').split('/').pop() || artifact.path
876
+ artifact = { ...artifact, workspacePath: artifact.workspacePath ?? workspacePath }
877
+ const id = `preview:${JSON.stringify([artifact.workspacePath, artifact.path])}`
878
+ setToolSurfaces((current) => {
879
+ const next = current.filter((surface) => surface.id !== id)
880
+ return [...next, { id, kind: 'preview', title: fileName, artifact }]
881
+ })
882
+ setActiveSurfaceId(id)
883
+ }
884
+
885
+ const openWorkspacePreview = async (root: string, path: string) => {
886
+ setToolsPanelOpen(true)
887
+ workspacePreviewOpener.current ??= createWorkspacePreviewOpener({
888
+ readWorkspaceFile: (file, workspace) => runtimePlatform.readWorkspaceFile(file, workspace),
889
+ readWorkspaceImage: (file, workspace) => runtimePlatform.readWorkspaceImage(file, workspace),
890
+ readWorkspaceMedia: (file, workspace) => runtimePlatform.readWorkspaceMedia(file, workspace),
891
+ startHtmlPreview: (workspace, file) => workspacePlatform.startHtmlPreview(workspace, file),
892
+ selectIfOpen: id => {
893
+ if (!toolSurfacesRef.current.some(surface => surface.id === id)) return false
894
+ setActiveSurfaceId(id)
895
+ return true
896
+ },
897
+ open: openArtifactSurface,
898
+ })
899
+ await workspacePreviewOpener.current(root, path)
900
+ }
901
+
902
+ const openReviewSurface = (review: Extract<ToolSurface, { kind: 'review' }>) => {
903
+ setToolsPanelOpen(true)
904
+ setToolSurfaces((current) => [...current.filter((surface) => surface.id !== review.id), review])
905
+ setActiveSurfaceId(review.id)
906
+ }
907
+
908
+ const openConversationReview = () => {
909
+ const conversationReviewChanges = mergeFileChanges([
910
+ ...interactions.map((interaction) => {
911
+ const interactionId = String(interaction.id)
912
+ return parseFileChangesFromProgress(agentActivitySnapshots[interactionId] ?? interaction.agentActivity ?? [])
913
+ }),
914
+ parseFileChangesFromProgress(agentProgress),
915
+ ])
916
+ setToolsPanelOpen(true)
917
+ setToolSurfaces((current) => {
918
+ const reviewCount = current.filter((surface) => surface.kind === 'review').length
919
+ const id = `review:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
920
+ const title = reviewCount === 0 ? 'Review' : `Review (${reviewCount + 1})`
921
+ const next = {
922
+ id,
923
+ kind: 'review' as const,
924
+ title,
925
+ reviewTitle: 'All conversation changes',
926
+ changes: conversationReviewChanges,
927
+ }
928
+ setActiveSurfaceId(id)
929
+ return [...current, next]
930
+ })
931
+ }
932
+
933
+ const closeToolSurface = (id: string) => {
934
+ setToolSurfaces((current) => {
935
+ const index = current.findIndex((surface) => surface.id === id)
936
+ const next = current.filter((surface) => surface.id !== id)
937
+ setActiveSurfaceId((active) => {
938
+ if (active !== id) return active
939
+ return next[Math.max(0, index - 1)]?.id ?? null
940
+ })
941
+ return next
942
+ })
943
+ }
944
+
945
+ const closeAllToolSurfaces = () => {
946
+ setToolSurfaces([])
947
+ setActiveSurfaceId(null)
948
+ }
949
+
950
+ const toggleSidebar = () => {
951
+ if (window.innerWidth <= 760) {
952
+ setMobileSidebarOpen(false)
953
+ return
954
+ }
955
+ const next = !sidebarCollapsed
956
+ window.localStorage.setItem('mint:sidebar-collapsed', String(next))
957
+ setSidebarCollapsed(next)
958
+ setMobileSidebarOpen(false)
959
+ }
960
+
961
+ const handleSidebarResize = (width: number) => {
962
+ setSidebarWidth(Math.max(SIDEBAR_COLLAPSE_THRESHOLD, Math.min(SIDEBAR_MAX_WIDTH, width)))
963
+ }
964
+
965
+ const handleSidebarResizeEnd = (width: number) => {
966
+ if (width < SIDEBAR_COLLAPSE_THRESHOLD) {
967
+ // Dragged small enough — snap fully closed instead of leaving a sliver.
968
+ setSidebarWidth(SIDEBAR_DEFAULT_WIDTH)
969
+ window.localStorage.setItem('mint:sidebar-width', String(SIDEBAR_DEFAULT_WIDTH))
970
+ window.localStorage.setItem('mint:sidebar-collapsed', 'true')
971
+ setSidebarCollapsed(true)
972
+ return
973
+ }
974
+ const clamped = Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, width))
975
+ setSidebarWidth(clamped)
976
+ window.localStorage.setItem('mint:sidebar-width', String(clamped))
977
+ }
978
+
979
+ const updateAgentMode = (enabled: boolean) => {
980
+ window.localStorage.setItem('mint:agent-mode', String(enabled))
981
+ setAgentMode(enabled)
982
+ }
983
+
984
+ const updatePlanMode = (enabled: boolean) => {
985
+ window.localStorage.setItem('mint:plan-mode', String(enabled))
986
+ setPlanMode(enabled)
987
+ }
988
+
989
+ const updateWorkspacePath = (path: string, persist = true) => {
990
+ const next = path.trim()
991
+ conversationActions.selectWorkspace(next)
992
+ const activeId = conversationActions.getSnapshot().conversationId
993
+ if (persist && isPersistedConversation(activeId, chatSessions)) {
994
+ void handleUpdateSessionWorkspace(activeId, next || null)
995
+ }
996
+ }
997
+
998
+ const handleApproval = conversationActions.resolveApproval
999
+ const handleCancelMessage = conversationActions.cancel
1000
+
1001
+ async function sendPrompt(promptText: string, options: PromptOptions = {}) {
1002
+ if (conversationActions.getSnapshot().sending) return
1003
+ setError('')
1004
+ liveThinkingOpenRef.current = true
1005
+ liveExtendedThinkingOpenRef.current = true
1006
+ setThinkingExpanded(current => ({ ...current, live: true, 'live-extended': true }))
1007
+ await conversationActions.sendPrompt(promptText, { ...options, agentMode, planMode })
1008
+ }
1009
+
1010
+ async function handleSubmit(event: FormEvent<HTMLFormElement>) {
1011
+ event.preventDefault()
1012
+ const trimmed = conversationActions.getSnapshot().message.trim()
1013
+ const currentImages = imageAttachments
1014
+ const currentVideos = videoAttachments
1015
+ const currentDocument = documentAttachment
1016
+ const hasAttachments = currentImages.length > 0 || currentVideos.length > 0 || Boolean(currentDocument)
1017
+ if (!trimmed && !hasAttachments) return
1018
+
1019
+ if (sending) {
1020
+ conversationActions.queuePrompt({
1021
+ text: trimmed,
1022
+ imageAttachments: currentImages.map(img => ({ dataUri: img.dataUri, name: img.name })),
1023
+ videoAttachments: currentVideos.map(vid => ({ dataUri: vid.dataUri, name: vid.name })),
1024
+ documentAttachment: currentDocument,
1025
+ })
1026
+ conversationActions.clearComposer()
1027
+ return
1028
+ }
1029
+
1030
+ if (trimmed.startsWith('/')) {
1031
+ // Shared Rust engine first (mint_core::slash). Anything it doesn't own
1032
+ // comes back as `not_handled` and falls through to the TS processor below.
1033
+ let engineResp: SlashResponse | null = null
1034
+ try {
1035
+ engineResp = await runSlashCommand(trimmed, workspacePath || null, conversationId)
1036
+ } catch {
1037
+ engineResp = null
1038
+ }
1039
+ if (engineResp && engineResp.kind !== 'not_handled') {
1040
+ conversationActions.clearComposer()
1041
+ await handleEngineSlashResponse(trimmed, engineResp)
1042
+ return
1043
+ }
1044
+
1045
+ const slashResult = executeSlashCommand(trimmed)
1046
+
1047
+ if (slashResult.handled) {
1048
+ conversationActions.clearComposer()
1049
+
1050
+ if (slashResult.action === 'open_image_picker') {
1051
+ document.getElementById('vision-file-input')?.click()
1052
+ return
1053
+ } else if (slashResult.action === 'paste_image') {
1054
+ readClipboardImage().then((uri) => {
1055
+ if (uri) conversationActions.attachImage({ dataUri: uri, name: 'Clipboard Image' })
1056
+ }).catch(() => {})
1057
+ return
1058
+ } else if (slashResult.action === 'generate_veo') {
1059
+ changeView('veo')
1060
+ return
1061
+ } else if (slashResult.action === 'open_settings') {
1062
+ window.location.hash = '#/settings'
1063
+ return
1064
+ } else if (slashResult.action === 'resume_session') {
1065
+ const query = (slashResult.payload?.query || '').trim().toLowerCase()
1066
+ const target = query
1067
+ ? chatSessions.find((s) => s.id.toLowerCase() === query || s.id.toLowerCase().includes(query) || s.title.toLowerCase().includes(query))
1068
+ : chatSessions.find((s) => s.id !== conversationId)
1069
+ if (target) {
1070
+ activateConversation(target.id)
1071
+ const targetPath = getCleanPathForView('chat', target.id)
1072
+ window.history.replaceState({}, '', targetPath)
1073
+ const systemMsg = {
1074
+ id: Date.now(),
1075
+ userText: trimmed,
1076
+ aiText: `🔄 Resumed session: **${target.title}** (\`${target.id}\`)`,
1077
+ createdAt: new Date().toISOString(),
1078
+ provider: 'system',
1079
+ model: 'mint-cli',
1080
+ }
1081
+ conversationActions.appendInteraction(systemMsg)
1082
+ } else {
1083
+ const systemMsg = {
1084
+ id: Date.now(),
1085
+ userText: trimmed,
1086
+ aiText: query ? `⚠️ No matching session found for "${query}".` : 'ℹ️ No other sessions found to resume.',
1087
+ createdAt: new Date().toISOString(),
1088
+ provider: 'system',
1089
+ model: 'mint-cli',
1090
+ }
1091
+ conversationActions.appendInteraction(systemMsg)
1092
+ }
1093
+ return
1094
+ }
1095
+
1096
+ if (slashResult.systemText) {
1097
+ const systemMsg = {
1098
+ id: Date.now(),
1099
+ userText: trimmed,
1100
+ aiText: slashResult.systemText,
1101
+ createdAt: new Date().toISOString(),
1102
+ provider: 'system',
1103
+ model: 'mint-cli',
1104
+ }
1105
+ conversationActions.appendInteraction(systemMsg)
1106
+ saveSystemInteraction(conversationId, trimmed, slashResult.systemText || '', 'system', 'mint-cli').catch(() => {})
1107
+ }
1108
+ return
1109
+ }
1110
+ }
1111
+
1112
+ const promptText = trimmed || (
1113
+ currentImages.length > 0 ? (currentImages.length > 1 ? 'Describe these images.' : 'Describe this image.') :
1114
+ currentVideos.length > 0 ? (currentVideos.length > 1 ? 'Describe these videos.' : 'Describe this video.') :
1115
+ 'Summarize this document.'
1116
+ )
1117
+ const mcpNames = Object.entries(settingsConfig?.mcpServers || {})
1118
+ .filter(([, srv]: [string, any]) => srv?.disabled !== true)
1119
+ .map(([name]) => name)
1120
+ const pinnedServer = mcpNames.find((name) =>
1121
+ new RegExp(`(^|\\s)@${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`).test(trimmed)
1122
+ )
1123
+ await sendPrompt(promptText, {
1124
+ imageAttachments: currentImages,
1125
+ videoAttachments: currentVideos,
1126
+ documentAttachment: currentDocument,
1127
+ clearComposer: true,
1128
+ pinnedMcpServer: pinnedServer,
1129
+ forceAgentMode: Boolean(pinnedServer),
1130
+ })
1131
+ }
1132
+
1133
+ async function sendVoiceMessage(transcript: string, audioDataUri?: string | null) {
1134
+ const promptText = transcript.trim() || 'Voice message'
1135
+ if (!promptText || sending) return
1136
+ await sendPrompt(promptText, {
1137
+ audioDataUri,
1138
+ systemInstruction: audioDataUri
1139
+ ? 'The user attached a voice message. Listen to the audio and reply naturally in the same language as the user. Do not mention transcription or this instruction.'
1140
+ : '',
1141
+ })
1142
+ }
1143
+
1144
+ async function selectImage(event: ChangeEvent<HTMLInputElement>) {
1145
+ const file = event.target.files?.[0]
1146
+ if (!file) return
1147
+ try {
1148
+ const objectUrl = createObjectUrlPreview(file).objectUrl
1149
+ const dataUri = await readImage(file)
1150
+ const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
1151
+ conversationActions.attachImage({ dataUri, previewDataUri, objectUrl, name: file.name })
1152
+ } catch (reason) {
1153
+ setError(errorMessage(reason))
1154
+ } finally {
1155
+ event.target.value = ''
1156
+ }
1157
+ }
1158
+ const MAX_VIDEO_BYTES = 25 * 1024 * 1024 // 25 MB
1159
+ async function selectVideo(event: ChangeEvent<HTMLInputElement>) {
1160
+ const file = event.target.files?.[0]
1161
+ if (!file) return
1162
+ try {
1163
+ if (file.size > MAX_VIDEO_BYTES) {
1164
+ setError(`Video is too large. Maximum allowed is ${Math.round(MAX_VIDEO_BYTES / (1024 * 1024))} MB.`)
1165
+ return
1166
+ }
1167
+ const dataUri = await new Promise<string>((resolve, reject) => {
1168
+ const reader = new FileReader()
1169
+ reader.onload = () => resolve(reader.result as string)
1170
+ reader.onerror = reject
1171
+ reader.readAsDataURL(file)
1172
+ })
1173
+ conversationActions.attachVideo({ dataUri, name: file.name })
1174
+ } catch (reason) {
1175
+ setError(errorMessage(reason))
1176
+ } finally {
1177
+ event.target.value = ''
1178
+ }
1179
+ }
1180
+
1181
+ function pasteImage(clipboardData: DataTransfer) {
1182
+ let file: File | null = null
1183
+
1184
+ if (clipboardData.files && clipboardData.files.length > 0) {
1185
+ for (let i = 0; i < clipboardData.files.length; i++) {
1186
+ const f = clipboardData.files[i]
1187
+ if (f && f.type.startsWith('image/')) {
1188
+ file = f
1189
+ break
1190
+ }
1191
+ }
1192
+ }
1193
+
1194
+ if (!file && clipboardData.items && clipboardData.items.length > 0) {
1195
+ for (let i = 0; i < clipboardData.items.length; i++) {
1196
+ const item = clipboardData.items[i]
1197
+ if (item && item.type.startsWith('image/')) {
1198
+ const f = item.getAsFile()
1199
+ if (f) {
1200
+ file = f
1201
+ break
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+
1207
+ if (!file) {
1208
+ if (isDesktopApp) {
1209
+ // Fallback for Tauri desktop app where WebKitGTK/WebView clipboard paste events do not provide file handles
1210
+ const now = Date.now()
1211
+ if (now - lastNativePasteTimeRef.current > 100) {
1212
+ lastNativePasteTimeRef.current = now
1213
+ readClipboardImage().then((dataUri) => {
1214
+ if (dataUri) {
1215
+ const name = 'Pasted image'
1216
+ createTrimmedImagePreview(dataUri)
1217
+ .catch(() => dataUri)
1218
+ .then((previewDataUri) => {
1219
+ conversationActions.attachImage({ dataUri, previewDataUri, name })
1220
+ })
1221
+ }
1222
+ }).catch((err) => {
1223
+ console.warn('Failed to read clipboard image via Tauri API:', err)
1224
+ })
1225
+ }
1226
+ }
1227
+ // No image in the clipboard (e.g. pasting plain text) — nothing more
1228
+ // to do here regardless of platform. Must return before `readImage`
1229
+ // below, which requires a real File and would otherwise be called
1230
+ // with `file` still null on every non-image paste.
1231
+ return false
1232
+ }
1233
+
1234
+
1235
+
1236
+ readImage(file)
1237
+ .then((dataUri) => {
1238
+ const name = file.name && file.name !== 'image.png' ? file.name : 'Pasted image'
1239
+ createTrimmedImagePreview(dataUri)
1240
+ .catch(() => dataUri)
1241
+ .then((previewDataUri) => {
1242
+ conversationActions.attachImage({ dataUri, previewDataUri, name })
1243
+ })
1244
+ })
1245
+ .catch((reason) => setError(errorMessage(reason)))
1246
+ return true
1247
+ }
1248
+
1249
+
1250
+
1251
+ async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
1252
+ const file = event.target.files?.[0]
1253
+ if (!file) return
1254
+ try {
1255
+ if (!isSupportedDocument(file.name)) {
1256
+ throw new Error('Unsupported document type')
1257
+ }
1258
+ conversationActions.attachDocument({
1259
+ filename: file.name,
1260
+ dataUri: await readDocument(file),
1261
+ })
1262
+ } catch (reason) {
1263
+ setError(errorMessage(reason))
1264
+ } finally {
1265
+ event.target.value = ''
1266
+ }
1267
+ }
1268
+
1269
+ function startWebSearch() {
1270
+ updateAgentMode(true)
1271
+ const currentMessage = conversationActions.getSnapshot().message.trim()
1272
+ conversationActions.compose(currentMessage ? `Search web: ${currentMessage}` : 'Search web: ')
1273
+ }
1274
+
1275
+ async function selectWorkspace(path?: string, showWorkspace = false) {
1276
+ const targetView = isDesktopApp && (showWorkspace || view === 'workspace') ? 'workspace' : 'chat'
1277
+ await conversationActions.selectProject(
1278
+ async () => {
1279
+ const selected = path || await selectLinkedFolderPath()
1280
+ if (selected) workspaceListPreferences.restore(selected)
1281
+ return selected
1282
+ },
1283
+ {
1284
+ openSession: (session, nextView) => selectConversation(session.id, session, nextView),
1285
+ newChat: (targetPath, nextView) => clearHistory('New chat', targetPath, nextView),
1286
+ showCurrentView: nextView => changeView(nextView, conversationActions.getSnapshot().conversationId),
1287
+ }, targetView,
1288
+ )
1289
+ }
1290
+
1291
+ function captureScreen() {
1292
+ setScreenCaptureOpen(true)
1293
+ }
1294
+
1295
+ async function startLiveTranslation() {
1296
+ setScreenCaptureOpen(false)
1297
+ await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))
1298
+ try {
1299
+ await window.api.startVision()
1300
+ } catch (reason) {
1301
+ setError(errorMessage(reason))
1302
+ }
1303
+ }
1304
+
1305
+ function attachScreenCapture(image: string) {
1306
+ void createTrimmedImagePreview(image)
1307
+ .catch(() => image)
1308
+ .then((previewDataUri) => {
1309
+ conversationActions.attachImage({ dataUri: image, previewDataUri, name: 'Screen capture' })
1310
+ setScreenCaptureOpen(false)
1311
+ })
1312
+ }
1313
+
1314
+ async function clearHistory(action: 'New chat' | 'Clear history', targetWorkspacePath?: string | null, targetView: 'chat' | 'workspace' = 'chat') {
1315
+ try {
1316
+ if (action === 'New chat') {
1317
+ const next = createConversationId()
1318
+ activateConversation(next)
1319
+ setViewState(targetView)
1320
+
1321
+ if (targetWorkspacePath !== undefined) {
1322
+ updateWorkspacePath(targetWorkspacePath || '')
1323
+ } else {
1324
+ updateWorkspacePath(workspacePath)
1325
+ }
1326
+
1327
+ if (typeof window !== 'undefined') {
1328
+ const currentPath = (window.location.pathname || '').replace(/\/+$/, '')
1329
+ if (currentPath !== '' && currentPath !== '/chat') {
1330
+ window.history.pushState({}, '', '/chat')
1331
+ }
1332
+ }
1333
+ return
1334
+ } else {
1335
+ if (!window.confirm(`${action} will clear the current conversation history. Continue?`)) return
1336
+ await conversationActions.clearHistory()
1337
+ }
1338
+ } catch (reason) {
1339
+ setError(errorMessage(reason))
1340
+ }
1341
+ }
1342
+
1343
+ // Append a synthetic "system" message to the current conversation — used by
1344
+ // the slash-command dispatcher for `/help`-style output and `/cron` etc.
1345
+ // listings. Mirrors the inline push in `handleSubmit`.
1346
+ function pushSystemMessage(userText: string, aiText: string) {
1347
+ const systemMsg = {
1348
+ id: Date.now(),
1349
+ userText,
1350
+ aiText,
1351
+ createdAt: new Date().toISOString(),
1352
+ provider: 'system',
1353
+ model: 'mint-cli',
1354
+ }
1355
+ conversationActions.appendInteraction(systemMsg)
1356
+ saveSystemInteraction(conversationId, userText, aiText, 'system', 'mint-cli').catch(() => {})
1357
+ }
1358
+
1359
+ // Map the shared engine's nav target onto a DashboardView.
1360
+ function slashNavView(target: string): DashboardView {
1361
+ switch (target) {
1362
+ case 'linked_folders':
1363
+ return 'link'
1364
+ case 'cron':
1365
+ return 'cron'
1366
+ case 'skills':
1367
+ return 'skills'
1368
+ case 'plugins':
1369
+ return 'plugins'
1370
+ case 'mcp':
1371
+ return 'mcp'
1372
+ case 'veo':
1373
+ return 'veo'
1374
+ default:
1375
+ return 'chat'
1376
+ }
1377
+ }
1378
+
1379
+ // Act on a response from the shared Rust slash engine (mint_core::slash).
1380
+ async function handleEngineSlashResponse(userText: string, resp: SlashResponse) {
1381
+ switch (resp.kind) {
1382
+ case 'message':
1383
+ pushSystemMessage(userText.startsWith('/mods set ') ? '/mods' : userText, resp.markdown)
1384
+ if (userText.startsWith('/mods set ')) {
1385
+ requestAnimationFrame(() => document.getElementById('chat-input')?.focus())
1386
+ }
1387
+ break
1388
+ case 'applied': {
1389
+ const wsChange = resp.effects.find((e) => e.kind === 'workspace_changed') as
1390
+ | { kind: 'workspace_changed'; path: string }
1391
+ | undefined
1392
+ if (wsChange) updateWorkspacePath(wsChange.path)
1393
+ if (resp.effects.some((effect) => effect.kind === 'workspace_files_changed')) {
1394
+ setWorkspaceRefreshRevision((revision) => revision + 1)
1395
+ }
1396
+ const planModeChange = resp.effects.find((e) => e.kind === 'plan_mode_changed') as
1397
+ | { kind: 'plan_mode_changed'; enabled: boolean }
1398
+ | undefined
1399
+ if (planModeChange) setPlanMode(planModeChange.enabled)
1400
+ const providerChange = resp.effects.find((e) => e.kind === 'provider_changed') as
1401
+ | { kind: 'provider_changed'; display: string }
1402
+ | undefined
1403
+ if (providerChange) {
1404
+ // Render the same "✨ Gemini • model" chip the GUI's own /models shows.
1405
+ // The engine already logged the `provider_change` row into the shared
1406
+ // "cli" conversation, so this is just the immediate local echo.
1407
+ conversationActions.appendInteraction({
1408
+ id: Date.now(),
1409
+ userText: providerChange.display,
1410
+ aiText: '',
1411
+ createdAt: new Date().toISOString(),
1412
+ provider: 'system',
1413
+ model: 'provider_change',
1414
+ })
1415
+ }
1416
+ if (resp.effects.some((e) => e.kind === 'history_cleared')) {
1417
+ await conversationActions.clearHistory().catch(() => {})
1418
+ }
1419
+ if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1420
+ if (resp.effects.some((e) => e.kind !== 'history_cleared' && e.kind !== 'workspace_changed')) {
1421
+ // Desktop also gets a `settings-changed` push; refetch anyway so web stays in sync.
1422
+ ;(window as any).settingsApi?.getSettings?.().then((c: any) => c && setSettingsConfig(c)).catch(() => {})
1423
+ getRuntimeStatus().then(setStatus).catch(() => {})
1424
+ }
1425
+ break
1426
+ }
1427
+ case 'navigate':
1428
+ if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1429
+ changeView(slashNavView(resp.target as any))
1430
+ break
1431
+ case 'forward_to_agent':
1432
+ if (resp.plan_mode) setPlanMode(true)
1433
+ if (resp.agent_mode) setAgentMode(true)
1434
+ await sendPrompt(resp.prompt, { clearComposer: true })
1435
+ break
1436
+ case 'needs_choice':
1437
+ setPendingSlashChoice({ command: resp.command, title: resp.title, options: resp.options })
1438
+ break
1439
+ case 'exit':
1440
+ break
1441
+ }
1442
+ }
1443
+
1444
+ function handleThinkingExpandedChange(key: string, open: boolean) {
1445
+ if (key === 'live') liveThinkingOpenRef.current = open
1446
+ if (key === 'live-extended') liveExtendedThinkingOpenRef.current = open
1447
+ setThinkingExpanded((current) => ({ ...current, [key]: open }))
1448
+ }
1449
+
1450
+ async function selectConversation(id: string, selectedSession?: ChatSession, targetView: 'chat' | 'workspace' = 'chat') {
1451
+ savedNavigationEpoch.current += 1
1452
+ window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, id)
1453
+ touchActiveTimestamp()
1454
+ activateConversation(id)
1455
+ changeView(targetView, id)
1456
+ const session = selectedSession ?? chatSessions.find((item) => item.id === id)
1457
+ if (session) {
1458
+ if (session.workspacePath) {
1459
+ if (session.workspacePath !== workspacePath) {
1460
+ updateWorkspacePath(session.workspacePath, false)
1461
+ }
1462
+ } else {
1463
+ if (workspacePath) {
1464
+ updateWorkspacePath('', false)
1465
+ }
1466
+ }
1467
+ }
1468
+ try {
1469
+ await conversationActions.refreshHistory()
1470
+ } catch (reason) {
1471
+ setError(errorMessage(reason))
1472
+ }
1473
+ }
1474
+
1475
+ const handleNewChatInProject = (targetPath: string) => {
1476
+ void clearHistory('New chat', targetPath)
1477
+ }
1478
+
1479
+ async function deleteConversation(id: string) {
1480
+ if (id === 'cli') {
1481
+ if (!window.confirm('Clear history for default CLI session? This cannot be undone.')) return
1482
+ try {
1483
+ await clearChatHistory('cli')
1484
+ await refreshHistory()
1485
+ await refreshChatSessions()
1486
+ showToast('CLI session history cleared')
1487
+ } catch (reason) {
1488
+ setError(errorMessage(reason))
1489
+ }
1490
+ return
1491
+ }
1492
+ const session = chatSessions.find((item) => item.id === id)
1493
+ const title = session?.title || 'this chat'
1494
+ if (!window.confirm(`Delete "${title}"? This will remove the conversation and its messages.`)) return
1495
+
1496
+ try {
1497
+ await deleteChatSession(id)
1498
+ const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && !item.id.startsWith('cli') && item.id !== 'conversation-default')
1499
+ const currentChatId = conversationActions.getSnapshot().conversationId
1500
+ const nextActive = id === currentChatId
1501
+ ? (remaining[0]?.id ?? createConversationId())
1502
+ : currentChatId
1503
+
1504
+ if (nextActive !== currentChatId) {
1505
+ if (remaining.length > 0 && remaining[0]?.id) {
1506
+ window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, nextActive)
1507
+ if (typeof window !== 'undefined') {
1508
+ window.history.replaceState({}, '', `/chat/${encodeURIComponent(nextActive)}`)
1509
+ }
1510
+ } else {
1511
+ window.localStorage.removeItem(ACTIVE_CONVERSATION_ID_KEY)
1512
+ if (typeof window !== 'undefined') {
1513
+ window.history.replaceState({}, '', '/chat')
1514
+ }
1515
+ }
1516
+ activateConversation(nextActive)
1517
+ await conversationActions.refreshHistory()
1518
+ }
1519
+
1520
+ await refreshChatSessions()
1521
+ } catch (reason) {
1522
+ setError(errorMessage(reason))
1523
+ }
1524
+ }
1525
+
1526
+ async function renameConversation(id: string, newTitle: string) {
1527
+ if (!newTitle.trim()) return
1528
+ try {
1529
+ await renameChatSession(id, newTitle.trim())
1530
+ await refreshChatSessions()
1531
+ } catch (reason) {
1532
+ setError(errorMessage(reason))
1533
+ }
1534
+ }
1535
+
1536
+ function formatProviderChangeText(provider: string, model: string) {
1537
+ let providerName = provider
1538
+ if (provider === 'gemini') providerName = 'Gemini'
1539
+ else if (provider === 'openai') providerName = 'OpenAI'
1540
+ else if (provider === 'openrouter') providerName = 'OpenRouter'
1541
+ else if (provider === 'deepseek') providerName = 'DeepSeek'
1542
+ else if (provider === 'anthropic') providerName = 'Claude'
1543
+ else if (provider === 'huggingface') providerName = 'HF'
1544
+ else if (provider === 'local_openai') providerName = 'Local'
1545
+ else if (provider === 'ollama') providerName = 'Ollama'
1546
+ else if (provider.startsWith('custom:')) {
1547
+ const id = provider.replace(/^custom:/, '')
1548
+ const cp = (settingsConfig?.customProviders ?? []).find((p: any) => p.id === id)
1549
+ providerName = cp?.displayName || id
1550
+ }
1551
+
1552
+ if (providerName && providerName === provider) {
1553
+ providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
1554
+ }
1555
+ return [providerName, model].filter(Boolean).join(' • ')
1556
+ }
1557
+
1558
+ function getActiveModelName(config: any, provider: string) {
1559
+ switch (provider) {
1560
+ case 'gemini': return config.geminiModel || 'gemini-1.5-flash'
1561
+ case 'openai': return config.openaiModel || 'gpt-4o'
1562
+ case 'openrouter': return config.openrouterModel || 'anthropic/claude-3.5-sonnet'
1563
+ case 'deepseek': return config.deepseekModel || 'deepseek-chat'
1564
+ case 'anthropic': return config.anthropicModel || 'claude-3-5-sonnet-20240620'
1565
+ case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
1566
+ case 'local_openai': return config.localModelName || 'llama3'
1567
+ case 'ollama': return config.ollamaModel || 'llama3:latest'
1568
+ default: {
1569
+ if (provider.startsWith('custom:')) {
1570
+ const id = provider.replace(/^custom:/, '')
1571
+ const cp = (config.customProviders ?? []).find((p: any) => p.id === id)
1572
+ return (config.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
1573
+ }
1574
+ return ''
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ async function changeProvider(provider: string) {
1580
+ try {
1581
+ const config = await window.settingsApi.getSettings()
1582
+ if (config.aiProvider === provider) return
1583
+ config.aiProvider = provider
1584
+ await window.settingsApi.saveSettings(config)
1585
+ setSettingsConfig(config)
1586
+ setStatus(await getRuntimeStatus())
1587
+
1588
+ // Record system event in chat history if conversation already has messages
1589
+ const activeModel = getActiveModelName(config, provider)
1590
+ const displayName = formatProviderChangeText(provider, activeModel)
1591
+ if (interactions.length > 0) {
1592
+ await saveSystemInteraction(conversationId, displayName, '', 'system', 'provider_change')
1593
+ await refreshHistory()
1594
+ }
1595
+ } catch (reason) {
1596
+ setError(errorMessage(reason))
1597
+ }
1598
+ }
1599
+
1600
+ async function changeModel(modelName: string) {
1601
+ try {
1602
+ const config = await window.settingsApi.getSettings()
1603
+ const provider = config.aiProvider
1604
+ const currentModel = getActiveModelName(config, provider)
1605
+ if (currentModel === modelName) return
1606
+ if (provider === 'gemini') {
1607
+ config.geminiModel = modelName
1608
+ } else if (provider === 'openai') {
1609
+ config.openaiModel = modelName
1610
+ } else if (provider === 'openrouter') {
1611
+ config.openrouterModel = modelName
1612
+ } else if (provider === 'deepseek') {
1613
+ config.deepseekModel = modelName
1614
+ } else if (provider === 'anthropic') {
1615
+ config.anthropicModel = modelName
1616
+ } else if (provider === 'huggingface') {
1617
+ config.hfModel = modelName
1618
+ } else if (provider === 'local_openai') {
1619
+ config.localModelName = modelName
1620
+ } else if (provider === 'ollama') {
1621
+ config.ollamaModel = modelName
1622
+ } else if (provider.startsWith('custom:')) {
1623
+ const id = provider.replace(/^custom:/, '')
1624
+ config.customModelSelections = {
1625
+ ...(config.customModelSelections ?? {}),
1626
+ [id]: modelName
1627
+ }
1628
+ }
1629
+ await window.settingsApi.saveSettings(config)
1630
+ setSettingsConfig(config)
1631
+ setStatus(await getRuntimeStatus())
1632
+
1633
+ // Record system event in chat history if conversation already has messages
1634
+ const displayName = formatProviderChangeText(provider, modelName)
1635
+ if (interactions.length > 0) {
1636
+ await saveSystemInteraction(conversationId, displayName, '', 'system', 'provider_change')
1637
+ await refreshHistory()
1638
+ }
1639
+ } catch (reason) {
1640
+ setError(errorMessage(reason))
1641
+ }
1642
+ }
1643
+
1644
+ async function changeProviderAndModel(provider: string, modelName: string) {
1645
+ try {
1646
+ const config = await window.settingsApi.getSettings()
1647
+ config.aiProvider = provider
1648
+ if (provider === 'gemini') {
1649
+ config.geminiModel = modelName
1650
+ } else if (provider === 'openai') {
1651
+ config.openaiModel = modelName
1652
+ } else if (provider === 'openrouter') {
1653
+ config.openrouterModel = modelName
1654
+ } else if (provider === 'deepseek') {
1655
+ config.deepseekModel = modelName
1656
+ } else if (provider === 'anthropic') {
1657
+ config.anthropicModel = modelName
1658
+ } else if (provider === 'huggingface') {
1659
+ config.hfModel = modelName
1660
+ } else if (provider === 'local_openai') {
1661
+ config.localModelName = modelName
1662
+ } else if (provider === 'ollama') {
1663
+ config.ollamaModel = modelName
1664
+ } else if (provider.startsWith('custom:')) {
1665
+ const id = provider.replace(/^custom:/, '')
1666
+ config.customModelSelections = {
1667
+ ...(config.customModelSelections ?? {}),
1668
+ [id]: modelName
1669
+ }
1670
+ }
1671
+ await window.settingsApi.saveSettings(config)
1672
+ setSettingsConfig(config)
1673
+ setStatus(await getRuntimeStatus())
1674
+
1675
+ // Record system event in chat history if conversation already has messages
1676
+ const displayName = formatProviderChangeText(provider, modelName)
1677
+ if (interactions.length > 0) {
1678
+ await saveSystemInteraction(conversationId, displayName, '', 'system', 'provider_change')
1679
+ await refreshHistory()
1680
+ }
1681
+ } catch (reason) {
1682
+ setError(errorMessage(reason))
1683
+ }
1684
+ }
1685
+
1686
+ async function changeGeminiLiveVoice(voiceName: string) {
1687
+ try {
1688
+ const config = await window.settingsApi.getSettings()
1689
+ if (config.geminiLiveVoice === voiceName) return
1690
+ config.geminiLiveVoice = voiceName
1691
+ await window.settingsApi.saveSettings(config)
1692
+ setSettingsConfig(config)
1693
+ } catch (reason) {
1694
+ setError(errorMessage(reason))
1695
+ }
1696
+ }
1697
+
1698
+ const chatConversation: ConversationViewModel = {
1699
+ interactions,
1700
+ hasOlder: hasOlderInteractions,
1701
+ sendingInteractionId: streamingConversationId === conversationId ? activeTurnId : null,
1702
+ sending: sending && streamingConversationId === conversationId,
1703
+ sendingMessage: streamingConversationId === conversationId ? sendingMessage : '',
1704
+ sendingImageCount: streamingConversationId === conversationId ? sendingImageCount : 0,
1705
+ sendingVideoCount: streamingConversationId === conversationId ? sendingVideoCount : 0,
1706
+ streamedReply: streamingConversationId === conversationId ? streamedReply : '',
1707
+ streamedResponse: streamingConversationId === conversationId ? streamedResponse : null,
1708
+ agentProgress: streamingConversationId === conversationId ? agentProgress : [],
1709
+ agentActivitySnapshots,
1710
+ thinkingExpanded,
1711
+ message,
1712
+ imageAttachments,
1713
+ videoAttachments,
1714
+ documentName: documentAttachment?.filename ?? '',
1715
+ pendingApproval: pendingApprovals[conversationId] ?? null,
1716
+ agentMode,
1717
+ planMode,
1718
+ status,
1719
+ workspacePath,
1720
+ chatId: conversationId,
1721
+ recentWorkspacePaths: availableWorkspacePaths,
1722
+ chatEnd,
1723
+ welcomeInteraction: MOCK_WELCOME_INTERACTION,
1724
+ settingsConfig,
1725
+ slashChoice: pendingSlashChoice,
1726
+ isCliSession: conversationId.startsWith('cli') || conversationId === 'cli',
1727
+ cliSessionId: conversationId.startsWith('cli') ? conversationId : undefined,
1728
+ conversationTitle: chatSessions.find((session) => session.id === conversationId)?.title,
1729
+ queuedPrompts: queuedPrompts ?? [],
1730
+ queuePaused: queuePaused ?? false,
1731
+ }
1732
+ const chatActions: ConversationActions = {
1733
+ onSelectSlashChoice: (value) => {
1734
+ if (!pendingSlashChoice) return
1735
+ const next = `${pendingSlashChoice.command} ${value}`
1736
+ setPendingSlashChoice(null)
1737
+ requestAnimationFrame(() => document.getElementById('chat-input')?.focus())
1738
+ runSlashCommand(next, workspacePath || null, conversationId)
1739
+ .then((response) => handleEngineSlashResponse(next, response))
1740
+ .catch(() => {})
1741
+ },
1742
+ onDismissSlashChoice: () => setPendingSlashChoice(null),
1743
+ onLoadOlder: loadOlderInteractions,
1744
+ onThinkingExpandedChange: handleThinkingExpandedChange,
1745
+ onSubmit: handleSubmit,
1746
+ onSelectImage: selectImage,
1747
+ onSelectVideo: selectVideo,
1748
+ onSelectDocument: selectDocument,
1749
+ onPasteImage: pasteImage,
1750
+ onSetMessage: conversationActions.compose,
1751
+ subscribeMessage: conversationActions.subscribe,
1752
+ getMessage: () => conversationActions.getSnapshot().message,
1753
+ onSendVoiceMessage: sendVoiceMessage,
1754
+ onRemoveImage: conversationActions.removeImage,
1755
+ onRemoveVideo: conversationActions.removeVideo,
1756
+ onRemoveDocument: () => conversationActions.attachDocument(null),
1757
+ onStartWebSearch: startWebSearch,
1758
+ onCaptureScreen: captureScreen,
1759
+ onSetAgentMode: updateAgentMode,
1760
+ onSetPlanMode: isDesktopApp ? updatePlanMode : undefined,
1761
+ onSetProvider: changeProvider,
1762
+ onSelectWorkspace: selectWorkspace,
1763
+ onWorkspaceChanged: () => setWorkspaceRefreshRevision((revision) => revision + 1),
1764
+ onApproval: handleApproval,
1765
+ onUpdateSettings: (updated) => setSettingsConfig(updated),
1766
+ onSetModel: changeModel,
1767
+ onSelectModelAndProvider: changeProviderAndModel,
1768
+ onCancelMessage: handleCancelMessage,
1769
+ onClearMessages: () => clearHistory('Clear history'),
1770
+ onSetGeminiLiveVoice: changeGeminiLiveVoice,
1771
+ onToggleMobileSidebar: () => setMobileSidebarOpen(!mobileSidebarOpen),
1772
+ onBackToCode: () => changeView('code'),
1773
+ onOpenArtifact: openArtifactSurface,
1774
+ onOpenReview: isDesktopApp ? openReviewSurface : undefined,
1775
+ onRemoveQueuedPrompt: conversationActions.removeQueuedPrompt,
1776
+ onEditQueuedPrompt: conversationActions.editQueuedPrompt,
1777
+ onClearQueuedPrompts: conversationActions.clearQueuedPrompts,
1778
+ onSteerQueuedPrompt: conversationActions.steerQueuedPrompt,
1779
+ onResumeQueue: conversationActions.resumeQueue,
1780
+ }
1781
+
1782
+ const stableToggleSidebar = useLatestCallback(toggleSidebar)
1783
+ const stableSidebarResize = useLatestCallback(handleSidebarResize)
1784
+ const stableSidebarResizeEnd = useLatestCallback(handleSidebarResizeEnd)
1785
+ const stableClearHistory = useLatestCallback(clearHistory)
1786
+ const stableSelectConversation = useLatestCallback(selectConversation)
1787
+ const stableDeleteConversation = useLatestCallback(deleteConversation)
1788
+ const stableRenameConversation = useLatestCallback(renameConversation)
1789
+ const stableChangeView = useLatestCallback(changeView)
1790
+ const stableBrowseFolderForMove = useLatestCallback(handleBrowseFolderForMove)
1791
+ const stableNewChatInProject = useLatestCallback(handleNewChatInProject)
1792
+
1793
+ return (
1794
+ <div className={`app-container ${startupReady ? '' : 'is-loading'}`}>
1795
+ {screenCaptureOpen && (
1796
+ <ScreenCaptureDialog
1797
+ onClose={() => setScreenCaptureOpen(false)}
1798
+ onAttach={attachScreenCapture}
1799
+ capture={() => captureScreenForChat(isDesktopApp)}
1800
+ onLiveTranslate={isDesktopApp ? () => { void startLiveTranslation() } : undefined}
1801
+ />
1802
+ )}
1803
+ {isDesktopApp && (
1804
+ <DesktopTitlebar
1805
+ sidebarCollapsed={sidebarCollapsed}
1806
+ onToggleSidebar={toggleSidebar}
1807
+ onNewChat={() => clearHistory('New chat')}
1808
+ onOpenWorkspace={() => selectWorkspace(undefined, true)}
1809
+ onShowWorkspace={() => { setToolsPanelOpen(false); changeView('workspace') }}
1810
+ onOpenTerminal={openTerminalSurface}
1811
+ onToggleTerminal={toggleTerminalSurface}
1812
+ onOpenBrowser={() => openNativeBrowser()}
1813
+ toolsPanelOpen={toolsPanelOpen}
1814
+ backgroundTaskCount={backgroundTaskCount}
1815
+ onToggleToolsPanel={() => setToolsPanelOpen((open) => !open)}
1816
+ onOpenSettings={() => changeView('settings')}
1817
+ onCheckForUpdates={checkForUpdatesFromMenu}
1818
+ onShowAbout={() => showToast('Mint Agent — AI workspace')}
1819
+ workspacePath={workspacePath}
1820
+ workspaceActive={view === 'workspace' && !toolsPanelOpen}
1821
+ workspaceRevision={workspaceRefreshRevision}
1822
+ sending={sending}
1823
+ onWorkspaceUndoError={showToast}
1824
+ onRefreshWorkspace={() => setWorkspaceRefreshRevision((revision) => revision + 1)}
1825
+ />
1826
+ )}
1827
+ {notificationNotice && (
1828
+ <section className="mint-notification-notice" role="status" data-kind={notificationNotice.type}>
1829
+ <span>{notificationNotice.message}</span>
1830
+ {notificationNotice.chatId && <button type="button" onClick={() => { void notificationNavigationRef.current(notificationNotice.chatId!); setNotificationNotice(null) }}>Open chat</button>}
1831
+ <button type="button" aria-label="Dismiss notification" onClick={() => setNotificationNotice(null)}>×</button>
1832
+ </section>
1833
+ )}
1834
+ {error && <ErrorNotice key={error} message={error} onDismiss={() => setError('')} />}
1835
+ <div
1836
+ className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''} ${toolsPanelOpen ? 'tool-surface-active' : ''}`}
1837
+ style={{
1838
+ '--sidebar-expanded-width': `${sidebarWidth}px`,
1839
+ '--terminal-dock-size': `${terminalRightWidth}px`,
1840
+ // A terminal shown in the right tool surface resizes the surface
1841
+ // itself (and therefore the remaining chat area), not just its
1842
+ // inner xterm viewport.
1843
+ '--tool-surface-width': `${terminalRightWidth}px`,
1844
+ } as CSSProperties}
1845
+ >
1846
+ {!isDesktopApp && view !== 'chat' && (
1847
+ <button
1848
+ type="button"
1849
+ className="web-mobile-nav-btn"
1850
+ aria-label="Open navigation menu"
1851
+ aria-expanded={mobileSidebarOpen}
1852
+ onClick={() => setMobileSidebarOpen(true)}
1853
+ >
1854
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
1855
+ <line x1="3" y1="6" x2="21" y2="6" />
1856
+ <line x1="3" y1="12" x2="21" y2="12" />
1857
+ <line x1="3" y1="18" x2="21" y2="18" />
1858
+ </svg>
1859
+ </button>
1860
+ )}
1861
+ {mobileSidebarOpen && (
1862
+ <button
1863
+ type="button"
1864
+ aria-label="Close navigation menu"
1865
+ className="sidebar-backdrop"
1866
+ onClick={() => setMobileSidebarOpen(false)}
1867
+ style={{
1868
+ position: 'fixed',
1869
+ top: 0,
1870
+ left: 0,
1871
+ right: 0,
1872
+ bottom: 0,
1873
+ background: 'rgba(0, 0, 0, 0.5)',
1874
+ backdropFilter: 'blur(4px)',
1875
+ zIndex: 9998,
1876
+ }}
1877
+ />
1878
+ )}
1879
+ {toastMessage && <div role="status" style={{ position: 'fixed', bottom: 22, right: 22, zIndex: 10000, padding: '12px 18px', borderRadius: 10, background: 'var(--bg-surface)', color: 'var(--text-main)', border: '1px solid var(--border-color)' }}>{toastMessage}</div>}
1880
+ <DashboardSidebar
1881
+ view={view}
1882
+ sidebarCollapsed={sidebarCollapsed}
1883
+ sending={sending}
1884
+ onToggleSidebar={stableToggleSidebar}
1885
+ onSidebarResize={stableSidebarResize}
1886
+ onSidebarResizeEnd={stableSidebarResizeEnd}
1887
+ onClearHistory={stableClearHistory}
1888
+ chatSessions={chatSessions}
1889
+ activeConversationId={conversationId}
1890
+ onSelectConversation={stableSelectConversation}
1891
+ onDeleteConversation={stableDeleteConversation}
1892
+ onRenameConversation={stableRenameConversation}
1893
+ onSetView={stableChangeView}
1894
+ isSearchOpen={isSearchOpen}
1895
+ onSetSearchOpen={setIsSearchOpen}
1896
+ showWorkspaceTab={isDesktopApp}
1897
+ promoteMediaStudios={!isDesktopApp}
1898
+ activeWorkspacePath={workspacePath}
1899
+ recentWorkspacePaths={recentWorkspacePaths}
1900
+ onBrowseFolder={isDesktopApp ? stableBrowseFolderForMove : undefined}
1901
+ onUpdateSessionWorkspace={handleUpdateSessionWorkspace}
1902
+ onNewChatInProject={stableNewChatInProject}
1903
+ />
1904
+ <main className={`assistant-workspace ${view === 'workspace' ? '' : 'chat-only'} ${view === 'workspace' ? 'workspace-open' : ''}`} style={(view === 'skills' || view === 'mcp' || view === 'plugins' || view === 'cron' || view === 'link' || view === 'pictures' || view === 'imagine' || view === 'veo' || view === 'code') ? { display: 'none' } : undefined}>
1905
+ {proactiveSuggestion && (
1906
+ <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
1907
+ <div className="proactive-header">
1908
+ <span className="proactive-icon">✨</span>
1909
+ <div className="proactive-message">{proactiveSuggestion.message}</div>
1910
+ <button className="proactive-dismiss-btn" onClick={dismissProactiveSuggestion}>
1911
+ Dismiss
1912
+ </button>
1913
+ </div>
1914
+ <div className="proactive-chips">
1915
+ {proactiveSuggestion.suggestions?.map((sug: any, i: number) => (
1916
+ <button
1917
+ key={i}
1918
+ className="suggestion-chip"
1919
+ onClick={() => handleProactiveAction(sug.action)}
1920
+ >
1921
+ {sug.label}
1922
+ </button>
1923
+ ))}
1924
+ </div>
1925
+ </div>
1926
+ )}
1927
+ {view === 'workspace' && (
1928
+ <Suspense fallback={<LazyPanelFallback />}>
1929
+ <WorkspacePanel
1930
+ key={workspacePath}
1931
+ agentMode={agentMode}
1932
+ sending={sending}
1933
+ workspacePath={workspacePath}
1934
+ onEnableAgentMode={() => updateAgentMode(true)}
1935
+ onSetMessage={conversationActions.compose}
1936
+ onWorkspaceReady={updateWorkspacePath}
1937
+ refreshRevision={workspaceRefreshRevision}
1938
+ onOpenHtml={openHtmlFile}
1939
+ onOpenPreview={openWorkspacePreview}
1940
+ />
1941
+ </Suspense>
1942
+ )}
1943
+ <ChatPanel conversation={chatConversation} actions={chatActions} tools={{
1944
+ modsPanelTarget, onOpenMods: () => openUtilitySurface('mods'),
1945
+ open: toolsPanelOpen, backgroundTaskCount,
1946
+ onToggle: isDesktopApp ? undefined : () => setToolsPanelOpen(open => !open),
1947
+ }} />
1948
+ </main>
1949
+ {toolsPanelOpen && (
1950
+ <Suspense fallback={<LazyPanelFallback />}>
1951
+ <ToolSurfacePage
1952
+ surfaces={toolSurfaces}
1953
+ activeSurfaceId={activeSurfaceId}
1954
+ workspacePath={workspacePath}
1955
+ desktopTools={isDesktopApp}
1956
+ onDropWorkspaceFile={isDesktopApp ? file => openWorkspacePreview(file.root, file.path) : undefined}
1957
+ backgroundTaskCount={backgroundTaskCount}
1958
+ sourceCount={sessionSources.sources.length}
1959
+ onOpenSources={() => openUtilitySurface('sources')}
1960
+ sourcesContent={<SessionSources key={conversationId} {...sessionSources} onRetry={sessionSources.retry} onOpenLibrary={url => {
1961
+ window.history.pushState({}, '', url)
1962
+ window.dispatchEvent(new PopStateEvent('popstate'))
1963
+ setToolsPanelOpen(false)
1964
+ }} onOpenArtifact={openArtifactSurface} />}
1965
+ onOpenMods={() => openUtilitySurface('mods')}
1966
+ onOpenBackgroundTasks={() => openUtilitySurface('background')}
1967
+ onModsPanelTarget={setModsPanelTarget}
1968
+ onBackgroundPanelTarget={setBackgroundPanelTarget}
1969
+ terminalSize={terminalRightWidth}
1970
+ onSelect={setActiveSurfaceId}
1971
+ onClose={closeToolSurface}
1972
+ onCloseAll={closeAllToolSurfaces}
1973
+ onDuplicate={duplicateToolSurface}
1974
+ onRename={renameToolSurface}
1975
+ onCloseOthers={closeOtherToolSurfaces}
1976
+ onOpenTerminal={openTerminalSurface}
1977
+ onOpenBrowser={() => openNativeBrowser()}
1978
+ onOpenReview={openConversationReview}
1979
+ onOpenFiles={() => {
1980
+ setActiveSurfaceId(null)
1981
+ setToolsPanelOpen(false)
1982
+ changeView('workspace')
1983
+ }}
1984
+ onOpenSideChat={() => { setActiveSurfaceId(null); setToolsPanelOpen(false) }}
1985
+ onClosePanel={closeToolsPanel}
1986
+ onResizeTerminal={(size) => {
1987
+ setTerminalRightWidth(size)
1988
+ if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
1989
+ terminalSaveTimeoutRef.current = setTimeout(() => {
1990
+ window.localStorage.setItem('mint:terminal-right-width', String(size))
1991
+ }, 250)
1992
+ }}
1993
+ onResizePanel={(size) => {
1994
+ setTerminalRightWidth(size)
1995
+ if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
1996
+ terminalSaveTimeoutRef.current = setTimeout(() => {
1997
+ window.localStorage.setItem('mint:terminal-right-width', String(size))
1998
+ }, 250)
1999
+ }}
2000
+ />
2001
+ </Suspense>
2002
+ )}
2003
+ <BackgroundTerminals workspacePath={workspacePath} panelTarget={backgroundPanelTarget} onCountChange={setBackgroundTaskCount} />
2004
+ {view === 'skills' && (
2005
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2006
+ <Suspense fallback={<LazyPanelFallback />}>
2007
+ <SkillsView
2008
+ listSkills={listLearnedSkills}
2009
+ addSkill={addLearnedSkill}
2010
+ deleteSkill={deleteLearnedSkill}
2011
+ workspacePath={workspacePath}
2012
+ />
2013
+ </Suspense>
2014
+ </div>
2015
+ )}
2016
+ {view === 'mcp' && (
2017
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2018
+ <Suspense fallback={<LazyPanelFallback />}>
2019
+ <McpServersView
2020
+ config={settingsConfig || DEFAULT_CONFIG}
2021
+ updateField={handleUpdateSettingsField}
2022
+ detectTools={detectSystemTools}
2023
+ onReauth={reauthMcpServer}
2024
+ listServerTools={listMcpServerTools}
2025
+ />
2026
+ </Suspense>
2027
+ </div>
2028
+ )}
2029
+ {view === 'plugins' && (
2030
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2031
+ <Suspense fallback={<LazyPanelFallback />}>
2032
+ <PluginsView
2033
+ config={settingsConfig || DEFAULT_CONFIG}
2034
+ updateField={handleUpdateSettingsField}
2035
+ handleConnectPlugin={handleConnectPlugin}
2036
+ />
2037
+ </Suspense>
2038
+ </div>
2039
+ )}
2040
+ {view === 'cron' && (
2041
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2042
+ <Suspense fallback={<LazyPanelFallback />}>
2043
+ <ScheduledTasksView
2044
+ listCronJobs={listCronJobs}
2045
+ addCronJob={addCronJob}
2046
+ removeCronJob={removeCronJob}
2047
+ setCronJobEnabled={setCronJobEnabled}
2048
+ workspacePath={workspacePath}
2049
+ />
2050
+ </Suspense>
2051
+ </div>
2052
+ )}
2053
+ {view === 'link' && (
2054
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2055
+ <Suspense fallback={<LazyPanelFallback />}>
2056
+ <LinkedFoldersView
2057
+ listLinkedFolders={listLinkedFolders}
2058
+ addLinkedFolder={addLinkedFolder}
2059
+ removeLinkedFolder={removeLinkedFolder}
2060
+ linkedFolderStatus={linkedFolderStatus}
2061
+ refreshLinkedFolder={refreshLinkedFolder}
2062
+ listLinkedFolderNotes={listLinkedFolderNotes}
2063
+ readLinkedFolderNote={readLinkedFolderNote}
2064
+ openLinkedFolderNote={openLinkedFolderNote}
2065
+ canOpenNote={isTauriRuntime()}
2066
+ // Desktop: native Tauri picker. Web: asks `mint web` (same
2067
+ // machine) to open its own dialog via a loopback-gated route.
2068
+ selectFolder={selectLinkedFolderPath}
2069
+ />
2070
+ </Suspense>
2071
+ </div>
2072
+ )}
2073
+ {view === 'code' && (
2074
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2075
+ <Suspense fallback={<LazyPanelFallback />}>
2076
+ <CliSessionsView
2077
+ chatSessions={chatSessions}
2078
+ activeConversationId={conversationId}
2079
+ workspacePath={workspacePath}
2080
+ recentWorkspacePaths={availableWorkspacePaths}
2081
+ onSelectFolder={selectCodeHubFolder}
2082
+ onSelectSession={(id) => {
2083
+ selectConversation(id)
2084
+ }}
2085
+ onDeleteSession={deleteConversation}
2086
+ onRenameSession={renameConversation}
2087
+ onRefreshSessions={refreshChatSessions}
2088
+ onShowToast={showToast}
2089
+ />
2090
+ </Suspense>
2091
+ </div>
2092
+ )}
2093
+ {view === 'pictures' && (
2094
+ <Suspense fallback={<LazyPanelFallback />}>
2095
+ <LibraryPage chatId={conversationId} onClose={() => changeView('chat')} onOpenConversation={id => { void selectConversation(id) }} />
2096
+ </Suspense>
2097
+ )}
2098
+ {view === 'imagine' && (
2099
+ <Suspense fallback={<LazyPanelFallback />}>
2100
+ <ImageStudioPanel
2101
+ view={view}
2102
+ onSendToChat={async (imagePath, imgPrompt) => {
2103
+ try {
2104
+ const response = await fetch(convertFileSrc(imagePath))
2105
+ if (!response.ok) throw new Error(`Unable to load image (${response.status})`)
2106
+ const blob = await response.blob()
2107
+ const imageName = imagePath.split(/[\\/]/).pop() || 'generated-image.png'
2108
+ const dataUri = await readImage(new File([blob], imageName, { type: blob.type || 'image/png' }))
2109
+ const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
2110
+
2111
+ changeView('chat')
2112
+ conversationActions.compose(imgPrompt)
2113
+ conversationActions.attachImage({ dataUri, previewDataUri, name: imageName })
2114
+ } catch (error) {
2115
+ showToast(`Could not attach image: ${errorMessage(error)}`)
2116
+ }
2117
+ }}
2118
+ />
2119
+ </Suspense>
2120
+ )}
2121
+ {view === 'veo' && (
2122
+ <Suspense fallback={<LazyPanelFallback />}>
2123
+ <VeoStudioPanel
2124
+ view={view}
2125
+ onSendToChat={(vidPrompt) => {
2126
+ changeView('chat')
2127
+ conversationActions.compose(vidPrompt)
2128
+ }}
2129
+ />
2130
+ </Suspense>
2131
+ )}
2132
+ </div>
2133
+ <div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
2134
+ <div className="startup-loading-content">
2135
+ <div className="startup-loading-dots" aria-hidden="true"><span /><span /><span /></div>
2136
+ <div className="startup-loading-text">Loading Mint Agent</div>
2137
+ </div>
2138
+ </div>
2139
+
2140
+ {isSearchOpen && (
2141
+ <Suspense fallback={null}>
2142
+ <CommandPalette
2143
+ isOpen
2144
+ onClose={() => setIsSearchOpen(false)}
2145
+ onSelectChat={(chatId) => {
2146
+ selectConversation(chatId)
2147
+ setIsSearchOpen(false)
2148
+ }}
2149
+ onNewChat={() => {
2150
+ clearHistory('New chat')
2151
+ setIsSearchOpen(false)
2152
+ }}
2153
+ onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
2154
+ onOpenSettings={() => {
2155
+ changeView('settings')
2156
+ setIsSearchOpen(false)
2157
+ }}
2158
+ onChangeView={(targetView) => {
2159
+ changeView(targetView)
2160
+ setIsSearchOpen(false)
2161
+ }}
2162
+ onChangeModel={(model) => {
2163
+ handleUpdateSettingsField('model', model)
2164
+ setToastMessage(`Switched model to ${model}`)
2165
+ }}
2166
+ onChangeProvider={(provider) => {
2167
+ handleUpdateSettingsField('provider', provider)
2168
+ setToastMessage(`Switched provider to ${provider}`)
2169
+ }}
2170
+ onExecuteSlash={(cmd) => {
2171
+ conversationActions.compose(cmd + ' ')
2172
+ setIsSearchOpen(false)
2173
+ }}
2174
+ chatSessions={chatSessions}
2175
+ currentChatId={conversationId}
2176
+ workspacePath={workspacePath}
2177
+ />
2178
+ </Suspense>
2179
+ )}
2180
+ </div>
2181
+ )
2182
+ }