@pheem49/mint 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1162) hide show
  1. package/Cargo.lock +1012 -69
  2. package/Cargo.toml +5 -2
  3. package/LICENSE +21 -654
  4. package/README.md +92 -817
  5. package/Release_Note.md +71 -579
  6. package/apps/mint-companion/src-tauri/Cargo.toml +20 -0
  7. package/apps/mint-companion/src-tauri/build.rs +3 -0
  8. package/apps/mint-companion/src-tauri/src/compact.rs +69 -0
  9. package/apps/mint-companion/src-tauri/src/main.rs +675 -0
  10. package/apps/mint-companion/tsconfig.json +20 -0
  11. package/apps/mint-companion/vite.config.ts +4 -0
  12. package/components.json +19 -0
  13. package/crates/mint-cli/Cargo.toml +6 -0
  14. package/crates/mint-cli/src/agent/live_status.rs +230 -41
  15. package/crates/mint-cli/src/agent.rs +476 -108
  16. package/crates/mint-cli/src/commands/agent.rs +294 -86
  17. package/crates/mint-cli/src/commands/browser.rs +127 -0
  18. package/crates/mint-cli/src/commands/config.rs +14 -1
  19. package/crates/mint-cli/src/commands/integrations.rs +7 -0
  20. package/crates/mint-cli/src/commands/knowledge.rs +28 -34
  21. package/crates/mint-cli/src/commands/mod.rs +59 -1
  22. package/crates/mint-cli/src/commands/tailnet_web.rs +2 -7
  23. package/crates/mint-cli/src/companion_navigation.rs +268 -0
  24. package/crates/mint-cli/src/image.rs +31 -33
  25. package/crates/mint-cli/src/interactive/chat_tui.rs +4977 -864
  26. package/crates/mint-cli/src/interactive/command_ui.rs +39 -0
  27. package/crates/mint-cli/src/interactive/input_box.rs +56 -6
  28. package/crates/mint-cli/src/interactive/slash_commands.rs +321 -61
  29. package/crates/mint-cli/src/interactive/tool_activity.rs +667 -0
  30. package/crates/mint-cli/src/interactive.rs +93 -37
  31. package/crates/mint-cli/src/main.rs +36 -9
  32. package/crates/mint-cli/src/mcp.rs +5 -0
  33. package/crates/mint-cli/src/mod_host.rs +6 -0
  34. package/crates/mint-cli/src/mods_cli.rs +62 -0
  35. package/crates/mint-cli/src/mods_ui.rs +187 -0
  36. package/crates/mint-cli/src/onboard.rs +3492 -1360
  37. package/crates/mint-cli/src/terminal_theme.rs +3 -1
  38. package/crates/mint-cli/src/updater.rs +56 -2
  39. package/crates/mint-cli/tests/library_intake.rs +77 -0
  40. package/crates/mint-cli/tests/mods_testing.rs +137 -0
  41. package/crates/mint-companion-protocol/Cargo.toml +10 -0
  42. package/crates/mint-companion-protocol/src/lib.rs +276 -0
  43. package/crates/mint-core/Cargo.toml +6 -3
  44. package/crates/mint-core/build.rs +26 -0
  45. package/crates/mint-core/src/agent/chat.rs +406 -39
  46. package/crates/mint-core/src/agent/code_tools.rs +15 -0
  47. package/crates/mint-core/src/agent/memory/sources.rs +240 -0
  48. package/crates/mint-core/src/agent/memory.rs +220 -19
  49. package/crates/mint-core/src/agent/safety.rs +56 -4
  50. package/crates/mint-core/src/agent/skill_inventory.rs +327 -0
  51. package/crates/mint-core/src/agent/skills.rs +438 -90
  52. package/crates/mint-core/src/agent.rs +1 -0
  53. package/crates/mint-core/src/api_server/routes/auth.rs +1 -4
  54. package/crates/mint-core/src/api_server/routes/background_jobs.rs +310 -0
  55. package/crates/mint-core/src/api_server/routes/chat.rs +245 -214
  56. package/crates/mint-core/src/api_server/routes/html_preview.rs +77 -0
  57. package/crates/mint-core/src/api_server/routes/library.rs +97 -0
  58. package/crates/mint-core/src/api_server/routes/media_gen.rs +10 -57
  59. package/crates/mint-core/src/api_server/routes/misc.rs +220 -107
  60. package/crates/mint-core/src/api_server/routes/mod.rs +6 -0
  61. package/crates/mint-core/src/api_server/routes/mods.rs +31 -0
  62. package/crates/mint-core/src/api_server/routes/profile_oauth.rs +43 -26
  63. package/crates/mint-core/src/api_server/routes/sessions.rs +127 -69
  64. package/crates/mint-core/src/api_server/routes/skills_subagents.rs +16 -42
  65. package/crates/mint-core/src/api_server/routes/slash.rs +50 -1
  66. package/crates/mint-core/src/api_server/routes/sources.rs +101 -0
  67. package/crates/mint-core/src/api_server/routes/status_health.rs +18 -8
  68. package/crates/mint-core/src/api_server.rs +327 -72
  69. package/crates/mint-core/src/avatar_bridge.rs +15 -3
  70. package/crates/mint-core/src/browser/cdp.rs +82 -26
  71. package/crates/mint-core/src/browser/lifecycle.rs +143 -43
  72. package/crates/mint-core/src/browser/mod.rs +5 -4
  73. package/crates/mint-core/src/browser/navigate.rs +82 -9
  74. package/crates/mint-core/src/browser/runtime.rs +284 -0
  75. package/crates/mint-core/src/browser/session.rs +315 -7
  76. package/crates/mint-core/src/browser/workflow.rs +386 -0
  77. package/crates/mint-core/src/companion/navigation.rs +115 -0
  78. package/crates/mint-core/src/companion.rs +824 -0
  79. package/crates/mint-core/src/cron/scheduler.rs +34 -2
  80. package/crates/mint-core/src/integrations/channels.rs +87 -345
  81. package/crates/mint-core/src/integrations/mcp.rs +397 -50
  82. package/crates/mint-core/src/integrations/mcp_calls.rs +823 -0
  83. package/crates/mint-core/src/integrations/mcp_catalog.rs +267 -0
  84. package/crates/mint-core/src/integrations/mcp_device.rs +319 -0
  85. package/crates/mint-core/src/integrations/mcp_result.rs +401 -0
  86. package/crates/mint-core/src/integrations/webhooks.rs +539 -0
  87. package/crates/mint-core/src/integrations.rs +2 -0
  88. package/crates/mint-core/src/lib.rs +95 -32
  89. package/crates/mint-core/src/media/gemini_live.rs +134 -14
  90. package/crates/mint-core/src/media/image_models.rs +3 -27
  91. package/crates/mint-core/src/media/image_proxy.rs +563 -0
  92. package/crates/mint-core/src/media/image_workflow.rs +216 -0
  93. package/crates/mint-core/src/media/library/intake.rs +144 -0
  94. package/crates/mint-core/src/media/library/migration.rs +56 -0
  95. package/crates/mint-core/src/media/library/removal.rs +55 -0
  96. package/crates/mint-core/src/media/library/tests.rs +730 -0
  97. package/crates/mint-core/src/media/library/thumbnails.rs +72 -0
  98. package/crates/mint-core/src/media/library.rs +545 -0
  99. package/crates/mint-core/src/media/model_catalog.rs +375 -0
  100. package/crates/mint-core/src/media/pictures.rs +79 -222
  101. package/crates/mint-core/src/media/tool_images.rs +196 -0
  102. package/crates/mint-core/src/media/tts/playback.rs +609 -0
  103. package/crates/mint-core/src/media/tts.rs +1 -0
  104. package/crates/mint-core/src/media/video_models.rs +3 -22
  105. package/crates/mint-core/src/media.rs +5 -0
  106. package/crates/mint-core/src/mods/manager.rs +1124 -0
  107. package/crates/mint-core/src/mods/mod.rs +569 -0
  108. package/crates/mint-core/src/mods/operations.rs +204 -0
  109. package/crates/mint-core/src/mods/session.rs +146 -0
  110. package/crates/mint-core/src/mods/slash.rs +59 -0
  111. package/crates/mint-core/src/mods/telemetry.rs +306 -0
  112. package/crates/mint-core/src/orchestration/approvals.rs +344 -0
  113. package/crates/mint-core/src/orchestration/context_render.rs +553 -32
  114. package/crates/mint-core/src/orchestration/decision_parsing.rs +5 -23
  115. package/crates/mint-core/src/orchestration/diagnostics.rs +307 -0
  116. package/crates/mint-core/src/orchestration/image_regressions.rs +379 -0
  117. package/crates/mint-core/src/orchestration/loop_guard.rs +334 -0
  118. package/crates/mint-core/src/orchestration/mcp_review.rs +153 -0
  119. package/crates/mint-core/src/orchestration/memory_skill.rs +104 -32
  120. package/crates/mint-core/src/orchestration/mod.rs +3194 -755
  121. package/crates/mint-core/src/orchestration/mod_authoring_regressions.rs +209 -0
  122. package/crates/mint-core/src/orchestration/progress_clock.rs +80 -0
  123. package/crates/mint-core/src/orchestration/reliability.rs +541 -0
  124. package/crates/mint-core/src/orchestration/repeated_failures.rs +124 -0
  125. package/crates/mint-core/src/orchestration/review_regressions.rs +595 -0
  126. package/crates/mint-core/src/orchestration/run_control.rs +196 -0
  127. package/crates/mint-core/src/orchestration/tools/files.rs +191 -16
  128. package/crates/mint-core/src/orchestration/tools/media.rs +37 -41
  129. package/crates/mint-core/src/orchestration/tools/misc.rs +3 -2
  130. package/crates/mint-core/src/orchestration/tools/mod.rs +2 -0
  131. package/crates/mint-core/src/orchestration/tools/mods.rs +161 -0
  132. package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +321 -74
  133. package/crates/mint-core/src/orchestration/tools/project.rs +1 -1
  134. package/crates/mint-core/src/orchestration/tools/shell.rs +124 -7
  135. package/crates/mint-core/src/orchestration/tools/web.rs +5 -1
  136. package/crates/mint-core/src/orchestration/trajectory_schema.rs +353 -0
  137. package/crates/mint-core/src/orchestration/truncation.rs +211 -0
  138. package/crates/mint-core/src/orchestration/verification.rs +36 -6
  139. package/crates/mint-core/src/orchestration/workspace_helpers.rs +57 -23
  140. package/crates/mint-core/src/prompts/agent.rs +47 -41
  141. package/crates/mint-core/src/prompts/tool_catalog.rs +158 -38
  142. package/crates/mint-core/src/search/knowledge.rs +7 -1
  143. package/crates/mint-core/src/search/linked_folders/index.rs +177 -13
  144. package/crates/mint-core/src/search/linked_folders.rs +83 -2
  145. package/crates/mint-core/src/search/web_search.rs +44 -3
  146. package/crates/mint-core/src/slash/catalog.rs +1 -1
  147. package/crates/mint-core/src/slash/mod.rs +638 -12
  148. package/crates/mint-core/src/slash/mods.rs +155 -0
  149. package/crates/mint-core/src/swarm/batch.rs +643 -0
  150. package/crates/mint-core/src/swarm/dossier.rs +152 -0
  151. package/crates/mint-core/src/swarm/labels.rs +100 -0
  152. package/crates/mint-core/src/swarm/mod.rs +158 -0
  153. package/crates/mint-core/src/swarm/permissions.rs +167 -0
  154. package/crates/mint-core/src/system/auth.rs +156 -7
  155. package/crates/mint-core/src/system/bg_shell.rs +1197 -95
  156. package/crates/mint-core/src/system/config.rs +97 -13
  157. package/crates/mint-core/src/system/html_preview.rs +398 -0
  158. package/crates/mint-core/src/system/mint_web.rs +185 -0
  159. package/crates/mint-core/src/system/release_notes.rs +18 -0
  160. package/crates/mint-core/src/system/shell.rs +200 -16
  161. package/crates/mint-core/src/system/workspace.rs +276 -8
  162. package/crates/mint-core/src/system/workspace_history.rs +604 -0
  163. package/crates/mint-core/src/system.rs +4 -0
  164. package/crates/mint-core/tests/background_retention.rs +34 -0
  165. package/crates/mint-core/tests/background_shutdown.rs +24 -0
  166. package/crates/mint-core/tests/library_http.rs +209 -0
  167. package/crates/mint-core/tests/mcp_contract.rs +283 -0
  168. package/crates/mint-core/tests/mcp_stdio.rs +11 -10
  169. package/crates/mint-core/tests/media_model_catalog_http.rs +403 -0
  170. package/crates/mint-core/tests/memory_persistence.rs +27 -0
  171. package/crates/mint-core/tests/mods_http.rs +215 -0
  172. package/crates/mint-core/tests/terminal_shutdown.rs +109 -0
  173. package/crates/mint-mod-runtime/Cargo.toml +18 -0
  174. package/crates/mint-mod-runtime/src/lib.rs +507 -0
  175. package/crates/mint-mod-runtime/src/sdk.js +56 -0
  176. package/crates/mint-mod-runtime/src/test_sdk.js +32 -0
  177. package/crates/mint-mod-runtime/src/testing.rs +94 -0
  178. package/crates/mint-mod-runtime/tests/runtime.rs +156 -0
  179. package/docs/mods/AUTHORING.md +61 -0
  180. package/docs/mods/README.md +139 -0
  181. package/docs/mods/TESTING.md +56 -0
  182. package/docs/mods/mint-mods.d.ts +66 -0
  183. package/examples/device-mcp/README.md +22 -0
  184. package/examples/device-mcp/contract.md +49 -0
  185. package/examples/device-mcp/mock-device.mjs +66 -0
  186. package/examples/device-mcp/server.mjs +48 -0
  187. package/examples/device-mcp/server.test.mjs +78 -0
  188. package/examples/mods/task-checklist/README.md +1 -0
  189. package/examples/mods/task-checklist/main.ts +17 -0
  190. package/examples/mods/task-checklist/manifest.json +1 -0
  191. package/examples/mods/task-checklist/tests/checklist.test.ts +15 -0
  192. package/examples/mods/token-meter/README.md +1 -0
  193. package/examples/mods/token-meter/main.ts +8 -0
  194. package/examples/mods/token-meter/manifest.json +1 -0
  195. package/examples/mods/tool-guard/README.md +1 -0
  196. package/examples/mods/tool-guard/main.ts +9 -0
  197. package/examples/mods/tool-guard/manifest.json +1 -0
  198. package/out/web/assets/ArtifactPreviewPanel-DAT_Y7i0.css +1 -0
  199. package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +2 -0
  200. package/out/web/assets/CalculationCard-CRjP3E1j.js +1 -0
  201. package/out/web/assets/CliSessionsView-C3Br2v5Y.css +1 -0
  202. package/out/web/assets/CliSessionsView-MI8kXMAb.js +1 -0
  203. package/out/web/assets/CodeReviewPage-CUJceQme.css +1 -0
  204. package/out/web/assets/CodeReviewPage-VCUgPla_.js +2 -0
  205. package/out/web/assets/CommandPalette-BD8SlTE5.js +1 -0
  206. package/out/web/assets/ImageGenCard-BCXxkVJX.js +1 -0
  207. package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +1 -0
  208. package/out/web/assets/ImageStudioPanel-BP7N4rLr.js +1 -0
  209. package/out/web/assets/LibraryPage-DAxUTAg7.css +1 -0
  210. package/out/web/assets/LibraryPage-WlviRdR8.js +2 -0
  211. package/out/web/assets/LinkedFoldersView-CKRgroWK.js +1 -0
  212. package/out/web/assets/McpRegistryPicker-CoS83vtc.js +1 -0
  213. package/out/web/assets/McpServersView-D3wHnuHg.js +1 -0
  214. package/out/web/assets/MermaidCard-NqrHdyJW.js +2 -0
  215. package/out/web/assets/MintDashboard-B2DJsDDO.css +1 -0
  216. package/out/web/assets/MintDashboard-COw_xol7.js +63 -0
  217. package/out/web/assets/PluginsView-jBYmcKh4.js +1 -0
  218. package/out/web/assets/RewindModal-DempuDod.js +1 -0
  219. package/out/web/assets/ScheduledTasksView-5uw9-V9H.js +1 -0
  220. package/out/web/assets/SelectField-DXBQtrUK.js +1 -0
  221. package/out/web/assets/SelectField-lFEkkWCU.css +1 -0
  222. package/out/web/assets/SettingsWindow-2YP3yj7s.js +4 -0
  223. package/out/web/assets/SettingsWindow-Dz8LQVgD.css +1 -0
  224. package/out/web/assets/SkillsView-C-gp9J7m.js +2 -0
  225. package/out/web/assets/StockCard-BDMCe7mx.js +1 -0
  226. package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +38 -0
  227. package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +1 -0
  228. package/out/web/assets/UiFeatureCard-6BwxTOCe.js +1 -0
  229. package/out/web/assets/UiGridCard-DvZcLNxb.js +1 -0
  230. package/out/web/assets/UiMockupWidget-DD73QWg1.js +1 -0
  231. package/out/web/assets/VeoStudioPanel-DfsOJKv0.js +4 -0
  232. package/out/web/assets/WeatherCard-CPucIfq8.js +1 -0
  233. package/out/web/assets/WorkspacePanel-BfHKkulU.js +1 -0
  234. package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +2 -0
  235. package/out/web/assets/abnfDiagram-VCTEODGH-BOo3Ckng.js +1 -0
  236. package/out/web/assets/architecture-7GRP2DOG-ZOwheW3n.js +1 -0
  237. package/out/web/assets/architectureDiagram-5GKGNRK7-DC2FDzVd.js +36 -0
  238. package/out/web/assets/blockDiagram-I7D4REHJ-BrKFWdAH.js +129 -0
  239. package/out/web/assets/c4Diagram-7LVT6UL2-BHTHK1Yd.js +38 -0
  240. package/out/web/assets/chunk-2Q5K7J3B-D2JLPQzF.js +1 -0
  241. package/out/web/assets/chunk-4HAMMTFA-BYFkZwNj.js +62 -0
  242. package/out/web/assets/chunk-5VM5RSS4-BZ2oBPYQ.js +15 -0
  243. package/out/web/assets/chunk-75Z2AOVW-BSKfSKSA.js +2 -0
  244. package/out/web/assets/chunk-DU6HZSFF-vIhsiW6n.js +128 -0
  245. package/out/web/assets/chunk-F27PBJKO-BlYpW9xq.js +1 -0
  246. package/out/web/assets/chunk-FOHPRMQF-JXEeeJ9r.js +161 -0
  247. package/out/web/assets/chunk-GMAD6QVW-BIBz_Ug5.js +72 -0
  248. package/out/web/assets/chunk-GVQU2GXP-NPSHxeqC.js +1 -0
  249. package/out/web/assets/chunk-IMKFNOWR-YlJK8_Fp.js +231 -0
  250. package/out/web/assets/chunk-JWPE2WC7-BeEMUFDw.js +1 -0
  251. package/out/web/assets/chunk-L3NEJ4N5-DMYi4-af.js +1 -0
  252. package/out/web/assets/chunk-OSK3NFVY-Begd3-jA.js +10 -0
  253. package/out/web/assets/chunk-P2QGCYS3-D1I4xFHN.js +1 -0
  254. package/out/web/assets/chunk-POPQ4Y6H-CafZZzNE.js +1 -0
  255. package/out/web/assets/chunk-PWAF6VOD-Dwh6_Lnt.js +1 -0
  256. package/out/web/assets/chunk-SHT3W25Y-B0XrCSEb.js +168 -0
  257. package/out/web/assets/chunk-SVP7TREG-CxLsddxC.js +88 -0
  258. package/out/web/assets/chunk-TICWLB2K-y156QInb.js +206 -0
  259. package/out/web/assets/chunk-XXDRQBXY-B9YlhOkE.js +1 -0
  260. package/out/web/assets/chunk-Y2CYZVJY-BxCWjNVG.js +1 -0
  261. package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +1 -0
  262. package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +1 -0
  263. package/out/web/assets/config-CvIO5hkn.js +2 -0
  264. package/out/web/assets/config-DqXJXeZM.css +1 -0
  265. package/out/web/assets/core-Mi3s5D4_.js +1 -0
  266. package/out/web/assets/cose-bilkent-JH36ORCC-qlna31ty.js +1 -0
  267. package/out/web/assets/cynefin-OW5HDTMX-SMVvrLf-.js +1 -0
  268. package/out/web/assets/cynefinDiagram-5FMLGOSQ-rZznN24T.js +62 -0
  269. package/out/web/assets/cytoscape.esm-DWtAUG0r.js +321 -0
  270. package/out/web/assets/dagre-CnWJJM5f.js +1 -0
  271. package/out/web/assets/dagre-GXQ25YYZ-BOToydFu.js +4 -0
  272. package/out/web/assets/diagram-S7CK7UJ4-TMVaGQEJ.js +30 -0
  273. package/out/web/assets/diagram-UQ7AKVKN-C2JK-GP2.js +41 -0
  274. package/out/web/assets/diagram-VSXAHHWV-Cw8VSi2K.js +3 -0
  275. package/out/web/assets/diagram-VX7I27RA-DXp4fKk9.js +24 -0
  276. package/out/web/assets/diagram-Z3DM3KII-CzSicYiW.js +24 -0
  277. package/out/web/assets/dist-9ISJ7r-g.js +1 -0
  278. package/out/web/assets/dist-js-BV3mI7uw.js +1 -0
  279. package/out/web/assets/ebnfDiagram-PWID7BFC-Dqj-zqPu.js +1 -0
  280. package/out/web/assets/erDiagram-RLTQ6QDP-DZwFZAuD.js +99 -0
  281. package/out/web/assets/event-D7D-EBy9.js +1 -0
  282. package/out/web/assets/eventmodeling-NTZA5JFV-C0O-Lvgp.js +1 -0
  283. package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +1 -0
  284. package/out/web/assets/ganttDiagram-EL5Y4UJY-Da4ULXTl.js +292 -0
  285. package/out/web/assets/gitGraph-4MIJSDKK-B9wNFZIP.js +1 -0
  286. package/out/web/assets/gitGraphDiagram-WWUBYQGX-RjrhB0MP.js +106 -0
  287. package/out/web/assets/graphlib-Bh9gHply.js +1 -0
  288. package/out/web/assets/index-BmpulIkg.js +168 -0
  289. package/out/web/assets/index-CkvBwhX_.css +1 -0
  290. package/out/web/assets/info-A6RAGUB7-B3dfCpeY.js +1 -0
  291. package/out/web/assets/infoDiagram-27XIBGKW-QVWkLVx7.js +2 -0
  292. package/out/web/assets/ishikawaDiagram-5VMMS53U-CLFNIRI9.js +70 -0
  293. package/out/web/assets/journeyDiagram-3NMN7TZE-fgOQadYF.js +139 -0
  294. package/out/web/assets/jsx-runtime-DmUwWVTj.js +1 -0
  295. package/out/web/assets/kanban-definition-UXKFOSKX-G3Q_9g9O.js +89 -0
  296. package/out/web/assets/katex-MCdQaEAD.js +257 -0
  297. package/out/web/assets/mermaid-parser.core-v3Jm7nco.js +7 -0
  298. package/out/web/assets/mermaid.core-DTEjLUA1.js +44 -0
  299. package/out/web/assets/mindmap-definition-YA3MSWOX-C_D42_fF.js +96 -0
  300. package/out/web/assets/modelManager-BPLycQrN.js +2 -0
  301. package/out/web/assets/oauthManager-BUK7ic7q.js +1 -0
  302. package/out/web/assets/packet-AYTQ26CC-B-R-SHlF.js +1 -0
  303. package/out/web/assets/pdf-Bcahc2yC.js +56 -0
  304. package/out/web/assets/pdf.worker.min-Dkey6ZUl.mjs +27 -0
  305. package/out/web/assets/pegDiagram-XKGWAZYB-Da2q_giz.js +1 -0
  306. package/out/web/assets/pie-WAS4IAKB-BC8F0N7-.js +1 -0
  307. package/out/web/assets/pieDiagram-E7YTZNPT-Iy9REJNC.js +39 -0
  308. package/out/web/assets/quadrantDiagram-AXDQQJYC-ztsiYw7h.js +7 -0
  309. package/out/web/assets/radar-RG4KPBEZ-B_1P8Z_z.js +1 -0
  310. package/out/web/assets/railroad-74A4TZTK-DuTqczSo.js +1 -0
  311. package/out/web/assets/railroad-abnf-HS5TGJTU-Df29WLmp.js +1 -0
  312. package/out/web/assets/railroad-ebnf-LZEXJU2U-ePFMo-qp.js +1 -0
  313. package/out/web/assets/railroad-peg-WCYAUIDC-DYXLW4_E.js +1 -0
  314. package/out/web/assets/railroadDiagram-O6MQD6OU-LStnZf2S.js +1 -0
  315. package/out/web/assets/requirementDiagram-BXWQKSXE-DmzonMH-.js +84 -0
  316. package/out/web/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
  317. package/out/web/assets/rough.esm-CNnFptCR.js +1 -0
  318. package/out/web/assets/sankeyDiagram-P5KCCOFB-B8vjmUU1.js +40 -0
  319. package/out/web/assets/sequenceDiagram-WJ2MYXX4-CTnplOAX.js +162 -0
  320. package/out/web/assets/sizeCapture-INFHLROL-C8Q8DbV5.js +1 -0
  321. package/out/web/assets/src-CRDfRlHM.js +1 -0
  322. package/out/web/assets/stateDiagram-D77RDMKH-BFrIaLdI.js +1 -0
  323. package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +1 -0
  324. package/out/web/assets/studio-BBZQ_-QK.js +1 -0
  325. package/out/web/assets/swimlanes-42K2YHIH-BXGngUNU.js +1 -0
  326. package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +8 -0
  327. package/out/web/assets/timeline-definition-24CTP7MA-CegH7n43.js +120 -0
  328. package/out/web/assets/treeView-Q6P3EWNA-DTpJv2Tw.js +1 -0
  329. package/out/web/assets/treemap-WGGIJYW6-i8M9iJU6.js +1 -0
  330. package/out/web/assets/vennDiagram-4TSXK5OY-Dpzo1S8U.js +34 -0
  331. package/out/web/assets/wardley-WFR3VGLG-BrNsgtfw.js +1 -0
  332. package/out/web/assets/wardleyDiagram-VM6X3IG4-lQ92LFE1.js +78 -0
  333. package/out/web/assets/window-CkUOP7t0.js +1 -0
  334. package/out/web/assets/workspaces-SYPrmbgC.js +1 -0
  335. package/out/web/assets/xychartDiagram-S5SC5T6Z-BaOaEoBd.js +7 -0
  336. package/out/web/index-web.html +4 -3
  337. package/out/web/index.html +4 -3
  338. package/out/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  339. package/out/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  340. package/out/web/pdfjs/cmaps/78-H.bcmap +0 -0
  341. package/out/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  342. package/out/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  343. package/out/web/pdfjs/cmaps/78-V.bcmap +0 -0
  344. package/out/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  345. package/out/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  346. package/out/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  347. package/out/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  348. package/out/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  349. package/out/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  350. package/out/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  351. package/out/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  352. package/out/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  353. package/out/web/pdfjs/cmaps/Add-H.bcmap +0 -0
  354. package/out/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  355. package/out/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  356. package/out/web/pdfjs/cmaps/Add-V.bcmap +0 -0
  357. package/out/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  358. package/out/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  359. package/out/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  360. package/out/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  361. package/out/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  362. package/out/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  363. package/out/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  364. package/out/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  365. package/out/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  366. package/out/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  367. package/out/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  368. package/out/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  369. package/out/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  370. package/out/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  371. package/out/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  372. package/out/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  373. package/out/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  374. package/out/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  375. package/out/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  376. package/out/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  377. package/out/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  378. package/out/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  379. package/out/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  380. package/out/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  381. package/out/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  382. package/out/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  383. package/out/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  384. package/out/web/pdfjs/cmaps/B5-H.bcmap +0 -0
  385. package/out/web/pdfjs/cmaps/B5-V.bcmap +0 -0
  386. package/out/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  387. package/out/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  388. package/out/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  389. package/out/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  390. package/out/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  391. package/out/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  392. package/out/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  393. package/out/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  394. package/out/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  395. package/out/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  396. package/out/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  397. package/out/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  398. package/out/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  399. package/out/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  400. package/out/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
  401. package/out/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
  402. package/out/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
  403. package/out/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  404. package/out/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  405. package/out/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
  406. package/out/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  407. package/out/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  408. package/out/web/pdfjs/cmaps/GB-H.bcmap +4 -0
  409. package/out/web/pdfjs/cmaps/GB-V.bcmap +0 -0
  410. package/out/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  411. package/out/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  412. package/out/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  413. package/out/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  414. package/out/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  415. package/out/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  416. package/out/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  417. package/out/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  418. package/out/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
  419. package/out/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
  420. package/out/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  421. package/out/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  422. package/out/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  423. package/out/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  424. package/out/web/pdfjs/cmaps/H.bcmap +0 -0
  425. package/out/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  426. package/out/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  427. package/out/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  428. package/out/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  429. package/out/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  430. package/out/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  431. package/out/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  432. package/out/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  433. package/out/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  434. package/out/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  435. package/out/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  436. package/out/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  437. package/out/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
  438. package/out/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
  439. package/out/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  440. package/out/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  441. package/out/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
  442. package/out/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  443. package/out/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  444. package/out/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
  445. package/out/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  446. package/out/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  447. package/out/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  448. package/out/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  449. package/out/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  450. package/out/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  451. package/out/web/pdfjs/cmaps/Katakana.bcmap +0 -0
  452. package/out/web/pdfjs/cmaps/LICENSE +36 -0
  453. package/out/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
  454. package/out/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
  455. package/out/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  456. package/out/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  457. package/out/web/pdfjs/cmaps/Roman.bcmap +0 -0
  458. package/out/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  459. package/out/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  460. package/out/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  461. package/out/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  462. package/out/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  463. package/out/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  464. package/out/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  465. package/out/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  466. package/out/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  467. package/out/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  468. package/out/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  469. package/out/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  470. package/out/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  471. package/out/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  472. package/out/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  473. package/out/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  474. package/out/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  475. package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  476. package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  477. package/out/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  478. package/out/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  479. package/out/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  480. package/out/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  481. package/out/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  482. package/out/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  483. package/out/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  484. package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  485. package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  486. package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  487. package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  488. package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  489. package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  490. package/out/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  491. package/out/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  492. package/out/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  493. package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  494. package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  495. package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  496. package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  497. package/out/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  498. package/out/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  499. package/out/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  500. package/out/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  501. package/out/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  502. package/out/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  503. package/out/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  504. package/out/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  505. package/out/web/pdfjs/cmaps/V.bcmap +0 -0
  506. package/out/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  507. package/out/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  508. package/out/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  509. package/out/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  510. package/out/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  511. package/out/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  512. package/out/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  513. package/out/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  514. package/out/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  515. package/out/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  516. package/out/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  517. package/out/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  518. package/out/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
  519. package/out/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  520. package/out/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  521. package/out/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  522. package/out/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  523. package/out/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
  524. package/out/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
  525. package/out/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  526. package/out/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  527. package/out/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
  528. package/out/web/pdfjs/wasm/LICENSE_QCMS +21 -0
  529. package/out/web/pdfjs/wasm/jbig2.wasm +0 -0
  530. package/out/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
  531. package/out/web/pdfjs/wasm/openjpeg.wasm +0 -0
  532. package/out/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
  533. package/out/web/pdfjs/wasm/qcms_bg.wasm +0 -0
  534. package/out/web/pdfjs/wasm/quickjs-eval.js +16 -0
  535. package/out/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
  536. package/package.json +74 -23
  537. package/shared-modules/.github/workflows/ci.yml +120 -0
  538. package/shared-modules/.github/workflows/update.yaml +28 -0
  539. package/slash-commands.json +14 -3
  540. package/src/bin/auto-build.js +66 -0
  541. package/src/bin/desktop.js +113 -0
  542. package/src/bin/desktop.test.js +113 -0
  543. package/src/bin/index.js +21 -25
  544. package/src/bin/index.test.js +89 -0
  545. package/src/bin/measure-effects.mjs +53 -0
  546. package/src/bin/package.test.js +28 -0
  547. package/src/bin/pdfjs-assets.mjs +31 -0
  548. package/src/bin/release-notes.test.js +84 -0
  549. package/src/bin/styles.test.js +20 -0
  550. package/src/bin/web.js +5 -0
  551. package/src/bin/web.test.js +39 -0
  552. package/src/renderer/index.html +0 -2
  553. package/src/renderer/node_modules/.vite/vitest/CACHEDIR.TAG +3 -0
  554. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  555. package/src/renderer/shared/agentProgress.ts +42 -0
  556. package/src/renderer/shared/components/Settings/AgentsTab.tsx +76 -11
  557. package/src/renderer/shared/components/Settings/AudioTab.tsx +8 -7
  558. package/src/renderer/shared/components/Settings/AutomationTab.tsx +59 -2
  559. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +18 -12
  560. package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.test.tsx +65 -0
  561. package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.tsx +456 -0
  562. package/src/renderer/shared/components/Settings/GeneralTab.tsx +72 -113
  563. package/src/renderer/shared/components/Settings/MemoryTab.test.tsx +37 -0
  564. package/src/renderer/shared/components/Settings/MemoryTab.tsx +71 -31
  565. package/src/renderer/shared/components/Settings/NotificationsTab.test.tsx +15 -0
  566. package/src/renderer/shared/components/Settings/NotificationsTab.tsx +84 -0
  567. package/src/renderer/shared/components/Settings/PluginsTab.tsx +101 -24
  568. package/src/renderer/shared/components/Settings/ProfileTab.tsx +1 -1
  569. package/src/renderer/shared/components/Settings/SettingsWindow.tsx +796 -0
  570. package/src/renderer/shared/components/Settings/ThemeTab.tsx +31 -11
  571. package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +220 -0
  572. package/src/renderer/shared/components/activity/AgentActivityTable.tsx +340 -0
  573. package/src/renderer/shared/components/activity/McpToolResults.tsx +118 -0
  574. package/src/renderer/shared/components/activity/PlanChecklistWidget.tsx +77 -0
  575. package/src/renderer/shared/components/activity/RunSummaryDashboard.test.tsx +127 -0
  576. package/src/renderer/shared/components/activity/RunSummaryDashboard.tsx +222 -0
  577. package/src/renderer/shared/components/activity/SubagentDagView.test.ts +123 -0
  578. package/src/renderer/shared/components/activity/SubagentDagView.tsx +420 -0
  579. package/src/renderer/shared/components/activity/SubagentStatusStrip.test.tsx +82 -0
  580. package/src/renderer/shared/components/activity/SubagentStatusStrip.tsx +107 -0
  581. package/src/renderer/shared/components/activity/ViewedImages.tsx +140 -0
  582. package/src/renderer/shared/components/app/AuthGate.tsx +227 -0
  583. package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +186 -0
  584. package/src/renderer/shared/components/app/CommandPalette.tsx +593 -0
  585. package/src/renderer/shared/components/app/DashboardSidebar.tsx +1230 -0
  586. package/src/renderer/shared/components/app/DesktopTitlebar.tsx +354 -0
  587. package/src/renderer/shared/components/app/ErrorNotice.tsx +29 -0
  588. package/src/renderer/shared/components/app/MintDashboard.tsx +2182 -0
  589. package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +127 -0
  590. package/src/renderer/shared/components/cards/CalculationCard.tsx +86 -0
  591. package/src/renderer/shared/components/cards/ImageGenCard.tsx +146 -0
  592. package/src/renderer/shared/components/cards/ImageSearchCard.tsx +145 -0
  593. package/src/renderer/shared/components/cards/MermaidCard.tsx +283 -0
  594. package/src/renderer/shared/components/cards/StockCard.tsx +152 -0
  595. package/src/renderer/shared/components/cards/ToolResultCards.tsx +86 -0
  596. package/src/renderer/shared/components/cards/UiFeatureCard.tsx +267 -0
  597. package/src/renderer/shared/components/cards/UiGridCard.tsx +188 -0
  598. package/src/renderer/shared/components/cards/WeatherCard.tsx +224 -0
  599. package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +24 -0
  600. package/src/renderer/shared/components/chat/ApprovalCard.tsx +347 -0
  601. package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +109 -0
  602. package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +145 -0
  603. package/src/renderer/shared/components/chat/ChatMessageItem.tsx +242 -0
  604. package/src/renderer/shared/components/chat/ChatPanel.test.tsx +31 -0
  605. package/src/renderer/shared/components/chat/ChatPanel.tsx +1999 -0
  606. package/src/renderer/shared/components/chat/CollapsiblePasteInput.tsx +90 -0
  607. package/src/renderer/shared/components/chat/ContextCompactionStatus.tsx +17 -0
  608. package/src/renderer/shared/components/chat/DiffReviewModal.tsx +565 -0
  609. package/src/renderer/shared/components/chat/GeminiLiveOverlay.css +86 -0
  610. package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +24 -0
  611. package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +112 -0
  612. package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +936 -0
  613. package/src/renderer/shared/components/chat/QueuedMessagesBanner.test.tsx +89 -0
  614. package/src/renderer/shared/components/chat/QueuedMessagesBanner.tsx +201 -0
  615. package/src/renderer/shared/components/chat/RewindModal.tsx +414 -0
  616. package/src/renderer/shared/components/chat/SlashChoicePanel.tsx +88 -0
  617. package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +33 -0
  618. package/src/renderer/shared/components/chat/SlashSuggestions.tsx +346 -0
  619. package/src/renderer/shared/components/chat/SourcesBlock.tsx +435 -0
  620. package/src/renderer/shared/components/chat/ThinkingBlock.tsx +102 -0
  621. package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +86 -0
  622. package/src/renderer/shared/components/chat/TurnFileChanges.tsx +430 -0
  623. package/src/renderer/shared/components/chat/VirtualChatHistory.tsx +82 -0
  624. package/src/renderer/shared/components/chat/VoicePill.css +85 -0
  625. package/src/renderer/shared/components/chat/VoicePill.d.ts +12 -0
  626. package/src/renderer/shared/components/chat/VoicePill.jsx +191 -0
  627. package/src/renderer/shared/components/chat/VoicePill.test.tsx +22 -0
  628. package/src/renderer/shared/components/extensions/CliSessionsView.test.tsx +125 -0
  629. package/src/renderer/shared/components/extensions/CliSessionsView.tsx +545 -0
  630. package/src/renderer/shared/components/extensions/LinkedFoldersView.tsx +614 -0
  631. package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +302 -0
  632. package/src/renderer/shared/components/extensions/McpServersView.tsx +1040 -0
  633. package/src/renderer/shared/components/extensions/McpToolAllowlist.tsx +182 -0
  634. package/src/renderer/shared/components/extensions/ModUi.test.tsx +70 -0
  635. package/src/renderer/shared/components/extensions/ModsPanel.tsx +208 -0
  636. package/src/renderer/shared/components/extensions/PluginsView.tsx +325 -0
  637. package/src/renderer/shared/components/extensions/ScheduledTasksView.tsx +614 -0
  638. package/src/renderer/shared/components/extensions/SkillsView.test.tsx +31 -0
  639. package/src/renderer/shared/components/extensions/SkillsView.tsx +363 -0
  640. package/src/renderer/shared/components/studio/ImageStudioPanel.tsx +740 -0
  641. package/src/renderer/shared/components/studio/LibraryItemCard.tsx +72 -0
  642. package/src/renderer/shared/components/studio/LibraryPage.tsx +165 -0
  643. package/src/renderer/shared/components/studio/LibraryPreview.tsx +59 -0
  644. package/src/renderer/shared/components/studio/VeoStudioPanel.tsx +708 -0
  645. package/src/renderer/shared/components/ui/GlideSelect.css +139 -0
  646. package/src/renderer/shared/components/ui/ProviderBrandIcon.test.tsx +67 -0
  647. package/src/renderer/shared/components/ui/ProviderBrandIcon.tsx +167 -0
  648. package/src/renderer/shared/components/ui/README.md +52 -0
  649. package/src/renderer/shared/components/ui/SelectField.test.tsx +27 -0
  650. package/src/renderer/shared/components/ui/SelectField.tsx +506 -0
  651. package/src/renderer/shared/components/ui/button.tsx +37 -0
  652. package/src/renderer/shared/components/ui/error-recovery.css +35 -0
  653. package/src/renderer/shared/components/ui/error-recovery.tsx +35 -0
  654. package/src/renderer/shared/components/ui/voice-powered-orb.css +6 -0
  655. package/src/renderer/shared/components/ui/voice-powered-orb.tsx +374 -0
  656. package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +700 -0
  657. package/src/renderer/shared/components/workspace/BackgroundTerminals.test.tsx +63 -0
  658. package/src/renderer/shared/components/workspace/BackgroundTerminals.tsx +212 -0
  659. package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +707 -0
  660. package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +442 -0
  661. package/src/renderer/shared/components/workspace/SessionSources.tsx +113 -0
  662. package/src/renderer/shared/components/workspace/TerminalDock.tsx +244 -0
  663. package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +68 -0
  664. package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +819 -0
  665. package/src/renderer/shared/components/workspace/ToolsPanelToggle.tsx +13 -0
  666. package/src/renderer/shared/components/workspace/WorkspaceFileInfo.tsx +28 -0
  667. package/src/renderer/shared/components/workspace/WorkspaceMediaPreview.tsx +17 -0
  668. package/src/renderer/shared/components/workspace/WorkspacePaneResizer.tsx +92 -0
  669. package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +89 -0
  670. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +89 -0
  671. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.tsx +19 -0
  672. package/src/renderer/shared/components/workspace/WorkspaceSelector.tsx +246 -0
  673. package/src/renderer/shared/components/workspace/background-terminals.css +292 -0
  674. package/src/renderer/shared/components/workspace/workspace-selector.css +330 -0
  675. package/src/renderer/shared/constants/config.ts +37 -1
  676. package/src/renderer/shared/constants/slashCommands.ts +1 -1
  677. package/src/renderer/shared/conversation/coordinator.test.ts +46 -0
  678. package/src/renderer/shared/conversation/coordinator.ts +50 -2
  679. package/src/renderer/shared/conversation/lifecycle.test.ts +524 -0
  680. package/src/renderer/shared/conversation/lifecycle.ts +575 -0
  681. package/src/renderer/shared/conversation/projectNavigation.test.ts +123 -0
  682. package/src/renderer/shared/conversation/projectNavigation.ts +50 -0
  683. package/src/renderer/shared/conversation/sourcesTransport.ts +27 -0
  684. package/src/renderer/shared/conversation/syncView.test.ts +1 -34
  685. package/src/renderer/shared/conversation/syncView.ts +0 -37
  686. package/src/renderer/shared/conversation/transport.test.ts +183 -0
  687. package/src/renderer/shared/conversation/transport.ts +201 -0
  688. package/src/renderer/shared/conversation/useConversationCoordinator.ts +67 -79
  689. package/src/renderer/shared/css/agent-observability.css +90 -30
  690. package/src/renderer/shared/css/auth-gate.css +112 -13
  691. package/src/renderer/shared/css/chat-ui-cards.css +35 -35
  692. package/src/renderer/shared/css/code-review.css +119 -86
  693. package/src/renderer/shared/css/code-sessions.css +37 -10
  694. package/src/renderer/shared/css/collapsed-paste.css +35 -0
  695. package/src/renderer/shared/css/effects.css +15 -0
  696. package/src/renderer/shared/css/error-notice.css +60 -0
  697. package/src/renderer/shared/css/file-changes.css +167 -5
  698. package/src/renderer/shared/css/library.css +476 -0
  699. package/src/renderer/shared/css/management-views.css +216 -37
  700. package/src/renderer/shared/css/mcp-results.css +54 -0
  701. package/src/renderer/shared/css/model-selector.css +163 -105
  702. package/src/renderer/shared/css/mods.css +77 -0
  703. package/src/renderer/shared/css/notification-notice.css +28 -0
  704. package/src/renderer/shared/css/queued-messages.css +221 -0
  705. package/src/renderer/shared/css/session-sources.css +28 -0
  706. package/src/renderer/shared/css/settings/agents.css +2 -2
  707. package/src/renderer/shared/css/settings/base.css +1260 -0
  708. package/src/renderer/shared/css/settings/general.css +384 -92
  709. package/src/renderer/shared/css/settings/memory.css +154 -0
  710. package/src/renderer/shared/css/settings/notifications.css +11 -0
  711. package/src/renderer/shared/css/settings/plugins.css +8 -8
  712. package/src/renderer/shared/css/settings/theme.css +49 -32
  713. package/src/renderer/shared/css/slash-choice.css +95 -0
  714. package/src/renderer/shared/css/slash-suggestions.css +68 -0
  715. package/src/renderer/shared/css/sources-block.css +91 -0
  716. package/src/renderer/shared/css/thinking-status.css +46 -0
  717. package/src/renderer/shared/css/tools-panel.css +7 -0
  718. package/src/renderer/shared/css/ui-tailwind.css +19 -0
  719. package/src/renderer/shared/css/viewed-images.css +104 -0
  720. package/src/renderer/shared/css/workspace-file-info.css +12 -0
  721. package/src/renderer/shared/css/workspace-media-preview.css +13 -0
  722. package/src/renderer/shared/env.d.ts +9 -0
  723. package/src/renderer/shared/fonts.css +46 -0
  724. package/src/renderer/shared/fonts.ts +3 -12
  725. package/src/renderer/shared/lib/utils.ts +6 -0
  726. package/src/renderer/shared/library/controller.test.ts +61 -0
  727. package/src/renderer/shared/library/controller.ts +57 -0
  728. package/src/renderer/shared/library/transport.test.ts +26 -0
  729. package/src/renderer/shared/library/transport.ts +60 -0
  730. package/src/renderer/shared/library/useLibraryFile.ts +38 -0
  731. package/src/renderer/shared/mods.ts +58 -0
  732. package/src/renderer/shared/platform.test.ts +200 -1
  733. package/src/renderer/shared/platform.ts +55 -4
  734. package/src/renderer/shared/speech/player.ts +160 -0
  735. package/src/renderer/shared/speech/types.ts +11 -0
  736. package/src/renderer/shared/theme/themeManager.test.ts +63 -0
  737. package/src/renderer/shared/theme/themeManager.ts +20 -5
  738. package/src/renderer/shared/theme/themes.ts +17 -4
  739. package/src/renderer/shared/theme/tokens.css +448 -51
  740. package/src/renderer/shared/types.ts +111 -2
  741. package/src/renderer/shared/utils/agentActivity.test.ts +234 -0
  742. package/src/renderer/shared/utils/agentActivity.ts +221 -32
  743. package/src/renderer/shared/utils/approval.tsx +7 -0
  744. package/src/renderer/shared/utils/artifactBrowser.test.ts +80 -0
  745. package/src/renderer/shared/utils/artifactBrowser.ts +41 -0
  746. package/src/renderer/shared/utils/artifacts.ts +55 -0
  747. package/src/renderer/shared/utils/contextCompaction.test.tsx +48 -0
  748. package/src/renderer/shared/utils/contextCompaction.ts +21 -0
  749. package/src/renderer/shared/utils/effectVisibility.ts +9 -0
  750. package/src/renderer/shared/utils/externalLinks.ts +91 -0
  751. package/src/renderer/shared/utils/lazyWithRetry.ts +13 -0
  752. package/src/renderer/shared/utils/markdown.tsx +172 -38
  753. package/src/renderer/shared/utils/markdown.webImages.test.tsx +124 -2
  754. package/src/renderer/shared/utils/mcpResults.test.tsx +114 -0
  755. package/src/renderer/shared/utils/modViewport.ts +7 -0
  756. package/src/renderer/shared/utils/nativeRecordingSession.test.ts +61 -0
  757. package/src/renderer/shared/utils/nativeRecordingSession.ts +46 -0
  758. package/src/renderer/shared/utils/notificationSettings.test.ts +14 -0
  759. package/src/renderer/shared/utils/notificationSettings.ts +23 -0
  760. package/src/renderer/shared/utils/notifications.test.ts +51 -0
  761. package/src/renderer/shared/utils/notifications.ts +54 -0
  762. package/src/renderer/shared/utils/pastedText.test.ts +20 -0
  763. package/src/renderer/shared/utils/pastedText.ts +13 -0
  764. package/src/renderer/shared/utils/pdfRange.ts +16 -0
  765. package/src/renderer/shared/utils/previewObjectUrls.ts +20 -0
  766. package/src/renderer/shared/utils/progressCache.test.ts +32 -0
  767. package/src/renderer/shared/utils/sessionSources.test.ts +71 -0
  768. package/src/renderer/shared/utils/sessionSources.ts +36 -0
  769. package/src/renderer/shared/utils/slashCommandProcessor.ts +5 -2
  770. package/src/renderer/shared/utils/sourceAttachments.ts +28 -0
  771. package/src/renderer/shared/utils/speech.ts +65 -9
  772. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +22 -0
  773. package/src/renderer/shared/utils/syntaxHighlight.tsx +26 -0
  774. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +49 -5
  775. package/src/renderer/shared/utils/useLatestCallback.ts +9 -0
  776. package/src/renderer/shared/utils/useNativeVoiceInput.ts +95 -73
  777. package/src/renderer/shared/utils/useSessionSources.ts +37 -0
  778. package/src/renderer/shared/utils/viewedImages.test.ts +129 -0
  779. package/src/renderer/shared/utils/viewedImages.ts +97 -0
  780. package/src/renderer/shared/utils/workspaceDrag.ts +30 -0
  781. package/src/renderer/shared/utils/workspaceImage.ts +17 -0
  782. package/src/renderer/shared/utils/workspaceListPreferences.test.ts +64 -0
  783. package/src/renderer/shared/utils/workspaceListPreferences.ts +54 -0
  784. package/src/renderer/shared/utils/workspaceMedia.ts +41 -0
  785. package/src/renderer/shared/utils/workspacePane.test.ts +33 -0
  786. package/src/renderer/shared/utils/workspacePane.ts +20 -0
  787. package/src/renderer/shared/utils/workspacePreview.test.ts +169 -0
  788. package/src/renderer/shared/utils/workspacePreview.ts +47 -0
  789. package/src/renderer/shared/utils/workspaceSnapshot.test.ts +49 -0
  790. package/src/renderer/shared/utils/workspaceSnapshot.ts +26 -0
  791. package/src/renderer/shared/utils/workspaces.ts +11 -0
  792. package/src/renderer/shared/workspaceUndo.ts +88 -0
  793. package/src/renderer/src/App.tsx +15 -9
  794. package/src/renderer/src/components/ChatPanel.tsx +1 -1
  795. package/src/renderer/src/components/DashboardSidebar.tsx +2 -2
  796. package/src/renderer/src/components/ImageStudioPanel.tsx +1 -1
  797. package/src/renderer/src/components/LiveTranslateControls.tsx +65 -0
  798. package/src/renderer/src/components/MintDashboard.tsx +1 -1
  799. package/src/renderer/src/components/ProactiveGlow.tsx +1 -1
  800. package/src/renderer/src/components/ScreenPicker.css +67 -30
  801. package/src/renderer/src/components/ScreenPicker.tsx +201 -60
  802. package/src/renderer/src/components/SettingsWindow.tsx +6 -825
  803. package/src/renderer/src/components/SpotlightWindow.tsx +4 -32
  804. package/src/renderer/src/components/VeoStudioPanel.tsx +1 -1
  805. package/src/renderer/src/components/WorkspacePanel.tsx +261 -38
  806. package/src/renderer/src/components/browser-shell.css +27 -27
  807. package/src/renderer/src/components/browser-suggestions-popup.css +9 -9
  808. package/src/renderer/src/components/liveTranslateFrames.test.ts +26 -5
  809. package/src/renderer/src/components/liveTranslateFrames.ts +18 -7
  810. package/src/renderer/src/components/liveTranslateLanguage.ts +6 -0
  811. package/src/renderer/src/css/app-shell.css +69 -234
  812. package/src/renderer/src/css/chat.css +207 -494
  813. package/src/renderer/src/css/desktop-tools.css +6 -139
  814. package/src/renderer/src/css/misc.css +0 -52
  815. package/src/renderer/src/css/sidebar-workspace.css +171 -94
  816. package/src/renderer/src/css/spotlight.css +2 -2
  817. package/src/renderer/src/css/tool-surfaces.css +387 -61
  818. package/src/renderer/src/css/widget.css +7 -6
  819. package/src/renderer/src/env.d.ts +14 -9
  820. package/src/renderer/src/index.css +16 -64
  821. package/src/renderer/src/main.tsx +2 -1
  822. package/src/renderer/src/proactiveSuggestions.ts +29 -0
  823. package/src/renderer/src/tauri.ts +195 -337
  824. package/src/renderer/src/voiceInput.ts +5 -2
  825. package/src/renderer/src-web/App.tsx +6 -8
  826. package/src/renderer/src-web/components/ChatPanel.tsx +1 -1
  827. package/src/renderer/src-web/components/DashboardSidebar.tsx +2 -2
  828. package/src/renderer/src-web/components/ImageStudioPanel.tsx +1 -1
  829. package/src/renderer/src-web/components/MintDashboard.tsx +1 -1
  830. package/src/renderer/src-web/components/PullToRefresh.tsx +1 -1
  831. package/src/renderer/src-web/components/SettingsWindow.tsx +6 -846
  832. package/src/renderer/src-web/components/VeoStudioPanel.tsx +1 -1
  833. package/src/renderer/src-web/css/app-shell.css +40 -221
  834. package/src/renderer/src-web/css/chat.css +207 -494
  835. package/src/renderer/src-web/css/misc.css +0 -52
  836. package/src/renderer/src-web/css/mobile.css +15 -14
  837. package/src/renderer/src-web/css/sidebar-workspace.css +29 -95
  838. package/src/renderer/src-web/env.d.ts +15 -12
  839. package/src/renderer/src-web/index.css +16 -64
  840. package/src/renderer/src-web/main.tsx +2 -1
  841. package/src/renderer/src-web/proactiveSuggestions.ts +3 -0
  842. package/src/renderer/src-web/tauri.ts +194 -361
  843. package/src/renderer/tests/backgroundTerminals.browser.html +81 -0
  844. package/src/renderer/tests/browserArtifacts.browser.html +1 -0
  845. package/src/renderer/tests/browserArtifacts.browser.tsx +68 -0
  846. package/src/renderer/tests/chatEscape.browser.html +1 -0
  847. package/src/renderer/tests/chatEscape.browser.tsx +25 -0
  848. package/src/renderer/tests/chatPanelFixture.ts +23 -0
  849. package/src/renderer/tests/chatPerformance.browser.html +71 -0
  850. package/src/renderer/tests/cliSessions.browser.html +82 -0
  851. package/src/renderer/tests/collapsedPaste.browser.html +70 -0
  852. package/src/renderer/tests/composerMenus.browser.html +116 -0
  853. package/src/renderer/tests/contextCompaction.browser.html +17 -0
  854. package/src/renderer/tests/conversationLifecycle.browser.html +70 -0
  855. package/src/renderer/tests/effectsPerformance.browser.html +13 -0
  856. package/src/renderer/tests/errorRecovery.browser.html +21 -0
  857. package/src/renderer/tests/fixtures/preview.webm +0 -0
  858. package/src/renderer/tests/glideSelect.browser.html +70 -0
  859. package/src/renderer/tests/glideSelect.preview.html +27 -0
  860. package/src/renderer/tests/library.browser.html +1 -0
  861. package/src/renderer/tests/library.browser.tsx +78 -0
  862. package/src/renderer/tests/liveOrb.browser.html +49 -0
  863. package/src/renderer/tests/liveVoice.browser.html +29 -0
  864. package/src/renderer/tests/mediaFixtures.ts +32 -0
  865. package/src/renderer/tests/modInput.browser.html +22 -0
  866. package/src/renderer/tests/sessionSources.browser.html +1 -0
  867. package/src/renderer/tests/sessionSources.browser.tsx +168 -0
  868. package/src/renderer/tests/skills.browser.html +24 -0
  869. package/src/renderer/tests/toolsPanel.browser.html +1 -0
  870. package/src/renderer/tests/toolsPanel.browser.tsx +113 -0
  871. package/src/renderer/tests/viewedImages.browser.html +1 -0
  872. package/src/renderer/tests/viewedImages.browser.tsx +93 -0
  873. package/src/renderer/tests/voiceDictation.browser.html +101 -0
  874. package/src/renderer/tests/voicePill.browser.html +80 -0
  875. package/src/renderer/tests/voicePill.preview.html +34 -0
  876. package/src/renderer/tests/webSources.browser.html +1 -0
  877. package/src/renderer/tests/webSources.browser.tsx +2 -0
  878. package/src/renderer/tests/workspaceDrop.browser.html +1 -0
  879. package/src/renderer/tests/workspaceDrop.browser.tsx +183 -0
  880. package/src/renderer/tests/workspaceExpansion.browser.html +1 -0
  881. package/src/renderer/tests/workspaceExpansion.browser.tsx +66 -0
  882. package/src/renderer/tests/workspaceRemoval.browser.html +66 -0
  883. package/src/renderer/tests/workspaceUndo.browser.html +100 -0
  884. package/src-tauri/Cargo.toml +40 -0
  885. package/src-tauri/build.rs +3 -0
  886. package/src-tauri/src/app/lifecycle.rs +320 -0
  887. package/src-tauri/src/app/mod.rs +4 -0
  888. package/src-tauri/src/app/shortcuts.rs +40 -0
  889. package/src-tauri/src/app/tray.rs +52 -0
  890. package/src-tauri/src/background/events.rs +70 -0
  891. package/src-tauri/src/background/headless.rs +230 -0
  892. package/src-tauri/src/background/mod.rs +4 -0
  893. package/src-tauri/src/background/proactive.rs +262 -0
  894. package/src-tauri/src/commands/approvals.rs +20 -0
  895. package/src-tauri/src/commands/auth.rs +127 -0
  896. package/src-tauri/src/commands/conversation.rs +668 -0
  897. package/src-tauri/src/commands/integrations.rs +163 -0
  898. package/src-tauri/src/commands/media.rs +186 -0
  899. package/src-tauri/src/commands/mod.rs +11 -0
  900. package/src-tauri/src/commands/mods.rs +6 -0
  901. package/src-tauri/src/commands/settings.rs +141 -0
  902. package/src-tauri/src/commands/voice.rs +153 -0
  903. package/src-tauri/src/commands/workspace.rs +539 -0
  904. package/src-tauri/src/lib.rs +10 -0
  905. package/src-tauri/src/main.rs +3 -0
  906. package/src-tauri/src/native/actions.rs +206 -0
  907. package/src-tauri/src/native/browser.rs +252 -0
  908. package/src-tauri/src/native/capture.rs +695 -0
  909. package/src-tauri/src/native/discord_rpc.rs +99 -0
  910. package/src-tauri/src/native/integrations.rs +126 -0
  911. package/src-tauri/src/native/mod.rs +12 -0
  912. package/src-tauri/src/native/notifications.rs +51 -0
  913. package/src-tauri/src/native/plugins.rs +14 -0
  914. package/src-tauri/src/native/system.rs +250 -0
  915. package/src-tauri/src/native/terminal.rs +201 -0
  916. package/src-tauri/src/native/updater.rs +148 -0
  917. package/src-tauri/src/native/windows.rs +143 -0
  918. package/tsconfig.json +17 -1
  919. package/vite.config.ts +5 -1
  920. package/vite.config.web.ts +6 -0
  921. package/.cargo/config.toml +0 -13
  922. package/.claude/settings.local.json +0 -182
  923. package/.codex +0 -0
  924. package/CLAUDE.md +0 -102
  925. package/ROADMAP.md +0 -30
  926. package/mint-presentation-demo.mp4 +0 -0
  927. package/out/web/Live2DCubismCore.js +0 -9
  928. package/out/web/assets/CliSessionsView-BxUVWEEL.js +0 -1
  929. package/out/web/assets/CliSessionsView-CBDTXuQv.css +0 -1
  930. package/out/web/assets/CodeReviewPage-DkMgfcos.css +0 -1
  931. package/out/web/assets/CodeReviewPage-jKlmLJwT.js +0 -3
  932. package/out/web/assets/CommandPalette-DnwPA5hy.js +0 -1
  933. package/out/web/assets/ImageStudioPanel-BR1XFwhR.js +0 -1
  934. package/out/web/assets/LinkedFoldersView-bi-CWQ-I.js +0 -1
  935. package/out/web/assets/McpRegistryPicker-D5tEFnyq.js +0 -1
  936. package/out/web/assets/McpServersView-BGG1dhFM.js +0 -1
  937. package/out/web/assets/MintDashboard-BIglDEWX.js +0 -20
  938. package/out/web/assets/MintDashboard-DSMywSgs.css +0 -1
  939. package/out/web/assets/ModelPanel-CbJ1PFoZ.js +0 -1
  940. package/out/web/assets/PicturesLibrary-CQP0fLP1.css +0 -1
  941. package/out/web/assets/PicturesLibrary-DoYN7T1p.js +0 -1
  942. package/out/web/assets/PluginsView-CU1HWXGk.js +0 -1
  943. package/out/web/assets/ScheduledTasksView-Bnl36y2N.js +0 -1
  944. package/out/web/assets/SettingsWindow-DhWLAn9T.css +0 -1
  945. package/out/web/assets/SettingsWindow-Dw8L01pw.js +0 -2
  946. package/out/web/assets/SkillsView-cnl8aY5p.js +0 -2
  947. package/out/web/assets/ToolSurfacePage-CgsQw6l5.css +0 -1
  948. package/out/web/assets/ToolSurfacePage-Cz2VJAPZ.js +0 -37
  949. package/out/web/assets/VeoStudioPanel-D4JnERWR.js +0 -4
  950. package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +0 -1
  951. package/out/web/assets/abnfDiagram-VCTEODGH-ByMyfanq.js +0 -1
  952. package/out/web/assets/architecture-7GRP2DOG-CLq2kEOB.js +0 -1
  953. package/out/web/assets/architectureDiagram-5GKGNRK7-6PoVSmNG.js +0 -36
  954. package/out/web/assets/blockDiagram-NRAW4CY4-cKw9mAKi.js +0 -129
  955. package/out/web/assets/c4Diagram-UCG6FXSJ-Cqy8N5Ge.js +0 -38
  956. package/out/web/assets/chunk-2Q5K7J3B-Dmqrq8Tp.js +0 -1
  957. package/out/web/assets/chunk-5VM5RSS4-BiPuE0io.js +0 -15
  958. package/out/web/assets/chunk-75Z2AOVW-3qybijF5.js +0 -2
  959. package/out/web/assets/chunk-CKBBP62Z-cIrXq6jN.js +0 -2
  960. package/out/web/assets/chunk-CLGD4ZFX-CndDQkww.js +0 -1
  961. package/out/web/assets/chunk-CacB07dV.js +0 -1
  962. package/out/web/assets/chunk-DSNCTWBM-Cc4NxvgD.js +0 -2
  963. package/out/web/assets/chunk-DU6HZSFF-drzh6GsU.js +0 -128
  964. package/out/web/assets/chunk-F27PBJKO-FiY35l4v.js +0 -1
  965. package/out/web/assets/chunk-FOHPRMQF-BaVC1uA7.js +0 -161
  966. package/out/web/assets/chunk-G27WJ6UU-Bjt9Il4A.js +0 -231
  967. package/out/web/assets/chunk-GMAD6QVW-Bppi3Ctd.js +0 -72
  968. package/out/web/assets/chunk-JQ64N6SF-DGg5kcEV.js +0 -1
  969. package/out/web/assets/chunk-JWPE2WC7-BwtYOHqU.js +0 -1
  970. package/out/web/assets/chunk-LCL6LL3I-CCYcjd7p.js +0 -206
  971. package/out/web/assets/chunk-LNGE3PJU-AdBoOWw2.js +0 -34
  972. package/out/web/assets/chunk-OBVCFTLP-DJeNil5M.js +0 -1
  973. package/out/web/assets/chunk-P2QGCYS3-lRzkRjBB.js +0 -1
  974. package/out/web/assets/chunk-POPQ4Y6H-DouASMBW.js +0 -1
  975. package/out/web/assets/chunk-PWAF6VOD-DMmD1vVf.js +0 -1
  976. package/out/web/assets/chunk-R7TYR2AO-CYLA5UC5.js +0 -10
  977. package/out/web/assets/chunk-RHFEMEQ7-BiXwEcUT.js +0 -168
  978. package/out/web/assets/chunk-SVP7TREG-BV-zPslF.js +0 -88
  979. package/out/web/assets/chunk-TEH6E4GO-B2RGEohZ.js +0 -62
  980. package/out/web/assets/chunk-XXDRQBXY-DnJkbUaw.js +0 -1
  981. package/out/web/assets/chunk-Y2CYZVJY-C9o-s6OY.js +0 -1
  982. package/out/web/assets/classDiagram-DTDB5LWJ-Dq0smRWk.js +0 -1
  983. package/out/web/assets/classDiagram-v2-JRS7N3AN-Dq0smRWk.js +0 -1
  984. package/out/web/assets/config-C-ZFtipU.css +0 -1
  985. package/out/web/assets/config-eEDngJXO.js +0 -1
  986. package/out/web/assets/core-hQ2VA9Ti.js +0 -1
  987. package/out/web/assets/cose-bilkent-JH36ORCC--UN4aw5-.js +0 -1
  988. package/out/web/assets/cynefin-OW5HDTMX-Cas4csHM.js +0 -1
  989. package/out/web/assets/cynefinDiagram-5FMLGOSQ-Bbpj66Y3.js +0 -62
  990. package/out/web/assets/cytoscape.esm-Df1UN4Y1.js +0 -321
  991. package/out/web/assets/dagre-3AP2YEHR-B0-2RDEd.js +0 -4
  992. package/out/web/assets/dagre-BWadQu2F.js +0 -1
  993. package/out/web/assets/diagram-S7CK7UJ4-BdzEjUyH.js +0 -30
  994. package/out/web/assets/diagram-UQ7AKVKN-CJDwyPxF.js +0 -41
  995. package/out/web/assets/diagram-VSXAHHWV-BxaG1r_B.js +0 -3
  996. package/out/web/assets/diagram-VX7I27RA-BMmUqm8f.js +0 -24
  997. package/out/web/assets/diagram-Z3DM3KII-CFtd03eE.js +0 -24
  998. package/out/web/assets/dist-BoTOASLF.js +0 -1
  999. package/out/web/assets/dist-js-Bf2bhoku.js +0 -1
  1000. package/out/web/assets/ebnfDiagram-PWID7BFC-DwAen1hO.js +0 -1
  1001. package/out/web/assets/erDiagram-SSCWMZ5O-BCZSHACj.js +0 -99
  1002. package/out/web/assets/event-ChBVpRy0.js +0 -1
  1003. package/out/web/assets/eventmodeling-NTZA5JFV-XHStE4E_.js +0 -1
  1004. package/out/web/assets/flowDiagram-A5DVABFB-BLg7nEcd.js +0 -1
  1005. package/out/web/assets/ganttDiagram-EL5Y4UJY-BAEu1Cql.js +0 -292
  1006. package/out/web/assets/gitGraph-4MIJSDKK-r32Vyper.js +0 -1
  1007. package/out/web/assets/gitGraphDiagram-WWUBYQGX-z9o9qt6V.js +0 -106
  1008. package/out/web/assets/graphlib-CTR5ZhLf.js +0 -1
  1009. package/out/web/assets/index-Btsf4L8M.css +0 -1
  1010. package/out/web/assets/index-D8D9MeRA.js +0 -11
  1011. package/out/web/assets/info-A6RAGUB7-BvLaC2cw.js +0 -1
  1012. package/out/web/assets/infoDiagram-RXCK75RN-Be6WAXXw.js +0 -2
  1013. package/out/web/assets/ishikawaDiagram-5VMMS53U-DZjoHJHC.js +0 -70
  1014. package/out/web/assets/journeyDiagram-EYS64GPL-CtVkjkGm.js +0 -139
  1015. package/out/web/assets/kanban-definition-3QL26DDD-CW5FMv1w.js +0 -89
  1016. package/out/web/assets/katex-O3jvqgnU.js +0 -257
  1017. package/out/web/assets/lucide-react-Ip3L6Jtn.js +0 -1
  1018. package/out/web/assets/markdown-BSeeUbJH.js +0 -38
  1019. package/out/web/assets/mermaid-parser.core-aUgCqsZ5.js +0 -7
  1020. package/out/web/assets/mermaid.core-D57sEiSL.js +0 -11
  1021. package/out/web/assets/mindmap-definition-FBJOCRG2-DAmSpM3a.js +0 -96
  1022. package/out/web/assets/modelManager-tMVmJVAW.js +0 -2
  1023. package/out/web/assets/oauthManager-9jxicDWJ.js +0 -1
  1024. package/out/web/assets/packet-AYTQ26CC-DJ27iIoJ.js +0 -1
  1025. package/out/web/assets/pegDiagram-XKGWAZYB-Bj3ii-gS.js +0 -1
  1026. package/out/web/assets/pie-WAS4IAKB-DG5mCUbc.js +0 -1
  1027. package/out/web/assets/pieDiagram-E7YTZNPT-BrJLPnDA.js +0 -39
  1028. package/out/web/assets/prompt-latin-400-normal-BQ9zjSN8.woff2 +0 -0
  1029. package/out/web/assets/prompt-latin-400-normal-CxU4ec_r.woff +0 -0
  1030. package/out/web/assets/prompt-latin-500-normal-B1edBDQ9.woff +0 -0
  1031. package/out/web/assets/prompt-latin-500-normal-CxzxEHZc.woff2 +0 -0
  1032. package/out/web/assets/prompt-latin-600-normal-CSDDLorv.woff +0 -0
  1033. package/out/web/assets/prompt-latin-600-normal-hKZWXsc1.woff2 +0 -0
  1034. package/out/web/assets/prompt-latin-700-normal-BfbftDSq.woff +0 -0
  1035. package/out/web/assets/prompt-latin-700-normal-I2gc831J.woff2 +0 -0
  1036. package/out/web/assets/prompt-thai-400-normal-BrkKv8cO.woff2 +0 -0
  1037. package/out/web/assets/prompt-thai-400-normal-DJypnfr3.woff +0 -0
  1038. package/out/web/assets/prompt-thai-500-normal-C18pDUoL.woff2 +0 -0
  1039. package/out/web/assets/prompt-thai-500-normal-D7YNqUJD.woff +0 -0
  1040. package/out/web/assets/prompt-thai-600-normal-CYPViXds.woff +0 -0
  1041. package/out/web/assets/prompt-thai-600-normal-MrdfU7zR.woff2 +0 -0
  1042. package/out/web/assets/prompt-thai-700-normal-Cb5ilR-c.woff +0 -0
  1043. package/out/web/assets/prompt-thai-700-normal-Cg4aQ0Nn.woff2 +0 -0
  1044. package/out/web/assets/quadrantDiagram-AXDQQJYC-3x_TmF3j.js +0 -7
  1045. package/out/web/assets/radar-RG4KPBEZ-CrXaTTuV.js +0 -1
  1046. package/out/web/assets/railroad-74A4TZTK-BpqMH9BE.js +0 -1
  1047. package/out/web/assets/railroad-abnf-HS5TGJTU-DHI0uLlV.js +0 -1
  1048. package/out/web/assets/railroad-ebnf-LZEXJU2U-CQCb316y.js +0 -1
  1049. package/out/web/assets/railroad-peg-WCYAUIDC-Ph1giKkH.js +0 -1
  1050. package/out/web/assets/railroadDiagram-O6MQD6OU-BRq6wIxx.js +0 -1
  1051. package/out/web/assets/requirementDiagram-EFPCY7ZU-DIJj6G1g.js +0 -84
  1052. package/out/web/assets/rough.esm-DFbTUy8a.js +0 -1
  1053. package/out/web/assets/sankeyDiagram-P5KCCOFB-DZajSryq.js +0 -40
  1054. package/out/web/assets/sequenceDiagram-WJ2MYXX4-DxJCWyZS.js +0 -162
  1055. package/out/web/assets/sizeCapture-X5ZJPWSS-DKeOCaN2.js +0 -1
  1056. package/out/web/assets/src-DnfeXQPr.js +0 -1
  1057. package/out/web/assets/stateDiagram-HBIQ2CUA-k6voi8Vf.js +0 -1
  1058. package/out/web/assets/stateDiagram-v2-4QOOHH4V-DeEc0lGZ.js +0 -1
  1059. package/out/web/assets/studio-B1mk40wD.js +0 -1
  1060. package/out/web/assets/swimlanes-XN3QIQJK-igYhTjXB.js +0 -1
  1061. package/out/web/assets/swimlanesDiagram-VK2B7HYN-DpVik9v7.js +0 -8
  1062. package/out/web/assets/timeline-definition-24CTP7MA-Dke2p93p.js +0 -120
  1063. package/out/web/assets/treeView-Q6P3EWNA-hNELnXUs.js +0 -1
  1064. package/out/web/assets/treemap-WGGIJYW6-D2B6wDsx.js +0 -1
  1065. package/out/web/assets/vennDiagram-4TSXK5OY-CDkW7rUP.js +0 -34
  1066. package/out/web/assets/wardley-WFR3VGLG-BECdXETi.js +0 -1
  1067. package/out/web/assets/wardleyDiagram-VM6X3IG4-B5BycpQ4.js +0 -78
  1068. package/out/web/assets/window-5qYXKoW7.js +0 -1
  1069. package/out/web/assets/xychartDiagram-S5SC5T6Z-CtJS3b--.js +0 -7
  1070. package/skills-lock.json +0 -77
  1071. package/src/renderer/public/Live2DCubismCore.js +0 -9
  1072. package/src/renderer/shared/components/AgentActivityDrawer.tsx +0 -203
  1073. package/src/renderer/shared/components/AgentActivityTable.tsx +0 -301
  1074. package/src/renderer/shared/components/ApprovalCard.tsx +0 -340
  1075. package/src/renderer/shared/components/ArtifactPreviewPanel.tsx +0 -505
  1076. package/src/renderer/shared/components/AuthGate.tsx +0 -191
  1077. package/src/renderer/shared/components/CalculationCard.tsx +0 -100
  1078. package/src/renderer/shared/components/ChatCodeBlock.tsx +0 -105
  1079. package/src/renderer/shared/components/ChatMessageItem.tsx +0 -306
  1080. package/src/renderer/shared/components/ChatPanel.tsx +0 -2226
  1081. package/src/renderer/shared/components/ChunkErrorBoundary.tsx +0 -284
  1082. package/src/renderer/shared/components/CliSessionsView.tsx +0 -495
  1083. package/src/renderer/shared/components/CodeReviewPage.tsx +0 -565
  1084. package/src/renderer/shared/components/CommandPalette.tsx +0 -593
  1085. package/src/renderer/shared/components/DashboardSidebar.tsx +0 -1341
  1086. package/src/renderer/shared/components/DesktopTitlebar.tsx +0 -345
  1087. package/src/renderer/shared/components/DiffReviewModal.tsx +0 -565
  1088. package/src/renderer/shared/components/GeminiLiveOverlay.tsx +0 -128
  1089. package/src/renderer/shared/components/GitBranchSelector.tsx +0 -442
  1090. package/src/renderer/shared/components/ImageGenCard.tsx +0 -159
  1091. package/src/renderer/shared/components/ImageSearchCard.tsx +0 -168
  1092. package/src/renderer/shared/components/ImageStudioPanel.tsx +0 -739
  1093. package/src/renderer/shared/components/InlineThoughtNote.tsx +0 -27
  1094. package/src/renderer/shared/components/LinkedFoldersView.tsx +0 -614
  1095. package/src/renderer/shared/components/McpRegistryPicker.tsx +0 -302
  1096. package/src/renderer/shared/components/McpServersView.tsx +0 -952
  1097. package/src/renderer/shared/components/McpToolAllowlist.tsx +0 -184
  1098. package/src/renderer/shared/components/MermaidCard.tsx +0 -267
  1099. package/src/renderer/shared/components/MintDashboard.tsx +0 -2665
  1100. package/src/renderer/shared/components/ModalErrorBoundary.tsx +0 -127
  1101. package/src/renderer/shared/components/ModelSelectorPopover.tsx +0 -929
  1102. package/src/renderer/shared/components/PlanChecklistWidget.tsx +0 -77
  1103. package/src/renderer/shared/components/PluginsView.tsx +0 -325
  1104. package/src/renderer/shared/components/RewindModal.tsx +0 -421
  1105. package/src/renderer/shared/components/RunSummaryDashboard.tsx +0 -149
  1106. package/src/renderer/shared/components/ScheduledTasksView.tsx +0 -613
  1107. package/src/renderer/shared/components/SkillsView.tsx +0 -347
  1108. package/src/renderer/shared/components/SlashSuggestions.tsx +0 -318
  1109. package/src/renderer/shared/components/SourcesBlock.tsx +0 -450
  1110. package/src/renderer/shared/components/StockCard.tsx +0 -168
  1111. package/src/renderer/shared/components/SubagentDagView.tsx +0 -366
  1112. package/src/renderer/shared/components/TerminalDock.tsx +0 -241
  1113. package/src/renderer/shared/components/ThinkingBlock.tsx +0 -102
  1114. package/src/renderer/shared/components/ToolSurfacePage.tsx +0 -520
  1115. package/src/renderer/shared/components/UiFeatureCard.tsx +0 -267
  1116. package/src/renderer/shared/components/UiGridCard.tsx +0 -188
  1117. package/src/renderer/shared/components/VeoStudioPanel.tsx +0 -707
  1118. package/src/renderer/shared/components/WeatherCard.tsx +0 -224
  1119. package/src/renderer/shared/components/WorkspaceContextPopover.tsx +0 -133
  1120. package/src/renderer/shared/components/WorkspaceSelector.tsx +0 -149
  1121. package/src/renderer/src/companionWidget.ts +0 -95
  1122. package/src/renderer/src/components/CalculationCard.tsx +0 -3
  1123. package/src/renderer/src/components/ImageGenCard.tsx +0 -2
  1124. package/src/renderer/src/components/ImageSearchCard.tsx +0 -2
  1125. package/src/renderer/src/components/Live2DStage.tsx +0 -412
  1126. package/src/renderer/src/components/MermaidCard.tsx +0 -1
  1127. package/src/renderer/src/components/ModelPanel.tsx +0 -155
  1128. package/src/renderer/src/components/PicturesLibrary.tsx +0 -288
  1129. package/src/renderer/src/components/Settings/AgentsTab.tsx +0 -4
  1130. package/src/renderer/src/components/Settings/AudioTab.tsx +0 -3
  1131. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -3
  1132. package/src/renderer/src/components/Settings/GeneralTab.tsx +0 -3
  1133. package/src/renderer/src/components/Settings/MemoryTab.tsx +0 -3
  1134. package/src/renderer/src/components/Settings/PluginsTab.tsx +0 -3
  1135. package/src/renderer/src/components/Settings/ProfileTab.tsx +0 -3
  1136. package/src/renderer/src/components/Settings/ShortcutsTab.tsx +0 -3
  1137. package/src/renderer/src/components/Settings/ThemeTab.tsx +0 -3
  1138. package/src/renderer/src/components/StockCard.tsx +0 -3
  1139. package/src/renderer/src/components/WeatherCard.tsx +0 -3
  1140. package/src/renderer/src/css/model.css +0 -628
  1141. package/src/renderer/src/css/pictures.css +0 -415
  1142. package/src/renderer/src/css/settings/base.css +0 -1263
  1143. package/src/renderer/src-web/companionWidget.ts +0 -37
  1144. package/src/renderer/src-web/components/ModelPanel.tsx +0 -12
  1145. package/src/renderer/src-web/components/PicturesLibrary.tsx +0 -285
  1146. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +0 -4
  1147. package/src/renderer/src-web/components/Settings/AudioTab.tsx +0 -3
  1148. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -3
  1149. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +0 -3
  1150. package/src/renderer/src-web/components/Settings/MemoryTab.tsx +0 -3
  1151. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +0 -3
  1152. package/src/renderer/src-web/components/Settings/ProfileTab.tsx +0 -3
  1153. package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +0 -3
  1154. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +0 -3
  1155. package/src/renderer/src-web/css/model.css +0 -628
  1156. package/src/renderer/src-web/css/pictures.css +0 -462
  1157. package/src/renderer/src-web/css/settings/base.css +0 -1263
  1158. package/src/renderer/src-web/css/spotlight.css +0 -102
  1159. package/src/renderer/src-web/css/widget.css +0 -213
  1160. /package/src/renderer/shared/components/{UiMockupWidget.tsx → cards/UiMockupWidget.tsx} +0 -0
  1161. /package/src/renderer/shared/components/{ScreenCaptureDialog.css → studio/ScreenCaptureDialog.css} +0 -0
  1162. /package/src/renderer/shared/components/{ScreenCaptureDialog.tsx → studio/ScreenCaptureDialog.tsx} +0 -0
