@pheem49/mint 1.14.0 → 1.15.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 (585) hide show
  1. package/CLAUDE.md +1 -1
  2. package/Cargo.lock +291 -11
  3. package/Cargo.toml +1 -1
  4. package/README.md +71 -9
  5. package/ROADMAP.md +30 -0
  6. package/Release_Note.md +579 -465
  7. package/crates/mint-cli/Cargo.toml +3 -1
  8. package/crates/mint-cli/src/agent/approval_prompts.rs +16 -3
  9. package/crates/mint-cli/src/agent/live_status.rs +367 -118
  10. package/crates/mint-cli/src/agent/markdown_render.rs +601 -61
  11. package/crates/mint-cli/src/agent.rs +452 -215
  12. package/crates/mint-cli/src/commands/agent.rs +16 -21
  13. package/crates/mint-cli/src/commands/eval.rs +9 -5
  14. package/crates/mint-cli/src/commands/git.rs +204 -0
  15. package/crates/mint-cli/src/commands/integrations.rs +28 -0
  16. package/crates/mint-cli/src/commands/knowledge.rs +1 -0
  17. package/crates/mint-cli/src/commands/mod.rs +37 -2
  18. package/crates/mint-cli/src/commands/tailnet_web.rs +235 -0
  19. package/crates/mint-cli/src/image.rs +26 -3
  20. package/crates/mint-cli/src/interactive/chat_tui.rs +4742 -0
  21. package/crates/mint-cli/src/interactive/command_ui.rs +477 -0
  22. package/crates/mint-cli/src/interactive/format.rs +8 -0
  23. package/crates/mint-cli/src/interactive/input_box.rs +38 -2
  24. package/crates/mint-cli/src/interactive/picker.rs +180 -37
  25. package/crates/mint-cli/src/interactive/resume_picker.rs +686 -0
  26. package/crates/mint-cli/src/interactive/slash_commands.rs +868 -450
  27. package/crates/mint-cli/src/interactive/thought_viewer.rs +286 -0
  28. package/crates/mint-cli/src/interactive.rs +400 -136
  29. package/crates/mint-cli/src/main.rs +84 -23
  30. package/crates/mint-cli/src/markdown.rs +12 -5
  31. package/crates/mint-cli/src/onboard.rs +32 -30
  32. package/crates/mint-cli/src/plugins_cli.rs +8 -5
  33. package/crates/mint-cli/src/setup.rs +10 -8
  34. package/crates/mint-cli/src/terminal_theme.rs +187 -0
  35. package/crates/mint-cli/src/updater.rs +56 -21
  36. package/crates/mint-core/src/agent/agent_loop.rs +1 -0
  37. package/crates/mint-core/src/agent/chat.rs +843 -177
  38. package/crates/mint-core/src/agent/code_tools.rs +83 -2
  39. package/crates/mint-core/src/agent/memory.rs +658 -20
  40. package/crates/mint-core/src/agent/skills.rs +1 -1
  41. package/crates/mint-core/src/api_server/routes/chat.rs +31 -5
  42. package/crates/mint-core/src/api_server/routes/linked_folders.rs +118 -3
  43. package/crates/mint-core/src/api_server/routes/misc.rs +10 -14
  44. package/crates/mint-core/src/api_server/routes/sessions.rs +44 -0
  45. package/crates/mint-core/src/api_server.rs +22 -3
  46. package/crates/mint-core/src/avatar_bridge.rs +2 -0
  47. package/crates/mint-core/src/browser/cdp.rs +74 -83
  48. package/crates/mint-core/src/browser/evidence.rs +260 -0
  49. package/crates/mint-core/src/browser/input.rs +1 -1
  50. package/crates/mint-core/src/browser/interact.rs +98 -124
  51. package/crates/mint-core/src/browser/lifecycle.rs +75 -4
  52. package/crates/mint-core/src/browser/mod.rs +16 -2
  53. package/crates/mint-core/src/browser/navigate.rs +27 -19
  54. package/crates/mint-core/src/browser/observe.js +40 -0
  55. package/crates/mint-core/src/browser/session.rs +1558 -0
  56. package/crates/mint-core/src/git/branches.rs +348 -0
  57. package/crates/mint-core/src/git/checkpoint.rs +40 -0
  58. package/crates/mint-core/src/git/diff.rs +206 -0
  59. package/crates/mint-core/src/git/mod.rs +10 -2
  60. package/crates/mint-core/src/integrations/channels.rs +1 -0
  61. package/crates/mint-core/src/lib.rs +22 -14
  62. package/crates/mint-core/src/live_sync.rs +28 -9
  63. package/crates/mint-core/src/media/auto_shorts.rs +1 -0
  64. package/crates/mint-core/src/media/gemini_live.rs +12 -1
  65. package/crates/mint-core/src/media/mic_transcribe.rs +1 -0
  66. package/crates/mint-core/src/media/subtitle.rs +1 -0
  67. package/crates/mint-core/src/orchestration/context_render.rs +1 -0
  68. package/crates/mint-core/src/orchestration/memory_skill.rs +6 -1
  69. package/crates/mint-core/src/orchestration/mod.rs +1024 -271
  70. package/crates/mint-core/src/orchestration/tools/browser.rs +16 -139
  71. package/crates/mint-core/src/orchestration/tools/misc.rs +2 -1
  72. package/crates/mint-core/src/orchestration/tools/web.rs +1 -1
  73. package/crates/mint-core/src/prompts/agent.rs +58 -29
  74. package/crates/mint-core/src/prompts/chat.rs +1 -1
  75. package/crates/mint-core/src/prompts/persona.rs +1 -1
  76. package/crates/mint-core/src/prompts/tool_catalog.rs +93 -25
  77. package/crates/mint-core/src/search/linked_folders/index.rs +732 -0
  78. package/crates/mint-core/src/search/linked_folders.rs +391 -130
  79. package/crates/mint-core/src/slash/mod.rs +470 -22
  80. package/crates/mint-core/src/slash/model_fetcher.rs +12 -3
  81. package/crates/mint-core/src/slash/models.rs +66 -10
  82. package/crates/mint-core/src/system/config.rs +230 -0
  83. package/crates/mint-core/src/system/workspace.rs +228 -0
  84. package/crates/mint-core/src/system.rs +1 -0
  85. package/crates/mint-core/tests/memory_persistence.rs +39 -0
  86. package/mint-presentation-demo.mp4 +0 -0
  87. package/out/web/assets/CliSessionsView-BxUVWEEL.js +1 -0
  88. package/out/web/assets/CliSessionsView-CBDTXuQv.css +1 -0
  89. package/out/web/assets/CodeReviewPage-DkMgfcos.css +1 -0
  90. package/out/web/assets/CodeReviewPage-jKlmLJwT.js +3 -0
  91. package/out/web/assets/CommandPalette-DnwPA5hy.js +1 -0
  92. package/out/web/assets/ImageStudioPanel-BR1XFwhR.js +1 -0
  93. package/out/web/assets/ImageStudioPanel-D0lnhQV5.css +1 -0
  94. package/out/web/assets/LinkedFoldersView-bi-CWQ-I.js +1 -0
  95. package/out/web/assets/McpRegistryPicker-D5tEFnyq.js +1 -0
  96. package/out/web/assets/McpServersView-BGG1dhFM.js +1 -0
  97. package/out/web/assets/MintDashboard-BIglDEWX.js +20 -0
  98. package/out/web/assets/MintDashboard-DSMywSgs.css +1 -0
  99. package/out/web/assets/ModelPanel-CbJ1PFoZ.js +1 -0
  100. package/out/web/assets/PicturesLibrary-CQP0fLP1.css +1 -0
  101. package/out/web/assets/PicturesLibrary-DoYN7T1p.js +1 -0
  102. package/out/web/assets/PluginsView-CU1HWXGk.js +1 -0
  103. package/out/web/assets/ScheduledTasksView-Bnl36y2N.js +1 -0
  104. package/out/web/assets/SettingsWindow-DhWLAn9T.css +1 -0
  105. package/out/web/assets/SettingsWindow-Dw8L01pw.js +2 -0
  106. package/out/web/assets/SkillsView-cnl8aY5p.js +2 -0
  107. package/out/web/assets/ToolSurfacePage-CgsQw6l5.css +1 -0
  108. package/out/web/assets/ToolSurfacePage-Cz2VJAPZ.js +37 -0
  109. package/out/web/assets/VeoStudioPanel-D4JnERWR.js +4 -0
  110. package/out/web/assets/VeoStudioPanel-DOj7_WR3.css +1 -0
  111. package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +1 -0
  112. package/out/web/assets/{abnfDiagram-VCTEODGH-DAOBA7ZG.js → abnfDiagram-VCTEODGH-ByMyfanq.js} +1 -1
  113. package/out/web/assets/{architecture-7GRP2DOG-CgSwQOPQ.js → architecture-7GRP2DOG-CLq2kEOB.js} +1 -1
  114. package/out/web/assets/{architectureDiagram-5GKGNRK7-CYp3yupP.js → architectureDiagram-5GKGNRK7-6PoVSmNG.js} +1 -1
  115. package/out/web/assets/{blockDiagram-NRAW4CY4-CyWybDWr.js → blockDiagram-NRAW4CY4-cKw9mAKi.js} +1 -1
  116. package/out/web/assets/{c4Diagram-UCG6FXSJ-BvY3U709.js → c4Diagram-UCG6FXSJ-Cqy8N5Ge.js} +1 -1
  117. package/out/web/assets/{chunk-75Z2AOVW-TLXCoZYv.js → chunk-75Z2AOVW-3qybijF5.js} +1 -1
  118. package/out/web/assets/chunk-CKBBP62Z-cIrXq6jN.js +2 -0
  119. package/out/web/assets/{chunk-CLGD4ZFX-xjsKtNS7.js → chunk-CLGD4ZFX-CndDQkww.js} +1 -1
  120. package/out/web/assets/chunk-DSNCTWBM-Cc4NxvgD.js +2 -0
  121. package/out/web/assets/chunk-DU6HZSFF-drzh6GsU.js +128 -0
  122. package/out/web/assets/{chunk-F27PBJKO-BFpgF55A.js → chunk-F27PBJKO-FiY35l4v.js} +1 -1
  123. package/out/web/assets/{chunk-G27WJ6UU-DfF7qW-C.js → chunk-G27WJ6UU-Bjt9Il4A.js} +1 -1
  124. package/out/web/assets/{chunk-GMAD6QVW-C-558kh_.js → chunk-GMAD6QVW-Bppi3Ctd.js} +1 -1
  125. package/out/web/assets/{chunk-JQ64N6SF-wJmeGKAw.js → chunk-JQ64N6SF-DGg5kcEV.js} +1 -1
  126. package/out/web/assets/{chunk-LCL6LL3I-C1wHD8Ie.js → chunk-LCL6LL3I-CCYcjd7p.js} +1 -1
  127. package/out/web/assets/{chunk-OBVCFTLP-BsRTPBqs.js → chunk-OBVCFTLP-DJeNil5M.js} +1 -1
  128. package/out/web/assets/{chunk-P2QGCYS3-HZIiM7Ni.js → chunk-P2QGCYS3-lRzkRjBB.js} +1 -1
  129. package/out/web/assets/{chunk-POPQ4Y6H-Dk9wvVb0.js → chunk-POPQ4Y6H-DouASMBW.js} +1 -1
  130. package/out/web/assets/{chunk-PWAF6VOD-BmjYLNbq.js → chunk-PWAF6VOD-DMmD1vVf.js} +1 -1
  131. package/out/web/assets/{chunk-R7TYR2AO-CaHIf-lK.js → chunk-R7TYR2AO-CYLA5UC5.js} +1 -1
  132. package/out/web/assets/{chunk-RHFEMEQ7-_T561hOU.js → chunk-RHFEMEQ7-BiXwEcUT.js} +1 -1
  133. package/out/web/assets/{chunk-SVP7TREG-CM-GGYoY.js → chunk-SVP7TREG-BV-zPslF.js} +1 -1
  134. package/out/web/assets/{chunk-TEH6E4GO-K2A8Vg2C.js → chunk-TEH6E4GO-B2RGEohZ.js} +1 -1
  135. package/out/web/assets/classDiagram-DTDB5LWJ-Dq0smRWk.js +1 -0
  136. package/out/web/assets/classDiagram-v2-JRS7N3AN-Dq0smRWk.js +1 -0
  137. package/out/web/assets/config-C-ZFtipU.css +1 -0
  138. package/out/web/assets/config-eEDngJXO.js +1 -0
  139. package/out/web/assets/core-hQ2VA9Ti.js +1 -0
  140. package/out/web/assets/{cynefin-OW5HDTMX-ClzaB6cc.js → cynefin-OW5HDTMX-Cas4csHM.js} +1 -1
  141. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-D_JzZBzf.js → cynefinDiagram-5FMLGOSQ-Bbpj66Y3.js} +1 -1
  142. package/out/web/assets/{dagre-3AP2YEHR-BDuUeMHj.js → dagre-3AP2YEHR-B0-2RDEd.js} +1 -1
  143. package/out/web/assets/{diagram-S7CK7UJ4-DgtcfE8P.js → diagram-S7CK7UJ4-BdzEjUyH.js} +1 -1
  144. package/out/web/assets/{diagram-UQ7AKVKN-DTLoEYti.js → diagram-UQ7AKVKN-CJDwyPxF.js} +1 -1
  145. package/out/web/assets/{diagram-VSXAHHWV-ChGl9pXz.js → diagram-VSXAHHWV-BxaG1r_B.js} +1 -1
  146. package/out/web/assets/{diagram-VX7I27RA-Be8DWz0S.js → diagram-VX7I27RA-BMmUqm8f.js} +1 -1
  147. package/out/web/assets/{diagram-Z3DM3KII-BMXjRTOF.js → diagram-Z3DM3KII-CFtd03eE.js} +1 -1
  148. package/out/web/assets/dist-js-Bf2bhoku.js +1 -0
  149. package/out/web/assets/{ebnfDiagram-PWID7BFC-8Q9pl1CJ.js → ebnfDiagram-PWID7BFC-DwAen1hO.js} +1 -1
  150. package/out/web/assets/{erDiagram-SSCWMZ5O-DjH8Lnkn.js → erDiagram-SSCWMZ5O-BCZSHACj.js} +1 -1
  151. package/out/web/assets/event-ChBVpRy0.js +1 -0
  152. package/out/web/assets/{eventmodeling-NTZA5JFV-BKUH_DOp.js → eventmodeling-NTZA5JFV-XHStE4E_.js} +1 -1
  153. package/out/web/assets/fira-code-latin-600-normal-BmSSHshu.woff +0 -0
  154. package/out/web/assets/fira-code-latin-600-normal-CvYZpPHg.woff2 +0 -0
  155. package/out/web/assets/fira-code-symbols2-600-normal-Dvq6IQE5.woff +0 -0
  156. package/out/web/assets/flowDiagram-A5DVABFB-BLg7nEcd.js +1 -0
  157. package/out/web/assets/{ganttDiagram-EL5Y4UJY-D7fZKJNM.js → ganttDiagram-EL5Y4UJY-BAEu1Cql.js} +1 -1
  158. package/out/web/assets/{gitGraph-4MIJSDKK-BypWaaBU.js → gitGraph-4MIJSDKK-r32Vyper.js} +1 -1
  159. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-DWiw_3bq.js → gitGraphDiagram-WWUBYQGX-z9o9qt6V.js} +1 -1
  160. package/out/web/assets/index-Btsf4L8M.css +1 -0
  161. package/out/web/assets/index-D8D9MeRA.js +11 -0
  162. package/out/web/assets/{info-A6RAGUB7-KkAUxT4j.js → info-A6RAGUB7-BvLaC2cw.js} +1 -1
  163. package/out/web/assets/{infoDiagram-RXCK75RN-BNb6E48y.js → infoDiagram-RXCK75RN-Be6WAXXw.js} +1 -1
  164. package/out/web/assets/{ishikawaDiagram-5VMMS53U-Caa-JoAK.js → ishikawaDiagram-5VMMS53U-DZjoHJHC.js} +1 -1
  165. package/out/web/assets/{journeyDiagram-EYS64GPL-DOrmFGz6.js → journeyDiagram-EYS64GPL-CtVkjkGm.js} +1 -1
  166. package/out/web/assets/{kanban-definition-3QL26DDD-BOWCYcK0.js → kanban-definition-3QL26DDD-CW5FMv1w.js} +1 -1
  167. package/out/web/assets/lucide-react-Ip3L6Jtn.js +1 -0
  168. package/out/web/assets/markdown-BSeeUbJH.js +38 -0
  169. package/out/web/assets/{mermaid-parser.core-BQqjCkS_.js → mermaid-parser.core-aUgCqsZ5.js} +3 -3
  170. package/out/web/assets/mermaid.core-D57sEiSL.js +11 -0
  171. package/out/web/assets/{mindmap-definition-FBJOCRG2-jTceWpr0.js → mindmap-definition-FBJOCRG2-DAmSpM3a.js} +1 -1
  172. package/out/web/assets/modelManager-tMVmJVAW.js +2 -0
  173. package/out/web/assets/oauthManager-9jxicDWJ.js +1 -0
  174. package/out/web/assets/{packet-AYTQ26CC-CJ5RXgol.js → packet-AYTQ26CC-DJ27iIoJ.js} +1 -1
  175. package/out/web/assets/{pegDiagram-XKGWAZYB-y8wMtd0y.js → pegDiagram-XKGWAZYB-Bj3ii-gS.js} +1 -1
  176. package/out/web/assets/{pie-WAS4IAKB-72fTBbl4.js → pie-WAS4IAKB-DG5mCUbc.js} +1 -1
  177. package/out/web/assets/{pieDiagram-E7YTZNPT-e0O02qWq.js → pieDiagram-E7YTZNPT-BrJLPnDA.js} +1 -1
  178. package/out/web/assets/prompt-latin-700-normal-BfbftDSq.woff +0 -0
  179. package/out/web/assets/prompt-latin-700-normal-I2gc831J.woff2 +0 -0
  180. package/out/web/assets/prompt-thai-700-normal-Cb5ilR-c.woff +0 -0
  181. package/out/web/assets/prompt-thai-700-normal-Cg4aQ0Nn.woff2 +0 -0
  182. package/out/web/assets/{quadrantDiagram-AXDQQJYC-C9phYLom.js → quadrantDiagram-AXDQQJYC-3x_TmF3j.js} +1 -1
  183. package/out/web/assets/{radar-RG4KPBEZ-BAU2hzUT.js → radar-RG4KPBEZ-CrXaTTuV.js} +1 -1
  184. package/out/web/assets/{railroad-74A4TZTK-B0bCAVHC.js → railroad-74A4TZTK-BpqMH9BE.js} +1 -1
  185. package/out/web/assets/{railroad-abnf-HS5TGJTU-WhxO7ndE.js → railroad-abnf-HS5TGJTU-DHI0uLlV.js} +1 -1
  186. package/out/web/assets/{railroad-ebnf-LZEXJU2U-BkA0m7-F.js → railroad-ebnf-LZEXJU2U-CQCb316y.js} +1 -1
  187. package/out/web/assets/{railroad-peg-WCYAUIDC-BsVpHIaj.js → railroad-peg-WCYAUIDC-Ph1giKkH.js} +1 -1
  188. package/out/web/assets/{railroadDiagram-O6MQD6OU-CyOLN65Q.js → railroadDiagram-O6MQD6OU-BRq6wIxx.js} +1 -1
  189. package/out/web/assets/{requirementDiagram-EFPCY7ZU-DQXJ7Dud.js → requirementDiagram-EFPCY7ZU-DIJj6G1g.js} +1 -1
  190. package/out/web/assets/{sankeyDiagram-P5KCCOFB-B2XLyIIc.js → sankeyDiagram-P5KCCOFB-DZajSryq.js} +1 -1
  191. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-DGQY4wwm.js → sequenceDiagram-WJ2MYXX4-DxJCWyZS.js} +1 -1
  192. package/out/web/assets/{stateDiagram-HBIQ2CUA-DHZpDwNy.js → stateDiagram-HBIQ2CUA-k6voi8Vf.js} +1 -1
  193. package/out/web/assets/stateDiagram-v2-4QOOHH4V-DeEc0lGZ.js +1 -0
  194. package/out/web/assets/studio-B1mk40wD.js +1 -0
  195. package/out/web/assets/{swimlanes-XN3QIQJK-Bn_jcZz-.js → swimlanes-XN3QIQJK-igYhTjXB.js} +1 -1
  196. package/out/web/assets/swimlanesDiagram-VK2B7HYN-DpVik9v7.js +8 -0
  197. package/out/web/assets/{timeline-definition-24CTP7MA-CdTERuJZ.js → timeline-definition-24CTP7MA-Dke2p93p.js} +1 -1
  198. package/out/web/assets/{treeView-Q6P3EWNA-TKBKQG6R.js → treeView-Q6P3EWNA-hNELnXUs.js} +1 -1
  199. package/out/web/assets/{treemap-WGGIJYW6-BP7gQoiC.js → treemap-WGGIJYW6-D2B6wDsx.js} +1 -1
  200. package/out/web/assets/{vennDiagram-4TSXK5OY-BHUmxDuK.js → vennDiagram-4TSXK5OY-CDkW7rUP.js} +1 -1
  201. package/out/web/assets/{wardley-WFR3VGLG-9bct5AAN.js → wardley-WFR3VGLG-BECdXETi.js} +1 -1
  202. package/out/web/assets/{wardleyDiagram-VM6X3IG4-BfJwDDso.js → wardleyDiagram-VM6X3IG4-B5BycpQ4.js} +1 -1
  203. package/out/web/assets/window-5qYXKoW7.js +1 -0
  204. package/out/web/assets/{xychartDiagram-S5SC5T6Z-Dqd9Mvpo.js → xychartDiagram-S5SC5T6Z-CtJS3b--.js} +1 -1
  205. package/out/web/index-web.html +3 -4
  206. package/out/web/index.html +3 -4
  207. package/package.json +17 -6
  208. package/skills-lock.json +36 -0
  209. package/slash-commands.json +9 -0
  210. package/src/renderer/index-web.html +1 -0
  211. package/src/renderer/index.html +1 -0
  212. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  213. package/src/renderer/shared/agentProgress.ts +168 -3
  214. package/src/renderer/shared/components/AgentActivityDrawer.tsx +6 -6
  215. package/src/renderer/shared/components/AgentActivityTable.tsx +192 -132
  216. package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +4 -4
  217. package/src/renderer/shared/components/AuthGate.tsx +4 -9
  218. package/src/renderer/shared/components/CalculationCard.tsx +41 -15
  219. package/src/renderer/shared/components/ChatMessageItem.tsx +20 -21
  220. package/src/renderer/shared/components/ChatPanel.tsx +432 -193
  221. package/src/renderer/shared/components/ChunkErrorBoundary.tsx +198 -49
  222. package/src/renderer/shared/components/CliSessionsView.tsx +495 -0
  223. package/src/renderer/shared/components/CodeReviewPage.tsx +565 -0
  224. package/src/renderer/shared/components/CommandPalette.tsx +8 -8
  225. package/src/renderer/shared/components/DashboardSidebar.tsx +862 -125
  226. package/src/renderer/shared/components/DesktopTitlebar.tsx +345 -0
  227. package/src/renderer/shared/components/DiffReviewModal.tsx +3 -3
  228. package/src/renderer/shared/components/GitBranchSelector.tsx +442 -0
  229. package/src/renderer/shared/components/ImageGenCard.tsx +31 -12
  230. package/src/renderer/shared/components/ImageSearchCard.tsx +59 -38
  231. package/src/renderer/shared/components/ImageStudioPanel.tsx +40 -41
  232. package/src/renderer/shared/components/InlineThoughtNote.tsx +27 -0
  233. package/src/renderer/shared/components/LinkedFoldersView.tsx +365 -46
  234. package/src/renderer/shared/components/McpRegistryPicker.tsx +1 -1
  235. package/src/renderer/shared/components/McpServersView.tsx +24 -22
  236. package/src/renderer/shared/components/MermaidCard.tsx +14 -8
  237. package/src/renderer/shared/components/MintDashboard.tsx +1141 -508
  238. package/src/renderer/shared/components/ModelSelectorPopover.tsx +535 -214
  239. package/src/renderer/shared/components/PlanChecklistWidget.tsx +1 -1
  240. package/src/renderer/shared/components/PluginsView.tsx +1 -1
  241. package/src/renderer/shared/components/RewindModal.tsx +6 -6
  242. package/src/renderer/shared/components/RunSummaryDashboard.tsx +3 -3
  243. package/src/renderer/shared/components/ScheduledTasksView.tsx +4 -4
  244. package/src/renderer/shared/components/ScreenCaptureDialog.css +221 -0
  245. package/src/renderer/shared/components/ScreenCaptureDialog.tsx +199 -0
  246. package/src/renderer/shared/components/Settings/AgentsTab.tsx +34 -39
  247. package/src/renderer/shared/components/Settings/AudioTab.tsx +8 -8
  248. package/src/renderer/shared/components/Settings/AutomationTab.tsx +8 -58
  249. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +368 -0
  250. package/src/renderer/shared/components/Settings/GeneralTab.tsx +335 -81
  251. package/src/renderer/shared/components/Settings/MemoryTab.tsx +3 -3
  252. package/src/renderer/shared/components/Settings/PluginsTab.tsx +20 -12
  253. package/src/renderer/shared/components/Settings/ProfileTab.tsx +29 -5
  254. package/src/renderer/shared/components/Settings/SearchableModelCombobox.tsx +411 -0
  255. package/src/renderer/shared/components/Settings/ThemeTab.tsx +401 -158
  256. package/src/renderer/shared/components/SkillsView.tsx +41 -16
  257. package/src/renderer/shared/components/SlashSuggestions.tsx +3 -2
  258. package/src/renderer/shared/components/SourcesBlock.tsx +44 -32
  259. package/src/renderer/shared/components/StockCard.tsx +58 -24
  260. package/src/renderer/shared/components/SubagentDagView.tsx +4 -4
  261. package/src/renderer/shared/components/TerminalDock.tsx +241 -0
  262. package/src/renderer/shared/components/ThinkingBlock.tsx +14 -8
  263. package/src/renderer/shared/components/ToolSurfacePage.tsx +520 -0
  264. package/src/renderer/shared/components/UiFeatureCard.tsx +267 -0
  265. package/src/renderer/shared/components/UiGridCard.tsx +188 -0
  266. package/src/renderer/shared/components/UiMockupWidget.tsx +149 -0
  267. package/src/renderer/shared/components/VeoStudioPanel.tsx +13 -29
  268. package/src/renderer/shared/components/WeatherCard.tsx +3 -3
  269. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +133 -0
  270. package/src/renderer/shared/components/WorkspaceSelector.tsx +149 -0
  271. package/src/renderer/shared/constants/config.ts +28 -3
  272. package/src/renderer/shared/constants/models.ts +189 -2
  273. package/src/renderer/shared/conversation/coordinator.test.ts +27 -0
  274. package/src/renderer/shared/conversation/coordinator.ts +82 -0
  275. package/src/renderer/shared/conversation/syncView.test.ts +46 -0
  276. package/src/renderer/shared/conversation/syncView.ts +43 -0
  277. package/src/renderer/shared/conversation/useConversationCoordinator.ts +106 -0
  278. package/src/renderer/shared/css/agent-observability.css +35 -3
  279. package/src/renderer/shared/css/auth-gate.css +2 -2
  280. package/src/renderer/shared/css/chat-ui-cards.css +495 -0
  281. package/src/renderer/shared/css/code-review.css +823 -0
  282. package/src/renderer/shared/css/code-sessions.css +452 -0
  283. package/src/renderer/shared/css/file-changes.css +132 -0
  284. package/src/renderer/shared/css/image-studio.css +16 -16
  285. package/src/renderer/shared/css/management-views.css +277 -27
  286. package/src/renderer/shared/css/model-selector.css +442 -42
  287. package/src/renderer/shared/css/settings/agents.css +1 -1
  288. package/src/renderer/shared/css/settings/automation.css +13 -13
  289. package/src/renderer/shared/css/settings/general.css +526 -5
  290. package/src/renderer/shared/css/settings/plugins.css +5 -5
  291. package/src/renderer/shared/css/settings/profile.css +331 -1
  292. package/src/renderer/shared/css/settings/theme.css +651 -3
  293. package/src/renderer/shared/css/veo-studio.css +16 -15
  294. package/src/renderer/shared/fonts.ts +19 -37
  295. package/src/renderer/shared/platform.test.ts +34 -0
  296. package/src/renderer/shared/platform.ts +173 -6
  297. package/src/renderer/shared/theme/themeManager.ts +144 -0
  298. package/src/renderer/shared/theme/themes.ts +116 -0
  299. package/src/renderer/shared/theme/tokens.css +318 -0
  300. package/src/renderer/shared/types.ts +82 -1
  301. package/src/renderer/shared/utils/agentActivity.ts +126 -5
  302. package/src/renderer/shared/utils/cardJson.test.ts +35 -0
  303. package/src/renderer/shared/utils/cardJson.ts +30 -0
  304. package/src/renderer/shared/utils/fileIcons.ts +1 -1
  305. package/src/renderer/shared/utils/markdown.tsx +329 -80
  306. package/src/renderer/shared/utils/markdown.webImages.test.tsx +40 -0
  307. package/src/renderer/shared/utils/providers.ts +8 -3
  308. package/src/renderer/shared/utils/screenCapture.ts +43 -0
  309. package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
  310. package/src/renderer/shared/utils/ui.ts +4 -64
  311. package/src/renderer/src/App.tsx +7 -0
  312. package/src/renderer/src/components/BrowserShell.tsx +108 -0
  313. package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +91 -0
  314. package/src/renderer/src/components/Live2DStage.tsx +32 -9
  315. package/src/renderer/src/components/MintDashboard.tsx +4 -2
  316. package/src/renderer/src/components/ModelPanel.tsx +2 -1
  317. package/src/renderer/src/components/PicturesLibrary.tsx +3 -2
  318. package/src/renderer/src/components/ScreenPicker.css +190 -0
  319. package/src/renderer/src/components/ScreenPicker.tsx +185 -540
  320. package/src/renderer/src/components/SettingsWindow.tsx +40 -2
  321. package/src/renderer/src/components/SpotlightWindow.tsx +1 -0
  322. package/src/renderer/src/components/WidgetWindow.tsx +27 -3
  323. package/src/renderer/src/components/WorkspacePanel.tsx +61 -43
  324. package/src/renderer/src/components/browser-shell.css +185 -0
  325. package/src/renderer/src/components/browser-suggestions-popup.css +55 -0
  326. package/src/renderer/src/components/liveTranslateFrames.test.ts +23 -0
  327. package/src/renderer/src/components/liveTranslateFrames.ts +34 -0
  328. package/src/renderer/src/css/app-shell.css +394 -113
  329. package/src/renderer/src/css/chat.css +1926 -380
  330. package/src/renderer/src/css/dashboard.css +11 -0
  331. package/src/renderer/src/css/desktop-tools.css +730 -0
  332. package/src/renderer/src/css/misc.css +19 -22
  333. package/src/renderer/src/css/model.css +49 -48
  334. package/src/renderer/src/css/pictures.css +144 -136
  335. package/src/renderer/src/css/settings/base.css +357 -123
  336. package/src/renderer/src/css/sidebar-workspace.css +930 -291
  337. package/src/renderer/src/css/spotlight.css +15 -26
  338. package/src/renderer/src/css/tool-surfaces.css +728 -0
  339. package/src/renderer/src/css/widget.css +35 -7
  340. package/src/renderer/src/env.d.ts +3 -0
  341. package/src/renderer/src/hooks/useProviderModels.ts +2 -9
  342. package/src/renderer/src/index.css +53 -46
  343. package/src/renderer/src/main.tsx +11 -0
  344. package/src/renderer/src/tauri.ts +201 -30
  345. package/src/renderer/src-web/App.tsx +40 -0
  346. package/src/renderer/src-web/components/MintDashboard.tsx +4 -2
  347. package/src/renderer/src-web/components/PicturesLibrary.tsx +3 -2
  348. package/src/renderer/src-web/components/PullToRefresh.tsx +107 -0
  349. package/src/renderer/src-web/components/SettingsWindow.tsx +28 -2
  350. package/src/renderer/src-web/css/app-shell.css +174 -96
  351. package/src/renderer/src-web/css/chat.css +1519 -316
  352. package/src/renderer/src-web/css/dashboard.css +11 -0
  353. package/src/renderer/src-web/css/misc.css +17 -17
  354. package/src/renderer/src-web/css/mobile.css +678 -0
  355. package/src/renderer/src-web/css/model.css +46 -46
  356. package/src/renderer/src-web/css/pictures.css +81 -34
  357. package/src/renderer/src-web/css/pull-to-refresh.css +54 -0
  358. package/src/renderer/src-web/css/settings/base.css +316 -100
  359. package/src/renderer/src-web/css/settings/mobile.css +320 -0
  360. package/src/renderer/src-web/css/sidebar-workspace.css +545 -100
  361. package/src/renderer/src-web/css/spotlight.css +9 -26
  362. package/src/renderer/src-web/css/widget.css +0 -5
  363. package/src/renderer/src-web/env.d.ts +3 -0
  364. package/src/renderer/src-web/hooks/useProviderModels.ts +2 -9
  365. package/src/renderer/src-web/index.css +53 -96
  366. package/src/renderer/src-web/main.tsx +11 -0
  367. package/src/renderer/src-web/tauri.ts +200 -30
  368. package/vite.config.web.ts +1 -0
  369. package/out/web/assets/MintDashboard-DWSqucgb.js +0 -57
  370. package/out/web/assets/SettingsWindow-DFM2aj5B.js +0 -2
  371. package/out/web/assets/chunk-CKBBP62Z-nr1uKPj9.js +0 -2
  372. package/out/web/assets/chunk-DSNCTWBM-Dtj8Rqsd.js +0 -2
  373. package/out/web/assets/chunk-DU6HZSFF-2tv5K1wT.js +0 -128
  374. package/out/web/assets/classDiagram-DTDB5LWJ-BdSFArgw.js +0 -1
  375. package/out/web/assets/classDiagram-v2-JRS7N3AN-BdSFArgw.js +0 -1
  376. package/out/web/assets/core-DImejIUG.js +0 -1
  377. package/out/web/assets/dist-js-BxuKEVWM.js +0 -1
  378. package/out/web/assets/event-BNl8Zahw.js +0 -1
  379. package/out/web/assets/fira-code-cyrillic-400-normal-UC0NFL4U.woff2 +0 -0
  380. package/out/web/assets/fira-code-cyrillic-400-normal-dc1q3VD7.woff +0 -0
  381. package/out/web/assets/fira-code-cyrillic-500-normal-Bu7VCL72.woff +0 -0
  382. package/out/web/assets/fira-code-cyrillic-500-normal-CX35h3Mg.woff2 +0 -0
  383. package/out/web/assets/fira-code-cyrillic-ext-400-normal-Buh61xzB.woff +0 -0
  384. package/out/web/assets/fira-code-cyrillic-ext-400-normal-txZ9Fk_1.woff2 +0 -0
  385. package/out/web/assets/fira-code-cyrillic-ext-500-normal-CYRspHgm.woff2 +0 -0
  386. package/out/web/assets/fira-code-cyrillic-ext-500-normal-CwtLmSFD.woff +0 -0
  387. package/out/web/assets/fira-code-greek-400-normal-B2Gh_Y8s.woff2 +0 -0
  388. package/out/web/assets/fira-code-greek-400-normal-DobhaxvF.woff +0 -0
  389. package/out/web/assets/fira-code-greek-500-normal-CVO0IRNt.woff2 +0 -0
  390. package/out/web/assets/fira-code-greek-500-normal-DEXIWiey.woff +0 -0
  391. package/out/web/assets/fira-code-greek-ext-400-normal-DR7mBgIM.woff2 +0 -0
  392. package/out/web/assets/fira-code-greek-ext-400-normal-DSPUheWg.woff +0 -0
  393. package/out/web/assets/fira-code-greek-ext-500-normal-BO3iq16n.woff2 +0 -0
  394. package/out/web/assets/fira-code-greek-ext-500-normal-CMiNLXER.woff +0 -0
  395. package/out/web/assets/fira-code-latin-ext-400-normal-DG73JjOf.woff +0 -0
  396. package/out/web/assets/fira-code-latin-ext-400-normal-KSMg0QLl.woff2 +0 -0
  397. package/out/web/assets/fira-code-latin-ext-500-normal-CaANqXjI.woff +0 -0
  398. package/out/web/assets/fira-code-latin-ext-500-normal-Crd9e_oI.woff2 +0 -0
  399. package/out/web/assets/flowDiagram-A5DVABFB-BpnHUSU7.js +0 -1
  400. package/out/web/assets/index-CVRg270z.css +0 -1
  401. package/out/web/assets/index-DxZQr9yD.js +0 -10
  402. package/out/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  403. package/out/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  404. package/out/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  405. package/out/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  406. package/out/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  407. package/out/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  408. package/out/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  409. package/out/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  410. package/out/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  411. package/out/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  412. package/out/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  413. package/out/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  414. package/out/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  415. package/out/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  416. package/out/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  417. package/out/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  418. package/out/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  419. package/out/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  420. package/out/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  421. package/out/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  422. package/out/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  423. package/out/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  424. package/out/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  425. package/out/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  426. package/out/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  427. package/out/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  428. package/out/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  429. package/out/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  430. package/out/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  431. package/out/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  432. package/out/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  433. package/out/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  434. package/out/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  435. package/out/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  436. package/out/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  437. package/out/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  438. package/out/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  439. package/out/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  440. package/out/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  441. package/out/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  442. package/out/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  443. package/out/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  444. package/out/web/assets/kanit-latin-400-normal-BUlsQXH3.woff2 +0 -0
  445. package/out/web/assets/kanit-latin-400-normal-HVZWrfev.woff +0 -0
  446. package/out/web/assets/kanit-latin-500-normal-CX_4XcKi.woff2 +0 -0
  447. package/out/web/assets/kanit-latin-500-normal-qBha86O4.woff +0 -0
  448. package/out/web/assets/kanit-latin-600-normal-0lS78yAd.woff2 +0 -0
  449. package/out/web/assets/kanit-latin-600-normal-DlacpeTg.woff +0 -0
  450. package/out/web/assets/kanit-latin-ext-400-normal-B2xPf58u.woff2 +0 -0
  451. package/out/web/assets/kanit-latin-ext-400-normal-CHvNKHHD.woff +0 -0
  452. package/out/web/assets/kanit-latin-ext-500-normal-BFdWrG22.woff +0 -0
  453. package/out/web/assets/kanit-latin-ext-500-normal-EHs3suD6.woff2 +0 -0
  454. package/out/web/assets/kanit-latin-ext-600-normal-BBDzoLaj.woff +0 -0
  455. package/out/web/assets/kanit-latin-ext-600-normal-DAX3wtuq.woff2 +0 -0
  456. package/out/web/assets/kanit-thai-400-normal-D2nliXKa.woff +0 -0
  457. package/out/web/assets/kanit-thai-400-normal-D80Pglro.woff2 +0 -0
  458. package/out/web/assets/kanit-thai-500-normal-BB8gri04.woff2 +0 -0
  459. package/out/web/assets/kanit-thai-500-normal-Iy_w0a80.woff +0 -0
  460. package/out/web/assets/kanit-thai-600-normal-Bj6oiaDc.woff2 +0 -0
  461. package/out/web/assets/kanit-thai-600-normal-wbTlPO7I.woff +0 -0
  462. package/out/web/assets/kanit-vietnamese-400-normal-DRI8YWyb.woff2 +0 -0
  463. package/out/web/assets/kanit-vietnamese-400-normal-dow6Bnto.woff +0 -0
  464. package/out/web/assets/kanit-vietnamese-500-normal-BzTrpCM3.woff2 +0 -0
  465. package/out/web/assets/kanit-vietnamese-500-normal-T1ndT7MF.woff +0 -0
  466. package/out/web/assets/kanit-vietnamese-600-normal-Bi7fw3I4.woff2 +0 -0
  467. package/out/web/assets/kanit-vietnamese-600-normal-DXCRAa_8.woff +0 -0
  468. package/out/web/assets/mali-latin-400-normal-ByPcZ8Gx.woff +0 -0
  469. package/out/web/assets/mali-latin-400-normal-Cuc6Ykl8.woff2 +0 -0
  470. package/out/web/assets/mali-latin-500-normal-B01M-LP6.woff2 +0 -0
  471. package/out/web/assets/mali-latin-500-normal-Thgqw_9f.woff +0 -0
  472. package/out/web/assets/mali-latin-600-normal-B9TiUNpT.woff2 +0 -0
  473. package/out/web/assets/mali-latin-600-normal-CycpQCwU.woff +0 -0
  474. package/out/web/assets/mali-latin-ext-400-normal-DUVdJQO4.woff +0 -0
  475. package/out/web/assets/mali-latin-ext-400-normal-iCG1ALuJ.woff2 +0 -0
  476. package/out/web/assets/mali-latin-ext-500-normal-BfJE4UMZ.woff2 +0 -0
  477. package/out/web/assets/mali-latin-ext-500-normal-FoLEyOKS.woff +0 -0
  478. package/out/web/assets/mali-latin-ext-600-normal-BTvIfiCp.woff2 +0 -0
  479. package/out/web/assets/mali-latin-ext-600-normal-DAQcLVmD.woff +0 -0
  480. package/out/web/assets/mali-thai-400-normal-BO7A3bLZ.woff2 +0 -0
  481. package/out/web/assets/mali-thai-400-normal-CX9VrjTn.woff +0 -0
  482. package/out/web/assets/mali-thai-500-normal-Cph2qha9.woff +0 -0
  483. package/out/web/assets/mali-thai-500-normal-D89rMT77.woff2 +0 -0
  484. package/out/web/assets/mali-thai-600-normal-DPgXT6rZ.woff +0 -0
  485. package/out/web/assets/mali-thai-600-normal-DlTx3OsB.woff2 +0 -0
  486. package/out/web/assets/mali-vietnamese-400-normal-DUKxzXaz.woff2 +0 -0
  487. package/out/web/assets/mali-vietnamese-400-normal-gFDLvsOK.woff +0 -0
  488. package/out/web/assets/mali-vietnamese-500-normal--OY07q2c.woff +0 -0
  489. package/out/web/assets/mali-vietnamese-500-normal-KDrNRwfW.woff2 +0 -0
  490. package/out/web/assets/mali-vietnamese-600-normal-CtTMUU7T.woff +0 -0
  491. package/out/web/assets/mali-vietnamese-600-normal-DFlJr66u.woff2 +0 -0
  492. package/out/web/assets/mermaid.core-DYGGlqOf.js +0 -11
  493. package/out/web/assets/mitr-latin-400-normal-Cb18vPmZ.woff +0 -0
  494. package/out/web/assets/mitr-latin-400-normal-CwbkLQ4a.woff2 +0 -0
  495. package/out/web/assets/mitr-latin-500-normal-B4JOhzAi.woff +0 -0
  496. package/out/web/assets/mitr-latin-500-normal-BmClOow2.woff2 +0 -0
  497. package/out/web/assets/mitr-latin-ext-400-normal-B3CPG7Yo.woff2 +0 -0
  498. package/out/web/assets/mitr-latin-ext-400-normal-DTx65hiC.woff +0 -0
  499. package/out/web/assets/mitr-latin-ext-500-normal-B_Diajz1.woff2 +0 -0
  500. package/out/web/assets/mitr-latin-ext-500-normal-DYOsVaVT.woff +0 -0
  501. package/out/web/assets/mitr-thai-400-normal-CS4Do6aM.woff +0 -0
  502. package/out/web/assets/mitr-thai-400-normal-CXWaAdVj.woff2 +0 -0
  503. package/out/web/assets/mitr-thai-500-normal-CVdBrEVH.woff2 +0 -0
  504. package/out/web/assets/mitr-thai-500-normal-CW_B5D-i.woff +0 -0
  505. package/out/web/assets/mitr-vietnamese-400-normal-CPMGWyoC.woff2 +0 -0
  506. package/out/web/assets/mitr-vietnamese-400-normal-Z6XhIpdY.woff +0 -0
  507. package/out/web/assets/mitr-vietnamese-500-normal-CDrv0585.woff2 +0 -0
  508. package/out/web/assets/mitr-vietnamese-500-normal-CMQWyq-Z.woff +0 -0
  509. package/out/web/assets/noto-sans-thai-latin-400-normal-BaWBrqFP.woff2 +0 -0
  510. package/out/web/assets/noto-sans-thai-latin-400-normal-Dh_FJjOB.woff +0 -0
  511. package/out/web/assets/noto-sans-thai-latin-500-normal-BC04jsmm.woff +0 -0
  512. package/out/web/assets/noto-sans-thai-latin-500-normal-fyBKClF1.woff2 +0 -0
  513. package/out/web/assets/noto-sans-thai-latin-600-normal-CemcjnVR.woff2 +0 -0
  514. package/out/web/assets/noto-sans-thai-latin-600-normal-Dbc-dNO6.woff +0 -0
  515. package/out/web/assets/noto-sans-thai-latin-ext-400-normal-B-1H0YkH.woff +0 -0
  516. package/out/web/assets/noto-sans-thai-latin-ext-400-normal-nBP59pVw.woff2 +0 -0
  517. package/out/web/assets/noto-sans-thai-latin-ext-500-normal-B-ZWb8SZ.woff2 +0 -0
  518. package/out/web/assets/noto-sans-thai-latin-ext-500-normal-CrEHO7C8.woff +0 -0
  519. package/out/web/assets/noto-sans-thai-latin-ext-600-normal-PkMZo0xa.woff +0 -0
  520. package/out/web/assets/noto-sans-thai-latin-ext-600-normal-maHHBsez.woff2 +0 -0
  521. package/out/web/assets/noto-sans-thai-thai-400-normal-BEOmKAI1.woff2 +0 -0
  522. package/out/web/assets/noto-sans-thai-thai-400-normal-Dxb_unuU.woff +0 -0
  523. package/out/web/assets/noto-sans-thai-thai-500-normal-BOGXO2tw.woff +0 -0
  524. package/out/web/assets/noto-sans-thai-thai-500-normal-CU_X86GQ.woff2 +0 -0
  525. package/out/web/assets/noto-sans-thai-thai-600-normal-CmgB8_JK.woff +0 -0
  526. package/out/web/assets/noto-sans-thai-thai-600-normal-Cs73sVjl.woff2 +0 -0
  527. package/out/web/assets/oauthManager-DNiwM8Rn.js +0 -2
  528. package/out/web/assets/oauthManager-DuamS1Av.css +0 -1
  529. package/out/web/assets/outfit-latin-300-normal-AWLfuCom.woff +0 -0
  530. package/out/web/assets/outfit-latin-300-normal-CRYTwY6m.woff2 +0 -0
  531. package/out/web/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
  532. package/out/web/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
  533. package/out/web/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
  534. package/out/web/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
  535. package/out/web/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
  536. package/out/web/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
  537. package/out/web/assets/outfit-latin-ext-300-normal-BYS6Dpvq.woff2 +0 -0
  538. package/out/web/assets/outfit-latin-ext-300-normal-CMIMshEd.woff +0 -0
  539. package/out/web/assets/outfit-latin-ext-400-normal-5tcqmc2S.woff2 +0 -0
  540. package/out/web/assets/outfit-latin-ext-400-normal-DHm7mdGe.woff +0 -0
  541. package/out/web/assets/outfit-latin-ext-500-normal-DrCvqoFD.woff +0 -0
  542. package/out/web/assets/outfit-latin-ext-500-normal-zeox_O30.woff2 +0 -0
  543. package/out/web/assets/outfit-latin-ext-600-normal-B85nYjL1.woff2 +0 -0
  544. package/out/web/assets/outfit-latin-ext-600-normal-CWJcPgd7.woff +0 -0
  545. package/out/web/assets/preload-helper-D0uHx7FM.js +0 -1
  546. package/out/web/assets/prompt-latin-ext-400-normal-1pHbN9uy.woff +0 -0
  547. package/out/web/assets/prompt-latin-ext-400-normal-DdSafGZ9.woff2 +0 -0
  548. package/out/web/assets/prompt-latin-ext-500-normal--EZ1um7s.woff2 +0 -0
  549. package/out/web/assets/prompt-latin-ext-500-normal-D-lI1EVT.woff +0 -0
  550. package/out/web/assets/prompt-latin-ext-600-normal-C8b50x67.woff +0 -0
  551. package/out/web/assets/prompt-latin-ext-600-normal-Cg9L7iJU.woff2 +0 -0
  552. package/out/web/assets/prompt-vietnamese-400-normal-BCPzsgPT.woff2 +0 -0
  553. package/out/web/assets/prompt-vietnamese-400-normal-D4pLyeNK.woff +0 -0
  554. package/out/web/assets/prompt-vietnamese-500-normal-DmzxmPwa.woff2 +0 -0
  555. package/out/web/assets/prompt-vietnamese-500-normal-w_sS_j2F.woff +0 -0
  556. package/out/web/assets/prompt-vietnamese-600-normal-1dyohtNh.woff +0 -0
  557. package/out/web/assets/prompt-vietnamese-600-normal-7QWjJBsF.woff2 +0 -0
  558. package/out/web/assets/sarabun-latin-400-normal-DmguIS-7.woff +0 -0
  559. package/out/web/assets/sarabun-latin-400-normal-URPBxl-K.woff2 +0 -0
  560. package/out/web/assets/sarabun-latin-500-normal-BjUTcdxu.woff2 +0 -0
  561. package/out/web/assets/sarabun-latin-500-normal-CJexTAnl.woff +0 -0
  562. package/out/web/assets/sarabun-latin-600-normal-9FonlkKx.woff +0 -0
  563. package/out/web/assets/sarabun-latin-600-normal-DMD3TROr.woff2 +0 -0
  564. package/out/web/assets/sarabun-latin-ext-400-normal-BbP7GpLL.woff2 +0 -0
  565. package/out/web/assets/sarabun-latin-ext-400-normal-BsmTn0SS.woff +0 -0
  566. package/out/web/assets/sarabun-latin-ext-500-normal-Cot16Bd2.woff +0 -0
  567. package/out/web/assets/sarabun-latin-ext-500-normal-DE1LysqI.woff2 +0 -0
  568. package/out/web/assets/sarabun-latin-ext-600-normal-0tXu9x_i.woff +0 -0
  569. package/out/web/assets/sarabun-latin-ext-600-normal-BRxjKUQ6.woff2 +0 -0
  570. package/out/web/assets/sarabun-thai-400-normal-C2DaJlKK.woff2 +0 -0
  571. package/out/web/assets/sarabun-thai-400-normal-CH-UbWgg.woff +0 -0
  572. package/out/web/assets/sarabun-thai-500-normal-BVssjame.woff2 +0 -0
  573. package/out/web/assets/sarabun-thai-500-normal-C3iDLEzi.woff +0 -0
  574. package/out/web/assets/sarabun-thai-600-normal-3dIAQ-_s.woff2 +0 -0
  575. package/out/web/assets/sarabun-thai-600-normal-DWKeTe6a.woff +0 -0
  576. package/out/web/assets/sarabun-vietnamese-400-normal-DPfkSCMy.woff +0 -0
  577. package/out/web/assets/sarabun-vietnamese-400-normal-igdrzAGY.woff2 +0 -0
  578. package/out/web/assets/sarabun-vietnamese-500-normal-CpRVvGRU.woff +0 -0
  579. package/out/web/assets/sarabun-vietnamese-500-normal-Dpe7Br9m.woff2 +0 -0
  580. package/out/web/assets/sarabun-vietnamese-600-normal-BMbOgAW-.woff +0 -0
  581. package/out/web/assets/sarabun-vietnamese-600-normal-DkxrOlLB.woff2 +0 -0
  582. package/out/web/assets/stateDiagram-v2-4QOOHH4V-s6P9g2_b.js +0 -1
  583. package/out/web/assets/swimlanesDiagram-VK2B7HYN-CV9fOLrV.js +0 -8
  584. package/out/web/assets/tauri-CNrHQ2Gb.js +0 -3
  585. package/out/web/assets/window-DkIhgI7B.js +0 -1
