@nextclaw/ui 0.11.19 → 0.11.21

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 (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/assets/{ChannelsList-DAx7wv0_.js → ChannelsList-ByHWHkQS.js} +1 -1
  3. package/dist/assets/ChatPage-FdT3pDnw.js +42 -0
  4. package/dist/assets/{DocBrowser-DKkE3Y4I.js → DocBrowser-3y_NHZ71.js} +1 -1
  5. package/dist/assets/DocBrowser-CMdPdbZj.js +1 -0
  6. package/dist/assets/{DocBrowserContext-BcZRBsCg.js → DocBrowserContext-CVJuwCcw.js} +1 -1
  7. package/dist/assets/{LogoBadge-BIPDLEwK.js → LogoBadge-D8fyilO-.js} +1 -1
  8. package/dist/assets/MarketplacePage-9oKmxN2n.js +1 -0
  9. package/dist/assets/{MarketplacePage-Dlp5BgCh.js → MarketplacePage-CmhsZXr1.js} +1 -1
  10. package/dist/assets/{McpMarketplacePage-CwKtAil8.js → McpMarketplacePage-C7PkCYbp.js} +1 -1
  11. package/dist/assets/{ModelConfig-Dg6F3Ldb.js → ModelConfig-DmCY6jWM.js} +1 -1
  12. package/dist/assets/{ProvidersList-f7bQdRxA.js → ProvidersList-ClT-34aX.js} +1 -1
  13. package/dist/assets/RemoteAccessPage-B6hUZl1O.js +1 -0
  14. package/dist/assets/{RuntimeConfig-M4OKjmgU.js → RuntimeConfig-C5aqliGk.js} +1 -1
  15. package/dist/assets/{SearchConfig-v46R5a2U.js → SearchConfig-Dm7r2yfp.js} +1 -1
  16. package/dist/assets/{SecretsConfig-CXvUpbB_.js → SecretsConfig-BBP_mbQh.js} +1 -1
  17. package/dist/assets/{SessionsConfig-7vUHMtOh.js → SessionsConfig-6wNJloZN.js} +1 -1
  18. package/dist/assets/{book-open-DzSduAaw.js → book-open-B26jGBjY.js} +1 -1
  19. package/dist/assets/{chat-session-display-CGfXhJoT.js → chat-session-display-Bjmn4aIZ.js} +1 -1
  20. package/dist/assets/{chunk-JZWAC4HX-C1vpvW4r.js → chunk-JZWAC4HX-B-4B29RN.js} +1 -1
  21. package/dist/assets/{config-Df97LeLR.js → config-BaC29Qf-.js} +1 -1
  22. package/dist/assets/{createLucideIcon-CcR5wVoU.js → createLucideIcon-DiFAvXmK.js} +1 -1
  23. package/dist/assets/{dist-BMlnBah3.js → dist-kW_O3kyZ.js} +1 -1
  24. package/dist/assets/{dist-Dii9v3X9.js → dist-pCfWPG1A.js} +1 -1
  25. package/dist/assets/{external-link-CnSDrvJE.js → external-link-D5-p-Gmm.js} +1 -1
  26. package/dist/assets/{hash-CAnX6PNt.js → hash-BlwrSV0q.js} +1 -1
  27. package/dist/assets/i18n-CSytxMFI.js +1 -0
  28. package/dist/assets/{index-BahpXJg8.css → index-CUy6doWo.css} +1 -1
  29. package/dist/assets/{index-B0DzQqwv.js → index-DvKS3L9j.js} +3 -3
  30. package/dist/assets/{label-CtIFj7_6.js → label-RyXfZqkP.js} +1 -1
  31. package/dist/assets/loader-circle-B2J777gj.js +1 -0
  32. package/dist/assets/{logos-3KFNiOej.js → logos-Bpl8QTgI.js} +1 -1
  33. package/dist/assets/{page-layout-BMwpn87D.js → page-layout--S0YBU0W.js} +1 -1
  34. package/dist/assets/plus-CM9XJ0Tf.js +1 -0
  35. package/dist/assets/{popover-BIzq25oH.js → popover-BEjfbEwy.js} +1 -1
  36. package/dist/assets/{react-ji6GGP_j.js → react-BuSP2-8B.js} +1 -1
  37. package/dist/assets/{save-CMgYkJ-y.js → save-DPPPpD_c.js} +1 -1
  38. package/dist/assets/search-Ctaw34Kp.js +1 -0
  39. package/dist/assets/{security-config-Xi5DYW7j.js → security-config-6t78Ph-I.js} +1 -1
  40. package/dist/assets/{select-Cz82gl01.js → select-CT50pzod.js} +1 -1
  41. package/dist/assets/skeleton-Bycyb0zU.js +1 -0
  42. package/dist/assets/{status-dot-C7q1HvLH.js → status-dot-BbBqRHfh.js} +1 -1
  43. package/dist/assets/{switch-DYswvkYj.js → switch-D3l6AcCk.js} +1 -1
  44. package/dist/assets/{tabs-custom-DKYQxrx1.js → tabs-custom-TZQ5WPWP.js} +1 -1
  45. package/dist/assets/{trash-2-DfXI7-ap.js → trash-2-B2_AGVE3.js} +1 -1
  46. package/dist/assets/{useConfirmDialog-CXDAxtRL.js → useConfirmDialog-BDpdjfIO.js} +1 -1
  47. package/dist/assets/{useMutation-s2sn2yzh.js → useMutation-BzCrO8j-.js} +1 -1
  48. package/dist/assets/x-CHOBE-63.js +1 -0
  49. package/dist/index.html +18 -18
  50. package/package.json +6 -6
  51. package/src/components/chat/adapters/chat-message-part.adapter.ts +74 -3
  52. package/src/components/chat/adapters/chat-message.adapter.test.ts +321 -3
  53. package/src/components/chat/adapters/chat-message.file-operation-card.ts +437 -0
  54. package/src/components/chat/adapters/chat-message.file-operation-diff.ts +408 -0
  55. package/src/components/chat/adapters/chat-message.partial-json.ts +89 -0
  56. package/src/components/chat/containers/chat-input-bar.container.tsx +8 -8
  57. package/src/components/chat/containers/chat-message-list.container.tsx +1 -0
  58. package/src/components/chat/ncp/ncp-session-adapter.test.ts +173 -0
  59. package/src/components/chat/useNcpAgentRuntime.test.tsx +90 -0
  60. package/src/lib/i18n.chat.ts +2 -1
  61. package/src/remote/remote-access-feedback.service.test.ts +18 -0
  62. package/src/remote/remote-access-feedback.service.ts +10 -1
  63. package/dist/assets/ChatPage-l2PYwCeB.js +0 -38
  64. package/dist/assets/DocBrowser-CIHLqoIm.js +0 -1
  65. package/dist/assets/MarketplacePage-TVeyVOuO.js +0 -1
  66. package/dist/assets/RemoteAccessPage-w_dY7P4T.js +0 -1
  67. package/dist/assets/i18n-CXBpwAwA.js +0 -1
  68. package/dist/assets/loader-circle-qgU4zQDw.js +0 -1
  69. package/dist/assets/plus-C9cYVbL-.js +0 -1
  70. package/dist/assets/search-sl1OeJFl.js +0 -1
  71. package/dist/assets/skeleton-rgIt7a5q.js +0 -1
  72. package/dist/assets/x-MIimOGs6.js +0 -1
@@ -0,0 +1,408 @@
1
+ import type { ChatFileOperationLineViewModel } from "@nextclaw/agent-chat-ui";
2
+
3
+ export type ParsedBlock = {
4
+ path: string;
5
+ display: "preview" | "diff";
6
+ caption?: string;
7
+ lines: ChatFileOperationLineViewModel[];
8
+ rawText?: string;
9
+ truncated?: boolean;
10
+ };
11
+
12
+ const MAX_VISIBLE_DIFF_LINES = 120;
13
+ const MAX_DIFF_MATRIX_CELLS = 12_000;
14
+
15
+ function splitLines(value: string): string[] {
16
+ const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
17
+ return normalized === "" ? [] : normalized.split("\n");
18
+ }
19
+
20
+ function buildCaption(params: {
21
+ operation?: string | null;
22
+ lines: ChatFileOperationLineViewModel[];
23
+ }): string | undefined {
24
+ const additions = params.lines.filter((line) => line.kind === "add").length;
25
+ const deletions = params.lines.filter((line) => line.kind === "remove").length;
26
+ const parts: string[] = [];
27
+ const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
28
+ if (normalizedOperation && normalizedOperation !== "update") {
29
+ parts.push(normalizedOperation);
30
+ }
31
+ if (additions > 0) {
32
+ parts.push(`+${additions}`);
33
+ }
34
+ if (deletions > 0) {
35
+ parts.push(`-${deletions}`);
36
+ }
37
+ return parts.length > 0 ? parts.join(" · ") : undefined;
38
+ }
39
+
40
+ function limitLines(
41
+ lines: ChatFileOperationLineViewModel[],
42
+ ): { lines: ChatFileOperationLineViewModel[]; truncated: boolean } {
43
+ if (lines.length <= MAX_VISIBLE_DIFF_LINES) {
44
+ return { lines, truncated: false };
45
+ }
46
+ return {
47
+ lines: lines.slice(0, MAX_VISIBLE_DIFF_LINES),
48
+ truncated: true,
49
+ };
50
+ }
51
+
52
+ export function buildRawPreviewBlock(params: {
53
+ path: string;
54
+ text: string;
55
+ operation?: string | null;
56
+ }): ParsedBlock | null {
57
+ const previewText = params.text.trim();
58
+ if (!previewText) {
59
+ return null;
60
+ }
61
+ const previewKind = params.operation?.trim().toLowerCase() === "write" ? "add" : "context";
62
+ const lines = splitLines(previewText).map((line, index) =>
63
+ previewKind === "add"
64
+ ? {
65
+ kind: "add" as const,
66
+ text: line,
67
+ newLineNumber: index + 1,
68
+ }
69
+ : {
70
+ kind: "context" as const,
71
+ text: line,
72
+ oldLineNumber: index + 1,
73
+ newLineNumber: index + 1,
74
+ },
75
+ );
76
+ return {
77
+ path: params.path,
78
+ display: "preview",
79
+ caption: buildCaption({
80
+ operation: params.operation,
81
+ lines,
82
+ }),
83
+ lines,
84
+ };
85
+ }
86
+
87
+ function buildFallbackDiffLines(params: {
88
+ beforeLines: string[];
89
+ afterLines: string[];
90
+ }): ChatFileOperationLineViewModel[] {
91
+ return [
92
+ ...params.beforeLines.map((line, index) => ({
93
+ kind: "remove" as const,
94
+ text: line,
95
+ oldLineNumber: index + 1,
96
+ })),
97
+ ...params.afterLines.map((line, index) => ({
98
+ kind: "add" as const,
99
+ text: line,
100
+ newLineNumber: index + 1,
101
+ })),
102
+ ];
103
+ }
104
+
105
+ function buildLcsMatrix(params: {
106
+ beforeLines: string[];
107
+ afterLines: string[];
108
+ }): number[][] {
109
+ const matrix: number[][] = Array.from(
110
+ { length: params.beforeLines.length + 1 },
111
+ () => Array.from({ length: params.afterLines.length + 1 }, () => 0),
112
+ );
113
+ for (let beforeIndex = params.beforeLines.length - 1; beforeIndex >= 0; beforeIndex -= 1) {
114
+ for (let afterIndex = params.afterLines.length - 1; afterIndex >= 0; afterIndex -= 1) {
115
+ matrix[beforeIndex]![afterIndex] =
116
+ params.beforeLines[beforeIndex] === params.afterLines[afterIndex]
117
+ ? (matrix[beforeIndex + 1]![afterIndex + 1] ?? 0) + 1
118
+ : Math.max(
119
+ matrix[beforeIndex + 1]![afterIndex] ?? 0,
120
+ matrix[beforeIndex]![afterIndex + 1] ?? 0,
121
+ );
122
+ }
123
+ }
124
+ return matrix;
125
+ }
126
+
127
+ function appendRemainingDiffLines(params: {
128
+ lines: ChatFileOperationLineViewModel[];
129
+ beforeLines: string[];
130
+ afterLines: string[];
131
+ beforeIndex: number;
132
+ afterIndex: number;
133
+ oldLineNumber: number;
134
+ newLineNumber: number;
135
+ }): void {
136
+ for (let index = params.beforeIndex; index < params.beforeLines.length; index += 1) {
137
+ params.lines.push({
138
+ kind: "remove",
139
+ text: params.beforeLines[index] ?? "",
140
+ oldLineNumber: params.oldLineNumber,
141
+ });
142
+ params.oldLineNumber += 1;
143
+ }
144
+ for (let index = params.afterIndex; index < params.afterLines.length; index += 1) {
145
+ params.lines.push({
146
+ kind: "add",
147
+ text: params.afterLines[index] ?? "",
148
+ newLineNumber: params.newLineNumber,
149
+ });
150
+ params.newLineNumber += 1;
151
+ }
152
+ }
153
+
154
+ function buildLineDiff(
155
+ beforeText: string,
156
+ afterText: string,
157
+ ): ChatFileOperationLineViewModel[] {
158
+ const beforeLines = splitLines(beforeText);
159
+ const afterLines = splitLines(afterText);
160
+ if (beforeLines.length * afterLines.length > MAX_DIFF_MATRIX_CELLS) {
161
+ return buildFallbackDiffLines({
162
+ beforeLines,
163
+ afterLines,
164
+ });
165
+ }
166
+
167
+ const lcs = buildLcsMatrix({
168
+ beforeLines,
169
+ afterLines,
170
+ });
171
+ const lines: ChatFileOperationLineViewModel[] = [];
172
+ let beforeIndex = 0;
173
+ let afterIndex = 0;
174
+ let oldLineNumber = 1;
175
+ let newLineNumber = 1;
176
+ while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
177
+ if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
178
+ lines.push({
179
+ kind: "context",
180
+ text: beforeLines[beforeIndex] ?? "",
181
+ oldLineNumber,
182
+ newLineNumber,
183
+ });
184
+ beforeIndex += 1;
185
+ afterIndex += 1;
186
+ oldLineNumber += 1;
187
+ newLineNumber += 1;
188
+ continue;
189
+ }
190
+
191
+ if ((lcs[beforeIndex + 1]![afterIndex] ?? 0) >= (lcs[beforeIndex]![afterIndex + 1] ?? 0)) {
192
+ lines.push({
193
+ kind: "remove",
194
+ text: beforeLines[beforeIndex] ?? "",
195
+ oldLineNumber,
196
+ });
197
+ beforeIndex += 1;
198
+ oldLineNumber += 1;
199
+ continue;
200
+ }
201
+
202
+ lines.push({
203
+ kind: "add",
204
+ text: afterLines[afterIndex] ?? "",
205
+ newLineNumber,
206
+ });
207
+ afterIndex += 1;
208
+ newLineNumber += 1;
209
+ }
210
+
211
+ appendRemainingDiffLines({
212
+ lines,
213
+ beforeLines,
214
+ afterLines,
215
+ beforeIndex,
216
+ afterIndex,
217
+ oldLineNumber,
218
+ newLineNumber,
219
+ });
220
+ return lines;
221
+ }
222
+
223
+ export function buildFullReplaceBlock(params: {
224
+ path: string;
225
+ beforeText?: string | null;
226
+ afterText?: string | null;
227
+ operation?: string | null;
228
+ }): ParsedBlock | null {
229
+ const lines = buildLineDiff(params.beforeText ?? "", params.afterText ?? "");
230
+ const limited = limitLines(lines);
231
+ if (limited.lines.length === 0) {
232
+ return null;
233
+ }
234
+ return {
235
+ path: params.path,
236
+ display: "diff",
237
+ caption: buildCaption({
238
+ operation: params.operation,
239
+ lines,
240
+ }),
241
+ lines: limited.lines,
242
+ truncated: limited.truncated,
243
+ };
244
+ }
245
+
246
+ function buildParsedPatchBlock(params: {
247
+ path: string;
248
+ operation: string | null;
249
+ lines: ChatFileOperationLineViewModel[];
250
+ }): ParsedBlock {
251
+ const limited = limitLines(params.lines);
252
+ return {
253
+ path: params.path,
254
+ display: "diff",
255
+ caption: buildCaption({
256
+ operation: params.operation,
257
+ lines: params.lines,
258
+ }),
259
+ lines: limited.lines,
260
+ truncated: limited.truncated,
261
+ };
262
+ }
263
+
264
+ function updateApplyPatchCursor(params: {
265
+ line: string;
266
+ flushCurrent: () => void;
267
+ setCurrent: (path: string, operation: string) => void;
268
+ }): boolean {
269
+ if (params.line.startsWith("*** Update File: ")) {
270
+ params.flushCurrent();
271
+ params.setCurrent(params.line.slice("*** Update File: ".length).trim(), "update");
272
+ return true;
273
+ }
274
+ if (params.line.startsWith("*** Add File: ")) {
275
+ params.flushCurrent();
276
+ params.setCurrent(params.line.slice("*** Add File: ".length).trim(), "add");
277
+ return true;
278
+ }
279
+ if (params.line.startsWith("*** Delete File: ")) {
280
+ params.flushCurrent();
281
+ params.setCurrent(params.line.slice("*** Delete File: ".length).trim(), "delete");
282
+ return true;
283
+ }
284
+ return false;
285
+ }
286
+
287
+ function appendPatchLine(
288
+ currentLines: ChatFileOperationLineViewModel[],
289
+ line: string,
290
+ ): void {
291
+ if (line.startsWith("+")) {
292
+ currentLines.push({
293
+ kind: "add",
294
+ text: line.slice(1),
295
+ });
296
+ return;
297
+ }
298
+ if (line.startsWith("-")) {
299
+ currentLines.push({
300
+ kind: "remove",
301
+ text: line.slice(1),
302
+ });
303
+ return;
304
+ }
305
+ if (line.startsWith(" ")) {
306
+ currentLines.push({
307
+ kind: "context",
308
+ text: line.slice(1),
309
+ });
310
+ }
311
+ }
312
+
313
+ function parseApplyPatchText(patchText: string): ParsedBlock[] {
314
+ const blocks: ParsedBlock[] = [];
315
+ let currentPath: string | null = null;
316
+ let currentOperation: string | null = null;
317
+ let currentLines: ChatFileOperationLineViewModel[] = [];
318
+
319
+ const flushCurrent = () => {
320
+ if (!currentPath) {
321
+ currentLines = [];
322
+ currentOperation = null;
323
+ return;
324
+ }
325
+ blocks.push(
326
+ buildParsedPatchBlock({
327
+ path: currentPath,
328
+ operation: currentOperation,
329
+ lines: currentLines,
330
+ }),
331
+ );
332
+ currentPath = null;
333
+ currentOperation = null;
334
+ currentLines = [];
335
+ };
336
+
337
+ for (const line of splitLines(patchText)) {
338
+ if (
339
+ updateApplyPatchCursor({
340
+ line,
341
+ flushCurrent,
342
+ setCurrent: (path, operation) => {
343
+ currentPath = path;
344
+ currentOperation = operation;
345
+ },
346
+ })
347
+ ) {
348
+ continue;
349
+ }
350
+ if (line.startsWith("*** Move to: ") || line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
351
+ continue;
352
+ }
353
+ if (line.startsWith("@@") || !currentPath) {
354
+ continue;
355
+ }
356
+ appendPatchLine(currentLines, line);
357
+ }
358
+
359
+ flushCurrent();
360
+ return blocks;
361
+ }
362
+
363
+ function parseUnifiedDiffText(patchText: string): ParsedBlock[] {
364
+ const blocks: ParsedBlock[] = [];
365
+ let currentPath: string | null = null;
366
+ let currentLines: ChatFileOperationLineViewModel[] = [];
367
+
368
+ const flushCurrent = () => {
369
+ if (!currentPath) {
370
+ currentLines = [];
371
+ return;
372
+ }
373
+ blocks.push(
374
+ buildParsedPatchBlock({
375
+ path: currentPath,
376
+ operation: "update",
377
+ lines: currentLines,
378
+ }),
379
+ );
380
+ currentPath = null;
381
+ currentLines = [];
382
+ };
383
+
384
+ for (const line of splitLines(patchText)) {
385
+ if (line.startsWith("+++ ")) {
386
+ flushCurrent();
387
+ currentPath = line.slice(4).trim().replace(/^b\//, "").replace(/^a\//, "");
388
+ continue;
389
+ }
390
+ if (line.startsWith("--- ") || line.startsWith("@@") || !currentPath) {
391
+ continue;
392
+ }
393
+ appendPatchLine(currentLines, line);
394
+ }
395
+
396
+ flushCurrent();
397
+ return blocks;
398
+ }
399
+
400
+ export function parsePatchBlocks(patchText: string): ParsedBlock[] {
401
+ if (patchText.includes("*** Begin Patch")) {
402
+ return parseApplyPatchText(patchText);
403
+ }
404
+ if (patchText.includes("--- ") && patchText.includes("+++ ")) {
405
+ return parseUnifiedDiffText(patchText);
406
+ }
407
+ return [];
408
+ }
@@ -0,0 +1,89 @@
1
+ export type PartialJsonStringValue = {
2
+ value: string;
3
+ truncated: boolean;
4
+ };
5
+
6
+ function decodeJsonStringChar(value: string): string {
7
+ switch (value) {
8
+ case '"':
9
+ case "\\":
10
+ case "/":
11
+ return value;
12
+ case "b":
13
+ return "\b";
14
+ case "f":
15
+ return "\f";
16
+ case "n":
17
+ return "\n";
18
+ case "r":
19
+ return "\r";
20
+ case "t":
21
+ return "\t";
22
+ default:
23
+ return value;
24
+ }
25
+ }
26
+
27
+ function locatePartialJsonStringValueStart(raw: string, fieldName: string): number | null {
28
+ const marker = `"${fieldName}"`;
29
+ const markerIndex = raw.indexOf(marker);
30
+ if (markerIndex < 0) {
31
+ return null;
32
+ }
33
+ const colonIndex = raw.indexOf(":", markerIndex + marker.length);
34
+ if (colonIndex < 0) {
35
+ return null;
36
+ }
37
+ let valueStart = colonIndex + 1;
38
+ while (valueStart < raw.length && /\s/.test(raw[valueStart] ?? "")) {
39
+ valueStart += 1;
40
+ }
41
+ return raw[valueStart] === '"' ? valueStart + 1 : null;
42
+ }
43
+
44
+ function consumePartialJsonStringValue(
45
+ raw: string,
46
+ valueStart: number,
47
+ maxChars = Number.POSITIVE_INFINITY,
48
+ ): PartialJsonStringValue | null {
49
+ let output = "";
50
+ let escaped = false;
51
+ for (let index = valueStart; index < raw.length; index += 1) {
52
+ const current = raw[index];
53
+ if (current == null) {
54
+ break;
55
+ }
56
+ if (escaped) {
57
+ output += decodeJsonStringChar(current);
58
+ escaped = false;
59
+ continue;
60
+ }
61
+ if (current === "\\") {
62
+ escaped = true;
63
+ continue;
64
+ }
65
+ if (current === '"') {
66
+ return { value: output, truncated: false };
67
+ }
68
+ output += current;
69
+ if (output.length >= maxChars) {
70
+ return { value: output, truncated: true };
71
+ }
72
+ }
73
+ return output.length > 0 ? { value: output, truncated: false } : null;
74
+ }
75
+
76
+ export function readPartialJsonStringField(
77
+ raw: string,
78
+ fieldNames: readonly string[],
79
+ maxChars = Number.POSITIVE_INFINITY,
80
+ ): PartialJsonStringValue | null {
81
+ for (const fieldName of fieldNames) {
82
+ const valueStart = locatePartialJsonStringValueStart(raw, fieldName);
83
+ if (valueStart == null) {
84
+ continue;
85
+ }
86
+ return consumePartialJsonStringValue(raw, valueStart, maxChars);
87
+ }
88
+ return null;
89
+ }
@@ -15,6 +15,7 @@ import {
15
15
  type ChatSkillRecord,
16
16
  type ChatThinkingLevel
17
17
  } from '@/components/chat/adapters/chat-input-bar.adapter';
18
+ import { deriveSelectedSkillsFromComposer } from '@/components/chat/chat-composer-state';
18
19
  import { usePresenter } from '@/components/chat/presenter/chat-presenter-context';
19
20
  import {
20
21
  CHAT_RECENT_MODELS_MIN_OPTIONS,
@@ -236,6 +237,12 @@ export function ChatInputBarContainer() {
236
237
  }
237
238
  });
