@lvce-editor/chat-view 2.8.0 → 2.10.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 +765 -142
- package/package.json +1 -1
|
@@ -1121,6 +1121,9 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
|
|
|
1121
1121
|
const activateByEvent$1 = (event, assetDir, platform) => {
|
|
1122
1122
|
return invoke('ExtensionHostManagement.activateByEvent', event, assetDir, platform);
|
|
1123
1123
|
};
|
|
1124
|
+
const getWorkspacePath = () => {
|
|
1125
|
+
return invoke('Workspace.getPath');
|
|
1126
|
+
};
|
|
1124
1127
|
const getPreference = async key => {
|
|
1125
1128
|
return await invoke('Preferences.get', key);
|
|
1126
1129
|
};
|
|
@@ -1543,8 +1546,56 @@ const create = (uid, x, y, width, height, platform, assetDir) => {
|
|
|
1543
1546
|
set(uid, state, state);
|
|
1544
1547
|
};
|
|
1545
1548
|
|
|
1549
|
+
const parseRenderHtmlArguments = rawArguments => {
|
|
1550
|
+
try {
|
|
1551
|
+
const parsed = JSON.parse(rawArguments);
|
|
1552
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
1553
|
+
return undefined;
|
|
1554
|
+
}
|
|
1555
|
+
const html = typeof Reflect.get(parsed, 'html') === 'string' ? String(Reflect.get(parsed, 'html')) : '';
|
|
1556
|
+
if (!html) {
|
|
1557
|
+
return undefined;
|
|
1558
|
+
}
|
|
1559
|
+
const css = typeof Reflect.get(parsed, 'css') === 'string' ? String(Reflect.get(parsed, 'css')) : '';
|
|
1560
|
+
const title = typeof Reflect.get(parsed, 'title') === 'string' ? String(Reflect.get(parsed, 'title')) : 'visual preview';
|
|
1561
|
+
return {
|
|
1562
|
+
css,
|
|
1563
|
+
html,
|
|
1564
|
+
title
|
|
1565
|
+
};
|
|
1566
|
+
} catch {
|
|
1567
|
+
return undefined;
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
const getRenderHtmlCss = (sessions, selectedSessionId) => {
|
|
1572
|
+
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
1573
|
+
if (!selectedSession) {
|
|
1574
|
+
return '';
|
|
1575
|
+
}
|
|
1576
|
+
const cssRules = new Set();
|
|
1577
|
+
for (const message of selectedSession.messages) {
|
|
1578
|
+
if (message.role !== 'assistant' || !message.toolCalls) {
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
for (const toolCall of message.toolCalls) {
|
|
1582
|
+
if (toolCall.name !== 'render_html') {
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
const parsed = parseRenderHtmlArguments(toolCall.arguments);
|
|
1586
|
+
if (!parsed || !parsed.css.trim()) {
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
cssRules.add(parsed.css);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return [...cssRules].join('\n\n');
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1546
1595
|
const isEqual$1 = (oldState, newState) => {
|
|
1547
|
-
|
|
1596
|
+
const oldRenderHtmlCss = getRenderHtmlCss(oldState.sessions, oldState.selectedSessionId);
|
|
1597
|
+
const newRenderHtmlCss = getRenderHtmlCss(newState.sessions, newState.selectedSessionId);
|
|
1598
|
+
return oldState.initial === newState.initial && oldState.chatMessageFontFamily === newState.chatMessageFontFamily && oldState.chatMessageFontSize === newState.chatMessageFontSize && oldState.chatMessageLineHeight === newState.chatMessageLineHeight && oldState.composerHeight === newState.composerHeight && oldState.composerLineHeight === newState.composerLineHeight && oldState.composerFontFamily === newState.composerFontFamily && oldState.composerFontSize === newState.composerFontSize && oldState.listItemHeight === newState.listItemHeight && oldRenderHtmlCss === newRenderHtmlCss;
|
|
1548
1599
|
};
|
|
1549
1600
|
|
|
1550
1601
|
const diffFocus = (oldState, newState) => {
|
|
@@ -1602,17 +1653,57 @@ const diff2 = uid => {
|
|
|
1602
1653
|
|
|
1603
1654
|
const Button$2 = 'button';
|
|
1604
1655
|
|
|
1656
|
+
const Audio = 0;
|
|
1605
1657
|
const Button$1 = 1;
|
|
1658
|
+
const Col = 2;
|
|
1659
|
+
const ColGroup = 3;
|
|
1606
1660
|
const Div = 4;
|
|
1661
|
+
const H1 = 5;
|
|
1607
1662
|
const Input = 6;
|
|
1608
1663
|
const Span = 8;
|
|
1664
|
+
const Table = 9;
|
|
1665
|
+
const TBody = 10;
|
|
1666
|
+
const Td = 11;
|
|
1609
1667
|
const Text = 12;
|
|
1668
|
+
const Th = 13;
|
|
1669
|
+
const THead = 14;
|
|
1670
|
+
const Tr = 15;
|
|
1671
|
+
const I = 16;
|
|
1672
|
+
const Img = 17;
|
|
1673
|
+
const H2 = 22;
|
|
1674
|
+
const H3 = 23;
|
|
1675
|
+
const H4 = 24;
|
|
1676
|
+
const H5 = 25;
|
|
1677
|
+
const H6 = 26;
|
|
1678
|
+
const Article = 27;
|
|
1679
|
+
const Aside = 28;
|
|
1680
|
+
const Footer = 29;
|
|
1681
|
+
const Header = 30;
|
|
1682
|
+
const Nav = 40;
|
|
1683
|
+
const Section = 41;
|
|
1684
|
+
const Dd = 43;
|
|
1685
|
+
const Dl = 44;
|
|
1686
|
+
const Figcaption = 45;
|
|
1687
|
+
const Figure = 46;
|
|
1688
|
+
const Hr = 47;
|
|
1610
1689
|
const Li = 48;
|
|
1611
1690
|
const Ol = 49;
|
|
1612
1691
|
const P = 50;
|
|
1692
|
+
const Pre = 51;
|
|
1693
|
+
const A = 53;
|
|
1694
|
+
const Abbr = 54;
|
|
1695
|
+
const Br = 55;
|
|
1696
|
+
const Tfoot = 59;
|
|
1697
|
+
const Ul = 60;
|
|
1613
1698
|
const TextArea = 62;
|
|
1614
1699
|
const Select$1 = 63;
|
|
1615
1700
|
const Option$1 = 64;
|
|
1701
|
+
const Code = 65;
|
|
1702
|
+
const Label$1 = 66;
|
|
1703
|
+
const Dt = 67;
|
|
1704
|
+
const Main = 69;
|
|
1705
|
+
const Strong = 70;
|
|
1706
|
+
const Em = 71;
|
|
1616
1707
|
const Reference = 100;
|
|
1617
1708
|
|
|
1618
1709
|
const Enter = 3;
|
|
@@ -2649,7 +2740,8 @@ const deleteSession = async (state, id) => {
|
|
|
2649
2740
|
};
|
|
2650
2741
|
|
|
2651
2742
|
const handleClickOpenApiApiKeySettings = async state => {
|
|
2652
|
-
|
|
2743
|
+
// Open the built-in settings editor so the user can inspect or edit their OpenAI API key.
|
|
2744
|
+
await invoke('Main.openUri', 'app://settings.json');
|
|
2653
2745
|
return state;
|
|
2654
2746
|
};
|
|
2655
2747
|
|
|
@@ -3053,7 +3145,42 @@ const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBa
|
|
|
3053
3145
|
}
|
|
3054
3146
|
};
|
|
3055
3147
|
|
|
3056
|
-
const
|
|
3148
|
+
const executeGetWorkspaceUriTool = async (_args, _options) => {
|
|
3149
|
+
try {
|
|
3150
|
+
const workspaceUri = await getWorkspacePath();
|
|
3151
|
+
return JSON.stringify({
|
|
3152
|
+
workspaceUri
|
|
3153
|
+
});
|
|
3154
|
+
} catch (error) {
|
|
3155
|
+
return JSON.stringify({
|
|
3156
|
+
error: String(error)
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
};
|
|
3160
|
+
|
|
3161
|
+
const isAbsoluteUri$1 = value => {
|
|
3162
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
|
|
3163
|
+
};
|
|
3164
|
+
const executeListFilesTool = async (args, _options) => {
|
|
3165
|
+
const uri = typeof args.uri === 'string' ? args.uri : '';
|
|
3166
|
+
if (!uri || !isAbsoluteUri$1(uri)) {
|
|
3167
|
+
return JSON.stringify({
|
|
3168
|
+
error: 'Invalid argument: uri must be an absolute URI.'
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
try {
|
|
3172
|
+
const entries = await invoke('FileSystem.readDirWithFileTypes', uri);
|
|
3173
|
+
return JSON.stringify({
|
|
3174
|
+
entries,
|
|
3175
|
+
uri
|
|
3176
|
+
});
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
return JSON.stringify({
|
|
3179
|
+
error: String(error),
|
|
3180
|
+
uri
|
|
3181
|
+
});
|
|
3182
|
+
}
|
|
3183
|
+
};
|
|
3057
3184
|
|
|
3058
3185
|
const isPathTraversalAttempt = path => {
|
|
3059
3186
|
if (!path) {
|
|
@@ -3071,6 +3198,7 @@ const isPathTraversalAttempt = path => {
|
|
|
3071
3198
|
const segments = path.split(/[\\/]/);
|
|
3072
3199
|
return segments.includes('..');
|
|
3073
3200
|
};
|
|
3201
|
+
|
|
3074
3202
|
const normalizeRelativePath = path => {
|
|
3075
3203
|
const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
|
|
3076
3204
|
if (segments.length === 0) {
|
|
@@ -3078,6 +3206,111 @@ const normalizeRelativePath = path => {
|
|
|
3078
3206
|
}
|
|
3079
3207
|
return segments.join('/');
|
|
3080
3208
|
};
|
|
3209
|
+
|
|
3210
|
+
const isAbsoluteUri = value => {
|
|
3211
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
|
|
3212
|
+
};
|
|
3213
|
+
const executeReadFileTool = async (args, _options) => {
|
|
3214
|
+
const uri = typeof args.uri === 'string' ? args.uri : '';
|
|
3215
|
+
if (uri) {
|
|
3216
|
+
if (!isAbsoluteUri(uri)) {
|
|
3217
|
+
return JSON.stringify({
|
|
3218
|
+
error: 'Invalid argument: uri must be an absolute URI.'
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
try {
|
|
3222
|
+
const content = await readFile(uri);
|
|
3223
|
+
return JSON.stringify({
|
|
3224
|
+
content,
|
|
3225
|
+
uri
|
|
3226
|
+
});
|
|
3227
|
+
} catch (error) {
|
|
3228
|
+
return JSON.stringify({
|
|
3229
|
+
error: String(error),
|
|
3230
|
+
uri
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
const filePath = typeof args.path === 'string' ? args.path : '';
|
|
3235
|
+
if (!filePath || isPathTraversalAttempt(filePath)) {
|
|
3236
|
+
return JSON.stringify({
|
|
3237
|
+
error: 'Access denied: path must be relative and stay within the open workspace folder.'
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
const normalizedPath = normalizeRelativePath(filePath);
|
|
3241
|
+
try {
|
|
3242
|
+
const content = await readFile(normalizedPath);
|
|
3243
|
+
return JSON.stringify({
|
|
3244
|
+
content,
|
|
3245
|
+
path: normalizedPath
|
|
3246
|
+
});
|
|
3247
|
+
} catch (error) {
|
|
3248
|
+
return JSON.stringify({
|
|
3249
|
+
error: String(error),
|
|
3250
|
+
path: normalizedPath
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3253
|
+
};
|
|
3254
|
+
|
|
3255
|
+
const maxPayloadLength = 40_000;
|
|
3256
|
+
const executeRenderHtmlTool = async (args, _options) => {
|
|
3257
|
+
const html = typeof args.html === 'string' ? args.html : '';
|
|
3258
|
+
const css = typeof args.css === 'string' ? args.css : '';
|
|
3259
|
+
const title = typeof args.title === 'string' ? args.title : '';
|
|
3260
|
+
if (!html) {
|
|
3261
|
+
return JSON.stringify({
|
|
3262
|
+
error: 'Missing required argument: html'
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
if (html.length > maxPayloadLength || css.length > maxPayloadLength) {
|
|
3266
|
+
return JSON.stringify({
|
|
3267
|
+
error: 'Payload too large: keep html/css under 40,000 characters each.'
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
return JSON.stringify({
|
|
3271
|
+
css,
|
|
3272
|
+
html,
|
|
3273
|
+
ok: true,
|
|
3274
|
+
title
|
|
3275
|
+
});
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
const OnFileSystem = 'onFileSystem';
|
|
3279
|
+
|
|
3280
|
+
const executeFileSystemCommand = async (method, params, options) => {
|
|
3281
|
+
return executeProvider({
|
|
3282
|
+
assetDir: options.assetDir,
|
|
3283
|
+
event: OnFileSystem,
|
|
3284
|
+
method,
|
|
3285
|
+
noProviderFoundMessage: 'No file system provider found',
|
|
3286
|
+
params,
|
|
3287
|
+
platform: options.platform
|
|
3288
|
+
});
|
|
3289
|
+
};
|
|
3290
|
+
|
|
3291
|
+
const executeWriteFileTool = async (args, options) => {
|
|
3292
|
+
const filePath = typeof args.path === 'string' ? args.path : '';
|
|
3293
|
+
const content = typeof args.content === 'string' ? args.content : '';
|
|
3294
|
+
if (!filePath || isPathTraversalAttempt(filePath)) {
|
|
3295
|
+
return JSON.stringify({
|
|
3296
|
+
error: 'Access denied: path must be relative and stay within the open workspace folder.'
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
const normalizedPath = normalizeRelativePath(filePath);
|
|
3300
|
+
try {
|
|
3301
|
+
await executeFileSystemCommand(FileSystemWriteFile, ['file', normalizedPath, content], options);
|
|
3302
|
+
return JSON.stringify({
|
|
3303
|
+
ok: true,
|
|
3304
|
+
path: normalizedPath
|
|
3305
|
+
});
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
return JSON.stringify({
|
|
3308
|
+
error: String(error),
|
|
3309
|
+
path: normalizedPath
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
};
|
|
3313
|
+
|
|
3081
3314
|
const parseToolArguments = rawArguments => {
|
|
3082
3315
|
if (typeof rawArguments !== 'string') {
|
|
3083
3316
|
return {};
|
|
@@ -3092,35 +3325,51 @@ const parseToolArguments = rawArguments => {
|
|
|
3092
3325
|
return {};
|
|
3093
3326
|
}
|
|
3094
3327
|
};
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3328
|
+
|
|
3329
|
+
const executeChatTool = async (name, rawArguments, options) => {
|
|
3330
|
+
const args = parseToolArguments(rawArguments);
|
|
3331
|
+
if (name === 'read_file') {
|
|
3332
|
+
return executeReadFileTool(args);
|
|
3333
|
+
}
|
|
3334
|
+
if (name === 'write_file') {
|
|
3335
|
+
return executeWriteFileTool(args, options);
|
|
3336
|
+
}
|
|
3337
|
+
if (name === 'list_files') {
|
|
3338
|
+
return executeListFilesTool(args);
|
|
3339
|
+
}
|
|
3340
|
+
if (name === 'getWorkspaceUri') {
|
|
3341
|
+
return executeGetWorkspaceUriTool();
|
|
3342
|
+
}
|
|
3343
|
+
if (name === 'render_html') {
|
|
3344
|
+
return executeRenderHtmlTool(args);
|
|
3345
|
+
}
|
|
3346
|
+
return JSON.stringify({
|
|
3347
|
+
error: `Unknown tool: ${name}`
|
|
3103
3348
|
});
|
|
3104
3349
|
};
|
|
3105
|
-
|
|
3106
|
-
|
|
3350
|
+
|
|
3351
|
+
const getReadFileTool = () => {
|
|
3352
|
+
return {
|
|
3107
3353
|
function: {
|
|
3108
|
-
description: 'Read UTF-8 text content from a file inside the currently open workspace folder.',
|
|
3354
|
+
description: 'Read UTF-8 text content from a file inside the currently open workspace folder. Only pass an absolute URI.',
|
|
3109
3355
|
name: 'read_file',
|
|
3110
3356
|
parameters: {
|
|
3111
3357
|
additionalProperties: false,
|
|
3112
3358
|
properties: {
|
|
3113
|
-
|
|
3114
|
-
description: '
|
|
3359
|
+
uri: {
|
|
3360
|
+
description: 'Absolute file URI within the workspace (for example: file:///workspace/src/index.ts).',
|
|
3115
3361
|
type: 'string'
|
|
3116
3362
|
}
|
|
3117
3363
|
},
|
|
3118
|
-
required: ['
|
|
3364
|
+
required: ['uri'],
|
|
3119
3365
|
type: 'object'
|
|
3120
3366
|
}
|
|
3121
3367
|
},
|
|
3122
3368
|
type: 'function'
|
|
3123
|
-
}
|
|
3369
|
+
};
|
|
3370
|
+
};
|
|
3371
|
+
const getWriteFileTool = () => {
|
|
3372
|
+
return {
|
|
3124
3373
|
function: {
|
|
3125
3374
|
description: 'Write UTF-8 text content to a file inside the currently open workspace folder.',
|
|
3126
3375
|
name: 'write_file',
|
|
@@ -3141,93 +3390,72 @@ const getBasicChatTools = () => {
|
|
|
3141
3390
|
}
|
|
3142
3391
|
},
|
|
3143
3392
|
type: 'function'
|
|
3144
|
-
}
|
|
3393
|
+
};
|
|
3394
|
+
};
|
|
3395
|
+
const getListFilesTool = () => {
|
|
3396
|
+
return {
|
|
3145
3397
|
function: {
|
|
3146
|
-
description: 'List direct children (files and folders) for a folder inside the currently open workspace folder.',
|
|
3398
|
+
description: 'List direct children (files and folders) for a folder URI inside the currently open workspace folder. Only pass an absolute URI.',
|
|
3147
3399
|
name: 'list_files',
|
|
3148
3400
|
parameters: {
|
|
3149
3401
|
additionalProperties: false,
|
|
3150
3402
|
properties: {
|
|
3151
|
-
|
|
3152
|
-
description: '
|
|
3403
|
+
uri: {
|
|
3404
|
+
description: 'Absolute folder URI within the workspace (for example: file:///workspace/src).',
|
|
3153
3405
|
type: 'string'
|
|
3154
3406
|
}
|
|
3155
3407
|
},
|
|
3408
|
+
required: ['uri'],
|
|
3156
3409
|
type: 'object'
|
|
3157
3410
|
}
|
|
3158
3411
|
},
|
|
3159
3412
|
type: 'function'
|
|
3160
|
-
}
|
|
3413
|
+
};
|
|
3161
3414
|
};
|
|
3162
|
-
const
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
}
|
|
3207
|
-
if (name === 'list_files') {
|
|
3208
|
-
const folderPath = typeof args.path === 'string' && args.path ? args.path : '.';
|
|
3209
|
-
if (isPathTraversalAttempt(folderPath)) {
|
|
3210
|
-
return JSON.stringify({
|
|
3211
|
-
error: 'Access denied: path must be relative and stay within the open workspace folder.'
|
|
3212
|
-
});
|
|
3213
|
-
}
|
|
3214
|
-
const normalizedPath = normalizeRelativePath(folderPath);
|
|
3215
|
-
try {
|
|
3216
|
-
const entries = await invoke('FileSystem.readDirWithFileTypes', normalizedPath);
|
|
3217
|
-
return JSON.stringify({
|
|
3218
|
-
entries,
|
|
3219
|
-
path: normalizedPath
|
|
3220
|
-
});
|
|
3221
|
-
} catch (error) {
|
|
3222
|
-
return JSON.stringify({
|
|
3223
|
-
error: String(error),
|
|
3224
|
-
path: normalizedPath
|
|
3225
|
-
});
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
return JSON.stringify({
|
|
3229
|
-
error: `Unknown tool: ${name}`
|
|
3230
|
-
});
|
|
3415
|
+
const getGetWorkspaceUriTool = () => {
|
|
3416
|
+
return {
|
|
3417
|
+
function: {
|
|
3418
|
+
description: 'Get the URI of the currently open workspace folder.',
|
|
3419
|
+
name: 'getWorkspaceUri',
|
|
3420
|
+
parameters: {
|
|
3421
|
+
additionalProperties: false,
|
|
3422
|
+
properties: {},
|
|
3423
|
+
type: 'object'
|
|
3424
|
+
}
|
|
3425
|
+
},
|
|
3426
|
+
type: 'function'
|
|
3427
|
+
};
|
|
3428
|
+
};
|
|
3429
|
+
const getRenderHtmlTool = () => {
|
|
3430
|
+
return {
|
|
3431
|
+
function: {
|
|
3432
|
+
description: 'Render custom HTML and optional CSS directly in the chat tool call list using native chat UI rendering. Use this for structured cards, tables, and small dashboards. After calling this tool, do not repeat the same HTML, data table, or long content again as plain text unless the user explicitly asks for a text-only version.',
|
|
3433
|
+
name: 'render_html',
|
|
3434
|
+
parameters: {
|
|
3435
|
+
additionalProperties: false,
|
|
3436
|
+
properties: {
|
|
3437
|
+
css: {
|
|
3438
|
+
description: 'Optional CSS string applied inside the preview document.',
|
|
3439
|
+
type: 'string'
|
|
3440
|
+
},
|
|
3441
|
+
html: {
|
|
3442
|
+
description: 'HTML string to render in the preview document.',
|
|
3443
|
+
type: 'string'
|
|
3444
|
+
},
|
|
3445
|
+
title: {
|
|
3446
|
+
description: 'Optional short title for the preview.',
|
|
3447
|
+
type: 'string'
|
|
3448
|
+
}
|
|
3449
|
+
},
|
|
3450
|
+
required: ['html'],
|
|
3451
|
+
type: 'object'
|
|
3452
|
+
}
|
|
3453
|
+
},
|
|
3454
|
+
type: 'function'
|
|
3455
|
+
};
|
|
3456
|
+
};
|
|
3457
|
+
const getBasicChatTools = () => {
|
|
3458
|
+
return [getReadFileTool(), getWriteFileTool(), getListFilesTool(), getGetWorkspaceUriTool(), getRenderHtmlTool()];
|
|
3231
3459
|
};
|
|
3232
3460
|
|
|
3233
3461
|
const getClientRequestIdHeader = () => {
|
|
@@ -4049,6 +4277,12 @@ const getOpenApiErrorMessage = errorResult => {
|
|
|
4049
4277
|
const errorMessage = errorResult.errorMessage?.trim();
|
|
4050
4278
|
const hasErrorCode = typeof errorResult.errorCode === 'string' && errorResult.errorCode.length > 0;
|
|
4051
4279
|
const hasErrorType = typeof errorResult.errorType === 'string' && errorResult.errorType.length > 0;
|
|
4280
|
+
|
|
4281
|
+
// Provide a concise, user-friendly message when OpenAI reports an invalid API key.
|
|
4282
|
+
if (errorResult.errorCode === 'invalid_api_key') {
|
|
4283
|
+
const status = typeof errorResult.statusCode === 'number' ? errorResult.statusCode : 401;
|
|
4284
|
+
return `OpenAI request failed (status ${status}): Invalid API key. Please verify your OpenAI API key in Chat settings.`;
|
|
4285
|
+
}
|
|
4052
4286
|
if (errorResult.statusCode === 429) {
|
|
4053
4287
|
let prefix = 'OpenAI rate limit exceeded (429)';
|
|
4054
4288
|
if (hasErrorCode) {
|
|
@@ -5641,8 +5875,8 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
|
5641
5875
|
};
|
|
5642
5876
|
};
|
|
5643
5877
|
|
|
5644
|
-
const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily) => {
|
|
5645
|
-
|
|
5878
|
+
const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, renderHtmlCss) => {
|
|
5879
|
+
const baseCss = `:root {
|
|
5646
5880
|
--ChatInputBoxHeight: ${composerHeight}px;
|
|
5647
5881
|
--ChatListItemHeight: ${listItemHeight}px;
|
|
5648
5882
|
--ChatMessageFontSize: ${chatMessageFontSize}px;
|
|
@@ -5676,7 +5910,42 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
|
|
|
5676
5910
|
.ChatToolCallReadFileLink {
|
|
5677
5911
|
color: var(--vscode-textLink-foreground);
|
|
5678
5912
|
text-decoration: underline;
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
.ChatToolCallRenderHtmlLabel {
|
|
5916
|
+
margin-bottom: 6px;
|
|
5917
|
+
color: var(--vscode-descriptionForeground);
|
|
5918
|
+
font-size: 12px;
|
|
5919
|
+
}
|
|
5920
|
+
|
|
5921
|
+
.ChatToolCallRenderHtmlContent {
|
|
5922
|
+
border: 1px solid var(--vscode-editorWidget-border);
|
|
5923
|
+
border-radius: 6px;
|
|
5924
|
+
background: var(--vscode-editor-background);
|
|
5925
|
+
overflow: hidden;
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
.ChatToolCallRenderHtmlBody {
|
|
5929
|
+
min-height: 180px;
|
|
5930
|
+
padding: 12px;
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
.ChatToolCallRenderHtmlBody * {
|
|
5934
|
+
box-sizing: border-box;
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5937
|
+
.ChatMessageLink {
|
|
5938
|
+
color: #4d94ff;
|
|
5939
|
+
text-decoration: underline;
|
|
5940
|
+
cursor: pointer;
|
|
5679
5941
|
}`;
|
|
5942
|
+
if (!renderHtmlCss.trim()) {
|
|
5943
|
+
return baseCss;
|
|
5944
|
+
}
|
|
5945
|
+
return `${baseCss}
|
|
5946
|
+
|
|
5947
|
+
/* render_html tool css */
|
|
5948
|
+
${renderHtmlCss}`;
|
|
5680
5949
|
};
|
|
5681
5950
|
|
|
5682
5951
|
// TODO render things like scrollbar height,scrollbar offset, textarea height,
|
|
@@ -5689,9 +5958,12 @@ const renderCss = (oldState, newState) => {
|
|
|
5689
5958
|
chatMessageLineHeight,
|
|
5690
5959
|
composerHeight,
|
|
5691
5960
|
listItemHeight,
|
|
5961
|
+
selectedSessionId,
|
|
5962
|
+
sessions,
|
|
5692
5963
|
uid
|
|
5693
5964
|
} = newState;
|
|
5694
|
-
const
|
|
5965
|
+
const renderHtmlCss = getRenderHtmlCss(sessions, selectedSessionId);
|
|
5966
|
+
const css = getCss(composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, renderHtmlCss);
|
|
5695
5967
|
return [SetCss, uid, css];
|
|
5696
5968
|
};
|
|
5697
5969
|
|
|
@@ -5746,6 +6018,10 @@ const ChatMessageContent = 'ChatMessageContent';
|
|
|
5746
6018
|
const ChatToolCalls = 'ChatToolCalls';
|
|
5747
6019
|
const ChatToolCallsLabel = 'ChatToolCallsLabel';
|
|
5748
6020
|
const ChatToolCallReadFileLink = 'ChatToolCallReadFileLink';
|
|
6021
|
+
const ChatToolCallRenderHtmlLabel = 'ChatToolCallRenderHtmlLabel';
|
|
6022
|
+
const ChatToolCallRenderHtmlContent = 'ChatToolCallRenderHtmlContent';
|
|
6023
|
+
const ChatToolCallRenderHtmlBody = 'ChatToolCallRenderHtmlBody';
|
|
6024
|
+
const ChatMessageLink = 'ChatMessageLink';
|
|
5749
6025
|
const ChatOrderedList = 'ChatOrderedList';
|
|
5750
6026
|
const ChatOrderedListItem = 'ChatOrderedListItem';
|
|
5751
6027
|
const MessageUser = 'MessageUser';
|
|
@@ -5970,6 +6246,47 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
|
|
|
5970
6246
|
}, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
|
|
5971
6247
|
};
|
|
5972
6248
|
|
|
6249
|
+
const getInlineNodeDom = inlineNode => {
|
|
6250
|
+
if (inlineNode.type === 'text') {
|
|
6251
|
+
return [text(inlineNode.text)];
|
|
6252
|
+
}
|
|
6253
|
+
return [{
|
|
6254
|
+
childCount: 1,
|
|
6255
|
+
className: ChatMessageLink,
|
|
6256
|
+
href: inlineNode.href,
|
|
6257
|
+
rel: 'noopener noreferrer',
|
|
6258
|
+
target: '_blank',
|
|
6259
|
+
title: inlineNode.href,
|
|
6260
|
+
type: A
|
|
6261
|
+
}, text(inlineNode.text)];
|
|
6262
|
+
};
|
|
6263
|
+
|
|
6264
|
+
const getOrderedListItemDom = item => {
|
|
6265
|
+
return [{
|
|
6266
|
+
childCount: item.children.length,
|
|
6267
|
+
className: ChatOrderedListItem,
|
|
6268
|
+
type: Li
|
|
6269
|
+
}, ...item.children.flatMap(getInlineNodeDom)];
|
|
6270
|
+
};
|
|
6271
|
+
const getMessageNodeDom = node => {
|
|
6272
|
+
if (node.type === 'text') {
|
|
6273
|
+
return [{
|
|
6274
|
+
childCount: node.children.length,
|
|
6275
|
+
className: Markdown,
|
|
6276
|
+
type: P
|
|
6277
|
+
}, ...node.children.flatMap(getInlineNodeDom)];
|
|
6278
|
+
}
|
|
6279
|
+
return [{
|
|
6280
|
+
childCount: node.items.length,
|
|
6281
|
+
className: ChatOrderedList,
|
|
6282
|
+
type: Ol
|
|
6283
|
+
}, ...node.items.flatMap(getOrderedListItemDom)];
|
|
6284
|
+
};
|
|
6285
|
+
|
|
6286
|
+
const getMessageContentDom = nodes => {
|
|
6287
|
+
return nodes.flatMap(getMessageNodeDom);
|
|
6288
|
+
};
|
|
6289
|
+
|
|
5973
6290
|
const getMissingApiKeyDom = ({
|
|
5974
6291
|
getApiKeyText,
|
|
5975
6292
|
inputName,
|
|
@@ -6118,7 +6435,7 @@ const getReadFileTarget = rawArguments => {
|
|
|
6118
6435
|
if (!title) {
|
|
6119
6436
|
return undefined;
|
|
6120
6437
|
}
|
|
6121
|
-
// `read_file` tool calls
|
|
6438
|
+
// `read_file` tool calls now use absolute `uri`; keep `path` as a legacy fallback for old transcripts.
|
|
6122
6439
|
const clickableUri = uriValue || pathValue;
|
|
6123
6440
|
return {
|
|
6124
6441
|
clickableUri,
|
|
@@ -6126,7 +6443,7 @@ const getReadFileTarget = rawArguments => {
|
|
|
6126
6443
|
};
|
|
6127
6444
|
};
|
|
6128
6445
|
|
|
6129
|
-
const getToolCallStatusLabel
|
|
6446
|
+
const getToolCallStatusLabel = toolCall => {
|
|
6130
6447
|
if (toolCall.status === 'not-found') {
|
|
6131
6448
|
return ' (not-found)';
|
|
6132
6449
|
}
|
|
@@ -6146,7 +6463,7 @@ const getToolCallReadFileVirtualDom = toolCall => {
|
|
|
6146
6463
|
}
|
|
6147
6464
|
const fileName = getFileNameFromUri(target.title);
|
|
6148
6465
|
const toolNameLabel = `${toolCall.name} `;
|
|
6149
|
-
const statusLabel = getToolCallStatusLabel
|
|
6466
|
+
const statusLabel = getToolCallStatusLabel(toolCall);
|
|
6150
6467
|
const fileNameClickableProps = target.clickableUri ? {
|
|
6151
6468
|
'data-uri': target.clickableUri,
|
|
6152
6469
|
onClick: HandleClickReadFile
|
|
@@ -6168,18 +6485,305 @@ const getToolCallReadFileVirtualDom = toolCall => {
|
|
|
6168
6485
|
}, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
|
|
6169
6486
|
};
|
|
6170
6487
|
|
|
6171
|
-
const
|
|
6172
|
-
|
|
6173
|
-
|
|
6488
|
+
const maxHtmlLength = 40_000;
|
|
6489
|
+
const tokenRegex = /<!--[\s\S]*?-->|<\/?[a-zA-Z][\w:-]*(?:\s[^<>]*?)?>|[^<]+/g;
|
|
6490
|
+
const attributeRegex = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
6491
|
+
const inlineTags = new Set(['a', 'abbr', 'b', 'code', 'em', 'i', 'label', 'small', 'span', 'strong', 'sub', 'sup', 'u']);
|
|
6492
|
+
const voidElements = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
|
|
6493
|
+
const sanitizeHtml = value => {
|
|
6494
|
+
return value.slice(0, maxHtmlLength).replaceAll(/<script\b[\s\S]*?<\/script>/gi, '').replaceAll(/<style\b[\s\S]*?<\/style>/gi, '').replaceAll(/<head\b[\s\S]*?<\/head>/gi, '').replaceAll(/<meta\b[^>]*>/gi, '').replaceAll(/<link\b[^>]*>/gi, '');
|
|
6495
|
+
};
|
|
6496
|
+
const decodeEntities = value => {
|
|
6497
|
+
return value.replaceAll(' ', ' ').replaceAll('"', '"').replaceAll(''', "'").replaceAll('<', '<').replaceAll('>', '>').replaceAll('&', '&');
|
|
6498
|
+
};
|
|
6499
|
+
const parseAttributes = token => {
|
|
6500
|
+
const withoutTag = token.replace(/^<\/?\s*[a-zA-Z][\w:-]*/, '').replace(/\/?\s*>$/, '').trim();
|
|
6501
|
+
if (!withoutTag) {
|
|
6502
|
+
return Object.create(null);
|
|
6174
6503
|
}
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6504
|
+
const attributes = Object.create(null);
|
|
6505
|
+
const matches = withoutTag.matchAll(attributeRegex);
|
|
6506
|
+
for (const match of matches) {
|
|
6507
|
+
const name = String(match[1] || '').toLowerCase();
|
|
6508
|
+
if (!name || name.startsWith('on')) {
|
|
6509
|
+
continue;
|
|
6178
6510
|
}
|
|
6179
|
-
|
|
6511
|
+
const value = String(match[2] ?? match[3] ?? match[4] ?? '');
|
|
6512
|
+
attributes[name] = decodeEntities(value);
|
|
6180
6513
|
}
|
|
6181
|
-
return
|
|
6514
|
+
return attributes;
|
|
6182
6515
|
};
|
|
6516
|
+
const parseHtml = value => {
|
|
6517
|
+
const root = {
|
|
6518
|
+
attributes: Object.create(null),
|
|
6519
|
+
children: [],
|
|
6520
|
+
tagName: 'root',
|
|
6521
|
+
type: 'element'
|
|
6522
|
+
};
|
|
6523
|
+
const stack = [root];
|
|
6524
|
+
const matches = sanitizeHtml(value).match(tokenRegex);
|
|
6525
|
+
if (!matches) {
|
|
6526
|
+
return [];
|
|
6527
|
+
}
|
|
6528
|
+
for (const token of matches) {
|
|
6529
|
+
if (token.startsWith('<!--')) {
|
|
6530
|
+
continue;
|
|
6531
|
+
}
|
|
6532
|
+
if (token.startsWith('</')) {
|
|
6533
|
+
const closingTagName = token.slice(2, -1).trim().toLowerCase();
|
|
6534
|
+
while (stack.length > 1) {
|
|
6535
|
+
const top = stack.at(-1);
|
|
6536
|
+
if (!top) {
|
|
6537
|
+
break;
|
|
6538
|
+
}
|
|
6539
|
+
stack.pop();
|
|
6540
|
+
if (top.tagName === closingTagName) {
|
|
6541
|
+
break;
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
continue;
|
|
6545
|
+
}
|
|
6546
|
+
if (token.startsWith('<')) {
|
|
6547
|
+
const openTagNameMatch = /^<\s*([a-zA-Z][\w:-]*)/.exec(token);
|
|
6548
|
+
if (!openTagNameMatch) {
|
|
6549
|
+
continue;
|
|
6550
|
+
}
|
|
6551
|
+
const tagName = openTagNameMatch[1].toLowerCase();
|
|
6552
|
+
const elementNode = {
|
|
6553
|
+
attributes: parseAttributes(token),
|
|
6554
|
+
children: [],
|
|
6555
|
+
tagName,
|
|
6556
|
+
type: 'element'
|
|
6557
|
+
};
|
|
6558
|
+
const parent = stack.at(-1);
|
|
6559
|
+
if (!parent) {
|
|
6560
|
+
continue;
|
|
6561
|
+
}
|
|
6562
|
+
parent.children.push(elementNode);
|
|
6563
|
+
const selfClosing = token.endsWith('/>') || voidElements.has(tagName);
|
|
6564
|
+
if (!selfClosing) {
|
|
6565
|
+
stack.push(elementNode);
|
|
6566
|
+
}
|
|
6567
|
+
continue;
|
|
6568
|
+
}
|
|
6569
|
+
const decoded = decodeEntities(token);
|
|
6570
|
+
if (!decoded) {
|
|
6571
|
+
continue;
|
|
6572
|
+
}
|
|
6573
|
+
const parent = stack.at(-1);
|
|
6574
|
+
if (!parent) {
|
|
6575
|
+
continue;
|
|
6576
|
+
}
|
|
6577
|
+
parent.children.push({
|
|
6578
|
+
type: 'text',
|
|
6579
|
+
value: decoded
|
|
6580
|
+
});
|
|
6581
|
+
}
|
|
6582
|
+
return root.children;
|
|
6583
|
+
};
|
|
6584
|
+
const getElementType = tagName => {
|
|
6585
|
+
switch (tagName) {
|
|
6586
|
+
case 'a':
|
|
6587
|
+
return A;
|
|
6588
|
+
case 'abbr':
|
|
6589
|
+
return Abbr;
|
|
6590
|
+
case 'article':
|
|
6591
|
+
return Article;
|
|
6592
|
+
case 'aside':
|
|
6593
|
+
return Aside;
|
|
6594
|
+
case 'audio':
|
|
6595
|
+
return Audio;
|
|
6596
|
+
case 'br':
|
|
6597
|
+
return Br;
|
|
6598
|
+
case 'button':
|
|
6599
|
+
return Button$1;
|
|
6600
|
+
case 'code':
|
|
6601
|
+
return Code;
|
|
6602
|
+
case 'col':
|
|
6603
|
+
return Col;
|
|
6604
|
+
case 'colgroup':
|
|
6605
|
+
return ColGroup;
|
|
6606
|
+
case 'dd':
|
|
6607
|
+
return Dd;
|
|
6608
|
+
case 'dl':
|
|
6609
|
+
return Dl;
|
|
6610
|
+
case 'dt':
|
|
6611
|
+
return Dt;
|
|
6612
|
+
case 'em':
|
|
6613
|
+
return Em;
|
|
6614
|
+
case 'figcaption':
|
|
6615
|
+
return Figcaption;
|
|
6616
|
+
case 'figure':
|
|
6617
|
+
return Figure;
|
|
6618
|
+
case 'footer':
|
|
6619
|
+
return Footer;
|
|
6620
|
+
case 'h1':
|
|
6621
|
+
return H1;
|
|
6622
|
+
case 'h2':
|
|
6623
|
+
return H2;
|
|
6624
|
+
case 'h3':
|
|
6625
|
+
return H3;
|
|
6626
|
+
case 'h4':
|
|
6627
|
+
return H4;
|
|
6628
|
+
case 'h5':
|
|
6629
|
+
return H5;
|
|
6630
|
+
case 'h6':
|
|
6631
|
+
return H6;
|
|
6632
|
+
case 'header':
|
|
6633
|
+
return Header;
|
|
6634
|
+
case 'hr':
|
|
6635
|
+
return Hr;
|
|
6636
|
+
case 'i':
|
|
6637
|
+
return I;
|
|
6638
|
+
case 'img':
|
|
6639
|
+
return Img;
|
|
6640
|
+
case 'input':
|
|
6641
|
+
return Input;
|
|
6642
|
+
case 'label':
|
|
6643
|
+
return Label$1;
|
|
6644
|
+
case 'li':
|
|
6645
|
+
return Li;
|
|
6646
|
+
case 'main':
|
|
6647
|
+
return Main;
|
|
6648
|
+
case 'nav':
|
|
6649
|
+
return Nav;
|
|
6650
|
+
case 'ol':
|
|
6651
|
+
return Ol;
|
|
6652
|
+
case 'option':
|
|
6653
|
+
return Option$1;
|
|
6654
|
+
case 'p':
|
|
6655
|
+
return P;
|
|
6656
|
+
case 'pre':
|
|
6657
|
+
return Pre;
|
|
6658
|
+
case 'section':
|
|
6659
|
+
return Section;
|
|
6660
|
+
case 'select':
|
|
6661
|
+
return Select$1;
|
|
6662
|
+
case 'span':
|
|
6663
|
+
return Span;
|
|
6664
|
+
case 'strong':
|
|
6665
|
+
return Strong;
|
|
6666
|
+
case 'table':
|
|
6667
|
+
return Table;
|
|
6668
|
+
case 'tbody':
|
|
6669
|
+
return TBody;
|
|
6670
|
+
case 'td':
|
|
6671
|
+
return Td;
|
|
6672
|
+
case 'textarea':
|
|
6673
|
+
return TextArea;
|
|
6674
|
+
case 'tfoot':
|
|
6675
|
+
return Tfoot;
|
|
6676
|
+
case 'th':
|
|
6677
|
+
return Th;
|
|
6678
|
+
case 'thead':
|
|
6679
|
+
return THead;
|
|
6680
|
+
case 'tr':
|
|
6681
|
+
return Tr;
|
|
6682
|
+
case 'ul':
|
|
6683
|
+
return Ul;
|
|
6684
|
+
default:
|
|
6685
|
+
return inlineTags.has(tagName) ? Span : Div;
|
|
6686
|
+
}
|
|
6687
|
+
};
|
|
6688
|
+
const normalizeUrl = url => {
|
|
6689
|
+
return url.toLowerCase().startsWith('javascript:') ? '#' : url;
|
|
6690
|
+
};
|
|
6691
|
+
const getElementAttributes = node => {
|
|
6692
|
+
const attributes = {};
|
|
6693
|
+
const className = node.attributes.class || node.attributes.classname;
|
|
6694
|
+
if (className) {
|
|
6695
|
+
attributes.className = className;
|
|
6696
|
+
}
|
|
6697
|
+
if (node.attributes.style) {
|
|
6698
|
+
attributes.style = node.attributes.style;
|
|
6699
|
+
}
|
|
6700
|
+
if (node.attributes.id) {
|
|
6701
|
+
attributes.id = node.attributes.id;
|
|
6702
|
+
}
|
|
6703
|
+
if (node.attributes.name) {
|
|
6704
|
+
attributes.name = node.attributes.name;
|
|
6705
|
+
}
|
|
6706
|
+
if (node.attributes.placeholder) {
|
|
6707
|
+
attributes.placeholder = node.attributes.placeholder;
|
|
6708
|
+
}
|
|
6709
|
+
if (node.attributes.title) {
|
|
6710
|
+
attributes.title = node.attributes.title;
|
|
6711
|
+
}
|
|
6712
|
+
if (node.attributes.value) {
|
|
6713
|
+
attributes.value = node.attributes.value;
|
|
6714
|
+
}
|
|
6715
|
+
if (node.attributes.href) {
|
|
6716
|
+
attributes.href = normalizeUrl(node.attributes.href);
|
|
6717
|
+
}
|
|
6718
|
+
if (node.attributes.src) {
|
|
6719
|
+
attributes.src = normalizeUrl(node.attributes.src);
|
|
6720
|
+
}
|
|
6721
|
+
if (node.attributes.target) {
|
|
6722
|
+
attributes.target = node.attributes.target;
|
|
6723
|
+
}
|
|
6724
|
+
if (node.attributes.rel) {
|
|
6725
|
+
attributes.rel = node.attributes.rel;
|
|
6726
|
+
}
|
|
6727
|
+
if ('checked' in node.attributes) {
|
|
6728
|
+
attributes.checked = node.attributes.checked !== 'false';
|
|
6729
|
+
}
|
|
6730
|
+
if ('disabled' in node.attributes) {
|
|
6731
|
+
attributes.disabled = node.attributes.disabled !== 'false';
|
|
6732
|
+
}
|
|
6733
|
+
if ('readonly' in node.attributes) {
|
|
6734
|
+
attributes.readOnly = node.attributes.readonly !== 'false';
|
|
6735
|
+
}
|
|
6736
|
+
return attributes;
|
|
6737
|
+
};
|
|
6738
|
+
const toVirtualDom = node => {
|
|
6739
|
+
if (node.type === 'text') {
|
|
6740
|
+
return [text(node.value)];
|
|
6741
|
+
}
|
|
6742
|
+
const children = node.children.flatMap(toVirtualDom);
|
|
6743
|
+
return [{
|
|
6744
|
+
childCount: node.children.length,
|
|
6745
|
+
...getElementAttributes(node),
|
|
6746
|
+
type: getElementType(node.tagName)
|
|
6747
|
+
}, ...children];
|
|
6748
|
+
};
|
|
6749
|
+
const parseHtmlToVirtualDomWithRootCount = value => {
|
|
6750
|
+
const rootNodes = parseHtml(value);
|
|
6751
|
+
return {
|
|
6752
|
+
rootChildCount: rootNodes.length,
|
|
6753
|
+
virtualDom: rootNodes.flatMap(toVirtualDom)
|
|
6754
|
+
};
|
|
6755
|
+
};
|
|
6756
|
+
|
|
6757
|
+
const getToolCallRenderHtmlVirtualDom = toolCall => {
|
|
6758
|
+
const parsed = parseRenderHtmlArguments(toolCall.arguments);
|
|
6759
|
+
if (!parsed) {
|
|
6760
|
+
return [];
|
|
6761
|
+
}
|
|
6762
|
+
const statusLabel = getToolCallStatusLabel(toolCall);
|
|
6763
|
+
const label = `${toolCall.name}: ${parsed.title}${statusLabel}`;
|
|
6764
|
+
const parsedHtml = parseHtmlToVirtualDomWithRootCount(parsed.html);
|
|
6765
|
+
const {
|
|
6766
|
+
rootChildCount
|
|
6767
|
+
} = parsedHtml;
|
|
6768
|
+
return [{
|
|
6769
|
+
childCount: 2,
|
|
6770
|
+
className: ChatOrderedListItem,
|
|
6771
|
+
type: Li
|
|
6772
|
+
}, {
|
|
6773
|
+
childCount: 1,
|
|
6774
|
+
className: ChatToolCallRenderHtmlLabel,
|
|
6775
|
+
type: Div
|
|
6776
|
+
}, text(label), {
|
|
6777
|
+
childCount: 1,
|
|
6778
|
+
className: ChatToolCallRenderHtmlContent,
|
|
6779
|
+
type: Div
|
|
6780
|
+
}, {
|
|
6781
|
+
childCount: rootChildCount,
|
|
6782
|
+
className: ChatToolCallRenderHtmlBody,
|
|
6783
|
+
type: Div
|
|
6784
|
+
}, ...parsedHtml.virtualDom];
|
|
6785
|
+
};
|
|
6786
|
+
|
|
6183
6787
|
const getToolCallDom = toolCall => {
|
|
6184
6788
|
if (toolCall.name === 'read_file') {
|
|
6185
6789
|
const virtualDom = getToolCallReadFileVirtualDom(toolCall);
|
|
@@ -6187,6 +6791,12 @@ const getToolCallDom = toolCall => {
|
|
|
6187
6791
|
return virtualDom;
|
|
6188
6792
|
}
|
|
6189
6793
|
}
|
|
6794
|
+
if (toolCall.name === 'render_html') {
|
|
6795
|
+
const virtualDom = getToolCallRenderHtmlVirtualDom(toolCall);
|
|
6796
|
+
if (virtualDom.length > 0) {
|
|
6797
|
+
return virtualDom;
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6190
6800
|
const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
|
|
6191
6801
|
const label = `${toolCall.name} ${argumentPreview}${getToolCallStatusLabel(toolCall)}`;
|
|
6192
6802
|
return [{
|
|
@@ -6216,10 +6826,50 @@ const getToolCallsDom = message => {
|
|
|
6216
6826
|
};
|
|
6217
6827
|
|
|
6218
6828
|
const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
|
|
6829
|
+
const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
6830
|
+
const parseInlineNodes = value => {
|
|
6831
|
+
const matches = value.matchAll(markdownLinkRegex);
|
|
6832
|
+
const nodes = [];
|
|
6833
|
+
let lastIndex = 0;
|
|
6834
|
+
for (const match of matches) {
|
|
6835
|
+
const fullMatch = match[0];
|
|
6836
|
+
const linkText = match[1];
|
|
6837
|
+
const href = match[2];
|
|
6838
|
+
const index = match.index ?? 0;
|
|
6839
|
+
if (index > lastIndex) {
|
|
6840
|
+
nodes.push({
|
|
6841
|
+
text: value.slice(lastIndex, index),
|
|
6842
|
+
type: 'text'
|
|
6843
|
+
});
|
|
6844
|
+
}
|
|
6845
|
+
nodes.push({
|
|
6846
|
+
href,
|
|
6847
|
+
text: linkText,
|
|
6848
|
+
type: 'link'
|
|
6849
|
+
});
|
|
6850
|
+
lastIndex = index + fullMatch.length;
|
|
6851
|
+
}
|
|
6852
|
+
if (lastIndex < value.length) {
|
|
6853
|
+
nodes.push({
|
|
6854
|
+
text: value.slice(lastIndex),
|
|
6855
|
+
type: 'text'
|
|
6856
|
+
});
|
|
6857
|
+
}
|
|
6858
|
+
if (nodes.length === 0) {
|
|
6859
|
+
return [{
|
|
6860
|
+
text: value,
|
|
6861
|
+
type: 'text'
|
|
6862
|
+
}];
|
|
6863
|
+
}
|
|
6864
|
+
return nodes;
|
|
6865
|
+
};
|
|
6219
6866
|
const parseMessageContent = rawMessage => {
|
|
6220
6867
|
if (rawMessage === '') {
|
|
6221
6868
|
return [{
|
|
6222
|
-
|
|
6869
|
+
children: [{
|
|
6870
|
+
text: '',
|
|
6871
|
+
type: 'text'
|
|
6872
|
+
}],
|
|
6223
6873
|
type: 'text'
|
|
6224
6874
|
}];
|
|
6225
6875
|
}
|
|
@@ -6232,7 +6882,7 @@ const parseMessageContent = rawMessage => {
|
|
|
6232
6882
|
return;
|
|
6233
6883
|
}
|
|
6234
6884
|
nodes.push({
|
|
6235
|
-
|
|
6885
|
+
children: parseInlineNodes(paragraphLines.join('\n')),
|
|
6236
6886
|
type: 'text'
|
|
6237
6887
|
});
|
|
6238
6888
|
paragraphLines = [];
|
|
@@ -6257,7 +6907,7 @@ const parseMessageContent = rawMessage => {
|
|
|
6257
6907
|
if (match) {
|
|
6258
6908
|
flushParagraph();
|
|
6259
6909
|
listItems.push({
|
|
6260
|
-
|
|
6910
|
+
children: parseInlineNodes(match[1]),
|
|
6261
6911
|
type: 'list-item'
|
|
6262
6912
|
});
|
|
6263
6913
|
continue;
|
|
@@ -6268,32 +6918,13 @@ const parseMessageContent = rawMessage => {
|
|
|
6268
6918
|
flushList();
|
|
6269
6919
|
flushParagraph();
|
|
6270
6920
|
return nodes.length === 0 ? [{
|
|
6271
|
-
|
|
6921
|
+
children: [{
|
|
6922
|
+
text: '',
|
|
6923
|
+
type: 'text'
|
|
6924
|
+
}],
|
|
6272
6925
|
type: 'text'
|
|
6273
6926
|
}] : nodes;
|
|
6274
6927
|
};
|
|
6275
|
-
const getMessageContentDom = nodes => {
|
|
6276
|
-
return nodes.flatMap(node => {
|
|
6277
|
-
if (node.type === 'text') {
|
|
6278
|
-
return [{
|
|
6279
|
-
childCount: 1,
|
|
6280
|
-
className: Markdown,
|
|
6281
|
-
type: P
|
|
6282
|
-
}, text(node.text)];
|
|
6283
|
-
}
|
|
6284
|
-
return [{
|
|
6285
|
-
childCount: node.items.length,
|
|
6286
|
-
className: ChatOrderedList,
|
|
6287
|
-
type: Ol
|
|
6288
|
-
}, ...node.items.flatMap(item => {
|
|
6289
|
-
return [{
|
|
6290
|
-
childCount: 1,
|
|
6291
|
-
className: ChatOrderedListItem,
|
|
6292
|
-
type: Li
|
|
6293
|
-
}, text(item.text)];
|
|
6294
|
-
})];
|
|
6295
|
-
});
|
|
6296
|
-
};
|
|
6297
6928
|
|
|
6298
6929
|
const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
6299
6930
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
@@ -6630,30 +7261,22 @@ const saveState = state => {
|
|
|
6630
7261
|
const {
|
|
6631
7262
|
chatListScrollTop,
|
|
6632
7263
|
composerValue,
|
|
6633
|
-
height,
|
|
6634
7264
|
messagesScrollTop,
|
|
6635
7265
|
nextMessageId,
|
|
6636
7266
|
renamingSessionId,
|
|
6637
7267
|
selectedModelId,
|
|
6638
7268
|
selectedSessionId,
|
|
6639
|
-
viewMode
|
|
6640
|
-
width,
|
|
6641
|
-
x,
|
|
6642
|
-
y
|
|
7269
|
+
viewMode
|
|
6643
7270
|
} = state;
|
|
6644
7271
|
return {
|
|
6645
7272
|
chatListScrollTop,
|
|
6646
7273
|
composerValue,
|
|
6647
|
-
height,
|
|
6648
7274
|
messagesScrollTop,
|
|
6649
7275
|
nextMessageId,
|
|
6650
7276
|
renamingSessionId,
|
|
6651
7277
|
selectedModelId,
|
|
6652
7278
|
selectedSessionId,
|
|
6653
|
-
viewMode
|
|
6654
|
-
width,
|
|
6655
|
-
x,
|
|
6656
|
-
y
|
|
7279
|
+
viewMode
|
|
6657
7280
|
};
|
|
6658
7281
|
};
|
|
6659
7282
|
|