@nextclaw/ui 0.14.1 → 0.14.3

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 (224) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
  3. package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
  4. package/dist/assets/chat-page-BRB0N5WW.js +105 -0
  5. package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
  10. package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
  11. package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
  12. package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
  15. package/dist/assets/index-BiE-K8-q.css +1 -0
  16. package/dist/assets/index-D9BAE_L_.js +105 -0
  17. package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-DotaTUHb.js +1 -0
  21. package/dist/assets/plus-BbZqquJe.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
  23. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
  25. package/dist/assets/remote-B8TiUfwW.js +1 -0
  26. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  27. package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
  34. package/dist/assets/use-config-CjRV2hwL.js +1 -0
  35. package/dist/assets/x-D9PXs_xP.js +1 -0
  36. package/dist/index.html +70 -19
  37. package/index.html +55 -5
  38. package/package.json +9 -9
  39. package/src/app/components/app-presenter-provider.tsx +11 -2
  40. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  41. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  42. package/src/app/components/layout/app-layout.tsx +18 -4
  43. package/src/app/components/layout/header.tsx +3 -3
  44. package/src/app/components/layout/page-layout.tsx +2 -2
  45. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  46. package/src/app/components/layout/sidebar-items.tsx +194 -71
  47. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  48. package/src/app/components/layout/sidebar.tsx +340 -83
  49. package/src/app/managers/viewport-layout.manager.ts +10 -0
  50. package/src/app/stores/viewport-layout.store.ts +39 -3
  51. package/src/app/styles/design-system.css +388 -26
  52. package/src/app/test/vitest-setup.ts +31 -0
  53. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  54. package/src/features/agents/components/agents-page.tsx +10 -6
  55. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  56. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  57. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  59. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  60. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  61. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
  62. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  63. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
  64. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  65. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  66. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
  68. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  69. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  70. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
  71. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
  72. package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
  73. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
  79. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  80. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
  81. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  82. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  83. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  84. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  85. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  86. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  87. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  88. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  89. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  90. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  91. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  92. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  93. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  94. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  95. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  96. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  97. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  98. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  99. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  100. package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
  101. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  102. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  103. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  104. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  105. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
  106. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  107. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  108. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  109. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  110. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  111. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  112. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  113. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  114. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  115. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  116. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  117. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  118. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  119. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  120. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  121. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  122. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
  123. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  124. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  125. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  126. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  127. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
  128. package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
  129. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  130. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  131. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  132. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  133. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  134. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  135. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  136. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  137. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  138. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  139. package/src/features/chat/index.ts +0 -1
  140. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  141. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  142. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  143. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  144. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  145. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  146. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  147. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  148. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  149. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  150. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  151. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  152. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  153. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  154. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  155. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  156. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  157. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  158. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  159. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  160. package/src/features/settings/components/config/provider-form.tsx +10 -10
  161. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  162. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  163. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  164. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  165. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  166. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  167. package/src/shared/components/config-split-page.tsx +11 -11
  168. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  169. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  170. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  171. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  172. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  173. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  174. package/src/shared/components/ui/button.tsx +4 -4
  175. package/src/shared/components/ui/card.tsx +4 -4
  176. package/src/shared/components/ui/dialog.tsx +5 -5
  177. package/src/shared/components/ui/input.tsx +1 -1
  178. package/src/shared/components/ui/label.tsx +1 -1
  179. package/src/shared/components/ui/popover.tsx +1 -1
  180. package/src/shared/components/ui/select.tsx +3 -3
  181. package/src/shared/components/ui/switch.tsx +3 -3
  182. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  183. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  184. package/src/shared/components/ui/tabs.tsx +4 -4
  185. package/src/shared/components/ui/textarea.tsx +1 -1
  186. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  187. package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
  188. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  189. package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
  190. package/src/shared/lib/theme/index.ts +94 -52
  191. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  192. package/src/shared/lib/ui-document-title/index.ts +50 -21
  193. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  194. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  195. package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
  196. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  197. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  198. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  199. package/dist/assets/index-7o1WdUbU.js +0 -103
  200. package/dist/assets/index-DQWmEWzk.css +0 -1
  201. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  202. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  203. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  204. package/dist/assets/plus-Bnt56oqG.js +0 -1
  205. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  206. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  207. package/dist/assets/remote-B0LMs8u7.js +0 -1
  208. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  209. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  210. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  211. package/dist/assets/x-CW2KO-Hz.js +0 -1
  212. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  213. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  214. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  215. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  216. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  217. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  218. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  219. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  220. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  221. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  222. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  223. package/src/features/chat/stores/chat-input.store.ts +0 -104
  224. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -1,430 +0,0 @@