238
239
 
240
+ const composerSelectedSkillCount = deriveSelectedSkillsFromComposer(snapshot.composerNodes).length;
241
+ const hasSendableDraft =
242
+ snapshot.draft.trim().length > 0 ||
243
+ snapshot.attachments.length > 0 ||
244
+ composerSelectedSkillCount > 0;
245
+
239
246
  return (
240
247
  <>
241
248
  <ChatInputBar
@@ -296,14 +303,7 @@ export function ChatInputBarContainer() {
296
303
  sendError: snapshot.sendError,
297
304
  isSending: snapshot.isSending,
298
305
  canStopGeneration: snapshot.canStopGeneration,
299
- sendDisabled:
300
- (
301
- snapshot.draft.trim().length === 0 &&
302
- snapshot.attachments.length === 0 &&
303
- snapshot.selectedSkills.length === 0
304
- ) ||
305
- !hasModelOptions ||
306
- snapshot.sessionTypeUnavailable,
306
+ sendDisabled: !hasSendableDraft || !hasModelOptions || snapshot.sessionTypeUnavailable,
307
307
  stopDisabled: !snapshot.canStopGeneration,
308
308
  stopHint: resolvedStopHint,
309
309
  sendButtonLabel: t('chatSend'),
@@ -40,6 +40,7 @@ function buildChatMessageAdapterTexts(
40
40
  reasoningLabel: t("chatReasoning"),
41
41
  toolCallLabel: t("chatToolCall"),
42
42
  toolResultLabel: t("chatToolResult"),
43
+ toolInputLabel: t("chatToolInput"),
43
44
  toolNoOutputLabel: t("chatToolNoOutput"),
44
45
  toolOutputLabel: t("chatToolOutput"),
45
46
  toolStatusPreparingLabel: t("chatToolStatusPreparing"),