@nextclaw/ui 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/assets/{ChannelsList-C7F_As4r.js → ChannelsList-DhvjpZcs.js} +1 -1
  3. package/dist/assets/ChatPage-B8VBaMQm.js +38 -0
  4. package/dist/assets/{DocBrowser-Dsd8Dlq8.js → DocBrowser-LpzGe8An.js} +1 -1
  5. package/dist/assets/{LogoBadge-2ChEc_oz.js → LogoBadge-Be4lktJN.js} +1 -1
  6. package/dist/assets/{MarketplacePage-BXck6-X3.js → MarketplacePage-Cx9AI3_h.js} +1 -1
  7. package/dist/assets/{ModelConfig-CgHRSD0b.js → ModelConfig-DuImUHIX.js} +1 -1
  8. package/dist/assets/{ProvidersList-PPfZucvS.js → ProvidersList-Ccleg25k.js} +1 -1
  9. package/dist/assets/{RuntimeConfig-ClLEKNTN.js → RuntimeConfig-C6iqpJR_.js} +1 -1
  10. package/dist/assets/{SearchConfig-CuXVCbrf.js → SearchConfig-Dvp1TAXu.js} +1 -1
  11. package/dist/assets/{SecretsConfig-udJz6Ake.js → SecretsConfig-D5Ymlvt9.js} +1 -1
  12. package/dist/assets/{SessionsConfig-C1XnFfiC.js → SessionsConfig-CIA_jA1P.js} +1 -1
  13. package/dist/assets/{chat-message-BETwXLD4.js → chat-message-B60Fh9kI.js} +1 -1
  14. package/dist/assets/{index-CsvP4CER.js → index-BiPDnzv0.js} +3 -3
  15. package/dist/assets/index-C8GsgIUn.css +1 -0
  16. package/dist/assets/{index-COJdlL0e.js → index-CPDASUXh.js} +1 -1
  17. package/dist/assets/{label-BGL-ztxh.js → label-D4fGx6Wb.js} +1 -1
  18. package/dist/assets/{page-layout-aw88k7tG.js → page-layout-twy8gmBE.js} +1 -1
  19. package/dist/assets/{popover-DyEvzhmV.js → popover-DYbYpt1j.js} +1 -1
  20. package/dist/assets/{security-config-BuPAQn82.js → security-config-BcIZ4rpb.js} +1 -1
  21. package/dist/assets/skeleton-DypBy7jp.js +1 -0
  22. package/dist/assets/{switch-BK8jIzto.js → switch-DqA6r5XR.js} +1 -1
  23. package/dist/assets/{tabs-custom-Da3cEOji.js → tabs-custom-C6enKKs1.js} +1 -1
  24. package/dist/assets/{useConfirmDialog-z0CE92iS.js → useConfirmDialog-CHBf5Of7.js} +1 -1
  25. package/dist/assets/{vendor-CkJHmX1g.js → vendor-DKBNiC31.js} +1 -1
  26. package/dist/index.html +3 -3
  27. package/package.json +6 -6
  28. package/src/components/chat/chat-composer-state.ts +53 -0
  29. package/src/components/chat/chat-stream/types.ts +3 -0
  30. package/src/components/chat/containers/chat-input-bar.container.tsx +12 -41
  31. package/src/components/chat/legacy/LegacyChatPage.tsx +1 -0
  32. package/src/components/chat/managers/chat-input.manager.ts +43 -13
  33. package/src/components/chat/ncp/NcpChatPage.tsx +11 -3
  34. package/src/components/chat/ncp/ncp-chat-input.manager.ts +42 -12
  35. package/src/components/chat/presenter/chat-presenter-context.tsx +2 -0
  36. package/src/components/chat/stores/chat-input.store.ts +4 -0
  37. package/dist/assets/ChatPage-Oo7-OUsx.js +0 -37
  38. package/dist/assets/index-D-bXl7qL.css +0 -1
  39. package/dist/assets/skeleton-drzO_tdU.js +0 -1
@@ -1,18 +1,15 @@
1
- import { useMemo } from 'react';
1
+ import { useMemo, useState } from 'react';
2
2
  import { ChatInputBar } from '@nextclaw/agent-chat-ui';