@@ -1,41 +1,34 @@
1
- import { type ChangeEvent, type CSSProperties, type FormEvent, useEffect, useRef, useState } from 'react'
2
- import { mergeActivitySnapshots, trimAgentProgress } from '../agentProgress'
1
+ import { lazy, Suspense, type ChangeEvent, type CSSProperties, type FormEvent, useCallback, useEffect, useRef, useState } from 'react'
3
2
  import {
4
- clearChatHistory,
5
- deleteChatSession,
6
- renameChatSession,
7
- getRecentInteractions,
8
- saveSystemInteraction,
9
- getRuntimeStatus,
10
- setActiveModel,
11
- listChatSessions,
12
- listSavedPictures,
13
- selectWorkspaceDirectory,
14
- selectLinkedFolderPath,
15
- saveInteractionAgentActivity,
16
- streamChatMessage,
17
- cancelChatMessage,
18
- submitToolApproval,
19
- listen,
20
- readClipboardImage,
21
- isTauriRuntime,
22
- type AgentProgress,
23
- type ChatResponse,
24
- type ChatSession,
25
- type DocumentAttachment,
26
- type PictureEntry,
27
- type RuntimeStatus,
28
- } from '@/tauri'
29
-
30
- import ChatPanel from './ChatPanel'
3
+ compactAgentProgressForPersistence,
4
+ mergeActivitySnapshots,
5
+ mergeFileChanges,
6
+ parseFileChangesFromProgress,
7
+ } from '../agentProgress'
8
+ import { catalogPlatform, conversationPlatform, mediaPlatform, runtimePlatform, type SlashResponse } from '../platform'
9
+ import type { AgentProgress, ChatResponse, ChatSession, DocumentAttachment, PictureEntry, RuntimeStatus } from '../types'
10
+
11
+ const {
12
+ clearChatHistory, deleteChatSession, renameChatSession,
13
+ getConversationSnapshot, getConversationChanges,
14
+ saveSystemInteraction, listChatSessions, updateChatSessionWorkspace, saveInteractionAgentActivity, streamChatMessage,
15
+ cancelChatMessage, submitToolApproval, listen, readClipboardImage,
16
+ } = conversationPlatform
17
+ const { getRuntimeStatus, setActiveModel, selectWorkspaceDirectory, selectLinkedFolderPath, isTauriRuntime } = runtimePlatform
18
+ const { listSavedPictures, convertFileSrc } = mediaPlatform
19
+ const {
20
+ listLearnedSkills, addLearnedSkill, deleteLearnedSkill, detectSystemTools, reauthMcpServer,
21
+ listMcpServerTools, setProfileValue, listCronJobs, addCronJob, removeCronJob,
22
+ setCronJobEnabled, listLinkedFolders, addLinkedFolder, removeLinkedFolder, runSlashCommand,
23
+ linkedFolderStatus, refreshLinkedFolder, listLinkedFolderNotes, readLinkedFolderNote, openLinkedFolderNote,
24
+ } = catalogPlatform
25
+
26
+ import ChatPanel, { type ConversationActions, type ConversationViewModel } from './ChatPanel'
27
+ import ScreenCaptureDialog from './ScreenCaptureDialog'
31
28
  import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
