@pheem49/mint 1.17.0 → 1.18.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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +2 -2
- package/README.md +8 -2
- package/Release_Note.md +222 -71
- package/crates/mint-cli/src/agent/diff_render.rs +66 -44
- package/crates/mint-cli/src/agent/live_status.rs +21 -4
- package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
- package/crates/mint-cli/src/agent.rs +25 -10
- package/crates/mint-cli/src/commands/agent.rs +4 -92
- package/crates/mint-cli/src/commands/browser.rs +2 -15
- package/crates/mint-cli/src/commands/integrations.rs +3 -3
- package/crates/mint-cli/src/commands/mod.rs +0 -3
- package/crates/mint-cli/src/commands/tailnet_web.rs +1 -0
- package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
- package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
- package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
- package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
- package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
- package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
- package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
- package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
- package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
- package/crates/mint-cli/src/interactive/command_ui.rs +53 -5
- package/crates/mint-cli/src/interactive/composer.rs +186 -0
- package/crates/mint-cli/src/interactive/format.rs +78 -3
- package/crates/mint-cli/src/interactive/input_box.rs +61 -1
- package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
- package/crates/mint-cli/src/interactive/slash_commands.rs +648 -371
- package/crates/mint-cli/src/interactive/tool_activity.rs +93 -20
- package/crates/mint-cli/src/interactive.rs +37 -0
- package/crates/mint-cli/src/main.rs +6 -3
- package/crates/mint-cli/src/markdown.rs +2 -37
- package/crates/mint-cli/src/onboard.rs +700 -282
- package/crates/mint-cli/src/setup.rs +1342 -624
- package/crates/mint-cli/src/terminal_theme.rs +159 -35
- package/crates/mint-cli/tests/library_intake.rs +1 -0
- package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
- package/crates/mint-core/src/agent/chat/custom.rs +149 -0
- package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
- package/crates/mint-core/src/agent/chat/mod.rs +857 -0
- package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
- package/crates/mint-core/src/agent/chat/openai.rs +582 -0
- package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
- package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
- package/crates/mint-core/src/agent/memory.rs +56 -2
- package/crates/mint-core/src/agent/tasks.rs +2 -2
- package/crates/mint-core/src/api_server/routes/chat.rs +24 -2
- package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
- package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
- package/crates/mint-core/src/api_server/routes/sessions.rs +21 -0
- package/crates/mint-core/src/avatar_bridge.rs +0 -22
- package/crates/mint-core/src/browser/cdp.rs +10 -4
- package/crates/mint-core/src/browser/headless.rs +582 -0
- package/crates/mint-core/src/browser/input.rs +7 -62
- package/crates/mint-core/src/browser/interact.rs +0 -14
- package/crates/mint-core/src/browser/mod.rs +8 -29
- package/crates/mint-core/src/browser/navigate.rs +2 -36
- package/crates/mint-core/src/browser/runtime.rs +2 -4
- package/crates/mint-core/src/browser/session.rs +50 -466
- package/crates/mint-core/src/browser/workflow.rs +142 -32
- package/crates/mint-core/src/cron/store.rs +2 -2
- package/crates/mint-core/src/integrations/mcp.rs +6 -8
- package/crates/mint-core/src/lib.rs +0 -5
- package/crates/mint-core/src/media/gemini_live.rs +636 -86
- package/crates/mint-core/src/media/library/migration.rs +26 -0
- package/crates/mint-core/src/media/library/tests.rs +1 -1
- package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
- package/crates/mint-core/src/media/pictures.rs +1 -1
- package/crates/mint-core/src/media/tool_images.rs +92 -1
- package/crates/mint-core/src/mods/manager.rs +101 -35
- package/crates/mint-core/src/orchestration/context_render.rs +441 -24
- package/crates/mint-core/src/orchestration/decision_parsing.rs +531 -16
- package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
- package/crates/mint-core/src/orchestration/loop_guard.rs +145 -0
- package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
- package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
- package/crates/mint-core/src/orchestration/mod.rs +320 -4249
- package/crates/mint-core/src/orchestration/reliability.rs +67 -27
- package/crates/mint-core/src/orchestration/review_regressions.rs +5 -0
- package/crates/mint-core/src/orchestration/tests.rs +1665 -0
- package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
- package/crates/mint-core/src/orchestration/tools/files.rs +1 -0
- package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
- package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
- package/crates/mint-core/src/orchestration/tools/shell.rs +129 -58
- package/crates/mint-core/src/orchestration/truncation.rs +170 -13
- package/crates/mint-core/src/orchestration/types.rs +648 -0
- package/crates/mint-core/src/orchestration/verification.rs +0 -3
- package/crates/mint-core/src/orchestration/workspace_helpers.rs +24 -6
- package/crates/mint-core/src/prompts/agent.rs +52 -76
- package/crates/mint-core/src/prompts/tool_catalog.rs +8 -151
- package/crates/mint-core/src/search/files.rs +20 -0
- package/crates/mint-core/src/slash/mod.rs +228 -265
- package/crates/mint-core/src/slash/models.rs +6 -1
- package/crates/mint-core/src/system/bg_shell.rs +1 -0
- package/crates/mint-core/src/system/config.rs +176 -34
- package/crates/mint-mod-runtime/src/lib.rs +37 -23
- package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
- package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
- package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
- package/out/web/assets/{CliSessionsView-MI8kXMAb.js → CliSessionsView-z5i6qGyb.js} +1 -1
- package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
- package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
- package/out/web/assets/{CommandPalette-BD8SlTE5.js → CommandPalette-DaKlzbKs.js} +1 -1
- package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
- package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
- package/out/web/assets/{ImageStudioPanel-BP7N4rLr.js → ImageStudioPanel-B5N7TjeB.js} +1 -1
- package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
- package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
- package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
- package/out/web/assets/{McpRegistryPicker-CoS83vtc.js → McpRegistryPicker-DnXPlzI7.js} +1 -1
- package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
- package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
- package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
- package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
- package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
- package/out/web/assets/{PluginsView-jBYmcKh4.js → PluginsView-DoTg9MVa.js} +1 -1
- package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
- package/out/web/assets/{ScheduledTasksView-5uw9-V9H.js → ScheduledTasksView-BD5DVPkZ.js} +1 -1
- package/out/web/assets/{SelectField-DXBQtrUK.js → SelectField-Cy6CghrU.js} +1 -1
- package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
- package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
- package/out/web/assets/{SkillsView-C-gp9J7m.js → SkillsView-CIeWwq5z.js} +1 -1
- package/out/web/assets/StockCard-DxUciKyp.js +1 -0
- package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
- package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
- package/out/web/assets/{UiFeatureCard-6BwxTOCe.js → UiFeatureCard-CdY0hGWy.js} +1 -1
- package/out/web/assets/{UiGridCard-DvZcLNxb.js → UiGridCard-BBK3I5hC.js} +1 -1
- package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
- package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css → VeoStudioPanel-9kU9YLEN.css} +1 -1
- package/out/web/assets/{VeoStudioPanel-DfsOJKv0.js → VeoStudioPanel-Co6-wEWM.js} +2 -2
- package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
- package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
- package/out/web/assets/{abnfDiagram-VCTEODGH-BOo3Ckng.js → abnfDiagram-VCTEODGH-CMfR95N2.js} +1 -1
- package/out/web/assets/{architecture-7GRP2DOG-ZOwheW3n.js → architecture-7GRP2DOG-DhAM4HyO.js} +1 -1
- package/out/web/assets/{architectureDiagram-5GKGNRK7-DC2FDzVd.js → architectureDiagram-5GKGNRK7-Yk3TUJ_F.js} +1 -1
- package/out/web/assets/{blockDiagram-I7D4REHJ-BrKFWdAH.js → blockDiagram-I7D4REHJ-X9fBgJik.js} +1 -1
- package/out/web/assets/{c4Diagram-7LVT6UL2-BHTHK1Yd.js → c4Diagram-7LVT6UL2-Bk9x7N-8.js} +1 -1
- package/out/web/assets/{chunk-4HAMMTFA-BYFkZwNj.js → chunk-4HAMMTFA-Dx5fk1_0.js} +1 -1
- package/out/web/assets/{chunk-75Z2AOVW-BSKfSKSA.js → chunk-75Z2AOVW-BszTQoM4.js} +1 -1
- package/out/web/assets/{chunk-DU6HZSFF-vIhsiW6n.js → chunk-DU6HZSFF-DK-As3rk.js} +1 -1
- package/out/web/assets/{chunk-F27PBJKO-BlYpW9xq.js → chunk-F27PBJKO-LmncZZIS.js} +1 -1
- package/out/web/assets/{chunk-GMAD6QVW-BIBz_Ug5.js → chunk-GMAD6QVW-DSQ_sDRJ.js} +1 -1
- package/out/web/assets/{chunk-GVQU2GXP-NPSHxeqC.js → chunk-GVQU2GXP-Cno3ulje.js} +1 -1
- package/out/web/assets/{chunk-IMKFNOWR-YlJK8_Fp.js → chunk-IMKFNOWR-bIzfY9Jc.js} +1 -1
- package/out/web/assets/{chunk-L3NEJ4N5-DMYi4-af.js → chunk-L3NEJ4N5-Dl6eOa0D.js} +1 -1
- package/out/web/assets/{chunk-OSK3NFVY-Begd3-jA.js → chunk-OSK3NFVY-BaUGJ-cM.js} +1 -1
- package/out/web/assets/{chunk-P2QGCYS3-D1I4xFHN.js → chunk-P2QGCYS3-_066ndhH.js} +1 -1
- package/out/web/assets/{chunk-POPQ4Y6H-CafZZzNE.js → chunk-POPQ4Y6H-YT1AKhTv.js} +1 -1
- package/out/web/assets/{chunk-PWAF6VOD-Dwh6_Lnt.js → chunk-PWAF6VOD-CrEgDsxh.js} +1 -1
- package/out/web/assets/{chunk-SHT3W25Y-B0XrCSEb.js → chunk-SHT3W25Y-_KwaRLOl.js} +1 -1
- package/out/web/assets/{chunk-SVP7TREG-CxLsddxC.js → chunk-SVP7TREG-BQ1qb32L.js} +1 -1
- package/out/web/assets/{chunk-TICWLB2K-y156QInb.js → chunk-TICWLB2K-BDyehCtg.js} +1 -1
- package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
- package/out/web/assets/{config-DqXJXeZM.css → config-2suVFPZg.css} +1 -1
- package/out/web/assets/config-CkRCdkxn.js +2 -0
- package/out/web/assets/{cynefin-OW5HDTMX-SMVvrLf-.js → cynefin-OW5HDTMX-Dl_31aaN.js} +1 -1
- package/out/web/assets/{cynefinDiagram-5FMLGOSQ-rZznN24T.js → cynefinDiagram-5FMLGOSQ-Bpir8jha.js} +1 -1
- package/out/web/assets/{dagre-GXQ25YYZ-BOToydFu.js → dagre-GXQ25YYZ-D9xu3nsq.js} +1 -1
- package/out/web/assets/{diagram-S7CK7UJ4-TMVaGQEJ.js → diagram-S7CK7UJ4-DeveoEeB.js} +1 -1
- package/out/web/assets/{diagram-UQ7AKVKN-C2JK-GP2.js → diagram-UQ7AKVKN-p4xWRESV.js} +1 -1
- package/out/web/assets/{diagram-VSXAHHWV-Cw8VSi2K.js → diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
- package/out/web/assets/{diagram-VX7I27RA-DXp4fKk9.js → diagram-VX7I27RA-DRiCa3Vd.js} +1 -1
- package/out/web/assets/{diagram-Z3DM3KII-CzSicYiW.js → diagram-Z3DM3KII-Bt7zduQX.js} +1 -1
- package/out/web/assets/{ebnfDiagram-PWID7BFC-Dqj-zqPu.js → ebnfDiagram-PWID7BFC-DV9WpwdR.js} +1 -1
- package/out/web/assets/{erDiagram-RLTQ6QDP-DZwFZAuD.js → erDiagram-RLTQ6QDP-BJ_nwG2m.js} +1 -1
- package/out/web/assets/{eventmodeling-NTZA5JFV-C0O-Lvgp.js → eventmodeling-NTZA5JFV-BTBMrfVa.js} +1 -1
- package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
- package/out/web/assets/{ganttDiagram-EL5Y4UJY-Da4ULXTl.js → ganttDiagram-EL5Y4UJY-DQLdsXrC.js} +1 -1
- package/out/web/assets/{gitGraph-4MIJSDKK-B9wNFZIP.js → gitGraph-4MIJSDKK-CL1OEOQ_.js} +1 -1
- package/out/web/assets/{gitGraphDiagram-WWUBYQGX-RjrhB0MP.js → gitGraphDiagram-WWUBYQGX-8cSi42H7.js} +1 -1
- package/out/web/assets/index-BocxnrDG.js +168 -0
- package/out/web/assets/index-yeh_r55a.css +1 -0
- package/out/web/assets/{info-A6RAGUB7-B3dfCpeY.js → info-A6RAGUB7-C1zsX5sc.js} +1 -1
- package/out/web/assets/{infoDiagram-27XIBGKW-QVWkLVx7.js → infoDiagram-27XIBGKW-DsRYtIyI.js} +1 -1
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-CLFNIRI9.js → ishikawaDiagram-5VMMS53U-B2crwIXj.js} +1 -1
- package/out/web/assets/{journeyDiagram-3NMN7TZE-fgOQadYF.js → journeyDiagram-3NMN7TZE-BTH-GyVR.js} +1 -1
- package/out/web/assets/{kanban-definition-UXKFOSKX-G3Q_9g9O.js → kanban-definition-UXKFOSKX-BeRfCnuA.js} +1 -1
- package/out/web/assets/{mermaid-parser.core-v3Jm7nco.js → mermaid-parser.core-BJwgCBBc.js} +3 -3
- package/out/web/assets/{mermaid.core-DTEjLUA1.js → mermaid.core-CsDDodN8.js} +4 -4
- package/out/web/assets/{mindmap-definition-YA3MSWOX-C_D42_fF.js → mindmap-definition-YA3MSWOX-B42efOQ1.js} +1 -1
- package/out/web/assets/{modelManager-BPLycQrN.js → modelManager-B3ZFMh8v.js} +2 -2
- package/out/web/assets/{oauthManager-BUK7ic7q.js → oauthManager-Ch5Du2fF.js} +1 -1
- package/out/web/assets/{packet-AYTQ26CC-B-R-SHlF.js → packet-AYTQ26CC-B6xUyE6r.js} +1 -1
- package/out/web/assets/{pegDiagram-XKGWAZYB-Da2q_giz.js → pegDiagram-XKGWAZYB-1LabVycL.js} +1 -1
- package/out/web/assets/{pie-WAS4IAKB-BC8F0N7-.js → pie-WAS4IAKB-DJwo-nf_.js} +1 -1
- package/out/web/assets/{pieDiagram-E7YTZNPT-Iy9REJNC.js → pieDiagram-E7YTZNPT-icmAqzy4.js} +1 -1
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-ztsiYw7h.js → quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +1 -1
- package/out/web/assets/{radar-RG4KPBEZ-B_1P8Z_z.js → radar-RG4KPBEZ-D7d-Xriw.js} +1 -1
- package/out/web/assets/{railroad-74A4TZTK-DuTqczSo.js → railroad-74A4TZTK-viuhdYbs.js} +1 -1
- package/out/web/assets/{railroad-abnf-HS5TGJTU-Df29WLmp.js → railroad-abnf-HS5TGJTU-DNXiCNea.js} +1 -1
- package/out/web/assets/{railroad-ebnf-LZEXJU2U-ePFMo-qp.js → railroad-ebnf-LZEXJU2U-nkSu1-0C.js} +1 -1
- package/out/web/assets/{railroad-peg-WCYAUIDC-DYXLW4_E.js → railroad-peg-WCYAUIDC-BhbMeQzz.js} +1 -1
- package/out/web/assets/{railroadDiagram-O6MQD6OU-LStnZf2S.js → railroadDiagram-O6MQD6OU-CLpG1jUX.js} +1 -1
- package/out/web/assets/{requirementDiagram-BXWQKSXE-DmzonMH-.js → requirementDiagram-BXWQKSXE-kRDrvWrc.js} +1 -1
- package/out/web/assets/{sankeyDiagram-P5KCCOFB-B8vjmUU1.js → sankeyDiagram-P5KCCOFB-DxAwmq4x.js} +1 -1
- package/out/web/assets/{sequenceDiagram-WJ2MYXX4-CTnplOAX.js → sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js} +1 -1
- package/out/web/assets/{stateDiagram-D77RDMKH-BFrIaLdI.js → stateDiagram-D77RDMKH-D5QWfOhp.js} +1 -1
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
- package/out/web/assets/{swimlanes-42K2YHIH-BXGngUNU.js → swimlanes-42K2YHIH-CRs5UyA-.js} +1 -1
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
- package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
- package/out/web/assets/{timeline-definition-24CTP7MA-CegH7n43.js → timeline-definition-24CTP7MA-Denqd9qb.js} +1 -1
- package/out/web/assets/{treeView-Q6P3EWNA-DTpJv2Tw.js → treeView-Q6P3EWNA-0my3Z3s4.js} +1 -1
- package/out/web/assets/{treemap-WGGIJYW6-i8M9iJU6.js → treemap-WGGIJYW6-B_EjkLYq.js} +1 -1
- package/out/web/assets/{vennDiagram-4TSXK5OY-Dpzo1S8U.js → vennDiagram-4TSXK5OY-DxVg3Ewr.js} +1 -1
- package/out/web/assets/{wardley-WFR3VGLG-BrNsgtfw.js → wardley-WFR3VGLG-CGD-Tj4p.js} +1 -1
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-lQ92LFE1.js → wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +1 -1
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-BaOaEoBd.js → xychartDiagram-S5SC5T6Z-BVM9bKis.js} +1 -1
- package/out/web/index-web.html +2 -2
- package/out/web/index.html +2 -2
- package/package.json +2 -2
- package/shared-modules/CODEOWNERS +3 -3
- package/shared-modules/SDL2/SDL2_mixer.json +27 -0
- package/shared-modules/appstream/README.md +13 -0
- package/shared-modules/appstream/appstream-compose.json +111 -0
- package/shared-modules/dbus-glib/dbus-glib.json +2 -2
- package/shared-modules/libportal/libportal.json +23 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
- package/shared-modules/linux-audio/fluidsynth2.json +2 -2
- package/shared-modules/linux-audio/libinstpatch.json +2 -2
- package/shared-modules/linux-audio/liblo-static.json +3 -0
- package/shared-modules/luajit/luajit.json +2 -2
- package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
- package/slash-commands.json +7 -2
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/renderer/shared/agentProgress.ts +51 -3
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -56
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +15 -1
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +25 -12
- package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
- package/src/renderer/shared/components/Settings/ThemeTab.tsx +106 -3
- package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +67 -17
- package/src/renderer/shared/components/activity/AgentActivityTable.tsx +376 -134
- package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
- package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
- package/src/renderer/shared/components/activity/McpToolResults.tsx +39 -2
- package/src/renderer/shared/components/activity/SubagentDagView.tsx +7 -7
- package/src/renderer/shared/components/activity/ViewedImages.tsx +41 -2
- package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +3 -3
- package/src/renderer/shared/components/app/CommandPalette.tsx +7 -7
- package/src/renderer/shared/components/app/DesktopTitlebar.tsx +2 -2
- package/src/renderer/shared/components/app/MintDashboard.tsx +27 -17
- package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +3 -3
- package/src/renderer/shared/components/cards/CalculationCard.tsx +3 -3
- package/src/renderer/shared/components/cards/ImageGenCard.tsx +2 -2
- package/src/renderer/shared/components/cards/ImageSearchCard.tsx +2 -2
- package/src/renderer/shared/components/cards/MermaidCard.tsx +1 -1
- package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
- package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
- package/src/renderer/shared/components/cards/StockCard.tsx +7 -7
- package/src/renderer/shared/components/cards/ToolResultCards.tsx +11 -3
- package/src/renderer/shared/components/cards/UiMockupWidget.tsx +4 -4
- package/src/renderer/shared/components/cards/WeatherCard.tsx +5 -5
- package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +11 -0
- package/src/renderer/shared/components/chat/ApprovalCard.tsx +21 -2
- package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
- package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +15 -1
- package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +40 -2
- package/src/renderer/shared/components/chat/ChatMessageItem.tsx +180 -34
- package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
- package/src/renderer/shared/components/chat/ChatPanel.test.tsx +22 -0
- package/src/renderer/shared/components/chat/ChatPanel.tsx +227 -53
- package/src/renderer/shared/components/chat/DiffReviewModal.tsx +6 -6
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +142 -1
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +45 -3
- package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +42 -14
- package/src/renderer/shared/components/chat/RewindModal.tsx +10 -10
- package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +14 -0
- package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
- package/src/renderer/shared/components/chat/SourcesBlock.tsx +236 -90
- package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +45 -1
- package/src/renderer/shared/components/chat/TurnFileChanges.tsx +202 -169
- package/src/renderer/shared/components/extensions/CliSessionsView.tsx +5 -4
- package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +3 -3
- package/src/renderer/shared/components/studio/LibraryPage.tsx +48 -5
- package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +125 -13
- package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
- package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +77 -16
- package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +78 -22
- package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +13 -0
- package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +48 -28
- package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +266 -47
- package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +18 -6
- package/src/renderer/shared/components/workspace/background-terminals.css +11 -11
- package/src/renderer/shared/constants/config.ts +1 -5
- package/src/renderer/shared/constants/models.test.ts +51 -0
- package/src/renderer/shared/constants/models.ts +139 -32
- package/src/renderer/shared/conversation/lifecycle.test.ts +91 -3
- package/src/renderer/shared/conversation/lifecycle.ts +104 -5
- package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
- package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
- package/src/renderer/shared/css/agent-observability.css +133 -0
- package/src/renderer/shared/css/chat-ui-cards.css +13 -13
- package/src/renderer/shared/css/code-review.css +63 -34
- package/src/renderer/shared/css/code-sessions.css +47 -18
- package/src/renderer/shared/css/collapsed-paste.css +1 -1
- package/src/renderer/shared/css/error-notice.css +1 -1
- package/src/renderer/shared/css/file-changes.css +165 -80
- package/src/renderer/shared/css/library.css +105 -10
- package/src/renderer/shared/css/management-views.css +1 -1
- package/src/renderer/shared/css/mcp-results.css +5 -5
- package/src/renderer/shared/css/model-selector.css +18 -18
- package/src/renderer/shared/css/mods.css +7 -7
- package/src/renderer/shared/css/notification-notice.css +2 -2
- package/src/renderer/shared/css/queued-messages.css +5 -5
- package/src/renderer/shared/css/settings/general.css +18 -13
- package/src/renderer/shared/css/settings/profile.css +5 -5
- package/src/renderer/shared/css/settings/theme.css +5 -5
- package/src/renderer/shared/css/slash-choice.css +2 -2
- package/src/renderer/shared/css/slash-suggestions.css +3 -3
- package/src/renderer/shared/css/sources-block.css +41 -2
- package/src/renderer/shared/css/tools-panel.css +2 -2
- package/src/renderer/shared/css/ui-tailwind.css +8 -0
- package/src/renderer/shared/css/veo-studio.css +2 -2
- package/src/renderer/shared/css/viewed-images.css +7 -7
- package/src/renderer/shared/platform.ts +2 -1
- package/src/renderer/shared/theme/themeManager.test.ts +17 -0
- package/src/renderer/shared/theme/themeManager.ts +21 -4
- package/src/renderer/shared/theme/themes.ts +54 -0
- package/src/renderer/shared/theme/tokens.css +6 -4
- package/src/renderer/shared/types.ts +1 -1
- package/src/renderer/shared/utils/agentActivity.test.ts +468 -2
- package/src/renderer/shared/utils/agentActivity.ts +602 -35
- package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
- package/src/renderer/shared/utils/artifactBrowser.ts +17 -19
- package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
- package/src/renderer/shared/utils/attachedDocument.ts +49 -0
- package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
- package/src/renderer/shared/utils/fileIcons.ts +149 -16
- package/src/renderer/shared/utils/markdown.tsx +72 -19
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +16 -0
- package/src/renderer/shared/utils/mcpResults.test.tsx +29 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
- package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
- package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
- package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
- package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
- package/src/renderer/shared/utils/workspaceDrag.ts +142 -0
- package/src/renderer/shared/utils/workspaceMedia.ts +10 -2
- package/src/renderer/src/App.tsx +14 -8
- package/src/renderer/src/agentProgress.ts +1 -0
- package/src/renderer/src/components/ScreenPicker.css +1 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +6 -5
- package/src/renderer/src/css/app-shell.css +4 -4
- package/src/renderer/src/css/chat.css +1261 -241
- package/src/renderer/src/css/desktop-tools.css +11 -11
- package/src/renderer/src/css/sidebar-workspace.css +2 -2
- package/src/renderer/src/css/tool-surfaces.css +12 -12
- package/src/renderer/src/index.css +38 -20
- package/src/renderer/src/tauri.ts +22 -2
- package/src/renderer/src-web/App.tsx +14 -0
- package/src/renderer/src-web/agentProgress.ts +1 -0
- package/src/renderer/src-web/css/app-shell.css +4 -4
- package/src/renderer/src-web/css/chat.css +1278 -190
- package/src/renderer/src-web/css/mobile.css +29 -13
- package/src/renderer/src-web/css/settings/mobile.css +2 -2
- package/src/renderer/src-web/css/sidebar-workspace.css +1 -1
- package/src/renderer/src-web/index.css +38 -20
- package/src/renderer/src-web/tauri.ts +22 -2
- package/src/renderer/tests/browserArtifacts.browser.tsx +1 -1
- package/src/renderer/tests/library.browser.tsx +3 -0
- package/src/renderer/tests/liveVoice.browser.html +1 -0
- package/src/renderer/tests/workspaceDrop.browser.tsx +1 -1
- package/src-tauri/src/app/lifecycle.rs +1 -14
- package/src-tauri/src/background/headless.rs +14 -8
- package/src-tauri/src/commands/conversation.rs +38 -0
- package/src-tauri/src/native/mod.rs +0 -1
- package/src-tauri/src/native/system.rs +4 -47
- package/crates/mint-cli/src/interactive/chat_tui.rs +0 -8855
- package/crates/mint-core/src/agent/chat.rs +0 -4394
- package/crates/mint-core/src/browser/lifecycle.rs +0 -291
- package/crates/mint-core/src/browser/logging.rs +0 -20
- package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
- package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +0 -2
- package/out/web/assets/CalculationCard-CRjP3E1j.js +0 -1
- package/out/web/assets/CliSessionsView-C3Br2v5Y.css +0 -1
- package/out/web/assets/CodeReviewPage-CUJceQme.css +0 -1
- package/out/web/assets/CodeReviewPage-VCUgPla_.js +0 -2
- package/out/web/assets/ImageGenCard-BCXxkVJX.js +0 -1
- package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +0 -1
- package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
- package/out/web/assets/LibraryPage-WlviRdR8.js +0 -2
- package/out/web/assets/LinkedFoldersView-CKRgroWK.js +0 -1
- package/out/web/assets/McpServersView-D3wHnuHg.js +0 -1
- package/out/web/assets/MermaidCard-NqrHdyJW.js +0 -2
- package/out/web/assets/MintDashboard-B2DJsDDO.css +0 -1
- package/out/web/assets/MintDashboard-COw_xol7.js +0 -63
- package/out/web/assets/RewindModal-DempuDod.js +0 -1
- package/out/web/assets/SettingsWindow-2YP3yj7s.js +0 -4
- package/out/web/assets/SettingsWindow-Dz8LQVgD.css +0 -1
- package/out/web/assets/StockCard-BDMCe7mx.js +0 -1
- package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +0 -38
- package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +0 -1
- package/out/web/assets/UiMockupWidget-DD73QWg1.js +0 -1
- package/out/web/assets/WeatherCard-CPucIfq8.js +0 -1
- package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +0 -2
- package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +0 -1
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +0 -1
- package/out/web/assets/config-CvIO5hkn.js +0 -2
- package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +0 -1
- package/out/web/assets/index-BmpulIkg.js +0 -168
- package/out/web/assets/index-CkvBwhX_.css +0 -1
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +0 -1
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +0 -8
- package/shared-modules/libsecret/libsecret.json +0 -23
- package/shared-modules/libusb/libusb.json +0 -27
- package/shared-modules/lzo/lzo.json +0 -27
- package/src/renderer/src/components/BrowserShell.tsx +0 -108
- package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
- package/src/renderer/src/components/browser-shell.css +0 -185
- package/src-tauri/src/native/browser.rs +0 -252
|
@@ -36,18 +36,20 @@ import { SubagentStatusStrip } from '../activity/SubagentStatusStrip'
|
|
|
36
36
|
import { VirtualChatHistory, type VirtualChatHistoryHandle } from './VirtualChatHistory'
|
|
37
37
|
import type { ArtifactFile } from '../../utils/artifacts'
|
|
38
38
|
import type { AgentProgress, ChatResponse, DiffHunk, FileChange, GitCheckpoint, QueuedPrompt, RuntimeStatus } from '../../types'
|
|
39
|
-
import { shouldShowSessionDivider, formatSessionDividerLabel } from '../../utils/ui'
|
|
39
|
+
import { shouldShowSessionDivider, formatSessionDividerLabel, createTrimmedImagePreview } from '../../utils/ui'
|
|
40
40
|
import { useVoiceInput } from '@/voiceInput'
|
|
41
41
|
import VoicePill from './VoicePill'
|
|
42
42
|
import { useGeminiLiveVoice } from '../../utils/useGeminiLiveVoice'
|
|
43
43
|
import GeminiLiveOverlay from './GeminiLiveOverlay'
|
|
44
44
|
import { isSupportedDocument, SUPPORTED_DOCUMENT_ACCEPT } from '../../utils/documentTypes'
|
|
45
45
|
import ModelSelectorPopover from './ModelSelectorPopover'
|
|
46
|
+
import ChatModeSelector from './ChatModeSelector'
|
|
46
47
|
import GitBranchSelector from '../workspace/GitBranchSelector'
|
|
47
48
|
import WorkspaceSelector from '../workspace/WorkspaceSelector'
|
|
48
49
|
import ToolsPanelToggle from '../workspace/ToolsPanelToggle'
|
|
49
50
|
import { ModsPanel } from '../extensions/ModsPanel'
|
|
50
51
|
import type { ToolSurface } from '../workspace/ToolSurfacePage'
|
|
52
|
+
import { WORKSPACE_PATH_MIME, WORKSPACE_FILE_MIME, WORKSPACE_IMAGE_MIME, isImageFileName, resolveImageToDataUri, formatWorkspaceReference } from '../../utils/workspaceDrag'
|
|
51
53
|
import QueuedMessagesBanner from './QueuedMessagesBanner'
|
|
52
54
|
import '../../css/thinking-status.css'
|
|
53
55
|
import '../../css/slash-choice.css'
|
|
@@ -78,7 +80,7 @@ export function ThinkingElapsedMeta() {
|
|
|
78
80
|
)
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
const STREAM_MARKDOWN_UPDATE_MS =
|
|
83
|
+
const STREAM_MARKDOWN_UPDATE_MS = 32
|
|
82
84
|
|
|
83
85
|
function useThrottledValue<T>(value: T, intervalMs: number): T {
|
|
84
86
|
const [throttledValue, setThrottledValue] = useState(value)
|
|
@@ -150,6 +152,7 @@ interface ChatPanelContract {
|
|
|
150
152
|
onSelectVideo: (event: ChangeEvent<HTMLInputElement>) => void
|
|
151
153
|
onSelectDocument: (event: ChangeEvent<HTMLInputElement>) => void
|
|
152
154
|
onPasteImage: (clipboardData: DataTransfer) => boolean
|
|
155
|
+
onAttachImage?: (image: { dataUri: string; previewDataUri?: string; objectUrl?: string; name: string }) => void
|
|
153
156
|
onSetMessage: (message: string) => void
|
|
154
157
|
onSendVoiceMessage: (message: string, audioDataUri?: string | null) => Promise<void>
|
|
155
158
|
onRemoveImage: (idx: number) => void
|
|
@@ -191,6 +194,9 @@ interface ChatPanelContract {
|
|
|
191
194
|
onClearQueuedPrompts?: () => void
|
|
192
195
|
onSteerQueuedPrompt?: (id: string) => void
|
|
193
196
|
onResumeQueue?: () => void
|
|
197
|
+
onResendFrom?: (fromId: number, newPrompt: string) => Promise<void> | void
|
|
198
|
+
onSyncHistory?: () => void
|
|
199
|
+
onReceiveApproval?: (approval: { token: string; approval: Record<string, unknown> }) => void
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
export type ConversationViewModel = Pick<ChatPanelContract,
|
|
@@ -269,6 +275,7 @@ export default function ChatPanel({
|
|
|
269
275
|
onSelectVideo,
|
|
270
276
|
onSelectDocument,
|
|
271
277
|
onPasteImage,
|
|
278
|
+
onAttachImage,
|
|
272
279
|
onSetMessage,
|
|
273
280
|
onSendVoiceMessage,
|
|
274
281
|
onRemoveImage,
|
|
@@ -306,6 +313,9 @@ export default function ChatPanel({
|
|
|
306
313
|
onClearQueuedPrompts,
|
|
307
314
|
onSteerQueuedPrompt,
|
|
308
315
|
onResumeQueue,
|
|
316
|
+
onResendFrom,
|
|
317
|
+
onSyncHistory,
|
|
318
|
+
onReceiveApproval,
|
|
309
319
|
} = { ...conversation, ...actions }
|
|
310
320
|
const message = useComposerDraft(initialMessage, actions.subscribeMessage, actions.getMessage)
|
|
311
321
|
const onThinkingExpandedChange = useLatestCallback(handleThinkingExpandedChange)
|
|
@@ -380,8 +390,12 @@ export default function ChatPanel({
|
|
|
380
390
|
})
|
|
381
391
|
|
|
382
392
|
const openReview = useLatestCallback((title: string, changes: FileChange[]) => {
|
|
383
|
-
if (onOpenReview)
|
|
384
|
-
|
|
393
|
+
if (onOpenReview) {
|
|
394
|
+
setReviewPage(null)
|
|
395
|
+
onOpenReview({ id: `review:${Date.now()}`, kind: 'review', title: 'Review', reviewTitle: title, changes })
|
|
396
|
+
} else {
|
|
397
|
+
setReviewPage({ title, changes })
|
|
398
|
+
}
|
|
385
399
|
})
|
|
386
400
|
|
|
387
401
|
const openAllConversationReview = useLatestCallback(() => {
|
|
@@ -691,6 +705,36 @@ export default function ChatPanel({
|
|
|
691
705
|
const dragCounter = useRef(0)
|
|
692
706
|
const lastDropTimeRef = useRef(0)
|
|
693
707
|
|
|
708
|
+
const resizeInput = useCallback((element: HTMLTextAreaElement) => {
|
|
709
|
+
element.style.height = 'auto'
|
|
710
|
+
element.style.height = `${Math.min(element.scrollHeight, 120)}px`
|
|
711
|
+
}, [])
|
|
712
|
+
|
|
713
|
+
// Direct file reference insertion into chat input
|
|
714
|
+
const handleReferenceFile = useCallback((rawPath: string) => {
|
|
715
|
+
const formatted = formatWorkspaceReference(rawPath, workspacePath).trim()
|
|
716
|
+
if (!formatted) return
|
|
717
|
+
const token = `${formatted} `
|
|
718
|
+
const textarea = textareaRef.current
|
|
719
|
+
if (!textarea) {
|
|
720
|
+
onSetMessage(message.trim() ? `${message.trimEnd()} ${token}` : token)
|
|
721
|
+
return
|
|
722
|
+
}
|
|
723
|
+
const start = textarea.selectionStart ?? message.length
|
|
724
|
+
const end = textarea.selectionEnd ?? message.length
|
|
725
|
+
const before = message.slice(0, start)
|
|
726
|
+
const after = message.slice(end)
|
|
727
|
+
const spacingBefore = before && !before.endsWith(' ') && !before.endsWith('\n') ? ' ' : ''
|
|
728
|
+
const updated = `${before}${spacingBefore}${token}${after}`
|
|
729
|
+
onSetMessage(updated)
|
|
730
|
+
const nextCursor = (before + spacingBefore + token).length
|
|
731
|
+
requestAnimationFrame(() => {
|
|
732
|
+
textarea.focus()
|
|
733
|
+
textarea.setSelectionRange(nextCursor, nextCursor)
|
|
734
|
+
resizeInput(textarea)
|
|
735
|
+
})
|
|
736
|
+
}, [message, onSetMessage, workspacePath, resizeInput])
|
|
737
|
+
|
|
694
738
|
const handleDragEnter = (e: DragEvent<HTMLElement>) => {
|
|
695
739
|
e.preventDefault()
|
|
696
740
|
if (e.dataTransfer?.types?.includes('Files')) {
|
|
@@ -711,6 +755,14 @@ export default function ChatPanel({
|
|
|
711
755
|
|
|
712
756
|
const handleDragOver = (e: DragEvent<HTMLElement>) => {
|
|
713
757
|
e.preventDefault()
|
|
758
|
+
if (
|
|
759
|
+
e.dataTransfer?.types?.includes(WORKSPACE_PATH_MIME) ||
|
|
760
|
+
e.dataTransfer?.types?.includes(WORKSPACE_FILE_MIME) ||
|
|
761
|
+
e.dataTransfer?.types?.includes(WORKSPACE_IMAGE_MIME) ||
|
|
762
|
+
e.dataTransfer?.types?.includes('application/x-mint-workspace-path')
|
|
763
|
+
) {
|
|
764
|
+
e.dataTransfer.dropEffect = 'copy'
|
|
765
|
+
}
|
|
714
766
|
}
|
|
715
767
|
|
|
716
768
|
const handleDrop = (e: DragEvent<HTMLElement>) => {
|
|
@@ -719,6 +771,27 @@ export default function ChatPanel({
|
|
|
719
771
|
dragCounter.current = 0
|
|
720
772
|
setIsDragging(false)
|
|
721
773
|
|
|
774
|
+
const wsPath =
|
|
775
|
+
e.dataTransfer?.getData(WORKSPACE_IMAGE_MIME) ||
|
|
776
|
+
e.dataTransfer?.getData(WORKSPACE_PATH_MIME) ||
|
|
777
|
+
e.dataTransfer?.getData('application/x-mint-workspace-path')
|
|
778
|
+
const plainRef = e.dataTransfer?.getData('text/plain')?.trim() || ''
|
|
779
|
+
const fileRef =
|
|
780
|
+
e.dataTransfer?.getData(WORKSPACE_PATH_MIME) ||
|
|
781
|
+
e.dataTransfer?.getData('application/x-mint-workspace-path') ||
|
|
782
|
+
(plainRef.startsWith('@') ? plainRef : '')
|
|
783
|
+
if (wsPath || fileRef) {
|
|
784
|
+
void tryAttachDroppedImage(e.dataTransfer).then((attached) => {
|
|
785
|
+
if (!attached) {
|
|
786
|
+
const raw = fileRef || wsPath
|
|
787
|
+
if (raw) {
|
|
788
|
+
handleReferenceFile(raw)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
})
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
|
|
722
795
|
const now = Date.now()
|
|
723
796
|
if (now - lastDropTimeRef.current < 250) {
|
|
724
797
|
return
|
|
@@ -792,9 +865,31 @@ export default function ChatPanel({
|
|
|
792
865
|
chatId,
|
|
793
866
|
startSession: startGeminiLiveSession,
|
|
794
867
|
sendAudioChunk: sendGeminiLiveAudioChunk,
|
|
795
|
-
stopSession: stopGeminiLiveSession
|
|
868
|
+
stopSession: stopGeminiLiveSession,
|
|
869
|
+
onTurnSaved: () => {
|
|
870
|
+
onSyncHistory?.()
|
|
871
|
+
},
|
|
872
|
+
onToolActivity: () => {
|
|
873
|
+
onWorkspaceChanged?.()
|
|
874
|
+
},
|
|
875
|
+
onApprovalRequested: (approval) => {
|
|
876
|
+
onReceiveApproval?.(approval)
|
|
877
|
+
},
|
|
796
878
|
})
|
|
797
879
|
|
|
880
|
+
useEffect(() => {
|
|
881
|
+
const onTriggerVoice = () => {
|
|
882
|
+
if (geminiLiveEnabled) {
|
|
883
|
+
geminiLive.setVoiceMode(true)
|
|
884
|
+
} else {
|
|
885
|
+
setVoiceMode(true)
|
|
886
|
+
void startRecognition()
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
window.addEventListener('mint:trigger-voice', onTriggerVoice)
|
|
890
|
+
return () => window.removeEventListener('mint:trigger-voice', onTriggerVoice)
|
|
891
|
+
}, [geminiLiveEnabled, geminiLive, setVoiceMode, startRecognition])
|
|
892
|
+
|
|
798
893
|
const canSubmit = Boolean(message.trim() || imageAttachments.length > 0 || videoAttachments.length > 0 || documentName)
|
|
799
894
|
const sendingImageMarkers = Array.from({ length: sendingImageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
|
|
800
895
|
const sendingVideoMarkers = Array.from({ length: sendingVideoCount || 0 }, (_, index) => `[Video #${index + 1}]`).join(' ')
|
|
@@ -1006,10 +1101,6 @@ export default function ChatPanel({
|
|
|
1006
1101
|
historyReadyRef.current = false; submittedDuringSessionRef.current = false; lastAutoSpokenIdRef.current = null
|
|
1007
1102
|
}, [chatId])
|
|
1008
1103
|
|
|
1009
|
-
const resizeInput = (element: HTMLTextAreaElement) => {
|
|
1010
|
-
element.style.height = 'auto'
|
|
1011
|
-
element.style.height = `${Math.min(element.scrollHeight, 120)}px`
|
|
1012
|
-
}
|
|
1013
1104
|
useEffect(() => {
|
|
1014
1105
|
return () => {
|
|
1015
1106
|
void speechPlayerRef.current?.dispose()
|
|
@@ -1113,13 +1204,77 @@ export default function ChatPanel({
|
|
|
1113
1204
|
if (!trimmed) return
|
|
1114
1205
|
onSetMessage(message.trim() ? `${message.trimEnd()} ${trimmed} ` : `${trimmed} `)
|
|
1115
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
const tryAttachDroppedImage = async (dataTransfer: DataTransfer): Promise<boolean> => {
|
|
1209
|
+
if (!onAttachImage) return false
|
|
1210
|
+
|
|
1211
|
+
const rawImage = dataTransfer.getData(WORKSPACE_IMAGE_MIME)
|
|
1212
|
+
let imagePayload: { path?: string; root?: string; url?: string; name?: string } | null = null
|
|
1213
|
+
if (rawImage) {
|
|
1214
|
+
try {
|
|
1215
|
+
imagePayload = JSON.parse(rawImage)
|
|
1216
|
+
} catch {
|
|
1217
|
+
// ignore
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const wsPath =
|
|
1222
|
+
imagePayload?.path ||
|
|
1223
|
+
dataTransfer.getData(WORKSPACE_PATH_MIME) ||
|
|
1224
|
+
dataTransfer.getData('application/x-mint-workspace-path')
|
|
1225
|
+
const plainText = dataTransfer.getData('text/plain')
|
|
1226
|
+
const uriList = dataTransfer.getData('text/uri-list')
|
|
1227
|
+
const candidatePath = (wsPath || plainText || uriList || '').replace(/^@/, '').trim()
|
|
1228
|
+
|
|
1229
|
+
const isImg =
|
|
1230
|
+
Boolean(imagePayload) ||
|
|
1231
|
+
isImageFileName(candidatePath) ||
|
|
1232
|
+
candidatePath.startsWith('blob:') ||
|
|
1233
|
+
candidatePath.startsWith('data:image/') ||
|
|
1234
|
+
Boolean(imagePayload?.url)
|
|
1235
|
+
if (!isImg) return false
|
|
1236
|
+
|
|
1237
|
+
const name = imagePayload?.name || candidatePath.split(/[\\/]/).pop()?.split('?')[0] || 'image.png'
|
|
1238
|
+
const root = imagePayload?.root || workspacePath || ''
|
|
1239
|
+
const url =
|
|
1240
|
+
imagePayload?.url ||
|
|
1241
|
+
(candidatePath.startsWith('http') || candidatePath.startsWith('blob:') || candidatePath.startsWith('data:image/')
|
|
1242
|
+
? candidatePath
|
|
1243
|
+
: '')
|
|
1244
|
+
|
|
1245
|
+
let dataUri = ''
|
|
1246
|
+
if (url) {
|
|
1247
|
+
dataUri = await resolveImageToDataUri(url, root, (p, r) => runtimePlatform.readWorkspaceImage(p, r))
|
|
1248
|
+
}
|
|
1249
|
+
if (!dataUri && candidatePath) {
|
|
1250
|
+
dataUri = await resolveImageToDataUri(candidatePath, root, (p, r) => runtimePlatform.readWorkspaceImage(p, r))
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (dataUri) {
|
|
1254
|
+
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
1255
|
+
onAttachImage({ dataUri, previewDataUri, name })
|
|
1256
|
+
textareaRef.current?.focus()
|
|
1257
|
+
return true
|
|
1258
|
+
}
|
|
1259
|
+
return false
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1116
1262
|
const handleWorkspaceDrop = (event: DragEvent<HTMLElement>) => {
|
|
1117
|
-
const reference =
|
|
1118
|
-
event.dataTransfer.getData('application/x-mint-workspace-path') ||
|
|
1119
|
-
event.dataTransfer.getData('text/plain')
|
|
1120
|
-
if (!reference.trim().startsWith('@')) return
|
|
1121
1263
|
event.preventDefault()
|
|
1122
|
-
|
|
1264
|
+
const wsPath =
|
|
1265
|
+
event.dataTransfer.getData(WORKSPACE_PATH_MIME) ||
|
|
1266
|
+
event.dataTransfer.getData('application/x-mint-workspace-path')
|
|
1267
|
+
const plainText = event.dataTransfer.getData('text/plain')
|
|
1268
|
+
const reference = wsPath || plainText
|
|
1269
|
+
|
|
1270
|
+
void tryAttachDroppedImage(event.dataTransfer).then((attached) => {
|
|
1271
|
+
if (attached) return
|
|
1272
|
+
if (!reference) return
|
|
1273
|
+
const trimmed = reference.trim()
|
|
1274
|
+
if (wsPath || trimmed.startsWith('@')) {
|
|
1275
|
+
handleReferenceFile(trimmed)
|
|
1276
|
+
}
|
|
1277
|
+
})
|
|
1123
1278
|
}
|
|
1124
1279
|
const isEmptyChat = interactions.length === 0 && !sending && !pendingApproval
|
|
1125
1280
|
|
|
@@ -1157,12 +1312,16 @@ export default function ChatPanel({
|
|
|
1157
1312
|
onToggle={toggleActivity}
|
|
1158
1313
|
pendingApproval={!!pendingApproval}
|
|
1159
1314
|
chatId={chatId}
|
|
1315
|
+
workspacePath={workspacePath}
|
|
1316
|
+
onReferenceFile={handleReferenceFile}
|
|
1160
1317
|
onOpenArtifact={openArtifact}
|
|
1161
1318
|
/>
|
|
1162
1319
|
)}
|
|
1163
1320
|
<TurnFileChanges
|
|
1164
1321
|
changes={composerChanges}
|
|
1165
1322
|
isLive
|
|
1323
|
+
workspacePath={workspacePath}
|
|
1324
|
+
onReferenceFile={handleReferenceFile}
|
|
1166
1325
|
onOpenArtifact={openArtifact}
|
|
1167
1326
|
onOpenReview={openAllConversationReview}
|
|
1168
1327
|
/>
|
|
@@ -1188,7 +1347,7 @@ export default function ChatPanel({
|
|
|
1188
1347
|
/>
|
|
1189
1348
|
)}
|
|
1190
1349
|
<div className="message-bubble">
|
|
1191
|
-
<McpToolResults progress={agentProgress} running chatId={chatId} />
|
|
1350
|
+
<McpToolResults progress={agentProgress} running chatId={chatId} mode="artifacts-only" />
|
|
1192
1351
|
<ViewedImages progress={agentProgress} chatId={chatId} onOpenArtifact={openArtifact} />
|
|
1193
1352
|
<span>
|
|
1194
1353
|
{compaction ? (
|
|
@@ -1223,7 +1382,7 @@ export default function ChatPanel({
|
|
|
1223
1382
|
type="button"
|
|
1224
1383
|
className={`msg-action-btn copy-btn ${copiedId === 'live' ? 'is-copied' : ''}`}
|
|
1225
1384
|
onClick={() => handleCopyMessage('live', throttledStreamedReply)}
|
|
1226
|
-
title={copiedId === 'live' ? '
|
|
1385
|
+
title={copiedId === 'live' ? 'Copied!' : 'Copy message'}
|
|
1227
1386
|
>
|
|
1228
1387
|
{renderCopyIcon(copiedId === 'live')}
|
|
1229
1388
|
</button>
|
|
@@ -1281,7 +1440,7 @@ export default function ChatPanel({
|
|
|
1281
1440
|
background: 'var(--bg-backdrop)',
|
|
1282
1441
|
backdropFilter: 'blur(8px)',
|
|
1283
1442
|
border: '2px dashed var(--accent)',
|
|
1284
|
-
borderRadius: '
|
|
1443
|
+
borderRadius: 'var(--radius-xl, 18px)',
|
|
1285
1444
|
margin: '12px',
|
|
1286
1445
|
display: 'flex',
|
|
1287
1446
|
flexDirection: 'column',
|
|
@@ -1404,7 +1563,7 @@ export default function ChatPanel({
|
|
|
1404
1563
|
<div className="chat-container" ref={chatContainerRef} onScroll={handleChatScroll} style={{ overflowAnchor: 'none' }}>
|
|
1405
1564
|
{hasOlder && onLoadOlder && (
|
|
1406
1565
|
<button type="button" className="load-older-messages" disabled={loadingOlder}
|
|
1407
|
-
style={{ display: 'block', margin: '0 auto 20px', padding: '7px 14px', borderRadius:
|
|
1566
|
+
style={{ display: 'block', margin: '0 auto 20px', padding: '7px 14px', borderRadius: 'var(--radius-md, 10px)',
|
|
1408
1567
|
border: '1px solid var(--border-color, #3a3a3a)', background: 'var(--surface, #252525)',
|
|
1409
1568
|
color: 'var(--text-secondary, #c0c0c0)', cursor: loadingOlder ? 'default' : 'pointer' }}
|
|
1410
1569
|
onClick={async () => {
|
|
@@ -1439,13 +1598,18 @@ export default function ChatPanel({
|
|
|
1439
1598
|
interaction={interaction}
|
|
1440
1599
|
copiedId={copiedId}
|
|
1441
1600
|
onEditMessage={handleEditMessage}
|
|
1601
|
+
onResendFrom={onResendFrom}
|
|
1442
1602
|
speakingText={speakingText}
|
|
1443
1603
|
agentActivitySnapshots={agentActivitySnapshots}
|
|
1444
1604
|
openActivityIds={openActivityIds}
|
|
1605
|
+
thinkingExpanded={thinkingExpanded}
|
|
1606
|
+
onThinkingExpandedChange={onThinkingExpandedChange}
|
|
1445
1607
|
handleCopyMessage={handleCopyMessage}
|
|
1446
1608
|
speak={speak}
|
|
1447
1609
|
toggleActivity={toggleActivity}
|
|
1448
1610
|
chatId={chatId}
|
|
1611
|
+
workspacePath={workspacePath}
|
|
1612
|
+
onReferenceFile={handleReferenceFile}
|
|
1449
1613
|
customProviders={settingsConfig?.customProviders}
|
|
1450
1614
|
onOpenArtifact={openArtifact}
|
|
1451
1615
|
onOpenReview={openAllConversationReview}
|
|
@@ -1519,24 +1683,6 @@ export default function ChatPanel({
|
|
|
1519
1683
|
{workspacePath && <GitBranchSelector workspacePath={workspacePath} disabled={sending} onBranchChanged={onWorkspaceChanged} />}
|
|
1520
1684
|
</div>
|
|
1521
1685
|
)}
|
|
1522
|
-
<div className="smart-context-bar">
|
|
1523
|
-
<label className={`smart-context-option ${agentMode ? 'is-active' : ''}`}>
|
|
1524
|
-
<span className="toggle-switch">
|
|
1525
|
-
<input type="checkbox" checked={agentMode} onChange={(event) => onSetAgentMode(event.target.checked)} />
|
|
1526
|
-
<span className="slider round" />
|
|
1527
|
-
</span>
|
|
1528
|
-
<span className="smart-context-copy"><span className="smart-context-title">Agent mode</span></span>
|
|
1529
|
-
</label>
|
|
1530
|
-
{onSetPlanMode && (
|
|
1531
|
-
<label className={`smart-context-option ${planMode ? 'is-active' : ''}`} title="Investigate read-only and require plan approval before editing files or running commands">
|
|
1532
|
-
<span className="toggle-switch">
|
|
1533
|
-
<input type="checkbox" checked={Boolean(planMode)} onChange={(event) => onSetPlanMode(event.target.checked)} />
|
|
1534
|
-
<span className="slider round" />
|
|
1535
|
-
</span>
|
|
1536
|
-
<span className="smart-context-copy"><span className="smart-context-title">Plan mode</span></span>
|
|
1537
|
-
</label>
|
|
1538
|
-
)}
|
|
1539
|
-
</div>
|
|
1540
1686
|
{queuedPrompts && queuedPrompts.length > 0 && (
|
|
1541
1687
|
<QueuedMessagesBanner
|
|
1542
1688
|
queuedPrompts={queuedPrompts}
|
|
@@ -1559,7 +1705,12 @@ export default function ChatPanel({
|
|
|
1559
1705
|
onSubmit(event)
|
|
1560
1706
|
}}
|
|
1561
1707
|
onDragOver={(event) => {
|
|
1562
|
-
if (
|
|
1708
|
+
if (
|
|
1709
|
+
event.dataTransfer.types.includes(WORKSPACE_PATH_MIME) ||
|
|
1710
|
+
event.dataTransfer.types.includes(WORKSPACE_FILE_MIME) ||
|
|
1711
|
+
event.dataTransfer.types.includes(WORKSPACE_IMAGE_MIME) ||
|
|
1712
|
+
event.dataTransfer.types.includes('application/x-mint-workspace-path')
|
|
1713
|
+
) {
|
|
1563
1714
|
event.preventDefault()
|
|
1564
1715
|
event.dataTransfer.dropEffect = 'copy'
|
|
1565
1716
|
}
|
|
@@ -1664,6 +1815,18 @@ export default function ChatPanel({
|
|
|
1664
1815
|
if (historyIndex !== null) setHistoryIndex(null)
|
|
1665
1816
|
}}
|
|
1666
1817
|
onKeyDown={handleInputKeyDown}
|
|
1818
|
+
onDragOver={(event) => {
|
|
1819
|
+
if (
|
|
1820
|
+
event.dataTransfer.types.includes(WORKSPACE_PATH_MIME) ||
|
|
1821
|
+
event.dataTransfer.types.includes(WORKSPACE_FILE_MIME) ||
|
|
1822
|
+
event.dataTransfer.types.includes(WORKSPACE_IMAGE_MIME) ||
|
|
1823
|
+
event.dataTransfer.types.includes('application/x-mint-workspace-path') ||
|
|
1824
|
+
event.dataTransfer.types.includes('text/plain')
|
|
1825
|
+
) {
|
|
1826
|
+
event.preventDefault()
|
|
1827
|
+
event.dataTransfer.dropEffect = 'copy'
|
|
1828
|
+
}
|
|
1829
|
+
}}
|
|
1667
1830
|
onDrop={handleWorkspaceDrop}
|
|
1668
1831
|
placeholder="Ask anything, @ to mention, / for actions"
|
|
1669
1832
|
rows={1}
|
|
@@ -1752,22 +1915,31 @@ export default function ChatPanel({
|
|
|
1752
1915
|
<path d="M8 22h8M12 18v4M7 10h10M12 5v10" />
|
|
1753
1916
|
</svg>
|
|
1754
1917
|
</button>
|
|
1755
|
-
<
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
onSelectModelAndProvider
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1918
|
+
<div className="chat-composer-pills">
|
|
1919
|
+
<ModelSelectorPopover
|
|
1920
|
+
activeProvider={status?.activeProvider ?? ''}
|
|
1921
|
+
activeModel={activeModel}
|
|
1922
|
+
availableProviders={status?.availableProviders ?? []}
|
|
1923
|
+
settingsConfig={settingsConfig}
|
|
1924
|
+
dynamicOllamaModels={dynamicOllamaModels}
|
|
1925
|
+
onSelect={(provider, model) => {
|
|
1926
|
+
if (onSelectModelAndProvider) {
|
|
1927
|
+
onSelectModelAndProvider(provider, model)
|
|
1928
|
+
} else {
|
|
1929
|
+
onSetProvider(provider)
|
|
1930
|
+
onSetModel(model)
|
|
1931
|
+
}
|
|
1932
|
+
}}
|
|
1933
|
+
onUpdateSettings={onUpdateSettings}
|
|
1934
|
+
/>
|
|
1935
|
+
<ChatModeSelector
|
|
1936
|
+
agentMode={agentMode}
|
|
1937
|
+
onSetAgentMode={onSetAgentMode}
|
|
1938
|
+
planMode={planMode}
|
|
1939
|
+
onSetPlanMode={onSetPlanMode}
|
|
1940
|
+
disabled={sending}
|
|
1941
|
+
/>
|
|
1942
|
+
</div>
|
|
1771
1943
|
<VoicePill
|
|
1772
1944
|
id="voice-input-btn"
|
|
1773
1945
|
className="composer-voice-pill"
|
|
@@ -1887,6 +2059,8 @@ export default function ChatPanel({
|
|
|
1887
2059
|
onEndCall={() => geminiLive.setVoiceMode(false)}
|
|
1888
2060
|
voice={settingsConfig?.geminiLiveVoice || 'Puck'}
|
|
1889
2061
|
voices={GEMINI_LIVE_VOICES}
|
|
2062
|
+
pendingApproval={pendingApproval}
|
|
2063
|
+
onApproval={onApproval}
|
|
1890
2064
|
onChangeVoice={async (voiceName) => {
|
|
1891
2065
|
await onSetGeminiLiveVoice(voiceName)
|
|
1892
2066
|
geminiLive.restart()
|
|
@@ -206,7 +206,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
|
|
|
206
206
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0, flex: 1 }}>
|
|
207
207
|
<div
|
|
208
208
|
className="management-card-icon"
|
|
209
|
-
style={{ width: 34, height: 34, fontSize: '0.72rem', fontWeight: 700, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'color-mix(in srgb, var(--accent) 15%, transparent)', color: 'var(--interactive-fg)', borderRadius: '
|
|
209
|
+
style={{ width: 34, height: 34, fontSize: '0.72rem', fontWeight: 700, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'color-mix(in srgb, var(--accent) 15%, transparent)', color: 'var(--interactive-fg)', borderRadius: 'var(--radius-sm, 8px)' }}
|
|
210
210
|
>
|
|
211
211
|
DIFF
|
|
212
212
|
</div>
|
|
@@ -237,7 +237,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
|
|
|
237
237
|
color: 'var(--interactive-fg)',
|
|
238
238
|
background: 'color-mix(in srgb, var(--accent) 15%, transparent)',
|
|
239
239
|
padding: '1px 6px',
|
|
240
|
-
borderRadius: '
|
|
240
|
+
borderRadius: 'var(--radius-xs, 6px)',
|
|
241
241
|
}}
|
|
242
242
|
>
|
|
243
243
|
+{additions}
|
|
@@ -249,7 +249,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
|
|
|
249
249
|
color: 'var(--status-error-fg)',
|
|
250
250
|
background: 'var(--status-error-bg)',
|
|
251
251
|
padding: '1px 6px',
|
|
252
|
-
borderRadius: '
|
|
252
|
+
borderRadius: 'var(--radius-xs, 6px)',
|
|
253
253
|
}}
|
|
254
254
|
>
|
|
255
255
|
-{deletions}
|
|
@@ -262,7 +262,7 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
|
|
|
262
262
|
color: 'var(--status-warning-fg)',
|
|
263
263
|
background: 'var(--status-warning-bg)',
|
|
264
264
|
padding: '1px 6px',
|
|
265
|
-
borderRadius: '
|
|
265
|
+
borderRadius: 'var(--radius-xs, 6px)',
|
|
266
266
|
textTransform: 'none',
|
|
267
267
|
}}
|
|
268
268
|
>
|
|
@@ -533,9 +533,9 @@ export const DiffReviewModal: React.FC<DiffReviewModalProps> = ({
|
|
|
533
533
|
>
|
|
534
534
|
<div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
|
|
535
535
|
<span>Shortcut: </span>
|
|
536
|
-
<kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius:
|
|
536
|
+
<kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius: 'var(--radius-xs, 6px)', fontSize: '0.72rem' }}>Esc</kbd> Cancel
|
|
537
537
|
<span style={{ margin: '0 8px' }}>•</span>
|
|
538
|
-
<kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius:
|
|
538
|
+
<kbd style={{ background: 'var(--bg-surface-soft)', border: '1px solid var(--border-subtle)', padding: '2px 5px', borderRadius: 'var(--radius-xs, 6px)', fontSize: '0.72rem' }}>Cmd/Ctrl+Enter</kbd> Approve
|
|
539
539
|
</div>
|
|
540
540
|
|
|
541
541
|
<div style={{ display: 'flex', gap: 10 }}>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
|
-
import GeminiLiveOverlay from './GeminiLiveOverlay'
|
|
4
|
+
import GeminiLiveOverlay, { resolveLiveOverlayEscape } from './GeminiLiveOverlay'
|
|
5
|
+
import {
|
|
6
|
+
applyLiveTranscriptFragment,
|
|
7
|
+
downsampleToPcm16,
|
|
8
|
+
routeGeminiLiveSideEffects,
|
|
9
|
+
stopQueuedAudioPlayback,
|
|
10
|
+
} from '../../utils/useGeminiLiveVoice'
|
|
5
11
|
|
|
6
12
|
const props = {
|
|
7
13
|
userTranscript: '', assistantTranscript: '', isPaused: false,
|
|
@@ -21,4 +27,139 @@ describe('Live conversation', () => {
|
|
|
21
27
|
expect(html).toContain('สวัสดี')
|
|
22
28
|
expect(html).toContain('Hello')
|
|
23
29
|
})
|
|
30
|
+
it('stops and disconnects all queued audio sources when interrupted', () => {
|
|
31
|
+
let stopped1 = false, disconnected1 = false
|
|
32
|
+
let stopped2 = false, disconnected2 = false
|
|
33
|
+
const sources = new Set<any>([
|
|
34
|
+
{ stop() { stopped1 = true }, disconnect() { disconnected1 = true } },
|
|
35
|
+
{ stop() { stopped2 = true }, disconnect() { disconnected2 = true } },
|
|
36
|
+
])
|
|
37
|
+
const nextPlaybackTimeRef = { current: 12.5 }
|
|
38
|
+
const speakingTimeoutRef = { current: null as number | null }
|
|
39
|
+
let speaking = true
|
|
40
|
+
|
|
41
|
+
stopQueuedAudioPlayback(sources, nextPlaybackTimeRef, speakingTimeoutRef, (val) => { speaking = val })
|
|
42
|
+
|
|
43
|
+
expect(stopped1).toBe(true)
|
|
44
|
+
expect(disconnected1).toBe(true)
|
|
45
|
+
expect(stopped2).toBe(true)
|
|
46
|
+
expect(disconnected2).toBe(true)
|
|
47
|
+
expect(sources.size).toBe(0)
|
|
48
|
+
expect(nextPlaybackTimeRef.current).toBe(0)
|
|
49
|
+
expect(speaking).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
it('renders an inline ApprovalCard inside the Live overlay when a voice tool requests approval', () => {
|
|
52
|
+
const html = renderToStaticMarkup(
|
|
53
|
+
<GeminiLiveOverlay
|
|
54
|
+
{...props}
|
|
55
|
+
status="thinking"
|
|
56
|
+
pendingApproval={{
|
|
57
|
+
token: 'tok-1',
|
|
58
|
+
approval: {
|
|
59
|
+
RunShell: {
|
|
60
|
+
command: 'cargo test --workspace',
|
|
61
|
+
mode: 'workspace-write',
|
|
62
|
+
background: false,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}}
|
|
66
|
+
onApproval={() => {}}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
expect(html).toContain('cargo test --workspace')
|
|
70
|
+
expect(html).toContain('Allow once')
|
|
71
|
+
expect(html).toContain('Reject')
|
|
72
|
+
})
|
|
73
|
+
it('routes turn persistence, tool activity, and approval requests from Gemini Live events', () => {
|
|
74
|
+
let turnsSaved = 0
|
|
75
|
+
let toolActivities = 0
|
|
76
|
+
const approvals: Array<{ token: string; approval: Record<string, unknown> }> = []
|
|
77
|
+
const callbacks = {
|
|
78
|
+
onTurnSaved: () => { turnsSaved += 1 },
|
|
79
|
+
onToolActivity: () => { toolActivities += 1 },
|
|
80
|
+
onApprovalRequested: (payload: { token: string; approval: Record<string, unknown> }) => {
|
|
81
|
+
approvals.push(payload)
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
routeGeminiLiveSideEffects({ type: 'turnComplete' }, callbacks)
|
|
86
|
+
expect(turnsSaved).toBe(1)
|
|
87
|
+
|
|
88
|
+
routeGeminiLiveSideEffects({ type: 'interrupted' }, callbacks)
|
|
89
|
+
expect(turnsSaved).toBe(2)
|
|
90
|
+
|
|
91
|
+
routeGeminiLiveSideEffects({ type: 'closed' }, callbacks)
|
|
92
|
+
expect(turnsSaved).toBe(3)
|
|
93
|
+
|
|
94
|
+
routeGeminiLiveSideEffects(
|
|
95
|
+
{ type: 'toolStatus', action: 'read_file', input: { path: 'README.md' }, result: 'ok' },
|
|
96
|
+
callbacks,
|
|
97
|
+
)
|
|
98
|
+
expect(toolActivities).toBe(1)
|
|
99
|
+
|
|
100
|
+
routeGeminiLiveSideEffects(
|
|
101
|
+
{
|
|
102
|
+
type: 'approvalRequested',
|
|
103
|
+
token: 'tok-live-99',
|
|
104
|
+
approval: { RunShell: { command: 'ls', mode: 'read-only', background: false } },
|
|
105
|
+
},
|
|
106
|
+
callbacks,
|
|
107
|
+
)
|
|
108
|
+
expect(approvals).toEqual([
|
|
109
|
+
{
|
|
110
|
+
token: 'tok-live-99',
|
|
111
|
+
approval: { RunShell: { command: 'ls', mode: 'read-only', background: false } },
|
|
112
|
+
},
|
|
113
|
+
])
|
|
114
|
+
})
|
|
115
|
+
it('downsamples mic audio to 16kHz PCM16 with boxcar anti-aliasing instead of nearest-neighbor aliasing', () => {
|
|
116
|
+
// 1. Direct 16kHz -> 16kHz conversion with clamping
|
|
117
|
+
const direct = downsampleToPcm16(new Float32Array([0, 0.5, -0.5, 1.5, -1.5]), 16000, 16000)
|
|
118
|
+
expect(Array.from(direct)).toEqual([0, 16384, -16384, 32767, -32768])
|
|
119
|
+
|
|
120
|
+
// 2. 48kHz -> 16kHz (ratio = 3): high-frequency alternating [1.0, -1.0, 0.0] averages to 0
|
|
121
|
+
// (nearest-neighbor would alias to +32767!), while steady [0.5, 0.5, 0.5] averages to 16384.
|
|
122
|
+
const antiAliased = downsampleToPcm16(
|
|
123
|
+
new Float32Array([1.0, -1.0, 0.0, 0.5, 0.5, 0.5]),
|
|
124
|
+
48000,
|
|
125
|
+
16000,
|
|
126
|
+
)
|
|
127
|
+
expect(Array.from(antiAliased)).toEqual([0, 16384])
|
|
128
|
+
|
|
129
|
+
// 3. Fractional 44.1kHz -> 16kHz (ratio = 2.75625) preserves DC level accurately
|
|
130
|
+
const dc44k = new Float32Array(441).fill(0.25)
|
|
131
|
+
const resampled = downsampleToPcm16(dc44k, 44100, 16000)
|
|
132
|
+
expect(resampled.length).toBe(160)
|
|
133
|
+
expect(resampled.every((sample) => Math.abs(sample - 8192) <= 1)).toBe(true)
|
|
134
|
+
})
|
|
135
|
+
it('accumulates interleaved user and assistant transcript fragments within a turn without wiping either line', () => {
|
|
136
|
+
const state = {
|
|
137
|
+
userTranscript: '',
|
|
138
|
+
assistantTranscript: '',
|
|
139
|
+
userFresh: true,
|
|
140
|
+
assistantFresh: true,
|
|
141
|
+
}
|
|
142
|
+
// User starts speaking, assistant starts replying while final user fragment arrives
|
|
143
|
+
applyLiveTranscriptFragment(state, 'user', 'ช่วยดู ')
|
|
144
|
+
applyLiveTranscriptFragment(state, 'assistant', 'ได้ครับ ')
|
|
145
|
+
applyLiveTranscriptFragment(state, 'user', 'ไฟล์ README หน่อย')
|
|
146
|
+
applyLiveTranscriptFragment(state, 'assistant', 'กำลังอ่านไฟล์ให้ครับ')
|
|
147
|
+
|
|
148
|
+
expect(state.userTranscript).toBe('ช่วยดู ไฟล์ README หน่อย')
|
|
149
|
+
expect(state.assistantTranscript).toBe('ได้ครับ กำลังอ่านไฟล์ให้ครับ')
|
|
150
|
+
|
|
151
|
+
// After turnComplete (resetting fresh flags), the next turn starts clean lines
|
|
152
|
+
state.userFresh = true
|
|
153
|
+
state.assistantFresh = true
|
|
154
|
+
applyLiveTranscriptFragment(state, 'user', 'ขอบคุณครับ')
|
|
155
|
+
expect(state.userTranscript).toBe('ขอบคุณครับ')
|
|
156
|
+
expect(state.assistantTranscript).toBe('ได้ครับ กำลังอ่านไฟล์ให้ครับ')
|
|
157
|
+
})
|
|
158
|
+
it('guards Escape key in GeminiLiveOverlay when voice picker or pending approval is active', () => {
|
|
159
|
+
expect(resolveLiveOverlayEscape({ voicePickerOpen: true, hasPendingApproval: false })).toBe('close_voice_picker')
|
|
160
|
+
expect(resolveLiveOverlayEscape({ voicePickerOpen: false, hasPendingApproval: true })).toBe('ignore')
|
|
161
|
+
expect(resolveLiveOverlayEscape({ voicePickerOpen: false, hasPendingApproval: false })).toBe('end_call')
|
|
162
|
+
})
|
|
24
163
|
})
|
|
164
|
+
|
|
165
|
+
|