@lvce-editor/chat-view 2.5.0 → 2.7.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 -35
- package/package.json +1 -1
|
@@ -1078,6 +1078,8 @@ const {
|
|
|
1078
1078
|
set: set$3
|
|
1079
1079
|
} = create$2(6002);
|
|
1080
1080
|
|
|
1081
|
+
const Ol$1 = 49;
|
|
1082
|
+
|
|
1081
1083
|
const ClientX = 'event.clientX';
|
|
1082
1084
|
const ClientY = 'event.clientY';
|
|
1083
1085
|
const Key = 'event.key';
|
|
@@ -1467,6 +1469,7 @@ const createDefaultState = () => {
|
|
|
1467
1469
|
composerHeight: composerLineHeight + 8,
|
|
1468
1470
|
composerLineHeight,
|
|
1469
1471
|
composerValue: '',
|
|
1472
|
+
emitStreamingFunctionCallEvents: false,
|
|
1470
1473
|
errorCount: 0,
|
|
1471
1474
|
focus: 'composer',
|
|
1472
1475
|
focused: false,
|
|
@@ -2739,22 +2742,166 @@ const readNextChunk = async () => {
|
|
|
2739
2742
|
return promise;
|
|
2740
2743
|
};
|
|
2741
2744
|
|
|
2742
|
-
const
|
|
2745
|
+
const parseSseDataLines = eventChunk => {
|
|
2746
|
+
const lines = eventChunk.split('\n');
|
|
2747
|
+
const dataLines = [];
|
|
2748
|
+
for (const line of lines) {
|
|
2749
|
+
if (!line.startsWith('data:')) {
|
|
2750
|
+
continue;
|
|
2751
|
+
}
|
|
2752
|
+
dataLines.push(line.slice(5).trimStart());
|
|
2753
|
+
}
|
|
2754
|
+
return dataLines;
|
|
2755
|
+
};
|
|
2756
|
+
const emitToolCalls = async (toolCallAccumulator, onToolCallsChunk) => {
|
|
2757
|
+
if (!onToolCallsChunk) {
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const toolCalls = Object.entries(toolCallAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => !!toolCall.name);
|
|
2761
|
+
if (toolCalls.length === 0) {
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
await onToolCallsChunk(toolCalls);
|
|
2765
|
+
};
|
|
2766
|
+
const getMockOpenApiAssistantText = async (stream, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished) => {
|
|
2743
2767
|
const error = takeErrorResponse();
|
|
2744
2768
|
if (error) {
|
|
2745
2769
|
return error;
|
|
2746
2770
|
}
|
|
2747
2771
|
let text = '';
|
|
2772
|
+
let remainder = '';
|
|
2773
|
+
let toolCallAccumulator = {};
|
|
2774
|
+
let finishedNotified = false;
|
|
2775
|
+
const notifyFinished = async () => {
|
|
2776
|
+
if (finishedNotified) {
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
finishedNotified = true;
|
|
2780
|
+
if (onEventStreamFinished) {
|
|
2781
|
+
await onEventStreamFinished();
|
|
2782
|
+
}
|
|
2783
|
+
};
|
|
2784
|
+
const handleParsedSseEvent = async parsed => {
|
|
2785
|
+
if (onDataEvent) {
|
|
2786
|
+
await onDataEvent(parsed);
|
|
2787
|
+
}
|
|
2788
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
const eventType = Reflect.get(parsed, 'type');
|
|
2792
|
+
if (eventType === 'response.completed') {
|
|
2793
|
+
await notifyFinished();
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
if (eventType === 'response.output_text.delta') {
|
|
2797
|
+
const delta = Reflect.get(parsed, 'delta');
|
|
2798
|
+
if (typeof delta !== 'string' || !delta) {
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
text += delta;
|
|
2802
|
+
if (stream && onTextChunk) {
|
|
2803
|
+
await onTextChunk(delta);
|
|
2804
|
+
}
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
if (eventType === 'response.output_item.added') {
|
|
2808
|
+
const outputIndex = Reflect.get(parsed, 'output_index');
|
|
2809
|
+
const item = Reflect.get(parsed, 'item');
|
|
2810
|
+
if (typeof outputIndex !== 'number' || !item || typeof item !== 'object') {
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
if (Reflect.get(item, 'type') !== 'function_call') {
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
const name = Reflect.get(item, 'name');
|
|
2817
|
+
const argumentsValue = Reflect.get(item, 'arguments');
|
|
2818
|
+
const callId = Reflect.get(item, 'call_id');
|
|
2819
|
+
toolCallAccumulator = {
|
|
2820
|
+
...toolCallAccumulator,
|
|
2821
|
+
[outputIndex]: {
|
|
2822
|
+
arguments: typeof argumentsValue === 'string' ? argumentsValue : '',
|
|
2823
|
+
...(typeof callId === 'string' ? {
|
|
2824
|
+
id: callId
|
|
2825
|
+
} : {}),
|
|
2826
|
+
name: typeof name === 'string' ? name : ''
|
|
2827
|
+
}
|
|
2828
|
+
};
|
|
2829
|
+
await emitToolCalls(toolCallAccumulator, onToolCallsChunk);
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
if (eventType === 'response.function_call_arguments.delta' || eventType === 'response.function_call_arguments.done') {
|
|
2833
|
+
const outputIndex = Reflect.get(parsed, 'output_index');
|
|
2834
|
+
if (typeof outputIndex !== 'number') {
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
const current = toolCallAccumulator[outputIndex] || {
|
|
2838
|
+
arguments: '',
|
|
2839
|
+
name: ''
|
|
2840
|
+
};
|
|
2841
|
+
const delta = Reflect.get(parsed, 'delta');
|
|
2842
|
+
const argumentsValue = Reflect.get(parsed, 'arguments');
|
|
2843
|
+
const name = Reflect.get(parsed, 'name');
|
|
2844
|
+
const callId = Reflect.get(parsed, 'call_id');
|
|
2845
|
+
const next = {
|
|
2846
|
+
arguments: typeof argumentsValue === 'string' ? argumentsValue : typeof delta === 'string' ? `${current.arguments}${delta}` : current.arguments,
|
|
2847
|
+
...(typeof callId === 'string' ? {
|
|
2848
|
+
id: callId
|
|
2849
|
+
} : current.id ? {
|
|
2850
|
+
id: current.id
|
|
2851
|
+
} : {}),
|
|
2852
|
+
name: typeof name === 'string' && name ? name : current.name
|
|
2853
|
+
};
|
|
2854
|
+
toolCallAccumulator = {
|
|
2855
|
+
...toolCallAccumulator,
|
|
2856
|
+
[outputIndex]: next
|
|
2857
|
+
};
|
|
2858
|
+
await emitToolCalls(toolCallAccumulator, onToolCallsChunk);
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
const consumeSseDataLines = async dataLines => {
|
|
2862
|
+
for (const line of dataLines) {
|
|
2863
|
+
if (line === '[DONE]') {
|
|
2864
|
+
await notifyFinished();
|
|
2865
|
+
continue;
|
|
2866
|
+
}
|
|
2867
|
+
let parsed;
|
|
2868
|
+
try {
|
|
2869
|
+
parsed = JSON.parse(line);
|
|
2870
|
+
} catch {
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
await handleParsedSseEvent(parsed);
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2748
2876
|
while (true) {
|
|
2749
2877
|
const chunk = await readNextChunk();
|
|
2750
2878
|
if (typeof chunk !== 'string') {
|
|
2751
2879
|
break;
|
|
2752
2880
|
}
|
|
2881
|
+
if (chunk.startsWith('data:')) {
|
|
2882
|
+
remainder += chunk;
|
|
2883
|
+
while (true) {
|
|
2884
|
+
const separatorIndex = remainder.indexOf('\n\n');
|
|
2885
|
+
if (separatorIndex === -1) {
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
const rawEvent = remainder.slice(0, separatorIndex);
|
|
2889
|
+
remainder = remainder.slice(separatorIndex + 2);
|
|
2890
|
+
const dataLines = parseSseDataLines(rawEvent);
|
|
2891
|
+
await consumeSseDataLines(dataLines);
|
|
2892
|
+
}
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2753
2895
|
text += chunk;
|
|
2754
2896
|
if (stream && onTextChunk) {
|
|
2755
2897
|
await onTextChunk(chunk);
|
|
2756
2898
|
}
|
|
2757
2899
|
}
|
|
2900
|
+
if (remainder) {
|
|
2901
|
+
const dataLines = parseSseDataLines(remainder);
|
|
2902
|
+
await consumeSseDataLines(dataLines);
|
|
2903
|
+
}
|
|
2904
|
+
await notifyFinished();
|
|
2758
2905
|
return {
|
|
2759
2906
|
text,
|
|
2760
2907
|
type: 'success'
|
|
@@ -3309,7 +3456,14 @@ const updateToolCallAccumulator = (accumulator, chunk) => {
|
|
|
3309
3456
|
toolCalls
|
|
3310
3457
|
};
|
|
3311
3458
|
};
|
|
3312
|
-
const
|
|
3459
|
+
const getResponseFunctionCallsFromStreamingAccumulator = toolCallAccumulator => {
|
|
3460
|
+
return Object.entries(toolCallAccumulator).toSorted((a, b) => Number(a[0]) - Number(b[0])).map(entry => entry[1]).filter(toolCall => typeof toolCall.id === 'string' && !!toolCall.id && !!toolCall.name).map(toolCall => ({
|
|
3461
|
+
arguments: toolCall.arguments,
|
|
3462
|
+
callId: toolCall.id,
|
|
3463
|
+
name: toolCall.name
|
|
3464
|
+
}));
|
|
3465
|
+
};
|
|
3466
|
+
const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDataEvent) => {
|
|
3313
3467
|
if (!response.body) {
|
|
3314
3468
|
return {
|
|
3315
3469
|
details: 'request-failed',
|
|
@@ -3321,17 +3475,9 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
|
|
|
3321
3475
|
let remainder = '';
|
|
3322
3476
|
let text = '';
|
|
3323
3477
|
let done = false;
|
|
3324
|
-
let finishedNotified = false;
|
|
3325
3478
|
let toolCallAccumulator = {};
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
return;
|
|
3329
|
-
}
|
|
3330
|
-
finishedNotified = true;
|
|
3331
|
-
if (onEventStreamFinished) {
|
|
3332
|
-
await onEventStreamFinished();
|
|
3333
|
-
}
|
|
3334
|
-
};
|
|
3479
|
+
let responseId;
|
|
3480
|
+
let completedResponseFunctionCalls = [];
|
|
3335
3481
|
const emitToolCallAccumulator = async () => {
|
|
3336
3482
|
if (!onToolCallsChunk) {
|
|
3337
3483
|
return;
|
|
@@ -3351,7 +3497,25 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
|
|
|
3351
3497
|
}
|
|
3352
3498
|
const eventType = Reflect.get(parsed, 'type');
|
|
3353
3499
|
if (eventType === 'response.completed') {
|
|
3354
|
-
|
|
3500
|
+
const response = Reflect.get(parsed, 'response');
|
|
3501
|
+
if (response && typeof response === 'object') {
|
|
3502
|
+
const parsedResponseId = Reflect.get(response, 'id');
|
|
3503
|
+
if (typeof parsedResponseId === 'string' && parsedResponseId) {
|
|
3504
|
+
responseId = parsedResponseId;
|
|
3505
|
+
}
|
|
3506
|
+
completedResponseFunctionCalls = getResponseFunctionCalls(response);
|
|
3507
|
+
}
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
if (eventType === 'response.created' || eventType === 'response.in_progress') {
|
|
3511
|
+
const response = Reflect.get(parsed, 'response');
|
|
3512
|
+
if (!response || typeof response !== 'object') {
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
const parsedResponseId = Reflect.get(response, 'id');
|
|
3516
|
+
if (typeof parsedResponseId === 'string' && parsedResponseId) {
|
|
3517
|
+
responseId = parsedResponseId;
|
|
3518
|
+
}
|
|
3355
3519
|
return;
|
|
3356
3520
|
}
|
|
3357
3521
|
if (eventType === 'response.output_text.delta') {
|
|
@@ -3392,6 +3556,38 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
|
|
|
3392
3556
|
await emitToolCallAccumulator();
|
|
3393
3557
|
return;
|
|
3394
3558
|
}
|
|
3559
|
+
if (eventType === 'response.output_item.done') {
|
|
3560
|
+
const outputIndex = Reflect.get(parsed, 'output_index');
|
|
3561
|
+
const item = Reflect.get(parsed, 'item');
|
|
3562
|
+
if (typeof outputIndex !== 'number' || !item || typeof item !== 'object') {
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
3565
|
+
const itemType = Reflect.get(item, 'type');
|
|
3566
|
+
if (itemType !== 'function_call') {
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
const callId = Reflect.get(item, 'call_id');
|
|
3570
|
+
const name = Reflect.get(item, 'name');
|
|
3571
|
+
const rawArguments = Reflect.get(item, 'arguments');
|
|
3572
|
+
const current = toolCallAccumulator[outputIndex] || {
|
|
3573
|
+
arguments: '',
|
|
3574
|
+
name: ''
|
|
3575
|
+
};
|
|
3576
|
+
toolCallAccumulator = {
|
|
3577
|
+
...toolCallAccumulator,
|
|
3578
|
+
[outputIndex]: {
|
|
3579
|
+
arguments: typeof rawArguments === 'string' ? rawArguments : current.arguments,
|
|
3580
|
+
...(typeof callId === 'string' ? {
|
|
3581
|
+
id: callId
|
|
3582
|
+
} : current.id ? {
|
|
3583
|
+
id: current.id
|
|
3584
|
+
} : {}),
|
|
3585
|
+
name: typeof name === 'string' && name ? name : current.name
|
|
3586
|
+
}
|
|
3587
|
+
};
|
|
3588
|
+
await emitToolCallAccumulator();
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3395
3591
|
if (eventType === 'response.function_call_arguments.delta' || eventType === 'response.function_call_arguments.done') {
|
|
3396
3592
|
const outputIndex = Reflect.get(parsed, 'output_index');
|
|
3397
3593
|
if (typeof outputIndex !== 'number') {
|
|
@@ -3476,7 +3672,6 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
|
|
|
3476
3672
|
}
|
|
3477
3673
|
for (const line of dataLines) {
|
|
3478
3674
|
if (line === '[DONE]') {
|
|
3479
|
-
await notifyFinished();
|
|
3480
3675
|
done = true;
|
|
3481
3676
|
break;
|
|
3482
3677
|
}
|
|
@@ -3494,7 +3689,6 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
|
|
|
3494
3689
|
const dataLines = parseSseEvent(remainder);
|
|
3495
3690
|
for (const line of dataLines) {
|
|
3496
3691
|
if (line === '[DONE]') {
|
|
3497
|
-
await notifyFinished();
|
|
3498
3692
|
continue;
|
|
3499
3693
|
}
|
|
3500
3694
|
let parsed;
|
|
@@ -3506,8 +3700,12 @@ const parseOpenApiStream = async (response, onTextChunk, onToolCallsChunk, onDat
|
|
|
3506
3700
|
await handleParsedStreamEvent(parsed);
|
|
3507
3701
|
}
|
|
3508
3702
|
}
|
|
3509
|
-
|
|
3703
|
+
const responseFunctionCalls = completedResponseFunctionCalls.length > 0 ? completedResponseFunctionCalls : getResponseFunctionCallsFromStreamingAccumulator(toolCallAccumulator);
|
|
3510
3704
|
return {
|
|
3705
|
+
...(responseId ? {
|
|
3706
|
+
responseId
|
|
3707
|
+
} : {}),
|
|
3708
|
+
responseFunctionCalls,
|
|
3511
3709
|
text,
|
|
3512
3710
|
type: 'success'
|
|
3513
3711
|
};
|
|
@@ -3599,7 +3797,20 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
3599
3797
|
};
|
|
3600
3798
|
}
|
|
3601
3799
|
if (stream) {
|
|
3602
|
-
|
|
3800
|
+
const streamResult = await parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent);
|
|
3801
|
+
if (streamResult.type !== 'success') {
|
|
3802
|
+
return streamResult;
|
|
3803
|
+
}
|
|
3804
|
+
if (streamResult.responseId) {
|
|
3805
|
+
previousResponseId = streamResult.responseId;
|
|
3806
|
+
}
|
|
3807
|
+
if (onEventStreamFinished) {
|
|
3808
|
+
await onEventStreamFinished();
|
|
3809
|
+
}
|
|
3810
|
+
return {
|
|
3811
|
+
text: streamResult.text,
|
|
3812
|
+
type: 'success'
|
|
3813
|
+
};
|
|
3603
3814
|
}
|
|
3604
3815
|
let parsed;
|
|
3605
3816
|
try {
|
|
@@ -4079,7 +4290,7 @@ const getAiResponse = async ({
|
|
|
4079
4290
|
const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
|
|
4080
4291
|
if (usesOpenApiModel) {
|
|
4081
4292
|
if (useMockApi) {
|
|
4082
|
-
const result = await getMockOpenApiAssistantText(streamingEnabled, onTextChunk);
|
|
4293
|
+
const result = await getMockOpenApiAssistantText(streamingEnabled, onTextChunk, onToolCallsChunk, onDataEvent, onEventStreamFinished);
|
|
4083
4294
|
if (result.type === 'success') {
|
|
4084
4295
|
const {
|
|
4085
4296
|
text: assistantText
|
|
@@ -4430,10 +4641,53 @@ const appendMessageToSelectedSession = (sessions, selectedSessionId, message) =>
|
|
|
4430
4641
|
};
|
|
4431
4642
|
});
|
|
4432
4643
|
};
|
|
4644
|
+
const hasLegacyStreamingToolCalls = parsed => {
|
|
4645
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
4646
|
+
return false;
|
|
4647
|
+
}
|
|
4648
|
+
const choices = Reflect.get(parsed, 'choices');
|
|
4649
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
4650
|
+
return false;
|
|
4651
|
+
}
|
|
4652
|
+
const firstChoice = choices[0];
|
|
4653
|
+
if (!firstChoice || typeof firstChoice !== 'object') {
|
|
4654
|
+
return false;
|
|
4655
|
+
}
|
|
4656
|
+
const delta = Reflect.get(firstChoice, 'delta');
|
|
4657
|
+
if (!delta || typeof delta !== 'object') {
|
|
4658
|
+
return false;
|
|
4659
|
+
}
|
|
4660
|
+
const toolCalls = Reflect.get(delta, 'tool_calls');
|
|
4661
|
+
return Array.isArray(toolCalls) && toolCalls.length > 0;
|
|
4662
|
+
};
|
|
4663
|
+
const isStreamingFunctionCallEvent = parsed => {
|
|
4664
|
+
if (hasLegacyStreamingToolCalls(parsed)) {
|
|
4665
|
+
return true;
|
|
4666
|
+
}
|
|
4667
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
4668
|
+
return false;
|
|
4669
|
+
}
|
|
4670
|
+
const type = Reflect.get(parsed, 'type');
|
|
4671
|
+
if (type === 'response.function_call_arguments.delta' || type === 'response.function_call_arguments.done') {
|
|
4672
|
+
return true;
|
|
4673
|
+
}
|
|
4674
|
+
if (type !== 'response.output_item.added' && type !== 'response.output_item.done') {
|
|
4675
|
+
return false;
|
|
4676
|
+
}
|
|
4677
|
+
const item = Reflect.get(parsed, 'item');
|
|
4678
|
+
if (!item || typeof item !== 'object') {
|
|
4679
|
+
return false;
|
|
4680
|
+
}
|
|
4681
|
+
return Reflect.get(item, 'type') === 'function_call';
|
|
4682
|
+
};
|
|
4683
|
+
const getSseEventType = value => {
|
|
4684
|
+
return value && typeof value === 'object' && Reflect.get(value, 'type') === 'response.completed' ? 'sse-response-completed' : 'sse-response-part';
|
|
4685
|
+
};
|
|
4433
4686
|
const handleSubmit = async state => {
|
|
4434
4687
|
const {
|
|
4435
4688
|
assetDir,
|
|
4436
4689
|
composerValue,
|
|
4690
|
+
emitStreamingFunctionCallEvents,
|
|
4437
4691
|
mockAiResponseDelay,
|
|
4438
4692
|
mockApiCommandId,
|
|
4439
4693
|
models,
|
|
@@ -4559,10 +4813,14 @@ const handleSubmit = async state => {
|
|
|
4559
4813
|
mockApiCommandId,
|
|
4560
4814
|
models,
|
|
4561
4815
|
onDataEvent: async value => {
|
|
4816
|
+
if (!emitStreamingFunctionCallEvents && isStreamingFunctionCallEvent(value)) {
|
|
4817
|
+
return;
|
|
4818
|
+
}
|
|
4819
|
+
const sseEventType = getSseEventType(value);
|
|
4562
4820
|
await appendChatViewEvent({
|
|
4563
4821
|
sessionId: optimisticState.selectedSessionId,
|
|
4564
4822
|
timestamp: new Date().toISOString(),
|
|
4565
|
-
type:
|
|
4823
|
+
type: sseEventType,
|
|
4566
4824
|
value
|
|
4567
4825
|
});
|
|
4568
4826
|
},
|
|
@@ -4770,6 +5028,13 @@ const handleClickNew = async state => {
|
|
|
4770
5028
|
return createSession(state);
|
|
4771
5029
|
};
|
|
4772
5030
|
|
|
5031
|
+
const handleClickReadFile = async uri => {
|
|
5032
|
+
if (!uri) {
|
|
5033
|
+
return;
|
|
5034
|
+
}
|
|
5035
|
+
await invoke('Main.openUri', uri);
|
|
5036
|
+
};
|
|
5037
|
+
|
|
4773
5038
|
const handleClickSessionDebug = async state => {
|
|
4774
5039
|
await invoke('Main.openUri', `chat-debug://${state.selectedSessionId}`);
|
|
4775
5040
|
return state;
|
|
@@ -5040,6 +5305,15 @@ const getSavedViewMode = savedState => {
|
|
|
5040
5305
|
return viewMode;
|
|
5041
5306
|
};
|
|
5042
5307
|
|
|
5308
|
+
const loadEmitStreamingFunctionCallEvents = async () => {
|
|
5309
|
+
try {
|
|
5310
|
+
const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
|
|
5311
|
+
return typeof savedEmitStreamingFunctionCallEvents === 'boolean' ? savedEmitStreamingFunctionCallEvents : false;
|
|
5312
|
+
} catch {
|
|
5313
|
+
return false;
|
|
5314
|
+
}
|
|
5315
|
+
};
|
|
5316
|
+
|
|
5043
5317
|
const loadOpenApiApiKey = async () => {
|
|
5044
5318
|
try {
|
|
5045
5319
|
const savedOpenApiKey = await get('secrets.openApiKey');
|
|
@@ -5087,9 +5361,11 @@ const loadStreamingEnabled = async () => {
|
|
|
5087
5361
|
const loadPreferences = async () => {
|
|
5088
5362
|
const openApiApiKey = await loadOpenApiApiKey();
|
|
5089
5363
|
const openRouterApiKey = await loadOpenRouterApiKey();
|
|
5364
|
+
const emitStreamingFunctionCallEvents = await loadEmitStreamingFunctionCallEvents();
|
|
5090
5365
|
const streamingEnabled = await loadStreamingEnabled();
|
|
5091
5366
|
const passIncludeObfuscation = await loadPassIncludeObfuscation();
|
|
5092
5367
|
return {
|
|
5368
|
+
emitStreamingFunctionCallEvents,
|
|
5093
5369
|
openApiApiKey,
|
|
5094
5370
|
openRouterApiKey,
|
|
5095
5371
|
passIncludeObfuscation,
|
|
@@ -5124,6 +5400,7 @@ const loadContent = async (state, savedState) => {
|
|
|
5124
5400
|
const savedSelectedModelId = getSavedSelectedModelId(savedState);
|
|
5125
5401
|
const savedViewMode = getSavedViewMode(savedState);
|
|
5126
5402
|
const {
|
|
5403
|
+
emitStreamingFunctionCallEvents,
|
|
5127
5404
|
openApiApiKey,
|
|
5128
5405
|
openRouterApiKey,
|
|
5129
5406
|
passIncludeObfuscation,
|
|
@@ -5156,6 +5433,7 @@ const loadContent = async (state, savedState) => {
|
|
|
5156
5433
|
return {
|
|
5157
5434
|
...state,
|
|
5158
5435
|
chatListScrollTop,
|
|
5436
|
+
emitStreamingFunctionCallEvents,
|
|
5159
5437
|
initial: false,
|
|
5160
5438
|
messagesScrollTop,
|
|
5161
5439
|
openApiApiKey,
|
|
@@ -5287,6 +5565,7 @@ const ChatHeader = 'ChatHeader';
|
|
|
5287
5565
|
const Button = 'Button';
|
|
5288
5566
|
const ButtonPrimary = 'ButtonPrimary';
|
|
5289
5567
|
const ButtonSecondary = 'ButtonSecondary';
|
|
5568
|
+
const FileIcon = 'FileIcon';
|
|
5290
5569
|
const IconButton = 'IconButton';
|
|
5291
5570
|
const InputBox = 'InputBox';
|
|
5292
5571
|
const Label = 'Label';
|
|
@@ -5327,6 +5606,7 @@ const HandleModelChange = 20;
|
|
|
5327
5606
|
const HandleChatListScroll = 21;
|
|
5328
5607
|
const HandleMessagesScroll = 22;
|
|
5329
5608
|
const HandleClickSessionDebug = 23;
|
|
5609
|
+
const HandleClickReadFile = 24;
|
|
5330
5610
|
|
|
5331
5611
|
const getModelLabel = model => {
|
|
5332
5612
|
if (model.provider === 'openRouter') {
|
|
@@ -5641,6 +5921,94 @@ const getToolCallArgumentPreview = rawArguments => {
|
|
|
5641
5921
|
return rawArguments;
|
|
5642
5922
|
};
|
|
5643
5923
|
|
|
5924
|
+
const getFileNameFromUri = uri => {
|
|
5925
|
+
const stripped = uri.replace(/[?#].*$/, '').replace(/\/$/, '');
|
|
5926
|
+
const slashIndex = Math.max(stripped.lastIndexOf('/'), stripped.lastIndexOf('\\\\'));
|
|
5927
|
+
const fileName = slashIndex === -1 ? stripped : stripped.slice(slashIndex + 1);
|
|
5928
|
+
return fileName || uri;
|
|
5929
|
+
};
|
|
5930
|
+
|
|
5931
|
+
const getReadFileTarget = rawArguments => {
|
|
5932
|
+
let parsed;
|
|
5933
|
+
try {
|
|
5934
|
+
parsed = JSON.parse(rawArguments);
|
|
5935
|
+
} catch {
|
|
5936
|
+
return undefined;
|
|
5937
|
+
}
|
|
5938
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
5939
|
+
return undefined;
|
|
5940
|
+
}
|
|
5941
|
+
const uri = Reflect.get(parsed, 'uri');
|
|
5942
|
+
const path = Reflect.get(parsed, 'path');
|
|
5943
|
+
const uriValue = typeof uri === 'string' ? uri : '';
|
|
5944
|
+
const pathValue = typeof path === 'string' ? path : '';
|
|
5945
|
+
const title = uriValue || pathValue;
|
|
5946
|
+
if (!title) {
|
|
5947
|
+
return undefined;
|
|
5948
|
+
}
|
|
5949
|
+
const clickableUri = uriValue || (pathValue.startsWith('file://') ? pathValue : '');
|
|
5950
|
+
return {
|
|
5951
|
+
clickableUri,
|
|
5952
|
+
title
|
|
5953
|
+
};
|
|
5954
|
+
};
|
|
5955
|
+
|
|
5956
|
+
const getToolCallReadFileVirtualDom = toolCall => {
|
|
5957
|
+
const target = getReadFileTarget(toolCall.arguments);
|
|
5958
|
+
if (!target) {
|
|
5959
|
+
return [];
|
|
5960
|
+
}
|
|
5961
|
+
const fileName = getFileNameFromUri(target.title);
|
|
5962
|
+
const clickableProps = target.clickableUri ? {
|
|
5963
|
+
'data-uri': target.clickableUri,
|
|
5964
|
+
onClick: HandleClickReadFile
|
|
5965
|
+
} : {};
|
|
5966
|
+
return [{
|
|
5967
|
+
childCount: 2,
|
|
5968
|
+
className: ChatOrderedListItem,
|
|
5969
|
+
...clickableProps,
|
|
5970
|
+
title: target.title,
|
|
5971
|
+
type: Li
|
|
5972
|
+
}, {
|
|
5973
|
+
childCount: 0,
|
|
5974
|
+
className: FileIcon,
|
|
5975
|
+
...clickableProps,
|
|
5976
|
+
type: Div
|
|
5977
|
+
}, {
|
|
5978
|
+
childCount: 1,
|
|
5979
|
+
...clickableProps,
|
|
5980
|
+
style: 'color: var(--vscode-textLink-foreground); text-decoration: underline;',
|
|
5981
|
+
type: Span
|
|
5982
|
+
}, text(fileName)];
|
|
5983
|
+
};
|
|
5984
|
+
|
|
5985
|
+
const getToolCallDom = toolCall => {
|
|
5986
|
+
if (toolCall.name === 'read_file') {
|
|
5987
|
+
const virtualDom = getToolCallReadFileVirtualDom(toolCall);
|
|
5988
|
+
if (virtualDom.length > 0) {
|
|
5989
|
+
return virtualDom;
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
|
|
5993
|
+
const label = `${toolCall.name} ${argumentPreview}`;
|
|
5994
|
+
return [{
|
|
5995
|
+
childCount: 1,
|
|
5996
|
+
className: ChatOrderedListItem,
|
|
5997
|
+
type: Li
|
|
5998
|
+
}, text(label)];
|
|
5999
|
+
};
|
|
6000
|
+
|
|
6001
|
+
const getToolCallsDom = message => {
|
|
6002
|
+
if (message.role !== 'assistant' || !message.toolCalls || message.toolCalls.length === 0) {
|
|
6003
|
+
return [];
|
|
6004
|
+
}
|
|
6005
|
+
return [{
|
|
6006
|
+
childCount: message.toolCalls.length,
|
|
6007
|
+
className: ChatOrderedList,
|
|
6008
|
+
type: Ol$1
|
|
6009
|
+
}, ...message.toolCalls.flatMap(getToolCallDom)];
|
|
6010
|
+
};
|
|
6011
|
+
|
|
5644
6012
|
const orderedListItemRegex = /^\s*\d+\.\s+(.*)$/;
|
|
5645
6013
|
const parseMessageContent = rawMessage => {
|
|
5646
6014
|
if (rawMessage === '') {
|
|
@@ -5721,20 +6089,6 @@ const getMessageContentDom = nodes => {
|
|
|
5721
6089
|
});
|
|
5722
6090
|
};
|
|
5723
6091
|
|
|
5724
|
-
const getToolCallsDom = message => {
|
|
5725
|
-
if (message.role !== 'assistant' || !message.toolCalls || message.toolCalls.length === 0) {
|
|
5726
|
-
return [];
|
|
5727
|
-
}
|
|
5728
|
-
return message.toolCalls.flatMap(toolCall => {
|
|
5729
|
-
const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
|
|
5730
|
-
const label = `${toolCall.name} ${argumentPreview}`;
|
|
5731
|
-
return [{
|
|
5732
|
-
childCount: 1,
|
|
5733
|
-
className: Markdown,
|
|
5734
|
-
type: P
|
|
5735
|
-
}, text(label)];
|
|
5736
|
-
});
|
|
5737
|
-
};
|
|
5738
6092
|
const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle') => {
|
|
5739
6093
|
const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
|
|
5740
6094
|
const isOpenApiApiKeyMissingMessage = message.role === 'assistant' && message.text === openApiApiKeyRequiredMessage;
|
|
@@ -5744,7 +6098,8 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
|
|
|
5744
6098
|
const messageIntermediate = parseMessageContent(message.text);
|
|
5745
6099
|
const messageDom = getMessageContentDom(messageIntermediate);
|
|
5746
6100
|
const toolCallsDom = getToolCallsDom(message);
|
|
5747
|
-
const
|
|
6101
|
+
const toolCallsChildCount = toolCallsDom.length > 0 ? 1 : 0;
|
|
6102
|
+
const extraChildCount = isOpenApiApiKeyMissingMessage || isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage || isOpenRouterTooManyRequestsMessage ? messageIntermediate.length + 1 + toolCallsChildCount : messageIntermediate.length + toolCallsChildCount;
|
|
5748
6103
|
return [{
|
|
5749
6104
|
childCount: 1,
|
|
5750
6105
|
className: mergeClassNames(Message, roleClassName),
|
|
@@ -5753,7 +6108,7 @@ const getChatMessageDom = (message, openRouterApiKeyInput, openApiApiKeyInput =
|
|
|
5753
6108
|
childCount: extraChildCount,
|
|
5754
6109
|
className: ChatMessageContent,
|
|
5755
6110
|
type: Div
|
|
5756
|
-
}, ...
|
|
6111
|
+
}, ...toolCallsDom, ...messageDom, ...(isOpenApiApiKeyMissingMessage ? getMissingOpenApiApiKeyDom(openApiApiKeyInput) : []), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput, openRouterApiKeyState) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : []), ...(isOpenRouterTooManyRequestsMessage ? getOpenRouterTooManyRequestsDom() : [])];
|
|
5757
6112
|
};
|
|
5758
6113
|
|
|
5759
6114
|
const getEmptyMessagesDom = () => {
|
|
@@ -5985,6 +6340,9 @@ const renderEventListeners = () => {
|
|
|
5985
6340
|
}, {
|
|
5986
6341
|
name: HandleClick,
|
|
5987
6342
|
params: ['handleClick', TargetName, 'event.target.dataset.id']
|
|
6343
|
+
}, {
|
|
6344
|
+
name: HandleClickReadFile,
|
|
6345
|
+
params: ['handleClickReadFile', 'event.target.dataset.uri']
|
|
5988
6346
|
}, {
|
|
5989
6347
|
name: HandleClickDelete,
|
|
5990
6348
|
params: ['handleClickDelete', 'event.target.dataset.id']
|
|
@@ -6110,6 +6468,13 @@ const setChatList = state => {
|
|
|
6110
6468
|
};
|
|
6111
6469
|
};
|
|
6112
6470
|
|
|
6471
|
+
const setEmitStreamingFunctionCallEvents = (state, emitStreamingFunctionCallEvents) => {
|
|
6472
|
+
return {
|
|
6473
|
+
...state,
|
|
6474
|
+
emitStreamingFunctionCallEvents
|
|
6475
|
+
};
|
|
6476
|
+
};
|
|
6477
|
+
|
|
6113
6478
|
const setStreamingEnabled = (state, streamingEnabled) => {
|
|
6114
6479
|
return {
|
|
6115
6480
|
...state,
|
|
@@ -6148,6 +6513,7 @@ const commandMap = {
|
|
|
6148
6513
|
'Chat.handleClickDelete': wrapCommand(handleClickDelete),
|
|
6149
6514
|
'Chat.handleClickList': wrapCommand(handleClickList),
|
|
6150
6515
|
'Chat.handleClickNew': wrapCommand(handleClickNew),
|
|
6516
|
+
'Chat.handleClickReadFile': handleClickReadFile,
|
|
6151
6517
|
'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
|
|
6152
6518
|
'Chat.handleClickSettings': handleClickSettings,
|
|
6153
6519
|
'Chat.handleInput': wrapCommand(handleInput),
|
|
@@ -6171,6 +6537,7 @@ const commandMap = {
|
|
|
6171
6537
|
'Chat.resize': wrapCommand(resize),
|
|
6172
6538
|
'Chat.saveState': wrapGetter(saveState),
|
|
6173
6539
|
'Chat.setChatList': wrapCommand(setChatList),
|
|
6540
|
+
'Chat.setEmitStreamingFunctionCallEvents': wrapCommand(setEmitStreamingFunctionCallEvents),
|
|
6174
6541
|
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
6175
6542
|
'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
|
|
6176
6543
|
'Chat.terminate': terminate,
|