1
- import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
2
- import { isRuntimeDefaultModelValue } from '@nextclaw/shared';
3
- import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
4
- import type { SetStateAction } from 'react';
5
- import type { ThinkingLevel } from '@/shared/lib/api';
6
- import { updateNcpSession } from '@/shared/lib/api';
7
- import {
8
- createChatComposerNodesFromDraft,
9
- createInitialChatComposerNodes,
10
- deriveChatComposerDraft,
11
- deriveNcpMessagePartsFromComposer,
12
- deriveSelectedSkillsFromComposer,
13
- pruneComposerAttachments,
14
- syncComposerAttachments,
15
- syncComposerSkills
16
- } from '@/features/chat/features/input/utils/chat-composer-state.utils';
17
- import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
18
- import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
19
- import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
20
- import type { ChatInputSnapshot } from '@/features/chat/stores/chat-input.store';
21
- import type { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
22
- import type { ChatSessionListManager } from '@/features/chat/managers/chat-session-list.manager';
23
- import { ChatSessionPreferenceSync } from '@/features/chat/managers/chat-session-preference-sync.manager';
24
- import { isNcpChatSendDisabled } from '@/features/chat/features/input/utils/ncp-chat-input-availability.utils';
25
- import { isNcpChatRuntimeBlocked } from '@/features/chat/features/runtime/utils/ncp-chat-runtime-availability.utils';
26
- import { chatRecentModelsManager } from '@/features/chat/managers/chat-recent-models.manager';
27
- import { chatRecentSkillsManager } from '@/features/chat/managers/chat-recent-skills.manager';
28
- import type { ChatModelOption } from '@/features/chat/types/chat-input.types';
29
- import { normalizeSessionType } from '@/features/chat/features/session-type/utils/chat-session-type.utils';
30
- import { systemStatusManager } from '@/features/system-status';
31
- import { shouldClearPendingProjectRootOverride } from '@/features/chat/features/session/utils/chat-run-metadata.utils';
32
- import { normalizeSessionProjectRootValue } from '@/shared/lib/session-project';
33
-
34
- function resolveModelForSend(value: string): string | undefined {
35
- return value || undefined;
36
- }
37
-
38
- export class ChatInputManager {
39
- private readonly sessionPreferenceSync = new ChatSessionPreferenceSync(updateNcpSession);
40
-
41
- private buildAttachmentSignature = (attachment: NcpDraftAttachment): string =>
42
- [
43
- attachment.assetUri ?? '',
44
- attachment.url ?? '',
45
- attachment.name,
46
- attachment.mimeType,
47
- String(attachment.sizeBytes),
48
- attachment.contentBase64 ?? '',
49
- ].join(':');
50
-
51
- constructor(
52
- private chatRunManager: ChatRunManager,
53
- private sessionListManager: ChatSessionListManager
54
- ) {}
55
-
56
- private resolveUpdateValue = <T>(prev: T, next: SetStateAction<T>): T => {
57
- if (typeof next === 'function') {
58
- return (next as (value: T) => T)(prev);
59
- }
60
- return next;
61
- };
62
-
63
- private isSameStringArray = (left: string[], right: string[]): boolean =>
64
- left.length === right.length && left.every((value, index) => value === right[index]);
65
-
66
- private hasSnapshotChanges = (patch: Partial<ChatInputSnapshot>): boolean => {
67
- const current = useChatInputStore.getState().snapshot;
68
- for (const [key, value] of Object.entries(patch) as Array<[keyof ChatInputSnapshot, ChatInputSnapshot[keyof ChatInputSnapshot]]>) {
69
- if (!Object.is(current[key], value)) {
70
- return true;
71
- }
72
- }
73
- return false;
74
- };
75
-
76
- private isRuntimeBlockedForSend = (): boolean =>
77
- isNcpChatRuntimeBlocked(systemStatusManager.getStatusView());
78
-
79
- private syncComposerSnapshot = (nodes: ChatComposerNode[]) => {
80
- const currentAttachments = useChatInputStore.getState().snapshot.attachments;
81
- const attachments = pruneComposerAttachments(nodes, currentAttachments);
82
- useChatInputStore.getState().setSnapshot({
83
- composerNodes: nodes,
84
- attachments,
85
- draft: deriveChatComposerDraft(nodes),
86
- selectedSkills: deriveSelectedSkillsFromComposer(nodes)
87
- });
88
- };
89
-
90
- private syncComposerSnapshotWithAttachments = (
91
- nodes: ChatComposerNode[],
92
- attachments: NcpDraftAttachment[]
93
- ) => {
94
- useChatInputStore.getState().setSnapshot({
95
- composerNodes: nodes,
96
- attachments,
97
- draft: deriveChatComposerDraft(nodes),
98
- selectedSkills: deriveSelectedSkillsFromComposer(nodes)
99
- });
100
- };
101
-
102
- private dedupeAttachments = (attachments: NcpDraftAttachment[]): NcpDraftAttachment[] => {
103
- const seen = new Set<string>();
104
- const output: NcpDraftAttachment[] = [];
105
- for (const attachment of attachments) {
106
- const signature = this.buildAttachmentSignature(attachment);
107
- if (seen.has(signature)) {
108
- continue;
109
- }
110
- seen.add(signature);
111
- output.push(attachment);
112
- }
113
- return output;
114
- };
115
-
116
- syncSnapshot = (patch: Partial<ChatInputSnapshot>) => {
117
- const current = useChatInputStore.getState().snapshot;
118
- let resolvedPatch = patch;
119
- if (
120
- patch.defaultSessionType &&
121
- current.selectedSessionType === undefined &&
122
- patch.selectedSessionType === undefined
123
- ) {
124
- resolvedPatch = Object.assign({}, patch, {
125
- selectedSessionType: patch.defaultSessionType,
126
- });
127
- }
128
- if (!this.hasSnapshotChanges(resolvedPatch)) {
129
- return;
130
- }
131
- useChatInputStore.getState().setSnapshot(resolvedPatch);
132
- if (
133
- Object.prototype.hasOwnProperty.call(resolvedPatch, 'modelOptions') ||
134
- Object.prototype.hasOwnProperty.call(resolvedPatch, 'selectedModel') ||
135
- Object.prototype.hasOwnProperty.call(resolvedPatch, 'selectedThinkingLevel')
136
- ) {
137
- const { selectedModel } = useChatInputStore.getState().snapshot;
138
- this.reconcileThinkingForModel(selectedModel);
139
- }
140
- };
141
-
142
- resolveProjectRootForSend = (params: {
143
- sessionKey: string | null | undefined;
144
- selectedSessionProjectRoot: string | null | undefined;
145
- }): string | null => {
146
- const { sessionKey, selectedSessionProjectRoot } = params;
147
- const { pendingProjectRoot, pendingProjectRootSessionKey } =
148
- useChatInputStore.getState().snapshot;
149
- if (
150
- pendingProjectRoot !== null &&
151
- (!sessionKey || sessionKey === pendingProjectRootSessionKey)
152
- ) {
153
- return pendingProjectRoot;
154
- }
155
- if (!sessionKey) {
156
- return useChatInputStore.getState().snapshot.defaultProjectRoot;
157
- }
158
- return selectedSessionProjectRoot ?? null;
159
- };
160
-
161
- clearPendingProjectRootOverrideForSession = (params: {
162
- sessionKey: string | null | undefined;
163
- selectedSessionProjectRoot: string | null | undefined;
164
- }) => {
165
- const { pendingProjectRoot, pendingProjectRootSessionKey } =
166
- useChatInputStore.getState().snapshot;
167
- if (
168
- !shouldClearPendingProjectRootOverride({
169
- pendingProjectRoot,
170
- pendingProjectRootSessionKey,
171
- sessionKey: params.sessionKey,
172
- selectedSessionProjectRoot: params.selectedSessionProjectRoot,
173
- })
174
- ) {
175
- return;
176
- }
177
- useChatInputStore.getState().setSnapshot({
178
- pendingProjectRoot: null,
179
- pendingProjectRootSessionKey: null,
180
- });
181
- };
182
-
183
- clearPendingProjectRootOverrideForCurrentThread = () => {
184
- const { sessionKey, sessionProjectRoot } = useChatThreadStore.getState().snapshot;
185
- this.clearPendingProjectRootOverrideForSession({
186
- sessionKey,
187
- selectedSessionProjectRoot: sessionProjectRoot,
188
- });
189
- };
190
-
191
- syncSessionPreferences = this.sessionPreferenceSync.syncInputSelection;
192
-
193
- setDraft = (next: SetStateAction<string>) => {
194
- const prev = useChatInputStore.getState().snapshot.draft;
195
- const value = this.resolveUpdateValue(prev, next);
196
- if (value === prev) {
197
- return;
198
- }
199
- this.syncComposerSnapshot(createChatComposerNodesFromDraft(value));
200
- };
201
-
202
- applyPromptSuggestion = (prompt: string) => {
203
- const normalizedPrompt = prompt.trim();
204
- if (!normalizedPrompt) {
205
- return;
206
- }
207
- this.setDraft(normalizedPrompt);
208
- this.requestComposerFocusAtEnd();
209
- };
210
-
211
- requestComposerFocusAtEnd = () => {
212
- const currentRequest = useChatInputStore.getState().snapshot.composerFocusRequest;
213
- useChatInputStore.getState().setSnapshot({
214
- composerFocusRequest: {
215
- id: (currentRequest?.id ?? 0) + 1,
216
- placement: 'end',
217
- },
218
- });
219
- };
220
-
221
- consumeComposerFocusRequest = (requestId: number) => {
222
- const currentRequest = useChatInputStore.getState().snapshot.composerFocusRequest;
223
- if (currentRequest?.id !== requestId) {
224
- return;
225
- }
226
- useChatInputStore.getState().setSnapshot({ composerFocusRequest: null });
227
- };
228
-
229
- setComposerNodes = (next: SetStateAction<ChatComposerNode[]>) => {
230
- const prev = useChatInputStore.getState().snapshot.composerNodes;
231
- const value = this.resolveUpdateValue(prev, next);
232
- if (Object.is(value, prev)) {
233
- return;
234
- }
235
- this.syncComposerSnapshot(value);
236
- };
237
-
238
- addAttachments = (attachments: NcpDraftAttachment[]): NcpDraftAttachment[] => {
239
- if (attachments.length === 0) {
240
- return [];
241
- }
242
- const { snapshot } = useChatInputStore.getState();
243
- const existingSignatures = new Set(snapshot.attachments.map(this.buildAttachmentSignature));
244
- const nextAttachments = this.dedupeAttachments([...snapshot.attachments, ...attachments]);
245
- const insertedAttachments = nextAttachments.filter(
246
- (attachment) => !existingSignatures.has(this.buildAttachmentSignature(attachment))
247
- );
248
- if (insertedAttachments.length === 0) {
249
- return [];
250
- }
251
- const nextNodes = syncComposerAttachments(snapshot.composerNodes, nextAttachments);
252
- this.syncComposerSnapshotWithAttachments(nextNodes, nextAttachments);
253
- return insertedAttachments;
254
- };
255
-
256
- restoreComposerState = (nodes: ChatComposerNode[], attachments: NcpDraftAttachment[]) => {
257
- const nextAttachments = this.dedupeAttachments(attachments);
258
- const nextNodes = syncComposerAttachments(nodes, nextAttachments);
259
- this.syncComposerSnapshotWithAttachments(nextNodes, nextAttachments);
260
- };
261
-
262
- setPendingSessionType = (next: SetStateAction<string>) => {
263
- const prev = useChatInputStore.getState().snapshot.pendingSessionType;
264
- const value = this.resolveUpdateValue(prev, next);
265
- if (value === prev) {
266
- return;
267
- }
268
- useChatInputStore.getState().setSnapshot({
269
- pendingSessionType: value,
270
- selectedSessionType: value,
271
- });
272
- };
273
-
274
- setPendingProjectRoot = (projectRoot: string | null) => {
275
- const normalizedProjectRoot = normalizeSessionProjectRootValue(projectRoot);
276
- useChatInputStore.getState().setSnapshot({
277
- pendingProjectRoot: normalizedProjectRoot,
278
- pendingProjectRootSessionKey: null,
279
- });
280
- };
281
-
282
- send = async () => {
283
- const inputSnapshot = useChatInputStore.getState().snapshot;
284
- const sessionSnapshot = useChatSessionListStore.getState().snapshot;
285
- const threadSnapshot = useChatThreadStore.getState().snapshot;
286
- const message = inputSnapshot.draft.trim();
287
- const { attachments } = inputSnapshot;
288
- const parts = deriveNcpMessagePartsFromComposer(inputSnapshot.composerNodes, attachments);
289
- const hasSendableContent = parts.some(
290
- (part) => part.type !== 'text' || part.text.trim().length > 0
291
- );
292
- if (
293
- isNcpChatSendDisabled({
294
- snapshot: inputSnapshot,
295
- hasSendableDraft: hasSendableContent,
296
- isRuntimeBlocked: this.isRuntimeBlockedForSend(),
297
- })
298
- ) {
299
- return;
300
- }
301
- const { selectedSkills: requestedSkills, composerNodes } = inputSnapshot;
302
- const sessionKey =
303
- threadSnapshot.sessionKey ??
304
- sessionSnapshot.selectedSessionKey ??
305
- null;
306
- if (!sessionKey && inputSnapshot.selectedSessionType?.trim()) {
307
- this.sessionListManager.ensureDraftSession(inputSnapshot.selectedSessionType);
308
- }
309
- this.setComposerNodes(createInitialChatComposerNodes());
310
- try {
311
- await this.chatRunManager.sendMessage({
312
- message,
313
- sessionKey: sessionKey ?? undefined,
314
- agentId: sessionSnapshot.selectedAgentId,
315
- sessionType: inputSnapshot.selectedSessionType,
316
- model: resolveModelForSend(inputSnapshot.selectedModel),
317
- thinkingLevel: inputSnapshot.selectedThinkingLevel ?? undefined,
318
- projectRoot: this.resolveProjectRootForSend({
319
- sessionKey,
320
- selectedSessionProjectRoot: threadSnapshot.sessionProjectRoot,
321
- }),
322
- requestedSkills,
323
- attachments,
324
- parts,
325
- composerNodes,
326
- });
327
- } catch (error) {
328
- this.restoreComposerState(composerNodes, attachments);
329
- throw error;
330
- }
331
- };
332
-
333
- stop = async () => {
334
- await this.chatRunManager.stopCurrentRun();
335
- };
336
-
337
- setSelectedModel = (next: SetStateAction<string>) => {
338
- const prev = useChatInputStore.getState().snapshot.selectedModel;
339
- const value = this.resolveUpdateValue(prev, next);
340
- if (value === prev) {
341
- return;
342
- }
343
- useChatInputStore.getState().setSnapshot({ selectedModel: value });
344
- this.reconcileThinkingForModel(value);
345
- };
346
-
347
- setSelectedThinkingLevel = (next: SetStateAction<ThinkingLevel | null>) => {
348
- const prev = useChatInputStore.getState().snapshot.selectedThinkingLevel;
349
- const value = this.resolveUpdateValue(prev, next);
350
- if (value === prev) {
351
- return;
352
- }
353
- useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: value });
354
- };
355
-
356
- selectSessionType = (value: string) => {
357
- const normalized = normalizeSessionType(value);
358
- useChatInputStore.getState().setSnapshot({ selectedSessionType: normalized, pendingSessionType: normalized });
359
- };
360
-
361
- setSelectedSkills = (next: SetStateAction<string[]>) => {
362
- const { snapshot } = useChatInputStore.getState();
363
- const { selectedSkills: prev } = snapshot;
364
- const value = this.resolveUpdateValue(prev, next);
365
- if (this.isSameStringArray(value, prev)) {
366
- return;
367
- }
368
- this.syncComposerSnapshot(syncComposerSkills(snapshot.composerNodes, value, snapshot.skillRecords));
369
- };
370
-
371
- selectModel = (value: string) => {
372
- this.setSelectedModel(value);
373
- chatRecentModelsManager.remember(value, {
374
- namespace: useChatInputStore.getState().snapshot.selectedSessionType,
375
- });
376
- if (!isRuntimeDefaultModelValue(value)) {
377
- chatRecentModelsManager.remember(value);
378
- }
379
- this.sessionPreferenceSync.syncSelectedSessionPreferences();
380
- };
381
-
382
- selectThinkingLevel = (value: ThinkingLevel) => {
383
- this.setSelectedThinkingLevel(value);
384
- this.sessionPreferenceSync.syncSelectedSessionPreferences();
385
- };
386
-
387
- rememberSkillSelection = (value: string) => {
388
- chatRecentSkillsManager.remember(value);
389
- };
390
-
391
- selectSkills = (next: string[]) => {
392
- const prev = useChatInputStore.getState().snapshot.selectedSkills;
393
- for (const value of next) {
394
- if (!prev.includes(value)) {
395
- this.rememberSkillSelection(value);
396
- }
397
- }
398
- this.setSelectedSkills(next);
399
- };
400
-
401
- private resolveThinkingForModel = (
402
- modelOption: ChatModelOption | undefined,
403
- current: ThinkingLevel | null
404
- ): ThinkingLevel | null => {
405
- const capability = modelOption?.thinkingCapability;
406
- if (!capability || capability.supported.length === 0) {
407
- return null;
408
- }
409
- if (current === 'off') {
410
- return 'off';
411
- }
412
- if (current && capability.supported.includes(current)) {
413
- return current;
414
- }
415
- if (capability.default && capability.supported.includes(capability.default)) {
416
- return capability.default;
417
- }
418
- return 'off';
419
- };
420
-
421
- private reconcileThinkingForModel = (model: string): void => {
422
- const { snapshot } = useChatInputStore.getState();
423
- const modelOption = snapshot.modelOptions.find((option) => option.value === model);
424
- const { selectedThinkingLevel } = snapshot;
425
- const nextThinking = this.resolveThinkingForModel(modelOption, selectedThinkingLevel);
426
- if (nextThinking !== selectedThinkingLevel) {
427
- useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: nextThinking });
428
- }
429
- };
430
- }
@@ -1,110 +0,0 @@
1
- import { buildNcpRequestEnvelope } from '@nextclaw/ncp-react';
2
- import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
3
- import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
4
- import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
5
- import type {
6
- ChatRunRuntime,
7
- ChatRunSnapshot,
8
- SendMessageParams,
9
- } from '@/features/chat/types/chat-run.types';
10
- import { buildChatRunMetadata } from '@/features/chat/features/session/utils/chat-run-metadata.utils';
11
-
12
- export class ChatRunManager {
13
- private activeRuntime: ChatRunRuntime | null = null;
14
-
15
- constructor(private uiManager: ChatUiManager) {}
16
-
17
- setActiveRuntime = (runtime: ChatRunRuntime | null) => {
18
- this.activeRuntime = runtime;
19
- };
20
-
21
- sendMessage = async (payload: SendMessageParams) => {
22
- const runtime = this.activeRuntime;
23
- if (!runtime || runtime.sessionKey !== (payload.sessionKey ?? null)) {
24
- return;
25
- }
26
- const metadata = buildChatRunMetadata({
27
- agentId: payload.agentId,
28
- model: payload.model,
29
- thinkingLevel: payload.thinkingLevel,
30
- sessionType: payload.sessionType,
31
- projectRoot: payload.projectRoot,
32
- requestedSkills: payload.requestedSkills,
33
- composerNodes: payload.composerNodes,
34
- });
35
- const envelope = buildNcpRequestEnvelope({
36
- sessionId: payload.sessionKey,
37
- text: payload.message,
38
- attachments: payload.attachments,
39
- parts: payload.parts,
40
- metadata,
41
- });
42
- if (!envelope) {
43
- return;
44
- }
45
- const handle = await runtime.sendEnvelope(envelope);
46
- if (!payload.sessionKey && handle?.sessionId) {
47
- this.materializeRootSessionRoute(handle.sessionId);
48
- }
49
- };
50
-
51
- stopCurrentRun = async () => {
52
- await this.activeRuntime?.abortCurrentRun();
53
- };
54
-
55
- resumeRun = async (run: { sessionKey: string }) => {
56
- if (this.activeRuntime?.sessionKey !== run.sessionKey) {
57
- return;
58
- }
59
- await this.activeRuntime.resumeCurrentSessionRun();
60
- };
61
-
62
- applyRunSnapshot = (snapshot: ChatRunSnapshot) => {
63
- const isSending = snapshot.isSending || snapshot.isRunning;
64
- const sessionKey =
65
- snapshot.routeSessionKey ?? snapshot.materializedSessionKey ?? null;
66
- useChatInputStore.getState().setSnapshot({
67
- canStopGeneration: snapshot.isRunning,
68
- stopDisabledReason: snapshot.isRunning ? null : '__preparing__',
69
- stopSupported: true,
70
- stopReason: undefined,
71
- sendError: snapshot.sendErrorMessage,
72
- isSending,
73
- });
74
- useChatThreadStore.getState().setSnapshot({
75
- sessionKey,
76
- isHistoryLoading: snapshot.isHydrating,
77
- messages: snapshot.visibleMessages,
78
- isSending,
79
- isAwaitingAssistantOutput: snapshot.isRunning,
80
- contextWindow: snapshot.contextWindow,
81
- });
82
- if (!snapshot.routeSessionKey && snapshot.materializedSessionKey) {
83
- this.materializeRootSessionRoute(snapshot.materializedSessionKey);
84
- }
85
- };
86
-
87
- clearRunState = () => {
88
- useChatInputStore.getState().setSnapshot({
89
- canStopGeneration: false,
90
- stopDisabledReason: '__preparing__',
91
- sendError: null,
92
- isSending: false,
93
- });
94
- useChatThreadStore.getState().setSnapshot({
95
- isHistoryLoading: false,
96
- messages: [],
97
- isSending: false,
98
- isAwaitingAssistantOutput: false,
99
- contextWindow: null,
100
- });
101
- };
102
-
103
- private materializeRootSessionRoute = (sessionKey: string) => {
104
- const normalizedSessionKey = sessionKey.trim();
105
- if (!normalizedSessionKey || !this.uiManager.isAtChatRoot()) {
106
- return;
107
- }
108
- this.uiManager.goToSession(normalizedSessionKey, { replace: true });
109
- };
110
- }
@@ -1,142 +0,0 @@
1
- import { isRuntimeDefaultModelValue } from '@nextclaw/shared';
2
- import type { SessionPatchUpdate, ThinkingLevel } from '@/shared/lib/api';
3
- import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
4
- import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
5
- import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
6
- import {
7
- resolveSelectedModelValue,
8
- resolveSelectedThinkingLevelValue,
9
- } from '@/features/chat/features/session/utils/chat-session-preference-governance.utils';
10
-
11
- type QueuedSessionPreferenceSync = { sessionKey: string; patch: SessionPatchUpdate };
12
-
13
- function normalizeOptionalModel(value: string): string | null {
14
- if (isRuntimeDefaultModelValue(value)) {
15
- return null;
16
- }
17
- const normalized = value.trim(); return normalized.length > 0 ? normalized : null;
18
- }
19
-
20
- export class ChatSessionPreferenceSync {
21
- private inFlight: Promise<void> | null = null;
22
- private queued: QueuedSessionPreferenceSync | null = null;
23
- private previousPreferenceSessionKey: string | null | undefined = undefined;
24
-
25
- constructor(
26
- private readonly updateSession: (sessionKey: string, patch: SessionPatchUpdate) => Promise<unknown>
27
- ) {}
28
-
29
- syncSelectedSessionPreferences = (): void => {
30
- const inputSnapshot = useChatInputStore.getState().snapshot;
31
- const sessionSnapshot = useChatSessionListStore.getState().snapshot;
32
- const threadSnapshot = useChatThreadStore.getState().snapshot;
33
- const sessionKey = sessionSnapshot.selectedSessionKey;
34
- if (!sessionKey || !threadSnapshot.canDeleteSession) {
35
- return;
36
- }
37
-
38
- this.enqueue({
39
- sessionKey,
40
- patch: {
41
- preferredModel: normalizeOptionalModel(inputSnapshot.selectedModel),
42
- preferredThinking: inputSnapshot.selectedThinkingLevel ?? null
43
- }
44
- });
45
- };
46
-
47
- syncInputSelection = (params: {
48
- selectedSessionKey?: string | null;
49
- selectedSessionExists: boolean;
50
- selectedSessionPreferredModel?: string;
51
- fallbackPreferredModel?: string;
52
- defaultModel?: string;
53
- selectedSessionPreferredThinking?: ThinkingLevel | null;
54
- fallbackPreferredThinking?: ThinkingLevel | null;
55
- }) => {
56
- const {
57
- defaultModel,
58
- fallbackPreferredModel,
59
- fallbackPreferredThinking,
60
- selectedSessionKey = null,
61
- selectedSessionExists,
62
- selectedSessionPreferredModel,
63
- selectedSessionPreferredThinking,
64
- } = params;
65
- const { snapshot } = useChatInputStore.getState();
66
- const { modelOptions } = snapshot;
67
- const sessionChanged = this.previousPreferenceSessionKey !== selectedSessionKey;
68
- const preserveCurrentPreference = sessionChanged && Boolean(selectedSessionKey) && !selectedSessionExists;
69
- const selectedModel = resolveSelectedModelValue({
70
- currentSelectedModel: snapshot.selectedModel,
71
- modelOptions,
72
- selectedSessionPreferredModel,
73
- fallbackPreferredModel,
74
- defaultModel,
75
- preferSessionPreferredModel: sessionChanged,
76
- preserveCurrentSelectedModelOnSessionChange: preserveCurrentPreference,
77
- });
78
- const modelOption = modelOptions.find((option) => option.value === selectedModel);
79
- const supportedThinkingLevels =
80
- (modelOption?.thinkingCapability?.supported as ThinkingLevel[] | undefined) ?? [];
81
- const selectedThinking = resolveSelectedThinkingLevelValue({
82
- currentSelectedThinkingLevel: snapshot.selectedThinkingLevel,
83
- supportedThinkingLevels,
84
- selectedSessionPreferredThinking,
85
- fallbackPreferredThinking,
86
- defaultThinkingLevel:
87
- (modelOption?.thinkingCapability?.default as ThinkingLevel | null | undefined) ?? null,
88
- preferSessionPreferredThinking: sessionChanged,
89
- preserveCurrentSelectedThinkingOnSessionChange: preserveCurrentPreference,
90
- });
91
- if (
92
- snapshot.selectedModel === selectedModel &&
93
- snapshot.selectedThinkingLevel === selectedThinking
94
- ) {
95
- this.recordSyncedPreferenceSessionKey(selectedSessionKey, selectedSessionExists);
96
- return;
97
- }
98
- useChatInputStore.getState().setSnapshot({
99
- selectedModel,
100
- selectedThinkingLevel: selectedThinking,
101
- });
102
- this.recordSyncedPreferenceSessionKey(selectedSessionKey, selectedSessionExists);
103
- };
104
-
105
- private recordSyncedPreferenceSessionKey = (
106
- selectedSessionKey: string | null,
107
- selectedSessionExists: boolean,
108
- ): void => {
109
- if (!selectedSessionKey || selectedSessionExists) {
110
- this.previousPreferenceSessionKey = selectedSessionKey;
111
- }
112
- };
113
-
114
- private enqueue = (next: QueuedSessionPreferenceSync): void => {
115
- this.queued = next;
116
- if (this.inFlight) {
117
- return;
118
- }
119
- this.startFlush();
120
- };
121
-
122
- private startFlush = (): void => {
123
- this.inFlight = this.flush()
124
- .catch((error) => {
125
- console.error(`Failed to sync chat session preferences: ${String(error)}`);
126
- })
127
- .finally(() => {
128
- this.inFlight = null;
129
- if (this.queued) {
130
- this.startFlush();
131
- }
132
- });
133
- };
134
-
135
- private flush = async (): Promise<void> => {
136
- while (this.queued) {
137
- const current = this.queued;
138
- this.queued = null;
139
- await this.updateSession(current.sessionKey, current.patch);
140
- }
141
- };
142
- }