@nextclaw/ui 0.24.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/.vite/manifest.json +538 -525
- package/dist/assets/{account-panel-BxMlLXYH.js → account-panel-UvHFa8Kl.js} +1 -1
- package/dist/assets/{api-Utt-xzrT.js → api-Dffeij78.js} +5 -5
- package/dist/assets/{app-data-DYXDL6X2.js → app-data-DzaIMy4D.js} +1 -1
- package/dist/assets/app-presenter-provider-O2tUKKGf.js +109 -0
- package/dist/assets/{appearance-settings-page-BfYznAK5.js → appearance-settings-page-D8cWniw-.js} +1 -1
- package/dist/assets/{book-open-DDW_3XpM.js → book-open-xqEj1OLE.js} +1 -1
- package/dist/assets/{boxes-DwJeuK_h.js → boxes-D6uPnkGo.js} +1 -1
- package/dist/assets/{card-IevY2nm-.js → card-CS8su2Zh.js} +1 -1
- package/dist/assets/{channels-list-page-DitnqbMR.js → channels-list-page-B_fYP1Pn.js} +1 -1
- package/dist/assets/{chevron-down-An7Km-t4.js → chevron-down-BL-yMYcX.js} +1 -1
- package/dist/assets/{circle-alert-CDXqELuo.js → circle-alert-lSa3LRvS.js} +1 -1
- package/dist/assets/{circle-dashed-CapXEpiV.js → circle-dashed-CU3yApvP.js} +1 -1
- package/dist/assets/{client-B0krol7A.js → client-BtGBwLTs.js} +1 -1
- package/dist/assets/{config-split-page-BtnkqWVp.js → config-split-page-6hHiiapl.js} +1 -1
- package/dist/assets/{confirm-dialog-DFt4jpN7.js → confirm-dialog-ClT6iaj8.js} +1 -1
- package/dist/assets/{createLucideIcon-BTHK0Ot9.js → createLucideIcon-BNKf0_kc.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-CCf14WId.js → desktop-capabilities-page-CB5pniTx.js} +1 -1
- package/dist/assets/{desktop-update-config-DVoVgfFi.js → desktop-update-config-OuQV73gg.js} +1 -1
- package/dist/assets/{dialog-BFN_1DBH.js → dialog-Xe73hpQz.js} +1 -1
- package/dist/assets/{dist-Bc9hOVNN.js → dist-BiIcnf9j.js} +1 -1
- package/dist/assets/{dist-CEhIDAnr.js → dist-C5I0v8Qn.js} +1 -1
- package/dist/assets/{dist-hkVPP1th.js → dist-czPa8xFO.js} +1 -1
- package/dist/assets/doc-browser-BFkslEzY.js +1 -0
- package/dist/assets/{doc-browser-BNUxmwD6.js → doc-browser-Cu58uiEg.js} +1 -1
- package/dist/assets/{doc-browser-context-C4yQrKrH.js → doc-browser-context-CekJxJFm.js} +1 -1
- package/dist/assets/{ellipsis-BRI6ROeu.js → ellipsis-C5YXdOUx.js} +1 -1
- package/dist/assets/{es2015-MOCbM0Eu.js → es2015-cuwbYI-l.js} +1 -1
- package/dist/assets/{extensions-DxUBMtHW.js → extensions-B5r-0jCq.js} +1 -1
- package/dist/assets/{eye-cnynUDGt.js → eye-C84inPcV.js} +1 -1
- package/dist/assets/{form-actions-gQ0JEUwB.js → form-actions-AIdXOoCL.js} +1 -1
- package/dist/assets/{i18n-B9yXA8KO.js → i18n-CPjq_hQX.js} +2 -2
- package/dist/assets/{i18n-provider-BYBJua91.js → i18n-provider-BYO9AhFy.js} +1 -1
- package/dist/assets/keyboard-shortcuts-settings-page-1efEkGwq.js +1 -0
- package/dist/assets/loader-circle-K1BDdHR5.js +1 -0
- package/dist/assets/{login-page-CqrHhzaN.js → login-page-DBqkVTVD.js} +1 -1
- package/dist/assets/login-page-DjcRQLQe.js +1 -0
- package/dist/assets/{logo-badge-Cr03YceJ.js → logo-badge-C-5-zim6.js} +1 -1
- package/dist/assets/main-CLwCjK9U.css +1 -0
- package/dist/assets/main-CQ6GUYdX.js +2 -0
- package/dist/assets/main-i18n-catalog.config-QB4DfJRJ.js +1 -0
- package/dist/assets/{mcp-marketplace-page-CifiVgFD.js → mcp-marketplace-page-CWAxBNaS.js} +1 -1
- package/dist/assets/mcp-marketplace-page-Ct090ceA.js +1 -0
- package/dist/assets/{model-config-page-C6jVNbSj.js → model-config-page-DBcuZyRC.js} +1 -1
- package/dist/assets/{navigation-link-kbCLRSr_.js → navigation-link-BAQjgx2F.js} +1 -1
- package/dist/assets/panel-app-i18n-catalog.config-CQFujXqt.js +1 -0
- package/dist/assets/panel-app-maumVhbl.js +1 -0
- package/dist/assets/{panelStandalone-DMBnYyTw.js → panelStandalone-BGA1s7JG.js} +2 -2
- package/dist/assets/plus-LA8-jpxP.js +1 -0
- package/dist/assets/{popover-XVDgjxcl.js → popover-NkrFlWHp.js} +1 -1
- package/dist/assets/{privacy-settings-page-BN27MBVA.js → privacy-settings-page-DVC-aq1v.js} +1 -1
- package/dist/assets/projects-Cq0E3c7N.js +1 -0
- package/dist/assets/{provider-scoped-model-input-BGZMCThn.js → provider-scoped-model-input-BGdZMWWa.js} +1 -1
- package/dist/assets/{providers-config-page-D5aBwbOD.js → providers-config-page-B-IhHl6x.js} +1 -1
- package/dist/assets/{puzzle-D5Dhacmb.js → puzzle-D6gulz7u.js} +1 -1
- package/dist/assets/{pwa-ClQK56Qe.js → pwa-C3kR9mdy.js} +1 -1
- package/dist/assets/{refresh-cw-Bw5BY7vp.js → refresh-cw-h9Xq3LY3.js} +1 -1
- package/dist/assets/remote-Cnawibbg.js +1 -0
- package/dist/assets/{rotate-cw-BksCLAeE.js → rotate-cw-B6xm41Wn.js} +1 -1
- package/dist/assets/runtime-config-page-LmrB2q65.js +1 -0
- package/dist/assets/{save-CVi8VWzb.js → save-fff_WpY2.js} +1 -1
- package/dist/assets/{search-DN-f7IIC.js → search-CUe41wjm.js} +1 -1
- package/dist/assets/{search-config-page-BmuZTVxs.js → search-config-page-BIXmv5FY.js} +1 -1
- package/dist/assets/{secrets-config-page-NPHpg9sb.js → secrets-config-page-DU3fadrq.js} +1 -1
- package/dist/assets/security-config-9s0i0I4N.js +1 -0
- package/dist/assets/{select-CmDTqk2C.js → select-CG5YWnwe.js} +1 -1
- package/dist/assets/{service-apps-panel-BYGJ9_pO.js → service-apps-panel-Bshf3Wmq.js} +1 -1
- package/dist/assets/{settings-2-B62YtZon.js → settings-2-CE3ELQyk.js} +1 -1
- package/dist/assets/skeleton-BL9h32W7.js +1 -0
- package/dist/assets/{tabs-Bk_mG7ql.js → tabs-BuE0qKMh.js} +1 -1
- package/dist/assets/{tag-chip-DgqKlI5D.js → tag-chip-gAH1IOsZ.js} +1 -1
- package/dist/assets/{theme-provider-CeBY2pyu.js → theme-provider-DE7m9jvf.js} +1 -1
- package/dist/assets/{tooltip-CgGbBdYf.js → tooltip-DYcuWhuP.js} +1 -1
- package/dist/assets/{trash-2-CVOev8yc.js → trash-2-BPPYt2NV.js} +1 -1
- package/dist/assets/{use-config-b2DN67gs.js → use-config-Cyiw06-j.js} +1 -1
- package/dist/assets/{use-desktop-capabilities-B3tLU1yl.js → use-desktop-capabilities-OKG96v4y.js} +1 -1
- package/dist/assets/{use-panel-app-scroll-restoration-BekzoX6z.js → use-panel-app-scroll-restoration-DsV1IJx-.js} +1 -1
- package/dist/assets/{use-viewport-layout-CUA3jQsA.js → use-viewport-layout-BS18drMT.js} +1 -1
- package/dist/assets/{utils-UBHTdG5Q.js → utils-CBZPzC_b.js} +1 -1
- package/dist/assets/x-DdXOqLnX.js +1 -0
- package/dist/index.html +50 -50
- package/dist/panel-standalone.html +15 -15
- package/package.json +7 -7
- 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-timeline.utils.test.ts +10 -0
- 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/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +90 -6
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +64 -2
- 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/panel-apps/components/__tests__/panel-app-open-standalone-menu-item.test.tsx +3 -2
- package/src/features/panel-apps/components/panel-app-open-standalone-menu-item.tsx +4 -1
- package/src/features/projects/AGENTS.md +1 -2
- package/src/features/projects/components/project-agreement.tsx +36 -77
- package/src/features/projects/components/project-artifacts.test.tsx +90 -62
- package/src/features/projects/components/project-artifacts.tsx +81 -160
- package/src/features/projects/components/project-materials.test.tsx +80 -0
- package/src/features/projects/components/project-overview.test.tsx +50 -40
- package/src/features/projects/components/project-overview.tsx +5 -4
- package/src/features/projects/components/project-skills.tsx +24 -14
- package/src/features/projects/components/work/project-work-item-drawer.test.tsx +3 -2
- package/src/features/projects/components/work/project-work-items.test.tsx +13 -1
- package/src/features/projects/components/work/project-work-items.tsx +7 -7
- package/src/features/projects/hooks/use-project-materials.test.tsx +65 -0
- package/src/features/projects/hooks/use-project-materials.ts +24 -0
- package/src/features/projects/hooks/use-project-work.test.tsx +57 -1
- package/src/features/projects/hooks/use-project-work.ts +11 -5
- package/src/features/projects/pages/project-home-page.tsx +51 -127
- package/src/features/projects/pages/projects-page.test.tsx +68 -75
- package/src/features/projects/presenters/project-home.presenter.test.ts +1 -64
- package/src/features/projects/presenters/project-home.presenter.ts +17 -54
- package/src/features/projects/utils/project-artifact-view.utils.test.ts +0 -46
- package/src/features/projects/utils/project-artifact-view.utils.ts +0 -47
- 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/en-US/projects.json +8 -59
- 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/i18n/locales/zh-CN/projects.json +8 -59
- 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-CLsFiXvV.js +0 -109
- package/dist/assets/doc-browser-w4eGZXLu.js +0 -1
- package/dist/assets/loader-circle-rWr5Yo0E.js +0 -1
- package/dist/assets/login-page-CfMSkOsg.js +0 -1
- package/dist/assets/main-8cVkMfgE.css +0 -1
- package/dist/assets/main-Bys58GxY.js +0 -2
- package/dist/assets/main-i18n-catalog.config-BSq9yiUQ.js +0 -1
- package/dist/assets/mcp-marketplace-page-DX5VE3mU.js +0 -1
- package/dist/assets/panel-app-BUUle0gP.js +0 -1
- package/dist/assets/panel-app-i18n-catalog.config-C2St67SP.js +0 -1
- package/dist/assets/plus-BHnKwuPq.js +0 -1
- package/dist/assets/projects-DV3UuJMS.js +0 -1
- package/dist/assets/remote-Bl005fzu.js +0 -1
- package/dist/assets/runtime-config-page-CmktQcMW.js +0 -1
- package/dist/assets/security-config-Djptuyf2.js +0 -1
- package/dist/assets/skeleton-Eh64l4T2.js +0 -1
- package/dist/assets/x-C0iaOsRW.js +0 -1
- package/src/features/projects/components/project-requests.tsx +0 -57
- package/src/features/projects/components/project-runs.tsx +0 -60
- package/src/features/projects/hooks/use-project-observation.test.tsx +0 -93
- package/src/features/projects/hooks/use-project-observation.ts +0 -115
- package/src/features/projects/utils/project-request-response.utils.test.ts +0 -44
- package/src/features/projects/utils/project-request-response.utils.ts +0 -39
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./createLucideIcon-BNKf0_kc.js";var t=e(`X`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]);export{t};
|
package/dist/index.html
CHANGED
|
@@ -258,62 +258,62 @@
|
|
|
258
258
|
})();
|
|
259
259
|
</script>
|
|
260
260
|
<title>NextClaw</title>
|
|
261
|
-
<script type="module" crossorigin src="/assets/main-
|
|
261
|
+
<script type="module" crossorigin src="/assets/main-CQ6GUYdX.js"></script>
|
|
262
262
|
<link rel="modulepreload" crossorigin href="/assets/chunk-DF37yw5_.js">
|
|
263
|
-
<link rel="modulepreload" crossorigin href="/assets/utils-
|
|
264
|
-
<link rel="modulepreload" crossorigin href="/assets/client-
|
|
263
|
+
<link rel="modulepreload" crossorigin href="/assets/utils-CBZPzC_b.js">
|
|
264
|
+
<link rel="modulepreload" crossorigin href="/assets/client-BtGBwLTs.js">
|
|
265
265
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-ca-nBW7U.js">
|
|
266
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
267
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
268
|
-
<link rel="modulepreload" crossorigin href="/assets/es2015-
|
|
269
|
-
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-
|
|
270
|
-
<link rel="modulepreload" crossorigin href="/assets/chevron-down-
|
|
271
|
-
<link rel="modulepreload" crossorigin href="/assets/select-
|
|
272
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
273
|
-
<link rel="modulepreload" crossorigin href="/assets/x-
|
|
274
|
-
<link rel="modulepreload" crossorigin href="/assets/dialog-
|
|
275
|
-
<link rel="modulepreload" crossorigin href="/assets/popover-
|
|
276
|
-
<link rel="modulepreload" crossorigin href="/assets/tooltip-
|
|
277
|
-
<link rel="modulepreload" crossorigin href="/assets/i18n-
|
|
278
|
-
<link rel="modulepreload" crossorigin href="/assets/api-
|
|
279
|
-
<link rel="modulepreload" crossorigin href="/assets/search-
|
|
280
|
-
<link rel="modulepreload" crossorigin href="/assets/book-open-
|
|
281
|
-
<link rel="modulepreload" crossorigin href="/assets/boxes-
|
|
266
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-BiIcnf9j.js">
|
|
267
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-C5I0v8Qn.js">
|
|
268
|
+
<link rel="modulepreload" crossorigin href="/assets/es2015-cuwbYI-l.js">
|
|
269
|
+
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BNKf0_kc.js">
|
|
270
|
+
<link rel="modulepreload" crossorigin href="/assets/chevron-down-BL-yMYcX.js">
|
|
271
|
+
<link rel="modulepreload" crossorigin href="/assets/select-CG5YWnwe.js">
|
|
272
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-czPa8xFO.js">
|
|
273
|
+
<link rel="modulepreload" crossorigin href="/assets/x-DdXOqLnX.js">
|
|
274
|
+
<link rel="modulepreload" crossorigin href="/assets/dialog-Xe73hpQz.js">
|
|
275
|
+
<link rel="modulepreload" crossorigin href="/assets/popover-NkrFlWHp.js">
|
|
276
|
+
<link rel="modulepreload" crossorigin href="/assets/tooltip-DYcuWhuP.js">
|
|
277
|
+
<link rel="modulepreload" crossorigin href="/assets/i18n-CPjq_hQX.js">
|
|
278
|
+
<link rel="modulepreload" crossorigin href="/assets/api-Dffeij78.js">
|
|
279
|
+
<link rel="modulepreload" crossorigin href="/assets/search-CUe41wjm.js">
|
|
280
|
+
<link rel="modulepreload" crossorigin href="/assets/book-open-xqEj1OLE.js">
|
|
281
|
+
<link rel="modulepreload" crossorigin href="/assets/boxes-D6uPnkGo.js">
|
|
282
282
|
<link rel="modulepreload" crossorigin href="/assets/host-capabilities-B107nwS3.js">
|
|
283
|
-
<link rel="modulepreload" crossorigin href="/assets/navigation-link-
|
|
284
|
-
<link rel="modulepreload" crossorigin href="/assets/plus-
|
|
285
|
-
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-
|
|
283
|
+
<link rel="modulepreload" crossorigin href="/assets/navigation-link-BAQjgx2F.js">
|
|
284
|
+
<link rel="modulepreload" crossorigin href="/assets/plus-LA8-jpxP.js">
|
|
285
|
+
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-h9Xq3LY3.js">
|
|
286
286
|
<link rel="modulepreload" crossorigin href="/assets/middleware-0o3JF3tS.js">
|
|
287
|
-
<link rel="modulepreload" crossorigin href="/assets/doc-browser-context-
|
|
288
|
-
<link rel="modulepreload" crossorigin href="/assets/use-panel-app-scroll-restoration-
|
|
289
|
-
<link rel="modulepreload" crossorigin href="/assets/doc-browser-
|
|
290
|
-
<link rel="modulepreload" crossorigin href="/assets/circle-alert-
|
|
291
|
-
<link rel="modulepreload" crossorigin href="/assets/loader-circle-
|
|
292
|
-
<link rel="modulepreload" crossorigin href="/assets/confirm-dialog-
|
|
293
|
-
<link rel="modulepreload" crossorigin href="/assets/i18n-provider-
|
|
294
|
-
<link rel="modulepreload" crossorigin href="/assets/tag-chip-
|
|
295
|
-
<link rel="modulepreload" crossorigin href="/assets/skeleton-
|
|
296
|
-
<link rel="modulepreload" crossorigin href="/assets/mcp-marketplace-page-
|
|
297
|
-
<link rel="modulepreload" crossorigin href="/assets/puzzle-
|
|
298
|
-
<link rel="modulepreload" crossorigin href="/assets/trash-2-
|
|
299
|
-
<link rel="modulepreload" crossorigin href="/assets/app-data-
|
|
300
|
-
<link rel="modulepreload" crossorigin href="/assets/ellipsis-
|
|
301
|
-
<link rel="modulepreload" crossorigin href="/assets/eye-
|
|
302
|
-
<link rel="modulepreload" crossorigin href="/assets/tabs-
|
|
303
|
-
<link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-
|
|
304
|
-
<link rel="modulepreload" crossorigin href="/assets/theme-provider-
|
|
305
|
-
<link rel="modulepreload" crossorigin href="/assets/use-desktop-capabilities-
|
|
306
|
-
<link rel="modulepreload" crossorigin href="/assets/logo-badge-
|
|
307
|
-
<link rel="modulepreload" crossorigin href="/assets/card-
|
|
308
|
-
<link rel="modulepreload" crossorigin href="/assets/use-config-
|
|
287
|
+
<link rel="modulepreload" crossorigin href="/assets/doc-browser-context-CekJxJFm.js">
|
|
288
|
+
<link rel="modulepreload" crossorigin href="/assets/use-panel-app-scroll-restoration-DsV1IJx-.js">
|
|
289
|
+
<link rel="modulepreload" crossorigin href="/assets/doc-browser-Cu58uiEg.js">
|
|
290
|
+
<link rel="modulepreload" crossorigin href="/assets/circle-alert-lSa3LRvS.js">
|
|
291
|
+
<link rel="modulepreload" crossorigin href="/assets/loader-circle-K1BDdHR5.js">
|
|
292
|
+
<link rel="modulepreload" crossorigin href="/assets/confirm-dialog-ClT6iaj8.js">
|
|
293
|
+
<link rel="modulepreload" crossorigin href="/assets/i18n-provider-BYO9AhFy.js">
|
|
294
|
+
<link rel="modulepreload" crossorigin href="/assets/tag-chip-gAH1IOsZ.js">
|
|
295
|
+
<link rel="modulepreload" crossorigin href="/assets/skeleton-BL9h32W7.js">
|
|
296
|
+
<link rel="modulepreload" crossorigin href="/assets/mcp-marketplace-page-CWAxBNaS.js">
|
|
297
|
+
<link rel="modulepreload" crossorigin href="/assets/puzzle-D6gulz7u.js">
|
|
298
|
+
<link rel="modulepreload" crossorigin href="/assets/trash-2-BPPYt2NV.js">
|
|
299
|
+
<link rel="modulepreload" crossorigin href="/assets/app-data-DzaIMy4D.js">
|
|
300
|
+
<link rel="modulepreload" crossorigin href="/assets/ellipsis-C5YXdOUx.js">
|
|
301
|
+
<link rel="modulepreload" crossorigin href="/assets/eye-C84inPcV.js">
|
|
302
|
+
<link rel="modulepreload" crossorigin href="/assets/tabs-BuE0qKMh.js">
|
|
303
|
+
<link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-BS18drMT.js">
|
|
304
|
+
<link rel="modulepreload" crossorigin href="/assets/theme-provider-DE7m9jvf.js">
|
|
305
|
+
<link rel="modulepreload" crossorigin href="/assets/use-desktop-capabilities-OKG96v4y.js">
|
|
306
|
+
<link rel="modulepreload" crossorigin href="/assets/logo-badge-C-5-zim6.js">
|
|
307
|
+
<link rel="modulepreload" crossorigin href="/assets/card-CS8su2Zh.js">
|
|
308
|
+
<link rel="modulepreload" crossorigin href="/assets/use-config-Cyiw06-j.js">
|
|
309
309
|
<link rel="modulepreload" crossorigin href="/assets/provider-models-5PGHb6mP.js">
|
|
310
|
-
<link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-
|
|
311
|
-
<link rel="modulepreload" crossorigin href="/assets/provider-scoped-model-input-
|
|
312
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-2-
|
|
313
|
-
<link rel="modulepreload" crossorigin href="/assets/pwa-
|
|
314
|
-
<link rel="modulepreload" crossorigin href="/assets/login-page-
|
|
310
|
+
<link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-O2tUKKGf.js">
|
|
311
|
+
<link rel="modulepreload" crossorigin href="/assets/provider-scoped-model-input-BGdZMWWa.js">
|
|
312
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-2-CE3ELQyk.js">
|
|
313
|
+
<link rel="modulepreload" crossorigin href="/assets/pwa-C3kR9mdy.js">
|
|
314
|
+
<link rel="modulepreload" crossorigin href="/assets/login-page-DBqkVTVD.js">
|
|
315
315
|
<link rel="stylesheet" crossorigin href="/assets/app-presenter-provider-D3BuuOyZ.css">
|
|
316
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
316
|
+
<link rel="stylesheet" crossorigin href="/assets/main-CLwCjK9U.css">
|
|
317
317
|
</head>
|
|
318
318
|
|
|
319
319
|
<body>
|
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#ffffff" />
|
|
7
7
|
<title>Panel App · NextClaw</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/panelStandalone-
|
|
8
|
+
<script type="module" crossorigin src="/assets/panelStandalone-BGA1s7JG.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-DF37yw5_.js">
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/utils-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/client-
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/utils-CBZPzC_b.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/client-BtGBwLTs.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-ca-nBW7U.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/i18n-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/api-
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/es2015-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/dist-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/x-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/dialog-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/assets/boxes-
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/use-panel-app-scroll-restoration-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/theme-provider-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/i18n-provider-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/i18n-CPjq_hQX.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/api-Dffeij78.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-BiIcnf9j.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/es2015-cuwbYI-l.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/dist-czPa8xFO.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BNKf0_kc.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/x-DdXOqLnX.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/dialog-Xe73hpQz.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="/assets/boxes-D6uPnkGo.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/use-panel-app-scroll-restoration-DsV1IJx-.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/theme-provider-DE7m9jvf.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/i18n-provider-BYO9AhFy.js">
|
|
25
25
|
<link rel="stylesheet" crossorigin href="/assets/panelStandalone-Y91OJkgV.css">
|
|
26
26
|
</head>
|
|
27
27
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
|
35
35
|
"zod": "^3.23.8",
|
|
36
36
|
"zustand": "^5.0.2",
|
|
37
|
-
"@nextclaw/agent-chat": "0.3.14",
|
|
38
|
-
"@nextclaw/agent-chat-ui": "0.7.2",
|
|
39
|
-
"@nextclaw/client-sdk": "0.11.2",
|
|
40
37
|
"@nextclaw/ncp": "0.10.0",
|
|
38
|
+
"@nextclaw/agent-chat": "0.3.14",
|
|
41
39
|
"@nextclaw/ncp-http-agent-client": "0.4.22",
|
|
42
|
-
"@nextclaw/
|
|
43
|
-
"@nextclaw/ncp-
|
|
44
|
-
"@nextclaw/
|
|
40
|
+
"@nextclaw/agent-chat-ui": "0.8.0",
|
|
41
|
+
"@nextclaw/ncp-react": "0.5.27",
|
|
42
|
+
"@nextclaw/client-sdk": "0.12.1",
|
|
43
|
+
"@nextclaw/ncp-toolkit": "0.6.24",
|
|
44
|
+
"@nextclaw/shared": "0.5.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@testing-library/react": "^16.3.0",
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Download,
|
|
9
9
|
Inbox,
|
|
10
10
|
KeyRound,
|
|
11
|
+
Keyboard,
|
|
11
12
|
MessageCircle,
|
|
12
13
|
MessageSquare,
|
|
13
14
|
MonitorCog,
|
|
@@ -35,6 +36,7 @@ export type AppNavigationSection = {
|
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
type SettingsNavigationOptions = {
|
|
39
|
+
includeKeyboardShortcuts?: boolean;
|
|
38
40
|
includeDesktopCapabilities?: boolean;
|
|
39
41
|
};
|
|
40
42
|
|
|
@@ -165,6 +167,11 @@ export function getSettingsNavItems(
|
|
|
165
167
|
label: translate("appearance"),
|
|
166
168
|
icon: Palette,
|
|
167
169
|
},
|
|
170
|
+
{
|
|
171
|
+
target: "/keyboard-shortcuts",
|
|
172
|
+
label: translate("keyboardShortcuts"),
|
|
173
|
+
icon: Keyboard,
|
|
174
|
+
},
|
|
168
175
|
{
|
|
169
176
|
target: "/security",
|
|
170
177
|
label: translate("security"),
|
|
@@ -211,9 +218,9 @@ export function getSettingsNavItems(
|
|
|
211
218
|
icon: Wrench,
|
|
212
219
|
},
|
|
213
220
|
];
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
return items.filter((item) =>
|
|
222
|
+
(options.includeDesktopCapabilities !== false || item.target !== "/desktop-capabilities") &&
|
|
223
|
+
(options.includeKeyboardShortcuts !== false || item.target !== "/keyboard-shortcuts"));
|
|
217
224
|
}
|
|
218
225
|
|
|
219
226
|
export function getSettingsNavSections(
|
package/src/app/index.tsx
CHANGED
|
@@ -42,6 +42,9 @@ const AppearanceSettingsPage = lazy(async () => ({
|
|
|
42
42
|
default: (await import("@/features/settings/pages/appearance-settings-page"))
|
|
43
43
|
.AppearanceSettingsPage,
|
|
44
44
|
}));
|
|
45
|
+
const KeyboardShortcutsSettingsPage = lazy(async () => ({
|
|
46
|
+
default: (await import("@/features/settings/pages/keyboard-shortcuts-settings-page")).KeyboardShortcutsSettingsPage,
|
|
47
|
+
}));
|
|
45
48
|
const SearchConfigPage = lazy(async () => ({
|
|
46
49
|
default: (await import("@/features/settings/pages/search-config-page"))
|
|
47
50
|
.SearchConfigPage,
|
|
@@ -157,6 +160,10 @@ const protectedRouteDefinitions: ProtectedRouteDefinition[] = [
|
|
|
157
160
|
path: "/appearance",
|
|
158
161
|
element: createLazyElement(<AppearanceSettingsPage />),
|
|
159
162
|
},
|
|
163
|
+
{
|
|
164
|
+
path: "/keyboard-shortcuts",
|
|
165
|
+
element: createLazyElement(<KeyboardShortcutsSettingsPage />),
|
|
166
|
+
},
|
|
160
167
|
{
|
|
161
168
|
path: "/model",
|
|
162
169
|
element: createLazyElement(<ModelConfigPage />),
|
|
@@ -6,12 +6,14 @@ import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
|
|
|
6
6
|
import type { ChatQueryManager } from '@/features/chat/managers/chat-query.manager';
|
|
7
7
|
import type { ChatThreadManager } from '@/features/chat/managers/chat-thread.manager';
|
|
8
8
|
import type { ChatComposerIntentManager } from '@/features/chat/managers/chat-composer-intent.manager';
|
|
9
|
+
import type { ChatVoiceInputManager } from '@/features/chat/managers/chat-voice-input.manager';
|
|
9
10
|
|
|
10
11
|
type PublicManager<T extends object> = Pick<T, keyof T>;
|
|
11
12
|
|
|
12
13
|
export type ChatThreadManagerLike = PublicManager<ChatThreadManager>;
|
|
13
14
|
|
|
14
15
|
export type ChatPresenterLike = {
|
|
16
|
+
chatVoiceInputManager: ChatVoiceInputManager;
|
|
15
17
|
chatUiManager: ChatUiManager;
|
|
16
18
|
chatQueryManager: ChatQueryManager;
|
|
17
19
|
chatSessionListManager: ChatSessionListManager;
|
|
@@ -309,6 +309,18 @@ describe("SessionConversationArea input boundary", () => {
|
|
|
309
309
|
expect(params.agent.isSending).toBe(false);
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
it("does not keep a settled run active because the session list cache is stale", () => {
|
|
313
|
+
mocks.inputQuery.selectedSession = { status: "running" };
|
|
314
|
+
mocks.agent.isRunning = false;
|
|
315
|
+
mocks.agent.isSending = false;
|
|
316
|
+
renderArea("session-1");
|
|
317
|
+
const params = mocks.controllerParamsSpy.mock.calls.at(-1)?.[0] as {
|
|
318
|
+
agent: { isRunning: boolean; isSending: boolean };
|
|
319
|
+
};
|
|
320
|
+
expect(params.agent.isRunning).toBe(false);
|
|
321
|
+
expect(params.agent.isSending).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
312
324
|
it("disables message recovery actions until the NCP agent is ready", () => {
|
|
313
325
|
mocks.agent.isRunning = false;
|
|
314
326
|
mocks.agent.isSending = false;
|
|
@@ -31,6 +31,8 @@ import { useSessionConversationInputState } from '@/features/chat/features/conve
|
|
|
31
31
|
import { useChatMessageLayoutStore } from '@/features/chat/stores/chat-message-layout.store';
|
|
32
32
|
import { useChatComposerDraftStore } from '@/features/chat/stores/chat-composer-draft.store';
|
|
33
33
|
import { ChatComposerIntentManager } from '@/features/chat/managers/chat-composer-intent.manager';
|
|
34
|
+
import { verifyVoiceDraftLifecycle } from './session-voice-input.test-utils';
|
|
35
|
+
import { ChatVoiceInputManager } from '@/features/chat/managers/chat-voice-input.manager';
|
|
34
36
|
import type { ThinkingLevel } from '@/shared/lib/api';
|
|
35
37
|
|
|
36
38
|
const uploadNcpAssetsMock = vi.hoisted(() => vi.fn());
|
|
@@ -221,6 +223,7 @@ type StreamingInputControl = {
|
|
|
221
223
|
|
|
222
224
|
const chatComposerIntentManager = new ChatComposerIntentManager();
|
|
223
225
|
const presenter = {
|
|
226
|
+
chatVoiceInputManager: new ChatVoiceInputManager(),
|
|
224
227
|
chatComposerIntentManager,
|
|
225
228
|
chatThreadManager: {
|
|
226
229
|
openSideChatDraft: vi.fn(),
|
|
@@ -270,6 +273,13 @@ function createStreamingInputSnapshot(
|
|
|
270
273
|
};
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
it('dictation uses the latest draft and never sends; cancel discards late results', async () => {
|
|
277
|
+
await verifyVoiceDraftLifecycle((voiceManager, send) => renderInput(
|
|
278
|
+
<StreamingSessionConversationInputHarness voiceManager={voiceManager} controlRef={{ current: null }}
|
|
279
|
+
initialText='Original' controllerOverride={{ ...controller, send, sendDisabled: false }} />,
|
|
280
|
+
));
|
|
281
|
+
});
|
|
282
|
+
|
|
273
283
|
function setImeDomText(textbox: HTMLElement, text: string): HTMLParagraphElement {
|
|
274
284
|
const paragraph = textbox.querySelector('p');
|
|
275
285
|
if (!paragraph) {
|
|
@@ -307,12 +317,14 @@ async function insertText(textbox: HTMLElement, text: string): Promise<void> {
|
|
|
307
317
|
}
|
|
308
318
|
|
|
309
319
|
function StreamingSessionConversationInputHarness({
|
|
320
|
+
voiceManager = presenter.chatVoiceInputManager,
|
|
310
321
|
controllerOverride = controller,
|
|
311
322
|
controlRef,
|
|
312
323
|
initialText = '',
|
|
313
324
|
sendError = null,
|
|
314
325
|
}: {
|
|
315
326
|
controllerOverride?: SessionConversationInputController;
|
|
327
|
+
voiceManager?: ChatVoiceInputManager;
|
|
316
328
|
controlRef: MutableRefObject<StreamingInputControl | null>;
|
|
317
329
|
initialText?: string;
|
|
318
330
|
sendError?: string | null;
|
|
@@ -389,7 +401,7 @@ function StreamingSessionConversationInputHarness({
|
|
|
389
401
|
|
|
390
402
|
return (
|
|
391
403
|
<I18nProvider>
|
|
392
|
-
<ChatPresenterProvider presenter={presenter}>
|
|
404
|
+
<ChatPresenterProvider presenter={{ ...presenter, chatVoiceInputManager: voiceManager }}>
|
|
393
405
|
<div data-testid="stream-chunk">{streamChunk}</div>
|
|
394
406
|
<SessionConversationInput
|
|
395
407
|
contextWindow={null}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { expect, vi } from 'vitest';
|
|
3
|
+
import { ChatVoiceInputManager, type SpeechRecognitionLike } from '@/features/chat/managers/chat-voice-input.manager';
|
|
4
|
+
|
|
5
|
+
/** Assembled voice/draft lifecycle check, shared with the real streaming input harness. */
|
|
6
|
+
export async function verifyVoiceDraftLifecycle(
|
|
7
|
+
renderHarness: (manager: ChatVoiceInputManager, send: () => void) => { unmount: () => void },
|
|
8
|
+
) {
|
|
9
|
+
const recognition: SpeechRecognitionLike = {
|
|
10
|
+
lang: '', continuous: false, interimResults: false,
|
|
11
|
+
start: vi.fn(), stop: vi.fn(), abort: vi.fn(),
|
|
12
|
+
onstart: null, onend: null, onresult: null, onerror: null,
|
|
13
|
+
};
|
|
14
|
+
const manager = new ChatVoiceInputManager(() => recognition);
|
|
15
|
+
const send = vi.fn();
|
|
16
|
+
const rendered = renderHarness(manager, send);
|
|
17
|
+
try {
|
|
18
|
+
fireEvent.click(screen.getByRole('button', { name: /^(语音输入|Voice input)$/ }));
|
|
19
|
+
act(() => { recognition.onstart?.(); });
|
|
20
|
+
const textbox = screen.getByRole('textbox');
|
|
21
|
+
const before = textbox.textContent;
|
|
22
|
+
act(() => { recognition.onresult?.({ results: [{ isFinal: false, 0: { transcript: 'speaking' } }] }); });
|
|
23
|
+
await waitFor(() => expect(textbox.textContent).toContain('speaking'));
|
|
24
|
+
expect(textbox.querySelector('[style*="underline"]')?.textContent).toBe('speaking');
|
|
25
|
+
act(() => { recognition.onresult?.({ results: [{ isFinal: false, 0: { transcript: 'speak' } }] }); });
|
|
26
|
+
await waitFor(() => expect(textbox.textContent).not.toContain('speaking'));
|
|
27
|
+
act(() => { recognition.onresult?.({ results: [{ isFinal: true, 0: { transcript: 'spoken' } }] }); });
|
|
28
|
+
fireEvent.click(screen.getAllByRole('button', { name: /结束录音|Finish recording/ })[0]);
|
|
29
|
+
act(() => { recognition.onend?.(); });
|
|
30
|
+
await waitFor(() => expect(textbox.textContent).toContain('spoken'));
|
|
31
|
+
expect(textbox.textContent).toContain(before);
|
|
32
|
+
expect(textbox.querySelector('[style*="underline"]')).toBeNull();
|
|
33
|
+
expect(send).not.toHaveBeenCalled();
|
|
34
|
+
fireEvent.click(screen.getByRole('button', { name: /^(语音输入|Voice input)$/ }));
|
|
35
|
+
const lateResult = recognition.onresult;
|
|
36
|
+
const lateEnd = recognition.onend;
|
|
37
|
+
fireEvent.click(screen.getAllByRole('button', { name: /^(取消|Cancel)$/ })[0]);
|
|
38
|
+
act(() => {
|
|
39
|
+
lateResult?.({ results: [{ isFinal: true, 0: { transcript: 'discard' } }] }); lateEnd?.();
|
|
40
|
+
});
|
|
41
|
+
expect(textbox.textContent).not.toContain('discard');
|
|
42
|
+
fireEvent.click(screen.getByRole('button', { name: /^(语音输入|Voice input)$/ }));
|
|
43
|
+
act(() => { recognition.onstart?.(); });
|
|
44
|
+
fireEvent.click(screen.getByRole('button', { name: /^(语音快捷键|Voice shortcut)$/ }));
|
|
45
|
+
act(() => { recognition.onerror?.({ error: 'no-speech' }); });
|
|
46
|
+
await waitFor(() => expect(screen.getByRole('button', { name: /绑定按键|Bind a key/ })).toBeTruthy());
|
|
47
|
+
expect(screen.queryByRole('alert')).toBeNull();
|
|
48
|
+
expect(screen.queryByRole('button', { name: /^(重试|Retry)$/ })).toBeNull();
|
|
49
|
+
} finally {
|
|
50
|
+
rendered.unmount();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Mic, Square, X, LoaderCircle, Settings } from 'lucide-react';
|
|
2
|
+
import { Button } from '@/shared/components/ui/button';
|
|
3
|
+
import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
|
|
4
|
+
import { t } from '@/shared/lib/i18n';
|
|
5
|
+
import type { ChatVoiceInputManager, VoiceError, VoiceSnapshot } from '@/features/chat/managers/chat-voice-input.manager';
|
|
6
|
+
import { ChatVoiceShortcutControl } from './chat-voice-shortcut-control';
|
|
7
|
+
|
|
8
|
+
const ERROR_KEYS: Record<VoiceError, string> = {
|
|
9
|
+
unsupported: 'chatInputVoiceUnsupported', permission: 'chatInputVoicePermissionDenied',
|
|
10
|
+
network: 'chatInputVoiceNetwork', 'audio-capture': 'chatInputVoiceDevice',
|
|
11
|
+
'no-speech': 'chatInputVoiceNoSpeech', interrupted: 'chatInputVoiceInterrupted',
|
|
12
|
+
timeout: 'chatInputVoiceTimeout', failed: 'chatInputVoiceFailed',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function ChatVoiceInputPanel({ manager, snapshot, onStart, desktop }: {
|
|
16
|
+
manager: ChatVoiceInputManager;
|
|
17
|
+
snapshot: VoiceSnapshot;
|
|
18
|
+
onStart: () => void;
|
|
19
|
+
desktop: boolean;
|
|
20
|
+
}) {
|
|
21
|
+
const { phase, error, seconds } = snapshot;
|
|
22
|
+
if (phase === 'ready') return desktop ? <VoiceShortcutPanel onClose={manager.cancel} /> : null;
|
|
23
|
+
const busy = phase === 'starting' || phase === 'stopping';
|
|
24
|
+
const status = {
|
|
25
|
+
idle: 'chatInputVoice', ready: 'chatInputVoiceShortcut', starting: 'chatInputVoiceStarting',
|
|
26
|
+
recording: 'chatInputVoiceRecording', stopping: 'chatInputVoiceFinishing', error: 'chatInputVoiceFailed',
|
|
27
|
+
}[phase];
|
|
28
|
+
return (
|
|
29
|
+
<div role='region' aria-label={t('chatInputVoice')}
|
|
30
|
+
className={'max-h-[var(--radix-popover-content-available-height,24rem)] max-w-full overflow-y-auto border border-border bg-popover text-popover-foreground shadow-lg ' +
|
|
31
|
+
(error ? 'w-80 rounded-xl p-3' : 'rounded-full px-3 py-1.5')}
|
|
32
|
+
onKeyDown={(event) => {
|
|
33
|
+
if (event.key === 'Escape' && !event.nativeEvent.isComposing) {
|
|
34
|
+
event.stopPropagation(); manager.cancel();
|
|
35
|
+
}
|
|
36
|
+
}}>
|
|
37
|
+
<div className='flex items-center gap-2'>
|
|
38
|
+
{busy ? <LoaderCircle className='h-4 w-4 shrink-0 motion-safe:animate-spin text-muted-foreground' />
|
|
39
|
+
: <Mic className={'h-4 w-4 shrink-0 ' + (phase === 'recording' ? 'text-destructive' : '')} />}
|
|
40
|
+
<span role='status' className='min-w-0 flex-1 text-xs font-medium'>{t(status)}</span>
|
|
41
|
+
{phase === 'recording' ? <span className='text-xs tabular-nums text-muted-foreground'>{seconds}s</span> : null}
|
|
42
|
+
{phase === 'recording' ? <IconActionButton icon={<Square className='h-3 w-3' />}
|
|
43
|
+
label={t('chatInputVoiceFinish')} onClick={manager.finish} /> : null}
|
|
44
|
+
<IconActionButton icon={<X className='h-4 w-4' />} label={t('chatInputVoiceCancel')} onClick={manager.cancel} />
|
|
45
|
+
{desktop ? <IconActionButton icon={<Settings className='h-4 w-4' />}
|
|
46
|
+
label={t('chatInputVoiceShortcut')} disabled={busy} onClick={manager.openSettings} /> : null}
|
|
47
|
+
</div>
|
|
48
|
+
{error ? <>
|
|
49
|
+
<p role='alert' className='mt-2 text-xs leading-5 text-destructive'>{t(ERROR_KEYS[error])}</p>
|
|
50
|
+
<p className='mt-1 text-xs text-muted-foreground'>{t('chatInputVoiceReviewHint')}</p>
|
|
51
|
+
<Button className='mt-2' size='sm' disabled={error === 'unsupported'} onClick={onStart}>{t('chatInputVoiceRetry')}</Button>
|
|
52
|
+
</> : null}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function VoiceShortcutPanel({ onClose }: { onClose: () => void }) {
|
|
58
|
+
return <div role='region' aria-label={t('chatInputVoiceShortcut')}
|
|
59
|
+
className='max-h-[var(--radix-popover-content-available-height,24rem)] w-80 max-w-full overflow-y-auto rounded-xl border border-border bg-popover p-3 text-popover-foreground shadow-lg'
|
|
60
|
+
onKeyDown={(event) => {
|
|
61
|
+
if (event.key === 'Escape' && !event.nativeEvent.isComposing) { event.stopPropagation(); onClose(); }
|
|
62
|
+
}}>
|
|
63
|
+
<div className='flex items-center gap-2'>
|
|
64
|
+
<Settings className='h-4 w-4' />
|
|
65
|
+
<span className='flex-1 text-sm font-medium'>{t('chatInputVoiceShortcut')}</span>
|
|
66
|
+
<IconActionButton icon={<X className='h-4 w-4' />} label={t('chatInputVoiceCloseSettings')} onClick={onClose} />
|
|
67
|
+
</div>
|
|
68
|
+
<ChatVoiceShortcutControl />
|
|
69
|
+
</div>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Button } from '@/shared/components/ui/button';
|
|
3
|
+
import { t } from '@/shared/lib/i18n';
|
|
4
|
+
import { isVoiceShortcut, useChatVoiceShortcutStore, voiceShortcutLabel } from '@/features/chat/stores/chat-voice-shortcut.store';
|
|
5
|
+
|
|
6
|
+
export function ChatVoiceShortcutControl() {
|
|
7
|
+
const { shortcut, setShortcut } = useChatVoiceShortcutStore();
|
|
8
|
+
const [capturing, setCapturing] = useState(false);
|
|
9
|
+
const [error, setError] = useState<string | null>(null);
|
|
10
|
+
return <div className='mt-3 border-t border-border/60 pt-3 text-xs text-muted-foreground'>
|
|
11
|
+
<div className='flex flex-wrap items-center gap-2'>
|
|
12
|
+
<Button variant='ghost' size='sm' aria-label={t('chatInputVoiceBind')}
|
|
13
|
+
onClick={() => { setCapturing(true); setError(null); }} onBlur={() => setCapturing(false)}
|
|
14
|
+
onKeyDown={(event) => {
|
|
15
|
+
if (!capturing || event.nativeEvent.isComposing) return;
|
|
16
|
+
if (event.key === 'Escape') { event.stopPropagation(); setCapturing(false); return; }
|
|
17
|
+
if (event.key === 'Tab') return;
|
|
18
|
+
event.preventDefault(); event.stopPropagation();
|
|
19
|
+
const value = { code: event.code, ctrl: event.ctrlKey, meta: event.metaKey, alt: event.altKey, shift: event.shiftKey };
|
|
20
|
+
if (!isVoiceShortcut(value)) { setError('chatInputVoiceBindInvalid'); return; }
|
|
21
|
+
try { setShortcut(value); setCapturing(false); setError(null); }
|
|
22
|
+
catch { setError('chatInputVoiceSaveFailed'); }
|
|
23
|
+
}}>
|
|
24
|
+
{capturing ? t('chatInputVoiceCapture') : shortcut ? voiceShortcutLabel(shortcut) : t('chatInputVoiceBind')}
|
|
25
|
+
</Button>
|
|
26
|
+
{shortcut ? <Button variant='ghost' size='sm' onClick={() => {
|
|
27
|
+
try { setShortcut(null); setError(null); } catch { setError('chatInputVoiceSaveFailed'); }
|
|
28
|
+
}}>{t('chatInputVoiceUnbind')}</Button> : null}
|
|
29
|
+
</div>
|
|
30
|
+
<p role={capturing ? 'status' : undefined} className='mt-1 leading-5'>{t(capturing ? 'chatInputVoiceCaptureHelp' : 'chatInputVoiceShortcutHelp')}</p>
|
|
31
|
+
{error ? <p role='alert' className='mt-1 text-destructive'>{t(error)}</p> : null}
|
|
32
|
+
</div>;
|
|
33
|
+
}
|
|
@@ -277,8 +277,6 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
|
|
|
277
277
|
setPendingSessionType: inputActions.setPendingSessionType,
|
|
278
278
|
});
|
|
279
279
|
const isRuntimeBlocked = isNcpChatRuntimeBlocked(systemStatus);
|
|
280
|
-
const currentSessionRunning =
|
|
281
|
-
agent.isRunning || inputQuery.selectedSession?.status === "running";
|
|
282
280
|
const runtimeError = agent.snapshot.error;
|
|
283
281
|
const rawLastSendError = agent.hydrateError?.message
|
|
284
282
|
?? (runtimeError?.code === "run-interrupted" ? null : runtimeError?.message)
|
|
@@ -296,16 +294,8 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
|
|
|
296
294
|
message: filteredLastSendError,
|
|
297
295
|
status: systemStatus,
|
|
298
296
|
});
|
|
299
|
-
const controllerAgent = useMemo(
|
|
300
|
-
() => ({
|
|
301
|
-
...agent,
|
|
302
|
-
isRunning: currentSessionRunning,
|
|
303
|
-
isSending: agent.isSending,
|
|
304
|
-
}),
|
|
305
|
-
[agent, currentSessionRunning],
|
|
306
|
-
);
|
|
307
297
|
const controller = useSessionConversationController({
|
|
308
|
-
agent
|
|
298
|
+
agent,
|
|
309
299
|
inputSnapshot,
|
|
310
300
|
inputQuery,
|
|
311
301
|
isRuntimeBlocked,
|
|
@@ -12,11 +12,15 @@ import {
|
|
|
12
12
|
} from '@nextclaw/agent-chat-ui';
|
|
13
13
|
import { isRuntimeDefaultModelValue } from '@nextclaw/shared';
|
|
14
14
|
|
|
15
|
+
import { Mic, Square } from 'lucide-react';
|
|
16
|
+
import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
|
|
17
|
+
import { ChatVoiceInputPanel } from './chat-voice-input-panel';
|
|
15
18
|
import { useI18n } from '@/app/components/i18n-provider';
|
|
16
19
|
import { useViewportLayout } from '@/app/hooks/use-viewport-layout';
|
|
17
20
|
import { type SessionSkillEntryView } from '@/shared/lib/api';
|
|
18
21
|
import { t } from '@/shared/lib/i18n';
|
|
19
22
|
import { usePresenter } from '@/features/chat/components/providers/chat-presenter.provider';
|
|
23
|
+
import { useChatVoiceInput } from '@/features/chat/features/conversation/hooks/use-chat-voice-input';
|
|
20
24
|
import {
|
|
21
25
|
useChatInputSurfaceState,
|
|
22
26
|
} from '@/features/chat/features/input/hooks/use-chat-input-surface-state';
|
|
@@ -288,6 +292,17 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
288
292
|
const persistSessionPreferences = useSessionConversationPreferencePersistence({ inputActions, selectedSessionKey: inputQuery.selectedSessionKey });
|
|
289
293
|
const handleNodesChange = useSessionConversationComposerNodes(inputActions);
|
|
290
294
|
|
|
295
|
+
const voiceManager = presenter.chatVoiceInputManager;
|
|
296
|
+
const voiceContext = JSON.stringify([
|
|
297
|
+
inputQuery.selectedSessionKey, inputQuery.sessionTypeState.selectedSessionType,
|
|
298
|
+
contextReferenceProjectRoot, selectedModel, inputDisabled,
|
|
299
|
+
]);
|
|
300
|
+
const beginVoiceDraft = useCallback(() => inputBarRef.current?.beginDictation(voiceManager.cancel) ?? null, [voiceManager]);
|
|
301
|
+
const voice = useChatVoiceInput(voiceManager, voiceContext, language, !isMobile && !inputDisabled, beginVoiceDraft);
|
|
302
|
+
const startVoice = () => voiceManager.start(language, voiceContext, beginVoiceDraft());
|
|
303
|
+
const sendWithVoiceCancelled = () => { voiceManager.acceptDraft(); return controller.send(); };
|
|
304
|
+
const steerWithVoiceCancelled = () => { voiceManager.acceptDraft(); return controller.sendSteering(); };
|
|
305
|
+
|
|
291
306
|
useChatComposerReferenceIntent({
|
|
292
307
|
inputBarRef,
|
|
293
308
|
intentManager: presenter.chatComposerIntentManager,
|
|
@@ -416,9 +431,9 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
416
431
|
<ChatInputBar
|
|
417
432
|
ref={inputBarRef}
|
|
418
433
|
surface={useReadingTrack ? 'embedded' : surface}
|
|
419
|
-
topSlot={controller.queuedInputs.length > 0
|
|
420
|
-
|
|
421
|
-
: null}
|
|
434
|
+
topSlot={controller.queuedInputs.length > 0 ? <SessionQueuedInputRows controller={controller} /> : null}
|
|
435
|
+
floatingSlot={voice.phase !== 'idle' ? <ChatVoiceInputPanel manager={voiceManager} snapshot={voice}
|
|
436
|
+
desktop={!isMobile} onStart={startVoice} /> : null}
|
|
422
437
|
composer={{
|
|
423
438
|
nodes: composerNodes,
|
|
424
439
|
placeholder: textareaPlaceholder,
|
|
@@ -441,6 +456,15 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
441
456
|
})}
|
|
442
457
|
toolbar={{
|
|
443
458
|
addMenuLabel: t('chatInputAdd'),
|
|
459
|
+
leadingSlot: <>
|
|
460
|
+
<IconActionButton icon={voice.phase === 'recording' ? <Square className='h-4 w-4' /> : <Mic className='h-4 w-4' />}
|
|
461
|
+
label={t(voice.phase === 'recording' ? 'chatInputVoiceFinish' : 'chatInputVoice')}
|
|
462
|
+
size='lg'
|
|
463
|
+
disabled={inputDisabled || voice.phase === 'stopping'}
|
|
464
|
+
aria-pressed={voice.phase === 'recording'}
|
|
465
|
+
onClick={() => voice.phase === 'recording' ? voiceManager.finish()
|
|
466
|
+
: voice.phase === 'starting' ? voiceManager.cancel() : startVoice()} />
|
|
467
|
+
</>,
|
|
444
468
|
selects: [],
|
|
445
469
|
trailingSelects: toolbarSelects,
|
|
446
470
|
accessories: [
|
|
@@ -465,8 +489,8 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
|
|
|
465
489
|
sendIcon: controller.primaryAction,
|
|
466
490
|
stopButtonLabel: t('chatStop'),
|
|
467
491
|
contextWindow,
|
|
468
|
-
onSend:
|
|
469
|
-
onAlternateSend:
|
|
492
|
+
onSend: sendWithVoiceCancelled,
|
|
493
|
+
onAlternateSend: steerWithVoiceCancelled,
|
|
470
494
|
onStop: controller.stop,
|
|
471
495
|
},
|
|
472
496
|
}}
|