@nextclaw/ui 0.11.20 → 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.
- package/CHANGELOG.md +11 -0
- package/dist/assets/{ChannelsList-DAx7wv0_.js → ChannelsList-ByHWHkQS.js} +1 -1
- package/dist/assets/ChatPage-FdT3pDnw.js +42 -0
- package/dist/assets/{DocBrowser-DKkE3Y4I.js → DocBrowser-3y_NHZ71.js} +1 -1
- package/dist/assets/DocBrowser-CMdPdbZj.js +1 -0
- package/dist/assets/{DocBrowserContext-BcZRBsCg.js → DocBrowserContext-CVJuwCcw.js} +1 -1
- package/dist/assets/{LogoBadge-BIPDLEwK.js → LogoBadge-D8fyilO-.js} +1 -1
- package/dist/assets/MarketplacePage-9oKmxN2n.js +1 -0
- package/dist/assets/{MarketplacePage-Dlp5BgCh.js → MarketplacePage-CmhsZXr1.js} +1 -1
- package/dist/assets/{McpMarketplacePage-CwKtAil8.js → McpMarketplacePage-C7PkCYbp.js} +1 -1
- package/dist/assets/{ModelConfig-Dg6F3Ldb.js → ModelConfig-DmCY6jWM.js} +1 -1
- package/dist/assets/{ProvidersList-f7bQdRxA.js → ProvidersList-ClT-34aX.js} +1 -1
- package/dist/assets/RemoteAccessPage-B6hUZl1O.js +1 -0
- package/dist/assets/{RuntimeConfig-M4OKjmgU.js → RuntimeConfig-C5aqliGk.js} +1 -1
- package/dist/assets/{SearchConfig-v46R5a2U.js → SearchConfig-Dm7r2yfp.js} +1 -1
- package/dist/assets/{SecretsConfig-CXvUpbB_.js → SecretsConfig-BBP_mbQh.js} +1 -1
- package/dist/assets/{SessionsConfig-7vUHMtOh.js → SessionsConfig-6wNJloZN.js} +1 -1
- package/dist/assets/{book-open-DzSduAaw.js → book-open-B26jGBjY.js} +1 -1
- package/dist/assets/{chat-session-display-CGfXhJoT.js → chat-session-display-Bjmn4aIZ.js} +1 -1
- package/dist/assets/{chunk-JZWAC4HX-C1vpvW4r.js → chunk-JZWAC4HX-B-4B29RN.js} +1 -1
- package/dist/assets/{config-Df97LeLR.js → config-BaC29Qf-.js} +1 -1
- package/dist/assets/{createLucideIcon-CcR5wVoU.js → createLucideIcon-DiFAvXmK.js} +1 -1
- package/dist/assets/{dist-BMlnBah3.js → dist-kW_O3kyZ.js} +1 -1
- package/dist/assets/{dist-Dii9v3X9.js → dist-pCfWPG1A.js} +1 -1
- package/dist/assets/{external-link-CnSDrvJE.js → external-link-D5-p-Gmm.js} +1 -1
- package/dist/assets/{hash-CAnX6PNt.js → hash-BlwrSV0q.js} +1 -1
- package/dist/assets/i18n-CSytxMFI.js +1 -0
- package/dist/assets/{index-BahpXJg8.css → index-CUy6doWo.css} +1 -1
- package/dist/assets/{index-B0DzQqwv.js → index-DvKS3L9j.js} +3 -3
- package/dist/assets/{label-CtIFj7_6.js → label-RyXfZqkP.js} +1 -1
- package/dist/assets/loader-circle-B2J777gj.js +1 -0
- package/dist/assets/{logos-3KFNiOej.js → logos-Bpl8QTgI.js} +1 -1
- package/dist/assets/{page-layout-BMwpn87D.js → page-layout--S0YBU0W.js} +1 -1
- package/dist/assets/plus-CM9XJ0Tf.js +1 -0
- package/dist/assets/{popover-BIzq25oH.js → popover-BEjfbEwy.js} +1 -1
- package/dist/assets/{react-ji6GGP_j.js → react-BuSP2-8B.js} +1 -1
- package/dist/assets/{save-CMgYkJ-y.js → save-DPPPpD_c.js} +1 -1
- package/dist/assets/search-Ctaw34Kp.js +1 -0
- package/dist/assets/{security-config-Xi5DYW7j.js → security-config-6t78Ph-I.js} +1 -1
- package/dist/assets/{select-Cz82gl01.js → select-CT50pzod.js} +1 -1
- package/dist/assets/skeleton-Bycyb0zU.js +1 -0
- package/dist/assets/{status-dot-C7q1HvLH.js → status-dot-BbBqRHfh.js} +1 -1
- package/dist/assets/{switch-DYswvkYj.js → switch-D3l6AcCk.js} +1 -1
- package/dist/assets/{tabs-custom-DKYQxrx1.js → tabs-custom-TZQ5WPWP.js} +1 -1
- package/dist/assets/{trash-2-DfXI7-ap.js → trash-2-B2_AGVE3.js} +1 -1
- package/dist/assets/{useConfirmDialog-CXDAxtRL.js → useConfirmDialog-BDpdjfIO.js} +1 -1
- package/dist/assets/{useMutation-s2sn2yzh.js → useMutation-BzCrO8j-.js} +1 -1
- package/dist/assets/x-CHOBE-63.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +6 -6
- package/src/components/chat/adapters/chat-message-part.adapter.ts +74 -3
- package/src/components/chat/adapters/chat-message.adapter.test.ts +321 -3
- package/src/components/chat/adapters/chat-message.file-operation-card.ts +437 -0
- package/src/components/chat/adapters/chat-message.file-operation-diff.ts +408 -0
- package/src/components/chat/adapters/chat-message.partial-json.ts +89 -0
- package/src/components/chat/containers/chat-input-bar.container.tsx +8 -8
- package/src/components/chat/containers/chat-message-list.container.tsx +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +173 -0
- package/src/components/chat/useNcpAgentRuntime.test.tsx +90 -0
- package/src/lib/i18n.chat.ts +2 -1
- package/src/remote/remote-access-feedback.service.test.ts +18 -0
- package/src/remote/remote-access-feedback.service.ts +10 -1
- package/dist/assets/ChatPage-l2PYwCeB.js +0 -38
- package/dist/assets/DocBrowser-CIHLqoIm.js +0 -1
- package/dist/assets/MarketplacePage-TVeyVOuO.js +0 -1
- package/dist/assets/RemoteAccessPage-w_dY7P4T.js +0 -1
- package/dist/assets/i18n-CXBpwAwA.js +0 -1
- package/dist/assets/loader-circle-qgU4zQDw.js +0 -1
- package/dist/assets/plus-C9cYVbL-.js +0 -1
- package/dist/assets/search-sl1OeJFl.js +0 -1
- package/dist/assets/skeleton-rgIt7a5q.js +0 -1
- 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"),
|