@zythum02/fello-server 0.1.55 → 0.1.57
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/out/renderer/assets/PopoverPopup-BcHY-oda.js +591 -0
- package/out/renderer/assets/PopoverPopup-BcHY-oda.js.br +0 -0
- package/out/renderer/assets/PopoverPopup-BcHY-oda.js.gz +0 -0
- package/out/renderer/assets/{_baseUniq-BdLqGxOi.js → _baseUniq-KD2XGr9F.js} +1 -1
- package/out/renderer/assets/_baseUniq-KD2XGr9F.js.br +0 -0
- package/out/renderer/assets/_baseUniq-KD2XGr9F.js.gz +0 -0
- package/out/renderer/assets/{arc-UpL8po5R.js → arc-CR7wDdln.js} +1 -1
- package/out/renderer/assets/arc-CR7wDdln.js.br +0 -0
- package/out/renderer/assets/arc-CR7wDdln.js.gz +0 -0
- package/out/renderer/assets/{architectureDiagram-Q4EWVU46-ManEkEbl.js → architectureDiagram-Q4EWVU46-ob7rXg_N.js} +6 -6
- package/out/renderer/assets/architectureDiagram-Q4EWVU46-ob7rXg_N.js.br +0 -0
- package/out/renderer/assets/architectureDiagram-Q4EWVU46-ob7rXg_N.js.gz +0 -0
- package/out/renderer/assets/{arrow-left-BnuWerui.js → arrow-left-Ck_ZVJr5.js} +1 -1
- package/out/renderer/assets/{arrow-up-quD46bYg.js → arrow-up-D7cOrlYE.js} +4 -15
- package/out/renderer/assets/{automation-cVl19jbl.js → automation-W30eYgU8.js} +36 -13
- package/out/renderer/assets/automation-W30eYgU8.js.br +0 -0
- package/out/renderer/assets/automation-W30eYgU8.js.gz +0 -0
- package/out/renderer/assets/{blockDiagram-DXYQGD6D-B7n8X8F6.js → blockDiagram-DXYQGD6D-DH0dgWHF.js} +7 -7
- package/out/renderer/assets/blockDiagram-DXYQGD6D-DH0dgWHF.js.br +0 -0
- package/out/renderer/assets/blockDiagram-DXYQGD6D-DH0dgWHF.js.gz +0 -0
- package/out/renderer/assets/{c4Diagram-AHTNJAMY-BoP_JNY0.js → c4Diagram-AHTNJAMY--55RlH_-.js} +3 -3
- package/out/renderer/assets/c4Diagram-AHTNJAMY--55RlH_-.js.br +0 -0
- package/out/renderer/assets/c4Diagram-AHTNJAMY--55RlH_-.js.gz +0 -0
- package/out/renderer/assets/{channel-BTAU8ahK.js → channel-Bz679Wzq.js} +1 -1
- package/out/renderer/assets/{chunk-4BX2VUAB-BCKz1EgZ.js → chunk-4BX2VUAB-0jn0O1rt.js} +1 -1
- package/out/renderer/assets/{chunk-4TB4RGXK-B2HhnMqB.js → chunk-4TB4RGXK-BI1TetiG.js} +5 -5
- package/out/renderer/assets/chunk-4TB4RGXK-BI1TetiG.js.br +0 -0
- package/out/renderer/assets/chunk-4TB4RGXK-BI1TetiG.js.gz +0 -0
- package/out/renderer/assets/{chunk-55IACEB6-Drurz_4k.js → chunk-55IACEB6-9LfmpuHC.js} +1 -1
- package/out/renderer/assets/{chunk-EDXVE4YY-Xfghcg7b.js → chunk-EDXVE4YY-CtHVLcvG.js} +1 -1
- package/out/renderer/assets/chunk-EDXVE4YY-CtHVLcvG.js.br +0 -0
- package/out/renderer/assets/chunk-EDXVE4YY-CtHVLcvG.js.gz +0 -0
- package/out/renderer/assets/{chunk-FMBD7UC4-Birgz8kz.js → chunk-FMBD7UC4-eUJaG2CL.js} +1 -1
- package/out/renderer/assets/{chunk-OYMX7WX6-Cv4jgShd.js → chunk-OYMX7WX6-D-sPmxYW.js} +3 -3
- package/out/renderer/assets/chunk-OYMX7WX6-D-sPmxYW.js.br +0 -0
- package/out/renderer/assets/chunk-OYMX7WX6-D-sPmxYW.js.gz +0 -0
- package/out/renderer/assets/{chunk-QZHKN3VN-DkqKjktH.js → chunk-QZHKN3VN-Pdv-mSqK.js} +1 -1
- package/out/renderer/assets/{chunk-YZCP3GAM-CJK_tBTq.js → chunk-YZCP3GAM-DLpllNY2.js} +1 -1
- package/out/renderer/assets/chunk-YZCP3GAM-DLpllNY2.js.br +0 -0
- package/out/renderer/assets/chunk-YZCP3GAM-DLpllNY2.js.gz +0 -0
- package/out/renderer/assets/{circle-x-C9_Yp0Tg.js → circle-x-Cs_T6g0B.js} +1 -1
- package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-Bf4To0VI.js → classDiagram-6PBFFD2Q-BQx6mUfF.js} +7 -7
- package/out/renderer/assets/{classDiagram-6PBFFD2Q-Bf4To0VI.js → classDiagram-v2-HSJHXN6E-BQx6mUfF.js} +7 -7
- package/out/renderer/assets/{clone-Ci5J50Pn.js → clone-DsYDv_WX.js} +1 -1
- package/out/renderer/assets/{code-editor-BwPwET3m.js → code-editor-Ce-8PAj3.js} +2 -2
- package/out/renderer/assets/code-editor-Ce-8PAj3.js.br +0 -0
- package/out/renderer/assets/code-editor-Ce-8PAj3.js.gz +0 -0
- package/out/renderer/assets/{collapsible-CMQ8h8ID.js → collapsible-CKgHeKAs.js} +1 -1
- package/out/renderer/assets/collapsible-CKgHeKAs.js.br +0 -0
- package/out/renderer/assets/collapsible-CKgHeKAs.js.gz +0 -0
- package/out/renderer/assets/{command-nKlwdb6i.js → command-Cgkdasyl.js} +1 -1
- package/out/renderer/assets/{cose-bilkent-S5V4N54A-BZpcn2q9.js → cose-bilkent-S5V4N54A-hNCr9Ibx.js} +2 -2
- package/out/renderer/assets/cose-bilkent-S5V4N54A-hNCr9Ibx.js.br +0 -0
- package/out/renderer/assets/cose-bilkent-S5V4N54A-hNCr9Ibx.js.gz +0 -0
- package/out/renderer/assets/{dagre-KV5264BT-onHZodsn.js → dagre-KV5264BT-CGyBht8M.js} +7 -7
- package/out/renderer/assets/dagre-KV5264BT-CGyBht8M.js.br +0 -0
- package/out/renderer/assets/dagre-KV5264BT-CGyBht8M.js.gz +0 -0
- package/out/renderer/assets/{diagram-5BDNPKRD-y3QVYwp7.js → diagram-5BDNPKRD-BiZfiCzA.js} +7 -7
- package/out/renderer/assets/diagram-5BDNPKRD-BiZfiCzA.js.br +0 -0
- package/out/renderer/assets/diagram-5BDNPKRD-BiZfiCzA.js.gz +0 -0
- package/out/renderer/assets/{diagram-G4DWMVQ6-LB3RUjrJ.js → diagram-G4DWMVQ6-CWj4c_xE.js} +7 -7
- package/out/renderer/assets/diagram-G4DWMVQ6-CWj4c_xE.js.br +0 -0
- package/out/renderer/assets/diagram-G4DWMVQ6-CWj4c_xE.js.gz +0 -0
- package/out/renderer/assets/{diagram-MMDJMWI5-7NpY26IL.js → diagram-MMDJMWI5-iBnXAZDJ.js} +6 -6
- package/out/renderer/assets/diagram-MMDJMWI5-iBnXAZDJ.js.br +0 -0
- package/out/renderer/assets/diagram-MMDJMWI5-iBnXAZDJ.js.gz +0 -0
- package/out/renderer/assets/{diagram-TYMM5635-DRM1RRr4.js → diagram-TYMM5635-C7IGtL42.js} +6 -6
- package/out/renderer/assets/diagram-TYMM5635-C7IGtL42.js.br +0 -0
- package/out/renderer/assets/diagram-TYMM5635-C7IGtL42.js.gz +0 -0
- package/out/renderer/assets/{ellipsis-D_eCkauJ.js → ellipsis-DSui0Rae.js} +1 -1
- package/out/renderer/assets/{erDiagram-SMLLAGMA-DNIZ639C.js → erDiagram-SMLLAGMA-ChPUl_x9.js} +5 -5
- package/out/renderer/assets/erDiagram-SMLLAGMA-ChPUl_x9.js.br +0 -0
- package/out/renderer/assets/erDiagram-SMLLAGMA-ChPUl_x9.js.gz +0 -0
- package/out/renderer/assets/external-link-CoW4VuI3.js +15 -0
- package/out/renderer/assets/{field-YZTi9Jxc.js → field-CXofwfDF.js} +1 -1
- package/out/renderer/assets/field-CXofwfDF.js.br +0 -0
- package/out/renderer/assets/field-CXofwfDF.js.gz +0 -0
- package/out/renderer/assets/{file-text-DDtS1g4Q.js → file-text-CwFSQGtt.js} +1 -1
- package/out/renderer/assets/{flowDiagram-DWJPFMVM-C0ghMibN.js → flowDiagram-DWJPFMVM-C5Zt4Sfn.js} +7 -7
- package/out/renderer/assets/flowDiagram-DWJPFMVM-C5Zt4Sfn.js.br +0 -0
- package/out/renderer/assets/flowDiagram-DWJPFMVM-C5Zt4Sfn.js.gz +0 -0
- package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-tqeHqMu4.js → ganttDiagram-T4ZO3ILL-Cp30RG1g.js} +3 -3
- package/out/renderer/assets/ganttDiagram-T4ZO3ILL-Cp30RG1g.js.br +0 -0
- package/out/renderer/assets/ganttDiagram-T4ZO3ILL-Cp30RG1g.js.gz +0 -0
- package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-D6acFhfE.js → gitGraphDiagram-UUTBAWPF-CkfzOUvM.js} +7 -7
- package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-CkfzOUvM.js.br +0 -0
- package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-CkfzOUvM.js.gz +0 -0
- package/out/renderer/assets/{graph-DqFGMaSz.js → graph-HfEt9-p2.js} +2 -2
- package/out/renderer/assets/graph-HfEt9-p2.js.br +0 -0
- package/out/renderer/assets/graph-HfEt9-p2.js.gz +0 -0
- package/out/renderer/assets/{highlighted-body-KPVGNVTW-D2IZ7Enb.js → highlighted-body-KPVGNVTW-CUokS6r6.js} +2 -2
- package/out/renderer/assets/index-Byirxhp5.css +382 -0
- package/out/renderer/assets/index-Byirxhp5.css.br +0 -0
- package/out/renderer/assets/index-Byirxhp5.css.gz +0 -0
- package/out/renderer/assets/{index-BeEZXfJZ.js → index-CKDwIiIb.js} +2277 -923
- package/out/renderer/assets/index-CKDwIiIb.js.br +0 -0
- package/out/renderer/assets/index-CKDwIiIb.js.gz +0 -0
- package/out/renderer/assets/{infoDiagram-42DDH7IO-BJeydI3V.js → infoDiagram-42DDH7IO-DbQvToBf.js} +5 -5
- package/out/renderer/assets/infoDiagram-42DDH7IO-DbQvToBf.js.br +0 -0
- package/out/renderer/assets/infoDiagram-42DDH7IO-DbQvToBf.js.gz +0 -0
- package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-DPgToD8D.js → ishikawaDiagram-UXIWVN3A-Crmx2UcL.js} +2 -2
- package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-Crmx2UcL.js.br +0 -0
- package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-Crmx2UcL.js.gz +0 -0
- package/out/renderer/assets/{item-SQsw2AyN.js → item-2gdt65LZ.js} +1 -1
- package/out/renderer/assets/item-2gdt65LZ.js.br +0 -0
- package/out/renderer/assets/item-2gdt65LZ.js.gz +0 -0
- package/out/renderer/assets/{journeyDiagram-VCZTEJTY-CAnZXd__.js → journeyDiagram-VCZTEJTY-BD3MzoDm.js} +5 -5
- package/out/renderer/assets/journeyDiagram-VCZTEJTY-BD3MzoDm.js.br +0 -0
- package/out/renderer/assets/journeyDiagram-VCZTEJTY-BD3MzoDm.js.gz +0 -0
- package/out/renderer/assets/{kanban-definition-6JOO6SKY-VCfgwCTi.js → kanban-definition-6JOO6SKY-DlPWdcq8.js} +3 -3
- package/out/renderer/assets/kanban-definition-6JOO6SKY-DlPWdcq8.js.br +0 -0
- package/out/renderer/assets/kanban-definition-6JOO6SKY-DlPWdcq8.js.gz +0 -0
- package/out/renderer/assets/keyboard-BmncEbVT.js +21 -0
- package/out/renderer/assets/{layout-lw2KhCMB.js → layout-OHsAY-70.js} +4 -4
- package/out/renderer/assets/layout-OHsAY-70.js.br +0 -0
- package/out/renderer/assets/layout-OHsAY-70.js.gz +0 -0
- package/out/renderer/assets/{linear-DmbMR7a-.js → linear-CnBTAk2r.js} +1 -1
- package/out/renderer/assets/linear-CnBTAk2r.js.br +0 -0
- package/out/renderer/assets/linear-CnBTAk2r.js.gz +0 -0
- package/out/renderer/assets/{link-2-CWpGKoWR.js → link-2-CABYiITN.js} +3 -15
- package/out/renderer/assets/{mermaid-HWGCJPDP-BufLuIVq.js → mermaid-HWGCJPDP-OB5tfzeR.js} +58 -54
- package/out/renderer/assets/mermaid-HWGCJPDP-OB5tfzeR.js.br +0 -0
- package/out/renderer/assets/mermaid-HWGCJPDP-OB5tfzeR.js.gz +0 -0
- package/out/renderer/assets/{mic-UZHvCQhw.js → mic-Bu7eaTKt.js} +1 -1
- package/out/renderer/assets/{min-BCqStlR1.js → min-lM3sC06g.js} +2 -2
- package/out/renderer/assets/min-lM3sC06g.js.br +0 -0
- package/out/renderer/assets/min-lM3sC06g.js.gz +0 -0
- package/out/renderer/assets/{mindmap-definition-QFDTVHPH-C0dBO3f9.js → mindmap-definition-QFDTVHPH-2KHTVjBe.js} +4 -4
- package/out/renderer/assets/mindmap-definition-QFDTVHPH-2KHTVjBe.js.br +0 -0
- package/out/renderer/assets/mindmap-definition-QFDTVHPH-2KHTVjBe.js.gz +0 -0
- package/out/renderer/assets/{open-guide-CWoE2W97.js → open-guide-CKryRC5n.js} +1 -1
- package/out/renderer/assets/{package-search-SZPcR_1L.js → package-search-DPG91yZB.js} +1 -1
- package/out/renderer/assets/{pieDiagram-DEJITSTG-Dv_tH6Z_.js → pieDiagram-DEJITSTG-CWRoGvfQ.js} +7 -7
- package/out/renderer/assets/pieDiagram-DEJITSTG-CWRoGvfQ.js.br +0 -0
- package/out/renderer/assets/pieDiagram-DEJITSTG-CWRoGvfQ.js.gz +0 -0
- package/out/renderer/assets/{plus-BZ0wwKkh.js → plus-CFoa8VhI.js} +1 -1
- package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-Cvsj5cyu.js → quadrantDiagram-34T5L4WZ-BJVP6FPm.js} +3 -3
- package/out/renderer/assets/quadrantDiagram-34T5L4WZ-BJVP6FPm.js.br +0 -0
- package/out/renderer/assets/quadrantDiagram-34T5L4WZ-BJVP6FPm.js.gz +0 -0
- package/out/renderer/assets/{requirementDiagram-MS252O5E-CxM7H308.js → requirementDiagram-MS252O5E-_MTj_l0V.js} +4 -4
- package/out/renderer/assets/requirementDiagram-MS252O5E-_MTj_l0V.js.br +0 -0
- package/out/renderer/assets/requirementDiagram-MS252O5E-_MTj_l0V.js.gz +0 -0
- package/out/renderer/assets/{resizable-DD8ADhVy.js → resizable-CcCcCDAN.js} +1 -1
- package/out/renderer/assets/resizable-CcCcCDAN.js.br +0 -0
- package/out/renderer/assets/resizable-CcCcCDAN.js.gz +0 -0
- package/out/renderer/assets/{sankeyDiagram-XADWPNL6-BGTOA6ee.js → sankeyDiagram-XADWPNL6-BWkr3adU.js} +2 -2
- package/out/renderer/assets/sankeyDiagram-XADWPNL6-BWkr3adU.js.br +0 -0
- package/out/renderer/assets/sankeyDiagram-XADWPNL6-BWkr3adU.js.gz +0 -0
- package/out/renderer/assets/{schedule-DMyyz9A1.js → schedule-H1BEvD9Q.js} +24 -11
- package/out/renderer/assets/schedule-H1BEvD9Q.js.br +0 -0
- package/out/renderer/assets/schedule-H1BEvD9Q.js.gz +0 -0
- package/out/renderer/assets/{search-XexOgLud.js → search-B3aFhEKs.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-FGHM5R23-DezlXo1X.js → sequenceDiagram-FGHM5R23-CfRs3_Dx.js} +4 -4
- package/out/renderer/assets/sequenceDiagram-FGHM5R23-CfRs3_Dx.js.br +0 -0
- package/out/renderer/assets/sequenceDiagram-FGHM5R23-CfRs3_Dx.js.gz +0 -0
- package/out/renderer/assets/{session-DQEqHTKb.js → session-D6NE3yNE.js} +2327 -2791
- package/out/renderer/assets/session-D6NE3yNE.js.br +0 -0
- package/out/renderer/assets/session-D6NE3yNE.js.gz +0 -0
- package/out/renderer/assets/setting-dialog-CANlzubW.js +5086 -0
- package/out/renderer/assets/setting-dialog-CANlzubW.js.br +0 -0
- package/out/renderer/assets/setting-dialog-CANlzubW.js.gz +0 -0
- package/out/renderer/assets/{settings-agents-f3HkdiTw.js → settings-agents-DxIwcY7M.js} +12 -12
- package/out/renderer/assets/settings-agents-DxIwcY7M.js.br +0 -0
- package/out/renderer/assets/settings-agents-DxIwcY7M.js.gz +0 -0
- package/out/renderer/assets/settings-general-B85uTAij.js +624 -0
- package/out/renderer/assets/settings-general-B85uTAij.js.br +0 -0
- package/out/renderer/assets/settings-general-B85uTAij.js.gz +0 -0
- package/out/renderer/assets/{settings-ilink-Da336Koc.js → settings-ilink-BgeA04Nv.js} +51 -20
- package/out/renderer/assets/settings-ilink-BgeA04Nv.js.br +0 -0
- package/out/renderer/assets/settings-ilink-BgeA04Nv.js.gz +0 -0
- package/out/renderer/assets/{settings-image-generation-RmWs73ZG.js → settings-image-generation-CsWv4wXY.js} +6 -6
- package/out/renderer/assets/settings-image-generation-CsWv4wXY.js.br +0 -0
- package/out/renderer/assets/settings-image-generation-CsWv4wXY.js.gz +0 -0
- package/out/renderer/assets/{settings-layout-DL6ZEzgP.js → settings-layout-DJbT4rIH.js} +32 -7
- package/out/renderer/assets/settings-layout-DJbT4rIH.js.br +0 -0
- package/out/renderer/assets/settings-layout-DJbT4rIH.js.gz +0 -0
- package/out/renderer/assets/{settings-mcp-BKW8JKtO.js → settings-mcp-Cz6GMogp.js} +97 -29
- package/out/renderer/assets/settings-mcp-Cz6GMogp.js.br +0 -0
- package/out/renderer/assets/settings-mcp-Cz6GMogp.js.gz +0 -0
- package/out/renderer/assets/{settings-memory-o4r-_vob.js → settings-memory-Cidr_Ghw.js} +2 -2
- package/out/renderer/assets/settings-memory-Cidr_Ghw.js.br +0 -0
- package/out/renderer/assets/settings-memory-Cidr_Ghw.js.gz +0 -0
- package/out/renderer/assets/settings-peripherals-DilwOpyO.js +269 -0
- package/out/renderer/assets/settings-peripherals-DilwOpyO.js.br +0 -0
- package/out/renderer/assets/settings-peripherals-DilwOpyO.js.gz +0 -0
- package/out/renderer/assets/{settings-shortcuts-BmUGS1q_.js → settings-shortcuts-dWdRCFxB.js} +16 -6
- package/out/renderer/assets/settings-shortcuts-dWdRCFxB.js.br +0 -0
- package/out/renderer/assets/settings-shortcuts-dWdRCFxB.js.gz +0 -0
- package/out/renderer/assets/{settings-snippets-CpAOJmDF.js → settings-snippets-CrtJYwLV.js} +9 -9
- package/out/renderer/assets/settings-snippets-CrtJYwLV.js.br +0 -0
- package/out/renderer/assets/settings-snippets-CrtJYwLV.js.gz +0 -0
- package/out/renderer/assets/settings-speech-CteE8F4J.js +1369 -0
- package/out/renderer/assets/settings-speech-CteE8F4J.js.br +0 -0
- package/out/renderer/assets/settings-speech-CteE8F4J.js.gz +0 -0
- package/out/renderer/assets/{settings-webui-DIEKvTrI.js → settings-webui-DKiOzDSD.js} +3 -3
- package/out/renderer/assets/settings-webui-DKiOzDSD.js.br +0 -0
- package/out/renderer/assets/settings-webui-DKiOzDSD.js.gz +0 -0
- package/out/renderer/assets/{skills-installed-BuWBDk6r.js → skills-installed-Beqe9G7p.js} +4 -4
- package/out/renderer/assets/skills-installed-Beqe9G7p.js.br +0 -0
- package/out/renderer/assets/skills-installed-Beqe9G7p.js.gz +0 -0
- package/out/renderer/assets/{skills-layout-ByPhfHG6.js → skills-layout-BAzfcnkT.js} +2 -2
- package/out/renderer/assets/skills-layout-BAzfcnkT.js.br +0 -0
- package/out/renderer/assets/skills-layout-BAzfcnkT.js.gz +0 -0
- package/out/renderer/assets/{skills-skills-sh-wR7YNmAJ.js → skills-skills-sh-Dzocqfu4.js} +4 -4
- package/out/renderer/assets/skills-skills-sh-Dzocqfu4.js.br +0 -0
- package/out/renderer/assets/skills-skills-sh-Dzocqfu4.js.gz +0 -0
- package/out/renderer/assets/{settings-general-BcCOVmx3.js → slider-DEK94SlL.js} +4 -623
- package/out/renderer/assets/slider-DEK94SlL.js.br +0 -0
- package/out/renderer/assets/slider-DEK94SlL.js.gz +0 -0
- package/out/renderer/assets/{sortable.esm-BWvT9HIK.js → sortable.esm-Cm4rqFU7.js} +1 -1
- package/out/renderer/assets/sortable.esm-Cm4rqFU7.js.br +0 -0
- package/out/renderer/assets/sortable.esm-Cm4rqFU7.js.gz +0 -0
- package/out/renderer/assets/{standard-schema-BkTVIf7N.js → standard-schema-B43ZsT5c.js} +52 -1
- package/out/renderer/assets/standard-schema-B43ZsT5c.js.br +0 -0
- package/out/renderer/assets/standard-schema-B43ZsT5c.js.gz +0 -0
- package/out/renderer/assets/{stateDiagram-FHFEXIEX-xdKKCs-p.js → stateDiagram-FHFEXIEX-CaiRC_NA.js} +9 -9
- package/out/renderer/assets/stateDiagram-FHFEXIEX-CaiRC_NA.js.br +0 -0
- package/out/renderer/assets/stateDiagram-FHFEXIEX-CaiRC_NA.js.gz +0 -0
- package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-BaaiQ6Kr.js → stateDiagram-v2-QKLJ7IA2-EA3ViVW-.js} +5 -5
- package/out/renderer/assets/{task-PHAOSE-W.js → task-DM-bWOFz.js} +13 -11
- package/out/renderer/assets/task-DM-bWOFz.js.br +0 -0
- package/out/renderer/assets/task-DM-bWOFz.js.gz +0 -0
- package/out/renderer/assets/{textarea-1ZWl3C-x.js → textarea-B0OAhnF7.js} +1 -1
- package/out/renderer/assets/{timeline-definition-GMOUNBTQ-DFLeLybV.js → timeline-definition-GMOUNBTQ-D-M9BAZz.js} +3 -3
- package/out/renderer/assets/timeline-definition-GMOUNBTQ-D-M9BAZz.js.br +0 -0
- package/out/renderer/assets/timeline-definition-GMOUNBTQ-D-M9BAZz.js.gz +0 -0
- package/out/renderer/assets/{vennDiagram-DHZGUBPP-PKmZLY7H.js → vennDiagram-DHZGUBPP-B_yjg_0U.js} +2 -2
- package/out/renderer/assets/vennDiagram-DHZGUBPP-B_yjg_0U.js.br +0 -0
- package/out/renderer/assets/vennDiagram-DHZGUBPP-B_yjg_0U.js.gz +0 -0
- package/out/renderer/assets/{wardley-RL74JXVD-BCH2vKnv.js → wardley-RL74JXVD-2Xd7XlwA.js} +4 -4
- package/out/renderer/assets/wardley-RL74JXVD-2Xd7XlwA.js.br +0 -0
- package/out/renderer/assets/wardley-RL74JXVD-2Xd7XlwA.js.gz +0 -0
- package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-Bkd3xMno.js → wardleyDiagram-NUSXRM2D-BA9tzMk-.js} +6 -6
- package/out/renderer/assets/wardleyDiagram-NUSXRM2D-BA9tzMk-.js.br +0 -0
- package/out/renderer/assets/wardleyDiagram-NUSXRM2D-BA9tzMk-.js.gz +0 -0
- package/out/renderer/assets/{wrench-8H0LZ83G.js → wrench-QfEqqx9F.js} +1 -1
- package/out/renderer/assets/wrench-QfEqqx9F.js.br +0 -0
- package/out/renderer/assets/wrench-QfEqqx9F.js.gz +0 -0
- package/out/renderer/assets/{xlsx-view-BYx5Ha2P.js → xlsx-view-Bov2NXsI.js} +141 -79
- package/out/renderer/assets/xlsx-view-Bov2NXsI.js.br +0 -0
- package/out/renderer/assets/xlsx-view-Bov2NXsI.js.gz +0 -0
- package/out/renderer/assets/{xychartDiagram-5P7HB3ND-DCOm4uPr.js → xychartDiagram-5P7HB3ND-svs-11is.js} +3 -3
- package/out/renderer/assets/xychartDiagram-5P7HB3ND-svs-11is.js.br +0 -0
- package/out/renderer/assets/xychartDiagram-5P7HB3ND-svs-11is.js.gz +0 -0
- package/out/renderer/index.html +2 -2
- package/out/renderer/index.html.br +0 -0
- package/out/renderer/index.html.gz +0 -0
- package/out/scripts/mcp-toolbox/server.mjs +80 -1
- package/out/server/main.js +1576 -353
- package/package.json +3 -2
- package/out/renderer/assets/_baseUniq-BdLqGxOi.js.br +0 -0
- package/out/renderer/assets/_baseUniq-BdLqGxOi.js.gz +0 -0
- package/out/renderer/assets/arc-UpL8po5R.js.br +0 -0
- package/out/renderer/assets/arc-UpL8po5R.js.gz +0 -0
- package/out/renderer/assets/architectureDiagram-Q4EWVU46-ManEkEbl.js.br +0 -0
- package/out/renderer/assets/architectureDiagram-Q4EWVU46-ManEkEbl.js.gz +0 -0
- package/out/renderer/assets/automation-cVl19jbl.js.br +0 -0
- package/out/renderer/assets/automation-cVl19jbl.js.gz +0 -0
- package/out/renderer/assets/blockDiagram-DXYQGD6D-B7n8X8F6.js.br +0 -0
- package/out/renderer/assets/blockDiagram-DXYQGD6D-B7n8X8F6.js.gz +0 -0
- package/out/renderer/assets/c4Diagram-AHTNJAMY-BoP_JNY0.js.br +0 -0
- package/out/renderer/assets/c4Diagram-AHTNJAMY-BoP_JNY0.js.gz +0 -0
- package/out/renderer/assets/chunk-4TB4RGXK-B2HhnMqB.js.br +0 -0
- package/out/renderer/assets/chunk-4TB4RGXK-B2HhnMqB.js.gz +0 -0
- package/out/renderer/assets/chunk-EDXVE4YY-Xfghcg7b.js.br +0 -0
- package/out/renderer/assets/chunk-EDXVE4YY-Xfghcg7b.js.gz +0 -0
- package/out/renderer/assets/chunk-OYMX7WX6-Cv4jgShd.js.br +0 -0
- package/out/renderer/assets/chunk-OYMX7WX6-Cv4jgShd.js.gz +0 -0
- package/out/renderer/assets/chunk-YZCP3GAM-CJK_tBTq.js.br +0 -0
- package/out/renderer/assets/chunk-YZCP3GAM-CJK_tBTq.js.gz +0 -0
- package/out/renderer/assets/code-editor-BwPwET3m.js.br +0 -0
- package/out/renderer/assets/code-editor-BwPwET3m.js.gz +0 -0
- package/out/renderer/assets/collapsible-CMQ8h8ID.js.br +0 -0
- package/out/renderer/assets/collapsible-CMQ8h8ID.js.gz +0 -0
- package/out/renderer/assets/cose-bilkent-S5V4N54A-BZpcn2q9.js.br +0 -0
- package/out/renderer/assets/cose-bilkent-S5V4N54A-BZpcn2q9.js.gz +0 -0
- package/out/renderer/assets/dagre-KV5264BT-onHZodsn.js.br +0 -0
- package/out/renderer/assets/dagre-KV5264BT-onHZodsn.js.gz +0 -0
- package/out/renderer/assets/diagram-5BDNPKRD-y3QVYwp7.js.br +0 -0
- package/out/renderer/assets/diagram-5BDNPKRD-y3QVYwp7.js.gz +0 -0
- package/out/renderer/assets/diagram-G4DWMVQ6-LB3RUjrJ.js.br +0 -0
- package/out/renderer/assets/diagram-G4DWMVQ6-LB3RUjrJ.js.gz +0 -0
- package/out/renderer/assets/diagram-MMDJMWI5-7NpY26IL.js.br +0 -0
- package/out/renderer/assets/diagram-MMDJMWI5-7NpY26IL.js.gz +0 -0
- package/out/renderer/assets/diagram-TYMM5635-DRM1RRr4.js.br +0 -0
- package/out/renderer/assets/diagram-TYMM5635-DRM1RRr4.js.gz +0 -0
- package/out/renderer/assets/erDiagram-SMLLAGMA-DNIZ639C.js.br +0 -0
- package/out/renderer/assets/erDiagram-SMLLAGMA-DNIZ639C.js.gz +0 -0
- package/out/renderer/assets/field-YZTi9Jxc.js.br +0 -0
- package/out/renderer/assets/field-YZTi9Jxc.js.gz +0 -0
- package/out/renderer/assets/flowDiagram-DWJPFMVM-C0ghMibN.js.br +0 -0
- package/out/renderer/assets/flowDiagram-DWJPFMVM-C0ghMibN.js.gz +0 -0
- package/out/renderer/assets/ganttDiagram-T4ZO3ILL-tqeHqMu4.js.br +0 -0
- package/out/renderer/assets/ganttDiagram-T4ZO3ILL-tqeHqMu4.js.gz +0 -0
- package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-D6acFhfE.js.br +0 -0
- package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-D6acFhfE.js.gz +0 -0
- package/out/renderer/assets/graph-DqFGMaSz.js.br +0 -0
- package/out/renderer/assets/graph-DqFGMaSz.js.gz +0 -0
- package/out/renderer/assets/index-BZTYKZe8.css +0 -382
- package/out/renderer/assets/index-BZTYKZe8.css.br +0 -0
- package/out/renderer/assets/index-BZTYKZe8.css.gz +0 -0
- package/out/renderer/assets/index-BeEZXfJZ.js.br +0 -0
- package/out/renderer/assets/index-BeEZXfJZ.js.gz +0 -0
- package/out/renderer/assets/infoDiagram-42DDH7IO-BJeydI3V.js.br +0 -0
- package/out/renderer/assets/infoDiagram-42DDH7IO-BJeydI3V.js.gz +0 -0
- package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-DPgToD8D.js.br +0 -0
- package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-DPgToD8D.js.gz +0 -0
- package/out/renderer/assets/item-SQsw2AyN.js.br +0 -0
- package/out/renderer/assets/item-SQsw2AyN.js.gz +0 -0
- package/out/renderer/assets/journeyDiagram-VCZTEJTY-CAnZXd__.js.br +0 -0
- package/out/renderer/assets/journeyDiagram-VCZTEJTY-CAnZXd__.js.gz +0 -0
- package/out/renderer/assets/kanban-definition-6JOO6SKY-VCfgwCTi.js.br +0 -0
- package/out/renderer/assets/kanban-definition-6JOO6SKY-VCfgwCTi.js.gz +0 -0
- package/out/renderer/assets/layout-lw2KhCMB.js.br +0 -0
- package/out/renderer/assets/layout-lw2KhCMB.js.gz +0 -0
- package/out/renderer/assets/linear-DmbMR7a-.js.br +0 -0
- package/out/renderer/assets/linear-DmbMR7a-.js.gz +0 -0
- package/out/renderer/assets/link-2-CWpGKoWR.js.br +0 -0
- package/out/renderer/assets/link-2-CWpGKoWR.js.gz +0 -0
- package/out/renderer/assets/mermaid-HWGCJPDP-BufLuIVq.js.br +0 -0
- package/out/renderer/assets/mermaid-HWGCJPDP-BufLuIVq.js.gz +0 -0
- package/out/renderer/assets/min-BCqStlR1.js.br +0 -0
- package/out/renderer/assets/min-BCqStlR1.js.gz +0 -0
- package/out/renderer/assets/mindmap-definition-QFDTVHPH-C0dBO3f9.js.br +0 -0
- package/out/renderer/assets/mindmap-definition-QFDTVHPH-C0dBO3f9.js.gz +0 -0
- package/out/renderer/assets/pieDiagram-DEJITSTG-Dv_tH6Z_.js.br +0 -0
- package/out/renderer/assets/pieDiagram-DEJITSTG-Dv_tH6Z_.js.gz +0 -0
- package/out/renderer/assets/quadrantDiagram-34T5L4WZ-Cvsj5cyu.js.br +0 -0
- package/out/renderer/assets/quadrantDiagram-34T5L4WZ-Cvsj5cyu.js.gz +0 -0
- package/out/renderer/assets/requirementDiagram-MS252O5E-CxM7H308.js.br +0 -0
- package/out/renderer/assets/requirementDiagram-MS252O5E-CxM7H308.js.gz +0 -0
- package/out/renderer/assets/resizable-DD8ADhVy.js.br +0 -0
- package/out/renderer/assets/resizable-DD8ADhVy.js.gz +0 -0
- package/out/renderer/assets/sankeyDiagram-XADWPNL6-BGTOA6ee.js.br +0 -0
- package/out/renderer/assets/sankeyDiagram-XADWPNL6-BGTOA6ee.js.gz +0 -0
- package/out/renderer/assets/schedule-DMyyz9A1.js.br +0 -0
- package/out/renderer/assets/schedule-DMyyz9A1.js.gz +0 -0
- package/out/renderer/assets/sequenceDiagram-FGHM5R23-DezlXo1X.js.br +0 -0
- package/out/renderer/assets/sequenceDiagram-FGHM5R23-DezlXo1X.js.gz +0 -0
- package/out/renderer/assets/session-DQEqHTKb.js.br +0 -0
- package/out/renderer/assets/session-DQEqHTKb.js.gz +0 -0
- package/out/renderer/assets/setting-dialog-BdiZbd8J.js +0 -642
- package/out/renderer/assets/setting-dialog-BdiZbd8J.js.br +0 -0
- package/out/renderer/assets/setting-dialog-BdiZbd8J.js.gz +0 -0
- package/out/renderer/assets/settings-2-q22Cv4G7.js +0 -16
- package/out/renderer/assets/settings-agents-f3HkdiTw.js.br +0 -0
- package/out/renderer/assets/settings-agents-f3HkdiTw.js.gz +0 -0
- package/out/renderer/assets/settings-general-BcCOVmx3.js.br +0 -0
- package/out/renderer/assets/settings-general-BcCOVmx3.js.gz +0 -0
- package/out/renderer/assets/settings-ilink-Da336Koc.js.br +0 -0
- package/out/renderer/assets/settings-ilink-Da336Koc.js.gz +0 -0
- package/out/renderer/assets/settings-image-generation-RmWs73ZG.js.br +0 -0
- package/out/renderer/assets/settings-image-generation-RmWs73ZG.js.gz +0 -0
- package/out/renderer/assets/settings-layout-DL6ZEzgP.js.br +0 -0
- package/out/renderer/assets/settings-layout-DL6ZEzgP.js.gz +0 -0
- package/out/renderer/assets/settings-mcp-BKW8JKtO.js.br +0 -0
- package/out/renderer/assets/settings-mcp-BKW8JKtO.js.gz +0 -0
- package/out/renderer/assets/settings-memory-o4r-_vob.js.br +0 -0
- package/out/renderer/assets/settings-memory-o4r-_vob.js.gz +0 -0
- package/out/renderer/assets/settings-shortcuts-BmUGS1q_.js.br +0 -0
- package/out/renderer/assets/settings-shortcuts-BmUGS1q_.js.gz +0 -0
- package/out/renderer/assets/settings-snippets-CpAOJmDF.js.br +0 -0
- package/out/renderer/assets/settings-snippets-CpAOJmDF.js.gz +0 -0
- package/out/renderer/assets/settings-speech-to-text-C8KHNGE4.js +0 -1054
- package/out/renderer/assets/settings-speech-to-text-C8KHNGE4.js.br +0 -0
- package/out/renderer/assets/settings-speech-to-text-C8KHNGE4.js.gz +0 -0
- package/out/renderer/assets/settings-webui-DIEKvTrI.js.br +0 -0
- package/out/renderer/assets/settings-webui-DIEKvTrI.js.gz +0 -0
- package/out/renderer/assets/skills-installed-BuWBDk6r.js.br +0 -0
- package/out/renderer/assets/skills-installed-BuWBDk6r.js.gz +0 -0
- package/out/renderer/assets/skills-layout-ByPhfHG6.js.br +0 -0
- package/out/renderer/assets/skills-layout-ByPhfHG6.js.gz +0 -0
- package/out/renderer/assets/skills-skills-sh-wR7YNmAJ.js.br +0 -0
- package/out/renderer/assets/skills-skills-sh-wR7YNmAJ.js.gz +0 -0
- package/out/renderer/assets/sortable.esm-BWvT9HIK.js.br +0 -0
- package/out/renderer/assets/sortable.esm-BWvT9HIK.js.gz +0 -0
- package/out/renderer/assets/standard-schema-BkTVIf7N.js.br +0 -0
- package/out/renderer/assets/standard-schema-BkTVIf7N.js.gz +0 -0
- package/out/renderer/assets/stateDiagram-FHFEXIEX-xdKKCs-p.js.br +0 -0
- package/out/renderer/assets/stateDiagram-FHFEXIEX-xdKKCs-p.js.gz +0 -0
- package/out/renderer/assets/task-PHAOSE-W.js.br +0 -0
- package/out/renderer/assets/task-PHAOSE-W.js.gz +0 -0
- package/out/renderer/assets/timeline-definition-GMOUNBTQ-DFLeLybV.js.br +0 -0
- package/out/renderer/assets/timeline-definition-GMOUNBTQ-DFLeLybV.js.gz +0 -0
- package/out/renderer/assets/vennDiagram-DHZGUBPP-PKmZLY7H.js.br +0 -0
- package/out/renderer/assets/vennDiagram-DHZGUBPP-PKmZLY7H.js.gz +0 -0
- package/out/renderer/assets/wardley-RL74JXVD-BCH2vKnv.js.br +0 -0
- package/out/renderer/assets/wardley-RL74JXVD-BCH2vKnv.js.gz +0 -0
- package/out/renderer/assets/wardleyDiagram-NUSXRM2D-Bkd3xMno.js.br +0 -0
- package/out/renderer/assets/wardleyDiagram-NUSXRM2D-Bkd3xMno.js.gz +0 -0
- package/out/renderer/assets/wrench-8H0LZ83G.js.br +0 -0
- package/out/renderer/assets/wrench-8H0LZ83G.js.gz +0 -0
- package/out/renderer/assets/xlsx-view-BYx5Ha2P.js.br +0 -0
- package/out/renderer/assets/xlsx-view-BYx5Ha2P.js.gz +0 -0
- package/out/renderer/assets/xychartDiagram-5P7HB3ND-DCOm4uPr.js.br +0 -0
- package/out/renderer/assets/xychartDiagram-5P7HB3ND-DCOm4uPr.js.gz +0 -0
package/out/server/main.js
CHANGED
|
@@ -11,7 +11,7 @@ process.treeSitterWasmPath = join(resourcesPath, "tree-sitter-wasm");
|
|
|
11
11
|
|
|
12
12
|
// src/backend/backend.ts
|
|
13
13
|
import { readdir as readdir2, rm as rm3, readFile as readFile10 } from "fs/promises";
|
|
14
|
-
import { join as
|
|
14
|
+
import { join as join26 } from "path";
|
|
15
15
|
|
|
16
16
|
// src/backend/storage/index.ts
|
|
17
17
|
import { join as join5 } from "path";
|
|
@@ -64,7 +64,8 @@ var DEFAULT_SETTINGS = {
|
|
|
64
64
|
shortcuts: {},
|
|
65
65
|
proxy: {
|
|
66
66
|
mode: "off"
|
|
67
|
-
}
|
|
67
|
+
},
|
|
68
|
+
peripherals: []
|
|
68
69
|
};
|
|
69
70
|
function settingsPath() {
|
|
70
71
|
return join3(FELLO_DIR, "settings.json");
|
|
@@ -252,24 +253,51 @@ function readSettings() {
|
|
|
252
253
|
model: p.model,
|
|
253
254
|
active: typeof p.active === "boolean" ? p.active : false
|
|
254
255
|
})) : [];
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
const speechProviders = (() => {
|
|
257
|
+
const isSpeechProvider = (value) => {
|
|
258
|
+
const provider = value.provider;
|
|
259
|
+
return typeof value.id === "string" && typeof value.name === "string" && typeof value.apiKey === "string" && (provider === "volcengine" || provider === "dashscope" || provider === "openai" || provider === "iflytek");
|
|
260
|
+
};
|
|
261
|
+
const parseShared = (value) => ({
|
|
262
|
+
id: value.id,
|
|
263
|
+
name: value.name,
|
|
264
|
+
provider: value.provider,
|
|
265
|
+
apiKey: value.apiKey,
|
|
266
|
+
appId: typeof value.appId === "string" ? value.appId : void 0,
|
|
267
|
+
apiSecret: typeof value.apiSecret === "string" ? value.apiSecret : void 0,
|
|
268
|
+
baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : void 0,
|
|
269
|
+
workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : void 0,
|
|
270
|
+
region: value.region === "cn-beijing" || value.region === "ap-southeast-1" ? value.region : void 0,
|
|
271
|
+
workspace: typeof value.workspace === "string" ? value.workspace : void 0,
|
|
272
|
+
language: typeof value.language === "string" ? value.language : void 0
|
|
273
|
+
});
|
|
274
|
+
const rows = (input) => Array.isArray(input) ? input.filter((value) => isObject(value)).filter(isSpeechProvider) : [];
|
|
275
|
+
if (Array.isArray(rawObj?.speechProviders)) {
|
|
276
|
+
return rows(rawObj.speechProviders).map((value) => ({
|
|
277
|
+
...parseShared(value),
|
|
278
|
+
asrModel: typeof value.asrModel === "string" ? value.asrModel : void 0,
|
|
279
|
+
asrResourceId: typeof value.asrResourceId === "string" ? value.asrResourceId : void 0,
|
|
280
|
+
ttsModel: typeof value.ttsModel === "string" ? value.ttsModel : void 0,
|
|
281
|
+
voice: typeof value.voice === "string" ? value.voice : void 0,
|
|
282
|
+
asrEnabled: typeof value.asrEnabled === "boolean" ? value.asrEnabled : false,
|
|
283
|
+
ttsEnabled: typeof value.ttsEnabled === "boolean" ? value.ttsEnabled : false
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
return rows(rawObj?.speechToText).map((value) => ({
|
|
287
|
+
...parseShared(value),
|
|
288
|
+
asrModel: typeof value.model === "string" ? value.model : void 0,
|
|
289
|
+
asrResourceId: typeof value.resourceId === "string" ? value.resourceId : void 0,
|
|
290
|
+
ttsModel: void 0,
|
|
291
|
+
voice: void 0,
|
|
292
|
+
asrEnabled: typeof value.active === "boolean" ? value.active : false,
|
|
293
|
+
ttsEnabled: false
|
|
294
|
+
}));
|
|
295
|
+
})();
|
|
296
|
+
const peripherals = Array.isArray(rawObj?.peripherals) ? rawObj.peripherals.filter(
|
|
297
|
+
(value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
298
|
+
).filter((value) => typeof value.id === "string").map((value) => ({
|
|
259
299
|
id: value.id,
|
|
260
|
-
|
|
261
|
-
provider: value.provider,
|
|
262
|
-
apiKey: value.apiKey,
|
|
263
|
-
appId: typeof value.appId === "string" ? value.appId : void 0,
|
|
264
|
-
apiSecret: typeof value.apiSecret === "string" ? value.apiSecret : void 0,
|
|
265
|
-
resourceId: typeof value.resourceId === "string" ? value.resourceId : void 0,
|
|
266
|
-
model: typeof value.model === "string" ? value.model : void 0,
|
|
267
|
-
baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : void 0,
|
|
268
|
-
workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : void 0,
|
|
269
|
-
region: value.region === "cn-beijing" || value.region === "ap-southeast-1" ? value.region : void 0,
|
|
270
|
-
workspace: typeof value.workspace === "string" ? value.workspace : void 0,
|
|
271
|
-
language: typeof value.language === "string" ? value.language : void 0,
|
|
272
|
-
active: typeof value.active === "boolean" ? value.active : false
|
|
300
|
+
enabled: typeof value.enabled === "boolean" ? value.enabled : false
|
|
273
301
|
})) : [];
|
|
274
302
|
const editor = (() => {
|
|
275
303
|
const raw2 = rawObj && isObject(rawObj.editor) ? rawObj.editor : null;
|
|
@@ -292,7 +320,8 @@ function readSettings() {
|
|
|
292
320
|
proxy,
|
|
293
321
|
snippets,
|
|
294
322
|
imageGeneration,
|
|
295
|
-
|
|
323
|
+
speechProviders,
|
|
324
|
+
peripherals
|
|
296
325
|
};
|
|
297
326
|
} catch {
|
|
298
327
|
return DEFAULT_SETTINGS;
|
|
@@ -404,21 +433,28 @@ function getSettings() {
|
|
|
404
433
|
model: p.model,
|
|
405
434
|
active: p.active
|
|
406
435
|
})),
|
|
407
|
-
|
|
436
|
+
speechProviders: (meta.speechProviders ?? []).map((p) => ({
|
|
408
437
|
id: p.id,
|
|
409
438
|
name: p.name,
|
|
410
439
|
provider: p.provider,
|
|
411
440
|
apiKey: p.apiKey,
|
|
412
441
|
appId: p.appId,
|
|
413
442
|
apiSecret: p.apiSecret,
|
|
414
|
-
resourceId: p.resourceId,
|
|
415
|
-
model: p.model,
|
|
416
443
|
baseUrl: p.baseUrl,
|
|
417
444
|
workspaceId: p.workspaceId,
|
|
418
445
|
region: p.region,
|
|
419
446
|
workspace: p.workspace,
|
|
420
447
|
language: p.language,
|
|
421
|
-
|
|
448
|
+
asrModel: p.asrModel,
|
|
449
|
+
asrResourceId: p.asrResourceId,
|
|
450
|
+
ttsModel: p.ttsModel,
|
|
451
|
+
voice: p.voice,
|
|
452
|
+
asrEnabled: p.asrEnabled,
|
|
453
|
+
ttsEnabled: p.ttsEnabled
|
|
454
|
+
})),
|
|
455
|
+
peripherals: (meta.peripherals ?? []).map((p) => ({
|
|
456
|
+
id: p.id,
|
|
457
|
+
enabled: p.enabled
|
|
422
458
|
}))
|
|
423
459
|
};
|
|
424
460
|
}
|
|
@@ -589,22 +625,29 @@ function updateSettings(settings) {
|
|
|
589
625
|
model: p.model,
|
|
590
626
|
active: p.active
|
|
591
627
|
})) : prevMeta.imageGeneration,
|
|
592
|
-
|
|
628
|
+
speechProviders: settings.speechProviders ? settings.speechProviders.map((p) => ({
|
|
593
629
|
id: p.id,
|
|
594
630
|
name: p.name,
|
|
595
631
|
provider: p.provider,
|
|
596
632
|
apiKey: p.apiKey,
|
|
597
633
|
appId: p.appId?.trim() || void 0,
|
|
598
634
|
apiSecret: p.apiSecret?.trim() || void 0,
|
|
599
|
-
resourceId: p.resourceId?.trim() || void 0,
|
|
600
|
-
model: p.model?.trim() || void 0,
|
|
601
635
|
baseUrl: p.baseUrl?.trim() || void 0,
|
|
602
636
|
workspaceId: p.workspaceId?.trim() || void 0,
|
|
603
637
|
region: p.region,
|
|
604
638
|
workspace: p.workspace?.trim() || void 0,
|
|
605
639
|
language: p.language?.trim() || void 0,
|
|
606
|
-
|
|
607
|
-
|
|
640
|
+
asrModel: p.asrModel?.trim() || void 0,
|
|
641
|
+
asrResourceId: p.asrResourceId?.trim() || void 0,
|
|
642
|
+
ttsModel: p.ttsModel?.trim() || void 0,
|
|
643
|
+
voice: p.voice?.trim() || void 0,
|
|
644
|
+
asrEnabled: p.asrEnabled,
|
|
645
|
+
ttsEnabled: p.ttsEnabled
|
|
646
|
+
})) : prevMeta.speechProviders ?? [],
|
|
647
|
+
peripherals: settings.peripherals ? settings.peripherals.map((p) => ({
|
|
648
|
+
id: p.id,
|
|
649
|
+
enabled: p.enabled
|
|
650
|
+
})) : prevMeta.peripherals ?? []
|
|
608
651
|
};
|
|
609
652
|
writeSettings(meta);
|
|
610
653
|
}
|
|
@@ -1086,26 +1129,24 @@ var en_default = {
|
|
|
1086
1129
|
promptRefusal: "\u26A0\uFE0F Model refused to respond",
|
|
1087
1130
|
promptCancelled: "\u26A0\uFE0F Generation was cancelled",
|
|
1088
1131
|
noActiveSession: "No active session",
|
|
1089
|
-
title: "Title",
|
|
1090
|
-
cwd: "Working Directory",
|
|
1091
1132
|
projectDir: "Project Directory",
|
|
1092
1133
|
agent: "Agent",
|
|
1093
1134
|
features: "Features",
|
|
1094
1135
|
mcpServers: "MCP Servers",
|
|
1095
1136
|
project: "Project",
|
|
1096
|
-
sessionInfo: "Session Info",
|
|
1097
1137
|
sessionList: "Switch Session",
|
|
1098
1138
|
sessionListDesc: "Please choose from the following sessions:",
|
|
1099
1139
|
newSessionDesc: "Please choose from the following projects:",
|
|
1100
1140
|
noSessions: "No sessions available.",
|
|
1101
1141
|
switchSessionHint: "Reply with a number to switch session",
|
|
1102
1142
|
switchSessionGuide: "\u{1F4A1} Send !s to switch session",
|
|
1103
|
-
|
|
1143
|
+
invalidMenuNumber: "Invalid number, please enter a number between {{min}}-{{max}}.",
|
|
1144
|
+
unknownCommand: "\u2753 Unknown command. Send ! to see the current session status and command list.",
|
|
1104
1145
|
switchedToSession: "Switched to \u300C{{label}}\u300D",
|
|
1105
1146
|
newSession: "New Chat",
|
|
1106
1147
|
newSessionTitle: "New Session",
|
|
1107
1148
|
noProjects: "No projects available.",
|
|
1108
|
-
createSessionHint: "Reply with a number to create a new session
|
|
1149
|
+
createSessionHint: "Reply with a number to create a new session",
|
|
1109
1150
|
createSessionGuide: "\u{1F4A1} Send !n to create a new session",
|
|
1110
1151
|
noAgent: "No available agent. Please configure one in settings.",
|
|
1111
1152
|
createdAndSwitched: "\u2705 Switched to new session in project\u300C{{project}}\u300D",
|
|
@@ -1120,7 +1161,32 @@ var en_default = {
|
|
|
1120
1161
|
snippetListDesc: "Please choose a quick phrase:",
|
|
1121
1162
|
noSnippets: "No quick phrases available. Please add some in settings.",
|
|
1122
1163
|
selectSnippetHint: "Reply with a number to send the quick phrase",
|
|
1123
|
-
snippetSent: "\u2705 Quick phrase\u300C{{title}}\u300Dhas been sent"
|
|
1164
|
+
snippetSent: "\u2705 Quick phrase\u300C{{title}}\u300Dhas been sent",
|
|
1165
|
+
featuresMenu: "Feature Switches",
|
|
1166
|
+
featuresMenuDesc: "Toggle the features below (applied immediately):",
|
|
1167
|
+
mcpMenu: "MCP Server Switches",
|
|
1168
|
+
mcpMenuDesc: "Toggle the MCP servers below (applied immediately):",
|
|
1169
|
+
toggleHint: "Reply with a number to toggle that item; send 0 to exit the menu",
|
|
1170
|
+
featureGuide: "\u{1F4A1} Send !f to toggle features",
|
|
1171
|
+
mcpServerGuide: "\u{1F4A1} Send !c to toggle MCP servers",
|
|
1172
|
+
noToggleItems: "Nothing to toggle here.",
|
|
1173
|
+
toggleCancelled: "Exited, nothing changed.",
|
|
1174
|
+
toggledOn: "\u2705 Enabled",
|
|
1175
|
+
toggledOff: "\u2705 Disabled",
|
|
1176
|
+
toggleSavedRestartFailed: "{{label}}, but restarting the session failed. The change will apply on the next resume.",
|
|
1177
|
+
permissionMenu: "Permission",
|
|
1178
|
+
permissionMenuDesc: "Please choose a permission mode:",
|
|
1179
|
+
permissionAsk: "Ask",
|
|
1180
|
+
permissionAllowAll: "Allow all",
|
|
1181
|
+
switchPermissionHint: "Reply with a number to switch permission mode",
|
|
1182
|
+
permissionGuide: "\u{1F4A1} Send !p to switch permission",
|
|
1183
|
+
permissionSwitched: "\u2705 Switched to {{mode}}",
|
|
1184
|
+
featureSkills: "Skills",
|
|
1185
|
+
featureSearch: "Search",
|
|
1186
|
+
featureImageGeneration: "Image Generation",
|
|
1187
|
+
featureMemory: "Memory",
|
|
1188
|
+
featureAskUser: "Ask Me",
|
|
1189
|
+
featureShareToUser: "Share to User"
|
|
1124
1190
|
}
|
|
1125
1191
|
};
|
|
1126
1192
|
|
|
@@ -1143,26 +1209,24 @@ var zh_CN_default = {
|
|
|
1143
1209
|
promptRefusal: "\u26A0\uFE0F \u6A21\u578B\u62D2\u7EDD\u56DE\u7B54",
|
|
1144
1210
|
promptCancelled: "\u26A0\uFE0F \u751F\u6210\u5DF2\u53D6\u6D88",
|
|
1145
1211
|
noActiveSession: "\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u4F1A\u8BDD",
|
|
1146
|
-
title: "\u6807\u9898",
|
|
1147
|
-
cwd: "\u5DE5\u4F5C\u76EE\u5F55",
|
|
1148
1212
|
projectDir: "\u9879\u76EE\u76EE\u5F55",
|
|
1149
1213
|
agent: "Agent",
|
|
1150
1214
|
features: "\u529F\u80FD",
|
|
1151
1215
|
mcpServers: "MCP \u670D\u52A1\u5668",
|
|
1152
1216
|
project: "\u9879\u76EE",
|
|
1153
|
-
sessionInfo: "\u5F53\u524D\u4F1A\u8BDD",
|
|
1154
1217
|
sessionList: "\u5207\u6362\u4F1A\u8BDD",
|
|
1155
1218
|
sessionListDesc: "\u8BF7\u5728\u4E0B\u5217\u4F1A\u8BDD\u4E2D\u9009\u62E9\uFF1A",
|
|
1156
1219
|
newSessionDesc: "\u8BF7\u5728\u4E0B\u5217\u9879\u76EE\u4E2D\u9009\u62E9\uFF1A",
|
|
1157
1220
|
noSessions: "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u4F1A\u8BDD\u3002",
|
|
1158
1221
|
switchSessionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u5207\u6362\u4F1A\u8BDD",
|
|
1159
1222
|
switchSessionGuide: "\u{1F4A1} \u53D1\u9001 !s \u5207\u6362\u4F1A\u8BDD",
|
|
1160
|
-
|
|
1223
|
+
invalidMenuNumber: "\u65E0\u6548\u5E8F\u53F7\uFF0C\u8BF7\u8F93\u5165 {{min}}-{{max}} \u4E4B\u95F4\u7684\u6570\u5B57\u3002",
|
|
1224
|
+
unknownCommand: "\u2753 \u672A\u77E5\u547D\u4EE4\u3002\u53D1\u9001 ! \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001\u4E0E\u547D\u4EE4\u5217\u8868\u3002",
|
|
1161
1225
|
switchedToSession: "\u5DF2\u5207\u6362\u5230\u300C{{label}}\u300D",
|
|
1162
1226
|
newSession: "\u65B0\u4F1A\u8BDD",
|
|
1163
1227
|
newSessionTitle: "\u65B0\u5EFA\u4F1A\u8BDD",
|
|
1164
1228
|
noProjects: "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u9879\u76EE\u3002",
|
|
1165
|
-
createSessionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u521B\u5EFA\u65B0\u4F1A\u8BDD
|
|
1229
|
+
createSessionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u521B\u5EFA\u65B0\u4F1A\u8BDD",
|
|
1166
1230
|
createSessionGuide: "\u{1F4A1} \u53D1\u9001 !n \u65B0\u5EFA\u4F1A\u8BDD",
|
|
1167
1231
|
noAgent: "\u6CA1\u6709\u53EF\u7528\u7684 Agent\uFF0C\u8BF7\u5148\u5728\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u3002",
|
|
1168
1232
|
createdAndSwitched: "\u2705 \u5DF2\u5207\u6362\u5230\u9879\u76EE\u300C{{project}}\u300D\u7684\u65B0\u4F1A\u8BDD",
|
|
@@ -1177,7 +1241,32 @@ var zh_CN_default = {
|
|
|
1177
1241
|
snippetListDesc: "\u8BF7\u5728\u4E0B\u5217\u5FEB\u6377\u77ED\u8BED\u4E2D\u9009\u62E9\uFF1A",
|
|
1178
1242
|
noSnippets: "\u5F53\u524D\u6CA1\u6709\u5FEB\u6377\u77ED\u8BED\uFF0C\u8BF7\u5148\u5728\u8BBE\u7F6E\u4E2D\u6DFB\u52A0\u3002",
|
|
1179
1243
|
selectSnippetHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u53D1\u9001\u5FEB\u6377\u77ED\u8BED",
|
|
1180
|
-
snippetSent: "\u2705 \u5FEB\u6377\u77ED\u8BED\u300C{{title}}\u300D\u5DF2\u53D1\u9001"
|
|
1244
|
+
snippetSent: "\u2705 \u5FEB\u6377\u77ED\u8BED\u300C{{title}}\u300D\u5DF2\u53D1\u9001",
|
|
1245
|
+
featuresMenu: "\u529F\u80FD\u5F00\u5173",
|
|
1246
|
+
featuresMenuDesc: "\u8BF7\u5728\u4E0B\u5217\u529F\u80FD\u4E2D\u5207\u6362\uFF08\u7ACB\u5373\u751F\u6548\uFF09\uFF1A",
|
|
1247
|
+
mcpMenu: "MCP \u670D\u52A1\u5668\u5F00\u5173",
|
|
1248
|
+
mcpMenuDesc: "\u8BF7\u5728\u4E0B\u5217 MCP \u670D\u52A1\u5668\u4E2D\u5207\u6362\uFF08\u7ACB\u5373\u751F\u6548\uFF09\uFF1A",
|
|
1249
|
+
toggleHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u5207\u6362\u8BE5\u9879\uFF0C\u53D1\u9001 0 \u9000\u51FA\u83DC\u5355",
|
|
1250
|
+
featureGuide: "\u{1F4A1} \u53D1\u9001 !f \u5207\u6362\u529F\u80FD",
|
|
1251
|
+
mcpServerGuide: "\u{1F4A1} \u53D1\u9001 !c \u5207\u6362 MCP \u670D\u52A1\u5668",
|
|
1252
|
+
noToggleItems: "\u5F53\u524D\u6CA1\u6709\u53EF\u5207\u6362\u7684\u9879\u3002",
|
|
1253
|
+
toggleCancelled: "\u5DF2\u9000\u51FA\uFF0C\u672A\u505A\u4FEE\u6539\u3002",
|
|
1254
|
+
toggledOn: "\u2705 \u5DF2\u5F00\u542F",
|
|
1255
|
+
toggledOff: "\u2705 \u5DF2\u5173\u95ED",
|
|
1256
|
+
toggleSavedRestartFailed: "{{label}}\uFF0C\u4F46\u4F1A\u8BDD\u91CD\u542F\u5931\u8D25\uFF0C\u6539\u52A8\u5C06\u5728\u4E0B\u6B21\u6062\u590D\u4F1A\u8BDD\u65F6\u751F\u6548\u3002",
|
|
1257
|
+
permissionMenu: "\u6743\u9650",
|
|
1258
|
+
permissionMenuDesc: "\u8BF7\u5728\u4E0B\u5217\u6743\u9650\u6A21\u5F0F\u4E2D\u9009\u62E9\uFF1A",
|
|
1259
|
+
permissionAsk: "\u6BCF\u6B21\u8BE2\u95EE",
|
|
1260
|
+
permissionAllowAll: "\u5168\u90E8\u5141\u8BB8",
|
|
1261
|
+
switchPermissionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u5207\u6362\u6743\u9650\u6A21\u5F0F",
|
|
1262
|
+
permissionGuide: "\u{1F4A1} \u53D1\u9001 !p \u5207\u6362\u6743\u9650",
|
|
1263
|
+
permissionSwitched: "\u2705 \u5DF2\u5207\u6362\u4E3A {{mode}}",
|
|
1264
|
+
featureSkills: "\u6280\u80FD",
|
|
1265
|
+
featureSearch: "\u9879\u76EE\u68C0\u7D22",
|
|
1266
|
+
featureImageGeneration: "\u56FE\u7247\u751F\u6210",
|
|
1267
|
+
featureMemory: "\u8BB0\u5FC6",
|
|
1268
|
+
featureAskUser: "\u5411\u6211\u63D0\u95EE",
|
|
1269
|
+
featureShareToUser: "\u5206\u4EAB\u7ED9\u7528\u6237"
|
|
1181
1270
|
}
|
|
1182
1271
|
};
|
|
1183
1272
|
|
|
@@ -1366,10 +1455,35 @@ import { join as join8 } from "path";
|
|
|
1366
1455
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1367
1456
|
|
|
1368
1457
|
// src/backend/serve-file.ts
|
|
1458
|
+
import { createReadStream } from "fs";
|
|
1369
1459
|
import { stat, readFile as readFile2 } from "fs/promises";
|
|
1370
1460
|
import { resolve, relative, isAbsolute } from "path";
|
|
1371
1461
|
import * as mimeTypes from "mime-types";
|
|
1372
|
-
|
|
1462
|
+
var RANGE_RE = /^bytes=(\d*)-(\d*)$/;
|
|
1463
|
+
function parseRange(header, size) {
|
|
1464
|
+
const raw = header?.trim();
|
|
1465
|
+
if (!raw) return null;
|
|
1466
|
+
const match = RANGE_RE.exec(raw);
|
|
1467
|
+
if (!match) return null;
|
|
1468
|
+
const [, startRaw = "", endRaw = ""] = match;
|
|
1469
|
+
if (!startRaw && !endRaw) return null;
|
|
1470
|
+
let start;
|
|
1471
|
+
let end;
|
|
1472
|
+
if (!startRaw) {
|
|
1473
|
+
const suffix = Number(endRaw);
|
|
1474
|
+
if (!Number.isFinite(suffix) || suffix <= 0) return null;
|
|
1475
|
+
start = Math.max(0, size - suffix);
|
|
1476
|
+
end = size - 1;
|
|
1477
|
+
} else {
|
|
1478
|
+
start = Number(startRaw);
|
|
1479
|
+
end = endRaw ? Math.min(Number(endRaw), size - 1) : size - 1;
|
|
1480
|
+
}
|
|
1481
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
|
|
1482
|
+
if (start >= size) return "unsatisfiable";
|
|
1483
|
+
if (end < start) return null;
|
|
1484
|
+
return { start, end };
|
|
1485
|
+
}
|
|
1486
|
+
async function serveFile(filename, cwd, options = {}) {
|
|
1373
1487
|
const safeCwd = resolve(cwd);
|
|
1374
1488
|
const fullPath = resolve(safeCwd, filename || "");
|
|
1375
1489
|
const rel = relative(safeCwd, fullPath);
|
|
@@ -1413,13 +1527,38 @@ async function serveFile(filename, cwd) {
|
|
|
1413
1527
|
error: `Not a file: ${filename}`
|
|
1414
1528
|
};
|
|
1415
1529
|
}
|
|
1530
|
+
const mimeType = mimeTypes.lookup(fullPath) || "application/octet-stream";
|
|
1531
|
+
const size = fileStat.size;
|
|
1532
|
+
const range = size > 0 ? parseRange(options.range, size) : null;
|
|
1533
|
+
if (range === "unsatisfiable") {
|
|
1534
|
+
return {
|
|
1535
|
+
status: 416,
|
|
1536
|
+
body: "",
|
|
1537
|
+
mimeType,
|
|
1538
|
+
headers: { "Accept-Ranges": "bytes", "Content-Range": `bytes */${size}` },
|
|
1539
|
+
error: `Range not satisfiable: ${options.range} (size ${size})`
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
if (range) {
|
|
1543
|
+
return {
|
|
1544
|
+
status: 206,
|
|
1545
|
+
stream: createReadStream(fullPath, { start: range.start, end: range.end }),
|
|
1546
|
+
mimeType,
|
|
1547
|
+
headers: {
|
|
1548
|
+
"Accept-Ranges": "bytes",
|
|
1549
|
+
"Content-Length": String(range.end - range.start + 1),
|
|
1550
|
+
"Content-Range": `bytes ${range.start}-${range.end}/${size}`
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1416
1554
|
try {
|
|
1417
1555
|
const content = await readFile2(fullPath);
|
|
1418
|
-
const mimeType = mimeTypes.lookup(fullPath) || "application/octet-stream";
|
|
1419
1556
|
return {
|
|
1420
1557
|
status: 200,
|
|
1421
1558
|
body: new Uint8Array(content),
|
|
1422
|
-
mimeType
|
|
1559
|
+
mimeType,
|
|
1560
|
+
// 声明支持 Range:媒体元素据此决定能否拖动进度条
|
|
1561
|
+
headers: { "Accept-Ranges": "bytes", "Content-Length": String(size) }
|
|
1423
1562
|
};
|
|
1424
1563
|
} catch (err) {
|
|
1425
1564
|
return {
|
|
@@ -1451,8 +1590,23 @@ function normalizeRemainingRuns(value) {
|
|
|
1451
1590
|
if (!Number.isFinite(n) || n < 0) return null;
|
|
1452
1591
|
return n;
|
|
1453
1592
|
}
|
|
1593
|
+
function normalizeCron(raw) {
|
|
1594
|
+
const type = raw?.type === "once" || raw?.type === "manual" ? raw.type : "cron";
|
|
1595
|
+
return {
|
|
1596
|
+
type,
|
|
1597
|
+
expr: type === "cron" ? raw?.expr ?? "" : void 0,
|
|
1598
|
+
at: type === "once" && typeof raw?.at === "number" ? raw.at : void 0
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1454
1601
|
function normalizeSchedule(raw) {
|
|
1455
|
-
|
|
1602
|
+
const cron = normalizeCron(raw.cron);
|
|
1603
|
+
const remainingRuns = normalizeRemainingRuns(raw.remainingRuns);
|
|
1604
|
+
return {
|
|
1605
|
+
...raw,
|
|
1606
|
+
cron,
|
|
1607
|
+
// 单次计划用 remainingRuns 作为「是否已执行」标记;缺失(历史数据)按未执行处理
|
|
1608
|
+
remainingRuns: cron.type === "once" ? remainingRuns ?? 1 : remainingRuns
|
|
1609
|
+
};
|
|
1456
1610
|
}
|
|
1457
1611
|
var store = {
|
|
1458
1612
|
scheduleDir(scheduleId) {
|
|
@@ -1506,11 +1660,16 @@ var store = {
|
|
|
1506
1660
|
agentId: params.agentId,
|
|
1507
1661
|
modelId: params.modelId,
|
|
1508
1662
|
prompt: params.prompt,
|
|
1509
|
-
cron: {
|
|
1663
|
+
cron: {
|
|
1664
|
+
type: params.cron.type,
|
|
1665
|
+
expr: params.cron.type === "cron" ? params.cron.expr ?? "" : void 0,
|
|
1666
|
+
at: params.cron.type === "once" ? params.cron.at : void 0
|
|
1667
|
+
},
|
|
1510
1668
|
createdAt: now,
|
|
1511
1669
|
updatedAt: now,
|
|
1512
1670
|
lastRunAt: null,
|
|
1513
|
-
remainingRuns
|
|
1671
|
+
// 单次计划用 remainingRuns 作为「是否已执行」标记:创建时固定为 1(待执行)
|
|
1672
|
+
remainingRuns: params.cron.type === "once" ? 1 : normalizeRemainingRuns(params.remainingRuns),
|
|
1514
1673
|
features: (params.features ?? []).filter((f) => f !== "ask_user" && f !== "share_to_user"),
|
|
1515
1674
|
mcpServers: params.mcpServers ?? []
|
|
1516
1675
|
};
|
|
@@ -1632,7 +1791,7 @@ function parseFileRoute(url) {
|
|
|
1632
1791
|
}
|
|
1633
1792
|
return null;
|
|
1634
1793
|
}
|
|
1635
|
-
async function serveRoute(route) {
|
|
1794
|
+
async function serveRoute(route, options = {}) {
|
|
1636
1795
|
switch (route.type) {
|
|
1637
1796
|
case "project": {
|
|
1638
1797
|
const project = storageOps.getProject(route.projectId);
|
|
@@ -1644,7 +1803,7 @@ async function serveRoute(route) {
|
|
|
1644
1803
|
error: "Project Not Found"
|
|
1645
1804
|
};
|
|
1646
1805
|
}
|
|
1647
|
-
return serveFile(route.relativePath, project.cwd);
|
|
1806
|
+
return serveFile(route.relativePath, project.cwd, options);
|
|
1648
1807
|
}
|
|
1649
1808
|
case "share": {
|
|
1650
1809
|
const shareDir = storageOps.sessionShareDir(route.sessionId);
|
|
@@ -1656,7 +1815,7 @@ async function serveRoute(route) {
|
|
|
1656
1815
|
error: "Session Not Found"
|
|
1657
1816
|
};
|
|
1658
1817
|
}
|
|
1659
|
-
return serveFile(route.sharePath, shareDir);
|
|
1818
|
+
return serveFile(route.sharePath, shareDir, options);
|
|
1660
1819
|
}
|
|
1661
1820
|
case "automation": {
|
|
1662
1821
|
const task = store.getTask(route.scheduleId, route.taskId);
|
|
@@ -1669,7 +1828,7 @@ async function serveRoute(route) {
|
|
|
1669
1828
|
};
|
|
1670
1829
|
}
|
|
1671
1830
|
const taskDir = store.taskDir(route.scheduleId, route.taskId);
|
|
1672
|
-
return serveFile(route.relativePath, taskDir);
|
|
1831
|
+
return serveFile(route.relativePath, taskDir, options);
|
|
1673
1832
|
}
|
|
1674
1833
|
}
|
|
1675
1834
|
}
|
|
@@ -1840,9 +1999,17 @@ function createWebUIModule() {
|
|
|
1840
1999
|
{
|
|
1841
2000
|
const route = parseFileRoute(url);
|
|
1842
2001
|
if (route) {
|
|
1843
|
-
const result = await serveRoute(route);
|
|
1844
|
-
res.writeHead(result.status, {
|
|
1845
|
-
|
|
2002
|
+
const result = await serveRoute(route, { range: req.headers.range });
|
|
2003
|
+
res.writeHead(result.status, {
|
|
2004
|
+
"Content-Type": result.mimeType,
|
|
2005
|
+
...result.headers
|
|
2006
|
+
});
|
|
2007
|
+
if (result.stream) {
|
|
2008
|
+
result.stream.on("error", () => res.destroy());
|
|
2009
|
+
result.stream.pipe(res);
|
|
2010
|
+
} else {
|
|
2011
|
+
res.end(result.body ?? "");
|
|
2012
|
+
}
|
|
1846
2013
|
return;
|
|
1847
2014
|
}
|
|
1848
2015
|
}
|
|
@@ -2016,22 +2183,22 @@ function prune() {
|
|
|
2016
2183
|
else records.delete(k);
|
|
2017
2184
|
}
|
|
2018
2185
|
}
|
|
2019
|
-
function recordSelfWrite(projectId, relativePath,
|
|
2186
|
+
function recordSelfWrite(projectId, relativePath, stat8) {
|
|
2020
2187
|
prune();
|
|
2021
2188
|
const k = keyOf(projectId, relativePath);
|
|
2022
2189
|
const list = records.get(k) ?? [];
|
|
2023
|
-
list.push({ mtimeMs:
|
|
2190
|
+
list.push({ mtimeMs: stat8.mtimeMs, size: stat8.size, at: Date.now() });
|
|
2024
2191
|
records.set(k, list);
|
|
2025
2192
|
}
|
|
2026
|
-
function isSelfWrite(projectId, relativePath,
|
|
2193
|
+
function isSelfWrite(projectId, relativePath, stat8) {
|
|
2027
2194
|
const k = keyOf(projectId, relativePath);
|
|
2028
2195
|
const list = records.get(k);
|
|
2029
|
-
if (!list || !
|
|
2196
|
+
if (!list || !stat8) return false;
|
|
2030
2197
|
const now = Date.now();
|
|
2031
2198
|
const alive = list.filter((r) => now - r.at <= TTL_MS);
|
|
2032
2199
|
if (alive.length > 0) records.set(k, alive);
|
|
2033
2200
|
else records.delete(k);
|
|
2034
|
-
return alive.some((r) => r.mtimeMs ===
|
|
2201
|
+
return alive.some((r) => r.mtimeMs === stat8.mtimeMs && r.size === stat8.size);
|
|
2035
2202
|
}
|
|
2036
2203
|
|
|
2037
2204
|
// src/backend/watcher.ts
|
|
@@ -3774,6 +3941,29 @@ function getFilenameFromUri(uri) {
|
|
|
3774
3941
|
}
|
|
3775
3942
|
|
|
3776
3943
|
// src/agents/acp-client-tools.ts
|
|
3944
|
+
var SHELL_OUTPUT_BYTE_LIMIT = 30 * 1024;
|
|
3945
|
+
var SHELL_OUTPUT_TRUNCATED_NOTICE = "[Shell output: earlier output exceeded the output limit and was dropped, only the tail is shown. Narrow the command (e.g. pipe through head/tail/grep) to see the part you need.]\n";
|
|
3946
|
+
function keepTailWithinByteLimit(text, byteLimit) {
|
|
3947
|
+
const encoded = new TextEncoder().encode(text);
|
|
3948
|
+
if (encoded.length <= byteLimit) return { text, trimmed: false };
|
|
3949
|
+
let start = encoded.length - byteLimit;
|
|
3950
|
+
while (start < encoded.length && (encoded[start] & 192) === 128) start += 1;
|
|
3951
|
+
return {
|
|
3952
|
+
text: new TextDecoder().decode(encoded.subarray(start)),
|
|
3953
|
+
trimmed: true
|
|
3954
|
+
};
|
|
3955
|
+
}
|
|
3956
|
+
function formatShellResultForModel(params) {
|
|
3957
|
+
const notes = [];
|
|
3958
|
+
if (params.timedOut) notes.push("timed out and was killed");
|
|
3959
|
+
const exitCode = params.exitStatus?.exitCode ?? null;
|
|
3960
|
+
if (exitCode != null && exitCode !== 0) notes.push(`exited with code ${exitCode}`);
|
|
3961
|
+
const signal = params.exitStatus?.signal ?? null;
|
|
3962
|
+
if (signal) notes.push(`killed by signal ${signal}`);
|
|
3963
|
+
if (notes.length === 0) return params.output;
|
|
3964
|
+
return `${params.output}
|
|
3965
|
+
[command ${notes.join("; ")}]`;
|
|
3966
|
+
}
|
|
3777
3967
|
function createACPClientTools(params) {
|
|
3778
3968
|
const terminals = /* @__PURE__ */ new Map();
|
|
3779
3969
|
const tools = {
|
|
@@ -4100,13 +4290,16 @@ Prefer dedicated tools first:
|
|
|
4100
4290
|
- Grep: search content (equivalent: rg "pattern" src or grep -R "pattern" src)
|
|
4101
4291
|
- Glob/find-style discovery: find files (equivalent: find . -name "*.ts")
|
|
4102
4292
|
Use Shell as fallback when those tools cannot complete the task.
|
|
4103
|
-
Avoid destructive commands and prefer deterministic, non-interactive commands
|
|
4293
|
+
Avoid destructive commands and prefer deterministic, non-interactive commands.
|
|
4294
|
+
Output returned to you is capped at 30720 bytes: beyond that only the tail is kept and a notice is prepended (the terminal buffer uses the same rule). The user still sees the full output in the terminal panel. When output may be large, narrow it in the command itself (pipe through head/tail/grep, or add flags like --no-pager).`,
|
|
4104
4295
|
inputSchema: z4.object({
|
|
4105
4296
|
command: z4.string().describe("Executable command."),
|
|
4106
4297
|
args: z4.array(z4.string()).optional().describe("Command arguments."),
|
|
4107
4298
|
cwd: z4.string().optional().describe("Absolute working directory."),
|
|
4108
4299
|
env: z4.record(z4.string(), z4.string()).optional().describe("Environment variables map."),
|
|
4109
|
-
outputByteLimit: z4.number().int().positive().optional().describe(
|
|
4300
|
+
outputByteLimit: z4.number().int().positive().optional().describe(
|
|
4301
|
+
"Terminal buffer retention limit in bytes. Defaults to 30720, which is also the cap on the output returned to you."
|
|
4302
|
+
),
|
|
4110
4303
|
timeoutSeconds: z4.number().positive().optional().describe("Timeout before killing command.")
|
|
4111
4304
|
}),
|
|
4112
4305
|
execute: async ({ command, args: args2, cwd, env, outputByteLimit, timeoutSeconds }, { toolCallId }) => {
|
|
@@ -4147,7 +4340,8 @@ Avoid destructive commands and prefer deterministic, non-interactive commands.`,
|
|
|
4147
4340
|
args: args2,
|
|
4148
4341
|
cwd: cwd ?? null,
|
|
4149
4342
|
env: toEnvVariables(env),
|
|
4150
|
-
|
|
4343
|
+
// 未指定时用统一预算,避免 ACP 侧落回 agent-bridge 的 1MB 默认值
|
|
4344
|
+
outputByteLimit: outputByteLimit ?? SHELL_OUTPUT_BYTE_LIMIT
|
|
4151
4345
|
});
|
|
4152
4346
|
terminals.set(terminal.id, terminal);
|
|
4153
4347
|
const toolCallStartUpdate = {
|
|
@@ -4184,11 +4378,12 @@ Avoid destructive commands and prefer deterministic, non-interactive commands.`,
|
|
|
4184
4378
|
terminals.delete(terminal.id);
|
|
4185
4379
|
}
|
|
4186
4380
|
const finalOutput = output ?? { output: "", truncated: false, exitStatus: null };
|
|
4381
|
+
const exitStatus = finalOutput.exitStatus ?? null;
|
|
4187
4382
|
const sessionOutput = {
|
|
4188
4383
|
terminalId: terminal.id,
|
|
4189
4384
|
output: finalOutput.output,
|
|
4190
4385
|
truncated: finalOutput.truncated,
|
|
4191
|
-
exitStatus
|
|
4386
|
+
exitStatus,
|
|
4192
4387
|
timedOut
|
|
4193
4388
|
};
|
|
4194
4389
|
await connection.sessionUpdate({
|
|
@@ -4201,7 +4396,13 @@ Avoid destructive commands and prefer deterministic, non-interactive commands.`,
|
|
|
4201
4396
|
content: toolCallStartUpdate.content
|
|
4202
4397
|
}
|
|
4203
4398
|
});
|
|
4204
|
-
|
|
4399
|
+
const keptOutput = keepTailWithinByteLimit(finalOutput.output, SHELL_OUTPUT_BYTE_LIMIT);
|
|
4400
|
+
const headDropped = finalOutput.truncated || keptOutput.trimmed;
|
|
4401
|
+
return formatShellResultForModel({
|
|
4402
|
+
output: headDropped ? SHELL_OUTPUT_TRUNCATED_NOTICE + keptOutput.text : keptOutput.text,
|
|
4403
|
+
exitStatus,
|
|
4404
|
+
timedOut
|
|
4405
|
+
});
|
|
4205
4406
|
} catch (error) {
|
|
4206
4407
|
const errorText = error instanceof Error ? error.message : String(error);
|
|
4207
4408
|
await connection.sessionUpdate({
|
|
@@ -4821,67 +5022,87 @@ import { z as z5 } from "zod";
|
|
|
4821
5022
|
function createImageAnalysisTool(params) {
|
|
4822
5023
|
return {
|
|
4823
5024
|
ImageAnalysis: tool2({
|
|
4824
|
-
description: `Analyze
|
|
5025
|
+
description: `Analyze one or more image files by reading them and using vision capabilities to understand their content.
|
|
4825
5026
|
|
|
4826
|
-
Use this when you need to visually understand or analyze image content (e.g., describe what's in the image, extract text from a screenshot, analyze a diagram, identify UI elements, read charts).
|
|
5027
|
+
Use this when you need to visually understand or analyze image content (e.g., describe what's in the image, extract text from a screenshot, analyze a diagram, identify UI elements, read charts). Pass every image that belongs to the same question in a single call (up to 10 per call) so the model can compare them: each image carries a name that labels it in the request, so you can refer to that name in the query and attribute the findings in the answer.
|
|
4827
5028
|
|
|
4828
5029
|
For simple metadata queries (dimensions, format), use image_metadata instead.
|
|
4829
5030
|
|
|
4830
|
-
Provide a specific query describing what you want to know about the
|
|
5031
|
+
Provide a specific query describing what you want to know about the images \u2014 the more specific the query, the better the analysis.`,
|
|
4831
5032
|
inputSchema: z5.object({
|
|
4832
|
-
|
|
5033
|
+
images: z5.array(
|
|
5034
|
+
z5.object({
|
|
5035
|
+
name: z5.string().describe(
|
|
5036
|
+
"Short label identifying this image (e.g. 'login page' or 'before'). It is sent to the model as the image's caption and also shown in the UI."
|
|
5037
|
+
),
|
|
5038
|
+
path: z5.string().describe("Absolute or project-relative path to the image file.")
|
|
5039
|
+
})
|
|
5040
|
+
).min(1).max(10).describe(
|
|
5041
|
+
"Images to analyze together, in order. Min 1, max 10 \u2014 every image is sent to the model in the same request, so split larger batches into several calls."
|
|
5042
|
+
),
|
|
4833
5043
|
query: z5.string().describe(
|
|
4834
|
-
"What you want to know about the
|
|
5044
|
+
"What you want to know about the images. Be specific (e.g., 'Extract all text from this screenshot', 'Describe the UI layout', 'Which of these charts has the highest value?')."
|
|
4835
5045
|
)
|
|
4836
5046
|
}),
|
|
4837
|
-
execute: async ({
|
|
5047
|
+
execute: async ({ images, query }, { toolCallId }) => {
|
|
4838
5048
|
const connection = params.getConnection();
|
|
5049
|
+
const imageLabel = images.map((image) => image.name).join(", ");
|
|
4839
5050
|
if (connection) {
|
|
4840
5051
|
await connection.sessionUpdate({
|
|
4841
5052
|
sessionId: params.sessionId,
|
|
4842
5053
|
update: {
|
|
4843
5054
|
sessionUpdate: "tool_call",
|
|
4844
5055
|
toolCallId,
|
|
4845
|
-
title: `image_analysis ${
|
|
5056
|
+
title: `image_analysis ${imageLabel}`,
|
|
4846
5057
|
kind: "read",
|
|
4847
5058
|
status: "in_progress",
|
|
4848
|
-
rawInput: {
|
|
5059
|
+
rawInput: { images, query }
|
|
4849
5060
|
}
|
|
4850
5061
|
});
|
|
4851
5062
|
}
|
|
4852
5063
|
try {
|
|
4853
|
-
const
|
|
4854
|
-
const
|
|
4855
|
-
const
|
|
4856
|
-
|
|
5064
|
+
const content = [];
|
|
5065
|
+
const failures = [];
|
|
5066
|
+
for (const image of images) {
|
|
5067
|
+
try {
|
|
5068
|
+
const absPath = resolve7(params.cwd, image.path);
|
|
5069
|
+
const buffer = await readFile6(absPath);
|
|
5070
|
+
const mimeType = mime.lookup(absPath) || "image/png";
|
|
5071
|
+
content.push({ type: "text", text: `Image "${image.name}":` });
|
|
5072
|
+
content.push({
|
|
5073
|
+
type: "file",
|
|
5074
|
+
data: buffer.toString("base64"),
|
|
5075
|
+
mediaType: mimeType
|
|
5076
|
+
});
|
|
5077
|
+
} catch (error) {
|
|
5078
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5079
|
+
failures.push(`${image.name} (${image.path}): ${reason}`);
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
if (content.length === 0) {
|
|
5083
|
+
throw new Error(`Failed to read any image \u2014 ${failures.join("; ")}`);
|
|
5084
|
+
}
|
|
5085
|
+
content.push({ type: "text", text: query });
|
|
4857
5086
|
const result = await generateText({
|
|
4858
5087
|
model: params.getModel(),
|
|
4859
5088
|
messages: [
|
|
4860
5089
|
{
|
|
4861
5090
|
role: "user",
|
|
4862
|
-
content
|
|
4863
|
-
{
|
|
4864
|
-
type: "image",
|
|
4865
|
-
image: base64,
|
|
4866
|
-
mediaType: mimeType
|
|
4867
|
-
},
|
|
4868
|
-
{
|
|
4869
|
-
type: "text",
|
|
4870
|
-
text: query
|
|
4871
|
-
}
|
|
4872
|
-
]
|
|
5091
|
+
content
|
|
4873
5092
|
}
|
|
4874
5093
|
],
|
|
4875
5094
|
abortSignal: params.parentSignal,
|
|
4876
5095
|
providerOptions: {
|
|
4877
5096
|
openaiCompatible: {
|
|
4878
5097
|
thinking: { type: "disabled" },
|
|
4879
|
-
enable_thinking: false
|
|
4880
|
-
reasoningEffort: "high"
|
|
5098
|
+
enable_thinking: false
|
|
4881
5099
|
}
|
|
4882
5100
|
}
|
|
4883
5101
|
});
|
|
4884
5102
|
const analysisText = result.text || "(no analysis returned)";
|
|
5103
|
+
const outputText = failures.length > 0 ? `${analysisText}
|
|
5104
|
+
|
|
5105
|
+
[failed to read: ${failures.join("; ")}]` : analysisText;
|
|
4885
5106
|
if (connection) {
|
|
4886
5107
|
await connection.sessionUpdate({
|
|
4887
5108
|
sessionId: params.sessionId,
|
|
@@ -4892,13 +5113,13 @@ Provide a specific query describing what you want to know about the image \u2014
|
|
|
4892
5113
|
content: [
|
|
4893
5114
|
{
|
|
4894
5115
|
type: "content",
|
|
4895
|
-
content: { type: "text", text:
|
|
5116
|
+
content: { type: "text", text: outputText }
|
|
4896
5117
|
}
|
|
4897
5118
|
]
|
|
4898
5119
|
}
|
|
4899
5120
|
});
|
|
4900
5121
|
}
|
|
4901
|
-
return
|
|
5122
|
+
return outputText;
|
|
4902
5123
|
} catch (error) {
|
|
4903
5124
|
const errorText = error instanceof Error ? error.message : String(error);
|
|
4904
5125
|
if (connection) {
|
|
@@ -4911,13 +5132,16 @@ Provide a specific query describing what you want to know about the image \u2014
|
|
|
4911
5132
|
content: [
|
|
4912
5133
|
{
|
|
4913
5134
|
type: "content",
|
|
4914
|
-
content: {
|
|
5135
|
+
content: {
|
|
5136
|
+
type: "text",
|
|
5137
|
+
text: `Error analyzing images: ${errorText}`
|
|
5138
|
+
}
|
|
4915
5139
|
}
|
|
4916
5140
|
]
|
|
4917
5141
|
}
|
|
4918
5142
|
});
|
|
4919
5143
|
}
|
|
4920
|
-
return `Error analyzing
|
|
5144
|
+
return `Error analyzing images: ${errorText}`;
|
|
4921
5145
|
}
|
|
4922
5146
|
}
|
|
4923
5147
|
})
|
|
@@ -5468,6 +5692,17 @@ var COMPACT_PROMPT = `Compress the conversation into a structured markdown summa
|
|
|
5468
5692
|
Use headings and bullet points for clarity.
|
|
5469
5693
|
Detect the user's commonly used language from the conversation history and respond in that language.
|
|
5470
5694
|
Output only the summary.`;
|
|
5695
|
+
function buildCompactSummaryMessage(summary) {
|
|
5696
|
+
return {
|
|
5697
|
+
role: "user",
|
|
5698
|
+
content: `<previous_conversation_summary>
|
|
5699
|
+
This session continues from a previous conversation that ran out of context.
|
|
5700
|
+
The summary below covers the earlier portion of the conversation.
|
|
5701
|
+
|
|
5702
|
+
${summary}
|
|
5703
|
+
</previous_conversation_summary>`
|
|
5704
|
+
};
|
|
5705
|
+
}
|
|
5471
5706
|
var OpenaiCompatibleAgent = class {
|
|
5472
5707
|
sessions = /* @__PURE__ */ new Map();
|
|
5473
5708
|
provider;
|
|
@@ -5498,6 +5733,9 @@ var OpenaiCompatibleAgent = class {
|
|
|
5498
5733
|
name: "openai-compatible",
|
|
5499
5734
|
baseURL: this.baseUrl,
|
|
5500
5735
|
apiKey: this.apiKey,
|
|
5736
|
+
// 流式请求需带 stream_options.include_usage,否则 vLLM 等服务端
|
|
5737
|
+
// 不会在流中返回 usage,导致整轮 token 用量为 0。
|
|
5738
|
+
includeUsage: true,
|
|
5501
5739
|
headers: {
|
|
5502
5740
|
"User-Agent": AgentDescription,
|
|
5503
5741
|
...this.headers
|
|
@@ -5863,12 +6101,7 @@ var OpenaiCompatibleAgent = class {
|
|
|
5863
6101
|
maxOutputTokens: 5e3
|
|
5864
6102
|
});
|
|
5865
6103
|
const summary = summaryResult.text;
|
|
5866
|
-
session.history = [
|
|
5867
|
-
{
|
|
5868
|
-
role: "system",
|
|
5869
|
-
content: `Summary of previous conversation: ${summary}`
|
|
5870
|
-
}
|
|
5871
|
-
];
|
|
6104
|
+
session.history = [buildCompactSummaryMessage(summary)];
|
|
5872
6105
|
await savePersistedSessionHistory({
|
|
5873
6106
|
agentId: this.agentId,
|
|
5874
6107
|
sessionId: session.id,
|
|
@@ -6016,7 +6249,7 @@ var OpenaiCompatibleAgent = class {
|
|
|
6016
6249
|
system: systemPrompt,
|
|
6017
6250
|
messages: [...session.history, userMessage],
|
|
6018
6251
|
tools: allTools,
|
|
6019
|
-
stopWhen: isStepCount2(
|
|
6252
|
+
stopWhen: isStepCount2(256),
|
|
6020
6253
|
abortSignal: abortController.signal,
|
|
6021
6254
|
providerOptions: {
|
|
6022
6255
|
openaiCompatible: {
|
|
@@ -6246,6 +6479,7 @@ var AgentTerminalManager = class {
|
|
|
6246
6479
|
process: proc,
|
|
6247
6480
|
outputBuffer: Buffer.alloc(0),
|
|
6248
6481
|
outputByteLimit,
|
|
6482
|
+
truncated: false,
|
|
6249
6483
|
exitCode: null,
|
|
6250
6484
|
signal: null,
|
|
6251
6485
|
isFinished: false,
|
|
@@ -6258,7 +6492,12 @@ var AgentTerminalManager = class {
|
|
|
6258
6492
|
const handleData = (chunk) => {
|
|
6259
6493
|
let newBuffer = Buffer.concat([terminal.outputBuffer, chunk]);
|
|
6260
6494
|
if (newBuffer.length > terminal.outputByteLimit) {
|
|
6261
|
-
|
|
6495
|
+
let start = newBuffer.length - terminal.outputByteLimit;
|
|
6496
|
+
while (start < newBuffer.length && (newBuffer.readUInt8(start) & 192) === 128) {
|
|
6497
|
+
start += 1;
|
|
6498
|
+
}
|
|
6499
|
+
newBuffer = newBuffer.subarray(start);
|
|
6500
|
+
terminal.truncated = true;
|
|
6262
6501
|
}
|
|
6263
6502
|
terminal.outputBuffer = newBuffer;
|
|
6264
6503
|
const text = terminal.outputDecoder.decode(chunk, { stream: true });
|
|
@@ -6287,8 +6526,7 @@ var AgentTerminalManager = class {
|
|
|
6287
6526
|
}
|
|
6288
6527
|
return {
|
|
6289
6528
|
output: terminal.outputDecoder.decode(terminal.outputBuffer),
|
|
6290
|
-
truncated:
|
|
6291
|
-
// Simplification
|
|
6529
|
+
truncated: terminal.truncated
|
|
6292
6530
|
};
|
|
6293
6531
|
}
|
|
6294
6532
|
getTerminal(terminalId) {
|
|
@@ -7710,6 +7948,9 @@ function createTerminalModule(ctx, deps) {
|
|
|
7710
7948
|
};
|
|
7711
7949
|
}
|
|
7712
7950
|
|
|
7951
|
+
// src/backend/project/index.ts
|
|
7952
|
+
import { stat as stat5 } from "fs/promises";
|
|
7953
|
+
|
|
7713
7954
|
// src/backend/project/filesystem.ts
|
|
7714
7955
|
import Fuse from "fuse.js";
|
|
7715
7956
|
import {
|
|
@@ -8203,7 +8444,12 @@ function createProjectModule(ctx, deps) {
|
|
|
8203
8444
|
return storage.listProjects();
|
|
8204
8445
|
}
|
|
8205
8446
|
async function addProject2(cwd) {
|
|
8206
|
-
const
|
|
8447
|
+
const trimmed = cwd.trim();
|
|
8448
|
+
const s = await stat5(trimmed).catch(() => null);
|
|
8449
|
+
if (!s || !s.isDirectory()) {
|
|
8450
|
+
throw new Error(`Not a directory: ${trimmed}`);
|
|
8451
|
+
}
|
|
8452
|
+
const info = storage.addProject(trimmed);
|
|
8207
8453
|
fs2.initProjectFsVersion(info.id);
|
|
8208
8454
|
await deps.watcher.syncWatchers();
|
|
8209
8455
|
sendEvent("projects-changed", void 0);
|
|
@@ -10067,6 +10313,32 @@ function createIlinkModule(ctx) {
|
|
|
10067
10313
|
if (!handlers) throw new Error("[ilink] handlers not yet initialized");
|
|
10068
10314
|
return handlers;
|
|
10069
10315
|
}
|
|
10316
|
+
function parseMenuIndex(input) {
|
|
10317
|
+
const match = input.match(/^\d+/);
|
|
10318
|
+
return match ? parseInt(match[0], 10) : null;
|
|
10319
|
+
}
|
|
10320
|
+
async function replyInvalidIndex(msg, max) {
|
|
10321
|
+
if (!msg.from_user_id) return;
|
|
10322
|
+
await bridge?.sendTextReply(
|
|
10323
|
+
msg.from_user_id,
|
|
10324
|
+
t("ilink.invalidMenuNumber", { min: "1", max: String(max) })
|
|
10325
|
+
);
|
|
10326
|
+
}
|
|
10327
|
+
async function replyUnknownCommand(msg) {
|
|
10328
|
+
if (!msg.from_user_id) return;
|
|
10329
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.unknownCommand"));
|
|
10330
|
+
}
|
|
10331
|
+
const FEATURE_LABEL_KEYS = {
|
|
10332
|
+
skills: "ilink.featureSkills",
|
|
10333
|
+
search: "ilink.featureSearch",
|
|
10334
|
+
image_generation: "ilink.featureImageGeneration",
|
|
10335
|
+
memory: "ilink.featureMemory",
|
|
10336
|
+
ask_user: "ilink.featureAskUser",
|
|
10337
|
+
share_to_user: "ilink.featureShareToUser"
|
|
10338
|
+
};
|
|
10339
|
+
function getFeatureLabel(feature) {
|
|
10340
|
+
return t(FEATURE_LABEL_KEYS[feature]);
|
|
10341
|
+
}
|
|
10070
10342
|
const state = {
|
|
10071
10343
|
getBridge: () => bridge,
|
|
10072
10344
|
getActiveSessionId: () => activeSessionId,
|
|
@@ -10099,88 +10371,99 @@ function createIlinkModule(ctx) {
|
|
|
10099
10371
|
const combinedText = [text, voiceText].filter(Boolean).join("\n");
|
|
10100
10372
|
if (!combinedText.trim() && !hasImages) return;
|
|
10101
10373
|
const trimmed = text.trim();
|
|
10102
|
-
if (commandPending) {
|
|
10103
|
-
commandPending(trimmed);
|
|
10104
|
-
commandPending = null;
|
|
10105
|
-
return;
|
|
10106
|
-
}
|
|
10107
10374
|
if (trimmed[0] === "!" || trimmed[0] === "\uFF01") {
|
|
10375
|
+
commandPending = null;
|
|
10108
10376
|
await handleIlinkCommand(trimmed, msg);
|
|
10109
10377
|
return;
|
|
10110
10378
|
}
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
t("ilink.switchSessionGuide"),
|
|
10119
|
-
t("ilink.createSessionGuide")
|
|
10120
|
-
];
|
|
10121
|
-
await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10122
|
-
}
|
|
10123
|
-
return;
|
|
10124
|
-
}
|
|
10125
|
-
const contents = [];
|
|
10126
|
-
if (combinedText.trim()) contents.push({ type: "text", text: combinedText });
|
|
10127
|
-
if (hasImages && bridge) {
|
|
10128
|
-
const { useOriginalImage } = storage.getSettings().ilink;
|
|
10129
|
-
for (const item of msg.item_list ?? []) {
|
|
10130
|
-
if (item.type !== 2 || !item.image_item) continue;
|
|
10131
|
-
try {
|
|
10132
|
-
const base64 = await bridge.downloadImage(item.image_item, { useOriginalImage });
|
|
10133
|
-
if (base64) contents.push({ type: "image", data: base64, mimeType: "image/jpeg" });
|
|
10134
|
-
} catch (err) {
|
|
10135
|
-
console.error("[iLink] Failed to download image:", err);
|
|
10136
|
-
}
|
|
10137
|
-
}
|
|
10138
|
-
}
|
|
10139
|
-
if (contents.length === 0) return;
|
|
10140
|
-
const h = getHandlers();
|
|
10141
|
-
const pending = await h.getPendingAskUserRequests({ sessionId });
|
|
10142
|
-
if (pending.length > 0) {
|
|
10143
|
-
const req = pending[0];
|
|
10144
|
-
const options = req.options;
|
|
10145
|
-
let respondedValue = null;
|
|
10146
|
-
if (/^\d+$/.test(trimmed)) {
|
|
10147
|
-
const index = parseInt(trimmed, 10) - 1;
|
|
10148
|
-
const option = options[index];
|
|
10149
|
-
if (option) respondedValue = option.value;
|
|
10150
|
-
}
|
|
10151
|
-
if (respondedValue !== null) {
|
|
10152
|
-
await h.respondAskUser({
|
|
10153
|
-
sessionId,
|
|
10154
|
-
askUserId: req.askUserId,
|
|
10155
|
-
value: respondedValue
|
|
10156
|
-
});
|
|
10157
|
-
} else {
|
|
10158
|
-
await h.respondAskUser({
|
|
10159
|
-
sessionId,
|
|
10160
|
-
askUserId: req.askUserId,
|
|
10161
|
-
value: null,
|
|
10162
|
-
reason: trimmed || t("ilink.noInput")
|
|
10163
|
-
});
|
|
10164
|
-
}
|
|
10165
|
-
if (bridge?.isConnected && activeSessionId === sessionId) {
|
|
10166
|
-
const userId = bridge.userId;
|
|
10167
|
-
if (userId) bridge.sendTyping(userId, true).catch(() => {
|
|
10168
|
-
});
|
|
10379
|
+
if (commandPending) {
|
|
10380
|
+
const index = parseMenuIndex(trimmed);
|
|
10381
|
+
if (index !== null) {
|
|
10382
|
+
const pending = commandPending;
|
|
10383
|
+
commandPending = null;
|
|
10384
|
+
await pending(index);
|
|
10385
|
+
return;
|
|
10169
10386
|
}
|
|
10170
|
-
|
|
10171
|
-
}
|
|
10172
|
-
try {
|
|
10173
|
-
await h.sendPrompt({ sessionId, contents });
|
|
10174
|
-
} catch (err) {
|
|
10175
|
-
console.error("[iLink] Failed to route message to session:", err);
|
|
10176
|
-
if (msg.from_user_id)
|
|
10177
|
-
await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10387
|
+
commandPending = null;
|
|
10178
10388
|
}
|
|
10389
|
+
await handleUserMessage(msg, { trimmed, combinedText, hasImages });
|
|
10179
10390
|
}
|
|
10180
10391
|
});
|
|
10181
10392
|
}
|
|
10182
10393
|
return bridge;
|
|
10183
10394
|
}
|
|
10395
|
+
async function handleUserMessage(msg, input) {
|
|
10396
|
+
const { trimmed, combinedText, hasImages } = input;
|
|
10397
|
+
const sessionId = activeSessionId ?? "";
|
|
10398
|
+
if (!sessionId) {
|
|
10399
|
+
console.warn("[iLink] No active session, ignoring message");
|
|
10400
|
+
if (msg.from_user_id) {
|
|
10401
|
+
const lines = [
|
|
10402
|
+
`\u{1F4CB} **${t("ilink.noActiveSession")}**`,
|
|
10403
|
+
"",
|
|
10404
|
+
t("ilink.switchSessionGuide"),
|
|
10405
|
+
"",
|
|
10406
|
+
t("ilink.createSessionGuide")
|
|
10407
|
+
];
|
|
10408
|
+
await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10409
|
+
}
|
|
10410
|
+
return;
|
|
10411
|
+
}
|
|
10412
|
+
const contents = [];
|
|
10413
|
+
if (combinedText.trim()) contents.push({ type: "text", text: combinedText });
|
|
10414
|
+
if (hasImages && bridge) {
|
|
10415
|
+
const { useOriginalImage } = storage.getSettings().ilink;
|
|
10416
|
+
for (const item of msg.item_list ?? []) {
|
|
10417
|
+
if (item.type !== 2 || !item.image_item) continue;
|
|
10418
|
+
try {
|
|
10419
|
+
const base64 = await bridge.downloadImage(item.image_item, { useOriginalImage });
|
|
10420
|
+
if (base64) contents.push({ type: "image", data: base64, mimeType: "image/jpeg" });
|
|
10421
|
+
} catch (err) {
|
|
10422
|
+
console.error("[iLink] Failed to download image:", err);
|
|
10423
|
+
}
|
|
10424
|
+
}
|
|
10425
|
+
}
|
|
10426
|
+
if (contents.length === 0) return;
|
|
10427
|
+
const h = getHandlers();
|
|
10428
|
+
const pending = await h.getPendingAskUserRequests({ sessionId });
|
|
10429
|
+
if (pending.length > 0) {
|
|
10430
|
+
const req = pending[0];
|
|
10431
|
+
const options = req.options;
|
|
10432
|
+
let respondedValue = null;
|
|
10433
|
+
if (/^\d+$/.test(trimmed)) {
|
|
10434
|
+
const index = parseInt(trimmed, 10) - 1;
|
|
10435
|
+
const option = options[index];
|
|
10436
|
+
if (option) respondedValue = option.value;
|
|
10437
|
+
}
|
|
10438
|
+
if (respondedValue !== null) {
|
|
10439
|
+
await h.respondAskUser({
|
|
10440
|
+
sessionId,
|
|
10441
|
+
askUserId: req.askUserId,
|
|
10442
|
+
value: respondedValue
|
|
10443
|
+
});
|
|
10444
|
+
} else {
|
|
10445
|
+
await h.respondAskUser({
|
|
10446
|
+
sessionId,
|
|
10447
|
+
askUserId: req.askUserId,
|
|
10448
|
+
value: null,
|
|
10449
|
+
reason: trimmed || t("ilink.noInput")
|
|
10450
|
+
});
|
|
10451
|
+
}
|
|
10452
|
+
if (bridge?.isConnected && activeSessionId === sessionId) {
|
|
10453
|
+
const userId = bridge.userId;
|
|
10454
|
+
if (userId) bridge.sendTyping(userId, true).catch(() => {
|
|
10455
|
+
});
|
|
10456
|
+
}
|
|
10457
|
+
return;
|
|
10458
|
+
}
|
|
10459
|
+
try {
|
|
10460
|
+
await h.sendPrompt({ sessionId, contents });
|
|
10461
|
+
} catch (err) {
|
|
10462
|
+
console.error("[iLink] Failed to route message to session:", err);
|
|
10463
|
+
if (msg.from_user_id)
|
|
10464
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10465
|
+
}
|
|
10466
|
+
}
|
|
10184
10467
|
async function handleIlinkCommand(trimmed, msg) {
|
|
10185
10468
|
const session = activeSessionId ? storage.getSession(activeSessionId) : null;
|
|
10186
10469
|
if (session && session.isStreaming) {
|
|
@@ -10189,11 +10472,16 @@ function createIlinkModule(ctx) {
|
|
|
10189
10472
|
});
|
|
10190
10473
|
}
|
|
10191
10474
|
const [command] = trimmed.slice(1).split(/\s+/);
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
else if (
|
|
10195
|
-
else if (
|
|
10196
|
-
else await
|
|
10475
|
+
const name = command.toLowerCase();
|
|
10476
|
+
if (name === "") await handleCommandInfo(msg);
|
|
10477
|
+
else if (name === "s") await handleCommandSwitchSession(msg);
|
|
10478
|
+
else if (name === "n") await handleCommandNewSession(msg);
|
|
10479
|
+
else if (name === "m") await handleCommandSwitchModel(msg);
|
|
10480
|
+
else if (name === "p") await handleCommandPermission(msg);
|
|
10481
|
+
else if (name === "f") await handleCommandFeatures(msg);
|
|
10482
|
+
else if (name === "c") await handleCommandMcpServers(msg);
|
|
10483
|
+
else if (name === "q") await handleCommandSnippet(msg);
|
|
10484
|
+
else await replyUnknownCommand(msg);
|
|
10197
10485
|
}
|
|
10198
10486
|
async function handleCommandSwitchSession(msg) {
|
|
10199
10487
|
const allSessions = storage.listSessions();
|
|
@@ -10205,7 +10493,7 @@ function createIlinkModule(ctx) {
|
|
|
10205
10493
|
const projectMap = new Map(projects.map((p) => [p.id, p]));
|
|
10206
10494
|
const lines = [];
|
|
10207
10495
|
lines.push(`\u{1F4CB} **${t("ilink.sessionList")}**`);
|
|
10208
|
-
lines.push(t("ilink.sessionListDesc"));
|
|
10496
|
+
lines.push("", t("ilink.sessionListDesc"));
|
|
10209
10497
|
let index = 1;
|
|
10210
10498
|
let isFirstGroup = true;
|
|
10211
10499
|
const sessionEntries = [];
|
|
@@ -10222,39 +10510,37 @@ function createIlinkModule(ctx) {
|
|
|
10222
10510
|
---`);
|
|
10223
10511
|
isFirstGroup = false;
|
|
10224
10512
|
lines.push(`
|
|
10225
|
-
**${projectName}
|
|
10513
|
+
**${projectName}**`, "");
|
|
10226
10514
|
for (const s of sessions) {
|
|
10227
10515
|
const marker = s.id === activeSessionId ? " \u{1F448}" : "";
|
|
10228
10516
|
const label = s.title || t("ilink.newSession");
|
|
10229
|
-
|
|
10517
|
+
const agentId = s.agentId;
|
|
10518
|
+
lines.push(` ${index}. [${agentId}] ${label}${marker}`);
|
|
10230
10519
|
sessionEntries.push({ sessionId: s.id, label });
|
|
10231
10520
|
index++;
|
|
10232
10521
|
}
|
|
10233
10522
|
}
|
|
10234
10523
|
lines.push("", "---", t("ilink.switchSessionHint"));
|
|
10235
10524
|
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10236
|
-
|
|
10237
|
-
const
|
|
10238
|
-
if (
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
msg.from_user_id,
|
|
10242
|
-
t("ilink.invalidSessionNumber", { min: "1", max: String(sessionEntries.length) })
|
|
10243
|
-
);
|
|
10525
|
+
const pending = async (index2) => {
|
|
10526
|
+
const entry = sessionEntries[index2 - 1];
|
|
10527
|
+
if (!entry) {
|
|
10528
|
+
await replyInvalidIndex(msg, sessionEntries.length);
|
|
10529
|
+
commandPending = pending;
|
|
10244
10530
|
return;
|
|
10245
10531
|
}
|
|
10246
|
-
const entry = sessionEntries[num - 1];
|
|
10247
10532
|
activeSessionId = entry.sessionId;
|
|
10248
10533
|
replyBuffer = "";
|
|
10249
10534
|
writeActiveSessionId(entry.sessionId).catch(() => {
|
|
10250
10535
|
});
|
|
10251
10536
|
sendEvent("ilink-active-session-changed", { sessionId: entry.sessionId });
|
|
10252
10537
|
if (msg.from_user_id)
|
|
10253
|
-
bridge?.sendTextReply(
|
|
10538
|
+
await bridge?.sendTextReply(
|
|
10254
10539
|
msg.from_user_id,
|
|
10255
10540
|
t("ilink.switchedToSession", { label: entry.label })
|
|
10256
10541
|
);
|
|
10257
10542
|
};
|
|
10543
|
+
commandPending = pending;
|
|
10258
10544
|
}
|
|
10259
10545
|
async function handleCommandNewSession(msg) {
|
|
10260
10546
|
const allProjects = storage.listProjects();
|
|
@@ -10265,7 +10551,7 @@ function createIlinkModule(ctx) {
|
|
|
10265
10551
|
const sortedProjects = [...allProjects].sort((a, b) => a.title.localeCompare(b.title));
|
|
10266
10552
|
const lines = [];
|
|
10267
10553
|
lines.push(`\u{1F4CB} **${t("ilink.newSessionTitle")}**`);
|
|
10268
|
-
lines.push(t("ilink.newSessionDesc"));
|
|
10554
|
+
lines.push("", t("ilink.newSessionDesc"));
|
|
10269
10555
|
const projectEntries = [];
|
|
10270
10556
|
sortedProjects.forEach((p, i) => {
|
|
10271
10557
|
lines.push(` ${i + 1}. ${p.title}`);
|
|
@@ -10273,17 +10559,13 @@ function createIlinkModule(ctx) {
|
|
|
10273
10559
|
});
|
|
10274
10560
|
lines.push("", "---", t("ilink.createSessionHint"));
|
|
10275
10561
|
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10276
|
-
|
|
10277
|
-
const
|
|
10278
|
-
if (
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
msg.from_user_id,
|
|
10282
|
-
t("ilink.invalidSessionNumber", { min: "1", max: String(projectEntries.length) })
|
|
10283
|
-
);
|
|
10562
|
+
const pending = (index) => {
|
|
10563
|
+
const entry = projectEntries[index - 1];
|
|
10564
|
+
if (!entry) {
|
|
10565
|
+
void replyInvalidIndex(msg, projectEntries.length);
|
|
10566
|
+
commandPending = pending;
|
|
10284
10567
|
return;
|
|
10285
10568
|
}
|
|
10286
|
-
const entry = projectEntries[num - 1];
|
|
10287
10569
|
const settings = storage.getSettings();
|
|
10288
10570
|
const agent2 = settings.agents.find((a) => !a.disabled);
|
|
10289
10571
|
if (!agent2) {
|
|
@@ -10313,31 +10595,34 @@ function createIlinkModule(ctx) {
|
|
|
10313
10595
|
if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10314
10596
|
});
|
|
10315
10597
|
};
|
|
10598
|
+
commandPending = pending;
|
|
10316
10599
|
}
|
|
10317
10600
|
async function handleCommandSwitchModel(msg) {
|
|
10318
10601
|
const sessionId = activeSessionId ?? "";
|
|
10319
10602
|
if (!sessionId) {
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
);
|
|
10603
|
+
await replyNoActiveSession(msg);
|
|
10604
|
+
return;
|
|
10605
|
+
}
|
|
10606
|
+
let modelState;
|
|
10607
|
+
try {
|
|
10608
|
+
modelState = await getHandlers().getModels({ sessionId });
|
|
10609
|
+
if (!modelState) {
|
|
10610
|
+
await getHandlers().loadSession({ sessionId });
|
|
10611
|
+
modelState = await getHandlers().getModels({ sessionId });
|
|
10330
10612
|
}
|
|
10613
|
+
} catch (err) {
|
|
10614
|
+
console.error("[iLink] Failed to prepare model list:", err);
|
|
10615
|
+
if (msg.from_user_id)
|
|
10616
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10331
10617
|
return;
|
|
10332
10618
|
}
|
|
10333
|
-
const modelState = await getHandlers().getModels({ sessionId });
|
|
10334
10619
|
if (!modelState || !modelState.availableModels || modelState.availableModels.length === 0) {
|
|
10335
10620
|
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, t("ilink.noModels"));
|
|
10336
10621
|
return;
|
|
10337
10622
|
}
|
|
10338
10623
|
const lines = [];
|
|
10339
10624
|
lines.push(`\u{1F9E0} **${t("ilink.modelList")}**`);
|
|
10340
|
-
lines.push(t("ilink.modelListDesc"));
|
|
10625
|
+
lines.push("", t("ilink.modelListDesc"));
|
|
10341
10626
|
const modelEntries = [];
|
|
10342
10627
|
modelState.availableModels.forEach((m, i) => {
|
|
10343
10628
|
const marker = m.modelId === modelState.currentModelId ? " \u{1F448}" : "";
|
|
@@ -10347,28 +10632,225 @@ function createIlinkModule(ctx) {
|
|
|
10347
10632
|
});
|
|
10348
10633
|
lines.push("", "---", t("ilink.switchModelHint"));
|
|
10349
10634
|
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10350
|
-
|
|
10351
|
-
const
|
|
10352
|
-
if (
|
|
10635
|
+
const pending = (index) => {
|
|
10636
|
+
const entry = modelEntries[index - 1];
|
|
10637
|
+
if (!entry) {
|
|
10638
|
+
void replyInvalidIndex(msg, modelEntries.length);
|
|
10639
|
+
commandPending = pending;
|
|
10640
|
+
return;
|
|
10641
|
+
}
|
|
10642
|
+
getHandlers().setModel({ sessionId, modelId: entry.modelId }).then(() => {
|
|
10353
10643
|
if (msg.from_user_id)
|
|
10354
10644
|
bridge?.sendTextReply(
|
|
10355
10645
|
msg.from_user_id,
|
|
10356
|
-
t("ilink.
|
|
10646
|
+
t("ilink.switchedToModel", { model: entry.label })
|
|
10357
10647
|
);
|
|
10648
|
+
}).catch((err) => {
|
|
10649
|
+
console.error("[iLink] Failed to set model:", err);
|
|
10650
|
+
if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10651
|
+
});
|
|
10652
|
+
};
|
|
10653
|
+
commandPending = pending;
|
|
10654
|
+
}
|
|
10655
|
+
async function replyNoActiveSession(msg) {
|
|
10656
|
+
if (!msg.from_user_id) return;
|
|
10657
|
+
await bridge?.sendTextReply(
|
|
10658
|
+
msg.from_user_id,
|
|
10659
|
+
[
|
|
10660
|
+
`\u{1F4CB} **${t("ilink.noActiveSession")}**`,
|
|
10661
|
+
"",
|
|
10662
|
+
t("ilink.switchSessionGuide"),
|
|
10663
|
+
"",
|
|
10664
|
+
t("ilink.createSessionGuide")
|
|
10665
|
+
].join("\n")
|
|
10666
|
+
);
|
|
10667
|
+
}
|
|
10668
|
+
const PERMISSION_MODES = ["ask", "allow-all"];
|
|
10669
|
+
function getPermissionModeLabel(mode) {
|
|
10670
|
+
return mode === "allow-all" ? t("ilink.permissionAllowAll") : t("ilink.permissionAsk");
|
|
10671
|
+
}
|
|
10672
|
+
async function handleCommandPermission(msg) {
|
|
10673
|
+
const sessionId = activeSessionId ?? "";
|
|
10674
|
+
const session = sessionId ? storage.getSession(sessionId) : null;
|
|
10675
|
+
if (!session) {
|
|
10676
|
+
await replyNoActiveSession(msg);
|
|
10677
|
+
return;
|
|
10678
|
+
}
|
|
10679
|
+
const lines = [];
|
|
10680
|
+
lines.push(`\u{1F4CB} **${t("ilink.permissionMenu")}**`);
|
|
10681
|
+
lines.push("", t("ilink.permissionMenuDesc"));
|
|
10682
|
+
const modeEntries = [];
|
|
10683
|
+
PERMISSION_MODES.forEach((mode, index) => {
|
|
10684
|
+
const marker = mode === session.permissionMode ? " \u{1F448}" : "";
|
|
10685
|
+
const label = getPermissionModeLabel(mode);
|
|
10686
|
+
lines.push(` ${index + 1}. ${label}${marker}`);
|
|
10687
|
+
modeEntries.push({ mode, label });
|
|
10688
|
+
});
|
|
10689
|
+
lines.push("", "---", t("ilink.switchPermissionHint"));
|
|
10690
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10691
|
+
const pending = (index) => {
|
|
10692
|
+
const entry = modeEntries[index - 1];
|
|
10693
|
+
if (!entry) {
|
|
10694
|
+
void replyInvalidIndex(msg, modeEntries.length);
|
|
10695
|
+
commandPending = pending;
|
|
10358
10696
|
return;
|
|
10359
10697
|
}
|
|
10360
|
-
|
|
10361
|
-
getHandlers().setModel({ sessionId, modelId: entry.modelId }).then(() => {
|
|
10698
|
+
getHandlers().updateSession({ sessionId, permissionMode: entry.mode }).then(() => {
|
|
10362
10699
|
if (msg.from_user_id)
|
|
10363
10700
|
bridge?.sendTextReply(
|
|
10364
10701
|
msg.from_user_id,
|
|
10365
|
-
t("ilink.
|
|
10702
|
+
t("ilink.permissionSwitched", { mode: entry.label })
|
|
10366
10703
|
);
|
|
10367
10704
|
}).catch((err) => {
|
|
10368
|
-
console.error("[iLink] Failed to
|
|
10705
|
+
console.error("[iLink] Failed to update permission mode:", err);
|
|
10369
10706
|
if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10370
10707
|
});
|
|
10371
10708
|
};
|
|
10709
|
+
commandPending = pending;
|
|
10710
|
+
}
|
|
10711
|
+
async function handleCommandFeatures(msg) {
|
|
10712
|
+
const sessionId = activeSessionId ?? "";
|
|
10713
|
+
if (!sessionId || !storage.getSession(sessionId)) {
|
|
10714
|
+
await replyNoActiveSession(msg);
|
|
10715
|
+
return;
|
|
10716
|
+
}
|
|
10717
|
+
const getEntries = () => {
|
|
10718
|
+
const enabled = new Set(storage.getSession(sessionId)?.features ?? []);
|
|
10719
|
+
return ALL_FEATURES.map((feature) => ({
|
|
10720
|
+
id: feature,
|
|
10721
|
+
label: getFeatureLabel(feature),
|
|
10722
|
+
enabled: enabled.has(feature)
|
|
10723
|
+
}));
|
|
10724
|
+
};
|
|
10725
|
+
const buildMenuText = (entries2) => {
|
|
10726
|
+
const lines = [];
|
|
10727
|
+
lines.push(`\u{1F4CB} **${t("ilink.featuresMenu")}**`);
|
|
10728
|
+
lines.push("", t("ilink.featuresMenuDesc"));
|
|
10729
|
+
entries2.forEach((entry, index) => {
|
|
10730
|
+
lines.push(` ${index + 1}. ${entry.enabled ? "\u2713" : "\u2717"} ${entry.label}`);
|
|
10731
|
+
});
|
|
10732
|
+
lines.push("", "---", t("ilink.toggleHint"));
|
|
10733
|
+
return lines.join("\n");
|
|
10734
|
+
};
|
|
10735
|
+
const entries = getEntries();
|
|
10736
|
+
if (entries.length === 0) {
|
|
10737
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, t("ilink.noToggleItems"));
|
|
10738
|
+
return;
|
|
10739
|
+
}
|
|
10740
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, buildMenuText(entries));
|
|
10741
|
+
const pending = async (index) => {
|
|
10742
|
+
if (index === 0) {
|
|
10743
|
+
if (msg.from_user_id)
|
|
10744
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.toggleCancelled"));
|
|
10745
|
+
return;
|
|
10746
|
+
}
|
|
10747
|
+
const current = getEntries();
|
|
10748
|
+
const target = current[index - 1];
|
|
10749
|
+
if (!target) {
|
|
10750
|
+
await replyInvalidIndex(msg, current.length);
|
|
10751
|
+
commandPending = pending;
|
|
10752
|
+
return;
|
|
10753
|
+
}
|
|
10754
|
+
const label = `${t(target.enabled ? "ilink.toggledOff" : "ilink.toggledOn")} ${target.label}`;
|
|
10755
|
+
const nextIds = current.filter((entry) => entry.enabled && entry.id !== target.id).map((entry) => entry.id);
|
|
10756
|
+
if (!target.enabled) nextIds.push(target.id);
|
|
10757
|
+
try {
|
|
10758
|
+
await getHandlers().updateSession({ sessionId, features: nextIds });
|
|
10759
|
+
} catch (err) {
|
|
10760
|
+
console.error("[iLink] Failed to update session features:", err);
|
|
10761
|
+
if (msg.from_user_id)
|
|
10762
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10763
|
+
return;
|
|
10764
|
+
}
|
|
10765
|
+
try {
|
|
10766
|
+
await getHandlers().loadSession({ sessionId, force: true });
|
|
10767
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, label);
|
|
10768
|
+
} catch (err) {
|
|
10769
|
+
console.error("[iLink] Failed to restart session after feature toggle:", err);
|
|
10770
|
+
if (msg.from_user_id)
|
|
10771
|
+
await bridge?.sendTextReply(
|
|
10772
|
+
msg.from_user_id,
|
|
10773
|
+
t("ilink.toggleSavedRestartFailed", { label })
|
|
10774
|
+
);
|
|
10775
|
+
}
|
|
10776
|
+
const fresh = getEntries();
|
|
10777
|
+
if (msg.from_user_id && fresh.length > 0)
|
|
10778
|
+
await bridge?.sendTextReply(msg.from_user_id, buildMenuText(fresh));
|
|
10779
|
+
commandPending = pending;
|
|
10780
|
+
};
|
|
10781
|
+
commandPending = pending;
|
|
10782
|
+
}
|
|
10783
|
+
async function handleCommandMcpServers(msg) {
|
|
10784
|
+
const sessionId = activeSessionId ?? "";
|
|
10785
|
+
if (!sessionId || !storage.getSession(sessionId)) {
|
|
10786
|
+
await replyNoActiveSession(msg);
|
|
10787
|
+
return;
|
|
10788
|
+
}
|
|
10789
|
+
const getEntries = () => {
|
|
10790
|
+
const enabled = new Set(storage.getSession(sessionId)?.mcpServers ?? []);
|
|
10791
|
+
return (storage.getSettings().mcpServers ?? []).map((server) => ({
|
|
10792
|
+
id: server.id,
|
|
10793
|
+
label: server.id,
|
|
10794
|
+
enabled: enabled.has(server.id)
|
|
10795
|
+
}));
|
|
10796
|
+
};
|
|
10797
|
+
const buildMenuText = (entries2) => {
|
|
10798
|
+
const lines = [];
|
|
10799
|
+
lines.push(`\u{1F4CB} **${t("ilink.mcpMenu")}**`);
|
|
10800
|
+
lines.push("", t("ilink.mcpMenuDesc"));
|
|
10801
|
+
entries2.forEach((entry, index) => {
|
|
10802
|
+
lines.push(` ${index + 1}. ${entry.enabled ? "\u2713" : "\u2717"} ${entry.label}`);
|
|
10803
|
+
});
|
|
10804
|
+
lines.push("", "---", t("ilink.toggleHint"));
|
|
10805
|
+
return lines.join("\n");
|
|
10806
|
+
};
|
|
10807
|
+
const entries = getEntries();
|
|
10808
|
+
if (entries.length === 0) {
|
|
10809
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, t("ilink.noToggleItems"));
|
|
10810
|
+
return;
|
|
10811
|
+
}
|
|
10812
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, buildMenuText(entries));
|
|
10813
|
+
const pending = async (index) => {
|
|
10814
|
+
if (index === 0) {
|
|
10815
|
+
if (msg.from_user_id)
|
|
10816
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.toggleCancelled"));
|
|
10817
|
+
return;
|
|
10818
|
+
}
|
|
10819
|
+
const current = getEntries();
|
|
10820
|
+
const target = current[index - 1];
|
|
10821
|
+
if (!target) {
|
|
10822
|
+
await replyInvalidIndex(msg, current.length);
|
|
10823
|
+
commandPending = pending;
|
|
10824
|
+
return;
|
|
10825
|
+
}
|
|
10826
|
+
const label = `${t(target.enabled ? "ilink.toggledOff" : "ilink.toggledOn")} ${target.label}`;
|
|
10827
|
+
const nextIds = current.filter((entry) => entry.enabled && entry.id !== target.id).map((entry) => entry.id);
|
|
10828
|
+
if (!target.enabled) nextIds.push(target.id);
|
|
10829
|
+
try {
|
|
10830
|
+
await getHandlers().updateSession({ sessionId, mcpServers: nextIds });
|
|
10831
|
+
} catch (err) {
|
|
10832
|
+
console.error("[iLink] Failed to update session MCP servers:", err);
|
|
10833
|
+
if (msg.from_user_id)
|
|
10834
|
+
await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10835
|
+
return;
|
|
10836
|
+
}
|
|
10837
|
+
try {
|
|
10838
|
+
await getHandlers().loadSession({ sessionId, force: true });
|
|
10839
|
+
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, label);
|
|
10840
|
+
} catch (err) {
|
|
10841
|
+
console.error("[iLink] Failed to restart session after MCP toggle:", err);
|
|
10842
|
+
if (msg.from_user_id)
|
|
10843
|
+
await bridge?.sendTextReply(
|
|
10844
|
+
msg.from_user_id,
|
|
10845
|
+
t("ilink.toggleSavedRestartFailed", { label })
|
|
10846
|
+
);
|
|
10847
|
+
}
|
|
10848
|
+
const fresh = getEntries();
|
|
10849
|
+
if (msg.from_user_id && fresh.length > 0)
|
|
10850
|
+
await bridge?.sendTextReply(msg.from_user_id, buildMenuText(fresh));
|
|
10851
|
+
commandPending = pending;
|
|
10852
|
+
};
|
|
10853
|
+
commandPending = pending;
|
|
10372
10854
|
}
|
|
10373
10855
|
async function handleCommandSnippet(msg) {
|
|
10374
10856
|
const settings = storage.getSettings();
|
|
@@ -10379,7 +10861,7 @@ function createIlinkModule(ctx) {
|
|
|
10379
10861
|
}
|
|
10380
10862
|
const lines = [];
|
|
10381
10863
|
lines.push(`\u{1F4DD} **${t("ilink.snippetList")}**`);
|
|
10382
|
-
lines.push(t("ilink.snippetListDesc"));
|
|
10864
|
+
lines.push("", t("ilink.snippetListDesc"));
|
|
10383
10865
|
const snippetEntries = [];
|
|
10384
10866
|
snippets.forEach((s, i) => {
|
|
10385
10867
|
const preview = s.content.length > 50 ? s.content.substring(0, 50) + "..." : s.content;
|
|
@@ -10388,17 +10870,13 @@ function createIlinkModule(ctx) {
|
|
|
10388
10870
|
});
|
|
10389
10871
|
lines.push("", "---", t("ilink.selectSnippetHint"));
|
|
10390
10872
|
if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
|
|
10391
|
-
|
|
10392
|
-
const
|
|
10393
|
-
if (
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
msg.from_user_id,
|
|
10397
|
-
t("ilink.invalidSessionNumber", { min: "1", max: String(snippetEntries.length) })
|
|
10398
|
-
);
|
|
10873
|
+
const pending = (index) => {
|
|
10874
|
+
const entry = snippetEntries[index - 1];
|
|
10875
|
+
if (!entry) {
|
|
10876
|
+
void replyInvalidIndex(msg, snippetEntries.length);
|
|
10877
|
+
commandPending = pending;
|
|
10399
10878
|
return;
|
|
10400
10879
|
}
|
|
10401
|
-
const entry = snippetEntries[num - 1];
|
|
10402
10880
|
const sessionId = activeSessionId ?? "";
|
|
10403
10881
|
if (!sessionId) {
|
|
10404
10882
|
if (msg.from_user_id)
|
|
@@ -10408,6 +10886,7 @@ function createIlinkModule(ctx) {
|
|
|
10408
10886
|
`\u{1F4CB} **${t("ilink.noActiveSession")}**`,
|
|
10409
10887
|
"",
|
|
10410
10888
|
t("ilink.switchSessionGuide"),
|
|
10889
|
+
"",
|
|
10411
10890
|
t("ilink.createSessionGuide")
|
|
10412
10891
|
].join("\n")
|
|
10413
10892
|
);
|
|
@@ -10421,43 +10900,55 @@ function createIlinkModule(ctx) {
|
|
|
10421
10900
|
if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
|
|
10422
10901
|
});
|
|
10423
10902
|
};
|
|
10903
|
+
commandPending = pending;
|
|
10424
10904
|
}
|
|
10425
10905
|
async function handleCommandInfo(msg) {
|
|
10426
10906
|
const currentSession = activeSessionId ? storage.getSession(activeSessionId) : null;
|
|
10427
10907
|
const message = (() => {
|
|
10428
10908
|
const lines = [];
|
|
10429
|
-
lines.push(`\u{1F4CB} **${t("ilink.sessionInfo")}**`);
|
|
10430
10909
|
if (!currentSession) {
|
|
10431
10910
|
lines.push(t("ilink.noActiveSession"));
|
|
10432
|
-
lines.push("", "---", t("ilink.switchSessionGuide"), t("ilink.createSessionGuide"));
|
|
10911
|
+
lines.push("", "---", t("ilink.switchSessionGuide"), "", t("ilink.createSessionGuide"));
|
|
10433
10912
|
return lines.join("\n");
|
|
10434
10913
|
}
|
|
10435
10914
|
const projects = storage.listProjects();
|
|
10436
10915
|
const project = projects.find((p) => p.id === currentSession.projectId);
|
|
10437
|
-
lines.push(
|
|
10438
|
-
|
|
10439
|
-
lines.push(`**${t("ilink.
|
|
10440
|
-
|
|
10916
|
+
lines.push(`\u{1F4CB} **${currentSession.title || t("ilink.newSession")}**
|
|
10917
|
+
`);
|
|
10918
|
+
if (project) lines.push(`**${t("ilink.project")}**: ${project.title}
|
|
10919
|
+
`);
|
|
10920
|
+
lines.push(`**${t("ilink.projectDir")}**: \`${currentSession.cwd}\`
|
|
10921
|
+
`);
|
|
10922
|
+
lines.push(`**${t("ilink.agent")}**: \`${currentSession.agentId}\`
|
|
10923
|
+
`);
|
|
10924
|
+
lines.push(
|
|
10925
|
+
`**${t("ilink.permissionMenu")}**: ${getPermissionModeLabel(currentSession.permissionMode)}
|
|
10926
|
+
`
|
|
10927
|
+
);
|
|
10441
10928
|
const enabledFeatures = new Set(currentSession.features ?? []);
|
|
10442
10929
|
lines.push(`**${t("ilink.features")}**:`);
|
|
10443
|
-
for (const f of ALL_FEATURES)
|
|
10444
|
-
|
|
10930
|
+
for (const f of ALL_FEATURES)
|
|
10931
|
+
lines.push(` - ${enabledFeatures.has(f) ? "\u2713" : "\u2717"} ${getFeatureLabel(f)}`);
|
|
10445
10932
|
const sessionMcpIds = new Set(currentSession.mcpServers ?? []);
|
|
10446
|
-
const allMcpServers =
|
|
10933
|
+
const allMcpServers = storage.getSettings().mcpServers ?? [];
|
|
10934
|
+
lines.push("");
|
|
10447
10935
|
if (allMcpServers.length > 0) {
|
|
10448
10936
|
lines.push(`**${t("ilink.mcpServers")}**:`);
|
|
10449
10937
|
for (const srv of allMcpServers)
|
|
10450
|
-
lines.push(` - ${sessionMcpIds.has(srv.id)
|
|
10938
|
+
lines.push(` - ${sessionMcpIds.has(srv.id) ? "\u2713" : "\u2717"} ${srv.id}`);
|
|
10451
10939
|
} else {
|
|
10452
10940
|
lines.push(`**${t("ilink.mcpServers")}**: \u2014`);
|
|
10453
10941
|
}
|
|
10454
10942
|
lines.push(
|
|
10455
10943
|
"",
|
|
10456
10944
|
"---",
|
|
10457
|
-
t("ilink.switchSessionGuide"),
|
|
10458
|
-
t("ilink.createSessionGuide"),
|
|
10459
|
-
t("ilink.modelGuide"),
|
|
10460
|
-
t("ilink.
|
|
10945
|
+
"- " + t("ilink.switchSessionGuide"),
|
|
10946
|
+
"- " + t("ilink.createSessionGuide"),
|
|
10947
|
+
"- " + t("ilink.modelGuide"),
|
|
10948
|
+
"- " + t("ilink.permissionGuide"),
|
|
10949
|
+
"- " + t("ilink.featureGuide"),
|
|
10950
|
+
"- " + t("ilink.mcpServerGuide"),
|
|
10951
|
+
"- " + t("ilink.snippetGuide")
|
|
10461
10952
|
);
|
|
10462
10953
|
return lines.join("\n");
|
|
10463
10954
|
})();
|
|
@@ -10678,6 +11169,11 @@ function createAutomationModule(ctx, deps) {
|
|
|
10678
11169
|
const scheduledCrons = /* @__PURE__ */ new Map();
|
|
10679
11170
|
const runningTasks = /* @__PURE__ */ new Set();
|
|
10680
11171
|
function getNextRun(schedule) {
|
|
11172
|
+
if (schedule.cron.type === "once") {
|
|
11173
|
+
if (isRunLimitReached(schedule)) return null;
|
|
11174
|
+
const at = schedule.cron.at;
|
|
11175
|
+
return at && at > Date.now() ? at : null;
|
|
11176
|
+
}
|
|
10681
11177
|
const job = scheduledCrons.get(schedule.id);
|
|
10682
11178
|
if (!job) return null;
|
|
10683
11179
|
try {
|
|
@@ -10703,6 +11199,34 @@ function createAutomationModule(ctx, deps) {
|
|
|
10703
11199
|
function scheduleCron(schedule) {
|
|
10704
11200
|
unscheduleCron(schedule.id);
|
|
10705
11201
|
if (isRunLimitReached(schedule)) return;
|
|
11202
|
+
if (schedule.cron.type === "once") {
|
|
11203
|
+
const at = schedule.cron.at;
|
|
11204
|
+
if (!at || at <= Date.now()) return;
|
|
11205
|
+
try {
|
|
11206
|
+
const onceJob = new CronJob(
|
|
11207
|
+
new Date(at),
|
|
11208
|
+
async () => {
|
|
11209
|
+
try {
|
|
11210
|
+
await executeTask(schedule.id, "cron");
|
|
11211
|
+
} catch (err) {
|
|
11212
|
+
console.error(`[Automation] Failed to run once task for "${schedule.name}":`, err);
|
|
11213
|
+
} finally {
|
|
11214
|
+
unscheduleCron(schedule.id);
|
|
11215
|
+
}
|
|
11216
|
+
},
|
|
11217
|
+
null,
|
|
11218
|
+
false
|
|
11219
|
+
);
|
|
11220
|
+
scheduledCrons.set(schedule.id, onceJob);
|
|
11221
|
+
onceJob.start();
|
|
11222
|
+
console.log(
|
|
11223
|
+
`[Automation] Scheduled once "${schedule.name}" at ${new Date(at).toLocaleString(storage.getSettings().i18n?.language)}`
|
|
11224
|
+
);
|
|
11225
|
+
} catch (err) {
|
|
11226
|
+
console.error(`[Automation] Failed to schedule once "${schedule.name}":`, err);
|
|
11227
|
+
}
|
|
11228
|
+
return;
|
|
11229
|
+
}
|
|
10706
11230
|
if (schedule.cron.type !== "cron" || !schedule.cron.expr) return;
|
|
10707
11231
|
try {
|
|
10708
11232
|
const cronJob = new CronJob(
|
|
@@ -10742,9 +11266,21 @@ function createAutomationModule(ctx, deps) {
|
|
|
10742
11266
|
function restoreActiveSchedules() {
|
|
10743
11267
|
const schedules = store.listSchedules();
|
|
10744
11268
|
for (const s of schedules) {
|
|
10745
|
-
if (s.cron.type === "cron" && s.cron.expr)
|
|
10746
|
-
|
|
10747
|
-
|
|
11269
|
+
if (s.cron.type === "cron" && s.cron.expr) {
|
|
11270
|
+
scheduleCron(s);
|
|
11271
|
+
} else if (s.cron.type === "once") {
|
|
11272
|
+
if (isRunLimitReached(s)) continue;
|
|
11273
|
+
const at = s.cron.at;
|
|
11274
|
+
if (at && at > Date.now()) {
|
|
11275
|
+
scheduleCron(s);
|
|
11276
|
+
} else {
|
|
11277
|
+
console.log(
|
|
11278
|
+
`[Automation] "${s.name}" once trigger missed (scheduled at ${at ? new Date(at).toLocaleString(storage.getSettings().i18n?.language) : "?"})`
|
|
11279
|
+
);
|
|
11280
|
+
}
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
console.log(`[Automation] Restored ${scheduledCrons.size} active schedule(s)`);
|
|
10748
11284
|
}
|
|
10749
11285
|
function buildAutomationMcpServers(mcpIds) {
|
|
10750
11286
|
const settings = storage.getSettings();
|
|
@@ -10789,6 +11325,10 @@ function createAutomationModule(ctx, deps) {
|
|
|
10789
11325
|
const task = { id: taskId, scheduleId, startedAt, completedAt: null, status: "running" };
|
|
10790
11326
|
store.saveTask(scheduleId, task);
|
|
10791
11327
|
sendEvent("task-update", { scheduleId, task });
|
|
11328
|
+
schedule.lastRunAt = Date.now();
|
|
11329
|
+
schedule.updatedAt = Date.now();
|
|
11330
|
+
store.saveSchedule(schedule);
|
|
11331
|
+
sendEvent("schedules-changed", void 0);
|
|
10792
11332
|
const taskDir = store.taskDir(scheduleId, taskId);
|
|
10793
11333
|
try {
|
|
10794
11334
|
if (source === "cron") consumeRun(schedule);
|
|
@@ -10837,7 +11377,6 @@ function createAutomationModule(ctx, deps) {
|
|
|
10837
11377
|
2
|
|
10838
11378
|
)
|
|
10839
11379
|
);
|
|
10840
|
-
schedule.lastRunAt = Date.now();
|
|
10841
11380
|
schedule.updatedAt = Date.now();
|
|
10842
11381
|
store.saveSchedule(schedule);
|
|
10843
11382
|
sendEvent("schedules-changed", void 0);
|
|
@@ -10864,19 +11403,21 @@ function createAutomationModule(ctx, deps) {
|
|
|
10864
11403
|
}
|
|
10865
11404
|
function createSchedule(params) {
|
|
10866
11405
|
const schedule = store.createSchedule(params);
|
|
10867
|
-
if (schedule.cron.type
|
|
11406
|
+
if (schedule.cron.type !== "manual") scheduleCron(schedule);
|
|
10868
11407
|
return schedule;
|
|
10869
11408
|
}
|
|
10870
11409
|
function updateSchedule(scheduleId, updates) {
|
|
10871
11410
|
const schedule = store.getSchedule(scheduleId);
|
|
10872
11411
|
if (!schedule) throw new Error("Schedule not found");
|
|
10873
11412
|
Object.assign(schedule, updates);
|
|
10874
|
-
if ("
|
|
11413
|
+
if (schedule.cron.type === "once") {
|
|
11414
|
+
schedule.remainingRuns = 1;
|
|
11415
|
+
} else if ("remainingRuns" in updates) {
|
|
10875
11416
|
schedule.remainingRuns = normalizeRemainingRuns(updates.remainingRuns);
|
|
10876
11417
|
}
|
|
10877
11418
|
schedule.updatedAt = Date.now();
|
|
10878
11419
|
store.saveSchedule(schedule);
|
|
10879
|
-
if (schedule.cron.type
|
|
11420
|
+
if (schedule.cron.type !== "manual") scheduleCron(schedule);
|
|
10880
11421
|
else unscheduleCron(scheduleId);
|
|
10881
11422
|
return schedule;
|
|
10882
11423
|
}
|
|
@@ -11503,7 +12044,7 @@ async function callOpenAICompatibleApi(provider, request) {
|
|
|
11503
12044
|
}
|
|
11504
12045
|
function createImageGenerationModule(ctx, deps) {
|
|
11505
12046
|
const { storage } = ctx;
|
|
11506
|
-
function
|
|
12047
|
+
function getActiveProvider() {
|
|
11507
12048
|
const settings = storage.getSettings();
|
|
11508
12049
|
const provider = settings.imageGeneration?.find((p) => p.active);
|
|
11509
12050
|
if (!provider) {
|
|
@@ -11514,7 +12055,7 @@ function createImageGenerationModule(ctx, deps) {
|
|
|
11514
12055
|
return provider;
|
|
11515
12056
|
}
|
|
11516
12057
|
async function generateImage(sessionId, request) {
|
|
11517
|
-
const provider =
|
|
12058
|
+
const provider = getActiveProvider();
|
|
11518
12059
|
const apiResult = await callOpenAICompatibleApi(provider, request);
|
|
11519
12060
|
const shareDir = storage.sessionShareDir(sessionId);
|
|
11520
12061
|
if (!shareDir) throw new Error(`Session not found: ${sessionId}`);
|
|
@@ -11587,8 +12128,8 @@ function createImageGenerationModule(ctx, deps) {
|
|
|
11587
12128
|
}
|
|
11588
12129
|
|
|
11589
12130
|
// src/backend/toolbox.ts
|
|
11590
|
-
import { join as
|
|
11591
|
-
import { writeFile as
|
|
12131
|
+
import { join as join25, resolve as resolve8, dirname as dirname11, basename as basename4, extname as extname3 } from "path";
|
|
12132
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
11592
12133
|
import { createHash as createHash6, randomUUID as randomUUID13, randomInt } from "crypto";
|
|
11593
12134
|
import * as qr from "qr-image";
|
|
11594
12135
|
|
|
@@ -11733,6 +12274,606 @@ var qrCodeRespondSchema = z10.object({
|
|
|
11733
12274
|
output: z10.string()
|
|
11734
12275
|
})
|
|
11735
12276
|
});
|
|
12277
|
+
var audioTranscribeRequestSchema = z10.object({
|
|
12278
|
+
path: z10.string().describe("Absolute or project-relative path to the audio file to transcribe."),
|
|
12279
|
+
language: z10.string().optional().describe(
|
|
12280
|
+
"BCP-47 language hint, e.g. 'zh-CN' or 'en-US'. If omitted, the language configured for the active speech-to-text provider is used."
|
|
12281
|
+
),
|
|
12282
|
+
ffmpegPath: z10.string().optional().describe(
|
|
12283
|
+
"Absolute path to the ffmpeg executable, for when it is not on PATH (e.g. a custom install location). When provided it is used as-is: an unusable path is reported as an error instead of falling back to auto-detection. Omit it to auto-detect (FFMPEG_PATH env var, then PATH, then common install directories)."
|
|
12284
|
+
),
|
|
12285
|
+
timeoutSeconds: z10.number().int().min(10).max(3600).default(600).describe("Maximum time to wait for the transcription, in seconds (10-3600). Default: 600.")
|
|
12286
|
+
});
|
|
12287
|
+
var audioTranscribeRespondSchema = z10.object({
|
|
12288
|
+
result: z10.object({
|
|
12289
|
+
text: z10.string(),
|
|
12290
|
+
durationSeconds: z10.number(),
|
|
12291
|
+
segments: z10.number()
|
|
12292
|
+
})
|
|
12293
|
+
});
|
|
12294
|
+
var textToSpeechRequestSchema = z10.object({
|
|
12295
|
+
text: z10.string().min(1).describe("The text to synthesize into speech."),
|
|
12296
|
+
output: z10.string().optional().describe(
|
|
12297
|
+
"Output audio path; a relative path resolves against the project root. If omitted, saves as 'speech.wav' (or 'speech.mp3' when format is 'mp3') in the project root."
|
|
12298
|
+
),
|
|
12299
|
+
format: z10.enum(["wav", "mp3"]).default("wav").describe(
|
|
12300
|
+
"Output audio format. 'wav' needs no extra tools; 'mp3' additionally requires the system ffmpeg. Default: wav."
|
|
12301
|
+
)
|
|
12302
|
+
});
|
|
12303
|
+
var textToSpeechRespondSchema = z10.object({
|
|
12304
|
+
result: z10.object({
|
|
12305
|
+
output: z10.string(),
|
|
12306
|
+
format: z10.enum(["wav", "mp3"]),
|
|
12307
|
+
sampleRate: z10.number(),
|
|
12308
|
+
channels: z10.number(),
|
|
12309
|
+
durationSeconds: z10.number(),
|
|
12310
|
+
bytes: z10.number()
|
|
12311
|
+
})
|
|
12312
|
+
});
|
|
12313
|
+
|
|
12314
|
+
// src/backend/speech/transcribe.ts
|
|
12315
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
12316
|
+
import { createASRClient } from "unified-realtime-asr";
|
|
12317
|
+
|
|
12318
|
+
// src/shared/speech.ts
|
|
12319
|
+
var DASHSCOPE_ASR_MODEL = "fun-asr-flash-8k-realtime";
|
|
12320
|
+
var OPENAI_ASR_MODEL = "gpt-4o-transcribe";
|
|
12321
|
+
var DEFAULT_ASR_MODELS = {
|
|
12322
|
+
dashscope: DASHSCOPE_ASR_MODEL,
|
|
12323
|
+
openai: OPENAI_ASR_MODEL
|
|
12324
|
+
};
|
|
12325
|
+
function effectiveAsrModel(provider) {
|
|
12326
|
+
return provider.asrModel?.trim() || DEFAULT_ASR_MODELS[provider.provider];
|
|
12327
|
+
}
|
|
12328
|
+
var DASHSCOPE_TTS_VOICE = "longanhuan_v3.6";
|
|
12329
|
+
var OPENAI_TTS_VOICE = "coral";
|
|
12330
|
+
var IFLYTEK_TTS_VCN = "x5_lingxiaoxuan_flow";
|
|
12331
|
+
var VOLC_TTS_VOICE = "zh_female_vv_uranus_bigtts";
|
|
12332
|
+
var DEFAULT_TTS_VOICES = {
|
|
12333
|
+
dashscope: DASHSCOPE_TTS_VOICE,
|
|
12334
|
+
openai: OPENAI_TTS_VOICE,
|
|
12335
|
+
iflytek: IFLYTEK_TTS_VCN,
|
|
12336
|
+
volcengine: VOLC_TTS_VOICE
|
|
12337
|
+
};
|
|
12338
|
+
function effectiveTtsVoice(provider) {
|
|
12339
|
+
return provider.voice?.trim() || DEFAULT_TTS_VOICES[provider.provider];
|
|
12340
|
+
}
|
|
12341
|
+
|
|
12342
|
+
// src/backend/speech/util.ts
|
|
12343
|
+
function optionalString(value) {
|
|
12344
|
+
const trimmed = value?.trim();
|
|
12345
|
+
return trimmed || void 0;
|
|
12346
|
+
}
|
|
12347
|
+
function requireField(value, name, direction) {
|
|
12348
|
+
const result = optionalString(value);
|
|
12349
|
+
if (!result) throw new Error(`${direction}\u914D\u7F6E\u7F3A\u5C11 ${name}\u3002`);
|
|
12350
|
+
return result;
|
|
12351
|
+
}
|
|
12352
|
+
function errorMessage(error) {
|
|
12353
|
+
return error instanceof Error ? error.message : String(error);
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12356
|
+
// src/backend/speech/asr-config.ts
|
|
12357
|
+
function required(value, name) {
|
|
12358
|
+
return requireField(value, name, "\u5B9E\u65F6\u8BED\u97F3\u8BC6\u522B");
|
|
12359
|
+
}
|
|
12360
|
+
function buildOptions(provider, overrides = {}) {
|
|
12361
|
+
return {
|
|
12362
|
+
language: optionalString(overrides.language) ?? optionalString(provider.language) ?? "zh-CN",
|
|
12363
|
+
sampleRate: 16e3,
|
|
12364
|
+
channels: 1,
|
|
12365
|
+
format: "pcm",
|
|
12366
|
+
interimResults: true,
|
|
12367
|
+
punctuation: true,
|
|
12368
|
+
autoReconnect: false,
|
|
12369
|
+
transcriptionModel: provider.provider === "openai" ? effectiveAsrModel(provider) : void 0
|
|
12370
|
+
};
|
|
12371
|
+
}
|
|
12372
|
+
function buildAsrConfig(provider, overrides = {}) {
|
|
12373
|
+
const options = buildOptions(provider, overrides);
|
|
12374
|
+
const url = optionalString(provider.baseUrl);
|
|
12375
|
+
switch (provider.provider) {
|
|
12376
|
+
case "volcengine":
|
|
12377
|
+
return {
|
|
12378
|
+
provider: "volcengine",
|
|
12379
|
+
apiKey: required(provider.apiKey, "API Key"),
|
|
12380
|
+
resourceId: optionalString(provider.asrResourceId),
|
|
12381
|
+
appId: optionalString(provider.appId),
|
|
12382
|
+
url,
|
|
12383
|
+
options
|
|
12384
|
+
};
|
|
12385
|
+
case "dashscope":
|
|
12386
|
+
return {
|
|
12387
|
+
provider: "dashscope",
|
|
12388
|
+
apiKey: required(provider.apiKey, "API Key"),
|
|
12389
|
+
model: effectiveAsrModel(provider),
|
|
12390
|
+
workspaceId: optionalString(provider.workspaceId),
|
|
12391
|
+
region: provider.region,
|
|
12392
|
+
workspace: optionalString(provider.workspace),
|
|
12393
|
+
url,
|
|
12394
|
+
options
|
|
12395
|
+
};
|
|
12396
|
+
case "openai":
|
|
12397
|
+
return {
|
|
12398
|
+
provider: "openai",
|
|
12399
|
+
apiKey: required(provider.apiKey, "API Key"),
|
|
12400
|
+
url,
|
|
12401
|
+
options
|
|
12402
|
+
};
|
|
12403
|
+
case "iflytek":
|
|
12404
|
+
return {
|
|
12405
|
+
provider: "iflytek",
|
|
12406
|
+
appId: required(provider.appId, "App ID"),
|
|
12407
|
+
apiKey: required(provider.apiKey, "API Key"),
|
|
12408
|
+
apiSecret: required(provider.apiSecret, "API Secret"),
|
|
12409
|
+
url,
|
|
12410
|
+
options
|
|
12411
|
+
};
|
|
12412
|
+
}
|
|
12413
|
+
}
|
|
12414
|
+
function getActiveAsrProvider(ctx) {
|
|
12415
|
+
const provider = ctx.storage.getSettings().speechProviders.find((item) => item.asrEnabled);
|
|
12416
|
+
if (!provider) {
|
|
12417
|
+
throw new Error(
|
|
12418
|
+
"\u8BBE\u7F6E\u4E2D\u6CA1\u6709\u627E\u5230\u8BED\u97F3\u8BC6\u522B\uFF08ASR\uFF09\u914D\u7F6E\uFF1A\u5F53\u524D\u6CA1\u6709\u542F\u7528\u4E2D\u7684 Provider\u3002\u8BF7\u5148\u5728\u300C\u8BBE\u7F6E \u2192 \u8BED\u97F3 \u2192 \u8BC6\u522B\u300D\u4E2D\u914D\u7F6E\u5E76\u542F\u7528\u4E00\u4E2A Provider\uFF0C\u7136\u540E\u91CD\u65B0\u8C03\u7528\u672C\u5DE5\u5177\u3002"
|
|
12419
|
+
);
|
|
12420
|
+
}
|
|
12421
|
+
return provider;
|
|
12422
|
+
}
|
|
12423
|
+
|
|
12424
|
+
// src/backend/speech/ffmpeg.ts
|
|
12425
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
12426
|
+
import { access, constants } from "node:fs/promises";
|
|
12427
|
+
import { delimiter, dirname as dirname9, join as join24 } from "node:path";
|
|
12428
|
+
var FFMPEG_BIN = process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg";
|
|
12429
|
+
var MAX_STDERR_BYTES = 8192;
|
|
12430
|
+
function fallbackDirs() {
|
|
12431
|
+
switch (process.platform) {
|
|
12432
|
+
case "darwin":
|
|
12433
|
+
return ["/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin", "/usr/bin"];
|
|
12434
|
+
case "win32": {
|
|
12435
|
+
const dirs = ["C:\\ffmpeg\\bin"];
|
|
12436
|
+
if (process.env.ProgramFiles) dirs.unshift(join24(process.env.ProgramFiles, "ffmpeg", "bin"));
|
|
12437
|
+
if (process.env.LOCALAPPDATA) {
|
|
12438
|
+
dirs.push(join24(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Links"));
|
|
12439
|
+
}
|
|
12440
|
+
return dirs;
|
|
12441
|
+
}
|
|
12442
|
+
default:
|
|
12443
|
+
return ["/usr/bin", "/usr/local/bin", "/snap/bin", "/var/lib/flatpak/exports/bin"];
|
|
12444
|
+
}
|
|
12445
|
+
}
|
|
12446
|
+
async function isExecutable(filePath) {
|
|
12447
|
+
try {
|
|
12448
|
+
await access(filePath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
12449
|
+
return true;
|
|
12450
|
+
} catch {
|
|
12451
|
+
return false;
|
|
12452
|
+
}
|
|
12453
|
+
}
|
|
12454
|
+
function ffmpegInstallHint() {
|
|
12455
|
+
const howTo = process.platform === "darwin" ? " macOS: brew install ffmpeg" : process.platform === "win32" ? " Windows: winget install --id Gyan.FFmpeg -e (\u6216 choco install ffmpeg)" : " Linux: sudo apt install ffmpeg (\u6216 sudo dnf install ffmpeg)";
|
|
12456
|
+
return [
|
|
12457
|
+
"\u672A\u627E\u5230 ffmpeg\uFF0C\u65E0\u6CD5\u89E3\u7801\u97F3\u9891\u6587\u4EF6\u3002",
|
|
12458
|
+
"\u8BF7\u5148\u5B89\u88C5 ffmpeg\uFF0C\u7136\u540E\u91CD\u65B0\u8C03\u7528\u672C\u5DE5\u5177\uFF1A",
|
|
12459
|
+
howTo,
|
|
12460
|
+
"\u4E5F\u53EF\u4EE5\u7528 FFMPEG_PATH \u73AF\u5883\u53D8\u91CF\u6307\u5B9A ffmpeg \u53EF\u6267\u884C\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002"
|
|
12461
|
+
].join("\n");
|
|
12462
|
+
}
|
|
12463
|
+
function ffmpegPathUnusableHint(explicitPath) {
|
|
12464
|
+
return [
|
|
12465
|
+
`\u6307\u5B9A\u7684 ffmpeg \u8DEF\u5F84\u4E0D\u53EF\u7528\uFF1A${explicitPath}`,
|
|
12466
|
+
"\u8BE5\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u6CA1\u6709\u6267\u884C\u6743\u9650\u3002",
|
|
12467
|
+
"\u8BF7\u4F20\u5165\u6B63\u786E\u7684 ffmpegPath\uFF0C\u6216\u8005\u4E0D\u4F20\u8BE5\u53C2\u6570\u3001\u7531\u5DE5\u5177\u81EA\u52A8\u63A2\u6D4B\uFF08FFMPEG_PATH \u73AF\u5883\u53D8\u91CF \u2192 \u7CFB\u7EDF PATH \u2192 \u5E38\u89C1\u5B89\u88C5\u76EE\u5F55\uFF09\u3002"
|
|
12468
|
+
].join("\n");
|
|
12469
|
+
}
|
|
12470
|
+
async function resolveFfmpegPath(explicitPath) {
|
|
12471
|
+
const explicit = explicitPath?.trim();
|
|
12472
|
+
if (explicit) {
|
|
12473
|
+
return await isExecutable(explicit) ? explicit : null;
|
|
12474
|
+
}
|
|
12475
|
+
const candidates = [];
|
|
12476
|
+
const envPath = process.env.FFMPEG_PATH?.trim();
|
|
12477
|
+
if (envPath) candidates.push(envPath);
|
|
12478
|
+
const dirs = [
|
|
12479
|
+
...envPath ? [dirname9(envPath)] : [],
|
|
12480
|
+
...(process.env.PATH ?? "").split(delimiter),
|
|
12481
|
+
...fallbackDirs()
|
|
12482
|
+
];
|
|
12483
|
+
for (const dir of dirs) {
|
|
12484
|
+
if (dir) candidates.push(join24(dir, FFMPEG_BIN));
|
|
12485
|
+
}
|
|
12486
|
+
for (const candidate of candidates) {
|
|
12487
|
+
if (await isExecutable(candidate)) return candidate;
|
|
12488
|
+
}
|
|
12489
|
+
return null;
|
|
12490
|
+
}
|
|
12491
|
+
function decodeArgs(inputPath) {
|
|
12492
|
+
return [
|
|
12493
|
+
"-nostdin",
|
|
12494
|
+
"-hide_banner",
|
|
12495
|
+
"-loglevel",
|
|
12496
|
+
"error",
|
|
12497
|
+
"-i",
|
|
12498
|
+
inputPath,
|
|
12499
|
+
"-vn",
|
|
12500
|
+
"-f",
|
|
12501
|
+
"s16le",
|
|
12502
|
+
"-acodec",
|
|
12503
|
+
"pcm_s16le",
|
|
12504
|
+
"-ac",
|
|
12505
|
+
"1",
|
|
12506
|
+
"-ar",
|
|
12507
|
+
"16000",
|
|
12508
|
+
"-"
|
|
12509
|
+
];
|
|
12510
|
+
}
|
|
12511
|
+
async function decodeAudioToPcm16k(options) {
|
|
12512
|
+
const { ffmpegPath, inputPath, onChunk, signal } = options;
|
|
12513
|
+
const child = spawn4(ffmpegPath, decodeArgs(inputPath), {
|
|
12514
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12515
|
+
windowsHide: true
|
|
12516
|
+
});
|
|
12517
|
+
let stderr = "";
|
|
12518
|
+
child.stderr.setEncoding("utf8");
|
|
12519
|
+
child.stderr.on("data", (chunk) => {
|
|
12520
|
+
if (stderr.length < MAX_STDERR_BYTES) stderr += chunk;
|
|
12521
|
+
});
|
|
12522
|
+
const abort = () => child.kill("SIGKILL");
|
|
12523
|
+
if (signal?.aborted) abort();
|
|
12524
|
+
else signal?.addEventListener("abort", abort, { once: true });
|
|
12525
|
+
const exited = new Promise(
|
|
12526
|
+
(resolve9, reject) => {
|
|
12527
|
+
child.once("error", reject);
|
|
12528
|
+
child.once("close", (code2, closeSignal2) => resolve9({ code: code2, signal: closeSignal2 }));
|
|
12529
|
+
}
|
|
12530
|
+
);
|
|
12531
|
+
try {
|
|
12532
|
+
for await (const chunk of child.stdout) {
|
|
12533
|
+
await onChunk(chunk);
|
|
12534
|
+
}
|
|
12535
|
+
} catch (error) {
|
|
12536
|
+
child.kill("SIGKILL");
|
|
12537
|
+
await exited.catch(() => {
|
|
12538
|
+
});
|
|
12539
|
+
throw error;
|
|
12540
|
+
} finally {
|
|
12541
|
+
signal?.removeEventListener("abort", abort);
|
|
12542
|
+
}
|
|
12543
|
+
const { code, signal: closeSignal } = await exited;
|
|
12544
|
+
if (code !== 0) {
|
|
12545
|
+
const detail = stderr.trim() || `\u8FDB\u7A0B\u88AB\u7EC8\u6B62\uFF08${closeSignal ?? "unknown"}\uFF09`;
|
|
12546
|
+
throw new Error(`ffmpeg \u89E3\u7801\u97F3\u9891\u5931\u8D25\uFF1A${detail}`);
|
|
12547
|
+
}
|
|
12548
|
+
}
|
|
12549
|
+
var MP3_ARGS = ["-codec:a", "libmp3lame", "-b:a", "128k", "-f", "mp3"];
|
|
12550
|
+
async function encodePcmToMp3(options) {
|
|
12551
|
+
const { ffmpegPath, pcm, sampleRate, channels, outputPath } = options;
|
|
12552
|
+
const child = spawn4(
|
|
12553
|
+
ffmpegPath,
|
|
12554
|
+
[
|
|
12555
|
+
"-y",
|
|
12556
|
+
"-nostdin",
|
|
12557
|
+
"-hide_banner",
|
|
12558
|
+
"-loglevel",
|
|
12559
|
+
"error",
|
|
12560
|
+
"-f",
|
|
12561
|
+
"s16le",
|
|
12562
|
+
"-ar",
|
|
12563
|
+
String(sampleRate),
|
|
12564
|
+
"-ac",
|
|
12565
|
+
String(channels),
|
|
12566
|
+
"-i",
|
|
12567
|
+
"-",
|
|
12568
|
+
...MP3_ARGS,
|
|
12569
|
+
outputPath
|
|
12570
|
+
],
|
|
12571
|
+
{ stdio: ["pipe", "ignore", "pipe"], windowsHide: true }
|
|
12572
|
+
);
|
|
12573
|
+
let stderr = "";
|
|
12574
|
+
child.stderr.setEncoding("utf8");
|
|
12575
|
+
child.stderr.on("data", (chunk) => {
|
|
12576
|
+
if (stderr.length < MAX_STDERR_BYTES) stderr += chunk;
|
|
12577
|
+
});
|
|
12578
|
+
const exited = new Promise((resolve9, reject) => {
|
|
12579
|
+
child.once("error", reject);
|
|
12580
|
+
child.once("close", (code2) => resolve9(code2));
|
|
12581
|
+
});
|
|
12582
|
+
try {
|
|
12583
|
+
await new Promise((resolve9, reject) => {
|
|
12584
|
+
child.stdin.once("error", reject);
|
|
12585
|
+
child.stdin.end(pcm, () => resolve9());
|
|
12586
|
+
});
|
|
12587
|
+
} catch (error) {
|
|
12588
|
+
child.kill("SIGKILL");
|
|
12589
|
+
await exited.catch(() => {
|
|
12590
|
+
});
|
|
12591
|
+
throw new Error(
|
|
12592
|
+
`ffmpeg \u7F16\u7801\u97F3\u9891\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
12593
|
+
);
|
|
12594
|
+
}
|
|
12595
|
+
const code = await exited;
|
|
12596
|
+
if (code !== 0) {
|
|
12597
|
+
const detail = stderr.trim() || `\u9000\u51FA\u7801 ${code}`;
|
|
12598
|
+
throw new Error(`ffmpeg \u7F16\u7801 mp3 \u5931\u8D25\uFF1A${detail}`);
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
|
|
12602
|
+
// src/backend/speech/transcribe.ts
|
|
12603
|
+
var FEED_FRAME_BYTES = 320 * 2;
|
|
12604
|
+
var FEED_FRAME_MS = 20;
|
|
12605
|
+
var FEED_SPEED = 20;
|
|
12606
|
+
var FLUSH_WAIT_MS = 1500;
|
|
12607
|
+
var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
12608
|
+
function sleep(ms) {
|
|
12609
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
12610
|
+
}
|
|
12611
|
+
var LOCAL_INDEX_BASE = 1e6;
|
|
12612
|
+
function joinFinals(finals) {
|
|
12613
|
+
return [...finals.entries()].sort((a, b) => a[0] - b[0]).map(([, text]) => text).join("");
|
|
12614
|
+
}
|
|
12615
|
+
async function transcribeAudioFile(ctx, options) {
|
|
12616
|
+
const provider = getActiveAsrProvider(ctx);
|
|
12617
|
+
await stat6(options.path).catch(() => {
|
|
12618
|
+
throw new Error(`\u97F3\u9891\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${options.path}`);
|
|
12619
|
+
});
|
|
12620
|
+
const explicitFfmpegPath = options.ffmpegPath?.trim();
|
|
12621
|
+
const ffmpegPath = await resolveFfmpegPath(explicitFfmpegPath);
|
|
12622
|
+
if (!ffmpegPath) {
|
|
12623
|
+
throw new Error(
|
|
12624
|
+
explicitFfmpegPath ? ffmpegPathUnusableHint(explicitFfmpegPath) : ffmpegInstallHint()
|
|
12625
|
+
);
|
|
12626
|
+
}
|
|
12627
|
+
const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
12628
|
+
const timeoutError = () => new Error(`\u97F3\u9891\u6587\u4EF6\u8F6C\u5199\u8D85\u65F6\uFF08\u8D85\u8FC7 ${timeoutSeconds} \u79D2\uFF09\u3002`);
|
|
12629
|
+
const client2 = createASRClient(buildAsrConfig(provider, { language: options.language }));
|
|
12630
|
+
const finals = /* @__PURE__ */ new Map();
|
|
12631
|
+
let seq = 0;
|
|
12632
|
+
let lastError = null;
|
|
12633
|
+
client2.on("transcript", (transcript) => {
|
|
12634
|
+
if (!transcript.isFinal) return;
|
|
12635
|
+
finals.set(transcript.index ?? LOCAL_INDEX_BASE + ++seq, transcript.text);
|
|
12636
|
+
});
|
|
12637
|
+
client2.on("error", (error) => {
|
|
12638
|
+
lastError = errorMessage(error);
|
|
12639
|
+
});
|
|
12640
|
+
const controller = new AbortController();
|
|
12641
|
+
const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1e3);
|
|
12642
|
+
let fedBytes = 0;
|
|
12643
|
+
let pending = Buffer.alloc(0);
|
|
12644
|
+
let closed = false;
|
|
12645
|
+
let startedAt = 0;
|
|
12646
|
+
let fedMs = 0;
|
|
12647
|
+
const sendFrame = (frame) => {
|
|
12648
|
+
if (controller.signal.aborted) return;
|
|
12649
|
+
try {
|
|
12650
|
+
client2.sendAudio(frame);
|
|
12651
|
+
} catch (error) {
|
|
12652
|
+
throw new Error(`\u8BED\u97F3\u8BC6\u522B\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF1A${errorMessage(error)}`);
|
|
12653
|
+
}
|
|
12654
|
+
fedBytes += frame.length;
|
|
12655
|
+
};
|
|
12656
|
+
const feed = async (chunk) => {
|
|
12657
|
+
pending = pending.length === 0 ? chunk : Buffer.concat([pending, chunk]);
|
|
12658
|
+
while (pending.length >= FEED_FRAME_BYTES) {
|
|
12659
|
+
if (controller.signal.aborted) return;
|
|
12660
|
+
const frame = pending.subarray(0, FEED_FRAME_BYTES);
|
|
12661
|
+
pending = pending.subarray(FEED_FRAME_BYTES);
|
|
12662
|
+
sendFrame(frame);
|
|
12663
|
+
fedMs += FEED_FRAME_MS;
|
|
12664
|
+
const wait = startedAt + fedMs / FEED_SPEED - Date.now();
|
|
12665
|
+
if (wait > 1) await sleep(wait);
|
|
12666
|
+
}
|
|
12667
|
+
};
|
|
12668
|
+
try {
|
|
12669
|
+
await client2.connect();
|
|
12670
|
+
startedAt = Date.now();
|
|
12671
|
+
try {
|
|
12672
|
+
await decodeAudioToPcm16k({
|
|
12673
|
+
ffmpegPath,
|
|
12674
|
+
inputPath: options.path,
|
|
12675
|
+
signal: controller.signal,
|
|
12676
|
+
onChunk: feed
|
|
12677
|
+
});
|
|
12678
|
+
} catch (error) {
|
|
12679
|
+
if (controller.signal.aborted) throw timeoutError();
|
|
12680
|
+
throw error;
|
|
12681
|
+
}
|
|
12682
|
+
if (controller.signal.aborted) throw timeoutError();
|
|
12683
|
+
if (pending.length > 0) sendFrame(pending);
|
|
12684
|
+
await sleep(FLUSH_WAIT_MS);
|
|
12685
|
+
await client2.close();
|
|
12686
|
+
closed = true;
|
|
12687
|
+
if (finals.size === 0 && lastError) throw new Error(lastError);
|
|
12688
|
+
return {
|
|
12689
|
+
text: joinFinals(finals),
|
|
12690
|
+
durationSeconds: Math.round(fedBytes / 2 / 16e3 * 10) / 10,
|
|
12691
|
+
segments: finals.size
|
|
12692
|
+
};
|
|
12693
|
+
} finally {
|
|
12694
|
+
clearTimeout(timer);
|
|
12695
|
+
client2.removeAllListeners();
|
|
12696
|
+
if (!closed) await client2.close().catch(() => {
|
|
12697
|
+
});
|
|
12698
|
+
}
|
|
12699
|
+
}
|
|
12700
|
+
|
|
12701
|
+
// src/backend/speech/synthesize.ts
|
|
12702
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
12703
|
+
import { mkdir as mkdir3, stat as stat7, writeFile as writeFile6 } from "node:fs/promises";
|
|
12704
|
+
import { dirname as dirname10 } from "node:path";
|
|
12705
|
+
import { createTTSClient } from "unified-realtime-asr";
|
|
12706
|
+
|
|
12707
|
+
// src/backend/speech/tts-config.ts
|
|
12708
|
+
function required2(value, name) {
|
|
12709
|
+
return requireField(value, name, "\u8BED\u97F3\u5408\u6210");
|
|
12710
|
+
}
|
|
12711
|
+
function buildTtsConfig(provider) {
|
|
12712
|
+
const options = {
|
|
12713
|
+
voice: effectiveTtsVoice(provider),
|
|
12714
|
+
format: "pcm",
|
|
12715
|
+
language: optionalString(provider.language),
|
|
12716
|
+
autoReconnect: false
|
|
12717
|
+
};
|
|
12718
|
+
const baseUrl = optionalString(provider.baseUrl);
|
|
12719
|
+
switch (provider.provider) {
|
|
12720
|
+
case "volcengine":
|
|
12721
|
+
return {
|
|
12722
|
+
provider: "volcengine",
|
|
12723
|
+
apiKey: required2(provider.apiKey, "API Key"),
|
|
12724
|
+
appId: optionalString(provider.appId),
|
|
12725
|
+
// 双向流式:文本可增量输入,配合逐句 flush 延迟最低
|
|
12726
|
+
mode: "duplex",
|
|
12727
|
+
url: baseUrl,
|
|
12728
|
+
options
|
|
12729
|
+
};
|
|
12730
|
+
case "dashscope":
|
|
12731
|
+
return {
|
|
12732
|
+
provider: "dashscope",
|
|
12733
|
+
apiKey: required2(provider.apiKey, "API Key"),
|
|
12734
|
+
model: optionalString(provider.ttsModel),
|
|
12735
|
+
workspaceId: optionalString(provider.workspaceId),
|
|
12736
|
+
region: provider.region,
|
|
12737
|
+
workspace: optionalString(provider.workspace),
|
|
12738
|
+
url: baseUrl,
|
|
12739
|
+
options
|
|
12740
|
+
};
|
|
12741
|
+
case "openai":
|
|
12742
|
+
return {
|
|
12743
|
+
provider: "openai",
|
|
12744
|
+
apiKey: required2(provider.apiKey, "API Key"),
|
|
12745
|
+
model: optionalString(provider.ttsModel),
|
|
12746
|
+
baseUrl,
|
|
12747
|
+
options
|
|
12748
|
+
};
|
|
12749
|
+
case "iflytek":
|
|
12750
|
+
return {
|
|
12751
|
+
provider: "iflytek",
|
|
12752
|
+
appId: required2(provider.appId, "App ID"),
|
|
12753
|
+
apiKey: required2(provider.apiKey, "API Key"),
|
|
12754
|
+
apiSecret: required2(provider.apiSecret, "API Secret"),
|
|
12755
|
+
url: baseUrl,
|
|
12756
|
+
options
|
|
12757
|
+
};
|
|
12758
|
+
}
|
|
12759
|
+
}
|
|
12760
|
+
function getActiveTtsProvider(ctx) {
|
|
12761
|
+
const provider = ctx.storage.getSettings().speechProviders.find((item) => item.ttsEnabled);
|
|
12762
|
+
if (!provider) {
|
|
12763
|
+
throw new Error(
|
|
12764
|
+
"\u8BBE\u7F6E\u4E2D\u6CA1\u6709\u627E\u5230\u8BED\u97F3\u5408\u6210\uFF08TTS\uFF09\u914D\u7F6E\uFF1A\u5F53\u524D\u6CA1\u6709\u542F\u7528\u4E2D\u7684 Provider\u3002\u8BF7\u5148\u5728\u300C\u8BBE\u7F6E \u2192 \u8BED\u97F3 \u2192 \u5408\u6210\u300D\u4E2D\u914D\u7F6E\u5E76\u542F\u7528\u4E00\u4E2A Provider\uFF0C\u7136\u540E\u91CD\u8BD5\u3002"
|
|
12765
|
+
);
|
|
12766
|
+
}
|
|
12767
|
+
return provider;
|
|
12768
|
+
}
|
|
12769
|
+
|
|
12770
|
+
// src/backend/speech/synthesize.ts
|
|
12771
|
+
var MAX_TEXT_LENGTH = 1e4;
|
|
12772
|
+
var FALLBACK_SAMPLE_RATE = 24e3;
|
|
12773
|
+
var FALLBACK_CHANNELS = 1;
|
|
12774
|
+
var DEFAULT_TIMEOUT_SECONDS2 = 600;
|
|
12775
|
+
function encodeWav(pcm, sampleRate, channels) {
|
|
12776
|
+
const bitsPerSample = 16;
|
|
12777
|
+
const blockAlign = channels * (bitsPerSample / 8);
|
|
12778
|
+
const header = Buffer2.alloc(44);
|
|
12779
|
+
header.write("RIFF", 0, "ascii");
|
|
12780
|
+
header.writeUInt32LE(36 + pcm.length, 4);
|
|
12781
|
+
header.write("WAVE", 8, "ascii");
|
|
12782
|
+
header.write("fmt ", 12, "ascii");
|
|
12783
|
+
header.writeUInt32LE(16, 16);
|
|
12784
|
+
header.writeUInt16LE(1, 20);
|
|
12785
|
+
header.writeUInt16LE(channels, 22);
|
|
12786
|
+
header.writeUInt32LE(sampleRate, 24);
|
|
12787
|
+
header.writeUInt32LE(sampleRate * blockAlign, 28);
|
|
12788
|
+
header.writeUInt16LE(blockAlign, 32);
|
|
12789
|
+
header.writeUInt16LE(bitsPerSample, 34);
|
|
12790
|
+
header.write("data", 36, "ascii");
|
|
12791
|
+
header.writeUInt32LE(pcm.length, 40);
|
|
12792
|
+
return Buffer2.concat([header, pcm]);
|
|
12793
|
+
}
|
|
12794
|
+
async function requireFfmpegPath() {
|
|
12795
|
+
const ffmpegPath = await resolveFfmpegPath();
|
|
12796
|
+
if (!ffmpegPath) throw new Error(ffmpegInstallHint());
|
|
12797
|
+
return ffmpegPath;
|
|
12798
|
+
}
|
|
12799
|
+
async function synthesizeSpeechToFile(ctx, options) {
|
|
12800
|
+
const text = options.text.trim();
|
|
12801
|
+
if (!text) throw new Error("\u5F85\u5408\u6210\u7684\u6587\u672C\u4E3A\u7A7A\u3002");
|
|
12802
|
+
if (text.length > MAX_TEXT_LENGTH) {
|
|
12803
|
+
throw new Error(
|
|
12804
|
+
`\u5F85\u5408\u6210\u6587\u672C\u8FC7\u957F\uFF08${text.length} \u5B57\uFF0C\u4E0A\u9650 ${MAX_TEXT_LENGTH} \u5B57\uFF09\uFF1A\u8BF7\u62C6\u6210\u51E0\u6BB5\u5206\u522B\u5408\u6210\u3002`
|
|
12805
|
+
);
|
|
12806
|
+
}
|
|
12807
|
+
if (!/[\p{L}\p{N}]/u.test(text)) {
|
|
12808
|
+
throw new Error("\u5F85\u5408\u6210\u7684\u6587\u672C\u6CA1\u6709\u53EF\u6717\u8BFB\u5185\u5BB9\uFF08\u5168\u662F\u7B26\u53F7 / \u7A7A\u767D / emoji\uFF09\u3002");
|
|
12809
|
+
}
|
|
12810
|
+
const ffmpegPath = options.format === "mp3" ? await requireFfmpegPath() : null;
|
|
12811
|
+
await mkdir3(dirname10(options.output), { recursive: true });
|
|
12812
|
+
const client2 = createTTSClient(buildTtsConfig(getActiveTtsProvider(ctx)));
|
|
12813
|
+
const chunks = [];
|
|
12814
|
+
let sampleRate;
|
|
12815
|
+
let channels;
|
|
12816
|
+
let lastError = null;
|
|
12817
|
+
client2.on("audio", (chunk) => {
|
|
12818
|
+
chunks.push(Buffer2.from(chunk.audio));
|
|
12819
|
+
sampleRate ??= chunk.sampleRate;
|
|
12820
|
+
channels ??= chunk.channels;
|
|
12821
|
+
});
|
|
12822
|
+
client2.on("error", (error) => {
|
|
12823
|
+
lastError = errorMessage(error);
|
|
12824
|
+
});
|
|
12825
|
+
const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
|
|
12826
|
+
let timer;
|
|
12827
|
+
try {
|
|
12828
|
+
await client2.connect();
|
|
12829
|
+
await Promise.race([
|
|
12830
|
+
(async () => {
|
|
12831
|
+
client2.sendText(text);
|
|
12832
|
+
await client2.flush();
|
|
12833
|
+
})(),
|
|
12834
|
+
new Promise((_, reject) => {
|
|
12835
|
+
timer = setTimeout(
|
|
12836
|
+
() => reject(new Error(`\u8BED\u97F3\u5408\u6210\u8D85\u65F6\uFF08\u8D85\u8FC7 ${timeoutSeconds} \u79D2\uFF09\u3002`)),
|
|
12837
|
+
timeoutSeconds * 1e3
|
|
12838
|
+
);
|
|
12839
|
+
})
|
|
12840
|
+
]);
|
|
12841
|
+
} catch (error) {
|
|
12842
|
+
if (lastError) throw new Error(lastError);
|
|
12843
|
+
throw error;
|
|
12844
|
+
} finally {
|
|
12845
|
+
clearTimeout(timer);
|
|
12846
|
+
client2.removeAllListeners();
|
|
12847
|
+
await client2.close().catch(() => {
|
|
12848
|
+
});
|
|
12849
|
+
}
|
|
12850
|
+
const pcm = Buffer2.concat(chunks);
|
|
12851
|
+
if (pcm.length === 0) {
|
|
12852
|
+
throw new Error(lastError ?? "\u8BED\u97F3\u5408\u6210\u6CA1\u6709\u8FD4\u56DE\u4EFB\u4F55\u97F3\u9891\u6570\u636E\u3002");
|
|
12853
|
+
}
|
|
12854
|
+
const rate = sampleRate || FALLBACK_SAMPLE_RATE;
|
|
12855
|
+
const channelCount = channels || FALLBACK_CHANNELS;
|
|
12856
|
+
if (ffmpegPath) {
|
|
12857
|
+
await encodePcmToMp3({
|
|
12858
|
+
ffmpegPath,
|
|
12859
|
+
pcm,
|
|
12860
|
+
sampleRate: rate,
|
|
12861
|
+
channels: channelCount,
|
|
12862
|
+
outputPath: options.output
|
|
12863
|
+
});
|
|
12864
|
+
} else {
|
|
12865
|
+
await writeFile6(options.output, encodeWav(pcm, rate, channelCount));
|
|
12866
|
+
}
|
|
12867
|
+
const { size } = await stat7(options.output);
|
|
12868
|
+
return {
|
|
12869
|
+
output: options.output,
|
|
12870
|
+
format: options.format,
|
|
12871
|
+
sampleRate: rate,
|
|
12872
|
+
channels: channelCount,
|
|
12873
|
+
durationSeconds: Math.round(pcm.length / (rate * channelCount * 2) * 10) / 10,
|
|
12874
|
+
bytes: size
|
|
12875
|
+
};
|
|
12876
|
+
}
|
|
11736
12877
|
|
|
11737
12878
|
// src/backend/toolbox.ts
|
|
11738
12879
|
var CHARSETS = {
|
|
@@ -11751,7 +12892,7 @@ function getSharp() {
|
|
|
11751
12892
|
}
|
|
11752
12893
|
return sharpPromise;
|
|
11753
12894
|
}
|
|
11754
|
-
function createToolboxModule(
|
|
12895
|
+
function createToolboxModule(ctx) {
|
|
11755
12896
|
function extractMetadata(metadata) {
|
|
11756
12897
|
return {
|
|
11757
12898
|
width: metadata.width,
|
|
@@ -11842,8 +12983,8 @@ function createToolboxModule(_ctx) {
|
|
|
11842
12983
|
server.registry("toolbox/image-thumbnail", async (payload) => {
|
|
11843
12984
|
const { path: imgPath, width, output } = imageThumbnailRequestSchema.parse(payload);
|
|
11844
12985
|
const absPath = resolve8(projectDir, imgPath);
|
|
11845
|
-
const outputPath = output ? resolve8(projectDir, output) :
|
|
11846
|
-
|
|
12986
|
+
const outputPath = output ? resolve8(projectDir, output) : join25(
|
|
12987
|
+
dirname11(absPath),
|
|
11847
12988
|
`${basename4(absPath, extname3(absPath))}.thumb${width}${extname3(absPath)}`
|
|
11848
12989
|
);
|
|
11849
12990
|
const sharp = await getSharp();
|
|
@@ -11861,15 +13002,15 @@ function createToolboxModule(_ctx) {
|
|
|
11861
13002
|
const actualHeight = resizedMeta.height ?? height ?? 0;
|
|
11862
13003
|
const ext = extname3(absPath);
|
|
11863
13004
|
const base = basename4(absPath, ext);
|
|
11864
|
-
const outputPath = output ? resolve8(projectDir, output) :
|
|
11865
|
-
await
|
|
13005
|
+
const outputPath = output ? resolve8(projectDir, output) : join25(dirname11(absPath), `${base}.${actualWidth}x${actualHeight}${ext}`);
|
|
13006
|
+
await writeFile7(outputPath, resized);
|
|
11866
13007
|
const metadata = await sharp(outputPath).metadata();
|
|
11867
13008
|
return { result: { output: outputPath, metadata: extractMetadata(metadata) } };
|
|
11868
13009
|
});
|
|
11869
13010
|
server.registry("toolbox/image-convert", async (payload) => {
|
|
11870
13011
|
const { path: imgPath, format, quality, output } = imageConvertRequestSchema.parse(payload);
|
|
11871
13012
|
const absPath = resolve8(projectDir, imgPath);
|
|
11872
|
-
const outputPath = output ? resolve8(projectDir, output) :
|
|
13013
|
+
const outputPath = output ? resolve8(projectDir, output) : join25(dirname11(absPath), `${basename4(absPath, extname3(absPath))}.${format}`);
|
|
11873
13014
|
const sharp = await getSharp();
|
|
11874
13015
|
let pipeline = sharp(absPath);
|
|
11875
13016
|
const opts = {};
|
|
@@ -11888,16 +13029,41 @@ function createToolboxModule(_ctx) {
|
|
|
11888
13029
|
margin,
|
|
11889
13030
|
ec_level: errorCorrection
|
|
11890
13031
|
});
|
|
11891
|
-
await
|
|
13032
|
+
await writeFile7(outputPath, png);
|
|
11892
13033
|
return { result: { output: outputPath } };
|
|
11893
13034
|
});
|
|
13035
|
+
server.registry(
|
|
13036
|
+
"toolbox/audio-transcribe",
|
|
13037
|
+
async (payload) => {
|
|
13038
|
+
const {
|
|
13039
|
+
path: audioPath,
|
|
13040
|
+
language,
|
|
13041
|
+
ffmpegPath,
|
|
13042
|
+
timeoutSeconds
|
|
13043
|
+
} = audioTranscribeRequestSchema.parse(payload);
|
|
13044
|
+
const absPath = resolve8(projectDir, audioPath);
|
|
13045
|
+
const outcome = await transcribeAudioFile(ctx, {
|
|
13046
|
+
path: absPath,
|
|
13047
|
+
language,
|
|
13048
|
+
ffmpegPath,
|
|
13049
|
+
timeoutSeconds
|
|
13050
|
+
});
|
|
13051
|
+
return { result: outcome };
|
|
13052
|
+
}
|
|
13053
|
+
);
|
|
13054
|
+
server.registry("toolbox/text-to-speech", async (payload) => {
|
|
13055
|
+
const { text, output, format } = textToSpeechRequestSchema.parse(payload);
|
|
13056
|
+
const outputPath = resolve8(projectDir, output ?? `speech.${format}`);
|
|
13057
|
+
const outcome = await synthesizeSpeechToFile(ctx, { text, output: outputPath, format });
|
|
13058
|
+
return { result: outcome };
|
|
13059
|
+
});
|
|
11894
13060
|
}
|
|
11895
13061
|
function buildToolboxMcpServer(options) {
|
|
11896
13062
|
return {
|
|
11897
13063
|
name: "toolbox",
|
|
11898
13064
|
command: process.execPath,
|
|
11899
13065
|
args: [
|
|
11900
|
-
|
|
13066
|
+
join25(process.scriptsPath, "mcp-toolbox/server.mjs"),
|
|
11901
13067
|
"--project-dir",
|
|
11902
13068
|
options.projectDir,
|
|
11903
13069
|
"--socket-path",
|
|
@@ -11912,85 +13078,11 @@ function createToolboxModule(_ctx) {
|
|
|
11912
13078
|
};
|
|
11913
13079
|
}
|
|
11914
13080
|
|
|
11915
|
-
// src/backend/speech/manager.ts
|
|
11916
|
-
import { Buffer as
|
|
11917
|
-
import {
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
var DEFAULT_DASHSCOPE_MODEL = "fun-asr-flash-8k-realtime";
|
|
11921
|
-
var DEFAULT_OPENAI_MODEL = "gpt-4o-transcribe";
|
|
11922
|
-
function optionalString(value) {
|
|
11923
|
-
const trimmed = value?.trim();
|
|
11924
|
-
return trimmed || void 0;
|
|
11925
|
-
}
|
|
11926
|
-
function required(value, name) {
|
|
11927
|
-
const result = optionalString(value);
|
|
11928
|
-
if (!result) throw new Error(`\u5B9E\u65F6\u8BED\u97F3\u8BC6\u522B\u914D\u7F6E\u7F3A\u5C11 ${name}\u3002`);
|
|
11929
|
-
return result;
|
|
11930
|
-
}
|
|
11931
|
-
function buildOptions(provider) {
|
|
11932
|
-
return {
|
|
11933
|
-
language: optionalString(provider.language) ?? "zh-CN",
|
|
11934
|
-
sampleRate: 16e3,
|
|
11935
|
-
channels: 1,
|
|
11936
|
-
format: "pcm",
|
|
11937
|
-
interimResults: true,
|
|
11938
|
-
punctuation: true,
|
|
11939
|
-
autoReconnect: false,
|
|
11940
|
-
transcriptionModel: provider.provider === "openai" ? optionalString(provider.model) ?? DEFAULT_OPENAI_MODEL : void 0
|
|
11941
|
-
};
|
|
11942
|
-
}
|
|
11943
|
-
function buildConfig(provider) {
|
|
11944
|
-
const options = buildOptions(provider);
|
|
11945
|
-
const url = optionalString(provider.baseUrl);
|
|
11946
|
-
switch (provider.provider) {
|
|
11947
|
-
case "volcengine":
|
|
11948
|
-
return {
|
|
11949
|
-
provider: "volcengine",
|
|
11950
|
-
apiKey: required(provider.apiKey, "API Key"),
|
|
11951
|
-
resourceId: optionalString(provider.resourceId),
|
|
11952
|
-
appId: optionalString(provider.appId),
|
|
11953
|
-
url,
|
|
11954
|
-
options
|
|
11955
|
-
};
|
|
11956
|
-
case "dashscope":
|
|
11957
|
-
return {
|
|
11958
|
-
provider: "dashscope",
|
|
11959
|
-
apiKey: required(provider.apiKey, "API Key"),
|
|
11960
|
-
model: optionalString(provider.model) ?? DEFAULT_DASHSCOPE_MODEL,
|
|
11961
|
-
workspaceId: optionalString(provider.workspaceId),
|
|
11962
|
-
region: provider.region,
|
|
11963
|
-
workspace: optionalString(provider.workspace),
|
|
11964
|
-
url,
|
|
11965
|
-
options
|
|
11966
|
-
};
|
|
11967
|
-
case "openai":
|
|
11968
|
-
return {
|
|
11969
|
-
provider: "openai",
|
|
11970
|
-
apiKey: required(provider.apiKey, "API Key"),
|
|
11971
|
-
url,
|
|
11972
|
-
options
|
|
11973
|
-
};
|
|
11974
|
-
case "iflytek":
|
|
11975
|
-
return {
|
|
11976
|
-
provider: "iflytek",
|
|
11977
|
-
appId: required(provider.appId, "App ID"),
|
|
11978
|
-
apiKey: required(provider.apiKey, "API Key"),
|
|
11979
|
-
apiSecret: required(provider.apiSecret, "API Secret"),
|
|
11980
|
-
url,
|
|
11981
|
-
options
|
|
11982
|
-
};
|
|
11983
|
-
}
|
|
11984
|
-
}
|
|
11985
|
-
function getActiveProvider(ctx) {
|
|
11986
|
-
const provider = ctx.storage.getSettings().speechToText.find((item) => item.active);
|
|
11987
|
-
if (!provider) {
|
|
11988
|
-
throw new Error("\u8BF7\u5148\u5728\u8BBE\u7F6E \u2192 \u8BED\u97F3\u8BC6\u522B\u4E2D\u914D\u7F6E\u5E76\u542F\u7528\u4E00\u4E2A Provider\u3002");
|
|
11989
|
-
}
|
|
11990
|
-
return provider;
|
|
11991
|
-
}
|
|
11992
|
-
function errorMessage(error) {
|
|
11993
|
-
return error instanceof Error ? error.message : String(error);
|
|
13081
|
+
// src/backend/speech/asr-manager.ts
|
|
13082
|
+
import { Buffer as Buffer3 } from "node:buffer";
|
|
13083
|
+
import { createASRClient as createASRClient2 } from "unified-realtime-asr";
|
|
13084
|
+
function isNotConnectedError(error) {
|
|
13085
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "not-connected";
|
|
11994
13086
|
}
|
|
11995
13087
|
function createAsrManager(ctx) {
|
|
11996
13088
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -12017,9 +13109,9 @@ function createAsrManager(ctx) {
|
|
|
12017
13109
|
async start(clientId, asrSessionId) {
|
|
12018
13110
|
const key = keyOf2(clientId, asrSessionId);
|
|
12019
13111
|
if (sessions.has(key)) return { ok: true };
|
|
12020
|
-
const provider =
|
|
12021
|
-
const client2 =
|
|
12022
|
-
const active = { clientId, asrSessionId, client: client2 };
|
|
13112
|
+
const provider = getActiveAsrProvider(ctx);
|
|
13113
|
+
const client2 = createASRClient2(buildAsrConfig(provider));
|
|
13114
|
+
const active = { clientId, asrSessionId, client: client2, ready: false };
|
|
12023
13115
|
sessions.set(key, active);
|
|
12024
13116
|
client2.on("transcript", (transcript) => {
|
|
12025
13117
|
if (sessions.get(key) === active) emitTranscript(active, transcript);
|
|
@@ -12029,6 +13121,7 @@ function createAsrManager(ctx) {
|
|
|
12029
13121
|
});
|
|
12030
13122
|
client2.on("close", (info) => {
|
|
12031
13123
|
if (sessions.get(key) !== active) return;
|
|
13124
|
+
active.ready = false;
|
|
12032
13125
|
sessions.delete(key);
|
|
12033
13126
|
ctx.sendEvent("asr-closed", {
|
|
12034
13127
|
clientId,
|
|
@@ -12039,6 +13132,7 @@ function createAsrManager(ctx) {
|
|
|
12039
13132
|
});
|
|
12040
13133
|
try {
|
|
12041
13134
|
await client2.connect();
|
|
13135
|
+
active.ready = true;
|
|
12042
13136
|
return { ok: true };
|
|
12043
13137
|
} catch (error) {
|
|
12044
13138
|
sessions.delete(key);
|
|
@@ -12049,9 +13143,14 @@ function createAsrManager(ctx) {
|
|
|
12049
13143
|
frame(clientId, asrSessionId, audioB64) {
|
|
12050
13144
|
const active = sessions.get(keyOf2(clientId, asrSessionId));
|
|
12051
13145
|
if (!active) return;
|
|
13146
|
+
if (!active.ready) return;
|
|
12052
13147
|
try {
|
|
12053
|
-
active.client.sendAudio(
|
|
13148
|
+
active.client.sendAudio(Buffer3.from(audioB64, "base64"));
|
|
12054
13149
|
} catch (error) {
|
|
13150
|
+
if (isNotConnectedError(error)) {
|
|
13151
|
+
active.ready = false;
|
|
13152
|
+
return;
|
|
13153
|
+
}
|
|
12055
13154
|
emitError(active, error);
|
|
12056
13155
|
}
|
|
12057
13156
|
},
|
|
@@ -12084,6 +13183,116 @@ function createAsrManager(ctx) {
|
|
|
12084
13183
|
};
|
|
12085
13184
|
}
|
|
12086
13185
|
|
|
13186
|
+
// src/backend/speech/tts-manager.ts
|
|
13187
|
+
import { Buffer as Buffer4 } from "node:buffer";
|
|
13188
|
+
import { createTTSClient as createTTSClient2 } from "unified-realtime-asr";
|
|
13189
|
+
function createTtsManager(ctx) {
|
|
13190
|
+
const connections = /* @__PURE__ */ new Map();
|
|
13191
|
+
const ensureConnected = async (connection) => {
|
|
13192
|
+
if (connection.connected) return;
|
|
13193
|
+
if (connection.connectFailed) throw new Error("\u8BED\u97F3\u5408\u6210\u8FDE\u63A5\u4E0D\u53EF\u7528\u3002");
|
|
13194
|
+
if (!connection.connecting) {
|
|
13195
|
+
connection.connecting = connection.client.connect().then(() => {
|
|
13196
|
+
connection.connected = true;
|
|
13197
|
+
}).catch((error) => {
|
|
13198
|
+
connection.connectFailed = true;
|
|
13199
|
+
if (connections.get(connection.ttsSessionId) === connection) {
|
|
13200
|
+
ctx.sendEvent("tts-error", {
|
|
13201
|
+
ttsSessionId: connection.ttsSessionId,
|
|
13202
|
+
message: errorMessage(error)
|
|
13203
|
+
});
|
|
13204
|
+
}
|
|
13205
|
+
throw error;
|
|
13206
|
+
}).finally(() => {
|
|
13207
|
+
connection.connecting = null;
|
|
13208
|
+
});
|
|
13209
|
+
}
|
|
13210
|
+
await connection.connecting;
|
|
13211
|
+
};
|
|
13212
|
+
const teardown = (connection) => {
|
|
13213
|
+
if (connections.get(connection.ttsSessionId) === connection) {
|
|
13214
|
+
connections.delete(connection.ttsSessionId);
|
|
13215
|
+
}
|
|
13216
|
+
connection.client.removeAllListeners();
|
|
13217
|
+
};
|
|
13218
|
+
return {
|
|
13219
|
+
async start(ttsSessionId) {
|
|
13220
|
+
if (connections.has(ttsSessionId)) return { ok: true };
|
|
13221
|
+
const client2 = createTTSClient2(buildTtsConfig(getActiveTtsProvider(ctx)));
|
|
13222
|
+
const connection = {
|
|
13223
|
+
ttsSessionId,
|
|
13224
|
+
client: client2,
|
|
13225
|
+
connected: false,
|
|
13226
|
+
connecting: null,
|
|
13227
|
+
connectFailed: false
|
|
13228
|
+
};
|
|
13229
|
+
connections.set(ttsSessionId, connection);
|
|
13230
|
+
client2.on("audio", (chunk) => {
|
|
13231
|
+
if (connections.get(ttsSessionId) !== connection) return;
|
|
13232
|
+
ctx.sendEvent("tts-audio", {
|
|
13233
|
+
ttsSessionId,
|
|
13234
|
+
audioB64: Buffer4.from(chunk.audio).toString("base64"),
|
|
13235
|
+
format: chunk.format,
|
|
13236
|
+
sampleRate: chunk.sampleRate,
|
|
13237
|
+
channels: chunk.channels,
|
|
13238
|
+
isFinal: chunk.isFinal,
|
|
13239
|
+
index: chunk.index
|
|
13240
|
+
});
|
|
13241
|
+
});
|
|
13242
|
+
client2.on("error", (error) => {
|
|
13243
|
+
if (connections.get(ttsSessionId) !== connection) return;
|
|
13244
|
+
ctx.sendEvent("tts-error", { ttsSessionId, message: errorMessage(error) });
|
|
13245
|
+
});
|
|
13246
|
+
client2.on("close", (info) => {
|
|
13247
|
+
if (connections.get(ttsSessionId) !== connection) return;
|
|
13248
|
+
connections.delete(ttsSessionId);
|
|
13249
|
+
ctx.sendEvent("tts-closed", {
|
|
13250
|
+
ttsSessionId,
|
|
13251
|
+
code: info?.code,
|
|
13252
|
+
reason: info?.reason
|
|
13253
|
+
});
|
|
13254
|
+
});
|
|
13255
|
+
return { ok: true };
|
|
13256
|
+
},
|
|
13257
|
+
async speak(ttsSessionId, text) {
|
|
13258
|
+
const connection = connections.get(ttsSessionId);
|
|
13259
|
+
if (!connection) return;
|
|
13260
|
+
await ensureConnected(connection);
|
|
13261
|
+
connection.client.sendText(text);
|
|
13262
|
+
await connection.client.flush();
|
|
13263
|
+
},
|
|
13264
|
+
async end(ttsSessionId) {
|
|
13265
|
+
const connection = connections.get(ttsSessionId);
|
|
13266
|
+
if (!connection) return;
|
|
13267
|
+
if (connection.connected || connection.connecting) {
|
|
13268
|
+
try {
|
|
13269
|
+
await connection.connecting;
|
|
13270
|
+
} catch {
|
|
13271
|
+
}
|
|
13272
|
+
try {
|
|
13273
|
+
await connection.client.close();
|
|
13274
|
+
} catch (error) {
|
|
13275
|
+
ctx.sendEvent("tts-error", { ttsSessionId, message: errorMessage(error) });
|
|
13276
|
+
}
|
|
13277
|
+
}
|
|
13278
|
+
teardown(connection);
|
|
13279
|
+
},
|
|
13280
|
+
async closeAll() {
|
|
13281
|
+
await Promise.all(
|
|
13282
|
+
Array.from(connections.values(), async (connection) => {
|
|
13283
|
+
try {
|
|
13284
|
+
if (connection.connected || connection.connecting) await connection.client.close();
|
|
13285
|
+
} catch {
|
|
13286
|
+
} finally {
|
|
13287
|
+
teardown(connection);
|
|
13288
|
+
}
|
|
13289
|
+
})
|
|
13290
|
+
);
|
|
13291
|
+
connections.clear();
|
|
13292
|
+
}
|
|
13293
|
+
};
|
|
13294
|
+
}
|
|
13295
|
+
|
|
12087
13296
|
// src/backend/backend.ts
|
|
12088
13297
|
function initBackend(emitter) {
|
|
12089
13298
|
const listeners = [];
|
|
@@ -12110,6 +13319,7 @@ function initBackend(emitter) {
|
|
|
12110
13319
|
const memory = createMemoryModule(ctx, { inference: _inference });
|
|
12111
13320
|
const imageGeneration = createImageGenerationModule(ctx, { ilink: ilink.state });
|
|
12112
13321
|
const asr = createAsrManager(ctx);
|
|
13322
|
+
const tts = createTtsManager(ctx);
|
|
12113
13323
|
const session = createSessionModule(ctx, {
|
|
12114
13324
|
bridgeConnect,
|
|
12115
13325
|
askUser,
|
|
@@ -12129,6 +13339,8 @@ function initBackend(emitter) {
|
|
|
12129
13339
|
newSession: session.newSession,
|
|
12130
13340
|
getModels: session.getModels,
|
|
12131
13341
|
setModel: session.setModel,
|
|
13342
|
+
updateSession: session.updateSession,
|
|
13343
|
+
loadSession: session.loadSession,
|
|
12132
13344
|
respondAskUser: askUser.respondAskUser,
|
|
12133
13345
|
getPendingAskUserRequests: askUser.getPendingAskUserRequests
|
|
12134
13346
|
});
|
|
@@ -12228,7 +13440,7 @@ function initBackend(emitter) {
|
|
|
12228
13440
|
const projectRoot = projectId ? storageOps.getProject(projectId)?.cwd : void 0;
|
|
12229
13441
|
const skillDir = skills.getSkillSystemPathFromId(skillId, { projectRoot });
|
|
12230
13442
|
if (!skillDir) throw new Error(`Failed to read skill: ${skillId}`);
|
|
12231
|
-
return
|
|
13443
|
+
return join26(skillDir, SKILL_FILENAME);
|
|
12232
13444
|
},
|
|
12233
13445
|
async uninstallSkill({ skillId, projectId }) {
|
|
12234
13446
|
const projectRoot = projectId ? storageOps.getProject(projectId)?.cwd : void 0;
|
|
@@ -12480,7 +13692,7 @@ function initBackend(emitter) {
|
|
|
12480
13692
|
}) {
|
|
12481
13693
|
const shareDir = storageOps.sessionShareDir(sessionId);
|
|
12482
13694
|
if (!shareDir) throw new Error("Session not found");
|
|
12483
|
-
return
|
|
13695
|
+
return join26(shareDir, sharePath);
|
|
12484
13696
|
},
|
|
12485
13697
|
async deleteTask({ scheduleId, taskId }) {
|
|
12486
13698
|
automation.deleteTask(scheduleId, taskId);
|
|
@@ -12507,12 +13719,23 @@ function initBackend(emitter) {
|
|
|
12507
13719
|
},
|
|
12508
13720
|
async stopRealtimeAsr({ clientId, asrSessionId }) {
|
|
12509
13721
|
await asr.stop(clientId, asrSessionId);
|
|
13722
|
+
},
|
|
13723
|
+
// Speech Synthesis (TTS):无状态转发,文本整理在渲染层
|
|
13724
|
+
async startTts({ ttsSessionId }) {
|
|
13725
|
+
return tts.start(ttsSessionId);
|
|
13726
|
+
},
|
|
13727
|
+
async speakTts({ ttsSessionId, text }) {
|
|
13728
|
+
await tts.speak(ttsSessionId, text);
|
|
13729
|
+
},
|
|
13730
|
+
async endTts({ ttsSessionId }) {
|
|
13731
|
+
await tts.end(ttsSessionId);
|
|
12510
13732
|
}
|
|
12511
13733
|
};
|
|
12512
13734
|
webui.setHandlers(backendHandlers2);
|
|
12513
13735
|
async function closeBackend2() {
|
|
12514
13736
|
automation.stopAllCrons();
|
|
12515
13737
|
await asr.closeAll();
|
|
13738
|
+
await tts.closeAll();
|
|
12516
13739
|
webui.stop();
|
|
12517
13740
|
await ilink.stopIlink({ logout: false });
|
|
12518
13741
|
session.clearSession();
|
|
@@ -12521,14 +13744,14 @@ function initBackend(emitter) {
|
|
|
12521
13744
|
terminal.killAllTerminals();
|
|
12522
13745
|
await watcher2.stopAll();
|
|
12523
13746
|
for (const file of await readdir2(TEMP_DIR)) {
|
|
12524
|
-
await rm3(
|
|
13747
|
+
await rm3(join26(TEMP_DIR, file), { recursive: true, force: true });
|
|
12525
13748
|
}
|
|
12526
13749
|
}
|
|
12527
13750
|
return { backendHandlers: backendHandlers2, closeBackend: closeBackend2 };
|
|
12528
13751
|
}
|
|
12529
13752
|
|
|
12530
13753
|
// package.json
|
|
12531
|
-
var version = "0.1.
|
|
13754
|
+
var version = "0.1.57";
|
|
12532
13755
|
|
|
12533
13756
|
// src/server/main.ts
|
|
12534
13757
|
var port;
|