3
3
  import {
4
4
  buildChatSlashItems,
5
5
  buildModelStateHint,
6
6
  buildModelToolbarSelect,
7
- buildSelectedSkillItems,
8
7
  buildSkillPickerModel,
9
8
  buildThinkingToolbarSelect,
10
- resolveSlashQuery,
11
9
  type ChatModelRecord,
12
10
  type ChatSkillRecord,
13
11
  type ChatThinkingLevel
14
12
  } from '@/components/chat/adapters/chat-input-bar.adapter';
15
- import { useChatInputBarController } from '@/components/chat/chat-input/chat-input-bar.controller';
16
13
  import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
17
14
  import { useI18n } from '@/components/providers/I18nProvider';
18
15
  import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
@@ -72,6 +69,7 @@ export function ChatInputBarContainer() {
72
69
  const presenter = usePresenter();
73
70
  const { language } = useI18n();
74
71
  const snapshot = useChatInputStore((state) => state.snapshot);
72
+ const [slashQuery, setSlashQuery] = useState<string | null>(null);
75
73
 
76
74
  const officialSkillBadgeLabel = useMemo(() => {
77
75
  // Keep memo reactive to locale switches even though `t` is imported as a stable function.
@@ -110,31 +108,11 @@ export function ChatInputBarContainer() {
110
108
  ? t('chatInputPlaceholder')
111
109
  : t('chatModelNoOptions');
112
110
 
113
- const slashQuery = resolveSlashQuery(snapshot.draft);
114
111
  const slashItems = useMemo(
115
112
  () => buildChatSlashItems(skillRecords, slashQuery ?? '', slashTexts),
116
113
  [slashQuery, skillRecords, slashTexts]
117
114
  );
118
115
 
119
- const controller = useChatInputBarController({
120
- isSlashMode: slashQuery !== null,
121
- slashItems,
122
- isSlashLoading: snapshot.isSkillsLoading,
123
- onSelectSlashItem: (item) => {
124
- if (!item.value) {
125
- return;
126
- }
127
- if (!snapshot.selectedSkills.includes(item.value)) {
128
- presenter.chatInputManager.selectSkills([...snapshot.selectedSkills, item.value]);
129
- }
130
- presenter.chatInputManager.setDraft('');
131
- },
132
- onSend: presenter.chatInputManager.send,
133
- onStop: presenter.chatInputManager.stop,
134
- isSending: snapshot.isSending,
135
- canStopGeneration: snapshot.canStopGeneration
136
- });
137
-
138
116
  const selectedModelOption = modelRecords.find((option) => option.value === snapshot.selectedModel);
139
117
  const selectedModelThinkingCapability = selectedModelOption?.thinkingCapability;
140
118
  const thinkingSupportedLevels = selectedModelThinkingCapability?.supported ?? [];
@@ -183,27 +161,23 @@ export function ChatInputBarContainer() {
183
161
 
184
162
  return (
185
163
  <ChatInputBar
186
- value={snapshot.draft}
187
- placeholder={textareaPlaceholder}
188
- disabled={inputDisabled}
189
- onValueChange={presenter.chatInputManager.setDraft}
190
- onKeyDown={controller.onTextareaKeyDown}
164
+ composer={{
165
+ nodes: snapshot.composerNodes,
166
+ placeholder: textareaPlaceholder,
167
+ disabled: inputDisabled,
168
+ onNodesChange: presenter.chatInputManager.setComposerNodes,
169
+ onSlashQueryChange: setSlashQuery
170
+ }}
191
171
  slashMenu={{
192
- isOpen: controller.isSlashPanelOpen,
193
172
  isLoading: snapshot.isSkillsLoading,
194
173
  items: slashItems,
195
- activeIndex: controller.activeSlashIndex,
196
- activeItem: controller.activeSlashItem,
197
174
  texts: {
198
175
  slashLoadingLabel: t('chatSlashLoading'),
199
176
  slashSectionLabel: t('chatSlashSectionSkills'),
200
177
  slashEmptyLabel: t('chatSlashNoResult'),
201
178
  slashHintLabel: t('chatSlashHint'),
202
179
  slashSkillHintLabel: t('chatSlashSkillHint')
203
- },
204
- onSelectItem: controller.onSelectSlashItem,
205
- onOpenChange: controller.onSlashPanelOpenChange,
206
- onSetActiveIndex: controller.onSetActiveSlashIndex
180
+ }
207
181
  }}
208
182
  hint={buildModelStateHint({
209
183
  isModelOptionsLoading,
@@ -214,17 +188,14 @@ export function ChatInputBarContainer() {
214
188
  configureProviderLabel: t('chatGoConfigureProvider')
215
189
  }
216
190
  })}
217
- selectedItems={{
218
- items: buildSelectedSkillItems(snapshot.selectedSkills, skillRecords),
219
- onRemove: (key) => presenter.chatInputManager.selectSkills(snapshot.selectedSkills.filter((skill) => skill !== key))
220
- }}
221
191
  toolbar={{
222
192
  selects: toolbarSelects,
223
193
  accessories: [
224
194
  {
225
195
  key: 'attach',
226
- label: t('chatInputAttachComingSoon'),
196
+ label: t('chatInputAttach'),
227
197
  icon: 'paperclip',
198
+ iconOnly: true,
228
199
  disabled: true,
229
200
  tooltip: t('chatInputAttachComingSoon')
230
201
  }
@@ -75,6 +75,7 @@ export function LegacyChatPage({ view }: ChatPageProps) {
75
75
  selectedSessionKeyRef,
76
76
  setSelectedSessionKey: presenter.chatSessionListManager.setSelectedSessionKey,
77
77
  setDraft: presenter.chatInputManager.setDraft,
78
+ setComposerNodes: presenter.chatInputManager.setComposerNodes,
78
79
  refetchSessions: sessionsQuery.refetch,
79
80
  refetchHistory: historyQuery.refetch
80
81
  },
@@ -1,3 +1,11 @@
1
+ import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
2
+ import {
3
+ createInitialChatComposerNodes,
4
+ createChatComposerNodesFromDraft,
5
+ deriveChatComposerDraft,
6
+ deriveSelectedSkillsFromComposer,
7
+ syncComposerSkills
8
+ } from '@/components/chat/chat-composer-state';
1
9
  import { updateSession } from '@/api/config';
2
10
  import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
3
11
  import { buildNewSessionKey } from '@/components/chat/chat-session-route';
@@ -36,6 +44,17 @@ export class ChatInputManager {
36
44
  return next;
37
45
  };
38
46
 
47
+ private isSameStringArray = (left: string[], right: string[]): boolean =>
48
+ left.length === right.length && left.every((value, index) => value === right[index]);
49
+
50
+ private syncComposerSnapshot = (nodes: ChatComposerNode[]) => {
51
+ useChatInputStore.getState().setSnapshot({
52
+ composerNodes: nodes,
53
+ draft: deriveChatComposerDraft(nodes),
54
+ selectedSkills: deriveSelectedSkillsFromComposer(nodes)
55
+ });
56
+ };
57
+
39
58
  syncSnapshot = (patch: Partial<ChatInputSnapshot>) => {
40
59
  if (!this.hasSnapshotChanges(patch)) {
41
60
  return;
@@ -46,8 +65,8 @@ export class ChatInputManager {
46
65
  Object.prototype.hasOwnProperty.call(patch, 'selectedModel') ||
47
66
  Object.prototype.hasOwnProperty.call(patch, 'selectedThinkingLevel')
48
67
  ) {
49
- const snapshot = useChatInputStore.getState().snapshot;
50
- this.reconcileThinkingForModel(snapshot.selectedModel);
68
+ const { selectedModel } = useChatInputStore.getState().snapshot;
69
+ this.reconcileThinkingForModel(selectedModel);
51
70
  }
52
71
  };
53
72
 
@@ -57,7 +76,16 @@ export class ChatInputManager {
57
76
  if (value === prev) {
58
77
  return;
59
78
  }
60
- useChatInputStore.getState().setSnapshot({ draft: value });
79
+ this.syncComposerSnapshot(createChatComposerNodesFromDraft(value));
80
+ };
81
+
82
+ setComposerNodes = (next: SetStateAction<ChatComposerNode[]>) => {
83
+ const prev = useChatInputStore.getState().snapshot.composerNodes;
84
+ const value = this.resolveUpdateValue(prev, next);
85
+ if (Object.is(value, prev)) {
86
+ return;
87
+ }
88
+ this.syncComposerSnapshot(value);
61
89
  };
62
90
 
63
91
  setPendingSessionType = (next: SetStateAction<string>) => {
@@ -76,14 +104,13 @@ export class ChatInputManager {
76
104
  if (!message) {
77
105
  return;
78
106
  }
79
- const requestedSkills = inputSnapshot.selectedSkills;
107
+ const { selectedSkills: requestedSkills, composerNodes } = inputSnapshot;
80
108
  const hasSelectedSession = Boolean(sessionSnapshot.selectedSessionKey);
81
109
  const sessionKey = sessionSnapshot.selectedSessionKey ?? buildNewSessionKey(sessionSnapshot.selectedAgentId);
82
110
  if (!hasSelectedSession) {
83
111
  this.uiManager.goToSession(sessionKey, { replace: true });
84
112
  }
85
- this.setDraft('');
86
- this.setSelectedSkills([]);
113
+ this.setComposerNodes(createInitialChatComposerNodes());
87
114
  await this.streamActionsManager.sendMessage({
88
115
  message,
89
116
  sessionKey,
@@ -94,7 +121,8 @@ export class ChatInputManager {
94
121
  stopSupported: inputSnapshot.stopSupported,
95
122
  stopReason: inputSnapshot.stopReason,
96
123
  requestedSkills,
97
- restoreDraftOnError: true
124
+ restoreDraftOnError: true,
125
+ composerNodes
98
126
  });
99
127
  };
100
128
 
@@ -132,12 +160,13 @@ export class ChatInputManager {
132
160
  };
133
161
 
134
162
  setSelectedSkills = (next: SetStateAction<string[]>) => {
135
- const prev = useChatInputStore.getState().snapshot.selectedSkills;
163
+ const snapshot = useChatInputStore.getState().snapshot;
164
+ const { selectedSkills: prev } = snapshot;
136
165
  const value = this.resolveUpdateValue(prev, next);
137
- if (Object.is(value, prev)) {
166
+ if (this.isSameStringArray(value, prev)) {
138
167
  return;
139
168
  }
140
- useChatInputStore.getState().setSnapshot({ selectedSkills: value });
169
+ this.syncComposerSnapshot(syncComposerSkills(snapshot.composerNodes, value, snapshot.skillRecords));
141
170
  };
142
171
 
143
172
  selectModel = (value: string) => {
@@ -174,15 +203,16 @@ export class ChatInputManager {
174
203
  private reconcileThinkingForModel(model: string): void {
175
204
  const snapshot = useChatInputStore.getState().snapshot;
176
205
  const modelOption = snapshot.modelOptions.find((option) => option.value === model);
177
- const nextThinking = this.resolveThinkingForModel(modelOption, snapshot.selectedThinkingLevel);
178
- if (nextThinking !== snapshot.selectedThinkingLevel) {
206
+ const { selectedThinkingLevel } = snapshot;
207
+ const nextThinking = this.resolveThinkingForModel(modelOption, selectedThinkingLevel);
208
+ if (nextThinking !== selectedThinkingLevel) {
179
209
  useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: nextThinking });
180
210
  }
181
211
  }
182
212
 
183
213
  private syncRemoteSessionType = async (normalizedType: string) => {
184
214
  const sessionSnapshot = useChatSessionListStore.getState().snapshot;
185
- const selectedSessionKey = sessionSnapshot.selectedSessionKey;
215
+ const { selectedSessionKey } = sessionSnapshot;
186
216
  if (!selectedSessionKey) {
187
217
  return;
188
218
  }
@@ -212,9 +212,17 @@ export function NcpChatPage({ view }: ChatPageProps) {
212
212
  await sessionsQuery.refetch();
213
213
  } catch (error) {
214
214
  if (payload.restoreDraftOnError) {
215
- presenter.chatInputManager.setDraft((currentDraft) =>
216
- currentDraft.trim().length === 0 ? payload.message : currentDraft
217
- );
215
+ if (payload.composerNodes && payload.composerNodes.length > 0) {
216
+ presenter.chatInputManager.setComposerNodes((currentNodes) =>
217
+ currentNodes.length === 1 && currentNodes[0]?.type === 'text' && currentNodes[0].text.length === 0
218
+ ? payload.composerNodes ?? currentNodes
219
+ : currentNodes
220
+ );
221
+ } else {
222
+ presenter.chatInputManager.setDraft((currentDraft) =>
223
+ currentDraft.trim().length === 0 ? payload.message : currentDraft
224
+ );
225
+ }
218
226
  }
219
227
  throw error;
220
228
  }
@@ -1,6 +1,14 @@
1
+ import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
1
2
  import type { SetStateAction } from 'react';
2
3
  import type { ThinkingLevel } from '@/api/types';
3
4
  import { updateNcpSession } from '@/api/ncp-session';
5
+ import {
6
+ createChatComposerNodesFromDraft,
7
+ createInitialChatComposerNodes,
8
+ deriveChatComposerDraft,
9
+ deriveSelectedSkillsFromComposer,
10
+ syncComposerSkills
11
+ } from '@/components/chat/chat-composer-state';
4
12
  import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
5
13
  import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
6
14
  import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
@@ -36,6 +44,17 @@ export class NcpChatInputManager {
36
44
  return next;
37
45
  };
38
46
 
47
+ private isSameStringArray = (left: string[], right: string[]): boolean =>
48
+ left.length === right.length && left.every((value, index) => value === right[index]);
49
+
50
+ private syncComposerSnapshot = (nodes: ChatComposerNode[]) => {
51
+ useChatInputStore.getState().setSnapshot({
52
+ composerNodes: nodes,
53
+ draft: deriveChatComposerDraft(nodes),
54
+ selectedSkills: deriveSelectedSkillsFromComposer(nodes)
55
+ });
56
+ };
57
+
39
58
  syncSnapshot = (patch: Partial<ChatInputSnapshot>) => {
40
59
  if (!this.hasSnapshotChanges(patch)) {
41
60
  return;
@@ -46,8 +65,8 @@ export class NcpChatInputManager {
46
65
  Object.prototype.hasOwnProperty.call(patch, 'selectedModel') ||
47
66
  Object.prototype.hasOwnProperty.call(patch, 'selectedThinkingLevel')
48
67
  ) {
49
- const snapshot = useChatInputStore.getState().snapshot;
50
- this.reconcileThinkingForModel(snapshot.selectedModel);
68
+ const { selectedModel } = useChatInputStore.getState().snapshot;
69
+ this.reconcileThinkingForModel(selectedModel);
51
70
  }
52
71
  };
53
72
 
@@ -57,7 +76,16 @@ export class NcpChatInputManager {
57
76
  if (value === prev) {
58
77
  return;
59
78
  }
60
- useChatInputStore.getState().setSnapshot({ draft: value });
79
+ this.syncComposerSnapshot(createChatComposerNodesFromDraft(value));
80
+ };
81
+
82
+ setComposerNodes = (next: SetStateAction<ChatComposerNode[]>) => {
83
+ const prev = useChatInputStore.getState().snapshot.composerNodes;
84
+ const value = this.resolveUpdateValue(prev, next);
85
+ if (Object.is(value, prev)) {
86
+ return;
87
+ }
88
+ this.syncComposerSnapshot(value);
61
89
  };
62
90
 
63
91
  setPendingSessionType = (next: SetStateAction<string>) => {
@@ -76,13 +104,12 @@ export class NcpChatInputManager {
76
104
  if (!message) {
77
105
  return;
78
106
  }
79
- const requestedSkills = inputSnapshot.selectedSkills;
107
+ const { selectedSkills: requestedSkills, composerNodes } = inputSnapshot;
80
108
  const sessionKey = sessionSnapshot.selectedSessionKey ?? this.getDraftSessionId();
81
109
  if (!sessionSnapshot.selectedSessionKey) {
82
110
  this.uiManager.goToSession(sessionKey, { replace: true });
83
111
  }
84
- this.setDraft('');
85
- this.setSelectedSkills([]);
112
+ this.setComposerNodes(createInitialChatComposerNodes());
86
113
  await this.streamActionsManager.sendMessage({
87
114
  message,
88
115
  sessionKey,
@@ -92,7 +119,8 @@ export class NcpChatInputManager {
92
119
  thinkingLevel: inputSnapshot.selectedThinkingLevel ?? undefined,
93
120
  stopSupported: true,
94
121
  requestedSkills,
95
- restoreDraftOnError: true
122
+ restoreDraftOnError: true,
123
+ composerNodes
96
124
  });
97
125
  };
98
126
 
@@ -129,12 +157,13 @@ export class NcpChatInputManager {
129
157
  };
130
158
 
131
159
  setSelectedSkills = (next: SetStateAction<string[]>) => {
132
- const prev = useChatInputStore.getState().snapshot.selectedSkills;
160
+ const snapshot = useChatInputStore.getState().snapshot;
161
+ const { selectedSkills: prev } = snapshot;
133
162
  const value = this.resolveUpdateValue(prev, next);
134
- if (Object.is(value, prev)) {
163
+ if (this.isSameStringArray(value, prev)) {
135
164
  return;
136
165
  }
137
- useChatInputStore.getState().setSnapshot({ selectedSkills: value });
166
+ this.syncComposerSnapshot(syncComposerSkills(snapshot.composerNodes, value, snapshot.skillRecords));
138
167
  };
139
168
 
140
169
  selectModel = (value: string) => {
@@ -171,8 +200,9 @@ export class NcpChatInputManager {
171
200
  private reconcileThinkingForModel(model: string): void {
172
201
  const snapshot = useChatInputStore.getState().snapshot;
173
202
  const modelOption = snapshot.modelOptions.find((option) => option.value === model);
174
- const nextThinking = this.resolveThinkingForModel(modelOption, snapshot.selectedThinkingLevel);
175
- if (nextThinking !== snapshot.selectedThinkingLevel) {
203
+ const { selectedThinkingLevel } = snapshot;
204
+ const nextThinking = this.resolveThinkingForModel(modelOption, selectedThinkingLevel);
205
+ if (nextThinking !== selectedThinkingLevel) {
176
206
  useChatInputStore.getState().setSnapshot({ selectedThinkingLevel: nextThinking });
177
207
  }
178
208
  }
@@ -1,3 +1,4 @@
1
+ import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
1
2
  import { createContext, useContext } from 'react';
2
3
  import type { ReactNode } from 'react';
3
4
  import type { SetStateAction } from 'react';
@@ -11,6 +12,7 @@ import type { ThinkingLevel } from '@/api/types';
11
12
  export type ChatInputManagerLike = {
12
13
  syncSnapshot: (patch: Record<string, unknown>) => void;
13
14
  setDraft: (next: SetStateAction<string>) => void;
15
+ setComposerNodes: (next: SetStateAction<ChatComposerNode[]>) => void;
14
16
  setPendingSessionType: (next: SetStateAction<string>) => void;
15
17
  send: () => Promise<void>;
16
18
  stop: () => Promise<void>;
@@ -1,10 +1,13 @@
1
1
  import { create } from 'zustand';
2
+ import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
2
3
  import type { MarketplaceInstalledRecord } from '@/api/types';
3
4
  import type { ThinkingLevel } from '@/api/types';
4
5
  import type { ChatModelOption } from '@/components/chat/chat-input.types';
6
+ import { createInitialChatComposerNodes } from '@/components/chat/chat-composer-state';
5
7
 
6
8
  export type ChatInputSnapshot = {
7
9
  isProviderStateResolved: boolean;
10
+ composerNodes: ChatComposerNode[];
8
11
  draft: string;
9
12
  pendingSessionType: string;
10
13
  defaultSessionType: string;
@@ -33,6 +36,7 @@ type ChatInputStore = {
33
36
 
34
37
  const initialSnapshot: ChatInputSnapshot = {
35
38
  isProviderStateResolved: false,
39
+ composerNodes: createInitialChatComposerNodes(),
36
40
  draft: '',
37
41
  pendingSessionType: 'native',
38
42
  defaultSessionType: 'native',