@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.
Files changed (308) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/.vite/manifest.json +1309 -991
  3. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/dist/assets/{abnfDiagram-VRR7QNED-Kzlh3ZEC.js → abnfDiagram-VRR7QNED-DX_u5BBX.js} +1 -1
  63. package/dist/assets/{account-panel-BRTSwNvA.js → account-panel-QhOMbAQP.js} +1 -1
  64. package/dist/assets/{aiden0z-pptx-renderer.es-k1xZLzeo.js → aiden0z-pptx-renderer.es-C-E9e5yB.js} +1 -1
  65. package/dist/assets/{api-BnUzdltV.js → api-BWFOIoUm.js} +5 -5
  66. package/dist/assets/{app-data-Bjh_zM45.js → app-data-BnrEdmg2.js} +1 -1
  67. package/dist/assets/app-presenter-provider-CDRBc_lQ.js +115 -0
  68. package/dist/assets/app-presenter-provider-jNGlmBLe.css +1 -0
  69. package/dist/assets/{appearance-settings-page-BrOYSgG0.js → appearance-settings-page-C5z5sOkC.js} +1 -1
  70. package/dist/assets/{arc-CpivM836.js → arc-Cxwa624o.js} +1 -1
  71. package/dist/assets/architecture-TIHT7OUA-DEt0Abz9.js +1 -0
  72. package/dist/assets/{architectureDiagram-ZJ3FMSHR-ifjW1qjI.js → architectureDiagram-ZJ3FMSHR-D8mCj0H1.js} +1 -1
  73. package/dist/assets/{blockDiagram-677ZJIJ3-C_zeJrKF.js → blockDiagram-677ZJIJ3-CbGC1IHl.js} +1 -1
  74. package/dist/assets/{c4Diagram-LMCZKHZV-CpapRftp.js → c4Diagram-LMCZKHZV-BQhGC1Vc.js} +1 -1
  75. package/dist/assets/channel-CxQkbwRD.js +1 -0
  76. package/dist/assets/{channels-list-page-BE0TxB3B.js → channels-list-page-CzXYZZtK.js} +1 -1
  77. package/dist/assets/chunk-2Q5K7J3B-ClVhziok.js +1 -0
  78. package/dist/assets/{chunk-32BRIVSS-BKBwdmKR.js → chunk-32BRIVSS-B74ZZwYG.js} +1 -1
  79. package/dist/assets/{chunk-52WLFC77-BgZRCpDx.js → chunk-52WLFC77-BvmxW_f_.js} +1 -1
  80. package/dist/assets/{chunk-5FCAYU7R-YWfOINJi.js → chunk-5FCAYU7R-DQvBRnwL.js} +1 -1
  81. package/dist/assets/{chunk-5HE753X5-BZ6I07d2.js → chunk-5HE753X5-CDIH0_y3.js} +1 -1
  82. package/dist/assets/{chunk-5JV3BV7I-B7QYgZU5.js → chunk-5JV3BV7I-CcevlKd-.js} +1 -1
  83. package/dist/assets/{chunk-5TONJI2A-DjyQwgL2.js → chunk-5TONJI2A-_3dxVdnY.js} +1 -1
  84. package/dist/assets/{chunk-5VM5RSS4-DheHV7X2.js → chunk-5VM5RSS4-pGO5vvo-.js} +1 -1
  85. package/dist/assets/{chunk-7BUUIJ7U-D6bq2PKF.js → chunk-7BUUIJ7U-DEKF5uAN.js} +1 -1
  86. package/dist/assets/{chunk-BIQX33UG-CL9x0ibm.js → chunk-BIQX33UG-CjBFgq2r.js} +1 -1
  87. package/dist/assets/{chunk-C7G6YPKG-8z3B6RUH.js → chunk-C7G6YPKG-e_K9OGTz.js} +1 -1
  88. package/dist/assets/{chunk-CQNSW5MT-DpHkxY3n.js → chunk-CQNSW5MT-C2IiHyHz.js} +1 -1
  89. package/dist/assets/{chunk-CYSBUYHQ-DIJDLsdJ.js → chunk-CYSBUYHQ-DCC6KmGp.js} +1 -1
  90. package/dist/assets/{chunk-EMLP6XTP-B-LjAyVd.js → chunk-EMLP6XTP-BYGgekQ0.js} +1 -1
  91. package/dist/assets/{chunk-EX3LRPZG-BChuEPvO.js → chunk-EX3LRPZG-FGcwuVsA.js} +1 -1
  92. package/dist/assets/{chunk-FWX5IMBZ-fBFt0u3f.js → chunk-FWX5IMBZ-CV3Ih_Os.js} +2 -2
  93. package/dist/assets/{chunk-HOUHSVGY-MD0SpW5A.js → chunk-HOUHSVGY-CXpq_hfV.js} +1 -1
  94. package/dist/assets/{chunk-ICXQ74PX-DvCcw_hz.js → chunk-ICXQ74PX-CZIpjupq.js} +1 -1
  95. package/dist/assets/{chunk-JG7HCLWE-B8zqBnU9.js → chunk-JG7HCLWE-CdgwBpW7.js} +1 -1
  96. package/dist/assets/{chunk-JWPE2WC7-C9efnkGb.js → chunk-JWPE2WC7-BPjAZpOQ.js} +1 -1
  97. package/dist/assets/{chunk-MOJQB5TN-gXbfqoqq.js → chunk-MOJQB5TN-DbJDm8to.js} +1 -1
  98. package/dist/assets/{chunk-MOZMSUNE-twIwdkqC.js → chunk-MOZMSUNE-BUa1fqKn.js} +1 -1
  99. package/dist/assets/{chunk-OGEWGWER-DdR0_cWK.js → chunk-OGEWGWER-CtnzLgRu.js} +1 -1
  100. package/dist/assets/{chunk-OSBZ3O6U-D7D58Foa.js → chunk-OSBZ3O6U-fUgdpTUM.js} +1 -1
  101. package/dist/assets/{chunk-PUDLZKDR-DPILgOVY.js → chunk-PUDLZKDR-CtDWZT9-.js} +1 -1
  102. package/dist/assets/{chunk-Q4XR5HBZ-C0JJx7lW.js → chunk-Q4XR5HBZ-BxnK2qxr.js} +1 -1
  103. package/dist/assets/{chunk-QBLGF6JB-Dbsf69ng.js → chunk-QBLGF6JB-CH4UKoCF.js} +1 -1
  104. package/dist/assets/{chunk-R7FJI6CG-Bb8n616-.js → chunk-R7FJI6CG-BxwFoF1v.js} +1 -1
  105. package/dist/assets/{chunk-RYQCIY6F-1HOsOrSl.js → chunk-RYQCIY6F-DpHAmq7O.js} +1 -1
  106. package/dist/assets/{chunk-U6XO7XAA-BAFDVb1v.js → chunk-U6XO7XAA-BjH2VUHU.js} +1 -1
  107. package/dist/assets/{chunk-V7JOEXUC-8-g68lu5.js → chunk-V7JOEXUC-DKJZ3HGj.js} +1 -1
  108. package/dist/assets/chunk-VAUOI2AC-DZQxkIsv.js +1 -0
  109. package/dist/assets/{chunk-VR4S4FIN-P6UGmaYd.js → chunk-VR4S4FIN-Cf-o2GtI.js} +1 -1
  110. package/dist/assets/{chunk-WYO6CB5R-6wjFTXNP.js → chunk-WYO6CB5R-zeNXGm20.js} +3 -2
  111. package/dist/assets/chunk-XXDRQBXY-BkExtw5C.js +1 -0
  112. package/dist/assets/{chunk-YOTPTUD7-CLYXVZAi.js → chunk-YOTPTUD7-Ck87Kqxe.js} +1 -1
  113. package/dist/assets/{chunk-ZGVPDNZ5-BRTmSAhp.js → chunk-ZGVPDNZ5-BbfK6Bfd.js} +1 -1
  114. package/dist/assets/{chunk-ZIRB5QZD-CH_1eoSV.js → chunk-ZIRB5QZD-BDpzh4Yl.js} +1 -1
  115. package/dist/assets/classDiagram-OUVF2IWQ-Bd6apBb-.js +1 -0
  116. package/dist/assets/classDiagram-v2-EOCWNBFH-CYxUZGCI.js +1 -0
  117. package/dist/assets/{config-split-page-BxDwwAGa.js → config-split-page-CxLWGwdt.js} +1 -1
  118. package/dist/assets/{confirm-dialog-S5JxZcS1.js → confirm-dialog-BWS7gA_z.js} +1 -1
  119. package/dist/assets/{cose-bilkent-JH36ORCC-L7w_qScX.js → cose-bilkent-JH36ORCC-CQfMxKCT.js} +1 -1
  120. package/dist/assets/cynefin-VYW2F7L2-CV0bFLH9.js +1 -0
  121. package/dist/assets/{cynefinDiagram-TSTJHNR4-Bay7uDSR.js → cynefinDiagram-TSTJHNR4-CxFTiC-p.js} +1 -1
  122. package/dist/assets/{dagre-CJ_hX8xv.js → dagre-CaBNMcB1.js} +1 -1
  123. package/dist/assets/{dagre-VKFMJZFB-DDhk-2B3.js → dagre-VKFMJZFB-S_7Hc7bI.js} +1 -1
  124. package/dist/assets/{desktop-capabilities-page-BwHxJpbl.js → desktop-capabilities-page-BJNGkq4q.js} +1 -1
  125. package/dist/assets/{desktop-update-config-Bfg7OdJ2.js → desktop-update-config-7tBum3MA.js} +1 -1
  126. package/dist/assets/{diagram-FQU43EPY-somFq5y8.js → diagram-FQU43EPY-C7zENvFb.js} +1 -1
  127. package/dist/assets/{diagram-G47NLZAW-hzgyQsPd.js → diagram-G47NLZAW-DqYTCqZA.js} +1 -1
  128. package/dist/assets/{diagram-NH7WQ7WH-BkJ4gEQJ.js → diagram-NH7WQ7WH-BfSJtXRc.js} +1 -1
  129. package/dist/assets/{diagram-OA4YK3LP-CS3ZkK4w.js → diagram-OA4YK3LP-CFO-j67H.js} +1 -1
  130. package/dist/assets/{diagram-WEI45ONY-BApo-JHS.js → diagram-WEI45ONY-DBQDbTaJ.js} +1 -1
  131. package/dist/assets/{doc-browser-C8eqJLi_.js → doc-browser-BNiqa-zm.js} +1 -1
  132. package/dist/assets/doc-browser-DZDqghNc.js +1 -0
  133. package/dist/assets/{doc-browser-context-BPCDFcC-.js → doc-browser-context-Dp0dEMyT.js} +1 -1
  134. package/dist/assets/{docx-preview-ByCKKIVT.js → docx-preview-B4-aDfvO.js} +1 -1
  135. package/dist/assets/{ebnfDiagram-CCIWWBDH-DWjfeEie.js → ebnfDiagram-CCIWWBDH-CfDNIXFN.js} +1 -1
  136. package/dist/assets/{erDiagram-Q63AITRT-DR7vWYHM.js → erDiagram-Q63AITRT-D95rFXMM.js} +1 -1
  137. package/dist/assets/eventmodeling-45OFAUF4-BzkLWWdP.js +1 -0
  138. package/dist/assets/{extensions-DgKQRebe.js → extensions-WWEYpaUh.js} +1 -1
  139. package/dist/assets/flowDiagram-23GEKE2U-ukkCsB0j.js +1 -0
  140. package/dist/assets/{ganttDiagram-NO4QXBWP-E3l9tuaL.js → ganttDiagram-NO4QXBWP-Bawg0P0r.js} +1 -1
  141. package/dist/assets/gitGraph-TEB2WS4Q-BYFhT0Q5.js +1 -0
  142. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DiMvbHKf.js → gitGraphDiagram-IHSO6WYX-BbKh9nbL.js} +1 -1
  143. package/dist/assets/{i18n-CKvm9zyk.js → i18n-ClHx6jwj.js} +2 -2
  144. package/dist/assets/{i18n-provider-CPV43pMX.js → i18n-provider-C1NxogO4.js} +1 -1
  145. package/dist/assets/info-DKCQHKI2-CTmlJdcH.js +1 -0
  146. package/dist/assets/{infoDiagram-FWYZ7A6U-BZDL0NDJ.js → infoDiagram-FWYZ7A6U-DPffZXYh.js} +1 -1
  147. package/dist/assets/{ishikawaDiagram-FXEZZL3T-BZ9WvJb2.js → ishikawaDiagram-FXEZZL3T-CBMsLwZ2.js} +1 -1
  148. package/dist/assets/{journeyDiagram-5HDEW3XC-C3TZvKM8.js → journeyDiagram-5HDEW3XC-Mz5U4OzU.js} +1 -1
  149. package/dist/assets/{kanban-definition-HUTT4EX6-9FqauDEi.js → kanban-definition-HUTT4EX6-FXZq2k19.js} +1 -1
  150. package/dist/assets/{katex-J8JuvgNl.js → katex-B26hhTOT.js} +15 -15
  151. package/dist/assets/katex-DbZC06Q5.js +1 -0
  152. package/dist/assets/keyboard-shortcuts-settings-page-CS9BFOBW.js +1 -0
  153. package/dist/assets/{line-Ca49mj80.js → line-BZwCgT9K.js} +1 -1
  154. package/dist/assets/{linear-DlCeG66S.js → linear-CTe9mKUw.js} +1 -1
  155. package/dist/assets/{login-page-DeDQjVxv.js → login-page-CL0SrBCf.js} +1 -1
  156. package/dist/assets/login-page-Dq5nPUCU.js +1 -0
  157. package/dist/assets/main-CLwCjK9U.css +1 -0
  158. package/dist/assets/main-CvVBCOlN.js +2 -0
  159. package/dist/assets/main-i18n-catalog.config-D4qFo4OB.js +1 -0
  160. package/dist/assets/{map-WMnNq9UV.js → map-Ci48ZwAp.js} +1 -1
  161. package/dist/assets/mcp-marketplace-page-CATnpZ2Q.js +1 -0
  162. package/dist/assets/{mcp-marketplace-page-CWjJGp_D.js → mcp-marketplace-page-D75hPmzO.js} +1 -1
  163. package/dist/assets/{mermaid-parser.core-B8xk4hpS.js → mermaid-parser.core-CzPD3U_z.js} +2 -2
  164. package/dist/assets/{mermaid.core-CFPiczKH.js → mermaid.core-geIMNPbV.js} +3 -3
  165. package/dist/assets/{mindmap-definition-LN4V7U3C-CnxA52L6.js → mindmap-definition-LN4V7U3C-xqlKxsr_.js} +1 -1
  166. package/dist/assets/{model-config-page-Dv_zE7HN.js → model-config-page-DFfcV9lu.js} +1 -1
  167. package/dist/assets/{ordinal-PgiFlfBJ.js → ordinal-MxnX8yhv.js} +1 -1
  168. package/dist/assets/packet-7NZHBO7P-BbrI0ZlN.js +1 -0
  169. package/dist/assets/panel-app-B39rJPj8.js +1 -0
  170. package/dist/assets/panel-app-i18n-catalog.config-oUwmxoE7.js +1 -0
  171. package/dist/assets/{panelStandalone-CFRBHV94.js → panelStandalone-D2CqTyi_.js} +2 -2
  172. package/dist/assets/{pegDiagram-2B236MQR-A8oz7dTH.js → pegDiagram-2B236MQR-D50MFTN1.js} +1 -1
  173. package/dist/assets/pie-RZYD4A2V-B7zE1ZIr.js +1 -0
  174. package/dist/assets/{pieDiagram-ENE6RG2P-mtBnkYY0.js → pieDiagram-ENE6RG2P-CpA13oiC.js} +1 -1
  175. package/dist/assets/{privacy-settings-page-D6uc9f-A.js → privacy-settings-page-CE-xF9bo.js} +1 -1
  176. package/dist/assets/projects-BF0wpY4X.js +1 -0
  177. package/dist/assets/{provider-scoped-model-input-DUa_9uwg.js → provider-scoped-model-input-BvZitHkT.js} +1 -1
  178. package/dist/assets/{providers-config-page-DAiqxlF_.js → providers-config-page-cDeyluVG.js} +1 -1
  179. package/dist/assets/{pwa-C_Zte1xe.js → pwa-dXn0s_lD.js} +1 -1
  180. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DE1VybGO.js → quadrantDiagram-ABIIQ3AL-CYCgaj4l.js} +1 -1
  181. package/dist/assets/radar-I7S5WNFK-1X_q11Ed.js +1 -0
  182. package/dist/assets/railroad-3IZDKUUU-CIXnuFyE.js +1 -0
  183. package/dist/assets/railroad-abnf-AHOZXSZD-D6ImKl2r.js +1 -0
  184. package/dist/assets/railroad-ebnf-EBAXGLYW-2j9d3twU.js +1 -0
  185. package/dist/assets/railroad-peg-LSFZ7HO6-DMVaJGbm.js +1 -0
  186. package/dist/assets/{railroadDiagram-RFXS5EU6-u9EMg14B.js → railroadDiagram-RFXS5EU6-4LKDGlXN.js} +1 -1
  187. package/dist/assets/remote-CV5wQVVj.js +1 -0
  188. package/dist/assets/{requirementDiagram-TGXJPOKE-BAWHAqEi.js → requirementDiagram-TGXJPOKE-CxNOU0JH.js} +1 -1
  189. package/dist/assets/runtime-config-page-DNo4dzS1.js +1 -0
  190. package/dist/assets/{sankeyDiagram-HTMAVEWB-aDy-jdwF.js → sankeyDiagram-HTMAVEWB-DbCBDe2S.js} +1 -1
  191. package/dist/assets/{search-config-page-3xbhRlW2.js → search-config-page-CA0HxVy0.js} +1 -1
  192. package/dist/assets/{secrets-config-page-Dn8_rdOj.js → secrets-config-page-iT1wamQp.js} +1 -1
  193. package/dist/assets/security-config-B38PLI3M.js +1 -0
  194. package/dist/assets/{sequenceDiagram-DBY2YBRQ-DwHC4mbX.js → sequenceDiagram-DBY2YBRQ-DA6W2BS_.js} +1 -1
  195. package/dist/assets/{service-apps-panel-CGc04aMn.js → service-apps-panel-CU0tFrqT.js} +1 -1
  196. package/dist/assets/{sizeCapture-X5ZJPWSS-oOMY0Fag.js → sizeCapture-X5ZJPWSS-T_q_d_VW.js} +1 -1
  197. package/dist/assets/{src-CLPoo5iL.js → src-CYpUlx-4.js} +1 -1
  198. package/dist/assets/{stateDiagram-2N3HPSRC-B0Epk-VR.js → stateDiagram-2N3HPSRC-mi8z2G0a.js} +1 -1
  199. package/dist/assets/stateDiagram-v2-6OUMAXLB-OlIMT57r.js +1 -0
  200. package/dist/assets/{swimlanes-5IMT3BWC-T0w-Iuzf.js → swimlanes-5IMT3BWC-DkBEhWE4.js} +2 -2
  201. package/dist/assets/swimlanesDiagram-G3AALYLV-sLThrh07.js +8 -0
  202. package/dist/assets/{tag-chip-BDzpHEli.js → tag-chip-CY0Hqk_l.js} +1 -1
  203. package/dist/assets/{theme-provider-B6FinATQ.js → theme-provider-BvpXGXWH.js} +1 -1
  204. package/dist/assets/{timeline-definition-FHXFAJF6-BEonB5Np.js → timeline-definition-FHXFAJF6-BoFmqBw1.js} +1 -1
  205. package/dist/assets/treeView-QDETBFTQ-CCFyazDE.js +1 -0
  206. package/dist/assets/treemap-6X3UGDF4-D6wvq1Hz.js +1 -0
  207. package/dist/assets/{use-config-BPUM2lbo.js → use-config-C8ZkQiTc.js} +1 -1
  208. package/dist/assets/{use-desktop-capabilities-BDjArQP3.js → use-desktop-capabilities-DR9snbeo.js} +1 -1
  209. package/dist/assets/{vennDiagram-L72KCM5P-BDvcOCo2.js → vennDiagram-L72KCM5P-BlZ3Or5U.js} +1 -1
  210. package/dist/assets/wardley-OPB4EBWU-BRzaRyFf.js +1 -0
  211. package/dist/assets/{wardleyDiagram-EHGQE667-jMLMgjzE.js → wardleyDiagram-EHGQE667-yDfr33i3.js} +1 -1
  212. package/dist/assets/{xychartDiagram-FW5EYKEG-pzrpclEv.js → xychartDiagram-FW5EYKEG-mIXwIGAQ.js} +1 -1
  213. package/dist/index.html +24 -23
  214. package/dist/panel-standalone.html +6 -6
  215. package/package.json +8 -8
  216. package/src/app/configs/app-navigation.config.ts +10 -3
  217. package/src/app/index.tsx +7 -0
  218. package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
  219. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +12 -0
  220. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +13 -1
  221. package/src/features/chat/features/conversation/components/__tests__/session-voice-input.test-utils.ts +52 -0
  222. package/src/features/chat/features/conversation/components/chat-voice-input-panel.tsx +70 -0
  223. package/src/features/chat/features/conversation/components/chat-voice-shortcut-control.tsx +33 -0
  224. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -11
  225. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +29 -5
  226. package/src/features/chat/features/conversation/hooks/__tests__/use-chat-voice-input.test.tsx +185 -0
  227. package/src/features/chat/features/conversation/hooks/use-chat-voice-input.ts +52 -0
  228. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +13 -6
  229. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +28 -12
  230. package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +19 -1
  231. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +7 -6
  232. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +4 -1
  233. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +20 -0
  234. package/src/features/chat/index.ts +1 -0
  235. package/src/features/chat/managers/chat-voice-input.manager.ts +174 -0
  236. package/src/features/chat/presenters/chat.presenter.ts +2 -0
  237. package/src/features/chat/stores/chat-voice-shortcut.store.ts +31 -0
  238. package/src/features/desktop-capabilities/index.ts +1 -0
  239. package/src/features/settings/pages/keyboard-shortcuts-settings-page.tsx +15 -0
  240. package/src/platforms/mobile/components/mobile-settings-shell.tsx +2 -1
  241. package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +109 -1
  242. package/src/shared/lib/api/ncp-session-query-cache.utils.ts +71 -2
  243. package/src/shared/lib/i18n/locales/en-US/chat.json +32 -1
  244. package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
  245. package/src/shared/lib/i18n/locales/zh-CN/chat.json +32 -1
  246. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
  247. package/src/shared/lib/transport/__tests__/browser-realtime-recovery.service.test.ts +42 -0
  248. package/src/shared/lib/transport/__tests__/local-transport.service.test.ts +85 -0
  249. package/src/shared/lib/transport/__tests__/remote.transport.test.ts +54 -1
  250. package/src/shared/lib/transport/browser-realtime-recovery.service.ts +35 -0
  251. package/src/shared/lib/transport/local-transport.service.ts +32 -0
  252. package/src/shared/lib/transport/remote-transport.service.ts +30 -0
  253. package/dist/assets/app-presenter-provider-CBt-lyr_.js +0 -109
  254. package/dist/assets/app-presenter-provider-D3BuuOyZ.css +0 -1
  255. package/dist/assets/architecture-TIHT7OUA-CVRF_cYQ.js +0 -1
  256. package/dist/assets/channel-CBrWRr4N.js +0 -1
  257. package/dist/assets/chunk-2Q5K7J3B-B8mmRk7d.js +0 -1
  258. package/dist/assets/chunk-VAUOI2AC-Cpml0inz.js +0 -1
  259. package/dist/assets/chunk-XXDRQBXY-nCiqnCpF.js +0 -1
  260. package/dist/assets/classDiagram-OUVF2IWQ-C-DRqAaW.js +0 -1
  261. package/dist/assets/classDiagram-v2-EOCWNBFH-DXCcZ42U.js +0 -1
  262. package/dist/assets/cynefin-VYW2F7L2-2SOTgxxC.js +0 -1
  263. package/dist/assets/doc-browser-BgRP0Wni.js +0 -1
  264. package/dist/assets/eventmodeling-45OFAUF4-CXZNG56U.js +0 -1
  265. package/dist/assets/flowDiagram-23GEKE2U-2065oyKr.js +0 -1
  266. package/dist/assets/gitGraph-TEB2WS4Q-B0Ln3ZuK.js +0 -1
  267. package/dist/assets/info-DKCQHKI2-B06uqGM4.js +0 -1
  268. package/dist/assets/login-page-ClcrvN04.js +0 -1
  269. package/dist/assets/main-DGqlaAaI.css +0 -1
  270. package/dist/assets/main-i18n-catalog.config-CpNF5aVc.js +0 -1
  271. package/dist/assets/main-nMVfHb5K.js +0 -2
  272. package/dist/assets/mcp-marketplace-page-fvu2NcXT.js +0 -1
  273. package/dist/assets/packet-7NZHBO7P-CHx-igbz.js +0 -1
  274. package/dist/assets/panel-app-BUUle0gP.js +0 -1
  275. package/dist/assets/panel-app-i18n-catalog.config-BzxE3tZb.js +0 -1
  276. package/dist/assets/pie-RZYD4A2V-DOaMc-x9.js +0 -1
  277. package/dist/assets/projects-BD1rG75R.js +0 -1
  278. package/dist/assets/radar-I7S5WNFK-CXsdndDk.js +0 -1
  279. package/dist/assets/railroad-3IZDKUUU-DxgXdrYP.js +0 -1
  280. package/dist/assets/railroad-abnf-AHOZXSZD-CR1tnyjz.js +0 -1
  281. package/dist/assets/railroad-ebnf-EBAXGLYW-CjjxZR2U.js +0 -1
  282. package/dist/assets/railroad-peg-LSFZ7HO6-DgVGJCHN.js +0 -1
  283. package/dist/assets/remote-BO0QP7ua.js +0 -1
  284. package/dist/assets/runtime-config-page-w1UAkGF_.js +0 -1
  285. package/dist/assets/security-config-Y1nEGM3A.js +0 -1
  286. package/dist/assets/stateDiagram-v2-6OUMAXLB-Ct4zTw8P.js +0 -1
  287. package/dist/assets/swimlanesDiagram-G3AALYLV-CdmN0PBd.js +0 -8
  288. package/dist/assets/treeView-QDETBFTQ-Dad4_Wc7.js +0 -1
  289. package/dist/assets/treemap-6X3UGDF4-9_GWWZsY.js +0 -1
  290. package/dist/assets/wardley-OPB4EBWU-BXpvyee9.js +0 -1
  291. /package/dist/assets/{array-BHDgItFN.js → array-D0DyKZdT.js} +0 -0
  292. /package/dist/assets/{card-CS8su2Zh.js → card-BhfAZv5L.js} +0 -0
  293. /package/dist/assets/{chunk-KEIR6QF5-DstBw0Hk.js → chunk-KEIR6QF5-CuZFOa6q.js} +0 -0
  294. /package/dist/assets/{chunk-Y2CYZVJY-CA3ymQuH.js → chunk-Y2CYZVJY-Frut-1TF.js} +0 -0
  295. /package/dist/assets/{config-hints-CBPHrfYk.js → config-hints-B_C9DAp5.js} +0 -0
  296. /package/dist/assets/{cytoscape.esm-C7xr90o_.js → cytoscape.esm-BibuhnZD.js} +0 -0
  297. /package/dist/assets/{defaultLocale-BG1oDVag.js → defaultLocale-q7sRUXPc.js} +0 -0
  298. /package/dist/assets/{dist-Cnhwure9.js → dist-DhMddrrH.js} +0 -0
  299. /package/dist/assets/{form-actions-AIdXOoCL.js → form-actions-BRESe-m_.js} +0 -0
  300. /package/dist/assets/{graphlib-CNMGfuTb.js → graphlib-BM63WsW3.js} +0 -0
  301. /package/dist/assets/{init-wQIZuRSH.js → init-CRMkf9sL.js} +0 -0
  302. /package/dist/assets/{jszip.min-D_AtKL_8.js → jszip.min-6thm2J_a.js} +0 -0
  303. /package/dist/assets/{path-N-C4MRP0.js → path-BFMmafCM.js} +0 -0
  304. /package/dist/assets/{provider-models-5PGHb6mP.js → provider-models-hrHMcHqg.js} +0 -0
  305. /package/dist/assets/{rough.esm-BeWA068d.js → rough.esm-5Tjchx1D.js} +0 -0
  306. /package/dist/assets/{skeleton-BL9h32W7.js → skeleton-Drvn83YW.js} +0 -0
  307. /package/dist/assets/{use-panel-app-scroll-restoration-DsV1IJx-.js → use-panel-app-scroll-restoration-ZnicIfo3.js} +0 -0
  308. /package/dist/assets/{xlsx-CwUXNtD3.js → xlsx-BC8ibjXK.js} +0 -0
