@nextclaw/ui 0.24.3 → 0.25.1
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/CHANGELOG.md +39 -0
- package/dist/.vite/manifest.json +1309 -991
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Kzlh3ZEC.js → abnfDiagram-VRR7QNED-DX_u5BBX.js} +1 -1
- package/dist/assets/{account-panel-BRTSwNvA.js → account-panel-QhOMbAQP.js} +1 -1
- package/dist/assets/{aiden0z-pptx-renderer.es-k1xZLzeo.js → aiden0z-pptx-renderer.es-C-E9e5yB.js} +1 -1
- package/dist/assets/{api-BnUzdltV.js → api-BWFOIoUm.js} +5 -5
- package/dist/assets/{app-data-Bjh_zM45.js → app-data-BnrEdmg2.js} +1 -1
- package/dist/assets/app-presenter-provider-CDRBc_lQ.js +115 -0
- package/dist/assets/app-presenter-provider-jNGlmBLe.css +1 -0
- package/dist/assets/{appearance-settings-page-BrOYSgG0.js → appearance-settings-page-C5z5sOkC.js} +1 -1
- package/dist/assets/{arc-CpivM836.js → arc-Cxwa624o.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DEt0Abz9.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-ifjW1qjI.js → architectureDiagram-ZJ3FMSHR-D8mCj0H1.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-C_zeJrKF.js → blockDiagram-677ZJIJ3-CbGC1IHl.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-CpapRftp.js → c4Diagram-LMCZKHZV-BQhGC1Vc.js} +1 -1
- package/dist/assets/channel-CxQkbwRD.js +1 -0
- package/dist/assets/{channels-list-page-BE0TxB3B.js → channels-list-page-CzXYZZtK.js} +1 -1
- package/dist/assets/chunk-2Q5K7J3B-ClVhziok.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-BKBwdmKR.js → chunk-32BRIVSS-B74ZZwYG.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BgZRCpDx.js → chunk-52WLFC77-BvmxW_f_.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-YWfOINJi.js → chunk-5FCAYU7R-DQvBRnwL.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-BZ6I07d2.js → chunk-5HE753X5-CDIH0_y3.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-B7QYgZU5.js → chunk-5JV3BV7I-CcevlKd-.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-DjyQwgL2.js → chunk-5TONJI2A-_3dxVdnY.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DheHV7X2.js → chunk-5VM5RSS4-pGO5vvo-.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-D6bq2PKF.js → chunk-7BUUIJ7U-DEKF5uAN.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-CL9x0ibm.js → chunk-BIQX33UG-CjBFgq2r.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-8z3B6RUH.js → chunk-C7G6YPKG-e_K9OGTz.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-DpHkxY3n.js → chunk-CQNSW5MT-C2IiHyHz.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-DIJDLsdJ.js → chunk-CYSBUYHQ-DCC6KmGp.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-B-LjAyVd.js → chunk-EMLP6XTP-BYGgekQ0.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-BChuEPvO.js → chunk-EX3LRPZG-FGcwuVsA.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-fBFt0u3f.js → chunk-FWX5IMBZ-CV3Ih_Os.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-MD0SpW5A.js → chunk-HOUHSVGY-CXpq_hfV.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-DvCcw_hz.js → chunk-ICXQ74PX-CZIpjupq.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-B8zqBnU9.js → chunk-JG7HCLWE-CdgwBpW7.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C9efnkGb.js → chunk-JWPE2WC7-BPjAZpOQ.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-gXbfqoqq.js → chunk-MOJQB5TN-DbJDm8to.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-twIwdkqC.js → chunk-MOZMSUNE-BUa1fqKn.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-DdR0_cWK.js → chunk-OGEWGWER-CtnzLgRu.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-D7D58Foa.js → chunk-OSBZ3O6U-fUgdpTUM.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-DPILgOVY.js → chunk-PUDLZKDR-CtDWZT9-.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-C0JJx7lW.js → chunk-Q4XR5HBZ-BxnK2qxr.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-Dbsf69ng.js → chunk-QBLGF6JB-CH4UKoCF.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-Bb8n616-.js → chunk-R7FJI6CG-BxwFoF1v.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-1HOsOrSl.js → chunk-RYQCIY6F-DpHAmq7O.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-BAFDVb1v.js → chunk-U6XO7XAA-BjH2VUHU.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-8-g68lu5.js → chunk-V7JOEXUC-DKJZ3HGj.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-DZQxkIsv.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-P6UGmaYd.js → chunk-VR4S4FIN-Cf-o2GtI.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-6wjFTXNP.js → chunk-WYO6CB5R-zeNXGm20.js} +3 -2
- package/dist/assets/chunk-XXDRQBXY-BkExtw5C.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-CLYXVZAi.js → chunk-YOTPTUD7-Ck87Kqxe.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BRTmSAhp.js → chunk-ZGVPDNZ5-BbfK6Bfd.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-CH_1eoSV.js → chunk-ZIRB5QZD-BDpzh4Yl.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Bd6apBb-.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-CYxUZGCI.js +1 -0
- package/dist/assets/{config-split-page-BxDwwAGa.js → config-split-page-CxLWGwdt.js} +1 -1
- package/dist/assets/{confirm-dialog-S5JxZcS1.js → confirm-dialog-BWS7gA_z.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-L7w_qScX.js → cose-bilkent-JH36ORCC-CQfMxKCT.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-CV0bFLH9.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-Bay7uDSR.js → cynefinDiagram-TSTJHNR4-CxFTiC-p.js} +1 -1
- package/dist/assets/{dagre-CJ_hX8xv.js → dagre-CaBNMcB1.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-DDhk-2B3.js → dagre-VKFMJZFB-S_7Hc7bI.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-BwHxJpbl.js → desktop-capabilities-page-BJNGkq4q.js} +1 -1
- package/dist/assets/{desktop-update-config-Bfg7OdJ2.js → desktop-update-config-7tBum3MA.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-somFq5y8.js → diagram-FQU43EPY-C7zENvFb.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-hzgyQsPd.js → diagram-G47NLZAW-DqYTCqZA.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-BkJ4gEQJ.js → diagram-NH7WQ7WH-BfSJtXRc.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-CS3ZkK4w.js → diagram-OA4YK3LP-CFO-j67H.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-BApo-JHS.js → diagram-WEI45ONY-DBQDbTaJ.js} +1 -1
- package/dist/assets/{doc-browser-C8eqJLi_.js → doc-browser-BNiqa-zm.js} +1 -1
- package/dist/assets/doc-browser-DZDqghNc.js +1 -0
- package/dist/assets/{doc-browser-context-BPCDFcC-.js → doc-browser-context-Dp0dEMyT.js} +1 -1
- package/dist/assets/{docx-preview-ByCKKIVT.js → docx-preview-B4-aDfvO.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-DWjfeEie.js → ebnfDiagram-CCIWWBDH-CfDNIXFN.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-DR7vWYHM.js → erDiagram-Q63AITRT-D95rFXMM.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-BzkLWWdP.js +1 -0
- package/dist/assets/{extensions-DgKQRebe.js → extensions-WWEYpaUh.js} +1 -1
- package/dist/assets/flowDiagram-23GEKE2U-ukkCsB0j.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-E3l9tuaL.js → ganttDiagram-NO4QXBWP-Bawg0P0r.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BYFhT0Q5.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-DiMvbHKf.js → gitGraphDiagram-IHSO6WYX-BbKh9nbL.js} +1 -1
- package/dist/assets/{i18n-CKvm9zyk.js → i18n-ClHx6jwj.js} +2 -2
- package/dist/assets/{i18n-provider-CPV43pMX.js → i18n-provider-C1NxogO4.js} +1 -1
- package/dist/assets/info-DKCQHKI2-CTmlJdcH.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-BZDL0NDJ.js → infoDiagram-FWYZ7A6U-DPffZXYh.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-BZ9WvJb2.js → ishikawaDiagram-FXEZZL3T-CBMsLwZ2.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-C3TZvKM8.js → journeyDiagram-5HDEW3XC-Mz5U4OzU.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-9FqauDEi.js → kanban-definition-HUTT4EX6-FXZq2k19.js} +1 -1
- package/dist/assets/{katex-J8JuvgNl.js → katex-B26hhTOT.js} +15 -15
- package/dist/assets/katex-DbZC06Q5.js +1 -0
- package/dist/assets/keyboard-shortcuts-settings-page-CS9BFOBW.js +1 -0
- package/dist/assets/{line-Ca49mj80.js → line-BZwCgT9K.js} +1 -1
- package/dist/assets/{linear-DlCeG66S.js → linear-CTe9mKUw.js} +1 -1
- package/dist/assets/{login-page-DeDQjVxv.js → login-page-CL0SrBCf.js} +1 -1
- package/dist/assets/login-page-Dq5nPUCU.js +1 -0
- package/dist/assets/main-CLwCjK9U.css +1 -0
- package/dist/assets/main-CvVBCOlN.js +2 -0
- package/dist/assets/main-i18n-catalog.config-D4qFo4OB.js +1 -0
- package/dist/assets/{map-WMnNq9UV.js → map-Ci48ZwAp.js} +1 -1
- package/dist/assets/mcp-marketplace-page-CATnpZ2Q.js +1 -0
- package/dist/assets/{mcp-marketplace-page-CWjJGp_D.js → mcp-marketplace-page-D75hPmzO.js} +1 -1
- package/dist/assets/{mermaid-parser.core-B8xk4hpS.js → mermaid-parser.core-CzPD3U_z.js} +2 -2
- package/dist/assets/{mermaid.core-CFPiczKH.js → mermaid.core-geIMNPbV.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CnxA52L6.js → mindmap-definition-LN4V7U3C-xqlKxsr_.js} +1 -1
- package/dist/assets/{model-config-page-Dv_zE7HN.js → model-config-page-DFfcV9lu.js} +1 -1
- package/dist/assets/{ordinal-PgiFlfBJ.js → ordinal-MxnX8yhv.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-BbrI0ZlN.js +1 -0
- package/dist/assets/panel-app-B39rJPj8.js +1 -0
- package/dist/assets/panel-app-i18n-catalog.config-oUwmxoE7.js +1 -0
- package/dist/assets/{panelStandalone-CFRBHV94.js → panelStandalone-D2CqTyi_.js} +2 -2
- package/dist/assets/{pegDiagram-2B236MQR-A8oz7dTH.js → pegDiagram-2B236MQR-D50MFTN1.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-B7zE1ZIr.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-mtBnkYY0.js → pieDiagram-ENE6RG2P-CpA13oiC.js} +1 -1
- package/dist/assets/{privacy-settings-page-D6uc9f-A.js → privacy-settings-page-CE-xF9bo.js} +1 -1
- package/dist/assets/projects-BF0wpY4X.js +1 -0
- package/dist/assets/{provider-scoped-model-input-DUa_9uwg.js → provider-scoped-model-input-BvZitHkT.js} +1 -1
- package/dist/assets/{providers-config-page-DAiqxlF_.js → providers-config-page-cDeyluVG.js} +1 -1
- package/dist/assets/{pwa-C_Zte1xe.js → pwa-dXn0s_lD.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-DE1VybGO.js → quadrantDiagram-ABIIQ3AL-CYCgaj4l.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-1X_q11Ed.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-CIXnuFyE.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-D6ImKl2r.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-2j9d3twU.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-DMVaJGbm.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-u9EMg14B.js → railroadDiagram-RFXS5EU6-4LKDGlXN.js} +1 -1
- package/dist/assets/remote-CV5wQVVj.js +1 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-BAWHAqEi.js → requirementDiagram-TGXJPOKE-CxNOU0JH.js} +1 -1
- package/dist/assets/runtime-config-page-DNo4dzS1.js +1 -0
- package/dist/assets/{sankeyDiagram-HTMAVEWB-aDy-jdwF.js → sankeyDiagram-HTMAVEWB-DbCBDe2S.js} +1 -1
- package/dist/assets/{search-config-page-3xbhRlW2.js → search-config-page-CA0HxVy0.js} +1 -1
- package/dist/assets/{secrets-config-page-Dn8_rdOj.js → secrets-config-page-iT1wamQp.js} +1 -1
- package/dist/assets/security-config-B38PLI3M.js +1 -0
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-DwHC4mbX.js → sequenceDiagram-DBY2YBRQ-DA6W2BS_.js} +1 -1
- package/dist/assets/{service-apps-panel-CGc04aMn.js → service-apps-panel-CU0tFrqT.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-oOMY0Fag.js → sizeCapture-X5ZJPWSS-T_q_d_VW.js} +1 -1
- package/dist/assets/{src-CLPoo5iL.js → src-CYpUlx-4.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-B0Epk-VR.js → stateDiagram-2N3HPSRC-mi8z2G0a.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-OlIMT57r.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-T0w-Iuzf.js → swimlanes-5IMT3BWC-DkBEhWE4.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-sLThrh07.js +8 -0
- package/dist/assets/{tag-chip-BDzpHEli.js → tag-chip-CY0Hqk_l.js} +1 -1
- package/dist/assets/{theme-provider-B6FinATQ.js → theme-provider-BvpXGXWH.js} +1 -1
- package/dist/assets/{timeline-definition-FHXFAJF6-BEonB5Np.js → timeline-definition-FHXFAJF6-BoFmqBw1.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-CCFyazDE.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-D6wvq1Hz.js +1 -0
- package/dist/assets/{use-config-BPUM2lbo.js → use-config-C8ZkQiTc.js} +1 -1
- package/dist/assets/{use-desktop-capabilities-BDjArQP3.js → use-desktop-capabilities-DR9snbeo.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-BDvcOCo2.js → vennDiagram-L72KCM5P-BlZ3Or5U.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-BRzaRyFf.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-jMLMgjzE.js → wardleyDiagram-EHGQE667-yDfr33i3.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-pzrpclEv.js → xychartDiagram-FW5EYKEG-mIXwIGAQ.js} +1 -1
- package/dist/index.html +24 -23
- package/dist/panel-standalone.html +6 -6
- package/package.json +8 -8
- package/src/app/configs/app-navigation.config.ts +10 -3
- package/src/app/index.tsx +7 -0
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +12 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +13 -1
- package/src/features/chat/features/conversation/components/__tests__/session-voice-input.test-utils.ts +52 -0
- package/src/features/chat/features/conversation/components/chat-voice-input-panel.tsx +70 -0
- package/src/features/chat/features/conversation/components/chat-voice-shortcut-control.tsx +33 -0
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -11
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +29 -5
- package/src/features/chat/features/conversation/hooks/__tests__/use-chat-voice-input.test.tsx +185 -0
- package/src/features/chat/features/conversation/hooks/use-chat-voice-input.ts +52 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +13 -6
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +28 -12
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +19 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +7 -6
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +4 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +20 -0
- package/src/features/chat/index.ts +1 -0
- package/src/features/chat/managers/chat-voice-input.manager.ts +174 -0
- package/src/features/chat/presenters/chat.presenter.ts +2 -0
- package/src/features/chat/stores/chat-voice-shortcut.store.ts +31 -0
- package/src/features/desktop-capabilities/index.ts +1 -0
- package/src/features/settings/pages/keyboard-shortcuts-settings-page.tsx +15 -0
- package/src/platforms/mobile/components/mobile-settings-shell.tsx +2 -1
- package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +109 -1
- package/src/shared/lib/api/ncp-session-query-cache.utils.ts +71 -2
- package/src/shared/lib/i18n/locales/en-US/chat.json +32 -1
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +32 -1
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/transport/__tests__/browser-realtime-recovery.service.test.ts +42 -0
- package/src/shared/lib/transport/__tests__/local-transport.service.test.ts +85 -0
- package/src/shared/lib/transport/__tests__/remote.transport.test.ts +54 -1
- package/src/shared/lib/transport/browser-realtime-recovery.service.ts +35 -0
- package/src/shared/lib/transport/local-transport.service.ts +32 -0
- package/src/shared/lib/transport/remote-transport.service.ts +30 -0
- package/dist/assets/app-presenter-provider-CBt-lyr_.js +0 -109
- package/dist/assets/app-presenter-provider-D3BuuOyZ.css +0 -1
- package/dist/assets/architecture-TIHT7OUA-CVRF_cYQ.js +0 -1
- package/dist/assets/channel-CBrWRr4N.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-B8mmRk7d.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-Cpml0inz.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-nCiqnCpF.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-C-DRqAaW.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DXCcZ42U.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-2SOTgxxC.js +0 -1
- package/dist/assets/doc-browser-BgRP0Wni.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-CXZNG56U.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-2065oyKr.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-B0Ln3ZuK.js +0 -1
- package/dist/assets/info-DKCQHKI2-B06uqGM4.js +0 -1
- package/dist/assets/login-page-ClcrvN04.js +0 -1
- package/dist/assets/main-DGqlaAaI.css +0 -1
- package/dist/assets/main-i18n-catalog.config-CpNF5aVc.js +0 -1
- package/dist/assets/main-nMVfHb5K.js +0 -2
- package/dist/assets/mcp-marketplace-page-fvu2NcXT.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CHx-igbz.js +0 -1
- package/dist/assets/panel-app-BUUle0gP.js +0 -1
- package/dist/assets/panel-app-i18n-catalog.config-BzxE3tZb.js +0 -1
- package/dist/assets/pie-RZYD4A2V-DOaMc-x9.js +0 -1
- package/dist/assets/projects-BD1rG75R.js +0 -1
- package/dist/assets/radar-I7S5WNFK-CXsdndDk.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-DxgXdrYP.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-CR1tnyjz.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-CjjxZR2U.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-DgVGJCHN.js +0 -1
- package/dist/assets/remote-BO0QP7ua.js +0 -1
- package/dist/assets/runtime-config-page-w1UAkGF_.js +0 -1
- package/dist/assets/security-config-Y1nEGM3A.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Ct4zTw8P.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-CdmN0PBd.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-Dad4_Wc7.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-9_GWWZsY.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-BXpvyee9.js +0 -1
- /package/dist/assets/{array-BHDgItFN.js → array-D0DyKZdT.js} +0 -0
- /package/dist/assets/{card-CS8su2Zh.js → card-BhfAZv5L.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-DstBw0Hk.js → chunk-KEIR6QF5-CuZFOa6q.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-CA3ymQuH.js → chunk-Y2CYZVJY-Frut-1TF.js} +0 -0
- /package/dist/assets/{config-hints-CBPHrfYk.js → config-hints-B_C9DAp5.js} +0 -0
- /package/dist/assets/{cytoscape.esm-C7xr90o_.js → cytoscape.esm-BibuhnZD.js} +0 -0
- /package/dist/assets/{defaultLocale-BG1oDVag.js → defaultLocale-q7sRUXPc.js} +0 -0
- /package/dist/assets/{dist-Cnhwure9.js → dist-DhMddrrH.js} +0 -0
- /package/dist/assets/{form-actions-AIdXOoCL.js → form-actions-BRESe-m_.js} +0 -0
- /package/dist/assets/{graphlib-CNMGfuTb.js → graphlib-BM63WsW3.js} +0 -0
- /package/dist/assets/{init-wQIZuRSH.js → init-CRMkf9sL.js} +0 -0
- /package/dist/assets/{jszip.min-D_AtKL_8.js → jszip.min-6thm2J_a.js} +0 -0
- /package/dist/assets/{path-N-C4MRP0.js → path-BFMmafCM.js} +0 -0
- /package/dist/assets/{provider-models-5PGHb6mP.js → provider-models-hrHMcHqg.js} +0 -0
- /package/dist/assets/{rough.esm-BeWA068d.js → rough.esm-5Tjchx1D.js} +0 -0
- /package/dist/assets/{skeleton-BL9h32W7.js → skeleton-Drvn83YW.js} +0 -0
- /package/dist/assets/{use-panel-app-scroll-restoration-DsV1IJx-.js → use-panel-app-scroll-restoration-ZnicIfo3.js} +0 -0
- /package/dist/assets/{xlsx-CwUXNtD3.js → xlsx-BC8ibjXK.js} +0 -0
|
@@ -638,5 +638,36 @@
|
|
|
638
638
|
"chatWorkspaceExcerptCharacterCount": "{count} characters",
|
|
639
639
|
"chatInputAttachmentUnsupported": "This file type is not supported in the current upload flow.",
|
|
640
640
|
"chatInputAttachmentTooLarge": "Files must be {maxMb} MB or smaller.",
|
|
641
|
-
"chatInputAttachmentReadFailed": "Failed to read the file. Please try again."
|
|
641
|
+
"chatInputAttachmentReadFailed": "Failed to read the file. Please try again.",
|
|
642
|
+
"chatInputVoice": "Voice input",
|
|
643
|
+
"chatInputVoicePermissionDenied": "Microphone permission was denied. Allow it in the browser or system settings and retry.",
|
|
644
|
+
"chatInputVoiceUnsupported": "Speech input is not supported in this browser.",
|
|
645
|
+
"chatInputVoiceNoSpeech": "Could not hear you. Please try again.",
|
|
646
|
+
"chatInputVoiceFailed": "Voice input failed. Please try again.",
|
|
647
|
+
"chatInputVoicePrivacy": "Click to dictate at the cursor. Underlined words are still being recognized. Your browser may process audio online. Nothing is sent automatically.",
|
|
648
|
+
"chatInputVoiceStart": "Start recording",
|
|
649
|
+
"chatInputVoiceFinish": "Finish recording",
|
|
650
|
+
"chatInputVoiceCancel": "Cancel",
|
|
651
|
+
"chatInputVoiceStarting": "Connecting to microphone…",
|
|
652
|
+
"chatInputVoiceRecording": "Listening",
|
|
653
|
+
"chatInputVoiceFinishing": "Finishing transcription…",
|
|
654
|
+
"chatInputVoiceReview": "Recording stopped",
|
|
655
|
+
"chatInputVoiceSpeak": "Speak now. Your transcript will appear here.",
|
|
656
|
+
"chatInputVoiceReviewHint": "Confirmed words remain in your draft; provisional words were removed. Continue editing or try again.",
|
|
657
|
+
"chatInputVoiceTranscript": "Recognized text",
|
|
658
|
+
"chatInputVoiceInsert": "Keep in draft",
|
|
659
|
+
"chatInputVoiceRetry": "Retry",
|
|
660
|
+
"chatInputVoiceNetwork": "Could not connect to the speech service. Check your network and try again.",
|
|
661
|
+
"chatInputVoiceDevice": "Microphone unavailable. Check your device and system permissions.",
|
|
662
|
+
"chatInputVoiceInterrupted": "Recording stopped because the page or window lost focus.",
|
|
663
|
+
"chatInputVoiceTimeout": "The speech service timed out. Recording has stopped. Try again.",
|
|
664
|
+
"chatInputVoiceShortcut": "Voice shortcut",
|
|
665
|
+
"chatInputVoiceCloseSettings": "Close settings",
|
|
666
|
+
"chatInputVoiceBind": "Bind a key",
|
|
667
|
+
"chatInputVoiceUnbind": "Disable",
|
|
668
|
+
"chatInputVoiceCapture": "Press your shortcut…",
|
|
669
|
+
"chatInputVoiceCaptureHelp": "Use a letter, F6–F10, or Ctrl/⌘ + Alt/⌥ + a letter. Esc cancels; Tab exits. Common shortcuts such as copy and paste are reserved.",
|
|
670
|
+
"chatInputVoiceShortcutHelp": "Hold to speak; release to finish. Single keys work outside text inputs; combinations also work inside. Only active in this chat page. System shortcuts may take priority.",
|
|
671
|
+
"chatInputVoiceBindInvalid": "This key may interfere with normal controls. Choose one of the suggested keys.",
|
|
672
|
+
"chatInputVoiceSaveFailed": "Could not save the shortcut. Check browser storage permissions."
|
|
642
673
|
}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"settingsGroupBasic": "Basic Configuration",
|
|
16
16
|
"settingsGroupAdvanced": "Advanced Configuration",
|
|
17
17
|
"appearance": "Appearance",
|
|
18
|
+
"keyboardShortcuts": "Keyboard shortcuts",
|
|
19
|
+
"keyboardShortcutsDesktopOnly": "Keyboard shortcuts can be configured on desktop. Use the microphone button on mobile.",
|
|
18
20
|
"chatMessageLayoutTitle": "Message layout",
|
|
19
21
|
"chatMessageLayoutDescription": "Choose how chat messages are displayed.",
|
|
20
22
|
"chatMessageLayoutCard": "Cards",
|
|
@@ -638,5 +638,36 @@
|
|
|
638
638
|
"chatWorkspaceExcerptCharacterCount": "{count} 个字符",
|
|
639
639
|
"chatInputAttachmentUnsupported": "当前上传流程不支持该文件类型。",
|
|
640
640
|
"chatInputAttachmentTooLarge": "文件不能超过 {maxMb} MB。",
|
|
641
|
-
"chatInputAttachmentReadFailed": "读取文件失败,请重试。"
|
|
641
|
+
"chatInputAttachmentReadFailed": "读取文件失败,请重试。",
|
|
642
|
+
"chatInputVoice": "语音输入",
|
|
643
|
+
"chatInputVoicePermissionDenied": "麦克风权限被拒绝,请在浏览器或系统中允许后重试。",
|
|
644
|
+
"chatInputVoiceUnsupported": "当前浏览器不支持语音输入。",
|
|
645
|
+
"chatInputVoiceNoSpeech": "没有听清,请再试一次。",
|
|
646
|
+
"chatInputVoiceFailed": "语音输入失败,请重试。",
|
|
647
|
+
"chatInputVoicePrivacy": "点击录音,文字实时显示在光标处,下划线表示尚未确定的内容。浏览器语音服务可能联网处理声音,不会自动发送。",
|
|
648
|
+
"chatInputVoiceStart": "开始录音",
|
|
649
|
+
"chatInputVoiceFinish": "结束录音",
|
|
650
|
+
"chatInputVoiceCancel": "取消",
|
|
651
|
+
"chatInputVoiceStarting": "正在连接麦克风…",
|
|
652
|
+
"chatInputVoiceRecording": "正在聆听",
|
|
653
|
+
"chatInputVoiceFinishing": "正在完成转写…",
|
|
654
|
+
"chatInputVoiceReview": "录音已停止",
|
|
655
|
+
"chatInputVoiceSpeak": "请说话,转写会显示在这里",
|
|
656
|
+
"chatInputVoiceReviewHint": "已确认的文字保留在草稿中,未确定的片段已撤回。你可以继续编辑或重试。",
|
|
657
|
+
"chatInputVoiceTranscript": "已识别的文字",
|
|
658
|
+
"chatInputVoiceInsert": "保留到输入框",
|
|
659
|
+
"chatInputVoiceRetry": "重试",
|
|
660
|
+
"chatInputVoiceNetwork": "语音服务连接失败,请检查网络后重试。",
|
|
661
|
+
"chatInputVoiceDevice": "无法访问麦克风,请检查设备连接和系统权限。",
|
|
662
|
+
"chatInputVoiceInterrupted": "录音已因离开页面或窗口失焦而停止。",
|
|
663
|
+
"chatInputVoiceTimeout": "语音服务响应超时,录音已停止,请重试。",
|
|
664
|
+
"chatInputVoiceShortcut": "语音快捷键",
|
|
665
|
+
"chatInputVoiceCloseSettings": "关闭设置",
|
|
666
|
+
"chatInputVoiceBind": "绑定按键",
|
|
667
|
+
"chatInputVoiceUnbind": "停用",
|
|
668
|
+
"chatInputVoiceCapture": "请按下快捷键…",
|
|
669
|
+
"chatInputVoiceCaptureHelp": "支持单字母、F6–F10,或 Ctrl/⌘ + Alt/⌥ + 字母。Esc 取消,Tab 离开。复制、粘贴等常用组合键不接受。",
|
|
670
|
+
"chatInputVoiceShortcutHelp": "按住说话,松开结束。单键仅在输入框外生效;输入框内可用组合键。仅当前聊天页生效,系统已占用的键可能无法使用。",
|
|
671
|
+
"chatInputVoiceBindInvalid": "这个按键可能影响正常操作,请选择提示中的按键。",
|
|
672
|
+
"chatInputVoiceSaveFailed": "无法保存快捷键,请检查浏览器存储权限。"
|
|
642
673
|
}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"settingsGroupBasic": "基础配置",
|
|
16
16
|
"settingsGroupAdvanced": "高级配置",
|
|
17
17
|
"appearance": "外观",
|
|
18
|
+
"keyboardShortcuts": "快捷键",
|
|
19
|
+
"keyboardShortcutsDesktopOnly": "请在桌面端配置键盘快捷键。手机可使用麦克风按钮输入。",
|
|
18
20
|
"chatMessageLayoutTitle": "消息布局",
|
|
19
21
|
"chatMessageLayoutDescription": "选择聊天消息的展示方式。",
|
|
20
22
|
"chatMessageLayoutCard": "卡片式",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { BrowserRealtimeRecoveryService } from '@/shared/lib/transport/browser-realtime-recovery.service';
|
|
3
|
+
|
|
4
|
+
describe('BrowserRealtimeRecoveryService', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('requests recovery only when the page can actively reconnect', () => {
|
|
10
|
+
let visibilityState: DocumentVisibilityState = 'hidden';
|
|
11
|
+
vi.spyOn(document, 'visibilityState', 'get').mockImplementation(() => visibilityState);
|
|
12
|
+
const requestRecovery = vi.fn();
|
|
13
|
+
const recovery = new BrowserRealtimeRecoveryService(requestRecovery);
|
|
14
|
+
|
|
15
|
+
recovery.start();
|
|
16
|
+
window.dispatchEvent(new Event('online'));
|
|
17
|
+
expect(requestRecovery).not.toHaveBeenCalled();
|
|
18
|
+
|
|
19
|
+
visibilityState = 'visible';
|
|
20
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
21
|
+
window.dispatchEvent(new Event('online'));
|
|
22
|
+
expect(requestRecovery).toHaveBeenCalledTimes(2);
|
|
23
|
+
|
|
24
|
+
recovery.stop();
|
|
25
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
26
|
+
expect(requestRecovery).toHaveBeenCalledTimes(2);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('keeps start and stop idempotent', () => {
|
|
30
|
+
vi.spyOn(document, 'visibilityState', 'get').mockReturnValue('visible');
|
|
31
|
+
const requestRecovery = vi.fn();
|
|
32
|
+
const recovery = new BrowserRealtimeRecoveryService(requestRecovery);
|
|
33
|
+
|
|
34
|
+
recovery.start();
|
|
35
|
+
recovery.start();
|
|
36
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
37
|
+
expect(requestRecovery).toHaveBeenCalledTimes(1);
|
|
38
|
+
|
|
39
|
+
recovery.stop();
|
|
40
|
+
recovery.stop();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { LocalAppTransport } from '@/shared/lib/transport/local-transport.service';
|
|
3
|
+
|
|
4
|
+
class MockWebSocket {
|
|
5
|
+
static readonly CONNECTING = 0;
|
|
6
|
+
static readonly OPEN = 1;
|
|
7
|
+
static readonly CLOSING = 2;
|
|
8
|
+
static readonly CLOSED = 3;
|
|
9
|
+
static instances: MockWebSocket[] = [];
|
|
10
|
+
|
|
11
|
+
readyState = MockWebSocket.CONNECTING;
|
|
12
|
+
onopen: ((event: Event) => void) | null = null;
|
|
13
|
+
onmessage: ((event: MessageEvent) => void) | null = null;
|
|
14
|
+
onerror: ((event: Event) => void) | null = null;
|
|
15
|
+
onclose: ((event: CloseEvent) => void) | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(public readonly url: string) {
|
|
18
|
+
MockWebSocket.instances.push(this);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
send = vi.fn();
|
|
22
|
+
|
|
23
|
+
close = vi.fn(() => {
|
|
24
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
open = (): void => {
|
|
28
|
+
this.readyState = MockWebSocket.OPEN;
|
|
29
|
+
this.onopen?.({} as Event);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
finishClose = (): void => {
|
|
33
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
34
|
+
this.onclose?.({} as CloseEvent);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('LocalAppTransport browser connection recovery', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
MockWebSocket.instances = [];
|
|
41
|
+
vi.stubGlobal('WebSocket', MockWebSocket as unknown as typeof WebSocket);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
vi.unstubAllGlobals();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('replaces a possibly stale open socket when a mobile page becomes visible again', () => {
|
|
50
|
+
let visibilityState: DocumentVisibilityState = 'hidden';
|
|
51
|
+
vi.spyOn(document, 'visibilityState', 'get').mockImplementation(() => visibilityState);
|
|
52
|
+
|
|
53
|
+
const transport = new LocalAppTransport({ apiBase: 'https://vps.example.com' });
|
|
54
|
+
const handler = vi.fn();
|
|
55
|
+
const unsubscribe = transport.subscribe(handler);
|
|
56
|
+
const staleSocket = MockWebSocket.instances[0];
|
|
57
|
+
staleSocket?.open();
|
|
58
|
+
|
|
59
|
+
visibilityState = 'visible';
|
|
60
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
61
|
+
window.dispatchEvent(new Event('online'));
|
|
62
|
+
|
|
63
|
+
expect(staleSocket?.close).toHaveBeenCalledTimes(1);
|
|
64
|
+
expect(MockWebSocket.instances).toHaveLength(2);
|
|
65
|
+
expect(handler).toHaveBeenCalledWith({ type: 'connection.close', payload: {} });
|
|
66
|
+
|
|
67
|
+
const replacementSocket = MockWebSocket.instances[1];
|
|
68
|
+
replacementSocket?.open();
|
|
69
|
+
staleSocket?.finishClose();
|
|
70
|
+
|
|
71
|
+
expect(MockWebSocket.instances).toHaveLength(2);
|
|
72
|
+
expect(handler).toHaveBeenLastCalledWith({ type: 'connection.open', payload: {} });
|
|
73
|
+
window.dispatchEvent(new Event('online'));
|
|
74
|
+
const secondReplacement = MockWebSocket.instances[2];
|
|
75
|
+
secondReplacement?.open();
|
|
76
|
+
replacementSocket?.finishClose();
|
|
77
|
+
const event = { type: 'session.updated', payload: { sessionKey: 'after-second-recovery' } };
|
|
78
|
+
secondReplacement?.onmessage?.({ data: JSON.stringify(event) } as MessageEvent);
|
|
79
|
+
expect(handler).toHaveBeenLastCalledWith(event);
|
|
80
|
+
expect(MockWebSocket.instances).toHaveLength(3);
|
|
81
|
+
unsubscribe();
|
|
82
|
+
window.dispatchEvent(new Event('online'));
|
|
83
|
+
expect(MockWebSocket.instances).toHaveLength(3);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -25,7 +25,6 @@ class MockWebSocket {
|
|
|
25
25
|
|
|
26
26
|
close = (): void => {
|
|
27
27
|
this.readyState = MockWebSocket.CLOSED;
|
|
28
|
-
this.onclose?.({} as CloseEvent);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
open = (): void => {
|
|
@@ -39,6 +38,11 @@ class MockWebSocket {
|
|
|
39
38
|
} as MessageEvent);
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
finishClose = (): void => {
|
|
42
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
43
|
+
this.onclose?.({} as CloseEvent);
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
static reset(): void {
|
|
43
47
|
MockWebSocket.instances = [];
|
|
44
48
|
}
|
|
@@ -144,4 +148,53 @@ describe('RemoteSessionMultiplexTransport request path', () => {
|
|
|
144
148
|
|
|
145
149
|
await timeoutExpectation;
|
|
146
150
|
});
|
|
151
|
+
|
|
152
|
+
it('replaces a possibly stale open socket when a mobile page becomes visible again', async () => {
|
|
153
|
+
let visibilityState: DocumentVisibilityState = 'hidden';
|
|
154
|
+
vi.spyOn(document, 'visibilityState', 'get').mockImplementation(() => visibilityState);
|
|
155
|
+
const transport = new RemoteSessionMultiplexTransport({
|
|
156
|
+
mode: 'remote',
|
|
157
|
+
protocolVersion: 1,
|
|
158
|
+
wsPath: '/_remote/ws'
|
|
159
|
+
}, 'https://remote.claw.cool');
|
|
160
|
+
|
|
161
|
+
const handler = vi.fn();
|
|
162
|
+
const unsubscribe = transport.subscribe(handler);
|
|
163
|
+
const staleSocket = MockWebSocket.instances[0];
|
|
164
|
+
staleSocket?.open();
|
|
165
|
+
await Promise.resolve();
|
|
166
|
+
|
|
167
|
+
const pending = transport.request({ method: 'POST', path: '/api/ncp/agent/messages', body: { text: 'once' } });
|
|
168
|
+
const pendingFailure = expect(pending).rejects.toThrow('connection replaced');
|
|
169
|
+
await Promise.resolve();
|
|
170
|
+
expect(staleSocket?.sent).toHaveLength(1);
|
|
171
|
+
|
|
172
|
+
visibilityState = 'visible';
|
|
173
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
174
|
+
|
|
175
|
+
expect(staleSocket?.readyState).toBe(MockWebSocket.CLOSED);
|
|
176
|
+
expect(MockWebSocket.instances).toHaveLength(2);
|
|
177
|
+
expect(handler).toHaveBeenCalledWith({ type: 'connection.close', payload: {} });
|
|
178
|
+
|
|
179
|
+
const replacementSocket = MockWebSocket.instances[1];
|
|
180
|
+
replacementSocket?.open();
|
|
181
|
+
staleSocket?.finishClose();
|
|
182
|
+
|
|
183
|
+
expect(MockWebSocket.instances).toHaveLength(2);
|
|
184
|
+
expect(handler).toHaveBeenLastCalledWith({ type: 'connection.open', payload: {} });
|
|
185
|
+
await pendingFailure;
|
|
186
|
+
await Promise.resolve();
|
|
187
|
+
expect(replacementSocket?.sent).toHaveLength(0);
|
|
188
|
+
window.dispatchEvent(new Event('online'));
|
|
189
|
+
const secondReplacement = MockWebSocket.instances[2];
|
|
190
|
+
secondReplacement?.open();
|
|
191
|
+
await Promise.resolve();
|
|
192
|
+
const recovered = transport.request({ method: 'GET', path: '/api/sessions' });
|
|
193
|
+
await Promise.resolve();
|
|
194
|
+
const frame = lastSentRequestFrame(secondReplacement!);
|
|
195
|
+
secondReplacement?.receive({ type: 'response', id: frame.id, status: 200, body: { ok: true, data: { total: 1 } } });
|
|
196
|
+
await expect(recovered).resolves.toEqual({ total: 1 });
|
|
197
|
+
expect(MockWebSocket.instances).toHaveLength(3);
|
|
198
|
+
unsubscribe();
|
|
199
|
+
});
|
|
147
200
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class BrowserRealtimeRecoveryService {
|
|
2
|
+
private started = false;
|
|
3
|
+
|
|
4
|
+
constructor(private readonly requestRecovery: () => void) {}
|
|
5
|
+
|
|
6
|
+
start = (): void => {
|
|
7
|
+
if (this.started) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
this.started = true;
|
|
11
|
+
document.addEventListener('visibilitychange', this.handleVisibilityChange);
|
|
12
|
+
window.addEventListener('online', this.handleOnline);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
stop = (): void => {
|
|
16
|
+
if (!this.started) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.started = false;
|
|
20
|
+
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
|
|
21
|
+
window.removeEventListener('online', this.handleOnline);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
private readonly handleVisibilityChange = (): void => {
|
|
25
|
+
if (document.visibilityState === 'visible') {
|
|
26
|
+
this.requestRecovery();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
private readonly handleOnline = (): void => {
|
|
31
|
+
if (document.visibilityState === 'visible') {
|
|
32
|
+
this.requestRecovery();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { systemStatusManager } from '@/features/system-status';
|
|
2
2
|
import type { AppEvent, AppTransport, RequestInput, StreamInput, StreamSession } from './transport.types';
|
|
3
|
+
import { BrowserRealtimeRecoveryService } from './browser-realtime-recovery.service';
|
|
3
4
|
import { requestRawApiResponse } from './request-raw-api-response.utils';
|
|
4
5
|
import { readSseStreamResult } from './sse-stream.utils';
|
|
5
6
|
import { resolveTransportWebSocketUrl } from './transport-websocket-url.utils';
|
|
@@ -53,13 +54,18 @@ class LocalRealtimeGateway {
|
|
|
53
54
|
private socket: WebSocket | null = null;
|
|
54
55
|
private reconnectTimer: number | null = null;
|
|
55
56
|
private manualClose = false;
|
|
57
|
+
private replacingAfterBrowserRecovery = false;
|
|
56
58
|
private subscribers = new Set<EventHandler>();
|
|
59
|
+
private readonly browserRecovery = new BrowserRealtimeRecoveryService(() => {
|
|
60
|
+
this.replaceSocket();
|
|
61
|
+
});
|
|
57
62
|
|
|
58
63
|
constructor(private readonly wsUrl: string) {}
|
|
59
64
|
|
|
60
65
|
subscribe = (handler: EventHandler): () => void => {
|
|
61
66
|
this.subscribers.add(handler);
|
|
62
67
|
if (this.subscribers.size === 1) {
|
|
68
|
+
this.browserRecovery.start();
|
|
63
69
|
this.connect();
|
|
64
70
|
} else if (this.socket?.readyState === WebSocket.OPEN) {
|
|
65
71
|
handler({ type: 'connection.open', payload: {} });
|
|
@@ -68,6 +74,7 @@ class LocalRealtimeGateway {
|
|
|
68
74
|
return () => {
|
|
69
75
|
this.subscribers.delete(handler);
|
|
70
76
|
if (this.subscribers.size === 0) {
|
|
77
|
+
this.browserRecovery.stop();
|
|
71
78
|
this.disconnect();
|
|
72
79
|
}
|
|
73
80
|
};
|
|
@@ -88,6 +95,7 @@ class LocalRealtimeGateway {
|
|
|
88
95
|
this.socket = socket;
|
|
89
96
|
|
|
90
97
|
socket.onopen = () => {
|
|
98
|
+
this.replacingAfterBrowserRecovery = false;
|
|
91
99
|
this.emit({ type: 'connection.open', payload: {} });
|
|
92
100
|
};
|
|
93
101
|
|
|
@@ -105,6 +113,7 @@ class LocalRealtimeGateway {
|
|
|
105
113
|
};
|
|
106
114
|
|
|
107
115
|
socket.onclose = () => {
|
|
116
|
+
this.replacingAfterBrowserRecovery = false;
|
|
108
117
|
this.emit({ type: 'connection.close', payload: {} });
|
|
109
118
|
this.socket = null;
|
|
110
119
|
if (!this.manualClose && this.subscribers.size > 0) {
|
|
@@ -123,8 +132,31 @@ class LocalRealtimeGateway {
|
|
|
123
132
|
}, 3_000);
|
|
124
133
|
};
|
|
125
134
|
|
|
135
|
+
private replaceSocket = (): void => {
|
|
136
|
+
if (this.subscribers.size === 0 || this.replacingAfterBrowserRecovery) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.replacingAfterBrowserRecovery = true;
|
|
140
|
+
if (this.reconnectTimer !== null) {
|
|
141
|
+
window.clearTimeout(this.reconnectTimer);
|
|
142
|
+
this.reconnectTimer = null;
|
|
143
|
+
}
|
|
144
|
+
const staleSocket = this.socket;
|
|
145
|
+
this.socket = null;
|
|
146
|
+
if (staleSocket) {
|
|
147
|
+
staleSocket.onopen = null;
|
|
148
|
+
staleSocket.onmessage = null;
|
|
149
|
+
staleSocket.onerror = null;
|
|
150
|
+
staleSocket.onclose = null;
|
|
151
|
+
staleSocket.close();
|
|
152
|
+
this.emit({ type: 'connection.close', payload: {} });
|
|
153
|
+
}
|
|
154
|
+
this.connect();
|
|
155
|
+
};
|
|
156
|
+
|
|
126
157
|
private disconnect = (): void => {
|
|
127
158
|
this.manualClose = true;
|
|
159
|
+
this.replacingAfterBrowserRecovery = false;
|
|
128
160
|
if (this.reconnectTimer !== null) {
|
|
129
161
|
window.clearTimeout(this.reconnectTimer);
|
|
130
162
|
this.reconnectTimer = null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApiError } from '@nextclaw/client-sdk';
|
|
2
2
|
import type { AppEvent, AppTransport, RemoteRuntimeInfo, RequestInput, StreamInput, StreamSession } from './transport.types';
|
|
3
|
+
import { BrowserRealtimeRecoveryService } from './browser-realtime-recovery.service';
|
|
3
4
|
import { resolveTransportWebSocketUrl } from './transport-websocket-url.utils';
|
|
4
5
|
|
|
5
6
|
type RemoteTarget = {
|
|
@@ -95,6 +96,9 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
|
|
|
95
96
|
private subscribers = new Set<(event: AppEvent) => void>();
|
|
96
97
|
private pendingRequests = new Map<string, PendingRequest>();
|
|
97
98
|
private pendingStreams = new Map<string, PendingStream>();
|
|
99
|
+
private readonly browserRecovery = new BrowserRealtimeRecoveryService(() => {
|
|
100
|
+
this.replaceSocket();
|
|
101
|
+
});
|
|
98
102
|
|
|
99
103
|
constructor(
|
|
100
104
|
private readonly runtime: RemoteRuntimeInfo,
|
|
@@ -215,6 +219,7 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
|
|
|
215
219
|
|
|
216
220
|
subscribe = (handler: (event: AppEvent) => void): () => void => {
|
|
217
221
|
this.subscribers.add(handler);
|
|
222
|
+
this.browserRecovery.start();
|
|
218
223
|
void this.ensureSocket().catch((error) => {
|
|
219
224
|
handler({
|
|
220
225
|
type: 'connection.error',
|
|
@@ -223,6 +228,9 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
|
|
|
223
228
|
});
|
|
224
229
|
return () => {
|
|
225
230
|
this.subscribers.delete(handler);
|
|
231
|
+
if (this.subscribers.size === 0) {
|
|
232
|
+
this.browserRecovery.stop();
|
|
233
|
+
}
|
|
226
234
|
};
|
|
227
235
|
}
|
|
228
236
|
|
|
@@ -321,6 +329,28 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
|
|
|
321
329
|
}, 3_000);
|
|
322
330
|
}
|
|
323
331
|
|
|
332
|
+
private replaceSocket = (): void => {
|
|
333
|
+
if (this.subscribers.size === 0 || this.connectPromise !== null) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (this.reconnectTimer !== null) {
|
|
337
|
+
window.clearTimeout(this.reconnectTimer);
|
|
338
|
+
this.reconnectTimer = null;
|
|
339
|
+
}
|
|
340
|
+
const staleSocket = this.socket;
|
|
341
|
+
this.socket = null;
|
|
342
|
+
if (staleSocket) {
|
|
343
|
+
staleSocket.onopen = null;
|
|
344
|
+
staleSocket.onmessage = null;
|
|
345
|
+
staleSocket.onerror = null;
|
|
346
|
+
staleSocket.onclose = null;
|
|
347
|
+
staleSocket.close();
|
|
348
|
+
this.failPendingWork(new Error('Remote transport connection replaced after browser recovery.'));
|
|
349
|
+
this.emit({ type: 'connection.close', payload: {} });
|
|
350
|
+
}
|
|
351
|
+
void this.ensureSocket().catch(() => undefined);
|
|
352
|
+
}
|
|
353
|
+
|
|
324
354
|
private failPendingWork = (error: Error): void => {
|
|
325
355
|
for (const pending of this.pendingRequests.values()) {
|
|
326
356
|
window.clearTimeout(pending.timeoutId);
|