@@ -1,2665 +0,0 @@
1
- import { lazy, Suspense, type ChangeEvent, type CSSProperties, type FormEvent, useCallback, useEffect, useRef, useState } from 'react'
2
- import {
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'
28
- import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
29
- import DesktopTitlebar from './DesktopTitlebar'
30
- import type { ModelInteraction } from '@/components/ModelPanel'
31
- import type { ToolSurface } from './ToolSurfacePage'
32
- import {
33
- errorMessage,
34
- readImage,
35
- readDocument,
36
- createTrimmedImagePreview,
37
- createObjectUrlPreview,
38
- applyThemeStyles,
39
- parseUtcDate,
40
- } from '../utils/ui'
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'
45
-
46
-
47
- const EXPRESSIONS = [
48
- "Default",
49
- "Dumb Cat",
50
- "Dumb Cat Eye Roll",
51
- "Take Photo",
52
- "Poke",
53
- "Cat Filter",
54
- ]
55
-
56
- const ACCESSORIES = [
57
- "None",
58
- "Apron",
59
- "Glasses",
60
- "Hold Pen",
61
- ]
62
-
63
- import { DEFAULT_CONFIG, migrateTypographyScale } from '../constants/config'
64
-
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
68
- const ACTIVE_CONVERSATION_ID_KEY = 'mint:active-conversation-id'
69
- const LAST_ACTIVE_TIME_KEY = 'mint:last-active-timestamp'
70
- const DESKTOP_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 // 30 minutes
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
-
84
- function touchActiveTimestamp() {
85
- if (typeof window === 'undefined') return
86
- try {
87
- window.localStorage.setItem(LAST_ACTIVE_TIME_KEY, Date.now().toString())
88
- } catch {
89
- /* ignore */
90
- }
91
- }
92
-
93
- function isDesktopRecentlyActive(): boolean {
94
- if (typeof window === 'undefined') return false
95
- if (!isTauriRuntime()) return false
96
- const lastActiveStr = window.localStorage.getItem(LAST_ACTIVE_TIME_KEY)
97
- if (!lastActiveStr) return false
98
- const lastActive = parseInt(lastActiveStr, 10)
99
- if (isNaN(lastActive) || lastActive <= 0) return false
100
-
101
- const now = Date.now()
102
- const diff = now - lastActive
103
- if (diff < 0 || diff > DESKTOP_INACTIVITY_THRESHOLD_MS) {
104
- return false
105
- }
106
-
107
- // Also verify same calendar day
108
- const lastDate = new Date(lastActive).toDateString()
109
- const today = new Date(now).toDateString()
110
- return lastDate === today
111
- }
112
-
113
- // Unsent composer text, stashed per conversation so switching chats or
114
- // reloading the page doesn't lose an in-progress message.
115
- const DRAFT_KEY_PREFIX = 'mint:draft:'
116
- function readDraft(id: string | null | undefined): string {
117
- if (!id) return ''
118
- try {
119
- return window.localStorage.getItem(`${DRAFT_KEY_PREFIX}${id}`) || ''
120
- } catch {
121
- return ''
122
- }
123
- }
124
- const SIDEBAR_DEFAULT_WIDTH = 264
125
- const SIDEBAR_MIN_WIDTH = 200
126
- const SIDEBAR_MAX_WIDTH = 420
127
- /** Drag the sidebar edge past this width and it snaps fully closed on release. */
128
- const SIDEBAR_COLLAPSE_THRESHOLD = 170
129
-
130
- function createConversationId() {
131
- const random = Math.random().toString(36).slice(2, 10)
132
- return `conversation-${Date.now().toString(36)}-${random}`
133
- }
134
-
135
- function getConversationIdFromUrl(): string | null {
136
- if (typeof window === 'undefined') return null
137
- const pathname = window.location.pathname || ''
138
- const hash = (window.location.hash || '').replace(/^#/, '')
139
- const target = pathname || hash
140
-
141
- const match = target.match(/^\/chat\/(.+)$/i) || target.match(/^\/c\/(.+)$/i)
142
- if (match && match[1]) {
143
- return decodeURIComponent(match[1])
144
- }
145
-
146
- const searchParams = new URLSearchParams(window.location.search)
147
- const queryId = searchParams.get('id')
148
- if (queryId) return queryId
149
-
150
- return null
151
- }
152
-
153
- function isRootOrNewChatRoute(): boolean {
154
- if (typeof window === 'undefined') return true
155
- const pathname = (window.location.pathname || '').replace(/\/+$/, '')
156
- const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
157
- const target = (pathname || hash).toLowerCase()
158
-
159
- // Explicit conversation ID specified in URL
160
- if (getConversationIdFromUrl()) {
161
- return false
162
- }
163
-
164
- // Explicit other panel view (skills, mcp, plugins, etc.)
165
- if (
166
- target.includes('skills') ||
167
- target.includes('mcp') ||
168
- target.includes('plugins') ||
169
- target.includes('picture') ||
170
- target.includes('image-studio') ||
171
- target.includes('imagine') ||
172
- target.includes('veo-studio') ||
173
- target.includes('veo') ||
174
- target.includes('code') ||
175
- target === '/cli' ||
176
- target === 'cli' ||
177
- target.includes('cli-sessions') ||
178
- target.includes('settings')
179
- ) {
180
- return false
181
- }
182
-
183
- // Root or generic chat route without specific conversation ID
184
- return !target || target === '' || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html'
185
- }
186
-
187
- function activeConversationId() {
188
- const fromUrl = getConversationIdFromUrl()
189
- if (fromUrl) {
190
- window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, fromUrl)
191
- touchActiveTimestamp()
192
- return fromUrl
193
- }
194
-
195
- // Desktop Smart Inactivity:
196
- // On Desktop App (Tauri), if the user was active recently (within 30 mins and on the same calendar day),
197
- // resume the previous conversation so a quick window close/reopen or system restart doesn't drop context.
198
- if (isTauriRuntime() && isDesktopRecentlyActive()) {
199
- const existing = window.localStorage.getItem(ACTIVE_CONVERSATION_ID_KEY)
200
- if (existing) {
201
- touchActiveTimestamp()
202
- return existing === 'conversation-default' ? 'cli' : existing
203
- }
204
- }
205
-
206
- // When visiting Root URL ('/' or '/chat' without specific ID), always start a fresh New chat
207
- // (matching ChatGPT / Claude / Gemini industry-standard UX).
208
- // Applies to Web UI always, and Desktop App upon cold launch / inactivity / new day.
209
- if (isRootOrNewChatRoute()) {
210
- const next = createConversationId()
211
- touchActiveTimestamp()
212
- return next
213
- }
214
-
215
- const existing = window.localStorage.getItem(ACTIVE_CONVERSATION_ID_KEY)
216
- if (existing === 'conversation-default') {
217
- window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, 'cli')
218
- touchActiveTimestamp()
219
- return 'cli'
220
- }
221
- if (existing) {
222
- touchActiveTimestamp()
223
- return existing
224
- }
225
- const next = createConversationId()
226
- touchActiveTimestamp()
227
- return next
228
- }
229
-
230
- const MOCK_WELCOME_INTERACTION = {
231
- id: -1,
232
- userText: '',
233
- aiText: `Hi there! I'm Mint, your AI assistant! 🎯✨ I'm here and ready to help you with whatever you need. I've been organizing some background data to make things smoother for you. 💖\n\nBut the moment you start chatting with me, I'll put everything aside and focus on you! Is there something I can help you with today, or would you like to chat about something special? Let's do this! 🚀💪`,
234
- provider: 'gemini',
235
- model: 'gemini-3-flash-preview',
236
- createdAt: new Date().toISOString(),
237
- }
238
-
239
-
240
- import { isSupportedDocument } from '../utils/documentTypes'
241
- import { useCompanionWidget } from '@/companionWidget'
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
- }
260
-
261
- function getInitialViewFromUrl(): DashboardView {
262
- if (typeof window === 'undefined') return 'chat'
263
- const hash = (window.location.hash || '').toLowerCase().replace(/^#/, '')
264
- const pathname = (window.location.pathname || '').toLowerCase()
265
- const target = hash || pathname
266
-
267
- if (target.includes('skills')) return 'skills'
268
- if (target.includes('mcp')) return 'mcp'
269
- if (target.includes('plugins')) return 'plugins'
270
- if (target.includes('picture')) return 'pictures'
271
- if (target.includes('image-studio') || target.includes('imagine')) return 'imagine'
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'
274
- return 'chat'
275
- }
276
-
277
- function getCleanPathForView(v: string, activeId?: string): string {
278
- if (v === 'skills') return '/skills'
279
- if (v === 'mcp') return '/mcp'
280
- if (v === 'plugins') return '/plugins'
281
- if (v === 'pictures') return '/pictures'
282
- if (v === 'imagine') return '/image-studio'
283
- if (v === 'veo' || v === 'veo_studio') return '/veo-studio'
284
- if (v === 'code') return '/code'
285
- if (v === 'settings') return '/settings'
286
- if (activeId) return `/chat/${encodeURIComponent(activeId)}`
287
- return '/chat'
288
- }
289
-
290
- /** Keeps a desktop/browser notification body to one readable line. */
291
- function truncateForNotification(text: string, maxChars = 120): string {
292
- const clean = text.replace(/\s+/g, ' ').trim()
293
- return clean.length > maxChars ? `${clean.slice(0, maxChars)}…` : clean
294
- }
295
-
296
- /** One-line plain-text summary of an AgentApproval for a notification body
297
- * — ApprovalCard's own renderApprovalDetails returns JSX, not text, so
298
- * this is a separate, notification-only summary. AgentApproval serializes
299
- * as a single-key-tagged object per variant, e.g. {"WriteFile": {path, ...}}. */
300
- function describeApproval(payload: any): string {
301
- const entry = Object.entries(payload?.approval ?? {})[0]
302
- const kind = entry?.[0]
303
- const detail: any = entry?.[1]
304
- switch (kind) {
305
- case 'WriteFile': return `Write to ${detail?.path}?`
306
- case 'ApplyPatch': return `Edit ${detail?.path}?`
307
- case 'RunShell': return `Run: ${detail?.command}`
308
- case 'NoteWrite': return `Save note to ${detail?.path}?`
309
- case 'RunPlugin': return `Run plugin: ${detail?.name}?`
310
- case 'McpTool': return `Use ${detail?.tool} on ${detail?.server}?`
311
- case 'UserApproval': return detail?.title ?? 'Approval needed'
312
- case 'AskUser': return detail?.question ?? 'Mint has a question'
313
- case 'ExitPlanMode':
314
- case 'EnterPlanMode':
315
- return 'Plan mode change needs approval'
316
- default: return 'Mint needs your approval to continue'
317
- }
318
- }
319
-
320
- /** Fires the same notification mechanism as a finished AI reply, but only
321
- * while the window is unfocused — reused so both surfaces get identical
322
- * title/permission/dedupe behavior, just a different body string. */
323
- function notifyPendingApproval(payload: any) {
324
- if (document.hidden || !document.hasFocus()) {
325
- window.api?.notifyAiResponse?.(truncateForNotification(describeApproval(payload)))
326
- }
327
- }
328
-
329
- export default function MintDashboard() {
330
- const isDesktopApp = isTauriRuntime()
331
- const [view, setViewState] = useState<DashboardView>(getInitialViewFromUrl)
332
- const [conversationId, setConversationId] = useState(activeConversationId)
333
- // Declared here (not further down with the rest of the workspace-related
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
- }
402
- // Web only in practice (desktop's window has no mobile-width breakpoint,
403
- // so nothing ever sets this true there) — declared unconditionally so
404
- // `changeView` can close it on every navigation without branching.
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])
431
-
432
- const changeView = (newView: any, targetConversationId?: string) => {
433
- setMobileSidebarOpen(false)
434
- setViewState((prev) => {
435
- const next = typeof newView === 'function' ? newView(prev) : newView
436
- if (next === 'settings') {
437
- if ((window as any).api?.openSettings) {
438
- (window as any).api.openSettings()
439
- } else {
440
- if (window.location.pathname !== '/settings') {
441
- window.history.pushState({}, '', '/settings')
442
- }
443
- }
444
- return prev
445
- }
446
- const mappedView: DashboardView = (next === 'veo_studio' ? 'veo' : next) as DashboardView
447
- const targetPath = getCleanPathForView(mappedView, targetConversationId || conversationId)
448
- if (typeof window !== 'undefined' && window.location.pathname !== targetPath) {
449
- window.history.pushState({}, '', targetPath)
450
- }
451
- return mappedView
452
- })
453
- }
454
-
455
- useEffect(() => {
456
- const handleUrlChange = () => {
457
- if (window.location.hash === '#' || window.location.hash === '#/') {
458
- window.history.replaceState({}, '', window.location.pathname + window.location.search)
459
- }
460
- const pathname = (window.location.pathname || '').toLowerCase()
461
- const hash = (window.location.hash || '').toLowerCase()
462
- if (pathname.includes('/settings') || hash.includes('/settings')) return
463
- const nextView = getInitialViewFromUrl()
464
- setViewState(nextView)
465
-
466
- const urlSessionId = getConversationIdFromUrl()
467
- if (urlSessionId && urlSessionId !== conversationId) {
468
- window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, urlSessionId)
469
- activateConversation(urlSessionId)
470
- const generation = sessionGenerationRef.current
471
- loadConversationHistory(urlSessionId, generation).catch((error) => {
472
- console.error('Failed to load conversation history:', error)
473
- })
474
- } else if (!urlSessionId && isRootOrNewChatRoute()) {
475
- const next = createConversationId()
476
- activateConversation(next)
477
- setInteractions([])
478
- setAgentActivitySnapshots({})
479
- conversationActions.switchSession()
480
- }
481
- }
482
- window.addEventListener('popstate', handleUrlChange)
483
- window.addEventListener('hashchange', handleUrlChange)
484
- return () => {
485
- window.removeEventListener('popstate', handleUrlChange)
486
- window.removeEventListener('hashchange', handleUrlChange)
487
- }
488
- }, [conversationId, workspacePath])
489
-
490
- useEffect(() => {
491
- touchActiveTimestamp()
492
- const handleActivity = () => touchActiveTimestamp()
493
- window.addEventListener('beforeunload', handleActivity)
494
- document.addEventListener('visibilitychange', handleActivity)
495
- window.addEventListener('focus', handleActivity)
496
- return () => {
497
- window.removeEventListener('beforeunload', handleActivity)
498
- document.removeEventListener('visibilitychange', handleActivity)
499
- window.removeEventListener('focus', handleActivity)
500
- }
501
- }, [])
502
- const [status, setStatus] = useState<RuntimeStatus | null>(null)
503
- const [error, setError] = useState('')
504
- // When `conversationId` changes we swap `message` to that chat's saved draft;
505
- // this ref tells the persist effect to skip the render right after that swap,
506
- // so the outgoing chat's text is never written under the incoming chat's key.
507
- const [interactions, setInteractions] = useState<any[]>([])
508
- const [hasOlderInteractions, setHasOlderInteractions] = useState(false)
509
- const [pictures, setPictures] = useState<PictureEntry[]>([])
510
- const [streamingConversationId, setStreamingConversationId] = useState<string | null>(null)
511
- const [activeTurnId, setActiveTurnId] = useState<number | null>(null)
512
- const [agentActivitySnapshots, setAgentActivitySnapshots] = useState<Record<string, AgentProgress[]>>({})
513
- const [thinkingExpanded, setThinkingExpanded] = useState<Record<string, boolean>>({})
514
- const liveThinkingOpenRef = useRef(true)
515
- const liveExtendedThinkingOpenRef = useRef(true)
516
- const [sessionAutoApproved, setSessionAutoApproved] = useState(false)
517
- const sessionAutoApprovedRef = useRef(false)
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)
527
- const [sidebarWidth, setSidebarWidth] = useState(() => {
528
- const saved = Number(window.localStorage.getItem('mint:sidebar-width'))
529
- return saved >= SIDEBAR_MIN_WIDTH && saved <= SIDEBAR_MAX_WIDTH ? saved : SIDEBAR_DEFAULT_WIDTH
530
- })
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
-
545
- const [agentMode, setAgentMode] = useState(() => window.localStorage.getItem('mint:agent-mode') === 'true')
546
- const [planMode, setPlanMode] = useState(() => window.localStorage.getItem('mint:plan-mode') === 'true')
547
- const [toastMessage, setToastMessage] = useState('')
548
- const [dashboardDataReady, setDashboardDataReady] = useState(false)
549
- const [startupTimedOut, setStartupTimedOut] = useState(false)
550
- const [settingsConfig, setSettingsConfig] = useState<any>(null)
551
- const [chatSessions, setChatSessions] = useState<ChatSession[]>([])
552
- const chatEnd = useRef<HTMLDivElement | null>(null)
553
- const lastNativePasteTimeRef = useRef(0)
554
- const {
555
- modelVisible,
556
- scale,
557
- interactionEnabled,
558
- showInteractionGuide,
559
- isLocked,
560
- layoutPreset,
561
- expressionIndex,
562
- accessoryIndex,
563
- modelReady,
564
- proactiveSuggestion,
565
- setScale,
566
- setIsLocked,
567
- setExpressionIndex,
568
- setAccessoryIndex,
569
- setModelReady,
570
- toggleModel,
571
- changeLayoutPreset,
572
- updateInteractionEnabled,
573
- updateInteractionGuide,
574
- dismissProactiveSuggestion,
575
- handleProactiveAction,
576
- } = useCompanionWidget((message) => setError(message))
577
- const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
578
-
579
- const [mcpName, setMcpName] = useState('')
580
- const [mcpCmd, setMcpCmd] = useState('')
581
- const [mcpArgs, setMcpArgs] = useState('')
582
- const [mcpEnv, setMcpEnv] = useState('')
583
- const [mcpIcon, setMcpIcon] = useState('')
584
-
585
- const handleUpdateSettingsField = async (field: string, value: any) => {
586
- const currentConfig = settingsConfig || DEFAULT_CONFIG
587
- const updatedConfig = { ...currentConfig, [field]: value }
588
- setSettingsConfig(updatedConfig)
589
-
590
- if ((window as any).settingsApi) {
591
- await (window as any).settingsApi.saveSettings(updatedConfig)
592
- } else {
593
- try {
594
- await setProfileValue('user-settings', JSON.stringify(updatedConfig))
595
- } catch (e) {
596
- console.error('Failed to save settings field:', e)
597
- }
598
- }
599
- }
600
-
601
- const handleAddMcpServer = async (allowAll?: boolean) => {
602
- if (!mcpName.trim() || !mcpCmd.trim()) {
603
- alert('Please provide at least a server name and command.')
604
- return
605
- }
606
-
607
- let parsedEnv = {}
608
- if (mcpEnv.trim()) {
609
- try {
610
- parsedEnv = JSON.parse(mcpEnv)
611
- } catch {
612
- alert('Invalid JSON in Environment variable field.')
613
- return
614
- }
615
- }
616
-
617
- const name = mcpName.trim()
618
- const argList = mcpArgs.split(/\s+/).filter(Boolean)
619
- const currentConfig = settingsConfig || DEFAULT_CONFIG
620
- const updated: any = {
621
- ...currentConfig,
622
- mcpServers: {
623
- ...currentConfig?.mcpServers,
624
- [name]: {
625
- command: mcpCmd.trim(),
626
- args: argList,
627
- env: parsedEnv,
628
- icon: mcpIcon.trim() || undefined,
629
- },
630
- },
631
- }
632
- if (allowAll) {
633
- updated.allowedMcpTools = { ...(currentConfig?.allowedMcpTools || {}), [name]: ['*'] }
634
- }
635
-
636
- setSettingsConfig(updated)
637
- if ((window as any).settingsApi) {
638
- await (window as any).settingsApi.saveSettings(updated)
639
- } else {
640
- try {
641
- await setProfileValue('user-settings', JSON.stringify(updated))
642
- } catch (e) {
643
- console.error('Failed to save settings field:', e)
644
- }
645
- }
646
- setMcpName('')
647
- setMcpCmd('')
648
- setMcpArgs('')
649
- setMcpEnv('')
650
- setMcpIcon('')
651
- }
652
-
653
- const handleRemoveMcpServer = async (name: string) => {
654
- const currentConfig = settingsConfig || DEFAULT_CONFIG
655
- const updated = { ...(currentConfig?.mcpServers || {}) }
656
- delete updated[name]
657
- await handleUpdateSettingsField('mcpServers', updated)
658
- }
659
-
660
- const handleConnectPlugin = (_plugin: string) => {}
661
-
662
- const [isSearchOpen, setIsSearchOpen] = useState(false)
663
- // A pending `needs_choice` from the shared slash engine — rendered as a chip bar.
664
- const [pendingSlashChoice, setPendingSlashChoice] = useState<
665
- { command: string; title: string; options: { label: string; value: string }[] } | null
666
- >(null)
667
- const [searchQuery, setSearchQuery] = useState('')
668
-
669
- useEffect(() => {
670
- function handleKeyDown(event: KeyboardEvent) {
671
- if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
672
- event.preventDefault()
673
- setIsSearchOpen((prev) => !prev)
674
- } else if (event.key === 'Escape') {
675
- setIsSearchOpen(false)
676
- }
677
- }
678
- window.addEventListener('keydown', handleKeyDown)
679
- return () => window.removeEventListener('keydown', handleKeyDown)
680
- }, [])
681
-
682
- useEffect(() => {
683
- if (!isSearchOpen) {
684
- setSearchQuery('')
685
- }
686
- }, [isSearchOpen])
687
-
688
- // Another process — the CLI's interactive chat, a scheduled task, a
689
- // messaging bridge — can add messages to this same conversation while
690
- // this page is open, and nothing here would otherwise notice: Mint's
691
- // surfaces only share a SQLite file, not a live event bus, so this polls
692
- // for changes instead of requiring a manual refresh. Kept in a ref rather
693
- // than the effect's own dependency array so a poll landing doesn't tear
694
- // down and recreate the interval on every tick.
695
- const interactionsRef = useRef(interactions)
696
- const chatPollInFlightRef = useRef<{ chatId: string; generation: number } | null>(null)
697
- useEffect(() => {
698
- interactionsRef.current = interactions
699
- }, [interactions])
700
- useEffect(() => {
701
- if (interactions.length === 0) setHasOlderInteractions(false)
702
- }, [interactions.length])
703
-
704
- // Load the saved draft whenever the active conversation changes. Declared
705
- // before the persist effect so it runs first within the same commit. The
706
- // first run is a no-op: `message`'s initial value already came from the
707
- // lazy `useState` initializer above.
708
- useEffect(() => {
709
- if (view !== 'chat' || !conversationId) 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
726
- try {
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
- }
779
- } catch {
780
- // Best-effort — a transient fetch failure just waits for the next tick.
781
- } finally {
782
- if (chatPollInFlightRef.current === poll) chatPollInFlightRef.current = null
783
- }
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])
799
-
800
- const filteredSessions = chatSessions.filter((session) => {
801
- if (session.kind === 'cli' || session.id === 'conversation-default') return false
802
- return session.title.toLowerCase().includes(searchQuery.toLowerCase())
803
- })
804
-
805
- const groupSessionsByDate = (sessions: ChatSession[]) => {
806
- const groups: { [key: string]: ChatSession[] } = {}
807
-
808
- sessions.forEach((session) => {
809
- const dateStr = session.updatedAt || session.createdAt
810
- if (!dateStr) {
811
- const groupName = 'Other'
812
- if (!groups[groupName]) groups[groupName] = []
813
- groups[groupName].push(session)
814
- return
815
- }
816
-
817
- const date = parseUtcDate(dateStr)
818
- const today = new Date()
819
- const yesterday = new Date()
820
- yesterday.setDate(today.getDate() - 1)
821
-
822
- let groupName = ''
823
- if (date.toDateString() === today.toDateString()) {
824
- groupName = 'Today'
825
- } else if (date.toDateString() === yesterday.toDateString()) {
826
- groupName = 'Yesterday'
827
- } else {
828
- const diffTime = Math.abs(today.getTime() - date.getTime())
829
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
830
- if (diffDays <= 7) {
831
- groupName = 'Previous 7 days'
832
- } else if (diffDays <= 30) {
833
- groupName = 'Previous 30 days'
834
- } else {
835
- groupName = date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
836
- }
837
- }
838
-
839
- if (!groups[groupName]) {
840
- groups[groupName] = []
841
- }
842
- groups[groupName].push(session)
843
- })
844
-
845
- return groups
846
- }
847
-
848
- const groupedSearchSessions = groupSessionsByDate(filteredSessions)
849
-
850
-
851
- async function refreshHistory() {
852
- const chatId = conversationId
853
- const generation = sessionGenerationRef.current
854
- await loadConversationHistory(chatId, generation)
855
- }
856
-
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
- }, [])
927
-
928
- const [picturesRefreshing, setPicturesRefreshing] = useState(false)
929
-
930
- async function refreshPictures() {
931
- setPicturesRefreshing(true)
932
- try {
933
- setPictures(await listSavedPictures())
934
- } finally {
935
- setPicturesRefreshing(false)
936
- }
937
- }
938
-
939
- useEffect(() => {
940
- Promise.allSettled([
941
- getRuntimeStatus().then(setStatus),
942
- refreshHistory(),
943
- refreshChatSessions(),
944
- window.settingsApi?.getSettings()
945
- .then((loaded: any) => {
946
- const migrated = migrateTypographyScale(loaded)
947
- setSettingsConfig(migrated)
948
- applyThemeStyles({ ...DEFAULT_CONFIG, ...migrated })
949
- }),
950
- ]).then((results) => {
951
- const failure = results.find((result) => result.status === 'rejected')
952
- if (failure?.status === 'rejected') setError(errorMessage(failure.reason))
953
- setDashboardDataReady(true)
954
- })
955
- const unlistenSpotlight = window.api.onSpotlightToChat((query) => {
956
- changeView('chat')
957
- conversationActions.compose(query)
958
- })
959
- const unlistenVision = window.api.onVisionReady((image) => {
960
- createTrimmedImagePreview(image)
961
- .catch(() => image)
962
- .then((previewDataUri) => {
963
- conversationActions.attachImage({ dataUri: image, previewDataUri, name: 'Screen capture' })
964
- })
965
- })
966
- const handleWindowFocus = () => {
967
- getRuntimeStatus().then(setStatus).catch(() => {})
968
- window.settingsApi?.getSettings?.().then((loaded: any) => {
969
- if (loaded) {
970
- const migrated = migrateTypographyScale(loaded)
971
- setSettingsConfig(migrated)
972
- applyThemeStyles(migrated)
973
- }
974
- }).catch(() => {})
975
- window.api?.clearAiNotifications?.()
976
- }
977
- window.addEventListener('focus', handleWindowFocus)
978
-
979
- window.api?.onSettingsChanged?.((loaded: any) => {
980
- const migrated = migrateTypographyScale(loaded)
981
- setSettingsConfig(migrated)
982
- applyThemeStyles(migrated)
983
- getRuntimeStatus().then(setStatus).catch(() => {})
984
- })
985
-
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) {
990
- submitToolApproval(event.payload.token, true).catch((err) => {
991
- console.error("Auto approval failed:", err)
992
- })
993
- } else {
994
- setPendingApprovals((current) => ({ ...current, [chatId]: event.payload }))
995
- notifyPendingApproval(event.payload)
996
- }
997
- })
998
- return () => {
999
- window.removeEventListener('focus', handleWindowFocus)
1000
- unlistenPromise?.then?.((unlisten) => unlisten?.())
1001
- unlistenSpotlight?.then?.((unlisten) => unlisten?.())
1002
- unlistenVision?.then?.((unlisten) => unlisten?.())
1003
- }
1004
- }, [])
1005
-
1006
- useEffect(() => {
1007
- const timer = window.setTimeout(() => setStartupTimedOut(true), 10000)
1008
- return () => window.clearTimeout(timer)
1009
- }, [])
1010
-
1011
- useEffect(() => {
1012
- if (view === 'pictures' || view === 'imagine') refreshPictures().catch((reason: unknown) => setError(errorMessage(reason)))
1013
- }, [view])
1014
-
1015
- useEffect(() => {
1016
- if (view === 'workspace' && !agentMode) updateAgentMode(true)
1017
- }, [view, agentMode])
1018
-
1019
- const showToast = (nextMessage: string) => {
1020
- setToastMessage(nextMessage)
1021
- setTimeout(() => setToastMessage((current) => current === nextMessage ? '' : current), 3000)
1022
- }
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
-
1158
- const toggleSidebar = () => {
1159
- if (window.innerWidth <= 760) {
1160
- setMobileSidebarOpen(false)
1161
- return
1162
- }
1163
- const next = !sidebarCollapsed
1164
- window.localStorage.setItem('mint:sidebar-collapsed', String(next))
1165
- setSidebarCollapsed(next)
1166
- setMobileSidebarOpen(false)
1167
- }
1168
-
1169
- const handleSidebarResize = (width: number) => {
1170
- setSidebarWidth(Math.max(SIDEBAR_COLLAPSE_THRESHOLD, Math.min(SIDEBAR_MAX_WIDTH, width)))
1171
- }
1172
-
1173
- const handleSidebarResizeEnd = (width: number) => {
1174
- if (width < SIDEBAR_COLLAPSE_THRESHOLD) {
1175
- // Dragged small enough — snap fully closed instead of leaving a sliver.
1176
- setSidebarWidth(SIDEBAR_DEFAULT_WIDTH)
1177
- window.localStorage.setItem('mint:sidebar-width', String(SIDEBAR_DEFAULT_WIDTH))
1178
- window.localStorage.setItem('mint:sidebar-collapsed', 'true')
1179
- setSidebarCollapsed(true)
1180
- return
1181
- }
1182
- const clamped = Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, width))
1183
- setSidebarWidth(clamped)
1184
- window.localStorage.setItem('mint:sidebar-width', String(clamped))
1185
- }
1186
-
1187
- const updateAgentMode = (enabled: boolean) => {
1188
- window.localStorage.setItem('mint:agent-mode', String(enabled))
1189
- setAgentMode(enabled)
1190
- }
1191
-
1192
- const updatePlanMode = (enabled: boolean) => {
1193
- window.localStorage.setItem('mint:plan-mode', String(enabled))
1194
- setPlanMode(enabled)
1195
- }
1196
-
1197
- const updateWorkspacePath = (path: string) => {
1198
- const next = path.trim()
1199
- conversationActions.selectWorkspace(next)
1200
- }
1201
-
1202
- useEffect(() => {
1203
- if (!sending) {
1204
- sessionAutoApprovedRef.current = false
1205
- autoApprovedChatIdRef.current = null
1206
- setSessionAutoApproved(false)
1207
- }
1208
- }, [sending])
1209
-
1210
- async function handleApproval(approved: boolean, autoApproveSession = false, answer?: string) {
1211
- const chatId = conversationId
1212
- const pendingApproval = pendingApprovals[chatId]
1213
- if (!pendingApproval) return
1214
- try {
1215
- if (autoApproveSession) {
1216
- sessionAutoApprovedRef.current = true
1217
- autoApprovedChatIdRef.current = chatId
1218
- setSessionAutoApproved(true)
1219
- }
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
- })
1227
- } catch (reason) {
1228
- setError(errorMessage(reason))
1229
- }
1230
- }
1231
-
1232
- async function handleCancelMessage() {
1233
- if (!sending || !streamingConversationId) return
1234
- runGenerationRef.current += 1
1235
- try {
1236
- await conversationActions.executeCancellation(() =>
1237
- cancelChatMessage(streamingConversationId),
1238
- )
1239
- } catch (e) {
1240
- console.error("Failed to cancel message stream:", e)
1241
- } finally {
1242
- setPendingApprovals((current) => {
1243
- const next = { ...current }
1244
- delete next[streamingConversationId]
1245
- return next
1246
- })
1247
- setStreamingConversationId(null)
1248
- setActiveTurnId(null)
1249
- }
1250
- }
1251
-
1252
- async function sendPrompt(
1253
- promptText: string,
1254
- options: {
1255
- imageAttachments?: Array<{ dataUri: string; name: string; previewDataUri?: string }>
1256
- videoAttachments?: Array<{ dataUri: string; name: string }>
1257
- audioDataUri?: string | null
1258
- documentAttachment?: DocumentAttachment | null
1259
- systemInstruction?: string
1260
- clearComposer?: boolean
1261
- pinnedMcpServer?: string
1262
- forceAgentMode?: boolean
1263
- } = {},
1264
- ) {
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
1277
- const outgoingImages = options.imageAttachments ?? []
1278
- const outgoingVideos = options.videoAttachments ?? []
1279
- const outgoingDocument = options.documentAttachment ?? null
1280
- const shouldUseAgentMode = agentMode || options.forceAgentMode || promptText.toLowerCase().startsWith('search web:')
1281
- const outgoingImage = outgoingImages.map((img) => img.dataUri).join(' ')
1282
- const outgoingVideo = outgoingVideos.map((vid) => vid.dataUri).join(' ')
1283
- const outgoingImageCount = outgoingImages.length
1284
- conversationActions.startRun(promptText, outgoingImageCount, outgoingVideos.length)
1285
- setStreamingConversationId(conversationId)
1286
- setActiveTurnId(null)
1287
- setError('')
1288
- liveThinkingOpenRef.current = true
1289
- liveExtendedThinkingOpenRef.current = true
1290
- setThinkingExpanded((current) => ({ ...current, live: true, 'live-extended': true }))
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
- }
1312
- if (options.clearComposer) {
1313
- conversationActions.clearComposer()
1314
- }
1315
-
1316
- try {
1317
- const response = await conversationActions.executeStream((onChunk) => streamChatMessage(
1318
- shouldUseAgentMode ? promptText : `/chat ${promptText}`,
1319
- onChunk,
1320
- outgoingImage,
1321
- options.audioDataUri ?? null,
1322
- outgoingVideo,
1323
- options.systemInstruction ?? '',
1324
- (progress) => {
1325
- progressSnapshot.push(progress)
1326
- queueProgress(progress)
1327
- },
1328
- outgoingDocument,
1329
- originWorkspacePath,
1330
- originChatId,
1331
- undefined,
1332
- shouldUseAgentMode ? planMode : false,
1333
- options.pinnedMcpServer ?? null,
1334
- (payload) => {
1335
- if (sessionAutoApprovedRef.current && autoApprovedChatIdRef.current === originChatId) {
1336
- submitToolApproval(payload.token, true).catch((err) => {
1337
- console.error("Auto approval failed:", err)
1338
- })
1339
- } else {
1340
- setPendingApprovals((current) => ({ ...current, [originChatId]: payload }))
1341
- notifyPendingApproval(payload)
1342
- }
1343
- },
1344
- (id) => {
1345
- turnId = id
1346
- if (isCurrentRun()) setActiveTurnId(id)
1347
- },
1348
- ), isCurrentRun)
1349
- flushPendingProgress()
1350
- if (document.hidden || !document.hasFocus()) {
1351
- window.api?.notifyAiResponse?.(truncateForNotification(response.text))
1352
- }
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
- }))
1363
- setThinkingExpanded((current) => ({
1364
- ...current,
1365
- ...(liveThinkingOpenRef.current ? { [interactionKey]: true } : {}),
1366
- ...(liveExtendedThinkingOpenRef.current ? { [`extended-${interactionKey}`]: true } : {}),
1367
- }))
1368
- }
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)
1378
- }
1379
- }
1380
- await refreshChatSessions()
1381
- await refreshPictures()
1382
- if (isCurrentRun() && typeof window !== 'undefined') {
1383
- const pathname = (window.location.pathname || '').replace(/\/+$/, '')
1384
- const hash = (window.location.hash || '').replace(/^#\/?/, '').replace(/\/+$/, '')
1385
- const target = (pathname || hash).toLowerCase()
1386
- if (!target || target === '' || target === '/chat' || target === 'chat' || target === '/index.html' || target === 'index.html') {
1387
- window.history.replaceState({}, '', `/chat/${encodeURIComponent(originChatId)}`)
1388
- }
1389
- }
1390
- getRuntimeStatus().then(setStatus).catch(() => {})
1391
- if (isCurrentRun()) conversationActions.clearStream()
1392
- } catch (reason) {
1393
- if (isCurrentRun()) setError(errorMessage(reason))
1394
- } finally {
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
- }
1407
- }
1408
- }
1409
-
1410
- async function handleSubmit(event: FormEvent<HTMLFormElement>) {
1411
- event.preventDefault()
1412
- const trimmed = message.trim()
1413
- const currentImages = imageAttachments
1414
- const currentVideos = videoAttachments
1415
- const currentDocument = documentAttachment
1416
- const hasAttachments = currentImages.length > 0 || currentVideos.length > 0 || Boolean(currentDocument)
1417
- if ((!trimmed && !hasAttachments) || sending) return
1418
-
1419
- if (trimmed.startsWith('/')) {
1420
- // Shared Rust engine first (mint_core::slash). Anything it doesn't own
1421
- // comes back as `not_handled` and falls through to the TS processor below.
1422
- let engineResp: SlashResponse | null = null
1423
- try {
1424
- engineResp = await runSlashCommand(trimmed, workspacePath || null)
1425
- } catch {
1426
- engineResp = null
1427
- }
1428
- if (engineResp && engineResp.kind !== 'not_handled') {
1429
- conversationActions.clearComposer()
1430
- await handleEngineSlashResponse(trimmed, engineResp)
1431
- return
1432
- }
1433
-
1434
- const slashResult = executeSlashCommand(trimmed)
1435
-
1436
- if (slashResult.handled) {
1437
- conversationActions.clearComposer()
1438
-
1439
- if (slashResult.action === 'open_image_picker') {
1440
- document.getElementById('vision-file-input')?.click()
1441
- return
1442
- } else if (slashResult.action === 'paste_image') {
1443
- readClipboardImage().then((uri) => {
1444
- if (uri) conversationActions.attachImage({ dataUri: uri, name: 'Clipboard Image' })
1445
- }).catch(() => {})
1446
- return
1447
- } else if (slashResult.action === 'generate_veo') {
1448
- changeView('veo')
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
1480
- }
1481
-
1482
- if (slashResult.systemText) {
1483
- const systemMsg = {
1484
- id: Date.now(),
1485
- userText: trimmed,
1486
- aiText: slashResult.systemText,
1487
- createdAt: new Date().toISOString(),
1488
- provider: 'system',
1489
- model: 'mint-cli',
1490
- }
1491
- setInteractions((prev) => [...prev, systemMsg])
1492
- saveSystemInteraction(conversationId, trimmed, slashResult.systemText || '', 'system', 'mint-cli').catch(() => {})
1493
- }
1494
- return
1495
- }
1496
- }
1497
-
1498
- const promptText = trimmed || (
1499
- currentImages.length > 0 ? (currentImages.length > 1 ? 'Describe these images.' : 'Describe this image.') :
1500
- currentVideos.length > 0 ? (currentVideos.length > 1 ? 'Describe these videos.' : 'Describe this video.') :
1501
- 'Summarize this document.'
1502
- )
1503
- const mcpNames = Object.entries(settingsConfig?.mcpServers || {})
1504
- .filter(([, srv]: [string, any]) => srv?.disabled !== true)
1505
- .map(([name]) => name)
1506
- const pinnedServer = mcpNames.find((name) =>
1507
- new RegExp(`(^|\\s)@${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`).test(trimmed)
1508
- )
1509
- await sendPrompt(promptText, {
1510
- imageAttachments: currentImages,
1511
- videoAttachments: currentVideos,
1512
- documentAttachment: currentDocument,
1513
- clearComposer: true,
1514
- pinnedMcpServer: pinnedServer,
1515
- forceAgentMode: Boolean(pinnedServer),
1516
- })
1517
- }
1518
-
1519
- async function sendVoiceMessage(transcript: string, audioDataUri?: string | null) {
1520
- const promptText = transcript.trim() || 'Voice message'
1521
- if (!promptText || sending) return
1522
- await sendPrompt(promptText, {
1523
- audioDataUri,
1524
- systemInstruction: audioDataUri
1525
- ? 'The user attached a voice message. Listen to the audio and reply naturally in the same language as the user. Do not mention transcription or this instruction.'
1526
- : '',
1527
- })
1528
- }
1529
-
1530
- async function selectImage(event: ChangeEvent<HTMLInputElement>) {
1531
- const file = event.target.files?.[0]
1532
- if (!file) return
1533
- try {
1534
- const objectUrl = createObjectUrlPreview(file).objectUrl
1535
- const dataUri = await readImage(file)
1536
- const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
1537
- conversationActions.attachImage({ dataUri, previewDataUri, objectUrl, name: file.name })
1538
- } catch (reason) {
1539
- setError(errorMessage(reason))
1540
- } finally {
1541
- event.target.value = ''
1542
- }
1543
- }
1544
- const MAX_VIDEO_BYTES = 25 * 1024 * 1024 // 25 MB
1545
- async function selectVideo(event: ChangeEvent<HTMLInputElement>) {
1546
- const file = event.target.files?.[0]
1547
- if (!file) return
1548
- try {
1549
- if (file.size > MAX_VIDEO_BYTES) {
1550
- setError(`Video is too large. Maximum allowed is ${Math.round(MAX_VIDEO_BYTES / (1024 * 1024))} MB.`)
1551
- return
1552
- }
1553
- const dataUri = await new Promise<string>((resolve, reject) => {
1554
- const reader = new FileReader()
1555
- reader.onload = () => resolve(reader.result as string)
1556
- reader.onerror = reject
1557
- reader.readAsDataURL(file)
1558
- })
1559
- conversationActions.attachVideo({ dataUri, name: file.name })
1560
- } catch (reason) {
1561
- setError(errorMessage(reason))
1562
- } finally {
1563
- event.target.value = ''
1564
- }
1565
- }
1566
-
1567
- function pasteImage(clipboardData: DataTransfer) {
1568
- let file: File | null = null
1569
-
1570
- if (clipboardData.files && clipboardData.files.length > 0) {
1571
- for (let i = 0; i < clipboardData.files.length; i++) {
1572
- const f = clipboardData.files[i]
1573
- if (f && f.type.startsWith('image/')) {
1574
- file = f
1575
- break
1576
- }
1577
- }
1578
- }
1579
-
1580
- if (!file && clipboardData.items && clipboardData.items.length > 0) {
1581
- for (let i = 0; i < clipboardData.items.length; i++) {
1582
- const item = clipboardData.items[i]
1583
- if (item && item.type.startsWith('image/')) {
1584
- const f = item.getAsFile()
1585
- if (f) {
1586
- file = f
1587
- break
1588
- }
1589
- }
1590
- }
1591
- }
1592
-
1593
- if (!file) {
1594
- if (isDesktopApp) {
1595
- // Fallback for Tauri desktop app where WebKitGTK/WebView clipboard paste events do not provide file handles
1596
- const now = Date.now()
1597
- if (now - lastNativePasteTimeRef.current > 100) {
1598
- lastNativePasteTimeRef.current = now
1599
- readClipboardImage().then((dataUri) => {
1600
- if (dataUri) {
1601
- const name = 'Pasted image'
1602
- createTrimmedImagePreview(dataUri)
1603
- .catch(() => dataUri)
1604
- .then((previewDataUri) => {
1605
- conversationActions.attachImage({ dataUri, previewDataUri, name })
1606
- })
1607
- }
1608
- }).catch((err) => {
1609
- console.warn('Failed to read clipboard image via Tauri API:', err)
1610
- })
1611
- }
1612
- }
1613
- // No image in the clipboard (e.g. pasting plain text) — nothing more
1614
- // to do here regardless of platform. Must return before `readImage`
1615
- // below, which requires a real File and would otherwise be called
1616
- // with `file` still null on every non-image paste.
1617
- return false
1618
- }
1619
-
1620
-
1621
-
1622
- readImage(file)
1623
- .then((dataUri) => {
1624
- const name = file.name && file.name !== 'image.png' ? file.name : 'Pasted image'
1625
- createTrimmedImagePreview(dataUri)
1626
- .catch(() => dataUri)
1627
- .then((previewDataUri) => {
1628
- conversationActions.attachImage({ dataUri, previewDataUri, name })
1629
- })
1630
- })
1631
- .catch((reason) => setError(errorMessage(reason)))
1632
- return true
1633
- }
1634
-
1635
-
1636
-
1637
- async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
1638
- const file = event.target.files?.[0]
1639
- if (!file) return
1640
- try {
1641
- if (!isSupportedDocument(file.name)) {
1642
- throw new Error('Unsupported document type')
1643
- }
1644
- conversationActions.attachDocument({
1645
- filename: file.name,
1646
- dataUri: await readDocument(file),
1647
- })
1648
- } catch (reason) {
1649
- setError(errorMessage(reason))
1650
- } finally {
1651
- event.target.value = ''
1652
- }
1653
- }
1654
-
1655
- function startWebSearch() {
1656
- updateAgentMode(true)
1657
- conversationActions.compose(message.trim() ? `Search web: ${message.trim()}` : 'Search web: ')
1658
- }
1659
-
1660
- async function selectWorkspace(path?: string) {
1661
- try {
1662
- const selected = path || await selectWorkspaceDirectory()
1663
- if (selected) {
1664
- updateWorkspacePath(selected)
1665
- changeView('workspace')
1666
- }
1667
- } catch (reason) {
1668
- setError(errorMessage(reason))
1669
- }
1670
- }
1671
-
1672
- function captureScreen() {
1673
- setScreenCaptureOpen(true)
1674
- }
1675
-
1676
- async function startLiveTranslation() {
1677
- setScreenCaptureOpen(false)
1678
- await new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))
1679
- try {
1680
- await window.api.startVision()
1681
- } catch (reason) {
1682
- setError(errorMessage(reason))
1683
- }
1684
- }
1685
-
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
1698
- try {
1699
- if (action === 'New chat') {
1700
- const next = createConversationId()
1701
- activateConversation(next)
1702
- setInteractions([])
1703
- setAgentActivitySnapshots({})
1704
- conversationActions.switchSession()
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
-
1718
- if (typeof window !== 'undefined') {
1719
- const currentPath = (window.location.pathname || '').replace(/\/+$/, '')
1720
- if (currentPath !== '' && currentPath !== '/chat') {
1721
- window.history.pushState({}, '', '/chat')
1722
- }
1723
- }
1724
- return
1725
- } else {
1726
- if (!window.confirm(`${action} will clear the current conversation history. Continue?`)) return
1727
- await clearChatHistory(originChatId)
1728
- }
1729
- if (!isCurrentSession(originChatId, generation)) return
1730
- setInteractions([])
1731
- setAgentActivitySnapshots({})
1732
- conversationActions.switchSession()
1733
- } catch (reason) {
1734
- setError(errorMessage(reason))
1735
- }
1736
- }
1737
-
1738
- // Append a synthetic "system" message to the current conversation — used by
1739
- // the slash-command dispatcher for `/help`-style output and `/cron` etc.
1740
- // listings. Mirrors the inline push in `handleSubmit`.
1741
- function pushSystemMessage(userText: string, aiText: string) {
1742
- const systemMsg = {
1743
- id: Date.now(),
1744
- userText,
1745
- aiText,
1746
- createdAt: new Date().toISOString(),
1747
- provider: 'system',
1748
- model: 'mint-cli',
1749
- }
1750
- setInteractions((prev) => [...prev, systemMsg])
1751
- saveSystemInteraction(conversationId, userText, aiText, 'system', 'mint-cli').catch(() => {})
1752
- }
1753
-
1754
- // Map the shared engine's nav target onto a DashboardView.
1755
- function slashNavView(target: string): DashboardView {
1756
- switch (target) {
1757
- case 'linked_folders':
1758
- return 'link'
1759
- case 'cron':
1760
- return 'cron'
1761
- case 'skills':
1762
- return 'skills'
1763
- case 'plugins':
1764
- return 'plugins'
1765
- case 'mcp':
1766
- return 'mcp'
1767
- case 'veo':
1768
- return 'veo'
1769
- default:
1770
- return 'chat'
1771
- }
1772
- }
1773
-
1774
- // Act on a response from the shared Rust slash engine (mint_core::slash).
1775
- async function handleEngineSlashResponse(userText: string, resp: SlashResponse) {
1776
- switch (resp.kind) {
1777
- case 'message':
1778
- pushSystemMessage(userText, resp.markdown)
1779
- break
1780
- case 'applied': {
1781
- const wsChange = resp.effects.find((e) => e.kind === 'workspace_changed') as
1782
- | { kind: 'workspace_changed'; path: string }
1783
- | undefined
1784
- if (wsChange) conversationActions.selectWorkspace(wsChange.path)
1785
- const planModeChange = resp.effects.find((e) => e.kind === 'plan_mode_changed') as
1786
- | { kind: 'plan_mode_changed'; enabled: boolean }
1787
- | undefined
1788
- if (planModeChange) setPlanMode(planModeChange.enabled)
1789
- const providerChange = resp.effects.find((e) => e.kind === 'provider_changed') as
1790
- | { kind: 'provider_changed'; display: string }
1791
- | undefined
1792
- if (providerChange) {
1793
- // Render the same "✨ Gemini • model" chip the GUI's own /models shows.
1794
- // The engine already logged the `provider_change` row into the shared
1795
- // "cli" conversation, so this is just the immediate local echo.
1796
- setInteractions((prev) => [
1797
- ...prev,
1798
- {
1799
- id: Date.now(),
1800
- userText: providerChange.display,
1801
- aiText: '',
1802
- createdAt: new Date().toISOString(),
1803
- provider: 'system',
1804
- model: 'provider_change',
1805
- },
1806
- ])
1807
- }
1808
- if (resp.effects.some((e) => e.kind === 'history_cleared')) {
1809
- const chatId = conversationId
1810
- const generation = sessionGenerationRef.current
1811
- await clearChatHistory(chatId).catch(() => {})
1812
- if (isCurrentSession(chatId, generation)) {
1813
- setInteractions([])
1814
- }
1815
- }
1816
- if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1817
- if (resp.effects.some((e) => e.kind !== 'history_cleared' && e.kind !== 'workspace_changed')) {
1818
- // Desktop also gets a `settings-changed` push; refetch anyway so web stays in sync.
1819
- ;(window as any).settingsApi?.getSettings?.().then((c: any) => c && setSettingsConfig(c)).catch(() => {})
1820
- getRuntimeStatus().then(setStatus).catch(() => {})
1821
- }
1822
- break
1823
- }
1824
- case 'navigate':
1825
- if (resp.markdown) pushSystemMessage(userText, resp.markdown)
1826
- changeView(slashNavView(resp.target as any))
1827
- break
1828
- case 'forward_to_agent':
1829
- if (resp.plan_mode) setPlanMode(true)
1830
- if (resp.agent_mode) setAgentMode(true)
1831
- await sendPrompt(resp.prompt, { clearComposer: true })
1832
- break
1833
- case 'needs_choice':
1834
- setPendingSlashChoice({ command: resp.command, title: resp.title, options: resp.options })
1835
- break
1836
- case 'exit':
1837
- break
1838
- }
1839
- }
1840
-
1841
- function handleThinkingExpandedChange(key: string, open: boolean) {
1842
- if (key === 'live') liveThinkingOpenRef.current = open
1843
- if (key === 'live-extended') liveExtendedThinkingOpenRef.current = open
1844
- setThinkingExpanded((current) => ({ ...current, [key]: open }))
1845
- }
1846
-
1847
- async function selectConversation(id: string) {
1848
- window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, id)
1849
- touchActiveTimestamp()
1850
- activateConversation(id)
1851
- const generation = sessionGenerationRef.current
1852
- changeView('chat', id)
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
- }
1871
- }
1872
-
1873
- const handleNewChatInProject = useCallback((targetPath: string) => {
1874
- clearHistory('New chat', targetPath)
1875
- }, [])
1876
-
1877
- async function deleteConversation(id: string) {
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
- }
1890
- const session = chatSessions.find((item) => item.id === id)
1891
- const title = session?.title || 'this chat'
1892
- if (!window.confirm(`Delete "${title}"? This will remove the conversation and its messages.`)) return
1893
-
1894
- try {
1895
- await deleteChatSession(id)
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
1899
- ? (remaining[0]?.id ?? createConversationId())
1900
- : currentChatId
1901
-
1902
- if (nextActive !== currentChatId) {
1903
- if (remaining.length > 0 && remaining[0]?.id) {
1904
- window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, nextActive)
1905
- if (typeof window !== 'undefined') {
1906
- window.history.replaceState({}, '', `/chat/${encodeURIComponent(nextActive)}`)
1907
- }
1908
- } else {
1909
- window.localStorage.removeItem(ACTIVE_CONVERSATION_ID_KEY)
1910
- if (typeof window !== 'undefined') {
1911
- window.history.replaceState({}, '', '/chat')
1912
- }
1913
- }
1914
- activateConversation(nextActive)
1915
- const generation = sessionGenerationRef.current
1916
- conversationActions.clearProgress()
1917
- await loadConversationHistory(nextActive, generation)
1918
- if (!isCurrentSession(nextActive, generation)) return
1919
- }
1920
-
1921
- await refreshChatSessions()
1922
- if (id !== currentChatId) return
1923
- conversationActions.switchSession()
1924
- } catch (reason) {
1925
- setError(errorMessage(reason))
1926
- }
1927
- }
1928
-
1929
- async function renameConversation(id: string, newTitle: string) {
1930
- if (!newTitle.trim()) return
1931
- try {
1932
- await renameChatSession(id, newTitle.trim())
1933
- await refreshChatSessions()
1934
- } catch (reason) {
1935
- setError(errorMessage(reason))
1936
- }
1937
- }
1938
-
1939
- function formatProviderChangeText(provider: string, model: string) {
1940
- let providerName = provider
1941
- if (provider === 'gemini') providerName = 'Gemini'
1942
- else if (provider === 'openai') providerName = 'OpenAI'
1943
- else if (provider === 'openrouter') providerName = 'OpenRouter'
1944
- else if (provider === 'deepseek') providerName = 'DeepSeek'
1945
- else if (provider === 'anthropic') providerName = 'Claude'
1946
- else if (provider === 'huggingface') providerName = 'HF'
1947
- else if (provider === 'local_openai') providerName = 'Local'
1948
- else if (provider === 'ollama') providerName = 'Ollama'
1949
- else if (provider.startsWith('custom:')) {
1950
- const id = provider.replace(/^custom:/, '')
1951
- const cp = (settingsConfig?.customProviders ?? []).find((p: any) => p.id === id)
1952
- providerName = cp?.displayName || id
1953
- }
1954
-
1955
- if (providerName && providerName === provider) {
1956
- providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
1957
- }
1958
- return [providerName, model].filter(Boolean).join(' • ')
1959
- }
1960
-
1961
- function getActiveModelName(config: any, provider: string) {
1962
- switch (provider) {
1963
- case 'gemini': return config.geminiModel || 'gemini-1.5-flash'
1964
- case 'openai': return config.openaiModel || 'gpt-4o'
1965
- case 'openrouter': return config.openrouterModel || 'anthropic/claude-3.5-sonnet'
1966
- case 'deepseek': return config.deepseekModel || 'deepseek-chat'
1967
- case 'anthropic': return config.anthropicModel || 'claude-3-5-sonnet-20240620'
1968
- case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
1969
- case 'local_openai': return config.localModelName || 'llama3'
1970
- case 'ollama': return config.ollamaModel || 'llama3:latest'
1971
- default: {
1972
- if (provider.startsWith('custom:')) {
1973
- const id = provider.replace(/^custom:/, '')
1974
- const cp = (config.customProviders ?? []).find((p: any) => p.id === id)
1975
- return (config.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
1976
- }
1977
- return ''
1978
- }
1979
- }
1980
- }
1981
-
1982
- async function changeProvider(provider: string) {
1983
- try {
1984
- const config = await window.settingsApi.getSettings()
1985
- if (config.aiProvider === provider) return
1986
- config.aiProvider = provider
1987
- await window.settingsApi.saveSettings(config)
1988
- setSettingsConfig(config)
1989
- setStatus(await getRuntimeStatus())
1990
-
1991
- // Record system event in chat history if conversation already has messages
1992
- const activeModel = getActiveModelName(config, provider)
1993
- const displayName = formatProviderChangeText(provider, activeModel)
1994
- if (interactions.length > 0) {
1995
- await saveSystemInteraction(conversationId, displayName, '', 'system', 'provider_change')
1996
- await refreshHistory()
1997
- }
1998
- } catch (reason) {
1999
- setError(errorMessage(reason))
2000
- }
2001
- }
2002
-
2003
- async function changeModel(modelName: string) {
2004
- try {
2005
- const config = await window.settingsApi.getSettings()
2006
- const provider = config.aiProvider
2007
- const currentModel = getActiveModelName(config, provider)
2008
- if (currentModel === modelName) return
2009
- if (provider === 'gemini') {
2010
- config.geminiModel = modelName
2011
- } else if (provider === 'openai') {
2012
- config.openaiModel = modelName
2013
- } else if (provider === 'openrouter') {
2014
- config.openrouterModel = modelName
2015
- } else if (provider === 'deepseek') {
2016
- config.deepseekModel = modelName
2017
- } else if (provider === 'anthropic') {
2018
- config.anthropicModel = modelName
2019
- } else if (provider === 'huggingface') {
2020
- config.hfModel = modelName
2021
- } else if (provider === 'local_openai') {
2022
- config.localModelName = modelName
2023
- } else if (provider === 'ollama') {
2024
- config.ollamaModel = modelName
2025
- } else if (provider.startsWith('custom:')) {
2026
- const id = provider.replace(/^custom:/, '')
2027
- config.customModelSelections = {
2028
- ...(config.customModelSelections ?? {}),
2029
- [id]: modelName
2030
- }
2031
- }
2032
- await window.settingsApi.saveSettings(config)
2033
- setSettingsConfig(config)
2034
- setStatus(await getRuntimeStatus())
2035
-
2036
- // Record system event in chat history if conversation already has messages
2037
- const displayName = formatProviderChangeText(provider, modelName)
2038
- if (interactions.length > 0) {
2039
- await saveSystemInteraction(conversationId, displayName, '', 'system', 'provider_change')
2040
- await refreshHistory()
2041
- }
2042
- } catch (reason) {
2043
- setError(errorMessage(reason))
2044
- }
2045
- }
2046
-
2047
- async function changeProviderAndModel(provider: string, modelName: string) {
2048
- try {
2049
- const config = await window.settingsApi.getSettings()
2050
- config.aiProvider = provider
2051
- if (provider === 'gemini') {
2052
- config.geminiModel = modelName
2053
- } else if (provider === 'openai') {
2054
- config.openaiModel = modelName
2055
- } else if (provider === 'openrouter') {
2056
- config.openrouterModel = modelName
2057
- } else if (provider === 'deepseek') {
2058
- config.deepseekModel = modelName
2059
- } else if (provider === 'anthropic') {
2060
- config.anthropicModel = modelName
2061
- } else if (provider === 'huggingface') {
2062
- config.hfModel = modelName
2063
- } else if (provider === 'local_openai') {
2064
- config.localModelName = modelName
2065
- } else if (provider === 'ollama') {
2066
- config.ollamaModel = modelName
2067
- } else if (provider.startsWith('custom:')) {
2068
- const id = provider.replace(/^custom:/, '')
2069
- config.customModelSelections = {
2070
- ...(config.customModelSelections ?? {}),
2071
- [id]: modelName
2072
- }
2073
- }
2074
- await window.settingsApi.saveSettings(config)
2075
- setSettingsConfig(config)
2076
- setStatus(await getRuntimeStatus())
2077
-
2078
- // Record system event in chat history if conversation already has messages
2079
- const displayName = formatProviderChangeText(provider, modelName)
2080
- if (interactions.length > 0) {
2081
- await saveSystemInteraction(conversationId, displayName, '', 'system', 'provider_change')
2082
- await refreshHistory()
2083
- }
2084
- } catch (reason) {
2085
- setError(errorMessage(reason))
2086
- }
2087
- }
2088
-
2089
- async function changeGeminiLiveVoice(voiceName: string) {
2090
- try {
2091
- const config = await window.settingsApi.getSettings()
2092
- if (config.geminiLiveVoice === voiceName) return
2093
- config.geminiLiveVoice = voiceName
2094
- await window.settingsApi.saveSettings(config)
2095
- setSettingsConfig(config)
2096
- } catch (reason) {
2097
- setError(errorMessage(reason))
2098
- }
2099
- }
2100
-
2101
- async function handleModelInteraction(area: ModelInteraction) {
2102
- if (sending) return
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
- })
2112
- const labels: Record<ModelInteraction, string> = {
2113
- head: 'Pats Mint on the head',
2114
- cheek: 'Pokes Mint on the cheek',
2115
- 'left hand': "Touches Mint's left hand",
2116
- 'right hand': "Touches Mint's right hand",
2117
- body: 'Touches Mint',
2118
- 'lower body': "Touches Mint's lower body",
2119
- }
2120
- const interactionMessage = `*${labels[area]}*`
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.`
2122
-
2123
- conversationActions.startRun(interactionMessage)
2124
- setStreamingConversationId(conversationId)
2125
- setActiveTurnId(null)
2126
- setError('')
2127
-
2128
- try {
2129
- const response = await conversationActions.executeStream((onChunk) => streamChatMessage(
2130
- `/chat ${interactionMessage}`,
2131
- onChunk,
2132
- null,
2133
- null,
2134
- null,
2135
- instruction,
2136
- undefined,
2137
- null,
2138
- workspacePath || 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
- }
2150
- } catch (reason) {
2151
- if (isCurrentRun()) setError(errorMessage(reason))
2152
- } finally {
2153
- if (isCurrentRun()) {
2154
- conversationActions.finishRun()
2155
- setStreamingConversationId(null)
2156
- setActiveTurnId(null)
2157
- }
2158
- }
2159
- }
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
-
2225
- return (
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
- )}
2257
- <div
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}
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
- )}
2283
- {mobileSidebarOpen && (
2284
- <button
2285
- type="button"
2286
- aria-label="Close navigation menu"
2287
- className="sidebar-backdrop"
2288
- onClick={() => setMobileSidebarOpen(false)}
2289
- style={{
2290
- position: 'fixed',
2291
- top: 0,
2292
- left: 0,
2293
- right: 0,
2294
- bottom: 0,
2295
- background: 'rgba(0, 0, 0, 0.5)',
2296
- backdropFilter: 'blur(4px)',
2297
- zIndex: 9998,
2298
- }}
2299
- />
2300
- )}
2301
- <DashboardSidebar
2302
- view={view}
2303
- sidebarCollapsed={sidebarCollapsed}
2304
- modelVisible={modelVisible}
2305
- sending={sending}
2306
- expressionIndex={expressionIndex}
2307
- accessoryIndex={accessoryIndex}
2308
- expressions={EXPRESSIONS}
2309
- accessories={ACCESSORIES}
2310
- interactionEnabled={interactionEnabled}
2311
- showInteractionGuide={showInteractionGuide}
2312
- onToggleSidebar={toggleSidebar}
2313
- onSidebarResize={handleSidebarResize}
2314
- onSidebarResizeEnd={handleSidebarResizeEnd}
2315
- onClearHistory={clearHistory}
2316
- chatSessions={chatSessions}
2317
- activeConversationId={conversationId}
2318
- onSelectConversation={selectConversation}
2319
- onDeleteConversation={deleteConversation}
2320
- onRenameConversation={renameConversation}
2321
- onSetView={changeView}
2322
- onToggleModel={toggleModel}
2323
- onSetExpressionIndex={setExpressionIndex}
2324
- onSetAccessoryIndex={setAccessoryIndex}
2325
- onSetInteractionEnabled={updateInteractionEnabled}
2326
- onSetShowInteractionGuide={updateInteractionGuide}
2327
- onShowToast={showToast}
2328
- isSearchOpen={isSearchOpen}
2329
- onSetSearchOpen={setIsSearchOpen}
2330
- showWorkspaceTab={isDesktopApp}
2331
- promoteMediaStudios={!isDesktopApp}
2332
- activeWorkspacePath={workspacePath}
2333
- recentWorkspacePaths={recentWorkspacePaths}
2334
- onBrowseFolder={isDesktopApp ? handleBrowseFolderForMove : undefined}
2335
- onUpdateSessionWorkspace={handleUpdateSessionWorkspace}
2336
- onNewChatInProject={handleNewChatInProject}
2337
- />
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}>
2339
- {proactiveSuggestion && (
2340
- <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
2341
- <div className="proactive-header">
2342
- <span className="proactive-icon">✨</span>
2343
- <div className="proactive-message">{proactiveSuggestion.message}</div>
2344
- <button className="proactive-dismiss-btn" onClick={dismissProactiveSuggestion}>
2345
- Dismiss
2346
- </button>
2347
- </div>
2348
- <div className="proactive-chips">
2349
- {proactiveSuggestion.suggestions?.map((sug: any, i: number) => (
2350
- <button
2351
- key={i}
2352
- className="suggestion-chip"
2353
- onClick={() => handleProactiveAction(sug.action)}
2354
- >
2355
- {sug.label}
2356
- </button>
2357
- ))}
2358
- </div>
2359
- </div>
2360
- )}
2361
- {pendingSlashChoice && (
2362
- <div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
2363
- <div className="proactive-header">
2364
- <span className="proactive-icon">⚡</span>
2365
- <div className="proactive-message">{pendingSlashChoice.title}</div>
2366
- <button className="proactive-dismiss-btn" onClick={() => setPendingSlashChoice(null)}>
2367
- Dismiss
2368
- </button>
2369
- </div>
2370
- <div className="proactive-chips">
2371
- {pendingSlashChoice.options.map((opt) => (
2372
- <button
2373
- key={opt.value}
2374
- className="suggestion-chip"
2375
- onClick={() => {
2376
- const next = `${pendingSlashChoice.command} ${opt.value}`
2377
- setPendingSlashChoice(null)
2378
- runSlashCommand(next, workspacePath || null)
2379
- .then((r) => handleEngineSlashResponse(next, r))
2380
- .catch(() => {})
2381
- }}
2382
- >
2383
- {opt.label}
2384
- </button>
2385
- ))}
2386
- </div>
2387
- </div>
2388
- )}
2389
- {view === 'workspace' && (
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}
2410
- sending={sending}
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)}
2421
- />
2422
- </Suspense>
2423
- <ChatPanel conversation={chatConversation} actions={chatActions} />
2424
- </main>
2425
- {isDesktopApp && toolsPanelOpen && (
2426
- <Suspense fallback={<LazyPanelFallback />}>
2427
- <ToolSurfacePage
2428
- surfaces={toolSurfaces}
2429
- activeSurfaceId={activeSurfaceId}
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
- }}
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>
2475
- </div>
2476
- )}
2477
- {view === 'mcp' && (
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>
2500
- </div>
2501
- )}
2502
- {view === 'plugins' && (
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>
2511
- </div>
2512
- )}
2513
- {view === 'cron' && (
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>
2524
- </div>
2525
- )}
2526
- {view === 'link' && (
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>
2544
- </div>
2545
- )}
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
- )}
2604
- </div>
2605
- <div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
2606
- <div className="startup-loading-content">
2607
- <div className="startup-loading-dots" aria-hidden="true"><span /><span /><span /></div>
2608
- <div className="startup-loading-text">Loading Mint Agent</div>
2609
- </div>
2610
- </div>
2611
- {error && (
2612
- <div className="mint-error" style={{ position: 'absolute', bottom: '20px', right: '20px', zIndex: 100, margin: 0, boxShadow: '0 8px 24px rgba(0,0,0,0.3)', display: 'flex', alignItems: 'center', gap: '8px' }}>
2613
- <span>{error}</span>
2614
- <button onClick={() => setError('')} style={{ background: 'transparent', border: 0, color: 'white', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', padding: 0 }}>
2615
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
2616
- <line x1="18" y1="6" x2="6" y2="18"></line>
2617
- <line x1="6" y1="6" x2="18" y2="18"></line>
2618
- </svg>
2619
- </button>
2620
- </div>
2621
- )}
2622
-
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
- )}
2663
- </div>
2664
- )
2665
- }