@lvce-editor/chat-view 1.18.0 → 1.20.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 +571 -83
- package/package.json +1 -1
|
@@ -1192,6 +1192,9 @@ const getComposerWidth = width => {
|
|
|
1192
1192
|
const getMinComposerHeight = lineHeight => {
|
|
1193
1193
|
return lineHeight + 8;
|
|
1194
1194
|
};
|
|
1195
|
+
const getMaxComposerHeight = (lineHeight, maxComposerRows) => {
|
|
1196
|
+
return lineHeight * Math.max(1, maxComposerRows) + 8;
|
|
1197
|
+
};
|
|
1195
1198
|
const estimateComposerHeight = (value, lineHeight) => {
|
|
1196
1199
|
const lineCount = value.split('\n').length;
|
|
1197
1200
|
return lineCount * lineHeight + 8;
|
|
@@ -1200,17 +1203,19 @@ const getComposerHeight = async (state, value, width = state.width) => {
|
|
|
1200
1203
|
const {
|
|
1201
1204
|
composerFontFamily,
|
|
1202
1205
|
composerFontSize,
|
|
1203
|
-
composerLineHeight
|
|
1206
|
+
composerLineHeight,
|
|
1207
|
+
maxComposerRows
|
|
1204
1208
|
} = state;
|
|
1205
1209
|
const minimumHeight = getMinComposerHeight(composerLineHeight);
|
|
1210
|
+
const maximumHeight = getMaxComposerHeight(composerLineHeight, maxComposerRows);
|
|
1206
1211
|
const content = value || ' ';
|
|
1207
1212
|
const composerWidth = getComposerWidth(width);
|
|
1208
1213
|
try {
|
|
1209
1214
|
const measuredHeight = await measureTextBlockHeight(content, composerFontFamily, composerFontSize, composerLineHeight, composerWidth);
|
|
1210
1215
|
const height = Math.ceil(measuredHeight) + 8;
|
|
1211
|
-
return Math.max(minimumHeight, height);
|
|
1216
|
+
return Math.max(minimumHeight, Math.min(maximumHeight, height));
|
|
1212
1217
|
} catch {
|
|
1213
|
-
return Math.max(minimumHeight, estimateComposerHeight(value, composerLineHeight));
|
|
1218
|
+
return Math.max(minimumHeight, Math.min(maximumHeight, estimateComposerHeight(value, composerLineHeight)));
|
|
1214
1219
|
}
|
|
1215
1220
|
};
|
|
1216
1221
|
const getMinComposerHeightForState = state => {
|
|
@@ -1381,10 +1386,16 @@ const getDefaultModels = () => {
|
|
|
1381
1386
|
const createDefaultState = () => {
|
|
1382
1387
|
const defaultSessionId = 'session-1';
|
|
1383
1388
|
const defaultModelId = 'test';
|
|
1389
|
+
const chatMessageFontSize = 13;
|
|
1390
|
+
const chatMessageLineHeight = 20;
|
|
1384
1391
|
const composerFontSize = 13;
|
|
1385
1392
|
const composerLineHeight = 20;
|
|
1386
1393
|
return {
|
|
1387
1394
|
assetDir: '',
|
|
1395
|
+
chatListScrollTop: 0,
|
|
1396
|
+
chatMessageFontFamily: 'system-ui',
|
|
1397
|
+
chatMessageFontSize,
|
|
1398
|
+
chatMessageLineHeight,
|
|
1388
1399
|
composerFontFamily: 'system-ui',
|
|
1389
1400
|
composerFontSize,
|
|
1390
1401
|
composerHeight: composerLineHeight + 8,
|
|
@@ -1399,6 +1410,8 @@ const createDefaultState = () => {
|
|
|
1399
1410
|
inputSource: 'script',
|
|
1400
1411
|
lastSubmittedSessionId: '',
|
|
1401
1412
|
listItemHeight: 40,
|
|
1413
|
+
maxComposerRows: 5,
|
|
1414
|
+
messagesScrollTop: 0,
|
|
1402
1415
|
mockApiCommandId: '',
|
|
1403
1416
|
models: getDefaultModels(),
|
|
1404
1417
|
nextMessageId: 1,
|
|
@@ -1411,6 +1424,7 @@ const createDefaultState = () => {
|
|
|
1411
1424
|
openRouterApiKeyInput: '',
|
|
1412
1425
|
openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
|
|
1413
1426
|
openRouterApiKeyState: 'idle',
|
|
1427
|
+
passIncludeObfuscation: false,
|
|
1414
1428
|
platform: 0,
|
|
1415
1429
|
renamingSessionId: '',
|
|
1416
1430
|
selectedModelId: defaultModelId,
|
|
@@ -1420,6 +1434,7 @@ const createDefaultState = () => {
|
|
|
1420
1434
|
messages: [],
|
|
1421
1435
|
title: defaultSessionTitle()
|
|
1422
1436
|
}],
|
|
1437
|
+
streamingEnabled: false,
|
|
1423
1438
|
tokensMax: 0,
|
|
1424
1439
|
tokensUsed: 0,
|
|
1425
1440
|
uid: 0,
|
|
@@ -1457,7 +1472,7 @@ const create = (uid, x, y, width, height, platform, assetDir) => {
|
|
|
1457
1472
|
};
|
|
1458
1473
|
|
|
1459
1474
|
const isEqual$1 = (oldState, newState) => {
|
|
1460
|
-
return oldState.initial === newState.initial && oldState.composerHeight === newState.composerHeight && oldState.composerLineHeight === newState.composerLineHeight && oldState.composerFontFamily === newState.composerFontFamily && oldState.composerFontSize === newState.composerFontSize;
|
|
1475
|
+
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;
|
|
1461
1476
|
};
|
|
1462
1477
|
|
|
1463
1478
|
const diffFocus = (oldState, newState) => {
|
|
@@ -1471,12 +1486,17 @@ const isEqual = (oldState, newState) => {
|
|
|
1471
1486
|
return oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.viewMode === newState.viewMode;
|
|
1472
1487
|
};
|
|
1473
1488
|
|
|
1489
|
+
const diffScrollTop = (oldState, newState) => {
|
|
1490
|
+
return oldState.chatListScrollTop === newState.chatListScrollTop && oldState.messagesScrollTop === newState.messagesScrollTop;
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1474
1493
|
const RenderItems = 4;
|
|
1475
1494
|
const RenderFocus = 6;
|
|
1476
1495
|
const RenderFocusContext = 7;
|
|
1477
1496
|
const RenderValue = 8;
|
|
1478
1497
|
const RenderCss = 10;
|
|
1479
1498
|
const RenderIncremental = 11;
|
|
1499
|
+
const RenderScrollTop = 12;
|
|
1480
1500
|
|
|
1481
1501
|
const diffValue = (oldState, newState) => {
|
|
1482
1502
|
if (oldState.composerValue === newState.composerValue) {
|
|
@@ -1485,8 +1505,8 @@ const diffValue = (oldState, newState) => {
|
|
|
1485
1505
|
return newState.inputSource !== 'script';
|
|
1486
1506
|
};
|
|
1487
1507
|
|
|
1488
|
-
const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus];
|
|
1489
|
-
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext];
|
|
1508
|
+
const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus, diffScrollTop];
|
|
1509
|
+
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext, RenderScrollTop];
|
|
1490
1510
|
|
|
1491
1511
|
const diff = (oldState, newState) => {
|
|
1492
1512
|
const diffResult = [];
|
|
@@ -1519,6 +1539,7 @@ const FocusSelector = 'Viewlet.focusSelector';
|
|
|
1519
1539
|
const SetCss = 'Viewlet.setCss';
|
|
1520
1540
|
const SetDom2 = 'Viewlet.setDom2';
|
|
1521
1541
|
const SetFocusContext = 'Viewlet.setFocusContext';
|
|
1542
|
+
const SetProperty = 'Viewlet.setProperty';
|
|
1522
1543
|
const SetValueByName = 'Viewlet.setValueByName';
|
|
1523
1544
|
const SetPatches = 'Viewlet.setPatches';
|
|
1524
1545
|
|
|
@@ -1868,18 +1889,12 @@ const getListIndex = (state, eventX, eventY) => {
|
|
|
1868
1889
|
x,
|
|
1869
1890
|
y
|
|
1870
1891
|
} = state;
|
|
1871
|
-
|
|
1892
|
+
const relativeX = eventX - x;
|
|
1893
|
+
const relativeY = eventY - y - headerHeight;
|
|
1894
|
+
if (relativeX < 0 || relativeY < 0 || relativeX >= width || relativeY >= height - headerHeight) {
|
|
1872
1895
|
return -1;
|
|
1873
1896
|
}
|
|
1874
|
-
|
|
1875
|
-
return -1;
|
|
1876
|
-
}
|
|
1877
|
-
const listY = eventY - y - headerHeight;
|
|
1878
|
-
if (listY < 0) {
|
|
1879
|
-
return -1;
|
|
1880
|
-
}
|
|
1881
|
-
const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
|
|
1882
|
-
return Math.floor(listY / itemHeight);
|
|
1897
|
+
return Math.floor(relativeY / listItemHeight);
|
|
1883
1898
|
};
|
|
1884
1899
|
|
|
1885
1900
|
const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
|
|
@@ -2192,6 +2207,63 @@ const getMockAiResponse = async userMessage => {
|
|
|
2192
2207
|
return `Mock AI response: I received "${userMessage}".`;
|
|
2193
2208
|
};
|
|
2194
2209
|
|
|
2210
|
+
let queue = [];
|
|
2211
|
+
let waiters = [];
|
|
2212
|
+
let finished = false;
|
|
2213
|
+
const reset$1 = () => {
|
|
2214
|
+
queue = [];
|
|
2215
|
+
waiters = [];
|
|
2216
|
+
finished = false;
|
|
2217
|
+
};
|
|
2218
|
+
const pushChunk = chunk => {
|
|
2219
|
+
if (waiters.length > 0) {
|
|
2220
|
+
const resolve = waiters.shift();
|
|
2221
|
+
resolve?.(chunk);
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
queue.push(chunk);
|
|
2225
|
+
};
|
|
2226
|
+
const finish = () => {
|
|
2227
|
+
finished = true;
|
|
2228
|
+
if (waiters.length === 0) {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const activeWaiters = waiters;
|
|
2232
|
+
waiters = [];
|
|
2233
|
+
for (const resolve of activeWaiters) {
|
|
2234
|
+
resolve(undefined);
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
const readNextChunk = async () => {
|
|
2238
|
+
if (queue.length > 0) {
|
|
2239
|
+
return queue.shift();
|
|
2240
|
+
}
|
|
2241
|
+
if (finished) {
|
|
2242
|
+
return undefined;
|
|
2243
|
+
}
|
|
2244
|
+
return new Promise(resolve => {
|
|
2245
|
+
waiters.push(resolve);
|
|
2246
|
+
});
|
|
2247
|
+
};
|
|
2248
|
+
|
|
2249
|
+
const getMockOpenApiAssistantText = async (stream, onTextChunk) => {
|
|
2250
|
+
let text = '';
|
|
2251
|
+
while (true) {
|
|
2252
|
+
const chunk = await readNextChunk();
|
|
2253
|
+
if (typeof chunk !== 'string') {
|
|
2254
|
+
break;
|
|
2255
|
+
}
|
|
2256
|
+
text += chunk;
|
|
2257
|
+
if (stream && onTextChunk) {
|
|
2258
|
+
await onTextChunk(chunk);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
return {
|
|
2262
|
+
text,
|
|
2263
|
+
type: 'success'
|
|
2264
|
+
};
|
|
2265
|
+
};
|
|
2266
|
+
|
|
2195
2267
|
const activateByEvent = (event, assetDir, platform) => {
|
|
2196
2268
|
// @ts-ignore
|
|
2197
2269
|
return activateByEvent$1(event, assetDir, platform);
|
|
@@ -2495,7 +2567,16 @@ const executeChatTool = async (name, rawArguments, options) => {
|
|
|
2495
2567
|
});
|
|
2496
2568
|
};
|
|
2497
2569
|
|
|
2498
|
-
const
|
|
2570
|
+
const getClientRequestIdHeader = () => {
|
|
2571
|
+
return {
|
|
2572
|
+
'x-client-request-id': crypto.randomUUID()
|
|
2573
|
+
};
|
|
2574
|
+
};
|
|
2575
|
+
|
|
2576
|
+
const getOpenApiApiEndpoint = (openApiApiBaseUrl, stream) => {
|
|
2577
|
+
if (stream) {
|
|
2578
|
+
return `${openApiApiBaseUrl}/chat/completions?stream=true`;
|
|
2579
|
+
}
|
|
2499
2580
|
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2500
2581
|
};
|
|
2501
2582
|
|
|
@@ -2520,6 +2601,148 @@ const getTextContent = content => {
|
|
|
2520
2601
|
return textParts.join('\n');
|
|
2521
2602
|
};
|
|
2522
2603
|
|
|
2604
|
+
const getStreamChunkText = content => {
|
|
2605
|
+
if (typeof content === 'string') {
|
|
2606
|
+
return content;
|
|
2607
|
+
}
|
|
2608
|
+
if (!Array.isArray(content)) {
|
|
2609
|
+
return '';
|
|
2610
|
+
}
|
|
2611
|
+
return content.map(part => {
|
|
2612
|
+
if (!part || typeof part !== 'object') {
|
|
2613
|
+
return '';
|
|
2614
|
+
}
|
|
2615
|
+
const text = Reflect.get(part, 'text');
|
|
2616
|
+
return typeof text === 'string' ? text : '';
|
|
2617
|
+
}).join('');
|
|
2618
|
+
};
|
|
2619
|
+
const parseSseEvent = eventChunk => {
|
|
2620
|
+
const lines = eventChunk.split('\n');
|
|
2621
|
+
const dataLines = [];
|
|
2622
|
+
for (const line of lines) {
|
|
2623
|
+
if (!line.startsWith('data:')) {
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
dataLines.push(line.slice(5).trimStart());
|
|
2627
|
+
}
|
|
2628
|
+
return dataLines;
|
|
2629
|
+
};
|
|
2630
|
+
const parseOpenApiStream = async (response, onTextChunk) => {
|
|
2631
|
+
if (!response.body) {
|
|
2632
|
+
return {
|
|
2633
|
+
details: 'request-failed',
|
|
2634
|
+
type: 'error'
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
const reader = response.body.getReader();
|
|
2638
|
+
const decoder = new TextDecoder();
|
|
2639
|
+
let remainder = '';
|
|
2640
|
+
let text = '';
|
|
2641
|
+
let done = false;
|
|
2642
|
+
while (!done) {
|
|
2643
|
+
const {
|
|
2644
|
+
done: streamDone,
|
|
2645
|
+
value
|
|
2646
|
+
} = await reader.read();
|
|
2647
|
+
if (streamDone) {
|
|
2648
|
+
done = true;
|
|
2649
|
+
} else if (value) {
|
|
2650
|
+
remainder += decoder.decode(value, {
|
|
2651
|
+
stream: true
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
while (true) {
|
|
2655
|
+
const separatorIndex = remainder.indexOf('\n\n');
|
|
2656
|
+
if (separatorIndex === -1) {
|
|
2657
|
+
break;
|
|
2658
|
+
}
|
|
2659
|
+
const rawEvent = remainder.slice(0, separatorIndex);
|
|
2660
|
+
remainder = remainder.slice(separatorIndex + 2);
|
|
2661
|
+
const dataLines = parseSseEvent(rawEvent);
|
|
2662
|
+
if (dataLines.length === 0) {
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
for (const line of dataLines) {
|
|
2666
|
+
if (line === '[DONE]') {
|
|
2667
|
+
done = true;
|
|
2668
|
+
break;
|
|
2669
|
+
}
|
|
2670
|
+
let parsed;
|
|
2671
|
+
try {
|
|
2672
|
+
parsed = JSON.parse(line);
|
|
2673
|
+
} catch {
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2680
|
+
if (!Array.isArray(choices)) {
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
const firstChoice = choices[0];
|
|
2684
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2685
|
+
continue;
|
|
2686
|
+
}
|
|
2687
|
+
const delta = Reflect.get(firstChoice, 'delta');
|
|
2688
|
+
if (!delta || typeof delta !== 'object') {
|
|
2689
|
+
continue;
|
|
2690
|
+
}
|
|
2691
|
+
const content = Reflect.get(delta, 'content');
|
|
2692
|
+
const chunkText = getStreamChunkText(content);
|
|
2693
|
+
if (!chunkText) {
|
|
2694
|
+
continue;
|
|
2695
|
+
}
|
|
2696
|
+
text += chunkText;
|
|
2697
|
+
if (onTextChunk) {
|
|
2698
|
+
await onTextChunk(chunkText);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
if (remainder) {
|
|
2704
|
+
const dataLines = parseSseEvent(remainder);
|
|
2705
|
+
for (const line of dataLines) {
|
|
2706
|
+
if (line === '[DONE]') {
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
let parsed;
|
|
2710
|
+
try {
|
|
2711
|
+
parsed = JSON.parse(line);
|
|
2712
|
+
} catch {
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2716
|
+
continue;
|
|
2717
|
+
}
|
|
2718
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2719
|
+
if (!Array.isArray(choices)) {
|
|
2720
|
+
continue;
|
|
2721
|
+
}
|
|
2722
|
+
const firstChoice = choices[0];
|
|
2723
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2724
|
+
continue;
|
|
2725
|
+
}
|
|
2726
|
+
const delta = Reflect.get(firstChoice, 'delta');
|
|
2727
|
+
if (!delta || typeof delta !== 'object') {
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
const content = Reflect.get(delta, 'content');
|
|
2731
|
+
const chunkText = getStreamChunkText(content);
|
|
2732
|
+
if (!chunkText) {
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2735
|
+
text += chunkText;
|
|
2736
|
+
if (onTextChunk) {
|
|
2737
|
+
await onTextChunk(chunkText);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
return {
|
|
2742
|
+
text,
|
|
2743
|
+
type: 'success'
|
|
2744
|
+
};
|
|
2745
|
+
};
|
|
2523
2746
|
const getOpenApiErrorDetails = async response => {
|
|
2524
2747
|
let parsed;
|
|
2525
2748
|
try {
|
|
@@ -2543,7 +2766,14 @@ const getOpenApiErrorDetails = async response => {
|
|
|
2543
2766
|
errorType: typeof errorType === 'string' ? errorType : undefined
|
|
2544
2767
|
};
|
|
2545
2768
|
};
|
|
2546
|
-
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform) => {
|
|
2769
|
+
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform, options) => {
|
|
2770
|
+
const {
|
|
2771
|
+
includeObfuscation = false,
|
|
2772
|
+
onTextChunk,
|
|
2773
|
+
stream
|
|
2774
|
+
} = options ?? {
|
|
2775
|
+
stream: false
|
|
2776
|
+
};
|
|
2547
2777
|
const completionMessages = messages.map(message => ({
|
|
2548
2778
|
content: message.text,
|
|
2549
2779
|
role: message.role
|
|
@@ -2553,16 +2783,23 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
2553
2783
|
for (let i = 0; i <= maxToolIterations; i++) {
|
|
2554
2784
|
let response;
|
|
2555
2785
|
try {
|
|
2556
|
-
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
2786
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl, stream), {
|
|
2557
2787
|
body: JSON.stringify({
|
|
2558
2788
|
messages: completionMessages,
|
|
2559
2789
|
model: modelId,
|
|
2790
|
+
...(stream ? {
|
|
2791
|
+
stream: true
|
|
2792
|
+
} : {}),
|
|
2793
|
+
...(includeObfuscation ? {
|
|
2794
|
+
include_obfuscation: true
|
|
2795
|
+
} : {}),
|
|
2560
2796
|
tool_choice: 'auto',
|
|
2561
2797
|
tools
|
|
2562
2798
|
}),
|
|
2563
2799
|
headers: {
|
|
2564
2800
|
Authorization: `Bearer ${openApiApiKey}`,
|
|
2565
|
-
'Content-Type': 'application/json'
|
|
2801
|
+
'Content-Type': 'application/json',
|
|
2802
|
+
...getClientRequestIdHeader()
|
|
2566
2803
|
},
|
|
2567
2804
|
method: 'POST'
|
|
2568
2805
|
});
|
|
@@ -2587,6 +2824,9 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
2587
2824
|
type: 'error'
|
|
2588
2825
|
};
|
|
2589
2826
|
}
|
|
2827
|
+
if (stream) {
|
|
2828
|
+
return parseOpenApiStream(response, onTextChunk);
|
|
2829
|
+
}
|
|
2590
2830
|
let parsed;
|
|
2591
2831
|
try {
|
|
2592
2832
|
parsed = await response.json();
|
|
@@ -2758,7 +2998,8 @@ const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) =>
|
|
|
2758
2998
|
try {
|
|
2759
2999
|
response = await fetch(getOpenRouterKeyEndpoint(openRouterApiBaseUrl), {
|
|
2760
3000
|
headers: {
|
|
2761
|
-
Authorization: `Bearer ${openRouterApiKey}
|
|
3001
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
3002
|
+
...getClientRequestIdHeader()
|
|
2762
3003
|
},
|
|
2763
3004
|
method: 'GET'
|
|
2764
3005
|
});
|
|
@@ -2816,7 +3057,8 @@ const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, o
|
|
|
2816
3057
|
}),
|
|
2817
3058
|
headers: {
|
|
2818
3059
|
Authorization: `Bearer ${openRouterApiKey}`,
|
|
2819
|
-
'Content-Type': 'application/json'
|
|
3060
|
+
'Content-Type': 'application/json',
|
|
3061
|
+
...getClientRequestIdHeader()
|
|
2820
3062
|
},
|
|
2821
3063
|
method: 'POST'
|
|
2822
3064
|
});
|
|
@@ -3001,12 +3243,15 @@ const getAiResponse = async ({
|
|
|
3001
3243
|
mockApiCommandId,
|
|
3002
3244
|
models,
|
|
3003
3245
|
nextMessageId,
|
|
3246
|
+
onTextChunk,
|
|
3004
3247
|
openApiApiBaseUrl,
|
|
3005
3248
|
openApiApiKey,
|
|
3006
3249
|
openRouterApiBaseUrl,
|
|
3007
3250
|
openRouterApiKey,
|
|
3251
|
+
passIncludeObfuscation = false,
|
|
3008
3252
|
platform,
|
|
3009
3253
|
selectedModelId,
|
|
3254
|
+
streamingEnabled = false,
|
|
3010
3255
|
useMockApi,
|
|
3011
3256
|
userText
|
|
3012
3257
|
}) => {
|
|
@@ -3014,8 +3259,22 @@ const getAiResponse = async ({
|
|
|
3014
3259
|
const usesOpenApiModel = isOpenApiModel(selectedModelId, models);
|
|
3015
3260
|
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
3016
3261
|
if (usesOpenApiModel) {
|
|
3017
|
-
if (
|
|
3018
|
-
const result = await
|
|
3262
|
+
if (useMockApi) {
|
|
3263
|
+
const result = await getMockOpenApiAssistantText(streamingEnabled, onTextChunk);
|
|
3264
|
+
if (result.type === 'success') {
|
|
3265
|
+
const {
|
|
3266
|
+
text: assistantText
|
|
3267
|
+
} = result;
|
|
3268
|
+
text = assistantText;
|
|
3269
|
+
} else {
|
|
3270
|
+
text = getOpenApiErrorMessage(result);
|
|
3271
|
+
}
|
|
3272
|
+
} else if (openApiApiKey) {
|
|
3273
|
+
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl, assetDir, platform, {
|
|
3274
|
+
includeObfuscation: passIncludeObfuscation,
|
|
3275
|
+
onTextChunk,
|
|
3276
|
+
stream: streamingEnabled
|
|
3277
|
+
});
|
|
3019
3278
|
if (result.type === 'success') {
|
|
3020
3279
|
const {
|
|
3021
3280
|
text: assistantText
|
|
@@ -3233,6 +3492,67 @@ const focusInput = state => {
|
|
|
3233
3492
|
};
|
|
3234
3493
|
};
|
|
3235
3494
|
|
|
3495
|
+
const updateMessageTextInSelectedSession = (sessions, selectedSessionId, messageId, text, inProgress) => {
|
|
3496
|
+
return sessions.map(session => {
|
|
3497
|
+
if (session.id !== selectedSessionId) {
|
|
3498
|
+
return session;
|
|
3499
|
+
}
|
|
3500
|
+
return {
|
|
3501
|
+
...session,
|
|
3502
|
+
messages: session.messages.map(message => {
|
|
3503
|
+
if (message.id !== messageId) {
|
|
3504
|
+
return message;
|
|
3505
|
+
}
|
|
3506
|
+
return {
|
|
3507
|
+
...message,
|
|
3508
|
+
inProgress,
|
|
3509
|
+
text
|
|
3510
|
+
};
|
|
3511
|
+
})
|
|
3512
|
+
};
|
|
3513
|
+
});
|
|
3514
|
+
};
|
|
3515
|
+
const handleTextChunkFunction = async (uid, assistantMessageId, chunk, handleTextChunkState) => {
|
|
3516
|
+
const selectedSession = handleTextChunkState.latestState.sessions.find(session => session.id === handleTextChunkState.latestState.selectedSessionId);
|
|
3517
|
+
if (!selectedSession) {
|
|
3518
|
+
return {
|
|
3519
|
+
latestState: handleTextChunkState.latestState,
|
|
3520
|
+
previousState: handleTextChunkState.previousState
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
const assistantMessage = selectedSession.messages.find(message => message.id === assistantMessageId);
|
|
3524
|
+
if (!assistantMessage) {
|
|
3525
|
+
return {
|
|
3526
|
+
latestState: handleTextChunkState.latestState,
|
|
3527
|
+
previousState: handleTextChunkState.previousState
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
const updatedText = assistantMessage.text + chunk;
|
|
3531
|
+
const updatedSessions = updateMessageTextInSelectedSession(handleTextChunkState.latestState.sessions, handleTextChunkState.latestState.selectedSessionId, assistantMessageId, updatedText, true);
|
|
3532
|
+
const nextState = {
|
|
3533
|
+
...handleTextChunkState.latestState,
|
|
3534
|
+
sessions: updatedSessions
|
|
3535
|
+
};
|
|
3536
|
+
set(uid, handleTextChunkState.previousState, nextState);
|
|
3537
|
+
// @ts-ignore
|
|
3538
|
+
await invoke('Chat.rerender');
|
|
3539
|
+
return {
|
|
3540
|
+
latestState: nextState,
|
|
3541
|
+
previousState: nextState
|
|
3542
|
+
};
|
|
3543
|
+
};
|
|
3544
|
+
|
|
3545
|
+
const appendMessageToSelectedSession = (sessions, selectedSessionId, message) => {
|
|
3546
|
+
return sessions.map(session => {
|
|
3547
|
+
if (session.id !== selectedSessionId) {
|
|
3548
|
+
return session;
|
|
3549
|
+
}
|
|
3550
|
+
return {
|
|
3551
|
+
...session,
|
|
3552
|
+
messages: [...session.messages, message]
|
|
3553
|
+
};
|
|
3554
|
+
});
|
|
3555
|
+
};
|
|
3236
3556
|
const handleSubmit = async state => {
|
|
3237
3557
|
const {
|
|
3238
3558
|
assetDir,
|
|
@@ -3244,10 +3564,12 @@ const handleSubmit = async state => {
|
|
|
3244
3564
|
openApiApiKey,
|
|
3245
3565
|
openRouterApiBaseUrl,
|
|
3246
3566
|
openRouterApiKey,
|
|
3567
|
+
passIncludeObfuscation,
|
|
3247
3568
|
platform,
|
|
3248
3569
|
selectedModelId,
|
|
3249
3570
|
selectedSessionId,
|
|
3250
3571
|
sessions,
|
|
3572
|
+
streamingEnabled,
|
|
3251
3573
|
useMockApi,
|
|
3252
3574
|
viewMode
|
|
3253
3575
|
} = state;
|
|
@@ -3265,6 +3587,18 @@ const handleSubmit = async state => {
|
|
|
3265
3587
|
text: userText,
|
|
3266
3588
|
time: userTime
|
|
3267
3589
|
};
|
|
3590
|
+
const assistantMessageId = `message-${nextMessageId + 1}`;
|
|
3591
|
+
const assistantTime = new Date().toLocaleTimeString([], {
|
|
3592
|
+
hour: '2-digit',
|
|
3593
|
+
minute: '2-digit'
|
|
3594
|
+
});
|
|
3595
|
+
const inProgressAssistantMessage = {
|
|
3596
|
+
id: assistantMessageId,
|
|
3597
|
+
inProgress: true,
|
|
3598
|
+
role: 'assistant',
|
|
3599
|
+
text: '',
|
|
3600
|
+
time: assistantTime
|
|
3601
|
+
};
|
|
3268
3602
|
let workingSessions = sessions;
|
|
3269
3603
|
if (viewMode === 'detail') {
|
|
3270
3604
|
const loadedSession = await getChatSession(selectedSessionId);
|
|
@@ -3282,7 +3616,7 @@ const handleSubmit = async state => {
|
|
|
3282
3616
|
const newSessionId = generateSessionId();
|
|
3283
3617
|
const newSession = {
|
|
3284
3618
|
id: newSessionId,
|
|
3285
|
-
messages: [userMessage],
|
|
3619
|
+
messages: streamingEnabled ? [userMessage, inProgressAssistantMessage] : [userMessage],
|
|
3286
3620
|
title: `Chat ${workingSessions.length + 1}`
|
|
3287
3621
|
};
|
|
3288
3622
|
await saveChatSession(newSession);
|
|
@@ -3298,15 +3632,8 @@ const handleSubmit = async state => {
|
|
|
3298
3632
|
viewMode: 'detail'
|
|
3299
3633
|
});
|
|
3300
3634
|
} else {
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
return session;
|
|
3304
|
-
}
|
|
3305
|
-
return {
|
|
3306
|
-
...session,
|
|
3307
|
-
messages: [...session.messages, userMessage]
|
|
3308
|
-
};
|
|
3309
|
-
});
|
|
3635
|
+
const updatedWithUser = appendMessageToSelectedSession(workingSessions, selectedSessionId, userMessage);
|
|
3636
|
+
const updatedSessions = streamingEnabled ? appendMessageToSelectedSession(updatedWithUser, selectedSessionId, inProgressAssistantMessage) : updatedWithUser;
|
|
3310
3637
|
const selectedSession = updatedSessions.find(session => session.id === selectedSessionId);
|
|
3311
3638
|
if (selectedSession) {
|
|
3312
3639
|
await saveChatSession(selectedSession);
|
|
@@ -3324,39 +3651,44 @@ const handleSubmit = async state => {
|
|
|
3324
3651
|
set(state.uid, state, optimisticState);
|
|
3325
3652
|
// @ts-ignore
|
|
3326
3653
|
await invoke('Chat.rerender');
|
|
3654
|
+
let handleTextChunkState = {
|
|
3655
|
+
latestState: optimisticState,
|
|
3656
|
+
previousState: optimisticState
|
|
3657
|
+
};
|
|
3327
3658
|
const selectedOptimisticSession = optimisticState.sessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
3328
|
-
const messages = selectedOptimisticSession?.messages ?? [];
|
|
3659
|
+
const messages = (selectedOptimisticSession?.messages ?? []).filter(message => !message.inProgress);
|
|
3660
|
+
const handleTextChunkFunctionRef = streamingEnabled ? async chunk => {
|
|
3661
|
+
handleTextChunkState = await handleTextChunkFunction(state.uid, assistantMessageId, chunk, handleTextChunkState);
|
|
3662
|
+
} : undefined;
|
|
3329
3663
|
const assistantMessage = await getAiResponse({
|
|
3330
3664
|
assetDir,
|
|
3331
3665
|
messages,
|
|
3332
3666
|
mockApiCommandId,
|
|
3333
3667
|
models,
|
|
3334
3668
|
nextMessageId: optimisticState.nextMessageId,
|
|
3669
|
+
onTextChunk: handleTextChunkFunctionRef,
|
|
3335
3670
|
openApiApiBaseUrl,
|
|
3336
3671
|
openApiApiKey,
|
|
3337
3672
|
openRouterApiBaseUrl,
|
|
3338
3673
|
openRouterApiKey,
|
|
3674
|
+
passIncludeObfuscation,
|
|
3339
3675
|
platform,
|
|
3340
3676
|
selectedModelId,
|
|
3677
|
+
streamingEnabled,
|
|
3341
3678
|
useMockApi,
|
|
3342
3679
|
userText
|
|
3343
3680
|
});
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
...session,
|
|
3350
|
-
messages: [...session.messages, assistantMessage]
|
|
3351
|
-
};
|
|
3352
|
-
});
|
|
3353
|
-
const selectedSession = updatedSessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
3681
|
+
const {
|
|
3682
|
+
latestState
|
|
3683
|
+
} = handleTextChunkState;
|
|
3684
|
+
const updatedSessions = streamingEnabled ? updateMessageTextInSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessageId, assistantMessage.text, false) : appendMessageToSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessage);
|
|
3685
|
+
const selectedSession = updatedSessions.find(session => session.id === latestState.selectedSessionId);
|
|
3354
3686
|
if (selectedSession) {
|
|
3355
3687
|
await saveChatSession(selectedSession);
|
|
3356
3688
|
}
|
|
3357
3689
|
return focusInput({
|
|
3358
|
-
...
|
|
3359
|
-
nextMessageId:
|
|
3690
|
+
...latestState,
|
|
3691
|
+
nextMessageId: latestState.nextMessageId + 1,
|
|
3360
3692
|
sessions: updatedSessions
|
|
3361
3693
|
});
|
|
3362
3694
|
};
|
|
@@ -3658,6 +3990,25 @@ const handleNewline = async state => {
|
|
|
3658
3990
|
return handleInput(state, Composer, `${composerValue}\n`);
|
|
3659
3991
|
};
|
|
3660
3992
|
|
|
3993
|
+
const handleChatListScroll = async (state, chatListScrollTop) => {
|
|
3994
|
+
if (state.chatListScrollTop === chatListScrollTop) {
|
|
3995
|
+
return state;
|
|
3996
|
+
}
|
|
3997
|
+
return {
|
|
3998
|
+
...state,
|
|
3999
|
+
chatListScrollTop
|
|
4000
|
+
};
|
|
4001
|
+
};
|
|
4002
|
+
const handleMessagesScroll = async (state, messagesScrollTop) => {
|
|
4003
|
+
if (state.messagesScrollTop === messagesScrollTop) {
|
|
4004
|
+
return state;
|
|
4005
|
+
}
|
|
4006
|
+
return {
|
|
4007
|
+
...state,
|
|
4008
|
+
messagesScrollTop
|
|
4009
|
+
};
|
|
4010
|
+
};
|
|
4011
|
+
|
|
3661
4012
|
const id = 7201;
|
|
3662
4013
|
const sendMessagePortToExtensionHostWorker = async port => {
|
|
3663
4014
|
await sendMessagePortToExtensionHostWorker$1(port, id);
|
|
@@ -3684,6 +4035,32 @@ const isObject = value => {
|
|
|
3684
4035
|
return typeof value === 'object' && value !== null;
|
|
3685
4036
|
};
|
|
3686
4037
|
|
|
4038
|
+
const getSavedChatListScrollTop = savedState => {
|
|
4039
|
+
if (!isObject(savedState)) {
|
|
4040
|
+
return undefined;
|
|
4041
|
+
}
|
|
4042
|
+
const {
|
|
4043
|
+
chatListScrollTop
|
|
4044
|
+
} = savedState;
|
|
4045
|
+
if (typeof chatListScrollTop !== 'number') {
|
|
4046
|
+
return undefined;
|
|
4047
|
+
}
|
|
4048
|
+
return chatListScrollTop;
|
|
4049
|
+
};
|
|
4050
|
+
|
|
4051
|
+
const getSavedMessagesScrollTop = savedState => {
|
|
4052
|
+
if (!isObject(savedState)) {
|
|
4053
|
+
return undefined;
|
|
4054
|
+
}
|
|
4055
|
+
const {
|
|
4056
|
+
messagesScrollTop
|
|
4057
|
+
} = savedState;
|
|
4058
|
+
if (typeof messagesScrollTop !== 'number') {
|
|
4059
|
+
return undefined;
|
|
4060
|
+
}
|
|
4061
|
+
return messagesScrollTop;
|
|
4062
|
+
};
|
|
4063
|
+
|
|
3687
4064
|
const getSavedSelectedModelId = savedState => {
|
|
3688
4065
|
if (!isObject(savedState)) {
|
|
3689
4066
|
return undefined;
|
|
@@ -3736,6 +4113,63 @@ const getSavedViewMode = savedState => {
|
|
|
3736
4113
|
return viewMode;
|
|
3737
4114
|
};
|
|
3738
4115
|
|
|
4116
|
+
const loadOpenApiApiKey = async () => {
|
|
4117
|
+
try {
|
|
4118
|
+
const savedOpenApiKey = await get('secrets.openApiKey');
|
|
4119
|
+
if (typeof savedOpenApiKey === 'string' && savedOpenApiKey) {
|
|
4120
|
+
return savedOpenApiKey;
|
|
4121
|
+
}
|
|
4122
|
+
const legacySavedOpenApiApiKey = await get('secrets.openApiApiKey');
|
|
4123
|
+
if (typeof legacySavedOpenApiApiKey === 'string' && legacySavedOpenApiApiKey) {
|
|
4124
|
+
return legacySavedOpenApiApiKey;
|
|
4125
|
+
}
|
|
4126
|
+
const legacySavedOpenAiApiKey = await get('secrets.openAiApiKey');
|
|
4127
|
+
return typeof legacySavedOpenAiApiKey === 'string' ? legacySavedOpenAiApiKey : '';
|
|
4128
|
+
} catch {
|
|
4129
|
+
return '';
|
|
4130
|
+
}
|
|
4131
|
+
};
|
|
4132
|
+
|
|
4133
|
+
const loadOpenRouterApiKey = async () => {
|
|
4134
|
+
try {
|
|
4135
|
+
const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
|
|
4136
|
+
return typeof savedOpenRouterApiKey === 'string' ? savedOpenRouterApiKey : '';
|
|
4137
|
+
} catch {
|
|
4138
|
+
return '';
|
|
4139
|
+
}
|
|
4140
|
+
};
|
|
4141
|
+
|
|
4142
|
+
const loadPassIncludeObfuscation = async () => {
|
|
4143
|
+
try {
|
|
4144
|
+
const savedPassIncludeObfuscation = await get('chatView.passIncludeObfuscation');
|
|
4145
|
+
return typeof savedPassIncludeObfuscation === 'boolean' ? savedPassIncludeObfuscation : false;
|
|
4146
|
+
} catch {
|
|
4147
|
+
return false;
|
|
4148
|
+
}
|
|
4149
|
+
};
|
|
4150
|
+
|
|
4151
|
+
const loadStreamingEnabled = async () => {
|
|
4152
|
+
try {
|
|
4153
|
+
const savedStreamingEnabled = await get('chatView.streamingEnabled');
|
|
4154
|
+
return typeof savedStreamingEnabled === 'boolean' ? savedStreamingEnabled : false;
|
|
4155
|
+
} catch {
|
|
4156
|
+
return false;
|
|
4157
|
+
}
|
|
4158
|
+
};
|
|
4159
|
+
|
|
4160
|
+
const loadPreferences = async () => {
|
|
4161
|
+
const openApiApiKey = await loadOpenApiApiKey();
|
|
4162
|
+
const openRouterApiKey = await loadOpenRouterApiKey();
|
|
4163
|
+
const streamingEnabled = await loadStreamingEnabled();
|
|
4164
|
+
const passIncludeObfuscation = await loadPassIncludeObfuscation();
|
|
4165
|
+
return {
|
|
4166
|
+
openApiApiKey,
|
|
4167
|
+
openRouterApiKey,
|
|
4168
|
+
passIncludeObfuscation,
|
|
4169
|
+
streamingEnabled
|
|
4170
|
+
};
|
|
4171
|
+
};
|
|
4172
|
+
|
|
3739
4173
|
const toSummarySession = session => {
|
|
3740
4174
|
return {
|
|
3741
4175
|
id: session.id,
|
|
@@ -3761,30 +4195,12 @@ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
|
|
|
3761
4195
|
const loadContent = async (state, savedState) => {
|
|
3762
4196
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
3763
4197
|
const savedViewMode = getSavedViewMode(savedState);
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
const legacySavedOpenApiApiKey = await get('secrets.openApiApiKey');
|
|
3771
|
-
if (typeof legacySavedOpenApiApiKey === 'string' && legacySavedOpenApiApiKey) {
|
|
3772
|
-
openApiApiKey = legacySavedOpenApiApiKey;
|
|
3773
|
-
} else {
|
|
3774
|
-
const legacySavedOpenAiApiKey = await get('secrets.openAiApiKey');
|
|
3775
|
-
openApiApiKey = typeof legacySavedOpenAiApiKey === 'string' ? legacySavedOpenAiApiKey : '';
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
} catch {
|
|
3779
|
-
openApiApiKey = '';
|
|
3780
|
-
}
|
|
3781
|
-
let openRouterApiKey = '';
|
|
3782
|
-
try {
|
|
3783
|
-
const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
|
|
3784
|
-
openRouterApiKey = typeof savedOpenRouterApiKey === 'string' ? savedOpenRouterApiKey : '';
|
|
3785
|
-
} catch {
|
|
3786
|
-
openRouterApiKey = '';
|
|
3787
|
-
}
|
|
4198
|
+
const {
|
|
4199
|
+
openApiApiKey,
|
|
4200
|
+
openRouterApiKey,
|
|
4201
|
+
passIncludeObfuscation,
|
|
4202
|
+
streamingEnabled
|
|
4203
|
+
} = await loadPreferences();
|
|
3788
4204
|
const legacySavedSessions = getSavedSessions(savedState);
|
|
3789
4205
|
const storedSessions = await listChatSessions();
|
|
3790
4206
|
let sessions = storedSessions;
|
|
@@ -3802,6 +4218,8 @@ const loadContent = async (state, savedState) => {
|
|
|
3802
4218
|
}
|
|
3803
4219
|
const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
|
|
3804
4220
|
const preferredModelId = savedSelectedModelId || state.selectedModelId;
|
|
4221
|
+
const chatListScrollTop = getSavedChatListScrollTop(savedState) ?? state.chatListScrollTop;
|
|
4222
|
+
const messagesScrollTop = getSavedMessagesScrollTop(savedState) ?? state.messagesScrollTop;
|
|
3805
4223
|
const selectedModelId = state.models.some(model => model.id === preferredModelId) ? preferredModelId : state.models[0]?.id || '';
|
|
3806
4224
|
const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
|
|
3807
4225
|
sessions = await loadSelectedSessionMessages(sessions, selectedSessionId);
|
|
@@ -3809,18 +4227,37 @@ const loadContent = async (state, savedState) => {
|
|
|
3809
4227
|
const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
|
|
3810
4228
|
return {
|
|
3811
4229
|
...state,
|
|
4230
|
+
chatListScrollTop,
|
|
3812
4231
|
initial: false,
|
|
4232
|
+
messagesScrollTop,
|
|
3813
4233
|
openApiApiKey,
|
|
3814
4234
|
openApiApiKeyInput: openApiApiKey,
|
|
3815
4235
|
openRouterApiKey,
|
|
3816
4236
|
openRouterApiKeyInput: openRouterApiKey,
|
|
4237
|
+
passIncludeObfuscation,
|
|
3817
4238
|
selectedModelId,
|
|
3818
4239
|
selectedSessionId,
|
|
3819
4240
|
sessions,
|
|
4241
|
+
streamingEnabled,
|
|
3820
4242
|
viewMode
|
|
3821
4243
|
};
|
|
3822
4244
|
};
|
|
3823
4245
|
|
|
4246
|
+
const mockOpenApiStreamFinish = state => {
|
|
4247
|
+
finish();
|
|
4248
|
+
return state;
|
|
4249
|
+
};
|
|
4250
|
+
|
|
4251
|
+
const mockOpenApiStreamPushChunk = (state, chunk) => {
|
|
4252
|
+
pushChunk(chunk);
|
|
4253
|
+
return state;
|
|
4254
|
+
};
|
|
4255
|
+
|
|
4256
|
+
const mockOpenApiStreamReset = state => {
|
|
4257
|
+
reset$1();
|
|
4258
|
+
return state;
|
|
4259
|
+
};
|
|
4260
|
+
|
|
3824
4261
|
const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
3825
4262
|
const {
|
|
3826
4263
|
sessions: currentSessions
|
|
@@ -3855,9 +4292,13 @@ const openMockSession = async (state, mockSessionId, mockChatMessages) => {
|
|
|
3855
4292
|
};
|
|
3856
4293
|
};
|
|
3857
4294
|
|
|
3858
|
-
const getCss = composerHeight => {
|
|
4295
|
+
const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily) => {
|
|
3859
4296
|
return `:root {
|
|
3860
4297
|
--ChatInputBoxHeight: ${composerHeight}px;
|
|
4298
|
+
--ChatListItemHeight: ${listItemHeight}px;
|
|
4299
|
+
--ChatMessageFontSize: ${chatMessageFontSize}px;
|
|
4300
|
+
--ChatMessageLineHeight: ${chatMessageLineHeight}px;
|
|
4301
|
+
--ChatMessageFontFamily: ${chatMessageFontFamily};
|
|
3861
4302
|
}`;
|
|
3862
4303
|
};
|
|
3863
4304
|
|
|
@@ -3866,10 +4307,14 @@ const getCss = composerHeight => {
|
|
|
3866
4307
|
|
|
3867
4308
|
const renderCss = (oldState, newState) => {
|
|
3868
4309
|
const {
|
|
4310
|
+
chatMessageFontFamily,
|
|
4311
|
+
chatMessageFontSize,
|
|
4312
|
+
chatMessageLineHeight,
|
|
3869
4313
|
composerHeight,
|
|
4314
|
+
listItemHeight,
|
|
3870
4315
|
uid
|
|
3871
4316
|
} = newState;
|
|
3872
|
-
const css = getCss(composerHeight);
|
|
4317
|
+
const css = getCss(composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily);
|
|
3873
4318
|
return [SetCss, uid, css];
|
|
3874
4319
|
};
|
|
3875
4320
|
|
|
@@ -3946,6 +4391,8 @@ const HandleClickList = 17;
|
|
|
3946
4391
|
const HandleClickDelete = 18;
|
|
3947
4392
|
const HandleSubmit = 19;
|
|
3948
4393
|
const HandleModelChange = 20;
|
|
4394
|
+
const HandleChatListScroll = 21;
|
|
4395
|
+
const HandleMessagesScroll = 22;
|
|
3949
4396
|
|
|
3950
4397
|
const getModelLabel = model => {
|
|
3951
4398
|
if (model.provider === 'openRouter') {
|
|
@@ -4253,18 +4700,20 @@ const getEmptyMessagesDom = () => {
|
|
|
4253
4700
|
}, text(startConversation())];
|
|
4254
4701
|
};
|
|
4255
4702
|
|
|
4256
|
-
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
4703
|
+
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', messagesScrollTop = 0) => {
|
|
4257
4704
|
if (messages.length === 0) {
|
|
4258
4705
|
return getEmptyMessagesDom();
|
|
4259
4706
|
}
|
|
4260
4707
|
return [{
|
|
4261
4708
|
childCount: messages.length,
|
|
4262
4709
|
className: 'ChatMessages',
|
|
4710
|
+
onScroll: HandleMessagesScroll,
|
|
4711
|
+
scrollTop: messagesScrollTop,
|
|
4263
4712
|
type: Div
|
|
4264
4713
|
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
4265
4714
|
};
|
|
4266
4715
|
|
|
4267
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
4716
|
+
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, messagesScrollTop = 0) => {
|
|
4268
4717
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
4269
4718
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
4270
4719
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
@@ -4272,7 +4721,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
|
|
|
4272
4721
|
childCount: 3,
|
|
4273
4722
|
className: mergeClassNames(Viewlet, Chat),
|
|
4274
4723
|
type: Div
|
|
4275
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4724
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4276
4725
|
};
|
|
4277
4726
|
|
|
4278
4727
|
const getChatHeaderListModeDom = () => {
|
|
@@ -4329,7 +4778,7 @@ const getSessionDom = session => {
|
|
|
4329
4778
|
}, text('🗑')];
|
|
4330
4779
|
};
|
|
4331
4780
|
|
|
4332
|
-
const getChatListDom = (sessions, selectedSessionId) => {
|
|
4781
|
+
const getChatListDom = (sessions, selectedSessionId, chatListScrollTop = 0) => {
|
|
4333
4782
|
if (sessions.length === 0) {
|
|
4334
4783
|
return getEmptyChatSessionsDom();
|
|
4335
4784
|
}
|
|
@@ -4337,16 +4786,18 @@ const getChatListDom = (sessions, selectedSessionId) => {
|
|
|
4337
4786
|
childCount: sessions.length,
|
|
4338
4787
|
className: ChatList,
|
|
4339
4788
|
onClick: HandleClickList,
|
|
4789
|
+
onScroll: HandleChatListScroll,
|
|
4790
|
+
scrollTop: chatListScrollTop,
|
|
4340
4791
|
type: Div
|
|
4341
4792
|
}, ...sessions.flatMap(getSessionDom)];
|
|
4342
4793
|
};
|
|
4343
4794
|
|
|
4344
|
-
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
4795
|
+
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0) => {
|
|
4345
4796
|
return [{
|
|
4346
4797
|
childCount: 3,
|
|
4347
4798
|
className: mergeClassNames(Viewlet, Chat),
|
|
4348
4799
|
type: Div
|
|
4349
|
-
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4800
|
+
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4350
4801
|
};
|
|
4351
4802
|
|
|
4352
4803
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -4356,12 +4807,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
4356
4807
|
}, text(unknownViewMode())];
|
|
4357
4808
|
};
|
|
4358
4809
|
|
|
4359
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
4810
|
+
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0, messagesScrollTop = 0) => {
|
|
4360
4811
|
switch (viewMode) {
|
|
4361
4812
|
case 'detail':
|
|
4362
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
4813
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop);
|
|
4363
4814
|
case 'list':
|
|
4364
|
-
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
4815
|
+
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop);
|
|
4365
4816
|
default:
|
|
4366
4817
|
return getChatModeUnsupportedVirtualDom();
|
|
4367
4818
|
}
|
|
@@ -4369,12 +4820,14 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
4369
4820
|
|
|
4370
4821
|
const renderItems = (oldState, newState) => {
|
|
4371
4822
|
const {
|
|
4823
|
+
chatListScrollTop,
|
|
4372
4824
|
composerFontFamily,
|
|
4373
4825
|
composerFontSize,
|
|
4374
4826
|
composerHeight,
|
|
4375
4827
|
composerLineHeight,
|
|
4376
4828
|
composerValue,
|
|
4377
4829
|
initial,
|
|
4830
|
+
messagesScrollTop,
|
|
4378
4831
|
models,
|
|
4379
4832
|
openApiApiKeyInput,
|
|
4380
4833
|
openRouterApiKeyInput,
|
|
@@ -4391,7 +4844,7 @@ const renderItems = (oldState, newState) => {
|
|
|
4391
4844
|
if (initial) {
|
|
4392
4845
|
return [SetDom2, uid, []];
|
|
4393
4846
|
}
|
|
4394
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
4847
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop);
|
|
4395
4848
|
return [SetDom2, uid, dom];
|
|
4396
4849
|
};
|
|
4397
4850
|
|
|
@@ -4402,6 +4855,14 @@ const renderIncremental = (oldState, newState) => {
|
|
|
4402
4855
|
return [SetPatches, newState.uid, patches];
|
|
4403
4856
|
};
|
|
4404
4857
|
|
|
4858
|
+
const renderScrollTop = (oldState, newState) => {
|
|
4859
|
+
const {
|
|
4860
|
+
messagesScrollTop,
|
|
4861
|
+
uid
|
|
4862
|
+
} = newState;
|
|
4863
|
+
return [SetProperty, uid, '.ChatMessages', 'scrollTop', messagesScrollTop];
|
|
4864
|
+
};
|
|
4865
|
+
|
|
4405
4866
|
const renderValue = (oldState, newState) => {
|
|
4406
4867
|
const {
|
|
4407
4868
|
composerValue
|
|
@@ -4421,6 +4882,8 @@ const getRenderer = diffType => {
|
|
|
4421
4882
|
return renderIncremental;
|
|
4422
4883
|
case RenderItems:
|
|
4423
4884
|
return renderItems;
|
|
4885
|
+
case RenderScrollTop:
|
|
4886
|
+
return renderScrollTop;
|
|
4424
4887
|
case RenderValue:
|
|
4425
4888
|
return renderValue;
|
|
4426
4889
|
default:
|
|
@@ -4482,6 +4945,12 @@ const renderEventListeners = () => {
|
|
|
4482
4945
|
}, {
|
|
4483
4946
|
name: HandleModelChange,
|
|
4484
4947
|
params: ['handleModelChange', TargetValue]
|
|
4948
|
+
}, {
|
|
4949
|
+
name: HandleChatListScroll,
|
|
4950
|
+
params: ['handleChatListScroll', 'event.target.scrollTop']
|
|
4951
|
+
}, {
|
|
4952
|
+
name: HandleMessagesScroll,
|
|
4953
|
+
params: ['handleMessagesScroll', 'event.target.scrollTop']
|
|
4485
4954
|
}, {
|
|
4486
4955
|
name: HandleFocus,
|
|
4487
4956
|
params: ['handleInputFocus', TargetName]
|
|
@@ -4505,8 +4974,10 @@ const reset = async state => {
|
|
|
4505
4974
|
composerHeight: getMinComposerHeightForState(state),
|
|
4506
4975
|
composerValue: '',
|
|
4507
4976
|
openRouterApiKey: '',
|
|
4977
|
+
selectedModelId: 'test',
|
|
4508
4978
|
selectedSessionId: '',
|
|
4509
4979
|
sessions: [],
|
|
4980
|
+
streamingEnabled: false,
|
|
4510
4981
|
viewMode: 'list'
|
|
4511
4982
|
};
|
|
4512
4983
|
};
|
|
@@ -4520,8 +4991,10 @@ const resize = (state, dimensions) => {
|
|
|
4520
4991
|
|
|
4521
4992
|
const saveState = state => {
|
|
4522
4993
|
const {
|
|
4994
|
+
chatListScrollTop,
|
|
4523
4995
|
composerValue,
|
|
4524
4996
|
height,
|
|
4997
|
+
messagesScrollTop,
|
|
4525
4998
|
nextMessageId,
|
|
4526
4999
|
renamingSessionId,
|
|
4527
5000
|
selectedModelId,
|
|
@@ -4532,8 +5005,10 @@ const saveState = state => {
|
|
|
4532
5005
|
y
|
|
4533
5006
|
} = state;
|
|
4534
5007
|
return {
|
|
5008
|
+
chatListScrollTop,
|
|
4535
5009
|
composerValue,
|
|
4536
5010
|
height,
|
|
5011
|
+
messagesScrollTop,
|
|
4537
5012
|
nextMessageId,
|
|
4538
5013
|
renamingSessionId,
|
|
4539
5014
|
selectedModelId,
|
|
@@ -4567,6 +5042,13 @@ const setChatList = state => {
|
|
|
4567
5042
|
};
|
|
4568
5043
|
};
|
|
4569
5044
|
|
|
5045
|
+
const setStreamingEnabled = (state, streamingEnabled) => {
|
|
5046
|
+
return {
|
|
5047
|
+
...state,
|
|
5048
|
+
streamingEnabled
|
|
5049
|
+
};
|
|
5050
|
+
};
|
|
5051
|
+
|
|
4570
5052
|
const defaultMockApiCommandId = 'ChatE2e.mockApi';
|
|
4571
5053
|
const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) => {
|
|
4572
5054
|
if (!value) {
|
|
@@ -4591,6 +5073,7 @@ const commandMap = {
|
|
|
4591
5073
|
'Chat.getKeyBindings': getKeyBindings,
|
|
4592
5074
|
'Chat.getSelectedSessionId': wrapGetter(getSelectedSessionId),
|
|
4593
5075
|
'Chat.handleChatListContextMenu': handleChatListContextMenu,
|
|
5076
|
+
'Chat.handleChatListScroll': wrapCommand(handleChatListScroll),
|
|
4594
5077
|
'Chat.handleClick': wrapCommand(handleClick),
|
|
4595
5078
|
'Chat.handleClickBack': wrapCommand(handleClickBack),
|
|
4596
5079
|
'Chat.handleClickClose': handleClickClose,
|
|
@@ -4601,11 +5084,15 @@ const commandMap = {
|
|
|
4601
5084
|
'Chat.handleInput': wrapCommand(handleInput),
|
|
4602
5085
|
'Chat.handleInputFocus': wrapCommand(handleInputFocus),
|
|
4603
5086
|
'Chat.handleKeyDown': wrapCommand(handleKeyDown),
|
|
5087
|
+
'Chat.handleMessagesScroll': wrapCommand(handleMessagesScroll),
|
|
4604
5088
|
'Chat.handleModelChange': wrapCommand(handleModelChange),
|
|
4605
5089
|
'Chat.handleSubmit': wrapCommand(handleSubmit),
|
|
4606
5090
|
'Chat.initialize': initialize,
|
|
4607
5091
|
'Chat.loadContent': wrapCommand(loadContent),
|
|
4608
5092
|
'Chat.loadContent2': wrapCommand(loadContent),
|
|
5093
|
+
'Chat.mockOpenApiStreamFinish': wrapCommand(mockOpenApiStreamFinish),
|
|
5094
|
+
'Chat.mockOpenApiStreamPushChunk': wrapCommand(mockOpenApiStreamPushChunk),
|
|
5095
|
+
'Chat.mockOpenApiStreamReset': wrapCommand(mockOpenApiStreamReset),
|
|
4609
5096
|
'Chat.openMockSession': wrapCommand(openMockSession),
|
|
4610
5097
|
'Chat.render2': render2,
|
|
4611
5098
|
'Chat.renderEventListeners': renderEventListeners,
|
|
@@ -4615,6 +5102,7 @@ const commandMap = {
|
|
|
4615
5102
|
'Chat.saveState': wrapGetter(saveState),
|
|
4616
5103
|
'Chat.setChatList': wrapCommand(setChatList),
|
|
4617
5104
|
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
5105
|
+
'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
|
|
4618
5106
|
'Chat.terminate': terminate,
|
|
4619
5107
|
'Chat.useMockApi': wrapCommand(useMockApi)
|
|
4620
5108
|
};
|