@lobehub/chat 1.68.2 → 1.68.4
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/docs/usage/providers/azureai.mdx +69 -0
- package/docs/usage/providers/azureai.zh-CN.mdx +69 -0
- package/docs/usage/providers/deepseek.mdx +3 -3
- package/docs/usage/providers/deepseek.zh-CN.mdx +5 -4
- package/docs/usage/providers/jina.mdx +51 -0
- package/docs/usage/providers/jina.zh-CN.mdx +51 -0
- package/docs/usage/providers/lmstudio.mdx +75 -0
- package/docs/usage/providers/lmstudio.zh-CN.mdx +75 -0
- package/docs/usage/providers/nvidia.mdx +55 -0
- package/docs/usage/providers/nvidia.zh-CN.mdx +55 -0
- package/docs/usage/providers/ppio.mdx +7 -7
- package/docs/usage/providers/ppio.zh-CN.mdx +6 -6
- package/docs/usage/providers/sambanova.mdx +50 -0
- package/docs/usage/providers/sambanova.zh-CN.mdx +50 -0
- package/docs/usage/providers/tencentcloud.mdx +49 -0
- package/docs/usage/providers/tencentcloud.zh-CN.mdx +49 -0
- package/docs/usage/providers/vertexai.mdx +59 -0
- package/docs/usage/providers/vertexai.zh-CN.mdx +59 -0
- package/docs/usage/providers/vllm.mdx +98 -0
- package/docs/usage/providers/vllm.zh-CN.mdx +98 -0
- package/docs/usage/providers/volcengine.mdx +47 -0
- package/docs/usage/providers/volcengine.zh-CN.mdx +48 -0
- package/locales/ar/chat.json +29 -0
- package/locales/ar/models.json +48 -0
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/chat.json +29 -0
- package/locales/bg-BG/models.json +48 -0
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/chat.json +29 -0
- package/locales/de-DE/models.json +48 -0
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/chat.json +29 -0
- package/locales/en-US/models.json +48 -0
- package/locales/en-US/providers.json +3 -3
- package/locales/es-ES/chat.json +29 -0
- package/locales/es-ES/models.json +48 -0
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/chat.json +29 -0
- package/locales/fa-IR/models.json +48 -0
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/chat.json +29 -0
- package/locales/fr-FR/models.json +48 -0
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/chat.json +29 -0
- package/locales/it-IT/models.json +48 -0
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/chat.json +29 -0
- package/locales/ja-JP/models.json +48 -0
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/chat.json +29 -0
- package/locales/ko-KR/models.json +48 -0
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/chat.json +29 -0
- package/locales/nl-NL/models.json +48 -0
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/chat.json +29 -0
- package/locales/pl-PL/models.json +48 -0
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/chat.json +29 -0
- package/locales/pt-BR/models.json +48 -0
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/chat.json +29 -0
- package/locales/ru-RU/models.json +48 -0
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/chat.json +29 -0
- package/locales/tr-TR/models.json +48 -0
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/chat.json +29 -0
- package/locales/vi-VN/models.json +48 -0
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/chat.json +29 -0
- package/locales/zh-CN/models.json +51 -3
- package/locales/zh-CN/providers.json +3 -4
- package/locales/zh-TW/chat.json +29 -0
- package/locales/zh-TW/models.json +48 -0
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +1 -1
- package/packages/web-crawler/src/crawImpl/__test__/jina.test.ts +169 -0
- package/packages/web-crawler/src/crawImpl/jina.ts +1 -1
- package/packages/web-crawler/src/crawImpl/naive.ts +29 -3
- package/packages/web-crawler/src/urlRules.ts +7 -1
- package/packages/web-crawler/src/utils/errorType.ts +7 -0
- package/scripts/serverLauncher/startServer.js +11 -7
- package/src/config/modelProviders/ppio.ts +1 -1
- package/src/features/Conversation/Extras/Assistant.tsx +12 -20
- package/src/features/Conversation/Extras/Usage/UsageDetail/ModelCard.tsx +130 -0
- package/src/features/Conversation/Extras/Usage/UsageDetail/TokenProgress.tsx +71 -0
- package/src/features/Conversation/Extras/Usage/UsageDetail/index.tsx +146 -0
- package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.ts +94 -0
- package/src/features/Conversation/Extras/Usage/index.tsx +40 -0
- package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +14 -0
- package/src/libs/agent-runtime/utils/streams/anthropic.ts +25 -0
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +100 -10
- package/src/libs/agent-runtime/utils/streams/openai.ts +30 -4
- package/src/libs/agent-runtime/utils/streams/protocol.ts +4 -0
- package/src/locales/default/chat.ts +30 -1
- package/src/server/routers/tools/search.ts +1 -1
- package/src/store/aiInfra/slices/aiModel/initialState.ts +3 -1
- package/src/store/aiInfra/slices/aiModel/selectors.test.ts +1 -0
- package/src/store/aiInfra/slices/aiModel/selectors.ts +5 -0
- package/src/store/aiInfra/slices/aiProvider/action.ts +3 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +5 -1
- package/src/store/chat/slices/message/action.ts +3 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/store/global/selectors/systemStatus.ts +2 -0
- package/src/types/message/base.ts +18 -0
- package/src/types/message/chat.ts +4 -3
- package/src/utils/fetch/fetchSSE.ts +24 -1
- package/src/utils/format.ts +3 -1
@@ -222,6 +222,10 @@ describe('AnthropicStream', () => {
|
|
222
222
|
'id: msg_017aTuY86wNxth5TE544yqJq',
|
223
223
|
'event: stop',
|
224
224
|
'data: "tool_use"\n',
|
225
|
+
|
226
|
+
'id: msg_017aTuY86wNxth5TE544yqJq',
|
227
|
+
'event: usage',
|
228
|
+
'data: {"inputTokens":457,"outputTokens":84,"totalTokens":541}\n',
|
225
229
|
].map((item) => `${item}\n`),
|
226
230
|
);
|
227
231
|
|
@@ -375,6 +379,10 @@ describe('AnthropicStream', () => {
|
|
375
379
|
'event: stop',
|
376
380
|
'data: "tool_use"\n',
|
377
381
|
|
382
|
+
'id: msg_0175ryA67RbGrnRrGBXFQEYK',
|
383
|
+
'event: usage',
|
384
|
+
'data: {"inputTokens":485,"outputTokens":154,"totalTokens":639}\n',
|
385
|
+
|
378
386
|
'id: msg_0175ryA67RbGrnRrGBXFQEYK',
|
379
387
|
'event: stop',
|
380
388
|
'data: "message_stop"\n',
|
@@ -506,6 +514,9 @@ describe('AnthropicStream', () => {
|
|
506
514
|
'event: stop',
|
507
515
|
'data: "end_turn"\n',
|
508
516
|
'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
|
517
|
+
'event: usage',
|
518
|
+
'data: {"inputTokens":46,"outputTokens":365,"totalTokens":411}\n',
|
519
|
+
'id: msg_01MNsLe7n1uVLtu6W8rCFujD',
|
509
520
|
'event: stop',
|
510
521
|
'data: "message_stop"\n',
|
511
522
|
].map((item) => `${item}\n`),
|
@@ -663,6 +674,9 @@ describe('AnthropicStream', () => {
|
|
663
674
|
'event: stop',
|
664
675
|
'data: "end_turn"\n',
|
665
676
|
'id: msg_019q32esPvu3TftzZnL6JPys',
|
677
|
+
'event: usage',
|
678
|
+
'data: {"inputTokens":92,"outputTokens":263,"totalTokens":355}\n',
|
679
|
+
'id: msg_019q32esPvu3TftzZnL6JPys',
|
666
680
|
'event: stop',
|
667
681
|
'data: "message_stop"\n',
|
668
682
|
].map((item) => `${item}\n`),
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
2
2
|
import type { Stream } from '@anthropic-ai/sdk/streaming';
|
3
3
|
|
4
|
+
import { ModelTokensUsage } from '@/types/message';
|
5
|
+
|
4
6
|
import { ChatStreamCallbacks } from '../../types';
|
5
7
|
import {
|
6
8
|
StreamContext,
|
@@ -20,6 +22,11 @@ export const transformAnthropicStream = (
|
|
20
22
|
switch (chunk.type) {
|
21
23
|
case 'message_start': {
|
22
24
|
context.id = chunk.message.id;
|
25
|
+
context.usage = {
|
26
|
+
inputTokens: chunk.message.usage?.input_tokens,
|
27
|
+
outputTokens: chunk.message.usage?.output_tokens,
|
28
|
+
};
|
29
|
+
|
23
30
|
return { data: chunk.message, id: chunk.message.id, type: 'data' };
|
24
31
|
}
|
25
32
|
case 'content_block_start': {
|
@@ -133,6 +140,24 @@ export const transformAnthropicStream = (
|
|
133
140
|
}
|
134
141
|
|
135
142
|
case 'message_delta': {
|
143
|
+
const outputTokens = chunk.usage?.output_tokens + (context.usage?.outputTokens || 0);
|
144
|
+
const inputTokens = context.usage?.inputTokens || 0;
|
145
|
+
const totalTokens = inputTokens + outputTokens;
|
146
|
+
|
147
|
+
if (totalTokens > 0) {
|
148
|
+
return [
|
149
|
+
{ data: chunk.delta.stop_reason, id: context.id, type: 'stop' },
|
150
|
+
{
|
151
|
+
data: {
|
152
|
+
inputTokens: inputTokens,
|
153
|
+
outputTokens: outputTokens,
|
154
|
+
totalTokens: inputTokens + outputTokens,
|
155
|
+
} as ModelTokensUsage,
|
156
|
+
id: context.id,
|
157
|
+
type: 'usage',
|
158
|
+
},
|
159
|
+
];
|
160
|
+
}
|
136
161
|
return { data: chunk.delta.stop_reason, id: context.id, type: 'stop' };
|
137
162
|
}
|
138
163
|
|
@@ -348,6 +348,96 @@ describe('OpenAIStream', () => {
|
|
348
348
|
]);
|
349
349
|
});
|
350
350
|
|
351
|
+
it('should streaming token usage', async () => {
|
352
|
+
const data = [
|
353
|
+
{
|
354
|
+
id: 'chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
355
|
+
object: 'chat.completion.chunk',
|
356
|
+
created: 1741056525,
|
357
|
+
model: 'gpt-4o-mini-2024-07-18',
|
358
|
+
choices: [{ index: 0, delta: { role: 'assistant', content: '' } }],
|
359
|
+
service_tier: 'default',
|
360
|
+
system_fingerprint: 'fp_06737a9306',
|
361
|
+
},
|
362
|
+
{
|
363
|
+
id: 'chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
364
|
+
object: 'chat.completion.chunk',
|
365
|
+
created: 1741056525,
|
366
|
+
model: 'gpt-4o-mini-2024-07-18',
|
367
|
+
choices: [{ index: 0, delta: { content: '你好!' } }],
|
368
|
+
service_tier: 'default',
|
369
|
+
system_fingerprint: 'fp_06737a9306',
|
370
|
+
},
|
371
|
+
{
|
372
|
+
id: 'chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
373
|
+
object: 'chat.completion.chunk',
|
374
|
+
created: 1741056525,
|
375
|
+
model: 'gpt-4o-mini-2024-07-18',
|
376
|
+
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
|
377
|
+
service_tier: 'default',
|
378
|
+
system_fingerprint: 'fp_06737a9306',
|
379
|
+
},
|
380
|
+
{
|
381
|
+
id: 'chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
382
|
+
object: 'chat.completion.chunk',
|
383
|
+
created: 1741056525,
|
384
|
+
model: 'gpt-4o-mini-2024-07-18',
|
385
|
+
choices: [],
|
386
|
+
service_tier: 'default',
|
387
|
+
system_fingerprint: 'fp_06737a9306',
|
388
|
+
usage: {
|
389
|
+
prompt_tokens: 1646,
|
390
|
+
completion_tokens: 11,
|
391
|
+
total_tokens: 1657,
|
392
|
+
prompt_tokens_details: { audio_tokens: 0, cached_tokens: 0 },
|
393
|
+
completion_tokens_details: {
|
394
|
+
accepted_prediction_tokens: 0,
|
395
|
+
audio_tokens: 0,
|
396
|
+
reasoning_tokens: 0,
|
397
|
+
rejected_prediction_tokens: 0,
|
398
|
+
},
|
399
|
+
},
|
400
|
+
},
|
401
|
+
];
|
402
|
+
|
403
|
+
const mockOpenAIStream = new ReadableStream({
|
404
|
+
start(controller) {
|
405
|
+
data.forEach((chunk) => {
|
406
|
+
controller.enqueue(chunk);
|
407
|
+
});
|
408
|
+
|
409
|
+
controller.close();
|
410
|
+
},
|
411
|
+
});
|
412
|
+
|
413
|
+
const protocolStream = OpenAIStream(mockOpenAIStream);
|
414
|
+
|
415
|
+
const decoder = new TextDecoder();
|
416
|
+
const chunks = [];
|
417
|
+
|
418
|
+
// @ts-ignore
|
419
|
+
for await (const chunk of protocolStream) {
|
420
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
421
|
+
}
|
422
|
+
|
423
|
+
expect(chunks).toEqual(
|
424
|
+
[
|
425
|
+
'id: chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
426
|
+
'event: text',
|
427
|
+
`data: ""\n`,
|
428
|
+
'id: chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
429
|
+
'event: text',
|
430
|
+
`data: "你好!"\n`,
|
431
|
+
'id: chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
432
|
+
'event: stop',
|
433
|
+
`data: "stop"\n`,
|
434
|
+
'id: chatcmpl-B7CcnaeK3jqWBMOhxg7SSKFwlk7dC',
|
435
|
+
'event: usage',
|
436
|
+
`data: {"acceptedPredictionTokens":0,"cachedTokens":0,"inputAudioTokens":0,"inputTokens":1646,"outputAudioTokens":0,"outputTokens":11,"reasoningTokens":0,"rejectedPredictionTokens":0,"totalTokens":1657}\n`,
|
437
|
+
].map((i) => `${i}\n`),
|
438
|
+
);
|
439
|
+
});
|
440
|
+
|
351
441
|
describe('Tools Calling', () => {
|
352
442
|
it('should handle OpenAI official tool calls', async () => {
|
353
443
|
const mockOpenAIStream = new ReadableStream({
|
@@ -749,8 +839,8 @@ describe('OpenAIStream', () => {
|
|
749
839
|
'event: text',
|
750
840
|
`data: "帮助。"\n`,
|
751
841
|
'id: 1',
|
752
|
-
'event:
|
753
|
-
`data: "
|
842
|
+
'event: usage',
|
843
|
+
`data: {"cachedTokens":0,"inputCacheMissTokens":6,"inputTokens":6,"outputTokens":104,"reasoningTokens":70,"totalTokens":110}\n`,
|
754
844
|
].map((i) => `${i}\n`),
|
755
845
|
);
|
756
846
|
});
|
@@ -968,8 +1058,8 @@ describe('OpenAIStream', () => {
|
|
968
1058
|
'event: text',
|
969
1059
|
`data: "帮助。"\n`,
|
970
1060
|
'id: 1',
|
971
|
-
'event:
|
972
|
-
`data: "
|
1061
|
+
'event: usage',
|
1062
|
+
`data: {"cachedTokens":0,"inputCacheMissTokens":6,"inputTokens":6,"outputTokens":104,"reasoningTokens":70,"totalTokens":110}\n`,
|
973
1063
|
].map((i) => `${i}\n`),
|
974
1064
|
);
|
975
1065
|
});
|
@@ -1169,8 +1259,8 @@ describe('OpenAIStream', () => {
|
|
1169
1259
|
'event: text',
|
1170
1260
|
`data: "帮助。"\n`,
|
1171
1261
|
'id: 1',
|
1172
|
-
'event:
|
1173
|
-
`data: "
|
1262
|
+
'event: usage',
|
1263
|
+
`data: {"cachedTokens":0,"inputCacheMissTokens":6,"inputTokens":6,"outputTokens":104,"reasoningTokens":70,"totalTokens":110}\n`,
|
1174
1264
|
].map((i) => `${i}\n`),
|
1175
1265
|
);
|
1176
1266
|
});
|
@@ -1370,8 +1460,8 @@ describe('OpenAIStream', () => {
|
|
1370
1460
|
'event: text',
|
1371
1461
|
`data: "帮助。"\n`,
|
1372
1462
|
'id: 1',
|
1373
|
-
'event:
|
1374
|
-
`data: "
|
1463
|
+
'event: usage',
|
1464
|
+
`data: {"cachedTokens":0,"inputCacheMissTokens":6,"inputTokens":6,"outputTokens":104,"reasoningTokens":70,"totalTokens":110}\n`,
|
1375
1465
|
].map((i) => `${i}\n`),
|
1376
1466
|
);
|
1377
1467
|
});
|
@@ -1571,8 +1661,8 @@ describe('OpenAIStream', () => {
|
|
1571
1661
|
'event: text',
|
1572
1662
|
`data: "帮助。"\n`,
|
1573
1663
|
'id: 1',
|
1574
|
-
'event:
|
1575
|
-
`data: "
|
1664
|
+
'event: usage',
|
1665
|
+
`data: {"cachedTokens":0,"inputCacheMissTokens":6,"inputTokens":6,"outputTokens":104,"reasoningTokens":70,"totalTokens":110}\n`,
|
1576
1666
|
].map((i) => `${i}\n`),
|
1577
1667
|
);
|
1578
1668
|
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import OpenAI from 'openai';
|
2
2
|
import type { Stream } from 'openai/streaming';
|
3
3
|
|
4
|
-
import { ChatMessageError, CitationItem } from '@/types/message';
|
4
|
+
import { ChatMessageError, CitationItem, ModelTokensUsage } from '@/types/message';
|
5
5
|
|
6
6
|
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../../error';
|
7
7
|
import { ChatStreamCallbacks } from '../../types';
|
@@ -18,6 +18,22 @@ import {
|
|
18
18
|
generateToolCallId,
|
19
19
|
} from './protocol';
|
20
20
|
|
21
|
+
const convertUsage = (usage: OpenAI.Completions.CompletionUsage): ModelTokensUsage => {
|
22
|
+
return {
|
23
|
+
acceptedPredictionTokens: usage.completion_tokens_details?.accepted_prediction_tokens,
|
24
|
+
cachedTokens:
|
25
|
+
(usage as any).prompt_cache_hit_tokens || usage.prompt_tokens_details?.cached_tokens,
|
26
|
+
inputAudioTokens: usage.prompt_tokens_details?.audio_tokens,
|
27
|
+
inputCacheMissTokens: (usage as any).prompt_cache_miss_tokens,
|
28
|
+
inputTokens: usage.prompt_tokens,
|
29
|
+
outputAudioTokens: usage.completion_tokens_details?.audio_tokens,
|
30
|
+
outputTokens: usage.completion_tokens,
|
31
|
+
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
|
32
|
+
rejectedPredictionTokens: usage.completion_tokens_details?.rejected_prediction_tokens,
|
33
|
+
totalTokens: usage.total_tokens,
|
34
|
+
};
|
35
|
+
};
|
36
|
+
|
21
37
|
export const transformOpenAIStream = (
|
22
38
|
chunk: OpenAI.ChatCompletionChunk,
|
23
39
|
streamContext: StreamContext,
|
@@ -41,11 +57,16 @@ export const transformOpenAIStream = (
|
|
41
57
|
// maybe need another structure to add support for multiple choices
|
42
58
|
const item = chunk.choices[0];
|
43
59
|
if (!item) {
|
60
|
+
if (chunk.usage) {
|
61
|
+
const usage = chunk.usage;
|
62
|
+
return { data: convertUsage(usage), id: chunk.id, type: 'usage' };
|
63
|
+
}
|
64
|
+
|
44
65
|
return { data: chunk, id: chunk.id, type: 'data' };
|
45
66
|
}
|
46
67
|
|
47
|
-
|
48
|
-
|
68
|
+
if (item && typeof item.delta?.tool_calls === 'object' && item.delta.tool_calls?.length > 0) {
|
69
|
+
// tools calling
|
49
70
|
const tool_calls = item.delta.tool_calls.filter(
|
50
71
|
(value) => value.index >= 0 || typeof value.index === 'undefined',
|
51
72
|
);
|
@@ -97,6 +118,11 @@ export const transformOpenAIStream = (
|
|
97
118
|
return { data: item.delta.content, id: chunk.id, type: 'text' };
|
98
119
|
}
|
99
120
|
|
121
|
+
if (chunk.usage) {
|
122
|
+
const usage = chunk.usage;
|
123
|
+
return { data: convertUsage(usage), id: chunk.id, type: 'usage' };
|
124
|
+
}
|
125
|
+
|
100
126
|
return { data: item.finish_reason, id: chunk.id, type: 'stop' };
|
101
127
|
}
|
102
128
|
|
@@ -147,7 +173,7 @@ export const transformOpenAIStream = (
|
|
147
173
|
({
|
148
174
|
title: typeof item === 'string' ? item : item.title,
|
149
175
|
url: typeof item === 'string' ? item : item.url,
|
150
|
-
}) as CitationItem
|
176
|
+
}) as CitationItem,
|
151
177
|
),
|
152
178
|
},
|
153
179
|
id: chunk.id,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { ChatStreamCallbacks } from '@/libs/agent-runtime';
|
2
|
+
import { ModelTokensUsage } from '@/types/message';
|
2
3
|
|
3
4
|
import { AgentRuntimeErrorType } from '../../error';
|
4
5
|
|
@@ -23,6 +24,7 @@ export interface StreamContext {
|
|
23
24
|
name: string;
|
24
25
|
};
|
25
26
|
toolIndex?: number;
|
27
|
+
usage?: ModelTokensUsage;
|
26
28
|
}
|
27
29
|
|
28
30
|
export interface StreamProtocolChunk {
|
@@ -44,6 +46,8 @@ export interface StreamProtocolChunk {
|
|
44
46
|
| 'stop'
|
45
47
|
// Error
|
46
48
|
| 'error'
|
49
|
+
// token usage
|
50
|
+
| 'usage'
|
47
51
|
// unknown data result
|
48
52
|
| 'data';
|
49
53
|
}
|
@@ -81,6 +81,36 @@ export default {
|
|
81
81
|
deleteDisabledByThreads: '存在子话题,不能删除',
|
82
82
|
regenerate: '重新生成',
|
83
83
|
},
|
84
|
+
messages: {
|
85
|
+
modelCard: {
|
86
|
+
credit: '积分',
|
87
|
+
creditPricing: '定价',
|
88
|
+
creditTooltip:
|
89
|
+
'为便于计数,我们将 1$ 折算为 1M 积分,例如 $3/M tokens 即可折算为 3积分/token',
|
90
|
+
pricing: {
|
91
|
+
inputCachedTokens: '缓存输入 {{amount}}/积分 · ${{amount}}/M',
|
92
|
+
inputCharts: '${{amount}}/M 字符',
|
93
|
+
inputMinutes: '${{amount}}/分钟',
|
94
|
+
inputTokens: '输入 {{amount}}/积分 · ${{amount}}/M',
|
95
|
+
outputTokens: '输出 {{amount}}/积分 · ${{amount}}/M',
|
96
|
+
},
|
97
|
+
},
|
98
|
+
tokenDetails: {
|
99
|
+
input: '输入',
|
100
|
+
inputAudio: '音频输入',
|
101
|
+
inputCached: '输入缓存',
|
102
|
+
inputText: '文本输入',
|
103
|
+
inputTitle: '输入明细',
|
104
|
+
inputUncached: '输入未缓存',
|
105
|
+
output: '输出',
|
106
|
+
outputAudio: '音频输出',
|
107
|
+
outputText: '文本输出',
|
108
|
+
outputTitle: '输出明细',
|
109
|
+
reasoning: '深度思考',
|
110
|
+
title: '生成明细',
|
111
|
+
total: '总计消耗',
|
112
|
+
},
|
113
|
+
},
|
84
114
|
newAgent: '新建助手',
|
85
115
|
pin: '置顶',
|
86
116
|
pinOff: '取消置顶',
|
@@ -194,7 +224,6 @@ export default {
|
|
194
224
|
action: '语音朗读',
|
195
225
|
clear: '删除语音',
|
196
226
|
},
|
197
|
-
|
198
227
|
updateAgent: '更新助理信息',
|
199
228
|
upload: {
|
200
229
|
action: {
|
@@ -1,8 +1,9 @@
|
|
1
|
-
import { AiProviderModelListItem } from '@/types/aiModel';
|
1
|
+
import { AiProviderModelListItem, LobeDefaultAiModelListItem } from '@/types/aiModel';
|
2
2
|
|
3
3
|
export interface AIModelsState {
|
4
4
|
aiModelLoadingIds: string[];
|
5
5
|
aiProviderModelList: AiProviderModelListItem[];
|
6
|
+
builtinAiModelList: LobeDefaultAiModelListItem[];
|
6
7
|
isAiModelListInit?: boolean;
|
7
8
|
modelSearchKeyword: string;
|
8
9
|
}
|
@@ -10,5 +11,6 @@ export interface AIModelsState {
|
|
10
11
|
export const initialAIModelState: AIModelsState = {
|
11
12
|
aiModelLoadingIds: [],
|
12
13
|
aiProviderModelList: [],
|
14
|
+
builtinAiModelList: [],
|
13
15
|
modelSearchKeyword: '',
|
14
16
|
};
|
@@ -22,8 +22,12 @@ const filteredAiProviderModelList = (s: AIProviderStoreState) => {
|
|
22
22
|
};
|
23
23
|
|
24
24
|
const totalAiProviderModelList = (s: AIProviderStoreState) => s.aiProviderModelList.length;
|
25
|
+
|
25
26
|
const isEmptyAiProviderModelList = (s: AIProviderStoreState) => totalAiProviderModelList(s) === 0;
|
26
27
|
|
28
|
+
const getModelCard = (model: string, provider: string) => (s: AIProviderStoreState) =>
|
29
|
+
s.builtinAiModelList.find((item) => item.id === model && item.providerId === provider);
|
30
|
+
|
27
31
|
const hasRemoteModels = (s: AIProviderStoreState) =>
|
28
32
|
s.aiProviderModelList.some((m) => m.source === AiModelSourceEnum.Remote);
|
29
33
|
|
@@ -113,6 +117,7 @@ export const aiModelSelectors = {
|
|
113
117
|
filteredAiProviderModelList,
|
114
118
|
getAiModelById,
|
115
119
|
getEnabledModelById,
|
120
|
+
getModelCard,
|
116
121
|
hasRemoteModels,
|
117
122
|
isEmptyAiProviderModelList,
|
118
123
|
isModelEnabled,
|
@@ -184,7 +184,7 @@ export const createAiProviderSlice: StateCreator<
|
|
184
184
|
};
|
185
185
|
},
|
186
186
|
{
|
187
|
-
onSuccess: (data) => {
|
187
|
+
onSuccess: async (data) => {
|
188
188
|
if (!data) return;
|
189
189
|
|
190
190
|
const getModelListByType = (providerId: string, type: string) => {
|
@@ -206,10 +206,12 @@ export const createAiProviderSlice: StateCreator<
|
|
206
206
|
children: getModelListByType(provider.id, 'chat'),
|
207
207
|
name: provider.name || provider.id,
|
208
208
|
}));
|
209
|
+
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
209
210
|
|
210
211
|
set(
|
211
212
|
{
|
212
213
|
aiProviderRuntimeConfig: data.runtimeConfig,
|
214
|
+
builtinAiModelList: LOBE_DEFAULT_MODEL_LIST,
|
213
215
|
enabledAiModels: data.enabledAiModels,
|
214
216
|
enabledAiProviders: data.enabledAiProviders,
|
215
217
|
enabledChatModelList,
|
@@ -455,7 +455,10 @@ export const generateAIChat: StateCreator<
|
|
455
455
|
await messageService.updateMessageError(messageId, error);
|
456
456
|
await refreshMessages();
|
457
457
|
},
|
458
|
-
onFinish: async (
|
458
|
+
onFinish: async (
|
459
|
+
content,
|
460
|
+
{ traceId, observationId, toolCalls, reasoning, grounding, usage },
|
461
|
+
) => {
|
459
462
|
// if there is traceId, update it
|
460
463
|
if (traceId) {
|
461
464
|
msgTraceId = traceId;
|
@@ -474,6 +477,7 @@ export const generateAIChat: StateCreator<
|
|
474
477
|
toolCalls,
|
475
478
|
reasoning: !!reasoning ? { ...reasoning, duration } : undefined,
|
476
479
|
search: !!grounding?.citations ? grounding : undefined,
|
480
|
+
metadata: usage,
|
477
481
|
});
|
478
482
|
},
|
479
483
|
onMessageHandle: async (chunk) => {
|
@@ -17,6 +17,7 @@ import {
|
|
17
17
|
ChatMessageError,
|
18
18
|
ChatMessagePluginError,
|
19
19
|
CreateMessageParams,
|
20
|
+
MessageMetadata,
|
20
21
|
MessageToolCall,
|
21
22
|
ModelReasoning,
|
22
23
|
} from '@/types/message';
|
@@ -79,6 +80,7 @@ export interface ChatMessageAction {
|
|
79
80
|
toolCalls?: MessageToolCall[];
|
80
81
|
reasoning?: ModelReasoning;
|
81
82
|
search?: GroundingSearch;
|
83
|
+
metadata?: MessageMetadata;
|
82
84
|
},
|
83
85
|
) => Promise<void>;
|
84
86
|
/**
|
@@ -308,6 +310,7 @@ export const chatMessage: StateCreator<
|
|
308
310
|
tools: extra?.toolCalls ? internal_transformToolCalls(extra?.toolCalls) : undefined,
|
309
311
|
reasoning: extra?.reasoning,
|
310
312
|
search: extra?.search,
|
313
|
+
metadata: extra?.metadata,
|
311
314
|
});
|
312
315
|
await refreshMessages();
|
313
316
|
},
|
@@ -16,6 +16,7 @@ const showChatSideBar = (s: GlobalStore) => !s.status.zenMode && s.status.showCh
|
|
16
16
|
const showSessionPanel = (s: GlobalStore) => !s.status.zenMode && s.status.showSessionPanel;
|
17
17
|
const showFilePanel = (s: GlobalStore) => s.status.showFilePanel;
|
18
18
|
const hidePWAInstaller = (s: GlobalStore) => s.status.hidePWAInstaller;
|
19
|
+
const isShowCredit = (s: GlobalStore) => s.status.isShowCredit;
|
19
20
|
|
20
21
|
const showChatHeader = (s: GlobalStore) => !s.status.zenMode;
|
21
22
|
const inZenMode = (s: GlobalStore) => s.status.zenMode;
|
@@ -58,6 +59,7 @@ export const systemStatusSelectors = {
|
|
58
59
|
isPgliteInited,
|
59
60
|
isPgliteNotEnabled,
|
60
61
|
isPgliteNotInited,
|
62
|
+
isShowCredit,
|
61
63
|
mobileShowPortal,
|
62
64
|
mobileShowTopic,
|
63
65
|
portalWidth,
|
@@ -13,6 +13,23 @@ export interface ModelReasoning {
|
|
13
13
|
signature?: string;
|
14
14
|
}
|
15
15
|
|
16
|
+
export interface ModelTokensUsage {
|
17
|
+
acceptedPredictionTokens?: number;
|
18
|
+
cachedTokens?: number;
|
19
|
+
inputAudioTokens?: number;
|
20
|
+
inputCacheMissTokens?: number;
|
21
|
+
inputTokens?: number;
|
22
|
+
outputAudioTokens?: number;
|
23
|
+
outputTokens?: number;
|
24
|
+
reasoningTokens?: number;
|
25
|
+
rejectedPredictionTokens?: number;
|
26
|
+
totalTokens?: number;
|
27
|
+
}
|
28
|
+
|
29
|
+
export interface MessageMetadata extends ModelTokensUsage {
|
30
|
+
tps?: number;
|
31
|
+
}
|
32
|
+
|
16
33
|
export type MessageRoleType = 'user' | 'system' | 'assistant' | 'tool';
|
17
34
|
|
18
35
|
export interface MessageItem {
|
@@ -23,6 +40,7 @@ export interface MessageItem {
|
|
23
40
|
error: any | null;
|
24
41
|
favorite: boolean | null;
|
25
42
|
id: string;
|
43
|
+
metadata?: MessageMetadata | null;
|
26
44
|
model: string | null;
|
27
45
|
observationId: string | null;
|
28
46
|
parentId: string | null;
|
@@ -6,7 +6,7 @@ import { MetaData } from '@/types/meta';
|
|
6
6
|
import { MessageSemanticSearchChunk } from '@/types/rag';
|
7
7
|
import { GroundingSearch } from '@/types/search';
|
8
8
|
|
9
|
-
import { MessageRoleType, ModelReasoning } from './base';
|
9
|
+
import { MessageMetadata, MessageRoleType, ModelReasoning } from './base';
|
10
10
|
import { ChatPluginPayload, ChatToolPayload } from './tools';
|
11
11
|
import { Translate } from './translate';
|
12
12
|
|
@@ -82,15 +82,16 @@ export interface ChatMessage {
|
|
82
82
|
imageList?: ChatImageItem[];
|
83
83
|
meta: MetaData;
|
84
84
|
|
85
|
+
metadata?: MessageMetadata | null;
|
85
86
|
/**
|
86
87
|
* observation id
|
87
88
|
*/
|
88
89
|
observationId?: string;
|
90
|
+
|
89
91
|
/**
|
90
92
|
* parent message id
|
91
93
|
*/
|
92
94
|
parentId?: string;
|
93
|
-
|
94
95
|
plugin?: ChatPluginPayload;
|
95
96
|
pluginError?: any;
|
96
97
|
pluginState?: any;
|
@@ -100,8 +101,8 @@ export interface ChatMessage {
|
|
100
101
|
quotaId?: string;
|
101
102
|
ragQuery?: string | null;
|
102
103
|
ragQueryId?: string | null;
|
103
|
-
ragRawQuery?: string | null;
|
104
104
|
|
105
|
+
ragRawQuery?: string | null;
|
105
106
|
reasoning?: ModelReasoning | null;
|
106
107
|
/**
|
107
108
|
* message role type
|
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
MessageToolCallChunk,
|
11
11
|
MessageToolCallSchema,
|
12
12
|
ModelReasoning,
|
13
|
+
ModelTokensUsage,
|
13
14
|
} from '@/types/message';
|
14
15
|
import { GroundingSearch } from '@/types/search';
|
15
16
|
|
@@ -28,9 +29,15 @@ export type OnFinishHandler = (
|
|
28
29
|
toolCalls?: MessageToolCall[];
|
29
30
|
traceId?: string | null;
|
30
31
|
type?: SSEFinishType;
|
32
|
+
usage?: ModelTokensUsage;
|
31
33
|
},
|
32
34
|
) => Promise<void>;
|
33
35
|
|
36
|
+
export interface MessageUsageChunk {
|
37
|
+
type: 'usage';
|
38
|
+
usage: ModelTokensUsage;
|
39
|
+
}
|
40
|
+
|
34
41
|
export interface MessageTextChunk {
|
35
42
|
text: string;
|
36
43
|
type: 'text';
|
@@ -59,7 +66,12 @@ export interface FetchSSEOptions {
|
|
59
66
|
onErrorHandle?: (error: ChatMessageError) => void;
|
60
67
|
onFinish?: OnFinishHandler;
|
61
68
|
onMessageHandle?: (
|
62
|
-
chunk:
|
69
|
+
chunk:
|
70
|
+
| MessageTextChunk
|
71
|
+
| MessageToolCallsChunk
|
72
|
+
| MessageReasoningChunk
|
73
|
+
| MessageGroundingChunk
|
74
|
+
| MessageUsageChunk,
|
63
75
|
) => void;
|
64
76
|
smoothing?: SmoothingParams | boolean;
|
65
77
|
}
|
@@ -317,6 +329,7 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
317
329
|
});
|
318
330
|
|
319
331
|
let grounding: GroundingSearch | undefined = undefined;
|
332
|
+
let usage: ModelTokensUsage | undefined = undefined;
|
320
333
|
await fetchEventSource(url, {
|
321
334
|
body: options.body,
|
322
335
|
fetch: options?.fetcher,
|
@@ -377,6 +390,9 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
377
390
|
}
|
378
391
|
|
379
392
|
case 'text': {
|
393
|
+
// skip empty text
|
394
|
+
if (!data) break;
|
395
|
+
|
380
396
|
if (textSmoothing) {
|
381
397
|
textController.pushToQueue(data);
|
382
398
|
|
@@ -389,6 +405,12 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
389
405
|
break;
|
390
406
|
}
|
391
407
|
|
408
|
+
case 'usage': {
|
409
|
+
usage = data;
|
410
|
+
options.onMessageHandle?.({ type: 'usage', usage: data });
|
411
|
+
break;
|
412
|
+
}
|
413
|
+
|
392
414
|
case 'grounding': {
|
393
415
|
grounding = data;
|
394
416
|
options.onMessageHandle?.({ grounding: data, type: 'grounding' });
|
@@ -475,6 +497,7 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
475
497
|
toolCalls,
|
476
498
|
traceId,
|
477
499
|
type: finishedType,
|
500
|
+
usage,
|
478
501
|
});
|
479
502
|
}
|
480
503
|
}
|