@@ -31,6 +31,22 @@ function buildDiscoveredModelGroups(options: ChatModelRecord[]) {
31
31
  return [...groups.values()];
32
32
  }
33
33
 
34
+ function buildRemainingModelGroups(options: ChatModelRecord[], allModelsLabel: string) {
35
+ const groups = new Map<string, { key: string; label: string; options: Array<{ value: string; label: string }> }>();
36
+ for (const option of options) {
37
+ const separatorIndex = option.value.indexOf('/');
38
+ const providerKey = separatorIndex > 0 ? option.value.slice(0, separatorIndex) : "other";
39
+ const group = groups.get(providerKey) ?? {
40
+ key: `provider-${providerKey}`,
41
+ label: option.providerLabel.trim() || allModelsLabel,
42
+ options: [],
43
+ };
44
+ group.options.push({ value: option.value, label: option.modelLabel.trim() });
45
+ groups.set(providerKey, group);
46
+ }
47
+ return [...groups.values()];
48
+ }
49
+
34
50
  export function toChatModelRecords(snapshotModels: ChatModelOption[]): ChatModelRecord[] {
35
51
  return snapshotModels.map((model) => ({
36
52
  value: model.value,
@@ -183,8 +199,9 @@ export function buildModelToolbarSelect({
183
199
  const remainingOptions = modelOptions.filter(
184
200
  (option) => !favoriteValueSet.has(option.value) && !recentValueSet.has(option.value),
185
201
  );
186
- const optionGroups = favoriteOptions.length > 0 || recentOptions.length > 0
187
- ? [
202
+ const groups = [
203
+ ...(favoriteOptions.length > 0
204
+ ? [
188
205
  {
189
206
  key: "favorite-models",
190
207
  label: texts.favoriteModelsLabel,
@@ -193,6 +210,10 @@ export function buildModelToolbarSelect({
193
210
  label: formatModelOptionLabel(option),
194
211
  })),
195
212
  },
213
+ ]
214
+ : []),
215
+ ...(recentOptions.length > 0
216
+ ? [
196
217
  {
197
218
  key: "recent-models",
198
219
  label: texts.recentModelsLabel,
@@ -201,16 +222,11 @@ export function buildModelToolbarSelect({
201
222
  label: formatModelOptionLabel(option),
202
223
  })),
203
224
  },
204
- {
205
- key: "all-models",
206
- label: texts.allModelsLabel,
207
- options: remainingOptions.map((option) => ({
208
- value: option.value,
209
- label: formatModelOptionLabel(option),
210
- })),
211
- },
212
- ].filter((group) => group.options.length > 0)
213
- : undefined;
225
+ ]
226
+ : []),
227
+ ...buildRemainingModelGroups(remainingOptions, texts.allModelsLabel),
228
+ ].filter((group) => group.options.length > 0);
229
+ const optionGroups = groups.length > 0 ? groups : undefined;
214
230
  const discoveryOptions = discoveredModelOptions ?? [];
215
231
 
216
232
  return {
@@ -1,4 +1,5 @@
1
- import type { NcpMessage } from "@nextclaw/ncp";
1
+ import { NcpEventType, type NcpMessage } from "@nextclaw/ncp";
2
+ import { DefaultNcpAgentConversationStateManager } from "@nextclaw/ncp-toolkit";
2
3
  import { describe, expect, it } from "vitest";
3
4
  import { buildChatMessageProcessSummary } from "@/features/chat/features/message/utils/chat-message-process-summary.utils";
4
5
 
@@ -29,6 +30,23 @@ const baseAssistantMessage = {
29
30
  } satisfies NcpMessage;
30
31
 
31
32
  describe("buildChatMessageProcessSummary", () => {
33
+ it("shows elapsed time from the real completed-message then run-finished event sequence", async () => {
34
+ const manager = new DefaultNcpAgentConversationStateManager();
35
+ await manager.dispatchBatch([
36
+ { type: NcpEventType.RunStarted, payload: { sessionId: "session-1", runId: "run-1" } },
37
+ { type: NcpEventType.MessageCompleted, payload: { sessionId: "session-1", message: baseAssistantMessage } },
38
+ {
39
+ type: NcpEventType.RunFinished,
40
+ payload: {
41
+ sessionId: "session-1", runId: "run-1", messageId: baseAssistantMessage.id,
42
+ startedAt: "2026-03-31T10:00:00.000Z", endedAt: "2026-03-31T10:03:51.000Z",
43
+ },
44
+ },
45
+ ]);
46
+ const message = manager.getSnapshot().messages[0]!;
47
+ expect(buildChatMessageProcessSummary({ message, processedLabel: "已处理" })?.label).toBe("已处理 3m 51s");
48
+ });
49
+
32
50
  it("does not invent duration when lifecycle timing is absent", () => {
33
51
  expect(
34
52
  buildChatMessageProcessSummary({
@@ -242,10 +242,11 @@ it("preserves full generic tool args for the expanded body while keeping the hea
242
242
 
243
243
  it("keeps structured terminal results as structured data instead of raw json output", () => {
244
244
  const terminalResult = {
245
- status: "completed",
246
- command: "python3 -m http.server 8765",
247
- aggregated_output: "",
248
- exit_code: 0,
245
+ ok: true,
246
+ command: "nextclaw --version",
247
+ exitCode: 0,
248
+ stdout: "0.48.3\n",
249
+ stderr: "",
249
250
  };
250
251
 
251
252
  const adapted = adapt([
@@ -259,7 +260,7 @@ it("keeps structured terminal results as structured data instead of raw json out
259
260
  status: ToolInvocationStatus.RESULT,
260
261
  toolCallId: "call-terminal-result",
261
262
  toolName: "command_execution",
262
- args: '{"command":"python3 -m http.server 8765"}',
263
+ args: '{"command":"nextclaw --version"}',
263
264
  result: terminalResult,
264
265
  },
265
266
  },
@@ -271,7 +272,7 @@ it("keeps structured terminal results as structured data instead of raw json out
271
272
  type: "tool-card",
272
273
  card: {
273
274
  toolName: "command_execution",
274
- summary: "command: python3 -m http.server 8765",
275
+ summary: "command: nextclaw --version",
275
276
  output: undefined,
276
277
  outputData: terminalResult,
277
278
  statusTone: "success",
@@ -174,7 +174,10 @@ function buildToolInvocationPart(
174
174
  detail,
175
175
  inputData: fileOperationCardData ? undefined : invocation.parsedArgs ?? invocation.args,
176
176
  text: shouldShowRawResult && rawResult ? rawResult : undefined,
177
- outputData: shouldShowRawResult ? invocation.result : undefined,
177
+ outputData:
178
+ shouldShowRawResult || shouldHideStructuredTerminalJson
179
+ ? invocation.result
180
+ : undefined,
178
181
  callId: invocation.toolCallId || undefined,
179
182
  toolCallId: invocation.toolCallId || undefined,
180
183
  ...(invocation.execution ? { execution: invocation.execution } : {}),
@@ -513,6 +513,26 @@ describe("useNcpAgentRuntime", () => {
513
513
  });
514
514
 
515
515
  describe("useNcpAgentRuntime commands and live stream", () => {
516
+ it("keeps the run active after message completion and settles immediately on the terminal event", async () => {
517
+ const client = new DeferredSendClient();
518
+ const manager = new DefaultNcpAgentConversationStateManager();
519
+ const { result } = renderHook(() => useNcpAgentRuntime({ sessionId: "session-1", client, manager }));
520
+ await act(async () => {
521
+ await client.emit({ type: NcpEventType.RunStarted, payload: { sessionId: "session-1", runId: "run-1" } });
522
+ await client.emit({ type: NcpEventType.MessageCompleted, payload: {
523
+ sessionId: "session-1",
524
+ message: { id: "assistant-1", sessionId: "session-1", role: "assistant", status: "final", timestamp: now, parts: [{ type: "text", text: "Done" }] },
525
+ } });
526
+ });
527
+ await waitFor(() => expect(result.current.visibleMessages[0]?.status).toBe("final"));
528
+ expect(result.current.isRunning).toBe(true);
529
+ await act(async () => {
530
+ await client.emit({ type: NcpEventType.RunFinished, payload: { sessionId: "session-1", runId: "run-1", messageId: "assistant-1" } });
531
+ });
532
+ await waitFor(() => expect(result.current.isRunning).toBe(false));
533
+ expect(result.current.isSending).toBe(false);
534
+ });
535
+
516
536
  beforeEach(() => {
517
537
  vi.clearAllMocks();
518
538
  });
@@ -22,3 +22,4 @@ export {
22
22
  resolveSessionTypeLabel,
23
23
  type ChatSessionTypeOption,
24
24
  } from "./features/session-type/utils/chat-session-type.utils";
25
+ export { ChatVoiceShortcutControl } from './features/conversation/components/chat-voice-shortcut-control';
@@ -0,0 +1,174 @@
1
+ import type { ChatComposerDictationSession } from '@nextclaw/agent-chat-ui';
2
+
3
+ export type VoicePhase = 'idle' | 'ready' | 'starting' | 'recording' | 'stopping' | 'error';
4
+ export type VoiceError = 'unsupported' | 'permission' | 'network' | 'audio-capture' | 'no-speech' | 'interrupted' | 'timeout' | 'failed';
5
+ export type VoiceSnapshot = { phase: VoicePhase; text: string; interim: string; seconds: number; error: VoiceError | null };
6
+ export type SpeechRecognitionLike = {
7
+ lang: string;
8
+ continuous: boolean;
9
+ interimResults: boolean;
10
+ start: () => void;
11
+ stop: () => void;
12
+ abort: () => void;
13
+ onstart: (() => void) | null;
14
+ onresult: ((event: { results: ArrayLike<{ isFinal: boolean; 0: { transcript: string } }> }) => void) | null;
15
+ onend: (() => void) | null;
16
+ onerror: ((event: { error: string }) => void) | null;
17
+ };
18
+ type RecognitionConstructor = new () => SpeechRecognitionLike;
19
+ const createBrowserRecognition = (): SpeechRecognitionLike | null => {
20
+ const host = window as Window & { SpeechRecognition?: RecognitionConstructor; webkitSpeechRecognition?: RecognitionConstructor };
21
+ const Constructor = host.SpeechRecognition ?? host.webkitSpeechRecognition;
22
+ return Constructor ? new Constructor() : null;
23
+ };
24
+ const initialSnapshot = (): VoiceSnapshot => ({ phase: 'idle', text: '', interim: '', seconds: 0, error: null });
25
+ const isActive = (phase: VoicePhase) => ['starting', 'recording', 'stopping'].includes(phase);
26
+
27
+ /** Owns one explicit dictation session; never writes to the chat draft or sends messages. */
28
+ export class ChatVoiceInputManager {
29
+ private snapshot = initialSnapshot();
30
+ private listeners = new Set<() => void>();
31
+ private recognition: SpeechRecognitionLike | null = null;
32
+ private deadline: ReturnType<typeof setTimeout> | undefined;
33
+ private ticker: ReturnType<typeof setInterval> | undefined;
34
+ private context = '';
35
+ private settingsRequested = false;
36
+ private draft: ChatComposerDictationSession | null = null;
37
+
38
+ constructor(private readonly createRecognition = createBrowserRecognition) {}
39
+
40
+ getSnapshot = (): VoiceSnapshot => this.snapshot;
41
+ subscribe = (listener: () => void): (() => void) => {
42
+ this.listeners.add(listener);
43
+ return () => { this.listeners.delete(listener); };
44
+ };
45
+ private update = (patch: Partial<VoiceSnapshot>): void => {
46
+ this.snapshot = { ...this.snapshot, ...patch };
47
+ this.listeners.forEach((listener) => listener());
48
+ };
49
+ open = (context: string): void => {
50
+ if (this.snapshot.phase !== 'idle') return;
51
+ this.context = context;
52
+ this.update({ ...initialSnapshot(), phase: 'ready' });
53
+ };
54
+ openSettings = (): void => {
55
+ this.settingsRequested = true;
56
+ if (this.snapshot.phase === 'recording') { this.finish(); return; }
57
+ if (this.snapshot.phase === 'stopping') return;
58
+ const { context } = this;
59
+ this.acceptDraft();
60
+ this.open(context);
61
+ };
62
+ start = (language: string, context: string, draft: ChatComposerDictationSession | null): void => {
63
+ if (isActive(this.snapshot.phase)) return;
64
+ if (!draft) return;
65
+ this.release();
66
+ this.context = context;
67
+ this.draft = draft;
68
+ this.update({ ...initialSnapshot(), phase: 'starting' });
69
+ try {
70
+ const recognition = this.createRecognition();
71
+ if (!recognition) { this.fail('unsupported'); return; }
72
+ this.recognition = recognition;
73
+ recognition.lang = language === 'zh' ? 'zh-CN' : 'en-US';
74
+ recognition.continuous = true;
75
+ recognition.interimResults = true;
76
+ this.connect(recognition);
77
+ this.setDeadline(15000);
78
+ recognition.start();
79
+ } catch { this.fail('failed'); }
80
+ };
81
+ private connect = (recognition: SpeechRecognitionLike): void => {
82
+ recognition.onstart = () => {
83
+ if (this.recognition !== recognition || this.snapshot.phase !== 'starting') return;
84
+ clearTimeout(this.deadline);
85
+ this.update({ phase: 'recording' });
86
+ const started = Date.now();
87
+ this.ticker = setInterval(() => {
88
+ const seconds = Math.floor((Date.now() - started) / 1000);
89
+ this.update({ seconds });
90
+ if (seconds >= 60) this.finish();
91
+ }, 1000);
92
+ };
93
+ recognition.onresult = (event) => {
94
+ if (this.recognition !== recognition) return;
95
+ const finals: string[] = [];
96
+ const interim: string[] = [];
97
+ for (let i = 0; i < event.results.length; i += 1) {
98
+ const result = event.results[i];
99
+ const text = result[0]?.transcript?.trim();
100
+ if (text) (result.isFinal ? finals : interim).push(text);
101
+ }
102
+ this.update({ text: finals.join(' '), interim: interim.join(' ') });
103
+ this.draft?.update(this.snapshot.text, this.snapshot.interim);
104
+ };
105
+ recognition.onerror = ({ error }) => {
106
+ if (this.recognition !== recognition) return;
107
+ const reason: VoiceError = error === 'not-allowed' || error === 'service-not-allowed'
108
+ ? 'permission'
109
+ : error === 'network' || error === 'audio-capture' || error === 'no-speech' ? error : 'failed';
110
+ this.fail(reason);
111
+ };
112
+ recognition.onend = () => {
113
+ if (this.recognition !== recognition) return;
114
+ const text = this.snapshot.text.trim();
115
+ this.release(false);
116
+ if (!text) { this.fail('no-speech'); return; }
117
+ this.draft?.commit(text);
118
+ this.draft = null;
119
+ const { settingsRequested, context } = this;
120
+ this.cancel();
121
+ if (settingsRequested) this.open(context);
122
+ };
123
+ };
124
+ finish = (): void => {
125
+ if (this.snapshot.phase === 'starting') { this.cancel(); return; }
126
+ if (this.snapshot.phase !== 'recording' || !this.recognition) return;
127
+ clearInterval(this.ticker);
128
+ this.update({ phase: 'stopping' });
129
+ this.setDeadline(8000);
130
+ try { this.recognition.stop(); } catch { this.fail('failed'); }
131
+ };
132
+ interrupt = (): void => {
133
+ if (isActive(this.snapshot.phase)) this.fail('interrupted');
134
+ };
135
+ acceptDraft = (): void => {
136
+ this.draft?.commit();
137
+ this.draft = null;
138
+ this.cancel();
139
+ };
140
+ cancel = (): void => {
141
+ this.settingsRequested = false;
142
+ this.release();
143
+ this.context = '';
144
+ this.draft?.cancel();
145
+ this.draft = null;
146
+ this.update(initialSnapshot());
147
+ };
148
+ private fail = (error: VoiceError): void => {
149
+ this.release();
150
+ if (this.snapshot.text) this.draft?.commit(this.snapshot.text);
151
+ else this.draft?.cancel();
152
+ this.draft = null;
153
+ this.update(this.settingsRequested ? { ...initialSnapshot(), phase: 'ready' } : { phase: 'error', error, interim: '' });
154
+ this.settingsRequested = false;
155
+ };
156
+ private setDeadline = (milliseconds: number): void => {
157
+ clearTimeout(this.deadline);
158
+ this.deadline = setTimeout(() => this.fail('timeout'), milliseconds);
159
+ };
160
+ private release = (abort = true): void => {
161
+ clearTimeout(this.deadline);
162
+ clearInterval(this.ticker);
163
+ const { recognition } = this;
164
+ this.recognition = null;
165
+ if (!recognition) return;
166
+ recognition.onstart = null;
167
+ recognition.onend = null;
168
+ recognition.onerror = null;
169
+ recognition.onresult = null;
170
+ if (abort) {
171
+ try { recognition.abort(); } catch { /* Already released by the browser. */ }
172
+ }
173
+ };
174
+ }
@@ -4,8 +4,10 @@ import { ChatQueryManager } from '@/features/chat/managers/chat-query.manager';
4
4
  import { ChatThreadManager } from '@/features/chat/managers/chat-thread.manager';
5
5
  import type { ChatComposerIntentManager } from '@/features/chat/managers/chat-composer-intent.manager';
6
6
  import type { AppPresenter } from '@/app/presenters/app.presenter';
7
+ import { ChatVoiceInputManager } from '@/features/chat/managers/chat-voice-input.manager';
7
8
 
8
9
  export class ChatPresenter {
10
+ readonly chatVoiceInputManager = new ChatVoiceInputManager();
9
11
  readonly chatUiManager: ChatUiManager;
10
12
  readonly chatSessionListManager: ChatSessionListManager;
11
13
  readonly chatQueryManager: ChatQueryManager;
@@ -0,0 +1,31 @@
1
+ import { create } from 'zustand';
2
+ import { createJSONStorage, persist } from 'zustand/middleware';
3
+
4
+ export type VoiceShortcut = { code: string; ctrl: boolean; meta: boolean; alt: boolean; shift: boolean };
5
+ export function isVoiceShortcut(value: unknown): value is VoiceShortcut {
6
+ if (!value || typeof value !== 'object') return false;
7
+ const key = value as VoiceShortcut;
8
+ const unmodified = key.ctrl === false && key.meta === false && key.alt === false && key.shift === false;
9
+ return typeof key.code === 'string' && (/^Key[A-Z]$/.test(key.code) || /^F(?:[6-9]|10)$/.test(key.code)) &&
10
+ typeof key.ctrl === 'boolean' && typeof key.meta === 'boolean' &&
11
+ typeof key.alt === 'boolean' && typeof key.shift === 'boolean' &&
12
+ (unmodified || (key.alt && key.ctrl !== key.meta));
13
+ }
14
+
15
+ type ShortcutState = { shortcut: VoiceShortcut | null; setShortcut: (shortcut: VoiceShortcut | null) => void };
16
+ export const useChatVoiceShortcutStore = create<ShortcutState>()(persist(
17
+ (set) => ({ shortcut: null, setShortcut: (shortcut) => set({ shortcut }) }),
18
+ {
19
+ name: 'nextclaw.chat.voice-shortcut', version: 1,
20
+ storage: createJSONStorage(() => window.localStorage),
21
+ partialize: (state) => ({ shortcut: state.shortcut }),
22
+ merge: (persisted, current) => {
23
+ const value = (persisted as { shortcut?: unknown } | null)?.shortcut;
24
+ return { ...current, shortcut: isVoiceShortcut(value) ? value : null };
25
+ },
26
+ },
27
+ ));
28
+
29
+ export function voiceShortcutLabel(shortcut: VoiceShortcut): string {
30
+ return [shortcut.ctrl && 'Ctrl', shortcut.meta && '⌘', shortcut.alt && 'Alt/⌥', shortcut.shift && 'Shift', shortcut.code.replace(/^Key/, '')].filter(Boolean).join(' + ');
31
+ }
@@ -1 +1,2 @@
1
1
  export { DesktopAuthorizationDialog } from "./components/desktop-authorization-dialog";
2
+ export { useDesktopCapabilityAvailability } from './hooks/use-desktop-capabilities';
@@ -0,0 +1,15 @@
1
+ import { useViewportLayout } from '@/app/hooks/use-viewport-layout';
2
+ import { ChatVoiceShortcutControl } from '@/features/chat';
3
+ import { SettingsPage } from '@/shared/components/settings/settings-page';
4
+ import { SettingRow, SettingsGroup, SettingsSection } from '@/shared/components/settings/setting-row';
5
+ import { t } from '@/shared/lib/i18n';
6
+
7
+ export function KeyboardShortcutsSettingsPage() {
8
+ const { isMobile } = useViewportLayout();
9
+ return <SettingsPage title={t('keyboardShortcuts')}>
10
+ {isMobile ? <p className='text-sm text-muted-foreground'>{t('keyboardShortcutsDesktopOnly')}</p> :
11
+ <SettingsSection><SettingsGroup>
12
+ <SettingRow title={t('chatInputVoice')} layout='stacked'><ChatVoiceShortcutControl /></SettingRow>
13
+ </SettingsGroup></SettingsSection>}
14
+ </SettingsPage>;
15
+ }
@@ -2,11 +2,12 @@ import { NavLink } from "react-router-dom";
2
2
  import { getSettingsNavItems } from "@/app/configs/app-navigation.config";
3
3
  import { t } from "@/shared/lib/i18n";
4
4
  import { cn } from "@/shared/lib/utils";
5
- import { useDesktopCapabilityAvailability } from "@/features/desktop-capabilities/hooks/use-desktop-capabilities";
5
+ import { useDesktopCapabilityAvailability } from "@/features/desktop-capabilities";
6
6
 
7
7
  export function MobileSettingsShell() {
8
8
  const desktopCapabilityAvailable = useDesktopCapabilityAvailability();
9
9
  const settingsNavItems = getSettingsNavItems(t, {
10
+ includeKeyboardShortcuts: false,
10
11
  includeDesktopCapabilities: desktopCapabilityAvailable,
11
12
  });
12
13
 
@@ -1,4 +1,4 @@
1
- import { QueryClient } from '@tanstack/react-query';
1
+ import { QueryClient, type InfiniteData } from '@tanstack/react-query';
2
2
  import { describe, expect, it } from 'vitest';
3
3
  import {
4
4
  applyNcpSessionRealtimeEvent,
@@ -262,3 +262,111 @@ describe('ncp-session-query-cache', () => {
262
262
  expect(queryClient.getQueryData<NcpSessionsListView>(['ncp-sessions', 200, 'peer-b'])?.sessions).toEqual([]);
263
263
  });
264
264
  });
265
+
266
+ describe('paginated ncp session realtime cache', () => {
267
+ it('updates existing paginated summaries without invalidating the session pages', () => {
268
+ const queryClient = new QueryClient();
269
+ const queryKey = ['ncp-session-pages', 1, null] as const;
270
+ const current = createSessionsList();
271
+ queryClient.setQueryData<InfiniteData<NcpSessionsListView>>(queryKey, {
272
+ pages: [
273
+ { ...current, sessions: [current.sessions[0]!], page: 1, pageSize: 1, hasMore: true },
274
+ { ...current, sessions: [current.sessions[1]!], page: 2, pageSize: 1, hasMore: false }
275
+ ],
276
+ pageParams: [1, 2]
277
+ });
278
+
279
+ applyNcpSessionRealtimeEvent(queryClient, {
280
+ type: 'session.summary.upsert',
281
+ payload: {
282
+ summary: {
283
+ ...current.sessions[1]!,
284
+ updatedAt: '2026-03-29T11:00:00.000Z',
285
+ lastMessageAt: '2026-03-29T11:00:00.000Z',
286
+ messageCount: 3
287
+ }
288
+ }
289
+ });
290
+
291
+ const updated = queryClient.getQueryData<InfiniteData<NcpSessionsListView>>(queryKey);
292
+ expect(updated?.pages.map((page) => page.sessions[0]?.sessionId)).toEqual(['session-2', 'session-1']);
293
+ expect(queryClient.getQueryState(queryKey)?.isInvalidated).toBe(false);
294
+ });
295
+
296
+ it('invalidates paginated session queries when an upsert changes their membership', () => {
297
+ const queryClient = new QueryClient();
298
+ const queryKey = ['ncp-session-pages', 100, null] as const;
299
+ queryClient.setQueryData<InfiniteData<NcpSessionsListView>>(queryKey, {
300
+ pages: [{ ...createSessionsList(), page: 1, pageSize: 100, hasMore: false }],
301
+ pageParams: [1]
302
+ });
303
+
304
+ applyNcpSessionRealtimeEvent(queryClient, {
305
+ type: 'session.summary.upsert',
306
+ payload: {
307
+ summary: {
308
+ sessionId: 'session-3',
309
+ messageCount: 1,
310
+ updatedAt: '2026-03-29T12:00:00.000Z',
311
+ status: 'idle'
312
+ }
313
+ }
314
+ });
315
+
316
+ expect(queryClient.getQueryState(queryKey)?.isInvalidated).toBe(true);
317
+ });
318
+
319
+ it('patches paginated run status without invalidating the session pages', () => {
320
+ const queryClient = new QueryClient();
321
+ const queryKey = ['ncp-session-pages', 100, null] as const;
322
+ queryClient.setQueryData<InfiniteData<NcpSessionsListView>>(queryKey, {
323
+ pages: [{ ...createSessionsList(), page: 1, pageSize: 100, hasMore: false }],
324
+ pageParams: [1]
325
+ });
326
+
327
+ applyNcpSessionRealtimeEvent(queryClient, {
328
+ type: 'session.run-status',
329
+ payload: { sessionKey: 'session-2', status: 'running' }
330
+ });
331
+
332
+ const updated = queryClient.getQueryData<InfiniteData<NcpSessionsListView>>(queryKey);
333
+ expect(updated?.pages[0]?.sessions[1]).toMatchObject({
334
+ sessionId: 'session-2',
335
+ messageCount: 2,
336
+ status: 'running'
337
+ });
338
+ expect(queryClient.getQueryState(queryKey)?.isInvalidated).toBe(false);
339
+ });
340
+
341
+ it('invalidates filtered paginated queries when an upsert may change search membership', () => {
342
+ const queryClient = new QueryClient();
343
+ const queryKey = ['ncp-session-pages', 100, 'journal'] as const;
344
+ queryClient.setQueryData<InfiniteData<NcpSessionsListView>>(queryKey, {
345
+ pages: [{ ...createSessionsList(), page: 1, pageSize: 100, hasMore: false }],
346
+ pageParams: [1]
347
+ });
348
+
349
+ applyNcpSessionRealtimeEvent(queryClient, {
350
+ type: 'session.summary.upsert',
351
+ payload: { summary: { ...createSessionsList().sessions[0]!, metadata: { label: 'Journal' } } }
352
+ });
353
+
354
+ expect(queryClient.getQueryState(queryKey)?.isInvalidated).toBe(true);
355
+ });
356
+
357
+ it('invalidates paginated queries after deletion changes their membership', () => {
358
+ const queryClient = new QueryClient();
359
+ const queryKey = ['ncp-session-pages', 100, null] as const;
360
+ queryClient.setQueryData<InfiniteData<NcpSessionsListView>>(queryKey, {
361
+ pages: [{ ...createSessionsList(), page: 1, pageSize: 100, hasMore: false }],
362
+ pageParams: [1]
363
+ });
364
+
365
+ applyNcpSessionRealtimeEvent(queryClient, {
366
+ type: 'session.summary.delete',
367
+ payload: { sessionKey: 'session-1' }
368
+ });
369
+
370
+ expect(queryClient.getQueryState(queryKey)?.isInvalidated).toBe(true);
371
+ });
372
+ });
@@ -1,4 +1,4 @@
1
- import type { QueryClient } from '@tanstack/react-query';
1
+ import type { InfiniteData, QueryClient } from '@tanstack/react-query';
2
2
  import type { NcpSessionSummaryView, NcpSessionsListView, WsEvent } from '@/shared/lib/api';
3
3
 
4
4
  function readSessionActivityAt(summary: NcpSessionSummaryView): string {
@@ -26,6 +26,73 @@ function queryKeyAcceptsSessionSummary(queryKey: readonly unknown[], summary: Nc
26
26
  return !peerId || summary.peerId === peerId;
27
27
  }
28
28
 
29
+ function updateSessionPages(
30
+ current: InfiniteData<NcpSessionsListView> | undefined,
31
+ update: (sessions: NcpSessionsListView) => NcpSessionsListView | undefined
32
+ ): InfiniteData<NcpSessionsListView> | undefined {
33
+ if (!current) return current;
34
+ const pageLengths = current.pages.map((page) => page.sessions.length);
35
+ const sessions = current.pages.flatMap((page) => page.sessions);
36
+ const updated = update({
37
+ sessions,
38
+ total: current.pages[0]?.total ?? sessions.length
39
+ });
40
+ if (!updated) return current;
41
+ let offset = 0;
42
+ return {
43
+ ...current,
44
+ pages: current.pages.map((page, index) => {
45
+ const nextOffset = offset + (pageLengths[index] ?? 0);
46
+ const nextPage = {
47
+ ...page,
48
+ sessions: updated.sessions.slice(offset, nextOffset)
49
+ };
50
+ offset = nextOffset;
51
+ return nextPage;
52
+ })
53
+ };
54
+ }
55
+
56
+ function updateExistingNcpSessionSummaryPages(
57
+ queryClient: QueryClient | undefined,
58
+ summary: NcpSessionSummaryView
59
+ ): void {
60
+ if (!queryClient) return;
61
+ const entries = queryClient.getQueriesData<InfiniteData<NcpSessionsListView>>({
62
+ queryKey: ['ncp-session-pages']
63
+ });
64
+ for (const [queryKey, current] of entries) {
65
+ const query = typeof queryKey[2] === 'string' ? queryKey[2].trim() : '';
66
+ const containsSession = current?.pages.some((page) =>
67
+ page.sessions.some((session) => session.sessionId === summary.sessionId)
68
+ );
69
+ if (query || !containsSession) {
70
+ void queryClient.invalidateQueries({ queryKey, exact: true });
71
+ continue;
72
+ }
73
+ queryClient.setQueryData<InfiniteData<NcpSessionsListView>>(
74
+ queryKey,
75
+ (value) => updateSessionPages(
76
+ value,
77
+ (sessions) => upsertNcpSessionSummaryList(sessions, summary)
78
+ )
79
+ );
80
+ }
81
+ }
82
+
83
+ function updateNcpSessionRunStatusPages(
84
+ queryClient: QueryClient | undefined,
85
+ payload: { sessionKey: string; status: 'running' | 'idle' }
86
+ ): void {
87
+ queryClient?.setQueriesData<InfiniteData<NcpSessionsListView>>(
88
+ { queryKey: ['ncp-session-pages'] },
89
+ (current) => updateSessionPages(
90
+ current,
91
+ (sessions) => updateNcpSessionRunStatusList(sessions, payload)
92
+ )
93
+ );
94
+ }
95
+
29
96
  export function upsertNcpSessionSummaryList(
30
97
  current: NcpSessionsListView | undefined,
31
98
  summary: NcpSessionSummaryView
@@ -149,13 +216,14 @@ export function applyNcpSessionRealtimeEvent(
149
216
  queryClient: QueryClient | undefined,
150
217
  event: Extract<WsEvent, { type: 'session.run-status' | 'session.summary.upsert' | 'session.summary.delete' }>
151
218
  ): void {
152
- void queryClient?.invalidateQueries({ queryKey: ['ncp-session-pages'] });
153
219
  if (event.type === 'session.run-status') {
154
220
  updateNcpSessionRunStatusInQueryClient(queryClient, event.payload);
221
+ updateNcpSessionRunStatusPages(queryClient, event.payload);
155
222
  return;
156
223
  }
157
224
  if (event.type === 'session.summary.upsert') {
158
225
  upsertNcpSessionSummaryInQueryClient(queryClient, event.payload.summary);
226
+ updateExistingNcpSessionSummaryPages(queryClient, event.payload.summary);
159
227
  void queryClient?.invalidateQueries({
160
228
  queryKey: ['ncp-session-token-usage', event.payload.summary.sessionId],
161
229
  exact: true,
@@ -164,4 +232,5 @@ export function applyNcpSessionRealtimeEvent(
164
232
  }
165
233
 
166
234
  deleteNcpSessionSummaryInQueryClient(queryClient, event.payload.sessionKey);
235
+ void queryClient?.invalidateQueries({ queryKey: ['ncp-session-pages'] });
167
236
  }