32
- import ImageStudioPanel from './ImageStudioPanel'
33
- import VeoStudioPanel from './VeoStudioPanel'
34
- import ModelPanel from '@/components/ModelPanel'
29
+ import DesktopTitlebar from './DesktopTitlebar'
35
30
  import type { ModelInteraction } from '@/components/ModelPanel'
36
- import PicturesLibrary from '@/components/PicturesLibrary'
37
- import WorkspacePanel from '@/components/WorkspacePanel'
38
- import { CommandPalette } from './CommandPalette'
31
+ import type { ToolSurface } from './ToolSurfacePage'
39
32
  import {
40
33
  errorMessage,
41
34
  readImage,
@@ -46,6 +39,9 @@ import {
46
39
  parseUtcDate,
47
40
  } from '../utils/ui'
48
41
  import { executeSlashCommand } from '../utils/slashCommandProcessor'
42
+ import { captureScreenForChat } from '../utils/screenCapture'
43
+ import { useConversationCoordinator } from '../conversation/useConversationCoordinator'
44
+ import { matchesActiveRun, matchesActiveSession } from '../conversation/syncView'
49
45
 
50
46
 
51
47
  const EXPRESSIONS = [
@@ -64,13 +60,27 @@ const ACCESSORIES = [
64
60
  "Hold Pen",
65
61
  ]
66
62
 
67
- import { DEFAULT_CONFIG } from '../constants/config'
63
+ import { DEFAULT_CONFIG, migrateTypographyScale } from '../constants/config'
68
64
 
69
65
  const LAST_WORKSPACE_PATH_KEY = 'mint:last-workspace-path'
66
+ const RECENT_WORKSPACE_PATHS_KEY = 'mint:recent-workspace-paths'
67
+ const MAX_RECENT_WORKSPACES = 8
70
68
  const ACTIVE_CONVERSATION_ID_KEY = 'mint:active-conversation-id'
71
69
  const LAST_ACTIVE_TIME_KEY = 'mint:last-active-timestamp'
72
70
  const DESKTOP_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 // 30 minutes
73
71
 
72
+ function readRecentWorkspacePaths(): string[] {
73
+ if (typeof window === 'undefined') return []
74
+ try {
75
+ const parsed: unknown = JSON.parse(window.localStorage.getItem(RECENT_WORKSPACE_PATHS_KEY) || '[]')
76
+ return Array.isArray(parsed)
77
+ ? [...new Set(parsed.filter((path): path is string => typeof path === 'string' && Boolean(path.trim())))].slice(0, MAX_RECENT_WORKSPACES)
78
+ : []
79
+ } catch {
80
+ return []
81
+ }
82
+ }
83
+
74
84
  function touchActiveTimestamp() {
75
85
  if (typeof window === 'undefined') return
76
86
  try {
@@ -103,25 +113,14 @@ function isDesktopRecentlyActive(): boolean {
103
113
  // Unsent composer text, stashed per conversation so switching chats or
104
114
  // reloading the page doesn't lose an in-progress message.
105
115
  const DRAFT_KEY_PREFIX = 'mint:draft:'
106
- const draftStorageKey = (id: string) => `${DRAFT_KEY_PREFIX}${id}`
107
116
  function readDraft(id: string | null | undefined): string {
108
117
  if (!id) return ''
109
118
  try {
110
- return window.localStorage.getItem(draftStorageKey(id)) || ''
119
+ return window.localStorage.getItem(`${DRAFT_KEY_PREFIX}${id}`) || ''
111
120
  } catch {
112
121
  return ''
113
122
  }
114
123
  }
115
- function writeDraft(id: string | null | undefined, value: string) {
116
- if (!id) return
117
- try {
118
- if (value) window.localStorage.setItem(draftStorageKey(id), value)
119
- else window.localStorage.removeItem(draftStorageKey(id))
120
- } catch {
121
- /* private mode / quota — draft persistence is best-effort */
122
- }
123
- }
124
-
125
124
  const SIDEBAR_DEFAULT_WIDTH = 264
126
125
  const SIDEBAR_MIN_WIDTH = 200
127
126
  const SIDEBAR_MAX_WIDTH = 420
@@ -172,6 +171,10 @@ function isRootOrNewChatRoute(): boolean {
172
171
  target.includes('imagine') ||
173
172
  target.includes('veo-studio') ||
174
173
  target.includes('veo') ||
174
+ target.includes('code') ||
175
+ target === '/cli' ||
176
+ target === 'cli' ||
177
+ target.includes('cli-sessions') ||
175
178
  target.includes('settings')
176
179
  ) {
177
180
  return false
@@ -200,7 +203,7 @@ function activeConversationId() {
200
203
  }
201
204
  }
202
205
 
203
- // When visiting Root URL ('/' or '/chat' without specific ID), always start a fresh New Chat
206
+ // When visiting Root URL ('/' or '/chat' without specific ID), always start a fresh New chat
204
207
  // (matching ChatGPT / Claude / Gemini industry-standard UX).
205
208
  // Applies to Web UI always, and Desktop App upon cold launch / inactivity / new day.
206
209
  if (isRootOrNewChatRoute()) {
@@ -234,31 +237,26 @@ const MOCK_WELCOME_INTERACTION = {
234
237
  }
235
238
 
236
239
 
237
- import SkillsView from './SkillsView'
238
- import ScheduledTasksView from './ScheduledTasksView'
239
- import LinkedFoldersView from './LinkedFoldersView'
240
- import McpServersView from './McpServersView'
241
- import PluginsView from './PluginsView'
242
240
  import { isSupportedDocument } from '../utils/documentTypes'
243
241
  import { useCompanionWidget } from '@/companionWidget'
244
- import {
245
- listLearnedSkills,
246
- addLearnedSkill,
247
- deleteLearnedSkill,
248
- detectSystemTools,
249
- reauthMcpServer,
250
- listMcpServerTools,
251
- setProfileValue,
252
- listCronJobs,
253
- addCronJob,
254
- removeCronJob,
255
- setCronJobEnabled,
256
- listLinkedFolders,
257
- addLinkedFolder,
258
- removeLinkedFolder,
259
- runSlashCommand,
260
- } from '@/tauri'
261
- import type { SlashResponse } from '../platform'
242
+
243
+ const ModelPanel = lazy(() => import('@/components/ModelPanel'))
244
+ const WorkspacePanel = lazy(() => import('@/components/WorkspacePanel'))
245
+ const ToolSurfacePage = lazy(() => import('./ToolSurfacePage'))
246
+ const SkillsView = lazy(() => import('./SkillsView'))
247
+ const ScheduledTasksView = lazy(() => import('./ScheduledTasksView'))
248
+ const LinkedFoldersView = lazy(() => import('./LinkedFoldersView'))
249
+ const McpServersView = lazy(() => import('./McpServersView'))
250
+ const PluginsView = lazy(() => import('./PluginsView'))
251
+ const CliSessionsView = lazy(() => import('./CliSessionsView'))
252
+ const PicturesLibrary = lazy(() => import('@/components/PicturesLibrary'))
253
+ const ImageStudioPanel = lazy(() => import('./ImageStudioPanel'))
254
+ const VeoStudioPanel = lazy(() => import('./VeoStudioPanel'))
255
+ const CommandPalette = lazy(() => import('./CommandPalette').then(({ CommandPalette: Component }) => ({ default: Component })))
256
+
257
+ function LazyPanelFallback() {
258
+ return <div className="auth-gate-loading" style={{ minHeight: 160 }}>Loading panel…</div>
259
+ }
262
260
 
263
261
  function getInitialViewFromUrl(): DashboardView {
264
262
  if (typeof window === 'undefined') return 'chat'
@@ -272,6 +270,7 @@ function getInitialViewFromUrl(): DashboardView {
272
270
  if (target.includes('picture')) return 'pictures'
273
271
  if (target.includes('image-studio') || target.includes('imagine')) return 'imagine'
274
272
  if (target.includes('veo-studio') || target.includes('veo')) return 'veo'
273
+ if (target.includes('code') || target === '/cli' || target === 'cli' || target.includes('cli-sessions')) return 'code'
275
274
  return 'chat'
276
275
  }
277
276
 
@@ -282,6 +281,7 @@ function getCleanPathForView(v: string, activeId?: string): string {
282
281
  if (v === 'pictures') return '/pictures'
283
282
  if (v === 'imagine') return '/image-studio'
284
283
  if (v === 'veo' || v === 'veo_studio') return '/veo-studio'
284
+ if (v === 'code') return '/code'
285
285
  if (v === 'settings') return '/settings'
286
286
  if (activeId) return `/chat/${encodeURIComponent(activeId)}`
287
287
  return '/chat'
@@ -331,13 +331,103 @@ export default function MintDashboard() {
331
331
  const [view, setViewState] = useState<DashboardView>(getInitialViewFromUrl)
332
332
  const [conversationId, setConversationId] = useState(activeConversationId)
333
333
  // Declared here (not further down with the rest of the workspace-related
334
- // state) so the URL-change effect below can read it — that effect closes
335
- // over `workspacePath` to scope its own `getRecentInteractions` call.
336
- const [workspacePath, setWorkspacePath] = useState(() => window.localStorage.getItem(LAST_WORKSPACE_PATH_KEY) || '')
334
+ // state) so the URL-change effect below can read it.
335
+ const { conversation, actions: conversationActions } = useConversationCoordinator(
336
+ {
337
+ workspacePath: window.localStorage.getItem(LAST_WORKSPACE_PATH_KEY) || '',
338
+ message: readDraft(conversationId),
339
+ },
340
+ {
341
+ conversationId,
342
+ draftKeyPrefix: DRAFT_KEY_PREFIX,
343
+ workspaceStorageKey: LAST_WORKSPACE_PATH_KEY,
344
+ },
345
+ )
346
+ const {
347
+ workspacePath, message, imageAttachments, videoAttachments, documentAttachment,
348
+ sending, sendingMessage, sendingImageCount, sendingVideoCount, streamedReply,
349
+ streamedResponse, agentProgress,
350
+ } = conversation
351
+ const activeConversationRef = useRef(conversationId)
352
+ const sessionGenerationRef = useRef(0)
353
+ const runGenerationRef = useRef(0)
354
+ const conversationCursorRef = useRef<{ chatId: string; cursor: number } | null>(null)
355
+ const historyLoadSerialRef = useRef(0)
356
+ const [pendingApprovals, setPendingApprovals] = useState<Record<string, { token: string; approval: any }>>({})
357
+ const autoApprovedChatIdRef = useRef<string | null>(null)
358
+ const isCurrentSession = (chatId: string, generation: number) => matchesActiveSession(
359
+ { chatId, generation },
360
+ { chatId: activeConversationRef.current, generation: sessionGenerationRef.current },
361
+ )
362
+ const activateConversation = (id: string) => {
363
+ if (activeConversationRef.current !== id) {
364
+ activeConversationRef.current = id
365
+ sessionGenerationRef.current += 1
366
+ runGenerationRef.current += 1
367
+ conversationCursorRef.current = null
368
+ setInteractions([])
369
+ setHasOlderInteractions(false)
370
+ setAgentActivitySnapshots({})
371
+ setStreamingConversationId(null)
372
+ setActiveTurnId(null)
373
+ }
374
+ setConversationId(id)
375
+ }
376
+ async function loadConversationHistory(chatId: string, generation: number) {
377
+ const serial = ++historyLoadSerialRef.current
378
+ const snapshot = await getConversationSnapshot(chatId, null, 50)
379
+ if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return
380
+ const currentCursor = conversationCursorRef.current
381
+ if (currentCursor?.chatId === chatId && currentCursor.cursor > snapshot.cursor) return
382
+ conversationCursorRef.current = { chatId, cursor: snapshot.cursor }
383
+ setInteractions((current) => {
384
+ if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return current
385
+ if (conversationCursorRef.current?.chatId === chatId
386
+ && conversationCursorRef.current.cursor > snapshot.cursor) return current
387
+ return snapshot.interactions
388
+ })
389
+ setHasOlderInteractions((current) => {
390
+ if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return current
391
+ if (conversationCursorRef.current?.chatId === chatId
392
+ && conversationCursorRef.current.cursor > snapshot.cursor) return current
393
+ return snapshot.hasOlder
394
+ })
395
+ setAgentActivitySnapshots((current) => {
396
+ if (!isCurrentSession(chatId, generation) || serial !== historyLoadSerialRef.current) return current
397
+ if (conversationCursorRef.current?.chatId === chatId
398
+ && conversationCursorRef.current.cursor > snapshot.cursor) return current
399
+ return mergeActivitySnapshots(current, snapshot.interactions)
400
+ })
401
+ }
337
402
  // Web only in practice (desktop's window has no mobile-width breakpoint,
338
403
  // so nothing ever sets this true there) — declared unconditionally so
339
404
  // `changeView` can close it on every navigation without branching.
340
405
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
406
+ const [screenCaptureOpen, setScreenCaptureOpen] = useState(false)
407
+
408
+ useEffect(() => {
409
+ if (!mobileSidebarOpen) return
410
+ const closeOnEscape = (event: KeyboardEvent) => {
411
+ if (event.key === 'Escape') setMobileSidebarOpen(false)
412
+ }
413
+ window.addEventListener('keydown', closeOnEscape)
414
+ return () => window.removeEventListener('keydown', closeOnEscape)
415
+ }, [mobileSidebarOpen])
416
+ const [workspaceRefreshRevision, setWorkspaceRefreshRevision] = useState(0)
417
+ const [recentWorkspacePaths, setRecentWorkspacePaths] = useState<string[]>(readRecentWorkspacePaths)
418
+
419
+ useEffect(() => {
420
+ const currentPath = workspacePath.trim()
421
+ if (!currentPath) return
422
+ const recent = [currentPath, ...readRecentWorkspacePaths().filter((path) => path !== currentPath)]
423
+ .slice(0, MAX_RECENT_WORKSPACES)
424
+ try {
425
+ window.localStorage.setItem(RECENT_WORKSPACE_PATHS_KEY, JSON.stringify(recent))
426
+ } catch {
427
+ /* Keep the in-memory recent list usable when storage is unavailable. */
428
+ }
429
+ setRecentWorkspacePaths(recent)
430
+ }, [workspacePath])
341
431
 
342
432
  const changeView = (newView: any, targetConversationId?: string) => {
343
433
  setMobileSidebarOpen(false)
@@ -376,23 +466,17 @@ export default function MintDashboard() {
376
466
  const urlSessionId = getConversationIdFromUrl()
377
467
  if (urlSessionId && urlSessionId !== conversationId) {
378
468
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, urlSessionId)
379
- setConversationId(urlSessionId)
380
- getRecentInteractions(50, urlSessionId, workspacePath || null).then((history) => {
381
- const reversed = history.reverse()
382
- setInteractions(reversed)
383
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
469
+ activateConversation(urlSessionId)
470
+ const generation = sessionGenerationRef.current
471
+ loadConversationHistory(urlSessionId, generation).catch((error) => {
472
+ console.error('Failed to load conversation history:', error)
384
473
  })
385
474
  } else if (!urlSessionId && isRootOrNewChatRoute()) {
386
475
  const next = createConversationId()
387
- setConversationId(next)
476
+ activateConversation(next)
388
477
  setInteractions([])
389
478
  setAgentActivitySnapshots({})
390
- setStreamedReply('')
391
- setStreamedResponse(null)
392
- setMessage('')
393
- setImageAttachments([])
394
- setDocumentAttachment(null)
395
- setAgentProgress([])
479
+ conversationActions.switchSession()
396
480
  }
397
481
  }
398
482
  window.addEventListener('popstate', handleUrlChange)
@@ -417,36 +501,47 @@ export default function MintDashboard() {
417
501
  }, [])
418
502
  const [status, setStatus] = useState<RuntimeStatus | null>(null)
419
503
  const [error, setError] = useState('')
420
- const [message, setMessage] = useState(() => readDraft(conversationId))
421
504
  // When `conversationId` changes we swap `message` to that chat's saved draft;
422
505
  // this ref tells the persist effect to skip the render right after that swap,
423
506
  // so the outgoing chat's text is never written under the incoming chat's key.
424
- const skipDraftPersistRef = useRef(false)
425
507
  const [interactions, setInteractions] = useState<any[]>([])
508
+ const [hasOlderInteractions, setHasOlderInteractions] = useState(false)
426
509
  const [pictures, setPictures] = useState<PictureEntry[]>([])
427
- const [sending, setSending] = useState(false)
428
- const [sendingMessage, setSendingMessage] = useState('')
429
- const [sendingImageCount, setSendingImageCount] = useState(0)
430
- const [sendingVideoCount, setSendingVideoCount] = useState(0)
431
- const [streamedReply, setStreamedReply] = useState('')
432
- const [streamedResponse, setStreamedResponse] = useState<ChatResponse | null>(null)
433
510
  const [streamingConversationId, setStreamingConversationId] = useState<string | null>(null)
434
- const [agentProgress, setAgentProgress] = useState<AgentProgress[]>([])
511
+ const [activeTurnId, setActiveTurnId] = useState<number | null>(null)
435
512
  const [agentActivitySnapshots, setAgentActivitySnapshots] = useState<Record<string, AgentProgress[]>>({})
436
513
  const [thinkingExpanded, setThinkingExpanded] = useState<Record<string, boolean>>({})
437
514
  const liveThinkingOpenRef = useRef(true)
438
- const [imageAttachments, setImageAttachments] = useState<Array<{ dataUri: string; name: string; previewDataUri?: string }>>([])
439
- const [videoAttachments, setVideoAttachments] = useState<Array<{ dataUri: string; name: string }>>([])
440
- const [documentAttachment, setDocumentAttachment] = useState<DocumentAttachment | null>(null)
441
- const [pendingApproval, setPendingApproval] = useState<any | null>(null)
515
+ const liveExtendedThinkingOpenRef = useRef(true)
442
516
  const [sessionAutoApproved, setSessionAutoApproved] = useState(false)
443
517
  const sessionAutoApprovedRef = useRef(false)
444
518
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => window.localStorage.getItem('mint:sidebar-collapsed') === 'true')
519
+ const [toolSurfaces, setToolSurfaces] = useState<ToolSurface[]>([])
520
+ const [activeSurfaceId, setActiveSurfaceId] = useState<string | null>(null)
521
+ const [toolsPanelOpen, setToolsPanelOpen] = useState(false)
522
+ const [terminalRightWidth, setTerminalRightWidth] = useState(() => {
523
+ const saved = Number(window.localStorage.getItem('mint:terminal-right-width'))
524
+ return saved >= 280 && saved <= 900 ? saved : 440
525
+ })
526
+ const terminalSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
445
527
  const [sidebarWidth, setSidebarWidth] = useState(() => {
446
528
  const saved = Number(window.localStorage.getItem('mint:sidebar-width'))
447
529
  return saved >= SIDEBAR_MIN_WIDTH && saved <= SIDEBAR_MAX_WIDTH ? saved : SIDEBAR_DEFAULT_WIDTH
448
530
  })
449
- const [smartContext, setSmartContext] = useState(() => window.localStorage.getItem('mint:smart-context') !== 'false')
531
+ const recentAgentChanges = parseFileChangesFromProgress(agentProgress)
532
+ const conversationReviewChanges = mergeFileChanges([
533
+ ...interactions.map((interaction) => {
534
+ const interactionId = String(interaction.id)
535
+ return parseFileChangesFromProgress(agentActivitySnapshots[interactionId] ?? interaction.agentActivity ?? [])
536
+ }),
537
+ parseFileChangesFromProgress(agentProgress),
538
+ ])
539
+ const workspaceSourceNames = [
540
+ ...imageAttachments.map((attachment) => attachment.name),
541
+ ...videoAttachments.map((attachment) => attachment.name),
542
+ documentAttachment?.filename || '',
543
+ ]
544
+
450
545
  const [agentMode, setAgentMode] = useState(() => window.localStorage.getItem('mint:agent-mode') === 'true')
451
546
  const [planMode, setPlanMode] = useState(() => window.localStorage.getItem('mint:plan-mode') === 'true')
452
547
  const [toastMessage, setToastMessage] = useState('')
@@ -598,55 +693,109 @@ export default function MintDashboard() {
598
693
  // than the effect's own dependency array so a poll landing doesn't tear
599
694
  // down and recreate the interval on every tick.
600
695
  const interactionsRef = useRef(interactions)
696
+ const chatPollInFlightRef = useRef<{ chatId: string; generation: number } | null>(null)
601
697
  useEffect(() => {
602
698
  interactionsRef.current = interactions
603
699
  }, [interactions])
700
+ useEffect(() => {
701
+ if (interactions.length === 0) setHasOlderInteractions(false)
702
+ }, [interactions.length])
604
703
 
605
704
  // Load the saved draft whenever the active conversation changes. Declared
606
705
  // before the persist effect so it runs first within the same commit. The
607
706
  // first run is a no-op: `message`'s initial value already came from the
608
707
  // lazy `useState` initializer above.
609
- const draftLoadedRef = useRef(false)
610
- useEffect(() => {
611
- if (!draftLoadedRef.current) {
612
- draftLoadedRef.current = true
613
- return
614
- }
615
- skipDraftPersistRef.current = true
616
- setMessage(readDraft(conversationId))
617
- }, [conversationId])
618
-
619
- // Persist the composer text for the current conversation (debounced).
620
- useEffect(() => {
621
- if (skipDraftPersistRef.current) {
622
- skipDraftPersistRef.current = false
623
- return
624
- }
625
- const timer = window.setTimeout(() => writeDraft(conversationId, message), 300)
626
- return () => window.clearTimeout(timer)
627
- }, [message, conversationId])
628
-
629
708
  useEffect(() => {
630
709
  if (view !== 'chat' || !conversationId) return
631
- const interval = window.setInterval(async () => {
632
- // Skip while backgrounded (nothing to show anyone) or mid-send (a
633
- // fetch landing here would clobber the in-flight optimistic/streamed
634
- // reply with stale history).
635
- if (document.visibilityState !== 'visible' || sending) return
710
+ let disposed = false
711
+ const generation = sessionGenerationRef.current
712
+ const isCurrent = () => !disposed && isCurrentSession(conversationId, generation)
713
+ const syncHistory = async () => {
714
+ // A shared SQLite database has no cross-process event bus. Refresh as
715
+ // soon as the user returns, but avoid work while hidden or overlapping
716
+ // a slow read with the next polling tick.
717
+ if (
718
+ !isCurrent() ||
719
+ document.visibilityState !== 'visible' ||
720
+ (chatPollInFlightRef.current?.chatId === conversationId
721
+ && chatPollInFlightRef.current.generation === generation)
722
+ ) return
723
+
724
+ const poll = { chatId: conversationId, generation }
725
+ chatPollInFlightRef.current = poll
636
726
  try {
637
- const history = (await getRecentInteractions(50, conversationId, workspacePath || null)).reverse()
638
- const current = interactionsRef.current
639
- const currentLast = current[current.length - 1]
640
- const nextLast = history[history.length - 1]
641
- if (current.length === history.length && currentLast?.id === nextLast?.id) return
642
- setInteractions(history)
643
- setAgentActivitySnapshots((snapshots) => mergeActivitySnapshots(snapshots, history))
727
+ if (conversationCursorRef.current?.chatId !== conversationId) {
728
+ await loadConversationHistory(conversationId, generation)
729
+ return
730
+ }
731
+ let cursor = conversationCursorRef.current.cursor
732
+ let changed = false
733
+ for (let page = 0; page < 10; page++) {
734
+ const batch = await getConversationChanges(conversationId, cursor, 100)
735
+ if (!isCurrent()) return
736
+ if (conversationCursorRef.current?.chatId === conversationId
737
+ && conversationCursorRef.current.cursor > batch.cursor) return
738
+ cursor = batch.cursor
739
+ if (batch.changes.length > 0) {
740
+ changed = true
741
+ setInteractions((current) => {
742
+ if (!isCurrent()) return current
743
+ if (conversationCursorRef.current?.chatId === conversationId
744
+ && conversationCursorRef.current.cursor > batch.cursor) return current
745
+ const byId = new Map(current.map((interaction) => [interaction.id, interaction]))
746
+ for (const change of batch.changes) {
747
+ if (change.interaction) byId.set(change.interactionId, change.interaction)
748
+ else byId.delete(change.interactionId)
749
+ }
750
+ return Array.from(byId.values()).sort((a, b) => a.id - b.id)
751
+ })
752
+ setAgentActivitySnapshots((current) => {
753
+ if (!isCurrent()) return current
754
+ if (conversationCursorRef.current?.chatId === conversationId
755
+ && conversationCursorRef.current.cursor > batch.cursor) return current
756
+ const merged = { ...current }
757
+ for (const change of batch.changes) {
758
+ const key = String(change.interactionId)
759
+ if (change.interaction) {
760
+ if (change.interaction.agentActivity && Array.isArray(change.interaction.agentActivity)) {
761
+ merged[key] = change.interaction.agentActivity
762
+ }
763
+ } else {
764
+ delete merged[key]
765
+ }
766
+ }
767
+ return merged
768
+ })
769
+ }
770
+ if (!batch.hasMore) break
771
+ }
772
+ if (conversationCursorRef.current?.chatId === conversationId
773
+ && conversationCursorRef.current.cursor > cursor) return
774
+ conversationCursorRef.current = { chatId: conversationId, cursor }
775
+ if (changed) {
776
+ const sessions = await listChatSessions()
777
+ if (isCurrent()) setChatSessions(sessions)
778
+ }
644
779
  } catch {
645
780
  // Best-effort — a transient fetch failure just waits for the next tick.
781
+ } finally {
782
+ if (chatPollInFlightRef.current === poll) chatPollInFlightRef.current = null
646
783
  }
647
- }, 3000)
648
- return () => window.clearInterval(interval)
649
- }, [view, conversationId, sending, workspacePath])
784
+ }
785
+
786
+ const syncWhenActive = () => { void syncHistory() }
787
+ syncWhenActive()
788
+ window.addEventListener('focus', syncWhenActive)
789
+ document.addEventListener('visibilitychange', syncWhenActive)
790
+ const interval = window.setInterval(syncWhenActive, 1500)
791
+
792
+ return () => {
793
+ disposed = true
794
+ window.clearInterval(interval)
795
+ window.removeEventListener('focus', syncWhenActive)
796
+ document.removeEventListener('visibilitychange', syncWhenActive)
797
+ }
798
+ }, [view, conversationId, workspacePath])
650
799
 
651
800
  const filteredSessions = chatSessions.filter((session) => {
652
801
  if (session.kind === 'cli' || session.id === 'conversation-default') return false
@@ -700,16 +849,81 @@ export default function MintDashboard() {
700
849
 
701
850
 
702
851
  async function refreshHistory() {
703
- const history = await getRecentInteractions(50, conversationId, workspacePath || null)
704
- const reversed = history.reverse()
705
- setInteractions(reversed)
706
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
852
+ const chatId = conversationId
853
+ const generation = sessionGenerationRef.current
854
+ await loadConversationHistory(chatId, generation)
707
855
  }
708
856
 
709
- async function refreshChatSessions() {
710
- const sessions = await listChatSessions()
711
- setChatSessions(sessions)
712
- }
857
+ const refreshChatSessions = useCallback(async () => {
858
+ try {
859
+ const sessions = await listChatSessions()
860
+ setChatSessions((prev) => {
861
+ if (
862
+ prev.length === sessions.length &&
863
+ prev.every(
864
+ (s, i) =>
865
+ s.id === sessions[i]?.id &&
866
+ s.updatedAt === sessions[i]?.updatedAt &&
867
+ s.title === sessions[i]?.title &&
868
+ s.messageCount === sessions[i]?.messageCount &&
869
+ s.totalBytes === sessions[i]?.totalBytes
870
+ )
871
+ ) {
872
+ return prev
873
+ }
874
+ return sessions
875
+ })
876
+ } catch (e) {
877
+ console.error('Failed to refresh chat sessions:', e)
878
+ }
879
+ }, [])
880
+
881
+ useEffect(() => {
882
+ const refreshVisible = () => {
883
+ if (document.visibilityState === 'visible') void refreshChatSessions()
884
+ }
885
+ const interval = window.setInterval(refreshVisible, 5000)
886
+ window.addEventListener('focus', refreshVisible)
887
+ document.addEventListener('visibilitychange', refreshVisible)
888
+ return () => {
889
+ window.clearInterval(interval)
890
+ window.removeEventListener('focus', refreshVisible)
891
+ document.removeEventListener('visibilitychange', refreshVisible)
892
+ }
893
+ }, [refreshChatSessions])
894
+
895
+ const loadOlderInteractions = useCallback(async () => {
896
+ const generation = sessionGenerationRef.current
897
+ const firstId = interactionsRef.current[0]?.id
898
+ if (firstId == null) return
899
+ const page = await getConversationSnapshot(conversationId, firstId, 50)
900
+ if (!isCurrentSession(conversationId, generation)) return
901
+ setHasOlderInteractions(page.hasOlder)
902
+ if (page.interactions.length === 0) return
903
+ setInteractions((current) => {
904
+ if (!isCurrentSession(conversationId, generation)) return current
905
+ const byId = new Map([...page.interactions, ...current].map((interaction) => [interaction.id, interaction]))
906
+ return Array.from(byId.values()).sort((a, b) => a.id - b.id)
907
+ })
908
+ }, [conversationId])
909
+
910
+ const handleUpdateSessionWorkspace = useCallback(async (sessionId: string, targetPath: string | null) => {
911
+ try {
912
+ await updateChatSessionWorkspace(sessionId, targetPath)
913
+ await refreshChatSessions()
914
+ } catch (e) {
915
+ console.error('Failed to update session workspace:', e)
916
+ }
917
+ }, [refreshChatSessions])
918
+
919
+ const handleBrowseFolderForMove = useCallback(async () => {
920
+ try {
921
+ const selected = await selectWorkspaceDirectory()
922
+ return selected || null
923
+ } catch {
924
+ return null
925
+ }
926
+ }, [])
713
927
 
714
928
  const [picturesRefreshing, setPicturesRefreshing] = useState(false)
715
929
 
@@ -729,8 +943,9 @@ export default function MintDashboard() {
729
943
  refreshChatSessions(),
730
944
  window.settingsApi?.getSettings()
731
945
  .then((loaded: any) => {
732
- setSettingsConfig(loaded)
733
- applyThemeStyles({ ...DEFAULT_CONFIG, ...loaded })
946
+ const migrated = migrateTypographyScale(loaded)
947
+ setSettingsConfig(migrated)
948
+ applyThemeStyles({ ...DEFAULT_CONFIG, ...migrated })
734
949
  }),
735
950
  ]).then((results) => {
736
951
  const failure = results.find((result) => result.status === 'rejected')
@@ -739,21 +954,22 @@ export default function MintDashboard() {
739
954
  })
740
955
  const unlistenSpotlight = window.api.onSpotlightToChat((query) => {
741
956
  changeView('chat')
742
- setMessage(query)
957
+ conversationActions.compose(query)
743
958
  })
744
959
  const unlistenVision = window.api.onVisionReady((image) => {
745
960
  createTrimmedImagePreview(image)
746
961
  .catch(() => image)
747
962
  .then((previewDataUri) => {
748
- setImageAttachments((current) => [...current, { dataUri: image, previewDataUri, name: 'Screen capture' }])
963
+ conversationActions.attachImage({ dataUri: image, previewDataUri, name: 'Screen capture' })
749
964
  })
750
965
  })
751
966
  const handleWindowFocus = () => {
752
967
  getRuntimeStatus().then(setStatus).catch(() => {})
753
968
  window.settingsApi?.getSettings?.().then((loaded: any) => {
754
969
  if (loaded) {
755
- setSettingsConfig(loaded)
756
- applyThemeStyles(loaded)
970
+ const migrated = migrateTypographyScale(loaded)
971
+ setSettingsConfig(migrated)
972
+ applyThemeStyles(migrated)
757
973
  }
758
974
  }).catch(() => {})
759
975
  window.api?.clearAiNotifications?.()
@@ -761,18 +977,21 @@ export default function MintDashboard() {
761
977
  window.addEventListener('focus', handleWindowFocus)
762
978
 
763
979
  window.api?.onSettingsChanged?.((loaded: any) => {
764
- setSettingsConfig(loaded)
765
- applyThemeStyles(loaded)
980
+ const migrated = migrateTypographyScale(loaded)
981
+ setSettingsConfig(migrated)
982
+ applyThemeStyles(migrated)
766
983
  getRuntimeStatus().then(setStatus).catch(() => {})
767
984
  })
768
985
 
769
- const unlistenPromise = listen<any>('tool-approval-requested', (event) => {
770
- if (sessionAutoApprovedRef.current) {
986
+ const unlistenPromise = listen('tool-approval-requested', (event: { payload: any }) => {
987
+ const chatId = event.payload.chatId as string | undefined
988
+ if (!chatId) return
989
+ if (sessionAutoApprovedRef.current && autoApprovedChatIdRef.current === chatId) {
771
990
  submitToolApproval(event.payload.token, true).catch((err) => {
772
991
  console.error("Auto approval failed:", err)
773
992
  })
774
993
  } else {
775
- setPendingApproval(event.payload)
994
+ setPendingApprovals((current) => ({ ...current, [chatId]: event.payload }))
776
995
  notifyPendingApproval(event.payload)
777
996
  }
778
997
  })
@@ -797,15 +1016,145 @@ export default function MintDashboard() {
797
1016
  if (view === 'workspace' && !agentMode) updateAgentMode(true)
798
1017
  }, [view, agentMode])
799
1018
 
800
- useEffect(() => {
801
- chatEnd.current?.scrollIntoView({ behavior: 'smooth' })
802
- }, [interactions, sending, streamedReply, pendingApproval, agentProgress])
803
-
804
1019
  const showToast = (nextMessage: string) => {
805
1020
  setToastMessage(nextMessage)
806
1021
  setTimeout(() => setToastMessage((current) => current === nextMessage ? '' : current), 3000)
807
1022
  }
808
1023
 
1024
+ const openMintBrowser = async (url: string) => {
1025
+ if (!isDesktopApp) {
1026
+ window.open(url, '_blank', 'noopener,noreferrer')
1027
+ return
1028
+ }
1029
+ try {
1030
+ showToast('Opening Co-browsing browser (Mint Auto)...')
1031
+ const { invoke } = await import('@tauri-apps/api/core')
1032
+ await invoke('open_mint_browser', { url })
1033
+ } catch (error) {
1034
+ showToast(`Could not open browser: ${String(error)}`)
1035
+ }
1036
+ }
1037
+
1038
+ const openTerminalSurface = () => {
1039
+ setToolsPanelOpen(true)
1040
+ const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
1041
+ setToolSurfaces((current) => {
1042
+ const terminalCount = current.filter((surface) => surface.kind === 'terminal').length
1043
+ let baseTitle = 'terminal'
1044
+ if (workspacePath && workspacePath.trim()) {
1045
+ const cleanPath = workspacePath.trim().replace(/[\\/]+$/, '')
1046
+ const folderName = cleanPath.split(/[\\/]/).pop()
1047
+ if (folderName) baseTitle = folderName
1048
+ }
1049
+ const title = terminalCount === 0 ? baseTitle : `${baseTitle} (${terminalCount + 1})`
1050
+ const next = { id, kind: 'terminal' as const, title }
1051
+ setActiveSurfaceId(id)
1052
+ return [...current, next]
1053
+ })
1054
+ }
1055
+
1056
+ const openNativeBrowser = (url = 'https://www.google.com') => {
1057
+ void openMintBrowser(url)
1058
+ }
1059
+
1060
+ const duplicateToolSurface = (id: string) => {
1061
+ setToolSurfaces((current) => {
1062
+ const source = current.find((surface) => surface.id === id)
1063
+ if (!source) return current
1064
+ const duplicateId = `${source.kind}:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
1065
+ const duplicate = { ...source, id: duplicateId, title: `${source.title} copy` } as ToolSurface
1066
+ setActiveSurfaceId(duplicateId)
1067
+ return [...current, duplicate]
1068
+ })
1069
+ }
1070
+
1071
+ const renameToolSurface = (id: string) => {
1072
+ const source = toolSurfaces.find((surface) => surface.id === id)
1073
+ if (!source) return
1074
+ const nextTitle = window.prompt('Rename tab', source.title)?.trim()
1075
+ if (!nextTitle) return
1076
+ setToolSurfaces((current) => current.map((surface) => surface.id === id ? { ...surface, title: nextTitle } : surface))
1077
+ }
1078
+
1079
+ const closeOtherToolSurfaces = (id: string) => {
1080
+ setToolSurfaces((current) => current.filter((surface) => surface.id === id))
1081
+ setActiveSurfaceId(id)
1082
+ }
1083
+
1084
+ const toggleTerminalSurface = () => {
1085
+ setToolSurfaces((current) => {
1086
+ const terminal = current.find((surface) => surface.kind === 'terminal')
1087
+ if (!terminal) {
1088
+ const id = `terminal:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
1089
+ setActiveSurfaceId(id)
1090
+ return [...current, { id, kind: 'terminal' as const, title: 'Terminal' }]
1091
+ }
1092
+ setActiveSurfaceId((active) => active === terminal.id ? null : terminal.id)
1093
+ return current
1094
+ })
1095
+ }
1096
+
1097
+ const checkForUpdatesFromMenu = async () => {
1098
+ try {
1099
+ const update = await window.settingsApi.checkForUpdates()
1100
+ showToast(update?.available ? `Mint ${update.version} is available.` : 'Mint is up to date.')
1101
+ } catch (error) {
1102
+ showToast(`Could not check for updates: ${String(error)}`)
1103
+ }
1104
+ }
1105
+
1106
+ const openArtifactSurface = (artifact: import('./ArtifactPreviewPanel').ArtifactFile) => {
1107
+ setToolsPanelOpen(true)
1108
+ const fileName = artifact.path.replace(/\\/g, '/').split('/').pop() || artifact.path
1109
+ const id = `preview:${artifact.path}`
1110
+ setToolSurfaces((current) => {
1111
+ const next = current.filter((surface) => surface.id !== id)
1112
+ return [...next, { id, kind: 'preview', title: fileName, artifact }]
1113
+ })
1114
+ setActiveSurfaceId(id)
1115
+ }
1116
+
1117
+ const openReviewSurface = (review: Extract<ToolSurface, { kind: 'review' }>) => {
1118
+ setToolsPanelOpen(true)
1119
+ setToolSurfaces((current) => [...current.filter((surface) => surface.id !== review.id), review])
1120
+ setActiveSurfaceId(review.id)
1121
+ }
1122
+
1123
+ const openConversationReview = () => {
1124
+ setToolsPanelOpen(true)
1125
+ setToolSurfaces((current) => {
1126
+ const reviewCount = current.filter((surface) => surface.kind === 'review').length
1127
+ const id = `review:${Date.now()}:${Math.random().toString(36).slice(2, 7)}`
1128
+ const title = reviewCount === 0 ? 'Review' : `Review (${reviewCount + 1})`
1129
+ const next = {
1130
+ id,
1131
+ kind: 'review' as const,
1132
+ title,
1133
+ reviewTitle: 'All conversation changes',
1134
+ changes: conversationReviewChanges,
1135
+ }
1136
+ setActiveSurfaceId(id)
1137
+ return [...current, next]
1138
+ })
1139
+ }
1140
+
1141
+ const closeToolSurface = (id: string) => {
1142
+ setToolSurfaces((current) => {
1143
+ const index = current.findIndex((surface) => surface.id === id)
1144
+ const next = current.filter((surface) => surface.id !== id)
1145
+ setActiveSurfaceId((active) => {
1146
+ if (active !== id) return active
1147
+ return next[Math.max(0, index - 1)]?.id ?? null
1148
+ })
1149
+ return next
1150
+ })
1151
+ }
1152
+
1153
+ const closeAllToolSurfaces = () => {
1154
+ setToolSurfaces([])
1155
+ setActiveSurfaceId(null)
1156
+ }
1157
+
809
1158
  const toggleSidebar = () => {
810
1159
  if (window.innerWidth <= 760) {
811
1160
  setMobileSidebarOpen(false)
@@ -835,11 +1184,6 @@ export default function MintDashboard() {
835
1184
  window.localStorage.setItem('mint:sidebar-width', String(clamped))
836
1185
  }
837
1186
 
838
- const updateSmartContext = (enabled: boolean) => {
839
- window.localStorage.setItem('mint:smart-context', String(enabled))
840
- setSmartContext(enabled)
841
- }
842
-
843
1187
  const updateAgentMode = (enabled: boolean) => {
844
1188
  window.localStorage.setItem('mint:agent-mode', String(enabled))
845
1189
  setAgentMode(enabled)
@@ -852,48 +1196,56 @@ export default function MintDashboard() {
852
1196
 
853
1197
  const updateWorkspacePath = (path: string) => {
854
1198
  const next = path.trim()
855
- if (next) {
856
- window.localStorage.setItem(LAST_WORKSPACE_PATH_KEY, next)
857
- } else {
858
- window.localStorage.removeItem(LAST_WORKSPACE_PATH_KEY)
859
- }
860
- setWorkspacePath(next)
1199
+ conversationActions.selectWorkspace(next)
861
1200
  }
862
1201
 
863
1202
  useEffect(() => {
864
1203
  if (!sending) {
865
1204
  sessionAutoApprovedRef.current = false
1205
+ autoApprovedChatIdRef.current = null
866
1206
  setSessionAutoApproved(false)
867
1207
  }
868
1208
  }, [sending])
869
1209
 
870
1210
  async function handleApproval(approved: boolean, autoApproveSession = false, answer?: string) {
1211
+ const chatId = conversationId
1212
+ const pendingApproval = pendingApprovals[chatId]
871
1213
  if (!pendingApproval) return
872
1214
  try {
873
1215
  if (autoApproveSession) {
874
1216
  sessionAutoApprovedRef.current = true
1217
+ autoApprovedChatIdRef.current = chatId
875
1218
  setSessionAutoApproved(true)
876
1219
  }
877
1220
  await submitToolApproval(pendingApproval.token, approved, answer)
1221
+ setPendingApprovals((current) => {
1222
+ if (current[chatId]?.token !== pendingApproval.token) return current
1223
+ const next = { ...current }
1224
+ delete next[chatId]
1225
+ return next
1226
+ })
878
1227
  } catch (reason) {
879
1228
  setError(errorMessage(reason))
880
- } finally {
881
- setPendingApproval(null)
882
1229
  }
883
1230
  }
884
1231
 
885
1232
  async function handleCancelMessage() {
886
1233
  if (!sending || !streamingConversationId) return
1234
+ runGenerationRef.current += 1
887
1235
  try {
888
- await cancelChatMessage(streamingConversationId)
1236
+ await conversationActions.executeCancellation(() =>
1237
+ cancelChatMessage(streamingConversationId),
1238
+ )
889
1239
  } catch (e) {
890
1240
  console.error("Failed to cancel message stream:", e)
891
1241
  } finally {
892
- setSending(false)
1242
+ setPendingApprovals((current) => {
1243
+ const next = { ...current }
1244
+ delete next[streamingConversationId]
1245
+ return next
1246
+ })
893
1247
  setStreamingConversationId(null)
894
- setSendingMessage('')
895
- setSendingImageCount(0)
896
- setSendingVideoCount(0)
1248
+ setActiveTurnId(null)
897
1249
  }
898
1250
  }
899
1251
 
@@ -911,6 +1263,17 @@ export default function MintDashboard() {
911
1263
  } = {},
912
1264
  ) {
913
1265
  if (sending) return
1266
+ const originChatId = conversationId
1267
+ const originWorkspacePath = workspacePath || null
1268
+ const sessionGeneration = sessionGenerationRef.current
1269
+ const runGeneration = ++runGenerationRef.current
1270
+ const originRun = { chatId: originChatId, sessionGeneration, runGeneration }
1271
+ const isCurrentRun = () => matchesActiveRun(originRun, {
1272
+ chatId: activeConversationRef.current,
1273
+ sessionGeneration: sessionGenerationRef.current,
1274
+ runGeneration: runGenerationRef.current,
1275
+ })
1276
+ let turnId: number | null = null
914
1277
  const outgoingImages = options.imageAttachments ?? []
915
1278
  const outgoingVideos = options.videoAttachments ?? []
916
1279
  const outgoingDocument = options.documentAttachment ?? null
@@ -918,107 +1281,129 @@ export default function MintDashboard() {
918
1281
  const outgoingImage = outgoingImages.map((img) => img.dataUri).join(' ')
919
1282
  const outgoingVideo = outgoingVideos.map((vid) => vid.dataUri).join(' ')
920
1283
  const outgoingImageCount = outgoingImages.length
921
- setSending(true)
1284
+ conversationActions.startRun(promptText, outgoingImageCount, outgoingVideos.length)
922
1285
  setStreamingConversationId(conversationId)
923
- setSendingMessage(promptText)
924
- setSendingImageCount(outgoingImageCount)
925
- setSendingVideoCount(outgoingVideos.length)
1286
+ setActiveTurnId(null)
926
1287
  setError('')
927
- setStreamedReply('')
928
- setStreamedResponse(null)
929
- setAgentProgress([])
930
1288
  liveThinkingOpenRef.current = true
931
- setThinkingExpanded((current) => ({ ...current, live: true }))
1289
+ liveExtendedThinkingOpenRef.current = true
1290
+ setThinkingExpanded((current) => ({ ...current, live: true, 'live-extended': true }))
932
1291
  const progressSnapshot: AgentProgress[] = []
1292
+ let pendingProgress: AgentProgress[] = []
1293
+ let progressFlushTimer: ReturnType<typeof setTimeout> | null = null
1294
+ const flushPendingProgress = () => {
1295
+ if (progressFlushTimer) {
1296
+ clearTimeout(progressFlushTimer)
1297
+ progressFlushTimer = null
1298
+ }
1299
+ if (pendingProgress.length === 0) return
1300
+ const batch = pendingProgress
1301
+ pendingProgress = []
1302
+ if (isCurrentRun()) conversationActions.receiveProgress(batch)
1303
+ }
1304
+ const queueProgress = (progress: AgentProgress) => {
1305
+ pendingProgress.push(progress)
1306
+ if (progress.type !== 'ThinkingDelta') {
1307
+ flushPendingProgress()
1308
+ } else if (!progressFlushTimer) {
1309
+ progressFlushTimer = setTimeout(flushPendingProgress, 75)
1310
+ }
1311
+ }
933
1312
  if (options.clearComposer) {
934
- setMessage('')
935
- setImageAttachments([])
936
- setVideoAttachments([])
937
- setDocumentAttachment(null)
1313
+ conversationActions.clearComposer()
938
1314
  }
939
1315
 
940
1316
  try {
941
- const response = await streamChatMessage(
1317
+ const response = await conversationActions.executeStream((onChunk) => streamChatMessage(
942
1318
  shouldUseAgentMode ? promptText : `/chat ${promptText}`,
943
- (chunk) => setStreamedReply((current) => `${current}${chunk}`),
1319
+ onChunk,
944
1320
  outgoingImage,
945
1321
  options.audioDataUri ?? null,
946
1322
  outgoingVideo,
947
1323
  options.systemInstruction ?? '',
948
1324
  (progress) => {
949
1325
  progressSnapshot.push(progress)
950
- setAgentProgress((current) => trimAgentProgress([...current, progress]))
1326
+ queueProgress(progress)
951
1327
  },
952
1328
  outgoingDocument,
953
- workspacePath || null,
954
- conversationId,
1329
+ originWorkspacePath,
1330
+ originChatId,
955
1331
  undefined,
956
1332
  shouldUseAgentMode ? planMode : false,
957
1333
  options.pinnedMcpServer ?? null,
958
1334
  (payload) => {
959
- if (sessionAutoApprovedRef.current) {
1335
+ if (sessionAutoApprovedRef.current && autoApprovedChatIdRef.current === originChatId) {
960
1336
  submitToolApproval(payload.token, true).catch((err) => {
961
1337
  console.error("Auto approval failed:", err)
962
1338
  })
963
1339
  } else {
964
- setPendingApproval(payload)
1340
+ setPendingApprovals((current) => ({ ...current, [originChatId]: payload }))
965
1341
  notifyPendingApproval(payload)
966
1342
  }
967
1343
  },
968
- )
969
- setStreamedResponse(response)
1344
+ (id) => {
1345
+ turnId = id
1346
+ if (isCurrentRun()) setActiveTurnId(id)
1347
+ },
1348
+ ), isCurrentRun)
1349
+ flushPendingProgress()
970
1350
  if (document.hidden || !document.hasFocus()) {
971
1351
  window.api?.notifyAiResponse?.(truncateForNotification(response.text))
972
1352
  }
973
- const history = (await getRecentInteractions(50, conversationId, workspacePath || null)).reverse()
974
- let enrichedHistory = history
975
- if (progressSnapshot.length > 0) {
976
- const newestInteraction = [...history]
977
- .reverse()
978
- .find((interaction) => interaction.aiText === response.text || interaction.userText === promptText) ?? history[history.length - 1]
979
- if (newestInteraction?.id != null) {
980
- const interactionKey = String(newestInteraction.id)
981
- enrichedHistory = history.map((interaction) =>
982
- interaction.id === newestInteraction.id
983
- ? { ...interaction, agentActivity: progressSnapshot }
984
- : interaction,
985
- )
986
- setAgentActivitySnapshots((current) => ({
987
- ...current,
988
- [interactionKey]: progressSnapshot.slice(),
989
- }))
990
- if (liveThinkingOpenRef.current) {
1353
+ const persistedProgress = compactAgentProgressForPersistence(progressSnapshot)
1354
+ if (persistedProgress.length > 0) {
1355
+ if (turnId != null) {
1356
+ const interactionKey = String(turnId)
1357
+ await saveInteractionAgentActivity(turnId, persistedProgress)
1358
+ if (isCurrentRun()) {
1359
+ setAgentActivitySnapshots((current) => ({
1360
+ ...current,
1361
+ [interactionKey]: persistedProgress.slice(),
1362
+ }))
991
1363
  setThinkingExpanded((current) => ({
992
1364
  ...current,
993
- [interactionKey]: true,
1365
+ ...(liveThinkingOpenRef.current ? { [interactionKey]: true } : {}),
1366
+ ...(liveExtendedThinkingOpenRef.current ? { [`extended-${interactionKey}`]: true } : {}),
994
1367
  }))
995
1368
  }
996
- await saveInteractionAgentActivity(newestInteraction.id, progressSnapshot)
1369
+ } else {
1370
+ console.warn('Agent activity was not saved because the stream did not provide a turn ID')
1371
+ }
1372
+ }
1373
+ if (isCurrentRun()) {
1374
+ try {
1375
+ await loadConversationHistory(originChatId, sessionGeneration)
1376
+ } catch (error) {
1377
+ console.error('Failed to refresh completed conversation:', error)
997
1378
  }
998
1379
  }
999
- setInteractions(enrichedHistory)
1000
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, enrichedHistory))
1001
1380
  await refreshChatSessions()
1002
1381
  await refreshPictures()
1003
- if (typeof window !== 'undefined') {
1382
+ if (isCurrentRun() && typeof window !== 'undefined') {
1004
1383
  const pathname = (window.location.pathname || '').replace(/\/+$/, '')
1005
1384
  const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
1006
1385
  const target = (pathname || hash).toLowerCase()
1007
1386
  if (!target || target === '' || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html') {
1008
- window.history.replaceState({}, '', `/chat/${encodeURIComponent(conversationId)}`)
1387
+ window.history.replaceState({}, '', `/chat/${encodeURIComponent(originChatId)}`)
1009
1388
  }
1010
1389
  }
1011
1390
  getRuntimeStatus().then(setStatus).catch(() => {})
1012
- setStreamedReply('')
1013
- setStreamedResponse(null)
1391
+ if (isCurrentRun()) conversationActions.clearStream()
1014
1392
  } catch (reason) {
1015
- setError(errorMessage(reason))
1393
+ if (isCurrentRun()) setError(errorMessage(reason))
1016
1394
  } finally {
1017
- setSending(false)
1018
- setStreamingConversationId(null)
1019
- setSendingMessage('')
1020
- setSendingImageCount(0)
1021
- setSendingVideoCount(0)
1395
+ flushPendingProgress()
1396
+ setPendingApprovals((current) => {
1397
+ if (!current[originChatId]) return current
1398
+ const next = { ...current }
1399
+ delete next[originChatId]
1400
+ return next
1401
+ })
1402
+ if (isCurrentRun()) {
1403
+ conversationActions.finishRun()
1404
+ setStreamingConversationId(null)
1405
+ setActiveTurnId(null)
1406
+ }
1022
1407
  }
1023
1408
  }
1024
1409
 
@@ -1041,10 +1426,7 @@ export default function MintDashboard() {
1041
1426
  engineResp = null
1042
1427
  }
1043
1428
  if (engineResp && engineResp.kind !== 'not_handled') {
1044
- setMessage('')
1045
- setImageAttachments([])
1046
- setVideoAttachments([])
1047
- setDocumentAttachment(null)
1429
+ conversationActions.clearComposer()
1048
1430
  await handleEngineSlashResponse(trimmed, engineResp)
1049
1431
  return
1050
1432
  }
@@ -1052,22 +1434,49 @@ export default function MintDashboard() {
1052
1434
  const slashResult = executeSlashCommand(trimmed)
1053
1435
 
1054
1436
  if (slashResult.handled) {
1055
- setMessage('')
1056
- setImageAttachments([])
1057
- setVideoAttachments([])
1058
- setDocumentAttachment(null)
1437
+ conversationActions.clearComposer()
1059
1438
 
1060
1439
  if (slashResult.action === 'open_image_picker') {
1061
1440
  document.getElementById('vision-file-input')?.click()
1062
1441
  return
1063
1442
  } else if (slashResult.action === 'paste_image') {
1064
1443
  readClipboardImage().then((uri) => {
1065
- if (uri) setImageAttachments((curr) => [...curr, { dataUri: uri, name: 'Clipboard Image' }])
1444
+ if (uri) conversationActions.attachImage({ dataUri: uri, name: 'Clipboard Image' })
1066
1445
  }).catch(() => {})
1067
1446
  return
1068
1447
  } else if (slashResult.action === 'generate_veo') {
1069
1448
  changeView('veo')
1070
1449
  return
1450
+ } else if (slashResult.action === 'resume_session') {
1451
+ const query = (slashResult.payload?.query || '').trim().toLowerCase()
1452
+ const target = query
1453
+ ? chatSessions.find((s) => s.id.toLowerCase() === query || s.id.toLowerCase().includes(query) || s.title.toLowerCase().includes(query))
1454
+ : chatSessions.find((s) => s.id !== conversationId)
1455
+ if (target) {
1456
+ activateConversation(target.id)
1457
+ const targetPath = getCleanPathForView('chat', target.id)
1458
+ window.history.replaceState({}, '', targetPath)
1459
+ const systemMsg = {
1460
+ id: Date.now(),
1461
+ userText: trimmed,
1462
+ aiText: `🔄 Resumed session: **${target.title}** (\`${target.id}\`)`,
1463
+ createdAt: new Date().toISOString(),
1464
+ provider: 'system',
1465
+ model: 'mint-cli',
1466
+ }
1467
+ setInteractions((prev) => [...prev, systemMsg])
1468
+ } else {
1469
+ const systemMsg = {
1470
+ id: Date.now(),
1471
+ userText: trimmed,
1472
+ aiText: query ? `⚠️ No matching session found for "${query}".` : 'ℹ️ No other sessions found to resume.',
1473
+ createdAt: new Date().toISOString(),
1474
+ provider: 'system',
1475
+ model: 'mint-cli',
1476
+ }
1477
+ setInteractions((prev) => [...prev, systemMsg])
1478
+ }
1479
+ return
1071
1480
  }
1072
1481
 
1073
1482
  if (slashResult.systemText) {
@@ -1125,12 +1534,7 @@ export default function MintDashboard() {
1125
1534
  const objectUrl = createObjectUrlPreview(file).objectUrl
1126
1535
  const dataUri = await readImage(file)
1127
1536
  const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
1128
- setImageAttachments((current) => {
1129
- if (current.some((item) => item.name === file.name && item.dataUri === dataUri)) {
1130
- return current
1131
- }
1132
- return [...current, { dataUri, previewDataUri, objectUrl, name: file.name }]
1133
- })
1537
+ conversationActions.attachImage({ dataUri, previewDataUri, objectUrl, name: file.name })
1134
1538
  } catch (reason) {
1135
1539
  setError(errorMessage(reason))
1136
1540
  } finally {
@@ -1152,12 +1556,7 @@ export default function MintDashboard() {
1152
1556
  reader.onerror = reject
1153
1557
  reader.readAsDataURL(file)
1154
1558
  })
1155
- setVideoAttachments((current) => {
1156
- if (current.some((item) => item.name === file.name && item.dataUri === dataUri)) {
1157
- return current
1158
- }
1159
- return [...current, { dataUri, name: file.name }]
1160
- })
1559
+ conversationActions.attachVideo({ dataUri, name: file.name })
1161
1560
  } catch (reason) {
1162
1561
  setError(errorMessage(reason))
1163
1562
  } finally {
@@ -1203,7 +1602,7 @@ export default function MintDashboard() {
1203
1602
  createTrimmedImagePreview(dataUri)
1204
1603
  .catch(() => dataUri)
1205
1604
  .then((previewDataUri) => {
1206
- setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
1605
+ conversationActions.attachImage({ dataUri, previewDataUri, name })
1207
1606
  })
1208
1607
  }
1209
1608
  }).catch((err) => {
@@ -1226,7 +1625,7 @@ export default function MintDashboard() {
1226
1625
  createTrimmedImagePreview(dataUri)
1227
1626
  .catch(() => dataUri)
1228
1627
  .then((previewDataUri) => {
1229
- setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
1628
+ conversationActions.attachImage({ dataUri, previewDataUri, name })
1230
1629
  })
1231
1630
  })
1232
1631
  .catch((reason) => setError(errorMessage(reason)))
@@ -1242,7 +1641,7 @@ export default function MintDashboard() {
1242
1641
  if (!isSupportedDocument(file.name)) {
1243
1642
  throw new Error('Unsupported document type')
1244
1643
  }
1245
- setDocumentAttachment({
1644
+ conversationActions.attachDocument({
1246
1645
  filename: file.name,
1247
1646
  dataUri: await readDocument(file),
1248
1647
  })
@@ -1255,12 +1654,12 @@ export default function MintDashboard() {
1255
1654
 
1256
1655
  function startWebSearch() {
1257
1656
  updateAgentMode(true)
1258
- setMessage((current) => current.trim() ? `Search web: ${current.trim()}` : 'Search web: ')
1657
+ conversationActions.compose(message.trim() ? `Search web: ${message.trim()}` : 'Search web: ')
1259
1658
  }
1260
1659
 
1261
- async function selectWorkspace() {
1660
+ async function selectWorkspace(path?: string) {
1262
1661
  try {
1263
- const selected = await selectWorkspaceDirectory()
1662
+ const selected = path || await selectWorkspaceDirectory()
1264
1663
  if (selected) {
1265
1664
  updateWorkspacePath(selected)
1266
1665
  changeView('workspace')
@@ -1270,7 +1669,13 @@ export default function MintDashboard() {
1270
1669
  }
1271
1670
  }
1272
1671
 
1273
- async function captureScreen() {
1672
+ function captureScreen() {
1673
+ setScreenCaptureOpen(true)
1674
+ }
1675
+
1676
+ async function startLiveTranslation() {
1677
+ setScreenCaptureOpen(false)
1678
+ await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))
1274
1679
  try {
1275
1680
  await window.api.startVision()
1276
1681
  } catch (reason) {
@@ -1278,20 +1683,38 @@ export default function MintDashboard() {
1278
1683
  }
1279
1684
  }
1280
1685
 
1281
- async function clearHistory(action: 'New chat' | 'Clear history') {
1686
+ function attachScreenCapture(image: string) {
1687
+ void createTrimmedImagePreview(image)
1688
+ .catch(() => image)
1689
+ .then((previewDataUri) => {
1690
+ conversationActions.attachImage({ dataUri: image, previewDataUri, name: 'Screen capture' })
1691
+ setScreenCaptureOpen(false)
1692
+ })
1693
+ }
1694
+
1695
+ async function clearHistory(action: 'New chat' | 'Clear history', targetWorkspacePath?: string | null) {
1696
+ const originChatId = conversationId
1697
+ const generation = sessionGenerationRef.current
1282
1698
  try {
1283
1699
  if (action === 'New chat') {
1284
1700
  const next = createConversationId()
1285
- setConversationId(next)
1701
+ activateConversation(next)
1286
1702
  setInteractions([])
1287
1703
  setAgentActivitySnapshots({})
1288
- setStreamedReply('')
1289
- setStreamedResponse(null)
1290
- setMessage('')
1291
- setImageAttachments([])
1292
- setDocumentAttachment(null)
1293
- setAgentProgress([])
1704
+ conversationActions.switchSession()
1294
1705
  setViewState('chat')
1706
+
1707
+ if (targetWorkspacePath !== undefined) {
1708
+ updateWorkspacePath(targetWorkspacePath || '')
1709
+ } else {
1710
+ const currentSession = chatSessions.find((s) => s.id === conversationId)
1711
+ if (!currentSession || !currentSession.workspacePath) {
1712
+ updateWorkspacePath('')
1713
+ } else {
1714
+ updateWorkspacePath(currentSession.workspacePath)
1715
+ }
1716
+ }
1717
+
1295
1718
  if (typeof window !== 'undefined') {
1296
1719
  const currentPath = (window.location.pathname || '').replace(/\/+$/, '')
1297
1720
  if (currentPath !== '' && currentPath !== '/chat') {
@@ -1301,14 +1724,12 @@ export default function MintDashboard() {
1301
1724
  return
1302
1725
  } else {
1303
1726
  if (!window.confirm(`${action} will clear the current conversation history. Continue?`)) return
1304
- await clearChatHistory(conversationId)
1727
+ await clearChatHistory(originChatId)
1305
1728
  }
1729
+ if (!isCurrentSession(originChatId, generation)) return
1306
1730
  setInteractions([])
1307
1731
  setAgentActivitySnapshots({})
1308
- setStreamedReply('')
1309
- setStreamedResponse(null)
1310
- setMessage('')
1311
- setImageAttachments([])
1732
+ conversationActions.switchSession()
1312
1733
  } catch (reason) {
1313
1734
  setError(errorMessage(reason))
1314
1735
  }
@@ -1360,7 +1781,7 @@ export default function MintDashboard() {
1360
1781
  const wsChange = resp.effects.find((e) => e.kind === 'workspace_changed') as
1361
1782
  | { kind: 'workspace_changed'; path: string }
1362
1783
  | undefined
1363
- if (wsChange) setWorkspacePath(wsChange.path)
1784
+ if (wsChange) conversationActions.selectWorkspace(wsChange.path)
1364
1785
  const planModeChange = resp.effects.find((e) => e.kind === 'plan_mode_changed') as
1365
1786
  | { kind: 'plan_mode_changed'; enabled: boolean }
1366
1787
  | undefined
@@ -1385,8 +1806,12 @@ export default function MintDashboard() {
1385
1806
  ])
1386
1807
  }
1387
1808
  if (resp.effects.some((e) => e.kind === 'history_cleared')) {
1388
- await clearChatHistory(conversationId).catch(() => {})
1389
- setInteractions([])
1809
+ const chatId = conversationId
1810
+ const generation = sessionGenerationRef.current
1811
+ await clearChatHistory(chatId).catch(() => {})
1812
+ if (isCurrentSession(chatId, generation)) {
1813
+ setInteractions([])
1814
+ }
1390
1815
  }
1391
1816
  if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1392
1817
  if (resp.effects.some((e) => e.kind !== 'history_cleared' && e.kind !== 'workspace_changed')) {
@@ -1415,40 +1840,66 @@ export default function MintDashboard() {
1415
1840
 
1416
1841
  function handleThinkingExpandedChange(key: string, open: boolean) {
1417
1842
  if (key === 'live') liveThinkingOpenRef.current = open
1843
+ if (key === 'live-extended') liveExtendedThinkingOpenRef.current = open
1418
1844
  setThinkingExpanded((current) => ({ ...current, [key]: open }))
1419
1845
  }
1420
1846
 
1421
1847
  async function selectConversation(id: string) {
1422
1848
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, id)
1423
1849
  touchActiveTimestamp()
1424
- setConversationId(id)
1850
+ activateConversation(id)
1851
+ const generation = sessionGenerationRef.current
1425
1852
  changeView('chat', id)
1426
- setStreamedReply('')
1427
- setStreamedResponse(null)
1428
- setMessage('')
1429
- setImageAttachments([])
1430
- setDocumentAttachment(null)
1431
- setAgentProgress([])
1432
- const history = await getRecentInteractions(50, id, workspacePath || null)
1433
- const reversed = history.reverse()
1434
- setInteractions(reversed)
1435
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
1853
+ conversationActions.switchSession()
1854
+ const session = chatSessions.find((item) => item.id === id)
1855
+ if (session) {
1856
+ if (session.workspacePath) {
1857
+ if (session.workspacePath !== workspacePath) {
1858
+ updateWorkspacePath(session.workspacePath)
1859
+ }
1860
+ } else {
1861
+ if (workspacePath) {
1862
+ updateWorkspacePath('')
1863
+ }
1864
+ }
1865
+ }
1866
+ try {
1867
+ await loadConversationHistory(id, generation)
1868
+ } catch (reason) {
1869
+ if (isCurrentSession(id, generation)) setError(errorMessage(reason))
1870
+ }
1436
1871
  }
1437
1872
 
1873
+ const handleNewChatInProject = useCallback((targetPath: string) => {
1874
+ clearHistory('New chat', targetPath)
1875
+ }, [])
1876
+
1438
1877
  async function deleteConversation(id: string) {
1439
- if (id === 'cli') return
1878
+ if (id === 'cli') {
1879
+ if (!window.confirm('Clear history for default CLI session? This cannot be undone.')) return
1880
+ try {
1881
+ await clearChatHistory('cli')
1882
+ await refreshHistory()
1883
+ await refreshChatSessions()
1884
+ showToast('CLI session history cleared')
1885
+ } catch (reason) {
1886
+ setError(errorMessage(reason))
1887
+ }
1888
+ return
1889
+ }
1440
1890
  const session = chatSessions.find((item) => item.id === id)
1441
1891
  const title = session?.title || 'this chat'
1442
1892
  if (!window.confirm(`Delete "${title}"? This will remove the conversation and its messages.`)) return
1443
1893
 
1444
1894
  try {
1445
1895
  await deleteChatSession(id)
1446
- const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && item.id !== 'conversation-default')
1447
- const nextActive = id === conversationId
1896
+ const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && !item.id.startsWith('cli') && item.id !== 'conversation-default')
1897
+ const currentChatId = activeConversationRef.current
1898
+ const nextActive = id === currentChatId
1448
1899
  ? (remaining[0]?.id ?? createConversationId())
1449
- : conversationId
1900
+ : currentChatId
1450
1901
 
1451
- if (nextActive !== conversationId) {
1902
+ if (nextActive !== currentChatId) {
1452
1903
  if (remaining.length > 0 && remaining[0]?.id) {
1453
1904
  window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, nextActive)
1454
1905
  if (typeof window !== 'undefined') {
@@ -1460,21 +1911,16 @@ export default function MintDashboard() {
1460
1911
  window.history.replaceState({}, '', '/chat')
1461
1912
  }
1462
1913
  }
1463
- setConversationId(nextActive)
1464
- setAgentProgress([])
1465
- const history = await getRecentInteractions(50, nextActive, workspacePath || null)
1466
- const reversed = history.reverse()
1467
- setInteractions(reversed)
1468
- setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
1914
+ activateConversation(nextActive)
1915
+ const generation = sessionGenerationRef.current
1916
+ conversationActions.clearProgress()
1917
+ await loadConversationHistory(nextActive, generation)
1918
+ if (!isCurrentSession(nextActive, generation)) return
1469
1919
  }
1470
1920
 
1471
1921
  await refreshChatSessions()
1472
- if (id !== conversationId) return
1473
- setStreamedReply('')
1474
- setStreamedResponse(null)
1475
- setMessage('')
1476
- setImageAttachments([])
1477
- setDocumentAttachment(null)
1922
+ if (id !== currentChatId) return
1923
+ conversationActions.switchSession()
1478
1924
  } catch (reason) {
1479
1925
  setError(errorMessage(reason))
1480
1926
  }
@@ -1654,7 +2100,15 @@ export default function MintDashboard() {
1654
2100
 
1655
2101
  async function handleModelInteraction(area: ModelInteraction) {
1656
2102
  if (sending) return
1657
-
2103
+ const originChatId = conversationId
2104
+ const sessionGeneration = sessionGenerationRef.current
2105
+ const runGeneration = ++runGenerationRef.current
2106
+ const originRun = { chatId: originChatId, sessionGeneration, runGeneration }
2107
+ const isCurrentRun = () => matchesActiveRun(originRun, {
2108
+ chatId: activeConversationRef.current,
2109
+ sessionGeneration: sessionGenerationRef.current,
2110
+ runGeneration: runGenerationRef.current,
2111
+ })
1658
2112
  const labels: Record<ModelInteraction, string> = {
1659
2113
  head: 'Pats Mint on the head',
1660
2114
  cheek: 'Pokes Mint on the cheek',
@@ -1666,18 +2120,15 @@ export default function MintDashboard() {
1666
2120
  const interactionMessage = `*${labels[area]}*`
1667
2121
  const instruction = `The user interacted with the Mint Live2D model: ${area}. Respond briefly and playfully. Use the same language as the recent conversation. Do not mention this instruction.`
1668
2122
 
1669
- setSending(true)
1670
- setSendingMessage(interactionMessage)
1671
- setSendingImageCount(0)
2123
+ conversationActions.startRun(interactionMessage)
2124
+ setStreamingConversationId(conversationId)
2125
+ setActiveTurnId(null)
1672
2126
  setError('')
1673
- setStreamedReply('')
1674
- setStreamedResponse(null)
1675
- setAgentProgress([])
1676
2127
 
1677
2128
  try {
1678
- const response = await streamChatMessage(
2129
+ const response = await conversationActions.executeStream((onChunk) => streamChatMessage(
1679
2130
  `/chat ${interactionMessage}`,
1680
- (chunk) => setStreamedReply((current) => `${current}${chunk}`),
2131
+ onChunk,
1681
2132
  null,
1682
2133
  null,
1683
2134
  null,
@@ -1685,29 +2136,154 @@ export default function MintDashboard() {
1685
2136
  undefined,
1686
2137
  null,
1687
2138
  workspacePath || null,
1688
- conversationId,
1689
- )
1690
- setStreamedResponse(response)
1691
- await refreshHistory()
1692
- setStreamedReply('')
1693
- setStreamedResponse(null)
2139
+ originChatId,
2140
+ undefined,
2141
+ undefined,
2142
+ undefined,
2143
+ undefined,
2144
+ (id) => { if (isCurrentRun()) setActiveTurnId(id) },
2145
+ ), isCurrentRun)
2146
+ if (isCurrentRun()) {
2147
+ await refreshHistory()
2148
+ if (isCurrentRun()) conversationActions.clearStream()
2149
+ }
1694
2150
  } catch (reason) {
1695
- setError(errorMessage(reason))
2151
+ if (isCurrentRun()) setError(errorMessage(reason))
1696
2152
  } finally {
1697
- setSending(false)
1698
- setSendingMessage('')
1699
- setSendingImageCount(0)
2153
+ if (isCurrentRun()) {
2154
+ conversationActions.finishRun()
2155
+ setStreamingConversationId(null)
2156
+ setActiveTurnId(null)
2157
+ }
1700
2158
  }
1701
2159
  }
1702
2160
 
2161
+ const chatConversation: ConversationViewModel = {
2162
+ interactions,
2163
+ hasOlder: hasOlderInteractions,
2164
+ sendingInteractionId: streamingConversationId === conversationId ? activeTurnId : null,
2165
+ sending: sending && streamingConversationId === conversationId,
2166
+ sendingMessage: streamingConversationId === conversationId ? sendingMessage : '',
2167
+ sendingImageCount: streamingConversationId === conversationId ? sendingImageCount : 0,
2168
+ sendingVideoCount: streamingConversationId === conversationId ? sendingVideoCount : 0,
2169
+ streamedReply: streamingConversationId === conversationId ? streamedReply : '',
2170
+ streamedResponse: streamingConversationId === conversationId ? streamedResponse : null,
2171
+ agentProgress: streamingConversationId === conversationId ? agentProgress : [],
2172
+ agentActivitySnapshots,
2173
+ thinkingExpanded,
2174
+ message,
2175
+ imageAttachments,
2176
+ videoAttachments,
2177
+ documentName: documentAttachment?.filename ?? '',
2178
+ pendingApproval: pendingApprovals[conversationId] ?? null,
2179
+ agentMode,
2180
+ planMode,
2181
+ status,
2182
+ workspacePath,
2183
+ chatId: conversationId,
2184
+ recentWorkspacePaths,
2185
+ chatEnd,
2186
+ welcomeInteraction: MOCK_WELCOME_INTERACTION,
2187
+ settingsConfig,
2188
+ isCliSession: conversationId.startsWith('cli') || conversationId === 'cli',
2189
+ cliSessionId: conversationId.startsWith('cli') ? conversationId : undefined,
2190
+ conversationTitle: chatSessions.find((session) => session.id === conversationId)?.title,
2191
+ }
2192
+ const chatActions: ConversationActions = {
2193
+ onLoadOlder: loadOlderInteractions,
2194
+ onThinkingExpandedChange: handleThinkingExpandedChange,
2195
+ onSubmit: handleSubmit,
2196
+ onSelectImage: selectImage,
2197
+ onSelectVideo: selectVideo,
2198
+ onSelectDocument: selectDocument,
2199
+ onPasteImage: pasteImage,
2200
+ onSetMessage: conversationActions.compose,
2201
+ onSendVoiceMessage: sendVoiceMessage,
2202
+ onRemoveImage: conversationActions.removeImage,
2203
+ onRemoveVideo: conversationActions.removeVideo,
2204
+ onRemoveDocument: () => conversationActions.attachDocument(null),
2205
+ onStartWebSearch: startWebSearch,
2206
+ onCaptureScreen: captureScreen,
2207
+ onSetAgentMode: updateAgentMode,
2208
+ onSetPlanMode: isDesktopApp ? updatePlanMode : undefined,
2209
+ onSetProvider: changeProvider,
2210
+ onSelectWorkspace: isDesktopApp ? selectWorkspace : undefined,
2211
+ onWorkspaceChanged: () => setWorkspaceRefreshRevision((revision) => revision + 1),
2212
+ onApproval: handleApproval,
2213
+ onUpdateSettings: (updated) => setSettingsConfig(updated),
2214
+ onSetModel: changeModel,
2215
+ onSelectModelAndProvider: changeProviderAndModel,
2216
+ onCancelMessage: handleCancelMessage,
2217
+ onClearMessages: () => clearHistory('Clear history'),
2218
+ onSetGeminiLiveVoice: changeGeminiLiveVoice,
2219
+ onToggleMobileSidebar: () => setMobileSidebarOpen(!mobileSidebarOpen),
2220
+ onBackToCode: () => changeView('code'),
2221
+ onOpenArtifact: isDesktopApp ? openArtifactSurface : undefined,
2222
+ onOpenReview: isDesktopApp ? openReviewSurface : undefined,
2223
+ }
2224
+
1703
2225
  return (
1704
2226
  <div className={`app-container ${startupReady ? '' : 'is-loading'}`}>
2227
+ {screenCaptureOpen && (
2228
+ <ScreenCaptureDialog
2229
+ onClose={() => setScreenCaptureOpen(false)}
2230
+ onAttach={attachScreenCapture}
2231
+ capture={() => captureScreenForChat(isDesktopApp)}
2232
+ onLiveTranslate={isDesktopApp ? () => { void startLiveTranslation() } : undefined}
2233
+ />
2234
+ )}
2235
+ {isDesktopApp && (
2236
+ <DesktopTitlebar
2237
+ sidebarCollapsed={sidebarCollapsed}
2238
+ onToggleSidebar={toggleSidebar}
2239
+ onNewChat={() => clearHistory('New chat')}
2240
+ onOpenWorkspace={() => selectWorkspace()}
2241
+ onOpenTerminal={openTerminalSurface}
2242
+ onToggleTerminal={toggleTerminalSurface}
2243
+ onOpenBrowser={() => openNativeBrowser()}
2244
+ toolsPanelOpen={toolsPanelOpen}
2245
+ onToggleToolsPanel={() => setToolsPanelOpen((open) => !open)}
2246
+ onOpenSettings={() => changeView('settings')}
2247
+ onCheckForUpdates={checkForUpdatesFromMenu}
2248
+ onShowAbout={() => showToast('Mint Agent — AI workspace')}
2249
+ workspacePath={workspacePath}
2250
+ terminalCount={toolSurfaces.filter((surface) => surface.kind === 'terminal').length}
2251
+ sourceNames={workspaceSourceNames}
2252
+ recentChanges={recentAgentChanges}
2253
+ onOpenReview={(changes) => openReviewSurface({ id: `review:workspace:${Date.now()}`, kind: 'review', title: 'Review', reviewTitle: 'Recent agent changes', changes })}
2254
+ onRefreshWorkspace={() => setWorkspaceRefreshRevision((revision) => revision + 1)}
2255
+ />
2256
+ )}
1705
2257
  <div
1706
- className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}
1707
- style={{ '--sidebar-expanded-width': `${sidebarWidth}px` } as CSSProperties}
2258
+ className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''} ${toolsPanelOpen ? 'tool-surface-active' : ''}`}
2259
+ style={{
2260
+ '--sidebar-expanded-width': `${sidebarWidth}px`,
2261
+ '--terminal-dock-size': `${terminalRightWidth}px`,
2262
+ // A terminal shown in the right tool surface resizes the surface
2263
+ // itself (and therefore the remaining chat area), not just its
2264
+ // inner xterm viewport.
2265
+ '--tool-surface-width': `${terminalRightWidth}px`,
2266
+ } as CSSProperties}
1708
2267
  >
2268
+ {!isDesktopApp && view !== 'chat' && (
2269
+ <button
2270
+ type="button"
2271
+ className="web-mobile-nav-btn"
2272
+ aria-label="Open navigation menu"
2273
+ aria-expanded={mobileSidebarOpen}
2274
+ onClick={() => setMobileSidebarOpen(true)}
2275
+ >
2276
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
2277
+ <line x1="3" y1="6" x2="21" y2="6" />
2278
+ <line x1="3" y1="12" x2="21" y2="12" />
2279
+ <line x1="3" y1="18" x2="21" y2="18" />
2280
+ </svg>
2281
+ </button>
2282
+ )}
1709
2283
  {mobileSidebarOpen && (
1710
- <div
2284
+ <button
2285
+ type="button"
2286
+ aria-label="Close navigation menu"
1711
2287
  className="sidebar-backdrop"
1712
2288
  onClick={() => setMobileSidebarOpen(false)}
1713
2289
  style={{
@@ -1753,8 +2329,13 @@ export default function MintDashboard() {
1753
2329
  onSetSearchOpen={setIsSearchOpen}
1754
2330
  showWorkspaceTab={isDesktopApp}
1755
2331
  promoteMediaStudios={!isDesktopApp}
2332
+ activeWorkspacePath={workspacePath}
2333
+ recentWorkspacePaths={recentWorkspacePaths}
2334
+ onBrowseFolder={isDesktopApp ? handleBrowseFolderForMove : undefined}
2335
+ onUpdateSessionWorkspace={handleUpdateSessionWorkspace}
2336
+ onNewChatInProject={handleNewChatInProject}
1756
2337
  />
1757
- <main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === 'workspace' ? '' : 'model-hidden'} ${view === 'workspace' ? 'workspace-open' : ''}`} style={(view === 'skills' || view === 'mcp' || view === 'plugins' || view === 'cron' || view === 'link' || view === 'pictures' || view === 'imagine' || view === 'veo') ? { display: 'none' } : undefined}>
2338
+ <main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === 'workspace' ? '' : 'model-hidden'} ${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}>
1758
2339
  {proactiveSuggestion && (
1759
2340
  <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
1760
2341
  <div className="proactive-header">
@@ -1806,172 +2387,220 @@ export default function MintDashboard() {
1806
2387
  </div>
1807
2388
  )}
1808
2389
  {view === 'workspace' && (
1809
- <WorkspacePanel
1810
- agentMode={agentMode}
2390
+ <Suspense fallback={<LazyPanelFallback />}>
2391
+ <WorkspacePanel
2392
+ agentMode={agentMode}
2393
+ sending={sending}
2394
+ workspacePath={workspacePath}
2395
+ onEnableAgentMode={() => updateAgentMode(true)}
2396
+ onSetMessage={conversationActions.compose}
2397
+ onWorkspaceReady={updateWorkspacePath}
2398
+ refreshRevision={workspaceRefreshRevision}
2399
+ />
2400
+ </Suspense>
2401
+ )}
2402
+ <Suspense fallback={null}>
2403
+ <ModelPanel
2404
+ scale={scale}
2405
+ expressionIndex={expressionIndex}
2406
+ accessoryIndex={accessoryIndex}
2407
+ isLocked={isLocked}
2408
+ isActive={modelVisible && view !== 'pictures' && view !== 'workspace' && view !== 'imagine' && view !== 'veo' && view !== 'skills' && view !== 'mcp' && view !== 'plugins'}
2409
+ layoutPreset={layoutPreset}
1811
2410
  sending={sending}
1812
- workspacePath={workspacePath}
1813
- onEnableAgentMode={() => updateAgentMode(true)}
1814
- onSetMessage={setMessage}
1815
- onWorkspaceReady={updateWorkspacePath}
2411
+ interactionEnabled={interactionEnabled}
2412
+ showInteractionGuide={showInteractionGuide}
2413
+ toastMessage={toastMessage}
2414
+ onSetScale={setScale}
2415
+ onSetLocked={setIsLocked}
2416
+ onSetView={changeView}
2417
+ onChangeLayoutPreset={changeLayoutPreset}
2418
+ onDismissToast={() => setToastMessage('')}
2419
+ onInteract={handleModelInteraction}
2420
+ onModelLoadComplete={() => setModelReady(true)}
1816
2421
  />
1817
- )}
1818
- <ModelPanel
1819
- scale={scale}
1820
- expressionIndex={expressionIndex}
1821
- accessoryIndex={accessoryIndex}
1822
- isLocked={isLocked}
1823
- isActive={modelVisible && view !== 'pictures' && view !== 'workspace' && view !== 'imagine' && view !== 'veo' && view !== 'skills' && view !== 'mcp' && view !== 'plugins'}
1824
- layoutPreset={layoutPreset}
1825
- sending={sending}
1826
- interactionEnabled={interactionEnabled}
1827
- showInteractionGuide={showInteractionGuide}
1828
- toastMessage={toastMessage}
1829
- onSetScale={setScale}
1830
- onSetLocked={setIsLocked}
1831
- onSetView={changeView}
1832
- onChangeLayoutPreset={changeLayoutPreset}
1833
- onDismissToast={() => setToastMessage('')}
1834
- onInteract={handleModelInteraction}
1835
- onModelLoadComplete={() => setModelReady(true)}
1836
- />
1837
- <ChatPanel
1838
- interactions={interactions}
1839
- sending={sending && streamingConversationId === conversationId}
1840
- sendingMessage={streamingConversationId === conversationId ? sendingMessage : ''}
1841
- sendingImageCount={streamingConversationId === conversationId ? sendingImageCount : 0}
1842
- streamedReply={streamingConversationId === conversationId ? streamedReply : ''}
1843
- streamedResponse={streamingConversationId === conversationId ? streamedResponse : null}
1844
- agentProgress={streamingConversationId === conversationId ? agentProgress : []}
1845
- sendingVideoCount={streamingConversationId === conversationId ? sendingVideoCount : 0}
1846
- agentActivitySnapshots={agentActivitySnapshots}
1847
- thinkingExpanded={thinkingExpanded}
1848
- onThinkingExpandedChange={handleThinkingExpandedChange}
1849
- message={message}
1850
- imageAttachments={imageAttachments}
1851
- videoAttachments={videoAttachments}
1852
- documentName={documentAttachment?.filename ?? ''}
1853
- pendingApproval={streamingConversationId === conversationId ? pendingApproval : null}
1854
- smartContext={smartContext}
1855
- agentMode={agentMode}
1856
- planMode={planMode}
1857
- status={status}
1858
- workspacePath={workspacePath}
1859
- chatEnd={chatEnd}
1860
- welcomeInteraction={MOCK_WELCOME_INTERACTION}
1861
- onSubmit={handleSubmit}
1862
- onSelectImage={selectImage}
1863
- onSelectVideo={selectVideo}
1864
- onSelectDocument={selectDocument}
1865
- onPasteImage={pasteImage}
1866
- onSetMessage={setMessage}
1867
- onSendVoiceMessage={sendVoiceMessage}
1868
- onRemoveImage={(idx: number) => {
1869
- setImageAttachments((current) => current.filter((_, i) => i !== idx))
1870
- }}
1871
- onRemoveVideo={(idx: number) => {
1872
- setVideoAttachments((current) => current.filter((_, i) => i !== idx))
1873
- }}
1874
- onRemoveDocument={() => setDocumentAttachment(null)}
1875
- onStartWebSearch={startWebSearch}
1876
- onCaptureScreen={captureScreen}
1877
- onSetSmartContext={updateSmartContext}
1878
- onSetAgentMode={updateAgentMode}
1879
- onSetPlanMode={isDesktopApp ? updatePlanMode : undefined}
1880
- onSetProvider={changeProvider}
1881
- onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
1882
- settingsConfig={settingsConfig}
1883
- onSetModel={changeModel}
1884
- onSelectModelAndProvider={changeProviderAndModel}
1885
- onSetGeminiLiveVoice={changeGeminiLiveVoice}
1886
- onApproval={handleApproval}
1887
- onCancelMessage={handleCancelMessage}
1888
- onClearMessages={() => clearHistory('Clear history')}
1889
- onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
1890
- />
2422
+ </Suspense>
2423
+ <ChatPanel conversation={chatConversation} actions={chatActions} />
1891
2424
  </main>
1892
- {view === 'skills' && (
1893
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1894
- <SkillsView
1895
- listSkills={listLearnedSkills}
1896
- addSkill={addLearnedSkill}
1897
- deleteSkill={deleteLearnedSkill}
2425
+ {isDesktopApp && toolsPanelOpen && (
2426
+ <Suspense fallback={<LazyPanelFallback />}>
2427
+ <ToolSurfacePage
2428
+ surfaces={toolSurfaces}
2429
+ activeSurfaceId={activeSurfaceId}
1898
2430
  workspacePath={workspacePath}
2431
+ terminalSize={terminalRightWidth}
2432
+ onSelect={setActiveSurfaceId}
2433
+ onClose={closeToolSurface}
2434
+ onCloseAll={closeAllToolSurfaces}
2435
+ onDuplicate={duplicateToolSurface}
2436
+ onRename={renameToolSurface}
2437
+ onCloseOthers={closeOtherToolSurfaces}
2438
+ onOpenTerminal={openTerminalSurface}
2439
+ onOpenBrowser={() => openNativeBrowser()}
2440
+ onOpenReview={openConversationReview}
2441
+ onOpenFiles={() => {
2442
+ setActiveSurfaceId(null)
2443
+ setToolsPanelOpen(false)
2444
+ changeView('workspace')
2445
+ }}
2446
+ onOpenSideChat={() => { setActiveSurfaceId(null); setToolsPanelOpen(false) }}
2447
+ onClosePanel={() => setToolsPanelOpen(false)}
2448
+ onResizeTerminal={(size) => {
2449
+ setTerminalRightWidth(size)
2450
+ if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
2451
+ terminalSaveTimeoutRef.current = setTimeout(() => {
2452
+ window.localStorage.setItem('mint:terminal-right-width', String(size))
2453
+ }, 250)
2454
+ }}
2455
+ onResizePanel={(size) => {
2456
+ setTerminalRightWidth(size)
2457
+ if (terminalSaveTimeoutRef.current) clearTimeout(terminalSaveTimeoutRef.current)
2458
+ terminalSaveTimeoutRef.current = setTimeout(() => {
2459
+ window.localStorage.setItem('mint:terminal-right-width', String(size))
2460
+ }, 250)
2461
+ }}
1899
2462
  />
2463
+ </Suspense>
2464
+ )}
2465
+ {view === 'skills' && (
2466
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2467
+ <Suspense fallback={<LazyPanelFallback />}>
2468
+ <SkillsView
2469
+ listSkills={listLearnedSkills}
2470
+ addSkill={addLearnedSkill}
2471
+ deleteSkill={deleteLearnedSkill}
2472
+ workspacePath={workspacePath}
2473
+ />
2474
+ </Suspense>
1900
2475
  </div>
1901
2476
  )}
1902
2477
  {view === 'mcp' && (
1903
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1904
- <McpServersView
1905
- config={settingsConfig || DEFAULT_CONFIG}
1906
- updateField={handleUpdateSettingsField}
1907
- mcpName={mcpName}
1908
- setMcpName={setMcpName}
1909
- mcpCmd={mcpCmd}
1910
- setMcpCmd={setMcpCmd}
1911
- mcpArgs={mcpArgs}
1912
- setMcpArgs={setMcpArgs}
1913
- mcpEnv={mcpEnv}
1914
- setMcpEnv={setMcpEnv}
1915
- mcpIcon={mcpIcon}
1916
- setMcpIcon={setMcpIcon}
1917
- handleAddMcpServer={handleAddMcpServer}
1918
- handleRemoveMcpServer={handleRemoveMcpServer}
1919
- detectTools={detectSystemTools}
1920
- onReauth={reauthMcpServer}
1921
- listServerTools={listMcpServerTools}
1922
- />
2478
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2479
+ <Suspense fallback={<LazyPanelFallback />}>
2480
+ <McpServersView
2481
+ config={settingsConfig || DEFAULT_CONFIG}
2482
+ updateField={handleUpdateSettingsField}
2483
+ mcpName={mcpName}
2484
+ setMcpName={setMcpName}
2485
+ mcpCmd={mcpCmd}
2486
+ setMcpCmd={setMcpCmd}
2487
+ mcpArgs={mcpArgs}
2488
+ setMcpArgs={setMcpArgs}
2489
+ mcpEnv={mcpEnv}
2490
+ setMcpEnv={setMcpEnv}
2491
+ mcpIcon={mcpIcon}
2492
+ setMcpIcon={setMcpIcon}
2493
+ handleAddMcpServer={handleAddMcpServer}
2494
+ handleRemoveMcpServer={handleRemoveMcpServer}
2495
+ detectTools={detectSystemTools}
2496
+ onReauth={reauthMcpServer}
2497
+ listServerTools={listMcpServerTools}
2498
+ />
2499
+ </Suspense>
1923
2500
  </div>
1924
2501
  )}
1925
2502
  {view === 'plugins' && (
1926
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1927
- <PluginsView
1928
- config={settingsConfig || DEFAULT_CONFIG}
1929
- updateField={handleUpdateSettingsField}
1930
- handleConnectPlugin={handleConnectPlugin}
1931
- />
2503
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2504
+ <Suspense fallback={<LazyPanelFallback />}>
2505
+ <PluginsView
2506
+ config={settingsConfig || DEFAULT_CONFIG}
2507
+ updateField={handleUpdateSettingsField}
2508
+ handleConnectPlugin={handleConnectPlugin}
2509
+ />
2510
+ </Suspense>
1932
2511
  </div>
1933
2512
  )}
1934
2513
  {view === 'cron' && (
1935
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1936
- <ScheduledTasksView
1937
- listCronJobs={listCronJobs}
1938
- addCronJob={addCronJob}
1939
- removeCronJob={removeCronJob}
1940
- setCronJobEnabled={setCronJobEnabled}
1941
- workspacePath={workspacePath}
1942
- />
2514
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2515
+ <Suspense fallback={<LazyPanelFallback />}>
2516
+ <ScheduledTasksView
2517
+ listCronJobs={listCronJobs}
2518
+ addCronJob={addCronJob}
2519
+ removeCronJob={removeCronJob}
2520
+ setCronJobEnabled={setCronJobEnabled}
2521
+ workspacePath={workspacePath}
2522
+ />
2523
+ </Suspense>
1943
2524
  </div>
1944
2525
  )}
1945
2526
  {view === 'link' && (
1946
- <div style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
1947
- <LinkedFoldersView
1948
- listLinkedFolders={listLinkedFolders}
1949
- addLinkedFolder={addLinkedFolder}
1950
- removeLinkedFolder={removeLinkedFolder}
1951
- // Desktop: native Tauri picker. Web: asks `mint web` (same
1952
- // machine) to open its own dialog via a loopback-gated route.
1953
- selectFolder={selectLinkedFolderPath}
1954
- />
2527
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2528
+ <Suspense fallback={<LazyPanelFallback />}>
2529
+ <LinkedFoldersView
2530
+ listLinkedFolders={listLinkedFolders}
2531
+ addLinkedFolder={addLinkedFolder}
2532
+ removeLinkedFolder={removeLinkedFolder}
2533
+ linkedFolderStatus={linkedFolderStatus}
2534
+ refreshLinkedFolder={refreshLinkedFolder}
2535
+ listLinkedFolderNotes={listLinkedFolderNotes}
2536
+ readLinkedFolderNote={readLinkedFolderNote}
2537
+ openLinkedFolderNote={openLinkedFolderNote}
2538
+ canOpenNote={isTauriRuntime()}
2539
+ // Desktop: native Tauri picker. Web: asks `mint web` (same
2540
+ // machine) to open its own dialog via a loopback-gated route.
2541
+ selectFolder={selectLinkedFolderPath}
2542
+ />
2543
+ </Suspense>
1955
2544
  </div>
1956
2545
  )}
1957
- <PicturesLibrary view={view} pictures={pictures} onSetView={changeView} onRefreshPictures={refreshPictures} />
1958
- <ImageStudioPanel
1959
- view={view}
1960
- onRefreshPictures={refreshPictures}
1961
- onSendToChat={(_url, imgPrompt) => {
1962
- changeView('chat')
1963
- setMessage(imgPrompt)
1964
- }}
1965
- onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
1966
- />
1967
- <VeoStudioPanel
1968
- view={view}
1969
- onSendToChat={(vidPrompt) => {
1970
- changeView('chat')
1971
- setMessage(vidPrompt)
1972
- }}
1973
- onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
1974
- />
2546
+ {view === 'code' && (
2547
+ <div className="web-view-scroll" style={{ flex: 1, overflowY: 'auto', background: 'transparent' }}>
2548
+ <Suspense fallback={<LazyPanelFallback />}>
2549
+ <CliSessionsView
2550
+ chatSessions={chatSessions}
2551
+ activeConversationId={conversationId}
2552
+ workspacePath={workspacePath}
2553
+ onSelectSession={(id) => {
2554
+ selectConversation(id)
2555
+ }}
2556
+ onDeleteSession={deleteConversation}
2557
+ onRenameSession={renameConversation}
2558
+ onRefreshSessions={refreshChatSessions}
2559
+ onShowToast={showToast}
2560
+ />
2561
+ </Suspense>
2562
+ </div>
2563
+ )}
2564
+ {view === 'pictures' && (
2565
+ <Suspense fallback={<LazyPanelFallback />}>
2566
+ <PicturesLibrary view={view} pictures={pictures} onSetView={changeView} onRefreshPictures={refreshPictures} />
2567
+ </Suspense>
2568
+ )}
2569
+ {view === 'imagine' && (
2570
+ <Suspense fallback={<LazyPanelFallback />}>
2571
+ <ImageStudioPanel
2572
+ view={view}
2573
+ onRefreshPictures={refreshPictures}
2574
+ onSendToChat={async (imagePath, imgPrompt) => {
2575
+ try {
2576
+ const response = await fetch(convertFileSrc(imagePath))
2577
+ if (!response.ok) throw new Error(`Unable to load image (${response.status})`)
2578
+ const blob = await response.blob()
2579
+ const imageName = imagePath.split(/[\\/]/).pop() || 'generated-image.png'
2580
+ const dataUri = await readImage(new File([blob], imageName, { type: blob.type || 'image/png' }))
2581
+ const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
2582
+
2583
+ changeView('chat')
2584
+ conversationActions.compose(imgPrompt)
2585
+ conversationActions.attachImage({ dataUri, previewDataUri, name: imageName })
2586
+ } catch (error) {
2587
+ showToast(`Could not attach image: ${errorMessage(error)}`)
2588
+ }
2589
+ }}
2590
+ />
2591
+ </Suspense>
2592
+ )}
2593
+ {view === 'veo' && (
2594
+ <Suspense fallback={<LazyPanelFallback />}>
2595
+ <VeoStudioPanel
2596
+ view={view}
2597
+ onSendToChat={(vidPrompt) => {
2598
+ changeView('chat')
2599
+ conversationActions.compose(vidPrompt)
2600
+ }}
2601
+ />
2602
+ </Suspense>
2603
+ )}
1975
2604
  </div>
1976
2605
  <div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
1977
2606
  <div className="startup-loading-content">
@@ -1991,42 +2620,46 @@ export default function MintDashboard() {
1991
2620
  </div>
1992
2621
  )}
1993
2622
 
1994
- <CommandPalette
1995
- isOpen={isSearchOpen}
1996
- onClose={() => setIsSearchOpen(false)}
1997
- onSelectChat={(chatId) => {
1998
- selectConversation(chatId)
1999
- setIsSearchOpen(false)
2000
- }}
2001
- onNewChat={() => {
2002
- clearHistory('New chat')
2003
- setIsSearchOpen(false)
2004
- }}
2005
- onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
2006
- onOpenSettings={() => {
2007
- changeView('settings')
2008
- setIsSearchOpen(false)
2009
- }}
2010
- onChangeView={(targetView) => {
2011
- changeView(targetView)
2012
- setIsSearchOpen(false)
2013
- }}
2014
- onChangeModel={(model) => {
2015
- handleUpdateSettingsField('model', model)
2016
- setToastMessage(`Switched model to ${model}`)
2017
- }}
2018
- onChangeProvider={(provider) => {
2019
- handleUpdateSettingsField('provider', provider)
2020
- setToastMessage(`Switched provider to ${provider}`)
2021
- }}
2022
- onExecuteSlash={(cmd) => {
2023
- setMessage(cmd + ' ')
2024
- setIsSearchOpen(false)
2025
- }}
2026
- chatSessions={chatSessions}
2027
- currentChatId={conversationId}
2028
- workspacePath={workspacePath}
2029
- />
2623
+ {isSearchOpen && (
2624
+ <Suspense fallback={null}>
2625
+ <CommandPalette
2626
+ isOpen
2627
+ onClose={() => setIsSearchOpen(false)}
2628
+ onSelectChat={(chatId) => {
2629
+ selectConversation(chatId)
2630
+ setIsSearchOpen(false)
2631
+ }}
2632
+ onNewChat={() => {
2633
+ clearHistory('New chat')
2634
+ setIsSearchOpen(false)
2635
+ }}
2636
+ onSelectWorkspace={isDesktopApp ? selectWorkspace : undefined}
2637
+ onOpenSettings={() => {
2638
+ changeView('settings')
2639
+ setIsSearchOpen(false)
2640
+ }}
2641
+ onChangeView={(targetView) => {
2642
+ changeView(targetView)
2643
+ setIsSearchOpen(false)
2644
+ }}
2645
+ onChangeModel={(model) => {
2646
+ handleUpdateSettingsField('model', model)
2647
+ setToastMessage(`Switched model to ${model}`)
2648
+ }}
2649
+ onChangeProvider={(provider) => {
2650
+ handleUpdateSettingsField('provider', provider)
2651
+ setToastMessage(`Switched provider to ${provider}`)
2652
+ }}
2653
+ onExecuteSlash={(cmd) => {
2654
+ conversationActions.compose(cmd + ' ')
2655
+ setIsSearchOpen(false)
2656
+ }}
2657
+ chatSessions={chatSessions}
2658
+ currentChatId={conversationId}
2659
+ workspacePath={workspacePath}
2660
+ />
2661
+ </Suspense>
2662
+ )}
2030
2663
  </div>
2031
2664
  )
2032
2665
  }