@lvce-editor/chat-view 1.18.0 → 1.19.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 +402 -70
- 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 => {
|
|
@@ -1385,6 +1390,7 @@ const createDefaultState = () => {
|
|
|
1385
1390
|
const composerLineHeight = 20;
|
|
1386
1391
|
return {
|
|
1387
1392
|
assetDir: '',
|
|
1393
|
+
chatListScrollTop: 0,
|
|
1388
1394
|
composerFontFamily: 'system-ui',
|
|
1389
1395
|
composerFontSize,
|
|
1390
1396
|
composerHeight: composerLineHeight + 8,
|
|
@@ -1399,6 +1405,8 @@ const createDefaultState = () => {
|
|
|
1399
1405
|
inputSource: 'script',
|
|
1400
1406
|
lastSubmittedSessionId: '',
|
|
1401
1407
|
listItemHeight: 40,
|
|
1408
|
+
maxComposerRows: 5,
|
|
1409
|
+
messagesScrollTop: 0,
|
|
1402
1410
|
mockApiCommandId: '',
|
|
1403
1411
|
models: getDefaultModels(),
|
|
1404
1412
|
nextMessageId: 1,
|
|
@@ -1420,6 +1428,7 @@ const createDefaultState = () => {
|
|
|
1420
1428
|
messages: [],
|
|
1421
1429
|
title: defaultSessionTitle()
|
|
1422
1430
|
}],
|
|
1431
|
+
streamingEnabled: false,
|
|
1423
1432
|
tokensMax: 0,
|
|
1424
1433
|
tokensUsed: 0,
|
|
1425
1434
|
uid: 0,
|
|
@@ -1471,12 +1480,17 @@ const isEqual = (oldState, newState) => {
|
|
|
1471
1480
|
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
1481
|
};
|
|
1473
1482
|
|
|
1483
|
+
const diffScrollTop = (oldState, newState) => {
|
|
1484
|
+
return oldState.chatListScrollTop === newState.chatListScrollTop && oldState.messagesScrollTop === newState.messagesScrollTop;
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1474
1487
|
const RenderItems = 4;
|
|
1475
1488
|
const RenderFocus = 6;
|
|
1476
1489
|
const RenderFocusContext = 7;
|
|
1477
1490
|
const RenderValue = 8;
|
|
1478
1491
|
const RenderCss = 10;
|
|
1479
1492
|
const RenderIncremental = 11;
|
|
1493
|
+
const RenderScrollTop = 12;
|
|
1480
1494
|
|
|
1481
1495
|
const diffValue = (oldState, newState) => {
|
|
1482
1496
|
if (oldState.composerValue === newState.composerValue) {
|
|
@@ -1485,8 +1499,8 @@ const diffValue = (oldState, newState) => {
|
|
|
1485
1499
|
return newState.inputSource !== 'script';
|
|
1486
1500
|
};
|
|
1487
1501
|
|
|
1488
|
-
const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus];
|
|
1489
|
-
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext];
|
|
1502
|
+
const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus, diffScrollTop];
|
|
1503
|
+
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext, RenderScrollTop];
|
|
1490
1504
|
|
|
1491
1505
|
const diff = (oldState, newState) => {
|
|
1492
1506
|
const diffResult = [];
|
|
@@ -1519,6 +1533,7 @@ const FocusSelector = 'Viewlet.focusSelector';
|
|
|
1519
1533
|
const SetCss = 'Viewlet.setCss';
|
|
1520
1534
|
const SetDom2 = 'Viewlet.setDom2';
|
|
1521
1535
|
const SetFocusContext = 'Viewlet.setFocusContext';
|
|
1536
|
+
const SetProperty = 'Viewlet.setProperty';
|
|
1522
1537
|
const SetValueByName = 'Viewlet.setValueByName';
|
|
1523
1538
|
const SetPatches = 'Viewlet.setPatches';
|
|
1524
1539
|
|
|
@@ -2495,7 +2510,16 @@ const executeChatTool = async (name, rawArguments, options) => {
|
|
|
2495
2510
|
});
|
|
2496
2511
|
};
|
|
2497
2512
|
|
|
2498
|
-
const
|
|
2513
|
+
const getClientRequestIdHeader = () => {
|
|
2514
|
+
return {
|
|
2515
|
+
'x-client-request-id': crypto.randomUUID()
|
|
2516
|
+
};
|
|
2517
|
+
};
|
|
2518
|
+
|
|
2519
|
+
const getOpenApiApiEndpoint = (openApiApiBaseUrl, stream) => {
|
|
2520
|
+
if (stream) {
|
|
2521
|
+
return `${openApiApiBaseUrl}/chat/completions?stream=true`;
|
|
2522
|
+
}
|
|
2499
2523
|
return `${openApiApiBaseUrl}/chat/completions`;
|
|
2500
2524
|
};
|
|
2501
2525
|
|
|
@@ -2520,6 +2544,148 @@ const getTextContent = content => {
|
|
|
2520
2544
|
return textParts.join('\n');
|
|
2521
2545
|
};
|
|
2522
2546
|
|
|
2547
|
+
const getStreamChunkText = content => {
|
|
2548
|
+
if (typeof content === 'string') {
|
|
2549
|
+
return content;
|
|
2550
|
+
}
|
|
2551
|
+
if (!Array.isArray(content)) {
|
|
2552
|
+
return '';
|
|
2553
|
+
}
|
|
2554
|
+
return content.map(part => {
|
|
2555
|
+
if (!part || typeof part !== 'object') {
|
|
2556
|
+
return '';
|
|
2557
|
+
}
|
|
2558
|
+
const text = Reflect.get(part, 'text');
|
|
2559
|
+
return typeof text === 'string' ? text : '';
|
|
2560
|
+
}).join('');
|
|
2561
|
+
};
|
|
2562
|
+
const parseSseEvent = eventChunk => {
|
|
2563
|
+
const lines = eventChunk.split('\n');
|
|
2564
|
+
const dataLines = [];
|
|
2565
|
+
for (const line of lines) {
|
|
2566
|
+
if (!line.startsWith('data:')) {
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
dataLines.push(line.slice(5).trimStart());
|
|
2570
|
+
}
|
|
2571
|
+
return dataLines;
|
|
2572
|
+
};
|
|
2573
|
+
const parseOpenApiStream = async (response, onTextChunk) => {
|
|
2574
|
+
if (!response.body) {
|
|
2575
|
+
return {
|
|
2576
|
+
details: 'request-failed',
|
|
2577
|
+
type: 'error'
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
const reader = response.body.getReader();
|
|
2581
|
+
const decoder = new TextDecoder();
|
|
2582
|
+
let remainder = '';
|
|
2583
|
+
let text = '';
|
|
2584
|
+
let done = false;
|
|
2585
|
+
while (!done) {
|
|
2586
|
+
const {
|
|
2587
|
+
done: streamDone,
|
|
2588
|
+
value
|
|
2589
|
+
} = await reader.read();
|
|
2590
|
+
if (streamDone) {
|
|
2591
|
+
done = true;
|
|
2592
|
+
} else if (value) {
|
|
2593
|
+
remainder += decoder.decode(value, {
|
|
2594
|
+
stream: true
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
while (true) {
|
|
2598
|
+
const separatorIndex = remainder.indexOf('\n\n');
|
|
2599
|
+
if (separatorIndex === -1) {
|
|
2600
|
+
break;
|
|
2601
|
+
}
|
|
2602
|
+
const rawEvent = remainder.slice(0, separatorIndex);
|
|
2603
|
+
remainder = remainder.slice(separatorIndex + 2);
|
|
2604
|
+
const dataLines = parseSseEvent(rawEvent);
|
|
2605
|
+
if (dataLines.length === 0) {
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
for (const line of dataLines) {
|
|
2609
|
+
if (line === '[DONE]') {
|
|
2610
|
+
done = true;
|
|
2611
|
+
break;
|
|
2612
|
+
}
|
|
2613
|
+
let parsed;
|
|
2614
|
+
try {
|
|
2615
|
+
parsed = JSON.parse(line);
|
|
2616
|
+
} catch {
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2623
|
+
if (!Array.isArray(choices)) {
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
const firstChoice = choices[0];
|
|
2627
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
const delta = Reflect.get(firstChoice, 'delta');
|
|
2631
|
+
if (!delta || typeof delta !== 'object') {
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
const content = Reflect.get(delta, 'content');
|
|
2635
|
+
const chunkText = getStreamChunkText(content);
|
|
2636
|
+
if (!chunkText) {
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2639
|
+
text += chunkText;
|
|
2640
|
+
if (onTextChunk) {
|
|
2641
|
+
await onTextChunk(chunkText);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
if (remainder) {
|
|
2647
|
+
const dataLines = parseSseEvent(remainder);
|
|
2648
|
+
for (const line of dataLines) {
|
|
2649
|
+
if (line === '[DONE]') {
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
let parsed;
|
|
2653
|
+
try {
|
|
2654
|
+
parsed = JSON.parse(line);
|
|
2655
|
+
} catch {
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
2662
|
+
if (!Array.isArray(choices)) {
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
const firstChoice = choices[0];
|
|
2666
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
2667
|
+
continue;
|
|
2668
|
+
}
|
|
2669
|
+
const delta = Reflect.get(firstChoice, 'delta');
|
|
2670
|
+
if (!delta || typeof delta !== 'object') {
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
const content = Reflect.get(delta, 'content');
|
|
2674
|
+
const chunkText = getStreamChunkText(content);
|
|
2675
|
+
if (!chunkText) {
|
|
2676
|
+
continue;
|
|
2677
|
+
}
|
|
2678
|
+
text += chunkText;
|
|
2679
|
+
if (onTextChunk) {
|
|
2680
|
+
await onTextChunk(chunkText);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return {
|
|
2685
|
+
text,
|
|
2686
|
+
type: 'success'
|
|
2687
|
+
};
|
|
2688
|
+
};
|
|
2523
2689
|
const getOpenApiErrorDetails = async response => {
|
|
2524
2690
|
let parsed;
|
|
2525
2691
|
try {
|
|
@@ -2543,7 +2709,13 @@ const getOpenApiErrorDetails = async response => {
|
|
|
2543
2709
|
errorType: typeof errorType === 'string' ? errorType : undefined
|
|
2544
2710
|
};
|
|
2545
2711
|
};
|
|
2546
|
-
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform) => {
|
|
2712
|
+
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform, options) => {
|
|
2713
|
+
const {
|
|
2714
|
+
onTextChunk,
|
|
2715
|
+
stream
|
|
2716
|
+
} = options ?? {
|
|
2717
|
+
stream: false
|
|
2718
|
+
};
|
|
2547
2719
|
const completionMessages = messages.map(message => ({
|
|
2548
2720
|
content: message.text,
|
|
2549
2721
|
role: message.role
|
|
@@ -2553,16 +2725,20 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
2553
2725
|
for (let i = 0; i <= maxToolIterations; i++) {
|
|
2554
2726
|
let response;
|
|
2555
2727
|
try {
|
|
2556
|
-
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
2728
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl, stream), {
|
|
2557
2729
|
body: JSON.stringify({
|
|
2558
2730
|
messages: completionMessages,
|
|
2559
2731
|
model: modelId,
|
|
2732
|
+
...(stream ? {
|
|
2733
|
+
stream: true
|
|
2734
|
+
} : {}),
|
|
2560
2735
|
tool_choice: 'auto',
|
|
2561
2736
|
tools
|
|
2562
2737
|
}),
|
|
2563
2738
|
headers: {
|
|
2564
2739
|
Authorization: `Bearer ${openApiApiKey}`,
|
|
2565
|
-
'Content-Type': 'application/json'
|
|
2740
|
+
'Content-Type': 'application/json',
|
|
2741
|
+
...getClientRequestIdHeader()
|
|
2566
2742
|
},
|
|
2567
2743
|
method: 'POST'
|
|
2568
2744
|
});
|
|
@@ -2587,6 +2763,9 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
2587
2763
|
type: 'error'
|
|
2588
2764
|
};
|
|
2589
2765
|
}
|
|
2766
|
+
if (stream) {
|
|
2767
|
+
return parseOpenApiStream(response, onTextChunk);
|
|
2768
|
+
}
|
|
2590
2769
|
let parsed;
|
|
2591
2770
|
try {
|
|
2592
2771
|
parsed = await response.json();
|
|
@@ -2758,7 +2937,8 @@ const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) =>
|
|
|
2758
2937
|
try {
|
|
2759
2938
|
response = await fetch(getOpenRouterKeyEndpoint(openRouterApiBaseUrl), {
|
|
2760
2939
|
headers: {
|
|
2761
|
-
Authorization: `Bearer ${openRouterApiKey}
|
|
2940
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
2941
|
+
...getClientRequestIdHeader()
|
|
2762
2942
|
},
|
|
2763
2943
|
method: 'GET'
|
|
2764
2944
|
});
|
|
@@ -2816,7 +2996,8 @@ const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, o
|
|
|
2816
2996
|
}),
|
|
2817
2997
|
headers: {
|
|
2818
2998
|
Authorization: `Bearer ${openRouterApiKey}`,
|
|
2819
|
-
'Content-Type': 'application/json'
|
|
2999
|
+
'Content-Type': 'application/json',
|
|
3000
|
+
...getClientRequestIdHeader()
|
|
2820
3001
|
},
|
|
2821
3002
|
method: 'POST'
|
|
2822
3003
|
});
|
|
@@ -3001,12 +3182,14 @@ const getAiResponse = async ({
|
|
|
3001
3182
|
mockApiCommandId,
|
|
3002
3183
|
models,
|
|
3003
3184
|
nextMessageId,
|
|
3185
|
+
onTextChunk,
|
|
3004
3186
|
openApiApiBaseUrl,
|
|
3005
3187
|
openApiApiKey,
|
|
3006
3188
|
openRouterApiBaseUrl,
|
|
3007
3189
|
openRouterApiKey,
|
|
3008
3190
|
platform,
|
|
3009
3191
|
selectedModelId,
|
|
3192
|
+
streamingEnabled = false,
|
|
3010
3193
|
useMockApi,
|
|
3011
3194
|
userText
|
|
3012
3195
|
}) => {
|
|
@@ -3015,7 +3198,10 @@ const getAiResponse = async ({
|
|
|
3015
3198
|
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
3016
3199
|
if (usesOpenApiModel) {
|
|
3017
3200
|
if (openApiApiKey) {
|
|
3018
|
-
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl, assetDir, platform
|
|
3201
|
+
const result = await getOpenApiAssistantText(messages, getOpenApiModelId(selectedModelId), openApiApiKey, openApiApiBaseUrl, assetDir, platform, {
|
|
3202
|
+
onTextChunk,
|
|
3203
|
+
stream: streamingEnabled
|
|
3204
|
+
});
|
|
3019
3205
|
if (result.type === 'success') {
|
|
3020
3206
|
const {
|
|
3021
3207
|
text: assistantText
|
|
@@ -3233,6 +3419,37 @@ const focusInput = state => {
|
|
|
3233
3419
|
};
|
|
3234
3420
|
};
|
|
3235
3421
|
|
|
3422
|
+
const appendMessageToSelectedSession = (sessions, selectedSessionId, message) => {
|
|
3423
|
+
return sessions.map(session => {
|
|
3424
|
+
if (session.id !== selectedSessionId) {
|
|
3425
|
+
return session;
|
|
3426
|
+
}
|
|
3427
|
+
return {
|
|
3428
|
+
...session,
|
|
3429
|
+
messages: [...session.messages, message]
|
|
3430
|
+
};
|
|
3431
|
+
});
|
|
3432
|
+
};
|
|
3433
|
+
const updateMessageTextInSelectedSession = (sessions, selectedSessionId, messageId, text, inProgress) => {
|
|
3434
|
+
return sessions.map(session => {
|
|
3435
|
+
if (session.id !== selectedSessionId) {
|
|
3436
|
+
return session;
|
|
3437
|
+
}
|
|
3438
|
+
return {
|
|
3439
|
+
...session,
|
|
3440
|
+
messages: session.messages.map(message => {
|
|
3441
|
+
if (message.id !== messageId) {
|
|
3442
|
+
return message;
|
|
3443
|
+
}
|
|
3444
|
+
return {
|
|
3445
|
+
...message,
|
|
3446
|
+
inProgress,
|
|
3447
|
+
text
|
|
3448
|
+
};
|
|
3449
|
+
})
|
|
3450
|
+
};
|
|
3451
|
+
});
|
|
3452
|
+
};
|
|
3236
3453
|
const handleSubmit = async state => {
|
|
3237
3454
|
const {
|
|
3238
3455
|
assetDir,
|
|
@@ -3248,6 +3465,7 @@ const handleSubmit = async state => {
|
|
|
3248
3465
|
selectedModelId,
|
|
3249
3466
|
selectedSessionId,
|
|
3250
3467
|
sessions,
|
|
3468
|
+
streamingEnabled,
|
|
3251
3469
|
useMockApi,
|
|
3252
3470
|
viewMode
|
|
3253
3471
|
} = state;
|
|
@@ -3265,6 +3483,18 @@ const handleSubmit = async state => {
|
|
|
3265
3483
|
text: userText,
|
|
3266
3484
|
time: userTime
|
|
3267
3485
|
};
|
|
3486
|
+
const assistantMessageId = `message-${nextMessageId + 1}`;
|
|
3487
|
+
const assistantTime = new Date().toLocaleTimeString([], {
|
|
3488
|
+
hour: '2-digit',
|
|
3489
|
+
minute: '2-digit'
|
|
3490
|
+
});
|
|
3491
|
+
const inProgressAssistantMessage = {
|
|
3492
|
+
id: assistantMessageId,
|
|
3493
|
+
inProgress: true,
|
|
3494
|
+
role: 'assistant',
|
|
3495
|
+
text: '',
|
|
3496
|
+
time: assistantTime
|
|
3497
|
+
};
|
|
3268
3498
|
let workingSessions = sessions;
|
|
3269
3499
|
if (viewMode === 'detail') {
|
|
3270
3500
|
const loadedSession = await getChatSession(selectedSessionId);
|
|
@@ -3282,7 +3512,7 @@ const handleSubmit = async state => {
|
|
|
3282
3512
|
const newSessionId = generateSessionId();
|
|
3283
3513
|
const newSession = {
|
|
3284
3514
|
id: newSessionId,
|
|
3285
|
-
messages: [userMessage],
|
|
3515
|
+
messages: streamingEnabled ? [userMessage, inProgressAssistantMessage] : [userMessage],
|
|
3286
3516
|
title: `Chat ${workingSessions.length + 1}`
|
|
3287
3517
|
};
|
|
3288
3518
|
await saveChatSession(newSession);
|
|
@@ -3298,15 +3528,8 @@ const handleSubmit = async state => {
|
|
|
3298
3528
|
viewMode: 'detail'
|
|
3299
3529
|
});
|
|
3300
3530
|
} else {
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
return session;
|
|
3304
|
-
}
|
|
3305
|
-
return {
|
|
3306
|
-
...session,
|
|
3307
|
-
messages: [...session.messages, userMessage]
|
|
3308
|
-
};
|
|
3309
|
-
});
|
|
3531
|
+
const updatedWithUser = appendMessageToSelectedSession(workingSessions, selectedSessionId, userMessage);
|
|
3532
|
+
const updatedSessions = streamingEnabled ? appendMessageToSelectedSession(updatedWithUser, selectedSessionId, inProgressAssistantMessage) : updatedWithUser;
|
|
3310
3533
|
const selectedSession = updatedSessions.find(session => session.id === selectedSessionId);
|
|
3311
3534
|
if (selectedSession) {
|
|
3312
3535
|
await saveChatSession(selectedSession);
|
|
@@ -3324,39 +3547,56 @@ const handleSubmit = async state => {
|
|
|
3324
3547
|
set(state.uid, state, optimisticState);
|
|
3325
3548
|
// @ts-ignore
|
|
3326
3549
|
await invoke('Chat.rerender');
|
|
3327
|
-
|
|
3328
|
-
|
|
3550
|
+
let latestState = optimisticState;
|
|
3551
|
+
let previousState = optimisticState;
|
|
3552
|
+
const selectedOptimisticSession = latestState.sessions.find(session => session.id === latestState.selectedSessionId);
|
|
3553
|
+
const messages = (selectedOptimisticSession?.messages ?? []).filter(message => !message.inProgress);
|
|
3554
|
+
const onTextChunk = streamingEnabled ? async chunk => {
|
|
3555
|
+
const selectedSession = latestState.sessions.find(session => session.id === latestState.selectedSessionId);
|
|
3556
|
+
if (!selectedSession) {
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
const assistantMessage = selectedSession.messages.find(message => message.id === assistantMessageId);
|
|
3560
|
+
if (!assistantMessage) {
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
const updatedText = assistantMessage.text + chunk;
|
|
3564
|
+
const updatedSessions = updateMessageTextInSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessageId, updatedText, true);
|
|
3565
|
+
const nextState = {
|
|
3566
|
+
...latestState,
|
|
3567
|
+
sessions: updatedSessions
|
|
3568
|
+
};
|
|
3569
|
+
set(state.uid, previousState, nextState);
|
|
3570
|
+
previousState = nextState;
|
|
3571
|
+
latestState = nextState;
|
|
3572
|
+
// @ts-ignore
|
|
3573
|
+
await invoke('Chat.rerender');
|
|
3574
|
+
} : undefined;
|
|
3329
3575
|
const assistantMessage = await getAiResponse({
|
|
3330
3576
|
assetDir,
|
|
3331
3577
|
messages,
|
|
3332
3578
|
mockApiCommandId,
|
|
3333
3579
|
models,
|
|
3334
3580
|
nextMessageId: optimisticState.nextMessageId,
|
|
3581
|
+
onTextChunk,
|
|
3335
3582
|
openApiApiBaseUrl,
|
|
3336
3583
|
openApiApiKey,
|
|
3337
3584
|
openRouterApiBaseUrl,
|
|
3338
3585
|
openRouterApiKey,
|
|
3339
3586
|
platform,
|
|
3340
3587
|
selectedModelId,
|
|
3588
|
+
streamingEnabled,
|
|
3341
3589
|
useMockApi,
|
|
3342
3590
|
userText
|
|
3343
3591
|
});
|
|
3344
|
-
const updatedSessions =
|
|
3345
|
-
|
|
3346
|
-
return session;
|
|
3347
|
-
}
|
|
3348
|
-
return {
|
|
3349
|
-
...session,
|
|
3350
|
-
messages: [...session.messages, assistantMessage]
|
|
3351
|
-
};
|
|
3352
|
-
});
|
|
3353
|
-
const selectedSession = updatedSessions.find(session => session.id === optimisticState.selectedSessionId);
|
|
3592
|
+
const updatedSessions = streamingEnabled ? updateMessageTextInSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessageId, assistantMessage.text, false) : appendMessageToSelectedSession(latestState.sessions, latestState.selectedSessionId, assistantMessage);
|
|
3593
|
+
const selectedSession = updatedSessions.find(session => session.id === latestState.selectedSessionId);
|
|
3354
3594
|
if (selectedSession) {
|
|
3355
3595
|
await saveChatSession(selectedSession);
|
|
3356
3596
|
}
|
|
3357
3597
|
return focusInput({
|
|
3358
|
-
...
|
|
3359
|
-
nextMessageId:
|
|
3598
|
+
...latestState,
|
|
3599
|
+
nextMessageId: latestState.nextMessageId + 1,
|
|
3360
3600
|
sessions: updatedSessions
|
|
3361
3601
|
});
|
|
3362
3602
|
};
|
|
@@ -3658,6 +3898,25 @@ const handleNewline = async state => {
|
|
|
3658
3898
|
return handleInput(state, Composer, `${composerValue}\n`);
|
|
3659
3899
|
};
|
|
3660
3900
|
|
|
3901
|
+
const handleChatListScroll = async (state, chatListScrollTop) => {
|
|
3902
|
+
if (state.chatListScrollTop === chatListScrollTop) {
|
|
3903
|
+
return state;
|
|
3904
|
+
}
|
|
3905
|
+
return {
|
|
3906
|
+
...state,
|
|
3907
|
+
chatListScrollTop
|
|
3908
|
+
};
|
|
3909
|
+
};
|
|
3910
|
+
const handleMessagesScroll = async (state, messagesScrollTop) => {
|
|
3911
|
+
if (state.messagesScrollTop === messagesScrollTop) {
|
|
3912
|
+
return state;
|
|
3913
|
+
}
|
|
3914
|
+
return {
|
|
3915
|
+
...state,
|
|
3916
|
+
messagesScrollTop
|
|
3917
|
+
};
|
|
3918
|
+
};
|
|
3919
|
+
|
|
3661
3920
|
const id = 7201;
|
|
3662
3921
|
const sendMessagePortToExtensionHostWorker = async port => {
|
|
3663
3922
|
await sendMessagePortToExtensionHostWorker$1(port, id);
|
|
@@ -3684,6 +3943,32 @@ const isObject = value => {
|
|
|
3684
3943
|
return typeof value === 'object' && value !== null;
|
|
3685
3944
|
};
|
|
3686
3945
|
|
|
3946
|
+
const getSavedChatListScrollTop = savedState => {
|
|
3947
|
+
if (!isObject(savedState)) {
|
|
3948
|
+
return undefined;
|
|
3949
|
+
}
|
|
3950
|
+
const {
|
|
3951
|
+
chatListScrollTop
|
|
3952
|
+
} = savedState;
|
|
3953
|
+
if (typeof chatListScrollTop !== 'number') {
|
|
3954
|
+
return undefined;
|
|
3955
|
+
}
|
|
3956
|
+
return chatListScrollTop;
|
|
3957
|
+
};
|
|
3958
|
+
|
|
3959
|
+
const getSavedMessagesScrollTop = savedState => {
|
|
3960
|
+
if (!isObject(savedState)) {
|
|
3961
|
+
return undefined;
|
|
3962
|
+
}
|
|
3963
|
+
const {
|
|
3964
|
+
messagesScrollTop
|
|
3965
|
+
} = savedState;
|
|
3966
|
+
if (typeof messagesScrollTop !== 'number') {
|
|
3967
|
+
return undefined;
|
|
3968
|
+
}
|
|
3969
|
+
return messagesScrollTop;
|
|
3970
|
+
};
|
|
3971
|
+
|
|
3687
3972
|
const getSavedSelectedModelId = savedState => {
|
|
3688
3973
|
if (!isObject(savedState)) {
|
|
3689
3974
|
return undefined;
|
|
@@ -3736,6 +4021,39 @@ const getSavedViewMode = savedState => {
|
|
|
3736
4021
|
return viewMode;
|
|
3737
4022
|
};
|
|
3738
4023
|
|
|
4024
|
+
const loadOpenApiApiKey = async () => {
|
|
4025
|
+
try {
|
|
4026
|
+
const savedOpenApiKey = await get('secrets.openApiKey');
|
|
4027
|
+
if (typeof savedOpenApiKey === 'string' && savedOpenApiKey) {
|
|
4028
|
+
return savedOpenApiKey;
|
|
4029
|
+
}
|
|
4030
|
+
const legacySavedOpenApiApiKey = await get('secrets.openApiApiKey');
|
|
4031
|
+
if (typeof legacySavedOpenApiApiKey === 'string' && legacySavedOpenApiApiKey) {
|
|
4032
|
+
return legacySavedOpenApiApiKey;
|
|
4033
|
+
}
|
|
4034
|
+
const legacySavedOpenAiApiKey = await get('secrets.openAiApiKey');
|
|
4035
|
+
return typeof legacySavedOpenAiApiKey === 'string' ? legacySavedOpenAiApiKey : '';
|
|
4036
|
+
} catch {
|
|
4037
|
+
return '';
|
|
4038
|
+
}
|
|
4039
|
+
};
|
|
4040
|
+
const loadOpenRouterApiKey = async () => {
|
|
4041
|
+
try {
|
|
4042
|
+
const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
|
|
4043
|
+
return typeof savedOpenRouterApiKey === 'string' ? savedOpenRouterApiKey : '';
|
|
4044
|
+
} catch {
|
|
4045
|
+
return '';
|
|
4046
|
+
}
|
|
4047
|
+
};
|
|
4048
|
+
const loadPreferences = async () => {
|
|
4049
|
+
const openApiApiKey = await loadOpenApiApiKey();
|
|
4050
|
+
const openRouterApiKey = await loadOpenRouterApiKey();
|
|
4051
|
+
return {
|
|
4052
|
+
openApiApiKey,
|
|
4053
|
+
openRouterApiKey
|
|
4054
|
+
};
|
|
4055
|
+
};
|
|
4056
|
+
|
|
3739
4057
|
const toSummarySession = session => {
|
|
3740
4058
|
return {
|
|
3741
4059
|
id: session.id,
|
|
@@ -3761,30 +4079,10 @@ const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
|
|
|
3761
4079
|
const loadContent = async (state, savedState) => {
|
|
3762
4080
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
3763
4081
|
const savedViewMode = getSavedViewMode(savedState);
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
openApiApiKey = savedOpenApiKey;
|
|
3769
|
-
} else {
|
|
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
|
-
}
|
|
4082
|
+
const {
|
|
4083
|
+
openApiApiKey,
|
|
4084
|
+
openRouterApiKey
|
|
4085
|
+
} = await loadPreferences();
|
|
3788
4086
|
const legacySavedSessions = getSavedSessions(savedState);
|
|
3789
4087
|
const storedSessions = await listChatSessions();
|
|
3790
4088
|
let sessions = storedSessions;
|
|
@@ -3802,6 +4100,8 @@ const loadContent = async (state, savedState) => {
|
|
|
3802
4100
|
}
|
|
3803
4101
|
const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
|
|
3804
4102
|
const preferredModelId = savedSelectedModelId || state.selectedModelId;
|
|
4103
|
+
const chatListScrollTop = getSavedChatListScrollTop(savedState) ?? state.chatListScrollTop;
|
|
4104
|
+
const messagesScrollTop = getSavedMessagesScrollTop(savedState) ?? state.messagesScrollTop;
|
|
3805
4105
|
const selectedModelId = state.models.some(model => model.id === preferredModelId) ? preferredModelId : state.models[0]?.id || '';
|
|
3806
4106
|
const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
|
|
3807
4107
|
sessions = await loadSelectedSessionMessages(sessions, selectedSessionId);
|
|
@@ -3809,7 +4109,9 @@ const loadContent = async (state, savedState) => {
|
|
|
3809
4109
|
const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
|
|
3810
4110
|
return {
|
|
3811
4111
|
...state,
|
|
4112
|
+
chatListScrollTop,
|
|
3812
4113
|
initial: false,
|
|
4114
|
+
messagesScrollTop,
|
|
3813
4115
|
openApiApiKey,
|
|
3814
4116
|
openApiApiKeyInput: openApiApiKey,
|
|
3815
4117
|
openRouterApiKey,
|
|
@@ -3946,6 +4248,8 @@ const HandleClickList = 17;
|
|
|
3946
4248
|
const HandleClickDelete = 18;
|
|
3947
4249
|
const HandleSubmit = 19;
|
|
3948
4250
|
const HandleModelChange = 20;
|
|
4251
|
+
const HandleChatListScroll = 21;
|
|
4252
|
+
const HandleMessagesScroll = 22;
|
|
3949
4253
|
|
|
3950
4254
|
const getModelLabel = model => {
|
|
3951
4255
|
if (model.provider === 'openRouter') {
|
|
@@ -4253,18 +4557,20 @@ const getEmptyMessagesDom = () => {
|
|
|
4253
4557
|
}, text(startConversation())];
|
|
4254
4558
|
};
|
|
4255
4559
|
|
|
4256
|
-
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
4560
|
+
const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', messagesScrollTop = 0) => {
|
|
4257
4561
|
if (messages.length === 0) {
|
|
4258
4562
|
return getEmptyMessagesDom();
|
|
4259
4563
|
}
|
|
4260
4564
|
return [{
|
|
4261
4565
|
childCount: messages.length,
|
|
4262
4566
|
className: 'ChatMessages',
|
|
4567
|
+
onScroll: HandleMessagesScroll,
|
|
4568
|
+
scrollTop: messagesScrollTop,
|
|
4263
4569
|
type: Div
|
|
4264
4570
|
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
4265
4571
|
};
|
|
4266
4572
|
|
|
4267
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
4573
|
+
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
4574
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
4269
4575
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
4270
4576
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
@@ -4272,7 +4578,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
|
|
|
4272
4578
|
childCount: 3,
|
|
4273
4579
|
className: mergeClassNames(Viewlet, Chat),
|
|
4274
4580
|
type: Div
|
|
4275
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4581
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4276
4582
|
};
|
|
4277
4583
|
|
|
4278
4584
|
const getChatHeaderListModeDom = () => {
|
|
@@ -4329,7 +4635,7 @@ const getSessionDom = session => {
|
|
|
4329
4635
|
}, text('🗑')];
|
|
4330
4636
|
};
|
|
4331
4637
|
|
|
4332
|
-
const getChatListDom = (sessions, selectedSessionId) => {
|
|
4638
|
+
const getChatListDom = (sessions, selectedSessionId, chatListScrollTop = 0) => {
|
|
4333
4639
|
if (sessions.length === 0) {
|
|
4334
4640
|
return getEmptyChatSessionsDom();
|
|
4335
4641
|
}
|
|
@@ -4337,16 +4643,18 @@ const getChatListDom = (sessions, selectedSessionId) => {
|
|
|
4337
4643
|
childCount: sessions.length,
|
|
4338
4644
|
className: ChatList,
|
|
4339
4645
|
onClick: HandleClickList,
|
|
4646
|
+
onScroll: HandleChatListScroll,
|
|
4647
|
+
scrollTop: chatListScrollTop,
|
|
4340
4648
|
type: Div
|
|
4341
4649
|
}, ...sessions.flatMap(getSessionDom)];
|
|
4342
4650
|
};
|
|
4343
4651
|
|
|
4344
|
-
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20) => {
|
|
4652
|
+
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0) => {
|
|
4345
4653
|
return [{
|
|
4346
4654
|
childCount: 3,
|
|
4347
4655
|
className: mergeClassNames(Viewlet, Chat),
|
|
4348
4656
|
type: Div
|
|
4349
|
-
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4657
|
+
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
|
|
4350
4658
|
};
|
|
4351
4659
|
|
|
4352
4660
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -4356,12 +4664,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
4356
4664
|
}, text(unknownViewMode())];
|
|
4357
4665
|
};
|
|
4358
4666
|
|
|
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) => {
|
|
4667
|
+
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
4668
|
switch (viewMode) {
|
|
4361
4669
|
case 'detail':
|
|
4362
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
4670
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop);
|
|
4363
4671
|
case 'list':
|
|
4364
|
-
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
4672
|
+
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop);
|
|
4365
4673
|
default:
|
|
4366
4674
|
return getChatModeUnsupportedVirtualDom();
|
|
4367
4675
|
}
|
|
@@ -4369,12 +4677,14 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
4369
4677
|
|
|
4370
4678
|
const renderItems = (oldState, newState) => {
|
|
4371
4679
|
const {
|
|
4680
|
+
chatListScrollTop,
|
|
4372
4681
|
composerFontFamily,
|
|
4373
4682
|
composerFontSize,
|
|
4374
4683
|
composerHeight,
|
|
4375
4684
|
composerLineHeight,
|
|
4376
4685
|
composerValue,
|
|
4377
4686
|
initial,
|
|
4687
|
+
messagesScrollTop,
|
|
4378
4688
|
models,
|
|
4379
4689
|
openApiApiKeyInput,
|
|
4380
4690
|
openRouterApiKeyInput,
|
|
@@ -4391,7 +4701,7 @@ const renderItems = (oldState, newState) => {
|
|
|
4391
4701
|
if (initial) {
|
|
4392
4702
|
return [SetDom2, uid, []];
|
|
4393
4703
|
}
|
|
4394
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight);
|
|
4704
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop);
|
|
4395
4705
|
return [SetDom2, uid, dom];
|
|
4396
4706
|
};
|
|
4397
4707
|
|
|
@@ -4402,6 +4712,14 @@ const renderIncremental = (oldState, newState) => {
|
|
|
4402
4712
|
return [SetPatches, newState.uid, patches];
|
|
4403
4713
|
};
|
|
4404
4714
|
|
|
4715
|
+
const renderScrollTop = (oldState, newState) => {
|
|
4716
|
+
const {
|
|
4717
|
+
messagesScrollTop,
|
|
4718
|
+
uid
|
|
4719
|
+
} = newState;
|
|
4720
|
+
return [SetProperty, uid, '.ChatMessages', 'scrollTop', messagesScrollTop];
|
|
4721
|
+
};
|
|
4722
|
+
|
|
4405
4723
|
const renderValue = (oldState, newState) => {
|
|
4406
4724
|
const {
|
|
4407
4725
|
composerValue
|
|
@@ -4421,6 +4739,8 @@ const getRenderer = diffType => {
|
|
|
4421
4739
|
return renderIncremental;
|
|
4422
4740
|
case RenderItems:
|
|
4423
4741
|
return renderItems;
|
|
4742
|
+
case RenderScrollTop:
|
|
4743
|
+
return renderScrollTop;
|
|
4424
4744
|
case RenderValue:
|
|
4425
4745
|
return renderValue;
|
|
4426
4746
|
default:
|
|
@@ -4482,6 +4802,12 @@ const renderEventListeners = () => {
|
|
|
4482
4802
|
}, {
|
|
4483
4803
|
name: HandleModelChange,
|
|
4484
4804
|
params: ['handleModelChange', TargetValue]
|
|
4805
|
+
}, {
|
|
4806
|
+
name: HandleChatListScroll,
|
|
4807
|
+
params: ['handleChatListScroll', 'event.target.scrollTop']
|
|
4808
|
+
}, {
|
|
4809
|
+
name: HandleMessagesScroll,
|
|
4810
|
+
params: ['handleMessagesScroll', 'event.target.scrollTop']
|
|
4485
4811
|
}, {
|
|
4486
4812
|
name: HandleFocus,
|
|
4487
4813
|
params: ['handleInputFocus', TargetName]
|
|
@@ -4520,8 +4846,10 @@ const resize = (state, dimensions) => {
|
|
|
4520
4846
|
|
|
4521
4847
|
const saveState = state => {
|
|
4522
4848
|
const {
|
|
4849
|
+
chatListScrollTop,
|
|
4523
4850
|
composerValue,
|
|
4524
4851
|
height,
|
|
4852
|
+
messagesScrollTop,
|
|
4525
4853
|
nextMessageId,
|
|
4526
4854
|
renamingSessionId,
|
|
4527
4855
|
selectedModelId,
|
|
@@ -4532,8 +4860,10 @@ const saveState = state => {
|
|
|
4532
4860
|
y
|
|
4533
4861
|
} = state;
|
|
4534
4862
|
return {
|
|
4863
|
+
chatListScrollTop,
|
|
4535
4864
|
composerValue,
|
|
4536
4865
|
height,
|
|
4866
|
+
messagesScrollTop,
|
|
4537
4867
|
nextMessageId,
|
|
4538
4868
|
renamingSessionId,
|
|
4539
4869
|
selectedModelId,
|
|
@@ -4591,6 +4921,7 @@ const commandMap = {
|
|
|
4591
4921
|
'Chat.getKeyBindings': getKeyBindings,
|
|
4592
4922
|
'Chat.getSelectedSessionId': wrapGetter(getSelectedSessionId),
|
|
4593
4923
|
'Chat.handleChatListContextMenu': handleChatListContextMenu,
|
|
4924
|
+
'Chat.handleChatListScroll': wrapCommand(handleChatListScroll),
|
|
4594
4925
|
'Chat.handleClick': wrapCommand(handleClick),
|
|
4595
4926
|
'Chat.handleClickBack': wrapCommand(handleClickBack),
|
|
4596
4927
|
'Chat.handleClickClose': handleClickClose,
|
|
@@ -4601,6 +4932,7 @@ const commandMap = {
|
|
|
4601
4932
|
'Chat.handleInput': wrapCommand(handleInput),
|
|
4602
4933
|
'Chat.handleInputFocus': wrapCommand(handleInputFocus),
|
|
4603
4934
|
'Chat.handleKeyDown': wrapCommand(handleKeyDown),
|
|
4935
|
+
'Chat.handleMessagesScroll': wrapCommand(handleMessagesScroll),
|
|
4604
4936
|
'Chat.handleModelChange': wrapCommand(handleModelChange),
|
|
4605
4937
|
'Chat.handleSubmit': wrapCommand(handleSubmit),
|
|
4606
4938
|
'Chat.initialize': initialize,
|