@nextclaw/ui 0.11.20 → 0.11.22
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 +25 -0
- package/dist/assets/{ChannelsList-DAx7wv0_.js → ChannelsList-Zeys_w43.js} +6 -6
- package/dist/assets/ChatPage-DWOU_8P6.js +43 -0
- package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
- package/dist/assets/{DocBrowser-DKkE3Y4I.js → DocBrowser-BmtBLFU0.js} +1 -1
- package/dist/assets/{DocBrowserContext-BcZRBsCg.js → DocBrowserContext-YIKkPb76.js} +1 -1
- package/dist/assets/{LogoBadge-BIPDLEwK.js → LogoBadge-F7ZWdxLT.js} +1 -1
- package/dist/assets/MarketplacePage-BfaTTqN6.js +1 -0
- package/dist/assets/{MarketplacePage-Dlp5BgCh.js → MarketplacePage-Cd4faegU.js} +2 -2
- package/dist/assets/{McpMarketplacePage-CwKtAil8.js → McpMarketplacePage-C09Ngs7O.js} +2 -2
- package/dist/assets/ModelConfig-DJgdcgvQ.js +1 -0
- package/dist/assets/ProvidersList-w0rVFIBf.js +1 -0
- package/dist/assets/RemoteAccessPage-BJ_ckkOV.js +1 -0
- package/dist/assets/RuntimeConfig-Cmn2xPQO.js +1 -0
- package/dist/assets/{SearchConfig-v46R5a2U.js → SearchConfig-BT13qpR_.js} +1 -1
- package/dist/assets/{SecretsConfig-CXvUpbB_.js → SecretsConfig-CvqEVn0B.js} +2 -2
- package/dist/assets/{SessionsConfig-7vUHMtOh.js → SessionsConfig-DHHcYznk.js} +2 -2
- package/dist/assets/{book-open-DzSduAaw.js → book-open-CXoF5nQC.js} +1 -1
- package/dist/assets/chat-session-display-VW6ZMvZP.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-C1vpvW4r.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
- package/dist/assets/{config-Df97LeLR.js → config-DJswxxE8.js} +1 -1
- package/dist/assets/{createLucideIcon-CcR5wVoU.js → createLucideIcon-CjGHOWb6.js} +1 -1
- package/dist/assets/{dist-Dii9v3X9.js → dist-Cl2QB-2y.js} +1 -1
- package/dist/assets/{dist-BMlnBah3.js → dist-nqTTbVdA.js} +1 -1
- package/dist/assets/{external-link-CnSDrvJE.js → external-link-tIO7zING.js} +1 -1
- package/dist/assets/{hash-CAnX6PNt.js → hash-JWUyl1pT.js} +1 -1
- package/dist/assets/i18n-CDHMXlRZ.js +1 -0
- package/dist/assets/index-BlH4-cBw.css +1 -0
- package/dist/assets/{index-B0DzQqwv.js → index-C6d0xmtm.js} +3 -3
- package/dist/assets/{label-CtIFj7_6.js → label-BIpeNu4r.js} +1 -1
- package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
- package/dist/assets/{logos-3KFNiOej.js → logos-DThdM9lk.js} +1 -1
- package/dist/assets/{page-layout-BMwpn87D.js → page-layout-D3Xo605Z.js} +1 -1
- package/dist/assets/plus-PHf8q-Ct.js +1 -0
- package/dist/assets/{popover-BIzq25oH.js → popover-BJRUGA_H.js} +1 -1
- package/dist/assets/provider-models-bz5y28rq.js +1 -0
- package/dist/assets/{react-ji6GGP_j.js → react-7ZHqQtEV.js} +1 -1
- package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
- package/dist/assets/{save-CMgYkJ-y.js → save-DJM5RRWW.js} +1 -1
- package/dist/assets/search-C91yH_6y.js +1 -0
- package/dist/assets/{security-config-Xi5DYW7j.js → security-config-T5zpg16O.js} +1 -1
- package/dist/assets/{select-Cz82gl01.js → select-DSkTc61S.js} +1 -1
- package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
- package/dist/assets/{status-dot-C7q1HvLH.js → status-dot-LNBlDu3q.js} +1 -1
- package/dist/assets/{switch-DYswvkYj.js → switch-Bo-Y46HZ.js} +1 -1
- package/dist/assets/tabs-custom-DXv507_2.js +1 -0
- package/dist/assets/{trash-2-DfXI7-ap.js → trash-2-DFZmW6Gg.js} +1 -1
- package/dist/assets/useConfirmDialog-Bs5Ll17m.js +1 -0
- package/dist/assets/{useMutation-s2sn2yzh.js → useMutation-DrZrOgVL.js} +1 -1
- package/dist/assets/x-D7Q1yqSF.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +6 -6
- package/src/api/ncp-session.test.ts +37 -0
- package/src/api/ncp-session.ts +29 -1
- package/src/api/server-path.ts +23 -0
- package/src/api/types.ts +41 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +43 -7
- package/src/components/chat/ChatConversationPanel.tsx +23 -17
- package/src/components/chat/ChatSidebar.test.tsx +2 -2
- package/src/components/chat/ChatSidebar.tsx +2 -2
- package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
- package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
- package/src/components/chat/adapters/chat-message-part.adapter.ts +81 -6
- package/src/components/chat/adapters/chat-message.adapter.test.ts +393 -3
- package/src/components/chat/adapters/chat-message.partial-json.ts +89 -0
- package/src/components/chat/adapters/file-operation/card.ts +330 -0
- package/src/components/chat/adapters/file-operation/diff.ts +398 -0
- package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
- package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
- package/src/components/chat/chat-composer-state.ts +3 -3
- package/src/components/chat/chat-session-display.test.ts +21 -0
- package/src/components/chat/chat-session-display.ts +6 -1
- package/src/components/chat/containers/chat-input-bar.container.tsx +29 -32
- package/src/components/chat/containers/chat-message-list.container.tsx +1 -0
- package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
- package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
- package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
- package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
- package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
- package/src/components/chat/ncp/NcpChatPage.tsx +55 -17
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +176 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +16 -0
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
- package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
- package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
- package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
- package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
- package/src/components/chat/stores/chat-input.store.ts +6 -3
- package/src/components/chat/stores/chat-thread.store.ts +6 -2
- package/src/components/chat/useNcpAgentRuntime.test.tsx +90 -0
- package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
- package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
- package/src/hooks/server-path/use-server-path-browse.ts +19 -0
- package/src/hooks/useConfig.ts +26 -1
- package/src/lib/i18n/i18n-language-owner.ts +94 -0
- package/src/lib/i18n/i18n.path-picker.ts +12 -0
- package/src/lib/i18n.chat.ts +25 -1
- package/src/lib/i18n.ts +21 -84
- package/src/lib/session-project/session-project.utils.ts +30 -0
- 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/ModelConfig-Dg6F3Ldb.js +0 -1
- package/dist/assets/ProvidersList-f7bQdRxA.js +0 -1
- package/dist/assets/RemoteAccessPage-w_dY7P4T.js +0 -1
- package/dist/assets/RuntimeConfig-M4OKjmgU.js +0 -1
- package/dist/assets/chat-session-display-CGfXhJoT.js +0 -1
- package/dist/assets/i18n-CXBpwAwA.js +0 -1
- package/dist/assets/index-BahpXJg8.css +0 -1
- package/dist/assets/loader-circle-qgU4zQDw.js +0 -1
- package/dist/assets/plus-C9cYVbL-.js +0 -1
- package/dist/assets/provider-models-C8JQUd1E.js +0 -1
- package/dist/assets/search-sl1OeJFl.js +0 -1
- package/dist/assets/skeleton-rgIt7a5q.js +0 -1
- package/dist/assets/tabs-custom-DKYQxrx1.js +0 -1
- package/dist/assets/useConfirmDialog-CXDAxtRL.js +0 -1
- package/dist/assets/x-MIimOGs6.js +0 -1
- /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
- /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
- /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import type { ChatFileOperationLineViewModel } from "@nextclaw/agent-chat-ui";
|
|
2
|
+
import {
|
|
3
|
+
buildLineDiff,
|
|
4
|
+
buildPreviewLines,
|
|
5
|
+
createLine,
|
|
6
|
+
incrementLineNumber,
|
|
7
|
+
readUnifiedDiffHunkStart,
|
|
8
|
+
splitLines,
|
|
9
|
+
} from "@/components/chat/adapters/file-operation/line-builder";
|
|
10
|
+
|
|
11
|
+
export type ParsedBlock = {
|
|
12
|
+
path: string;
|
|
13
|
+
display: "preview" | "diff";
|
|
14
|
+
caption?: string;
|
|
15
|
+
lines: ChatFileOperationLineViewModel[];
|
|
16
|
+
rawText?: string;
|
|
17
|
+
truncated?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const MAX_VISIBLE_DIFF_LINES = 120;
|
|
21
|
+
|
|
22
|
+
function buildCaption(params: {
|
|
23
|
+
operation?: string | null;
|
|
24
|
+
lines: ChatFileOperationLineViewModel[];
|
|
25
|
+
}): string | undefined {
|
|
26
|
+
const additions = params.lines.filter((line) => line.kind === "add").length;
|
|
27
|
+
const deletions = params.lines.filter(
|
|
28
|
+
(line) => line.kind === "remove",
|
|
29
|
+
).length;
|
|
30
|
+
const parts: string[] = [];
|
|
31
|
+
const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
|
|
32
|
+
if (normalizedOperation && normalizedOperation !== "update") {
|
|
33
|
+
parts.push(normalizedOperation);
|
|
34
|
+
}
|
|
35
|
+
if (additions > 0) {
|
|
36
|
+
parts.push(`+${additions}`);
|
|
37
|
+
}
|
|
38
|
+
if (deletions > 0) {
|
|
39
|
+
parts.push(`-${deletions}`);
|
|
40
|
+
}
|
|
41
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readDefaultDiffStartLines(params: {
|
|
45
|
+
operation?: string | null;
|
|
46
|
+
beforeText?: string | null;
|
|
47
|
+
afterText?: string | null;
|
|
48
|
+
oldStartLine?: number | null;
|
|
49
|
+
newStartLine?: number | null;
|
|
50
|
+
}): {
|
|
51
|
+
oldStartLine?: number;
|
|
52
|
+
newStartLine?: number;
|
|
53
|
+
} {
|
|
54
|
+
const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
|
|
55
|
+
const oldStartLine =
|
|
56
|
+
typeof params.oldStartLine === "number"
|
|
57
|
+
? params.oldStartLine
|
|
58
|
+
: (normalizedOperation === "delete" ||
|
|
59
|
+
normalizedOperation === "remove") &&
|
|
60
|
+
params.beforeText != null
|
|
61
|
+
? 1
|
|
62
|
+
: undefined;
|
|
63
|
+
const newStartLine =
|
|
64
|
+
typeof params.newStartLine === "number"
|
|
65
|
+
? params.newStartLine
|
|
66
|
+
: (normalizedOperation === "write" || normalizedOperation === "add") &&
|
|
67
|
+
params.afterText != null
|
|
68
|
+
? 1
|
|
69
|
+
: undefined;
|
|
70
|
+
return { oldStartLine, newStartLine };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function limitLines(lines: ChatFileOperationLineViewModel[]): {
|
|
74
|
+
lines: ChatFileOperationLineViewModel[];
|
|
75
|
+
truncated: boolean;
|
|
76
|
+
} {
|
|
77
|
+
if (lines.length <= MAX_VISIBLE_DIFF_LINES) {
|
|
78
|
+
return { lines, truncated: false };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
lines: lines.slice(0, MAX_VISIBLE_DIFF_LINES),
|
|
82
|
+
truncated: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function buildRawPreviewBlock(params: {
|
|
87
|
+
path: string;
|
|
88
|
+
text: string;
|
|
89
|
+
operation?: string | null;
|
|
90
|
+
oldStartLine?: number | null;
|
|
91
|
+
newStartLine?: number | null;
|
|
92
|
+
}): ParsedBlock | null {
|
|
93
|
+
const previewText = params.text.trim();
|
|
94
|
+
if (!previewText) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const previewKind =
|
|
98
|
+
params.operation?.trim().toLowerCase() === "write" ? "add" : "context";
|
|
99
|
+
const oldStartLine =
|
|
100
|
+
typeof params.oldStartLine === "number" ? params.oldStartLine : 1;
|
|
101
|
+
const newStartLine =
|
|
102
|
+
typeof params.newStartLine === "number" ? params.newStartLine : 1;
|
|
103
|
+
const lines = buildPreviewLines({
|
|
104
|
+
text: previewText,
|
|
105
|
+
kind: previewKind,
|
|
106
|
+
oldStartLine,
|
|
107
|
+
newStartLine,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
path: params.path,
|
|
111
|
+
display: "preview",
|
|
112
|
+
caption: buildCaption({
|
|
113
|
+
operation: params.operation,
|
|
114
|
+
lines,
|
|
115
|
+
}),
|
|
116
|
+
lines,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function buildFullReplaceBlock(params: {
|
|
121
|
+
path: string;
|
|
122
|
+
beforeText?: string | null;
|
|
123
|
+
afterText?: string | null;
|
|
124
|
+
operation?: string | null;
|
|
125
|
+
oldStartLine?: number | null;
|
|
126
|
+
newStartLine?: number | null;
|
|
127
|
+
}): ParsedBlock | null {
|
|
128
|
+
const { oldStartLine, newStartLine } = readDefaultDiffStartLines(params);
|
|
129
|
+
const lines = buildLineDiff({
|
|
130
|
+
beforeText: params.beforeText ?? "",
|
|
131
|
+
afterText: params.afterText ?? "",
|
|
132
|
+
oldStartLine,
|
|
133
|
+
newStartLine,
|
|
134
|
+
});
|
|
135
|
+
const limited = limitLines(lines);
|
|
136
|
+
if (limited.lines.length === 0) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
path: params.path,
|
|
141
|
+
display: "diff",
|
|
142
|
+
caption: buildCaption({
|
|
143
|
+
operation: params.operation,
|
|
144
|
+
lines,
|
|
145
|
+
}),
|
|
146
|
+
lines: limited.lines,
|
|
147
|
+
truncated: limited.truncated,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildParsedPatchBlock(params: {
|
|
152
|
+
path: string;
|
|
153
|
+
operation: string | null;
|
|
154
|
+
lines: ChatFileOperationLineViewModel[];
|
|
155
|
+
}): ParsedBlock {
|
|
156
|
+
const limited = limitLines(params.lines);
|
|
157
|
+
return {
|
|
158
|
+
path: params.path,
|
|
159
|
+
display: "diff",
|
|
160
|
+
caption: buildCaption({
|
|
161
|
+
operation: params.operation,
|
|
162
|
+
lines: params.lines,
|
|
163
|
+
}),
|
|
164
|
+
lines: limited.lines,
|
|
165
|
+
truncated: limited.truncated,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function updateApplyPatchCursor(params: {
|
|
170
|
+
line: string;
|
|
171
|
+
flushCurrent: () => void;
|
|
172
|
+
setCurrent: (path: string, operation: string) => void;
|
|
173
|
+
}): boolean {
|
|
174
|
+
if (params.line.startsWith("*** Update File: ")) {
|
|
175
|
+
params.flushCurrent();
|
|
176
|
+
params.setCurrent(
|
|
177
|
+
params.line.slice("*** Update File: ".length).trim(),
|
|
178
|
+
"update",
|
|
179
|
+
);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
if (params.line.startsWith("*** Add File: ")) {
|
|
183
|
+
params.flushCurrent();
|
|
184
|
+
params.setCurrent(params.line.slice("*** Add File: ".length).trim(), "add");
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (params.line.startsWith("*** Delete File: ")) {
|
|
188
|
+
params.flushCurrent();
|
|
189
|
+
params.setCurrent(
|
|
190
|
+
params.line.slice("*** Delete File: ".length).trim(),
|
|
191
|
+
"delete",
|
|
192
|
+
);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function appendPatchLine(params: {
|
|
199
|
+
currentLines: ChatFileOperationLineViewModel[];
|
|
200
|
+
line: string;
|
|
201
|
+
oldLineNumber?: number;
|
|
202
|
+
newLineNumber?: number;
|
|
203
|
+
}): {
|
|
204
|
+
oldLineNumber?: number;
|
|
205
|
+
newLineNumber?: number;
|
|
206
|
+
} {
|
|
207
|
+
const { currentLines, line } = params;
|
|
208
|
+
if (line.startsWith("+")) {
|
|
209
|
+
currentLines.push(
|
|
210
|
+
createLine({
|
|
211
|
+
kind: "add",
|
|
212
|
+
text: line.slice(1),
|
|
213
|
+
newLineNumber: params.newLineNumber,
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
return {
|
|
217
|
+
oldLineNumber: params.oldLineNumber,
|
|
218
|
+
newLineNumber: incrementLineNumber(params.newLineNumber),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (line.startsWith("-")) {
|
|
222
|
+
currentLines.push(
|
|
223
|
+
createLine({
|
|
224
|
+
kind: "remove",
|
|
225
|
+
text: line.slice(1),
|
|
226
|
+
oldLineNumber: params.oldLineNumber,
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
return {
|
|
230
|
+
oldLineNumber: incrementLineNumber(params.oldLineNumber),
|
|
231
|
+
newLineNumber: params.newLineNumber,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (line.startsWith(" ")) {
|
|
235
|
+
currentLines.push(
|
|
236
|
+
createLine({
|
|
237
|
+
kind: "context",
|
|
238
|
+
text: line.slice(1),
|
|
239
|
+
oldLineNumber: params.oldLineNumber,
|
|
240
|
+
newLineNumber: params.newLineNumber,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
return {
|
|
244
|
+
oldLineNumber: incrementLineNumber(params.oldLineNumber),
|
|
245
|
+
newLineNumber: incrementLineNumber(params.newLineNumber),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
oldLineNumber: params.oldLineNumber,
|
|
250
|
+
newLineNumber: params.newLineNumber,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseApplyPatchText(patchText: string): ParsedBlock[] {
|
|
255
|
+
const blocks: ParsedBlock[] = [];
|
|
256
|
+
let currentPath: string | null = null;
|
|
257
|
+
let currentOperation: string | null = null;
|
|
258
|
+
let currentLines: ChatFileOperationLineViewModel[] = [];
|
|
259
|
+
let currentOldLineNumber: number | undefined;
|
|
260
|
+
let currentNewLineNumber: number | undefined;
|
|
261
|
+
|
|
262
|
+
const flushCurrent = () => {
|
|
263
|
+
if (!currentPath) {
|
|
264
|
+
currentLines = [];
|
|
265
|
+
currentOperation = null;
|
|
266
|
+
currentOldLineNumber = undefined;
|
|
267
|
+
currentNewLineNumber = undefined;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
blocks.push(
|
|
271
|
+
buildParsedPatchBlock({
|
|
272
|
+
path: currentPath,
|
|
273
|
+
operation: currentOperation,
|
|
274
|
+
lines: currentLines,
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
currentPath = null;
|
|
278
|
+
currentOperation = null;
|
|
279
|
+
currentLines = [];
|
|
280
|
+
currentOldLineNumber = undefined;
|
|
281
|
+
currentNewLineNumber = undefined;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
for (const line of splitLines(patchText)) {
|
|
285
|
+
if (
|
|
286
|
+
updateApplyPatchCursor({
|
|
287
|
+
line,
|
|
288
|
+
flushCurrent,
|
|
289
|
+
setCurrent: (path, operation) => {
|
|
290
|
+
currentPath = path;
|
|
291
|
+
currentOperation = operation;
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (
|
|
298
|
+
line.startsWith("*** Move to: ") ||
|
|
299
|
+
line.startsWith("*** Begin Patch") ||
|
|
300
|
+
line.startsWith("*** End Patch")
|
|
301
|
+
) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (line.startsWith("@@")) {
|
|
305
|
+
const hunkStart = readUnifiedDiffHunkStart(line);
|
|
306
|
+
currentOldLineNumber = hunkStart?.oldLineNumber;
|
|
307
|
+
currentNewLineNumber = hunkStart?.newLineNumber;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (!currentPath) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const nextCursor = appendPatchLine({
|
|
314
|
+
currentLines,
|
|
315
|
+
line,
|
|
316
|
+
oldLineNumber: currentOldLineNumber,
|
|
317
|
+
newLineNumber: currentNewLineNumber,
|
|
318
|
+
});
|
|
319
|
+
currentOldLineNumber = nextCursor.oldLineNumber;
|
|
320
|
+
currentNewLineNumber = nextCursor.newLineNumber;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
flushCurrent();
|
|
324
|
+
return blocks;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function parseUnifiedDiffText(patchText: string): ParsedBlock[] {
|
|
328
|
+
const blocks: ParsedBlock[] = [];
|
|
329
|
+
let currentPath: string | null = null;
|
|
330
|
+
let currentLines: ChatFileOperationLineViewModel[] = [];
|
|
331
|
+
let currentOldLineNumber: number | undefined;
|
|
332
|
+
let currentNewLineNumber: number | undefined;
|
|
333
|
+
|
|
334
|
+
const flushCurrent = () => {
|
|
335
|
+
if (!currentPath) {
|
|
336
|
+
currentLines = [];
|
|
337
|
+
currentOldLineNumber = undefined;
|
|
338
|
+
currentNewLineNumber = undefined;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
blocks.push(
|
|
342
|
+
buildParsedPatchBlock({
|
|
343
|
+
path: currentPath,
|
|
344
|
+
operation: "update",
|
|
345
|
+
lines: currentLines,
|
|
346
|
+
}),
|
|
347
|
+
);
|
|
348
|
+
currentPath = null;
|
|
349
|
+
currentLines = [];
|
|
350
|
+
currentOldLineNumber = undefined;
|
|
351
|
+
currentNewLineNumber = undefined;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
for (const line of splitLines(patchText)) {
|
|
355
|
+
if (line.startsWith("+++ ")) {
|
|
356
|
+
flushCurrent();
|
|
357
|
+
currentPath = line
|
|
358
|
+
.slice(4)
|
|
359
|
+
.trim()
|
|
360
|
+
.replace(/^b\//, "")
|
|
361
|
+
.replace(/^a\//, "");
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (line.startsWith("--- ")) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (line.startsWith("@@")) {
|
|
368
|
+
const hunkStart = readUnifiedDiffHunkStart(line);
|
|
369
|
+
currentOldLineNumber = hunkStart?.oldLineNumber;
|
|
370
|
+
currentNewLineNumber = hunkStart?.newLineNumber;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (!currentPath) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const nextCursor = appendPatchLine({
|
|
377
|
+
currentLines,
|
|
378
|
+
line,
|
|
379
|
+
oldLineNumber: currentOldLineNumber,
|
|
380
|
+
newLineNumber: currentNewLineNumber,
|
|
381
|
+
});
|
|
382
|
+
currentOldLineNumber = nextCursor.oldLineNumber;
|
|
383
|
+
currentNewLineNumber = nextCursor.newLineNumber;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
flushCurrent();
|
|
387
|
+
return blocks;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function parsePatchBlocks(patchText: string): ParsedBlock[] {
|
|
391
|
+
if (patchText.includes("*** Begin Patch")) {
|
|
392
|
+
return parseApplyPatchText(patchText);
|
|
393
|
+
}
|
|
394
|
+
if (patchText.includes("--- ") && patchText.includes("+++ ")) {
|
|
395
|
+
return parseUnifiedDiffText(patchText);
|
|
396
|
+
}
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { ChatFileOperationLineViewModel } from "@nextclaw/agent-chat-ui";
|
|
2
|
+
|
|
3
|
+
const MAX_DIFF_MATRIX_CELLS = 12_000;
|
|
4
|
+
const UNIFIED_DIFF_HUNK_HEADER_PATTERN =
|
|
5
|
+
/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
|
6
|
+
|
|
7
|
+
export function splitLines(value: string): string[] {
|
|
8
|
+
const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
9
|
+
return normalized === "" ? [] : normalized.split("\n");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function incrementLineNumber(value?: number): number | undefined {
|
|
13
|
+
return typeof value === "number" ? value + 1 : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createLine(params: {
|
|
17
|
+
kind: ChatFileOperationLineViewModel["kind"];
|
|
18
|
+
text: string;
|
|
19
|
+
oldLineNumber?: number;
|
|
20
|
+
newLineNumber?: number;
|
|
21
|
+
}): ChatFileOperationLineViewModel {
|
|
22
|
+
return {
|
|
23
|
+
kind: params.kind,
|
|
24
|
+
text: params.text,
|
|
25
|
+
...(typeof params.oldLineNumber === "number"
|
|
26
|
+
? { oldLineNumber: params.oldLineNumber }
|
|
27
|
+
: {}),
|
|
28
|
+
...(typeof params.newLineNumber === "number"
|
|
29
|
+
? { newLineNumber: params.newLineNumber }
|
|
30
|
+
: {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readUnifiedDiffHunkStart(line: string): {
|
|
35
|
+
oldLineNumber: number;
|
|
36
|
+
newLineNumber: number;
|
|
37
|
+
} | null {
|
|
38
|
+
const match = UNIFIED_DIFF_HUNK_HEADER_PATTERN.exec(line);
|
|
39
|
+
if (!match) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
oldLineNumber: Number(match[1]),
|
|
44
|
+
newLineNumber: Number(match[2]),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function buildPreviewLines(params: {
|
|
49
|
+
text: string;
|
|
50
|
+
kind: "add" | "context";
|
|
51
|
+
oldStartLine: number;
|
|
52
|
+
newStartLine: number;
|
|
53
|
+
}): ChatFileOperationLineViewModel[] {
|
|
54
|
+
return splitLines(params.text).map((line, index) =>
|
|
55
|
+
params.kind === "add"
|
|
56
|
+
? createLine({
|
|
57
|
+
kind: "add",
|
|
58
|
+
text: line,
|
|
59
|
+
newLineNumber: params.newStartLine + index,
|
|
60
|
+
})
|
|
61
|
+
: createLine({
|
|
62
|
+
kind: "context",
|
|
63
|
+
text: line,
|
|
64
|
+
oldLineNumber: params.oldStartLine + index,
|
|
65
|
+
newLineNumber: params.newStartLine + index,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildFallbackDiffLines(params: {
|
|
71
|
+
beforeLines: string[];
|
|
72
|
+
afterLines: string[];
|
|
73
|
+
oldStartLine?: number;
|
|
74
|
+
newStartLine?: number;
|
|
75
|
+
}): ChatFileOperationLineViewModel[] {
|
|
76
|
+
let oldLineNumber = params.oldStartLine;
|
|
77
|
+
let newLineNumber = params.newStartLine;
|
|
78
|
+
return [
|
|
79
|
+
...params.beforeLines.map((line) => {
|
|
80
|
+
const nextLine = createLine({
|
|
81
|
+
kind: "remove",
|
|
82
|
+
text: line,
|
|
83
|
+
oldLineNumber,
|
|
84
|
+
});
|
|
85
|
+
oldLineNumber = incrementLineNumber(oldLineNumber);
|
|
86
|
+
return nextLine;
|
|
87
|
+
}),
|
|
88
|
+
...params.afterLines.map((line) => {
|
|
89
|
+
const nextLine = createLine({
|
|
90
|
+
kind: "add",
|
|
91
|
+
text: line,
|
|
92
|
+
newLineNumber,
|
|
93
|
+
});
|
|
94
|
+
newLineNumber = incrementLineNumber(newLineNumber);
|
|
95
|
+
return nextLine;
|
|
96
|
+
}),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildLcsMatrix(params: {
|
|
101
|
+
beforeLines: string[];
|
|
102
|
+
afterLines: string[];
|
|
103
|
+
}): number[][] {
|
|
104
|
+
const matrix: number[][] = Array.from(
|
|
105
|
+
{ length: params.beforeLines.length + 1 },
|
|
106
|
+
() => Array.from({ length: params.afterLines.length + 1 }, () => 0),
|
|
107
|
+
);
|
|
108
|
+
for (
|
|
109
|
+
let beforeIndex = params.beforeLines.length - 1;
|
|
110
|
+
beforeIndex >= 0;
|
|
111
|
+
beforeIndex -= 1
|
|
112
|
+
) {
|
|
113
|
+
for (
|
|
114
|
+
let afterIndex = params.afterLines.length - 1;
|
|
115
|
+
afterIndex >= 0;
|
|
116
|
+
afterIndex -= 1
|
|
117
|
+
) {
|
|
118
|
+
matrix[beforeIndex]![afterIndex] =
|
|
119
|
+
params.beforeLines[beforeIndex] === params.afterLines[afterIndex]
|
|
120
|
+
? (matrix[beforeIndex + 1]![afterIndex + 1] ?? 0) + 1
|
|
121
|
+
: Math.max(
|
|
122
|
+
matrix[beforeIndex + 1]![afterIndex] ?? 0,
|
|
123
|
+
matrix[beforeIndex]![afterIndex + 1] ?? 0,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return matrix;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function appendRemainingDiffLines(params: {
|
|
131
|
+
lines: ChatFileOperationLineViewModel[];
|
|
132
|
+
beforeLines: string[];
|
|
133
|
+
afterLines: string[];
|
|
134
|
+
beforeIndex: number;
|
|
135
|
+
afterIndex: number;
|
|
136
|
+
oldLineNumber?: number;
|
|
137
|
+
newLineNumber?: number;
|
|
138
|
+
}): void {
|
|
139
|
+
for (
|
|
140
|
+
let index = params.beforeIndex;
|
|
141
|
+
index < params.beforeLines.length;
|
|
142
|
+
index += 1
|
|
143
|
+
) {
|
|
144
|
+
params.lines.push(
|
|
145
|
+
createLine({
|
|
146
|
+
kind: "remove",
|
|
147
|
+
text: params.beforeLines[index] ?? "",
|
|
148
|
+
oldLineNumber: params.oldLineNumber,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
params.oldLineNumber = incrementLineNumber(params.oldLineNumber);
|
|
152
|
+
}
|
|
153
|
+
for (
|
|
154
|
+
let index = params.afterIndex;
|
|
155
|
+
index < params.afterLines.length;
|
|
156
|
+
index += 1
|
|
157
|
+
) {
|
|
158
|
+
params.lines.push(
|
|
159
|
+
createLine({
|
|
160
|
+
kind: "add",
|
|
161
|
+
text: params.afterLines[index] ?? "",
|
|
162
|
+
newLineNumber: params.newLineNumber,
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
params.newLineNumber = incrementLineNumber(params.newLineNumber);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function buildLineDiff(params: {
|
|
170
|
+
beforeText: string;
|
|
171
|
+
afterText: string;
|
|
172
|
+
oldStartLine?: number;
|
|
173
|
+
newStartLine?: number;
|
|
174
|
+
}): ChatFileOperationLineViewModel[] {
|
|
175
|
+
const beforeLines = splitLines(params.beforeText);
|
|
176
|
+
const afterLines = splitLines(params.afterText);
|
|
177
|
+
if (beforeLines.length * afterLines.length > MAX_DIFF_MATRIX_CELLS) {
|
|
178
|
+
return buildFallbackDiffLines({
|
|
179
|
+
beforeLines,
|
|
180
|
+
afterLines,
|
|
181
|
+
oldStartLine: params.oldStartLine,
|
|
182
|
+
newStartLine: params.newStartLine,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const lcs = buildLcsMatrix({
|
|
187
|
+
beforeLines,
|
|
188
|
+
afterLines,
|
|
189
|
+
});
|
|
190
|
+
const lines: ChatFileOperationLineViewModel[] = [];
|
|
191
|
+
let beforeIndex = 0;
|
|
192
|
+
let afterIndex = 0;
|
|
193
|
+
let oldLineNumber = params.oldStartLine;
|
|
194
|
+
let newLineNumber = params.newStartLine;
|
|
195
|
+
while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
|
|
196
|
+
if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
|
|
197
|
+
lines.push(
|
|
198
|
+
createLine({
|
|
199
|
+
kind: "context",
|
|
200
|
+
text: beforeLines[beforeIndex] ?? "",
|
|
201
|
+
oldLineNumber,
|
|
202
|
+
newLineNumber,
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
beforeIndex += 1;
|
|
206
|
+
afterIndex += 1;
|
|
207
|
+
oldLineNumber = incrementLineNumber(oldLineNumber);
|
|
208
|
+
newLineNumber = incrementLineNumber(newLineNumber);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
(lcs[beforeIndex + 1]![afterIndex] ?? 0) >=
|
|
214
|
+
(lcs[beforeIndex]![afterIndex + 1] ?? 0)
|
|
215
|
+
) {
|
|
216
|
+
lines.push(
|
|
217
|
+
createLine({
|
|
218
|
+
kind: "remove",
|
|
219
|
+
text: beforeLines[beforeIndex] ?? "",
|
|
220
|
+
oldLineNumber,
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
beforeIndex += 1;
|
|
224
|
+
oldLineNumber = incrementLineNumber(oldLineNumber);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
lines.push(
|
|
229
|
+
createLine({
|
|
230
|
+
kind: "add",
|
|
231
|
+
text: afterLines[afterIndex] ?? "",
|
|
232
|
+
newLineNumber,
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
afterIndex += 1;
|
|
236
|
+
newLineNumber = incrementLineNumber(newLineNumber);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
appendRemainingDiffLines({
|
|
240
|
+
lines,
|
|
241
|
+
beforeLines,
|
|
242
|
+
afterLines,
|
|
243
|
+
beforeIndex,
|
|
244
|
+
afterIndex,
|
|
245
|
+
oldLineNumber,
|
|
246
|
+
newLineNumber,
|
|
247
|
+
});
|
|
248
|
+
return lines;
|
|
249
|
+
}
|