@lvce-editor/chat-view 6.8.0 → 6.9.0
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/dist/chatViewWorkerMain.js +203 -70
- package/package.json +1 -1
|
@@ -3499,6 +3499,38 @@ const executeRenderHtmlTool = async (args, _options) => {
|
|
|
3499
3499
|
});
|
|
3500
3500
|
};
|
|
3501
3501
|
|
|
3502
|
+
const toLines = value => {
|
|
3503
|
+
if (!value) {
|
|
3504
|
+
return [];
|
|
3505
|
+
}
|
|
3506
|
+
const split = value.split('\n').map(line => line.endsWith('\r') ? line.slice(0, -1) : line);
|
|
3507
|
+
if (split.length > 0 && split.at(-1) === '') {
|
|
3508
|
+
split.pop();
|
|
3509
|
+
}
|
|
3510
|
+
return split;
|
|
3511
|
+
};
|
|
3512
|
+
const getLineCounts = (before, after) => {
|
|
3513
|
+
const beforeLines = toLines(before);
|
|
3514
|
+
const afterLines = toLines(after);
|
|
3515
|
+
let start = 0;
|
|
3516
|
+
while (start < beforeLines.length && start < afterLines.length && beforeLines[start] === afterLines[start]) {
|
|
3517
|
+
start++;
|
|
3518
|
+
}
|
|
3519
|
+
let beforeEnd = beforeLines.length - 1;
|
|
3520
|
+
let afterEnd = afterLines.length - 1;
|
|
3521
|
+
while (beforeEnd >= start && afterEnd >= start && beforeLines[beforeEnd] === afterLines[afterEnd]) {
|
|
3522
|
+
beforeEnd--;
|
|
3523
|
+
afterEnd--;
|
|
3524
|
+
}
|
|
3525
|
+
return {
|
|
3526
|
+
linesAdded: Math.max(0, afterEnd - start + 1),
|
|
3527
|
+
linesDeleted: Math.max(0, beforeEnd - start + 1)
|
|
3528
|
+
};
|
|
3529
|
+
};
|
|
3530
|
+
const isFileNotFoundError = error => {
|
|
3531
|
+
const message = String(error).toLowerCase();
|
|
3532
|
+
return message.includes('enoent') || message.includes('not found');
|
|
3533
|
+
};
|
|
3502
3534
|
const executeWriteFileTool = async (args, _options) => {
|
|
3503
3535
|
const filePath = typeof args.path === 'string' ? args.path : '';
|
|
3504
3536
|
const content = typeof args.content === 'string' ? args.content : '';
|
|
@@ -3509,8 +3541,22 @@ const executeWriteFileTool = async (args, _options) => {
|
|
|
3509
3541
|
}
|
|
3510
3542
|
const normalizedPath = normalizeRelativePath(filePath);
|
|
3511
3543
|
try {
|
|
3544
|
+
let previousContent = '';
|
|
3545
|
+
try {
|
|
3546
|
+
previousContent = await readFile(normalizedPath);
|
|
3547
|
+
} catch (error) {
|
|
3548
|
+
if (!isFileNotFoundError(error)) {
|
|
3549
|
+
throw error;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3512
3552
|
await writeFile(normalizedPath, content);
|
|
3553
|
+
const {
|
|
3554
|
+
linesAdded,
|
|
3555
|
+
linesDeleted
|
|
3556
|
+
} = getLineCounts(previousContent, content);
|
|
3513
3557
|
return JSON.stringify({
|
|
3558
|
+
linesAdded,
|
|
3559
|
+
linesDeleted,
|
|
3514
3560
|
ok: true,
|
|
3515
3561
|
path: normalizedPath
|
|
3516
3562
|
});
|
|
@@ -9288,12 +9334,14 @@ const ChatHeader = 'ChatHeader';
|
|
|
9288
9334
|
const Button = 'Button';
|
|
9289
9335
|
const ButtonPrimary = 'ButtonPrimary';
|
|
9290
9336
|
const ButtonSecondary = 'ButtonSecondary';
|
|
9337
|
+
const Deletion = 'Deletion';
|
|
9291
9338
|
const Empty = '';
|
|
9292
9339
|
const FileIcon = 'FileIcon';
|
|
9293
9340
|
const IconButton = 'IconButton';
|
|
9294
9341
|
const IconButtonDisabled = 'IconButtonDisabled';
|
|
9295
9342
|
const ImageElement = 'ImageElement';
|
|
9296
9343
|
const InputBox = 'InputBox';
|
|
9344
|
+
const Insertion = 'Insertion';
|
|
9297
9345
|
const Label = 'Label';
|
|
9298
9346
|
const LabelDetail = 'LabelDetail';
|
|
9299
9347
|
const ChatList = 'ChatList';
|
|
@@ -10014,6 +10062,76 @@ const getFileNameFromUri = uri => {
|
|
|
10014
10062
|
return fileName || uri;
|
|
10015
10063
|
};
|
|
10016
10064
|
|
|
10065
|
+
const isCompleteJson = value => {
|
|
10066
|
+
const trimmed = value.trim();
|
|
10067
|
+
if (!trimmed) {
|
|
10068
|
+
return false;
|
|
10069
|
+
}
|
|
10070
|
+
let depth = 0;
|
|
10071
|
+
let inString = false;
|
|
10072
|
+
let escaped = false;
|
|
10073
|
+
for (const char of trimmed) {
|
|
10074
|
+
if (inString) {
|
|
10075
|
+
if (escaped) {
|
|
10076
|
+
escaped = false;
|
|
10077
|
+
continue;
|
|
10078
|
+
}
|
|
10079
|
+
if (char === '\\') {
|
|
10080
|
+
escaped = true;
|
|
10081
|
+
continue;
|
|
10082
|
+
}
|
|
10083
|
+
if (char === '"') {
|
|
10084
|
+
inString = false;
|
|
10085
|
+
}
|
|
10086
|
+
continue;
|
|
10087
|
+
}
|
|
10088
|
+
if (char === '"') {
|
|
10089
|
+
inString = true;
|
|
10090
|
+
continue;
|
|
10091
|
+
}
|
|
10092
|
+
if (char === '{' || char === '[') {
|
|
10093
|
+
depth += 1;
|
|
10094
|
+
continue;
|
|
10095
|
+
}
|
|
10096
|
+
if (char === '}' || char === ']') {
|
|
10097
|
+
depth -= 1;
|
|
10098
|
+
if (depth < 0) {
|
|
10099
|
+
return false;
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
}
|
|
10103
|
+
return depth === 0 && !inString && !escaped;
|
|
10104
|
+
};
|
|
10105
|
+
const getReadFileTarget = rawArguments => {
|
|
10106
|
+
// Tool arguments stream in chunks, so skip parsing until a full JSON payload is available.
|
|
10107
|
+
if (!isCompleteJson(rawArguments)) {
|
|
10108
|
+
return undefined;
|
|
10109
|
+
}
|
|
10110
|
+
let parsed;
|
|
10111
|
+
try {
|
|
10112
|
+
parsed = JSON.parse(rawArguments);
|
|
10113
|
+
} catch {
|
|
10114
|
+
return undefined;
|
|
10115
|
+
}
|
|
10116
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
10117
|
+
return undefined;
|
|
10118
|
+
}
|
|
10119
|
+
const uri = Reflect.get(parsed, 'uri');
|
|
10120
|
+
const path = Reflect.get(parsed, 'path');
|
|
10121
|
+
const uriValue = typeof uri === 'string' ? uri : '';
|
|
10122
|
+
const pathValue = typeof path === 'string' ? path : '';
|
|
10123
|
+
const title = uriValue || pathValue;
|
|
10124
|
+
if (!title) {
|
|
10125
|
+
return undefined;
|
|
10126
|
+
}
|
|
10127
|
+
// `read_file` tool calls now use absolute `uri`; keep `path` as a legacy fallback for old transcripts.
|
|
10128
|
+
const clickableUri = uriValue || pathValue;
|
|
10129
|
+
return {
|
|
10130
|
+
clickableUri,
|
|
10131
|
+
title
|
|
10132
|
+
};
|
|
10133
|
+
};
|
|
10134
|
+
|
|
10017
10135
|
const getToolCallArgumentPreview = rawArguments => {
|
|
10018
10136
|
if (!rawArguments.trim()) {
|
|
10019
10137
|
return '""';
|
|
@@ -10106,76 +10224,6 @@ const getToolCallAskQuestionVirtualDom = toolCall => {
|
|
|
10106
10224
|
}, text(answer.trim() ? answer : '(empty answer)')])];
|
|
10107
10225
|
};
|
|
10108
10226
|
|
|
10109
|
-
const isCompleteJson = value => {
|
|
10110
|
-
const trimmed = value.trim();
|
|
10111
|
-
if (!trimmed) {
|
|
10112
|
-
return false;
|
|
10113
|
-
}
|
|
10114
|
-
let depth = 0;
|
|
10115
|
-
let inString = false;
|
|
10116
|
-
let escaped = false;
|
|
10117
|
-
for (const char of trimmed) {
|
|
10118
|
-
if (inString) {
|
|
10119
|
-
if (escaped) {
|
|
10120
|
-
escaped = false;
|
|
10121
|
-
continue;
|
|
10122
|
-
}
|
|
10123
|
-
if (char === '\\') {
|
|
10124
|
-
escaped = true;
|
|
10125
|
-
continue;
|
|
10126
|
-
}
|
|
10127
|
-
if (char === '"') {
|
|
10128
|
-
inString = false;
|
|
10129
|
-
}
|
|
10130
|
-
continue;
|
|
10131
|
-
}
|
|
10132
|
-
if (char === '"') {
|
|
10133
|
-
inString = true;
|
|
10134
|
-
continue;
|
|
10135
|
-
}
|
|
10136
|
-
if (char === '{' || char === '[') {
|
|
10137
|
-
depth += 1;
|
|
10138
|
-
continue;
|
|
10139
|
-
}
|
|
10140
|
-
if (char === '}' || char === ']') {
|
|
10141
|
-
depth -= 1;
|
|
10142
|
-
if (depth < 0) {
|
|
10143
|
-
return false;
|
|
10144
|
-
}
|
|
10145
|
-
}
|
|
10146
|
-
}
|
|
10147
|
-
return depth === 0 && !inString && !escaped;
|
|
10148
|
-
};
|
|
10149
|
-
const getReadFileTarget = rawArguments => {
|
|
10150
|
-
// Tool arguments stream in chunks, so skip parsing until a full JSON payload is available.
|
|
10151
|
-
if (!isCompleteJson(rawArguments)) {
|
|
10152
|
-
return undefined;
|
|
10153
|
-
}
|
|
10154
|
-
let parsed;
|
|
10155
|
-
try {
|
|
10156
|
-
parsed = JSON.parse(rawArguments);
|
|
10157
|
-
} catch {
|
|
10158
|
-
return undefined;
|
|
10159
|
-
}
|
|
10160
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
10161
|
-
return undefined;
|
|
10162
|
-
}
|
|
10163
|
-
const uri = Reflect.get(parsed, 'uri');
|
|
10164
|
-
const path = Reflect.get(parsed, 'path');
|
|
10165
|
-
const uriValue = typeof uri === 'string' ? uri : '';
|
|
10166
|
-
const pathValue = typeof path === 'string' ? path : '';
|
|
10167
|
-
const title = uriValue || pathValue;
|
|
10168
|
-
if (!title) {
|
|
10169
|
-
return undefined;
|
|
10170
|
-
}
|
|
10171
|
-
// `read_file` tool calls now use absolute `uri`; keep `path` as a legacy fallback for old transcripts.
|
|
10172
|
-
const clickableUri = uriValue || pathValue;
|
|
10173
|
-
return {
|
|
10174
|
-
clickableUri,
|
|
10175
|
-
title
|
|
10176
|
-
};
|
|
10177
|
-
};
|
|
10178
|
-
|
|
10179
10227
|
const getToolCallReadFileVirtualDom = toolCall => {
|
|
10180
10228
|
const target = getReadFileTarget(toolCall.arguments);
|
|
10181
10229
|
if (!target) {
|
|
@@ -10514,8 +10562,19 @@ const getToolCallDisplayName = name => {
|
|
|
10514
10562
|
}
|
|
10515
10563
|
return name;
|
|
10516
10564
|
};
|
|
10565
|
+
const hasIncompleteJsonArguments = rawArguments => {
|
|
10566
|
+
try {
|
|
10567
|
+
JSON.parse(rawArguments);
|
|
10568
|
+
return false;
|
|
10569
|
+
} catch {
|
|
10570
|
+
return true;
|
|
10571
|
+
}
|
|
10572
|
+
};
|
|
10517
10573
|
const getToolCallLabel = toolCall => {
|
|
10518
10574
|
const displayName = getToolCallDisplayName(toolCall.name);
|
|
10575
|
+
if (toolCall.name === 'write_file' && !toolCall.status && hasIncompleteJsonArguments(toolCall.arguments)) {
|
|
10576
|
+
return `${displayName} (in progress)`;
|
|
10577
|
+
}
|
|
10519
10578
|
const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
|
|
10520
10579
|
const statusLabel = getToolCallStatusLabel(toolCall);
|
|
10521
10580
|
if (argumentPreview === '{}') {
|
|
@@ -10546,6 +10605,74 @@ const getToolCallGetWorkspaceUriVirtualDom = toolCall => {
|
|
|
10546
10605
|
type: Span
|
|
10547
10606
|
}, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
|
|
10548
10607
|
};
|
|
10608
|
+
const parseWriteFileLineCounts = rawResult => {
|
|
10609
|
+
if (!rawResult) {
|
|
10610
|
+
return {
|
|
10611
|
+
linesAdded: 0,
|
|
10612
|
+
linesDeleted: 0
|
|
10613
|
+
};
|
|
10614
|
+
}
|
|
10615
|
+
let parsed;
|
|
10616
|
+
try {
|
|
10617
|
+
parsed = JSON.parse(rawResult);
|
|
10618
|
+
} catch {
|
|
10619
|
+
return {
|
|
10620
|
+
linesAdded: 0,
|
|
10621
|
+
linesDeleted: 0
|
|
10622
|
+
};
|
|
10623
|
+
}
|
|
10624
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
10625
|
+
return {
|
|
10626
|
+
linesAdded: 0,
|
|
10627
|
+
linesDeleted: 0
|
|
10628
|
+
};
|
|
10629
|
+
}
|
|
10630
|
+
const linesAdded = Reflect.get(parsed, 'linesAdded');
|
|
10631
|
+
const linesDeleted = Reflect.get(parsed, 'linesDeleted');
|
|
10632
|
+
return {
|
|
10633
|
+
linesAdded: typeof linesAdded === 'number' ? Math.max(0, linesAdded) : 0,
|
|
10634
|
+
linesDeleted: typeof linesDeleted === 'number' ? Math.max(0, linesDeleted) : 0
|
|
10635
|
+
};
|
|
10636
|
+
};
|
|
10637
|
+
const getToolCallWriteFileVirtualDom = toolCall => {
|
|
10638
|
+
const target = getReadFileTarget(toolCall.arguments);
|
|
10639
|
+
if (!target) {
|
|
10640
|
+
return [];
|
|
10641
|
+
}
|
|
10642
|
+
const fileName = getFileNameFromUri(target.title);
|
|
10643
|
+
const statusLabel = getToolCallStatusLabel(toolCall);
|
|
10644
|
+
const {
|
|
10645
|
+
linesAdded,
|
|
10646
|
+
linesDeleted
|
|
10647
|
+
} = parseWriteFileLineCounts(toolCall.result);
|
|
10648
|
+
const fileNameClickableProps = target.clickableUri ? {
|
|
10649
|
+
'data-uri': target.clickableUri,
|
|
10650
|
+
onClick: HandleClickReadFile
|
|
10651
|
+
} : {};
|
|
10652
|
+
return [{
|
|
10653
|
+
childCount: statusLabel ? 6 : 5,
|
|
10654
|
+
className: ChatOrderedListItem,
|
|
10655
|
+
title: target.title,
|
|
10656
|
+
type: Li
|
|
10657
|
+
}, {
|
|
10658
|
+
childCount: 0,
|
|
10659
|
+
className: FileIcon,
|
|
10660
|
+
type: Div
|
|
10661
|
+
}, text('write_file '), {
|
|
10662
|
+
childCount: 1,
|
|
10663
|
+
className: ChatToolCallReadFileLink,
|
|
10664
|
+
...fileNameClickableProps,
|
|
10665
|
+
type: Span
|
|
10666
|
+
}, text(fileName), {
|
|
10667
|
+
childCount: 1,
|
|
10668
|
+
className: Insertion,
|
|
10669
|
+
type: Span
|
|
10670
|
+
}, text(` +${linesAdded}`), {
|
|
10671
|
+
childCount: 1,
|
|
10672
|
+
className: Deletion,
|
|
10673
|
+
type: Span
|
|
10674
|
+
}, text(` -${linesDeleted}`), ...(statusLabel ? [text(statusLabel)] : [])];
|
|
10675
|
+
};
|
|
10549
10676
|
const getToolCallDom = toolCall => {
|
|
10550
10677
|
if (toolCall.name === 'getWorkspaceUri') {
|
|
10551
10678
|
const virtualDom = getToolCallGetWorkspaceUriVirtualDom(toolCall);
|
|
@@ -10559,6 +10686,12 @@ const getToolCallDom = toolCall => {
|
|
|
10559
10686
|
return virtualDom;
|
|
10560
10687
|
}
|
|
10561
10688
|
}
|
|
10689
|
+
if (toolCall.name === 'write_file') {
|
|
10690
|
+
const virtualDom = getToolCallWriteFileVirtualDom(toolCall);
|
|
10691
|
+
if (virtualDom.length > 0) {
|
|
10692
|
+
return virtualDom;
|
|
10693
|
+
}
|
|
10694
|
+
}
|
|
10562
10695
|
if (toolCall.name === 'render_html') {
|
|
10563
10696
|
const virtualDom = getToolCallRenderHtmlVirtualDom(toolCall);
|
|
10564
10697
|
if (virtualDom.length > 0) {
|