@lobehub/chat 1.69.3 → 1.69.5
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 +52 -0
- package/changelog/v1.json +18 -0
- package/docker-compose/setup.sh +92 -1
- package/docs/self-hosting/advanced/auth/clerk.mdx +1 -0
- package/package.json +3 -3
- package/packages/web-crawler/package.json +2 -1
- package/packages/web-crawler/src/crawImpl/__tests__/browserless.test.ts +94 -0
- package/packages/web-crawler/src/crawImpl/browserless.ts +2 -1
- package/packages/web-crawler/src/urlRules.ts +6 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/MessageFromUrl.tsx +31 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +45 -39
- package/src/config/aiModels/openrouter.ts +26 -1
- package/src/features/Conversation/Extras/Usage/UsageDetail/ModelCard.tsx +6 -6
- package/src/features/Conversation/Extras/Usage/UsageDetail/index.tsx +3 -3
- package/src/libs/agent-runtime/mistral/index.test.ts +0 -3
- package/src/libs/agent-runtime/mistral/index.ts +10 -7
- package/src/libs/agent-runtime/openrouter/index.test.ts +33 -0
- package/src/libs/agent-runtime/openrouter/index.ts +11 -2
- package/src/libs/agent-runtime/openrouter/type.ts +19 -0
- package/src/libs/agent-runtime/utils/debugStream.ts +1 -1
- package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +8 -2
- package/src/services/chat.ts +19 -13
- package/src/store/agent/index.ts +1 -1
- package/src/store/agent/slices/chat/selectors/chatConfig.ts +3 -2
- package/src/store/agent/store.ts +2 -0
- package/src/store/aiInfra/index.ts +1 -1
- package/src/store/aiInfra/store.ts +2 -0
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +13 -16
- package/src/store/session/index.ts +1 -1
- package/src/store/session/store.ts +2 -0
- package/src/store/tool/index.ts +1 -1
- package/src/store/tool/store.ts +2 -0
- package/src/store/user/slices/modelList/action.ts +17 -16
- package/src/store/user/store.ts +2 -0
- package/src/types/agent/chatConfig.ts +66 -0
- package/src/types/agent/index.ts +4 -61
- package/src/store/chat/slices/aiChat/actions/helpers.ts +0 -13
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,58 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.69.5](https://github.com/lobehub/lobe-chat/compare/v1.69.4...v1.69.5)
|
6
|
+
|
7
|
+
<sup>Released on **2025-03-09**</sup>
|
8
|
+
|
9
|
+
#### 💄 Styles
|
10
|
+
|
11
|
+
- **chat**: Auto send message from URL.
|
12
|
+
- **misc**: Support openrouter claude 3.7 sonnet reasoning.
|
13
|
+
|
14
|
+
<br/>
|
15
|
+
|
16
|
+
<details>
|
17
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
18
|
+
|
19
|
+
#### Styles
|
20
|
+
|
21
|
+
- **chat**: Auto send message from URL, closes [#6497](https://github.com/lobehub/lobe-chat/issues/6497) ([30b2639](https://github.com/lobehub/lobe-chat/commit/30b2639))
|
22
|
+
- **misc**: Support openrouter claude 3.7 sonnet reasoning, closes [#6806](https://github.com/lobehub/lobe-chat/issues/6806) ([f1ffc2c](https://github.com/lobehub/lobe-chat/commit/f1ffc2c))
|
23
|
+
|
24
|
+
</details>
|
25
|
+
|
26
|
+
<div align="right">
|
27
|
+
|
28
|
+
[](#readme-top)
|
29
|
+
|
30
|
+
</div>
|
31
|
+
|
32
|
+
### [Version 1.69.4](https://github.com/lobehub/lobe-chat/compare/v1.69.3...v1.69.4)
|
33
|
+
|
34
|
+
<sup>Released on **2025-03-09**</sup>
|
35
|
+
|
36
|
+
#### 🐛 Bug Fixes
|
37
|
+
|
38
|
+
- **misc**: Fix mistral can not chat.
|
39
|
+
|
40
|
+
<br/>
|
41
|
+
|
42
|
+
<details>
|
43
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
44
|
+
|
45
|
+
#### What's fixed
|
46
|
+
|
47
|
+
- **misc**: Fix mistral can not chat, closes [#6828](https://github.com/lobehub/lobe-chat/issues/6828) ([00cba71](https://github.com/lobehub/lobe-chat/commit/00cba71))
|
48
|
+
|
49
|
+
</details>
|
50
|
+
|
51
|
+
<div align="right">
|
52
|
+
|
53
|
+
[](#readme-top)
|
54
|
+
|
55
|
+
</div>
|
56
|
+
|
5
57
|
### [Version 1.69.3](https://github.com/lobehub/lobe-chat/compare/v1.69.2...v1.69.3)
|
6
58
|
|
7
59
|
<sup>Released on **2025-03-08**</sup>
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
[
|
2
|
+
{
|
3
|
+
"children": {
|
4
|
+
"improvements": [
|
5
|
+
"Support openrouter claude 3.7 sonnet reasoning."
|
6
|
+
]
|
7
|
+
},
|
8
|
+
"date": "2025-03-09",
|
9
|
+
"version": "1.69.5"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"children": {
|
13
|
+
"fixes": [
|
14
|
+
"Fix mistral can not chat."
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"date": "2025-03-09",
|
18
|
+
"version": "1.69.4"
|
19
|
+
},
|
2
20
|
{
|
3
21
|
"children": {
|
4
22
|
"improvements": [
|
package/docker-compose/setup.sh
CHANGED
@@ -170,6 +170,16 @@ show_message() {
|
|
170
170
|
;;
|
171
171
|
esac
|
172
172
|
;;
|
173
|
+
tips_download_failed)
|
174
|
+
case $LANGUAGE in
|
175
|
+
zh_CN)
|
176
|
+
echo "$2 下载失败,请检查网络连接。"
|
177
|
+
;;
|
178
|
+
*)
|
179
|
+
echo "$2 Download failed, please check the network connection."
|
180
|
+
;;
|
181
|
+
esac
|
182
|
+
;;
|
173
183
|
tips_already_installed)
|
174
184
|
case $LANGUAGE in
|
175
185
|
zh_CN)
|
@@ -260,6 +270,30 @@ show_message() {
|
|
260
270
|
;;
|
261
271
|
esac
|
262
272
|
;;
|
273
|
+
tips_no_docker_permission)
|
274
|
+
case $LANGUAGE in
|
275
|
+
zh_CN)
|
276
|
+
echo "WARN: 看起来当前用户没有 Docker 权限。"
|
277
|
+
echo "使用 'sudo usermod -aG docker $USER' 为用户分配 Docker 权限(可能需要重新启动 shell)。"
|
278
|
+
;;
|
279
|
+
*)
|
280
|
+
echo "WARN: It look like the current user does not have Docker permissions."
|
281
|
+
echo "Use 'sudo usermod -aG docker $USER' to assign Docker permissions to the user (may require restarting shell)."
|
282
|
+
;;
|
283
|
+
esac
|
284
|
+
;;
|
285
|
+
tips_init_database_failed)
|
286
|
+
case $LANGUAGE in
|
287
|
+
zh_CN)
|
288
|
+
echo "无法初始化数据库,为了避免你的数据重复初始化,请在首次成功启动时运行以下指令清空 Casdoor 初始配置文件:"
|
289
|
+
echo "echo '{}' > init_data.json"
|
290
|
+
;;
|
291
|
+
*)
|
292
|
+
echo "Failed to initialize the database. To avoid your data being initialized repeatedly, run the following command to unmount the initial configuration file of Casdoor when you first start successfully:"
|
293
|
+
echo "echo '{}' > init_data.json"
|
294
|
+
;;
|
295
|
+
esac
|
296
|
+
;;
|
263
297
|
ask_regenerate_secrets)
|
264
298
|
case $LANGUAGE in
|
265
299
|
zh_CN)
|
@@ -320,12 +354,27 @@ show_message() {
|
|
320
354
|
;;
|
321
355
|
esac
|
322
356
|
;;
|
357
|
+
ask_init_database)
|
358
|
+
case $LANGUAGE in
|
359
|
+
zh_CN)
|
360
|
+
echo "是否初始化数据库?"
|
361
|
+
;;
|
362
|
+
*)
|
363
|
+
echo "Do you want to initialize the database?"
|
364
|
+
;;
|
365
|
+
esac
|
366
|
+
;;
|
323
367
|
esac
|
324
368
|
}
|
325
369
|
|
326
370
|
# Function to download files
|
327
371
|
download_file() {
|
328
|
-
wget
|
372
|
+
wget --show-progress "$1" -O "$2"
|
373
|
+
# If run failed, exit
|
374
|
+
if [ $? -ne 0 ]; then
|
375
|
+
show_message "tips_download_failed" "$2"
|
376
|
+
exit 1
|
377
|
+
fi
|
329
378
|
}
|
330
379
|
|
331
380
|
print_centered() {
|
@@ -629,12 +678,54 @@ section_regenerate_secrets() {
|
|
629
678
|
fi
|
630
679
|
fi
|
631
680
|
}
|
681
|
+
|
632
682
|
show_message "ask_regenerate_secrets"
|
633
683
|
ask "(y/n)" "y"
|
634
684
|
if [[ "$ask_result" == "y" ]]; then
|
635
685
|
section_regenerate_secrets
|
636
686
|
fi
|
637
687
|
|
688
|
+
section_init_database() {
|
689
|
+
if ! command -v docker &> /dev/null ; then
|
690
|
+
echo "docker" $(show_message "tips_no_executable")
|
691
|
+
return 1
|
692
|
+
fi
|
693
|
+
|
694
|
+
if ! docker compose &> /dev/null ; then
|
695
|
+
echo "docker compose" $(show_message "tips_no_executable")
|
696
|
+
return 1
|
697
|
+
fi
|
698
|
+
|
699
|
+
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
|
700
|
+
# If this fails, the user probably does not have permissions for Docker.
|
701
|
+
# ref: https://github.com/paperless-ngx/paperless-ngx/blob/89e5c08a1fe4ca0b7641ae8fbd5554502199ae40/install-paperless-ngx.sh#L64-L72
|
702
|
+
if ! docker stats --no-stream &> /dev/null ; then
|
703
|
+
echo $(show_message "tips_no_docker_permission")
|
704
|
+
return 1
|
705
|
+
fi
|
706
|
+
|
707
|
+
docker compose pull
|
708
|
+
docker compose up --detach postgresql casdoor
|
709
|
+
# hopefully enough time for even the slower systems
|
710
|
+
sleep 15
|
711
|
+
docker compose stop
|
712
|
+
|
713
|
+
# Init finished, remove init mount
|
714
|
+
echo '{}' > init_data.json
|
715
|
+
}
|
716
|
+
|
717
|
+
show_message "ask_init_database"
|
718
|
+
ask "(y/n)" "y"
|
719
|
+
if [[ "$ask_result" == "y" ]]; then
|
720
|
+
# If return 1 means failed
|
721
|
+
section_init_database
|
722
|
+
if [ $? -ne 0 ]; then
|
723
|
+
echo $(show_message "tips_init_database_failed")
|
724
|
+
fi
|
725
|
+
else
|
726
|
+
show_message "tips_init_database_failed"
|
727
|
+
fi
|
728
|
+
|
638
729
|
section_display_configurated_report() {
|
639
730
|
# Display configuration reports
|
640
731
|
echo $(show_message "security_secrect_regenerate_report")
|
@@ -27,6 +27,7 @@ Go to [Clerk](https://clerk.com?utm_source=lobehub\&utm_medium=docs) to register
|
|
27
27
|
```shell
|
28
28
|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxxxxxxxx
|
29
29
|
CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxx
|
30
|
+
NEXT_PUBLIC_ENABLE_NEXT_AUTH=0
|
30
31
|
```
|
31
32
|
|
32
33
|
### Create and Configure Webhook in Clerk
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.69.
|
3
|
+
"version": "1.69.5",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -122,7 +122,7 @@
|
|
122
122
|
"@cyntler/react-doc-viewer": "^1.17.0",
|
123
123
|
"@electric-sql/pglite": "0.2.13",
|
124
124
|
"@google-cloud/vertexai": "^1.9.2",
|
125
|
-
"@google/generative-ai": "^0.
|
125
|
+
"@google/generative-ai": "^0.24.0",
|
126
126
|
"@huggingface/inference": "^2.8.1",
|
127
127
|
"@icons-pack/react-simple-icons": "9.6.0",
|
128
128
|
"@khmyznikov/pwa-install": "0.3.9",
|
@@ -180,7 +180,7 @@
|
|
180
180
|
"langfuse": "3.29.1",
|
181
181
|
"langfuse-core": "3.29.1",
|
182
182
|
"lodash-es": "^4.17.21",
|
183
|
-
"lucide-react": "^0.
|
183
|
+
"lucide-react": "^0.479.0",
|
184
184
|
"mammoth": "^1.9.0",
|
185
185
|
"mdast-util-to-markdown": "^2.1.2",
|
186
186
|
"modern-screenshot": "^4.5.5",
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
2
|
+
|
3
|
+
import { browserless } from '../browserless';
|
4
|
+
|
5
|
+
describe('browserless', () => {
|
6
|
+
it('should throw BrowserlessInitError when env vars not set', async () => {
|
7
|
+
const originalEnv = { ...process.env };
|
8
|
+
process.env = { ...originalEnv };
|
9
|
+
delete process.env.BROWSERLESS_URL;
|
10
|
+
delete process.env.BROWSERLESS_TOKEN;
|
11
|
+
|
12
|
+
await expect(browserless('https://example.com', { filterOptions: {} })).rejects.toThrow(
|
13
|
+
'`BROWSERLESS_URL` or `BROWSERLESS_TOKEN` are required',
|
14
|
+
);
|
15
|
+
|
16
|
+
process.env = originalEnv;
|
17
|
+
});
|
18
|
+
|
19
|
+
it('should return undefined on fetch error', async () => {
|
20
|
+
process.env.BROWSERLESS_TOKEN = 'test-token';
|
21
|
+
global.fetch = vi.fn().mockRejectedValue(new Error('Fetch error'));
|
22
|
+
|
23
|
+
const result = await browserless('https://example.com', { filterOptions: {} });
|
24
|
+
expect(result).toBeUndefined();
|
25
|
+
});
|
26
|
+
|
27
|
+
it('should return undefined when content is empty', async () => {
|
28
|
+
process.env.BROWSERLESS_TOKEN = 'test-token';
|
29
|
+
global.fetch = vi.fn().mockResolvedValue({
|
30
|
+
text: vi.fn().mockResolvedValue('<html></html>'),
|
31
|
+
} as any);
|
32
|
+
|
33
|
+
const result = await browserless('https://example.com', { filterOptions: {} });
|
34
|
+
expect(result).toBeUndefined();
|
35
|
+
});
|
36
|
+
|
37
|
+
it('should return undefined when title is "Just a moment..."', async () => {
|
38
|
+
process.env.BROWSERLESS_TOKEN = 'test-token';
|
39
|
+
global.fetch = vi.fn().mockResolvedValue({
|
40
|
+
text: vi.fn().mockResolvedValue('<html><title>Just a moment...</title></html>'),
|
41
|
+
} as any);
|
42
|
+
|
43
|
+
const result = await browserless('https://example.com', { filterOptions: {} });
|
44
|
+
expect(result).toBeUndefined();
|
45
|
+
});
|
46
|
+
|
47
|
+
it('should return crawl result on successful fetch', async () => {
|
48
|
+
process.env.BROWSERLESS_TOKEN = 'test-token';
|
49
|
+
global.fetch = vi.fn().mockResolvedValue({
|
50
|
+
text: vi.fn().mockResolvedValue(`
|
51
|
+
<html>
|
52
|
+
<head>
|
53
|
+
<title>Test Title</title>
|
54
|
+
<meta name="description" content="Test Description">
|
55
|
+
</head>
|
56
|
+
<body>
|
57
|
+
<h1>Test Content</h1>
|
58
|
+
</body>
|
59
|
+
</html>
|
60
|
+
`),
|
61
|
+
} as any);
|
62
|
+
|
63
|
+
const result = await browserless('https://example.com', { filterOptions: {} });
|
64
|
+
|
65
|
+
expect(result).toEqual({
|
66
|
+
content: expect.any(String),
|
67
|
+
contentType: 'text',
|
68
|
+
description: expect.any(String),
|
69
|
+
length: expect.any(Number),
|
70
|
+
siteName: null,
|
71
|
+
title: 'Test Title',
|
72
|
+
url: 'https://example.com',
|
73
|
+
});
|
74
|
+
});
|
75
|
+
|
76
|
+
it('should use correct URL when BROWSERLESS_URL is provided', async () => {
|
77
|
+
const customUrl = 'https://custom.browserless.io';
|
78
|
+
const originalEnv = { ...process.env };
|
79
|
+
process.env.BROWSERLESS_TOKEN = 'test-token';
|
80
|
+
process.env.BROWSERLESS_URL = customUrl;
|
81
|
+
global.fetch = vi.fn().mockImplementation((url) => {
|
82
|
+
expect(url).toContain(customUrl);
|
83
|
+
return Promise.resolve({
|
84
|
+
text: () => Promise.resolve('<html><title>Test</title></html>'),
|
85
|
+
});
|
86
|
+
});
|
87
|
+
|
88
|
+
await browserless('https://example.com', { filterOptions: {} });
|
89
|
+
|
90
|
+
expect(global.fetch).toHaveBeenCalled();
|
91
|
+
|
92
|
+
process.env = originalEnv;
|
93
|
+
});
|
94
|
+
});
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import qs from 'query-string';
|
2
|
+
import urlJoin from 'url-join';
|
2
3
|
|
3
4
|
import { CrawlImpl, CrawlSuccessResult } from '../type';
|
4
5
|
import { htmlToMarkdown } from '../utils/htmlToMarkdown';
|
@@ -25,7 +26,7 @@ export const browserless: CrawlImpl = async (url, { filterOptions }) => {
|
|
25
26
|
|
26
27
|
try {
|
27
28
|
const res = await fetch(
|
28
|
-
qs.stringifyUrl({ query: { token: BROWSERLESS_TOKEN }, url:
|
29
|
+
qs.stringifyUrl({ query: { token: BROWSERLESS_TOKEN }, url: urlJoin(BASE_URL, '/content') }),
|
29
30
|
{
|
30
31
|
body: JSON.stringify(input),
|
31
32
|
headers: {
|
@@ -0,0 +1,31 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useSearchParams } from 'next/navigation';
|
4
|
+
import { useEffect } from 'react';
|
5
|
+
|
6
|
+
import { useSendMessage } from '@/features/ChatInput/useSend';
|
7
|
+
import { useChatStore } from '@/store/chat';
|
8
|
+
|
9
|
+
const MessageFromUrl = () => {
|
10
|
+
const updateInputMessage = useChatStore((s) => s.updateInputMessage);
|
11
|
+
const { send: sendMessage } = useSendMessage();
|
12
|
+
const searchParams = useSearchParams();
|
13
|
+
|
14
|
+
useEffect(() => {
|
15
|
+
const message = searchParams.get('message');
|
16
|
+
if (message) {
|
17
|
+
// Remove message from URL
|
18
|
+
const params = new URLSearchParams(searchParams.toString());
|
19
|
+
params.delete('message');
|
20
|
+
const newUrl = `${window.location.pathname}?${params.toString()}`;
|
21
|
+
window.history.replaceState({}, '', newUrl);
|
22
|
+
|
23
|
+
updateInputMessage(message);
|
24
|
+
sendMessage();
|
25
|
+
}
|
26
|
+
}, [searchParams, updateInputMessage, sendMessage]);
|
27
|
+
|
28
|
+
return null;
|
29
|
+
};
|
30
|
+
|
31
|
+
export default MessageFromUrl;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Button, Space } from 'antd';
|
2
2
|
import { createStyles } from 'antd-style';
|
3
3
|
import { rgba } from 'polished';
|
4
|
-
import { memo, useEffect, useState } from 'react';
|
4
|
+
import { Suspense, memo, useEffect, useState } from 'react';
|
5
5
|
import { useTranslation } from 'react-i18next';
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
7
7
|
|
@@ -13,6 +13,7 @@ import { useChatStore } from '@/store/chat';
|
|
13
13
|
import { chatSelectors } from '@/store/chat/selectors';
|
14
14
|
import { isMacOS } from '@/utils/platform';
|
15
15
|
|
16
|
+
import MessageFromUrl from './MessageFromUrl';
|
16
17
|
import SendMore from './SendMore';
|
17
18
|
import ShortcutHint from './ShortcutHint';
|
18
19
|
|
@@ -67,49 +68,54 @@ const Footer = memo<FooterProps>(({ onExpandChange, expand }) => {
|
|
67
68
|
}, [setIsMac]);
|
68
69
|
|
69
70
|
return (
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
<
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
>
|
92
|
-
{t('input.stop')}
|
93
|
-
</Button>
|
94
|
-
) : (
|
95
|
-
<Space.Compact>
|
71
|
+
<>
|
72
|
+
<Suspense fallback={null}>
|
73
|
+
<MessageFromUrl />
|
74
|
+
</Suspense>
|
75
|
+
<Flexbox
|
76
|
+
align={'end'}
|
77
|
+
className={styles.overrideAntdIcon}
|
78
|
+
distribution={'space-between'}
|
79
|
+
flex={'none'}
|
80
|
+
gap={8}
|
81
|
+
horizontal
|
82
|
+
padding={'0 24px'}
|
83
|
+
>
|
84
|
+
<Flexbox align={'center'} gap={8} horizontal style={{ overflow: 'hidden' }}>
|
85
|
+
{expand && <LocalFiles />}
|
86
|
+
</Flexbox>
|
87
|
+
<Flexbox align={'center'} flex={'none'} gap={8} horizontal>
|
88
|
+
<ShortcutHint />
|
89
|
+
<SaveTopic />
|
90
|
+
<Flexbox style={{ minWidth: 92 }}>
|
91
|
+
{isAIGenerating ? (
|
96
92
|
<Button
|
97
|
-
|
98
|
-
|
99
|
-
onClick={
|
100
|
-
sendMessage();
|
101
|
-
onExpandChange?.(false);
|
102
|
-
}}
|
103
|
-
type={'primary'}
|
93
|
+
className={styles.loadingButton}
|
94
|
+
icon={<StopLoadingIcon />}
|
95
|
+
onClick={stopGenerateMessage}
|
104
96
|
>
|
105
|
-
{t('input.
|
97
|
+
{t('input.stop')}
|
106
98
|
</Button>
|
107
|
-
|
108
|
-
|
109
|
-
|
99
|
+
) : (
|
100
|
+
<Space.Compact>
|
101
|
+
<Button
|
102
|
+
disabled={!canSend}
|
103
|
+
loading={!canSend}
|
104
|
+
onClick={() => {
|
105
|
+
sendMessage();
|
106
|
+
onExpandChange?.(false);
|
107
|
+
}}
|
108
|
+
type={'primary'}
|
109
|
+
>
|
110
|
+
{t('input.send')}
|
111
|
+
</Button>
|
112
|
+
<SendMore disabled={!canSend} isMac={isMac} />
|
113
|
+
</Space.Compact>
|
114
|
+
)}
|
115
|
+
</Flexbox>
|
110
116
|
</Flexbox>
|
111
117
|
</Flexbox>
|
112
|
-
|
118
|
+
</>
|
113
119
|
);
|
114
120
|
});
|
115
121
|
|
@@ -137,6 +137,31 @@ const openrouterChatModels: AIChatModelCard[] = [
|
|
137
137
|
releasedAt: '2024-06-20',
|
138
138
|
type: 'chat',
|
139
139
|
},
|
140
|
+
{
|
141
|
+
abilities: {
|
142
|
+
functionCall: true,
|
143
|
+
reasoning: true,
|
144
|
+
vision: true,
|
145
|
+
},
|
146
|
+
contextWindowTokens: 200_000,
|
147
|
+
description:
|
148
|
+
'Claude 3.7 Sonnet 是 Anthropic 迄今为止最智能的模型,也是市场上首个混合推理模型。Claude 3.7 Sonnet 可以产生近乎即时的响应或延长的逐步思考,用户可以清晰地看到这些过程。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
|
149
|
+
displayName: 'Claude 3.7 Sonnet',
|
150
|
+
enabled: true,
|
151
|
+
id: 'anthropic/claude-3.7-sonnet',
|
152
|
+
maxOutput: 8192,
|
153
|
+
pricing: {
|
154
|
+
cachedInput: 0.3,
|
155
|
+
input: 3,
|
156
|
+
output: 15,
|
157
|
+
writeCacheInput: 3.75,
|
158
|
+
},
|
159
|
+
releasedAt: '2025-02-24',
|
160
|
+
settings: {
|
161
|
+
extendParams: ['enableReasoning', 'reasoningBudgetToken'],
|
162
|
+
},
|
163
|
+
type: 'chat',
|
164
|
+
},
|
140
165
|
{
|
141
166
|
abilities: {
|
142
167
|
functionCall: true,
|
@@ -258,7 +283,7 @@ const openrouterChatModels: AIChatModelCard[] = [
|
|
258
283
|
id: 'deepseek/deepseek-r1:free',
|
259
284
|
releasedAt: '2025-01-20',
|
260
285
|
type: 'chat',
|
261
|
-
},
|
286
|
+
},
|
262
287
|
{
|
263
288
|
abilities: {
|
264
289
|
vision: true,
|
@@ -112,12 +112,6 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
|
|
112
112
|
</Flexbox>
|
113
113
|
</Tooltip>
|
114
114
|
)}
|
115
|
-
<Tooltip title={t('messages.modelCard.pricing.inputTokens', { amount: inputPrice })}>
|
116
|
-
<Flexbox gap={2} horizontal>
|
117
|
-
<Icon icon={ArrowUpFromDot} />
|
118
|
-
{inputPrice}
|
119
|
-
</Flexbox>
|
120
|
-
</Tooltip>
|
121
115
|
{pricing?.writeCacheInput && (
|
122
116
|
<Tooltip
|
123
117
|
title={t('messages.modelCard.pricing.writeCacheInputTokens', {
|
@@ -130,6 +124,12 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
|
|
130
124
|
</Flexbox>
|
131
125
|
</Tooltip>
|
132
126
|
)}
|
127
|
+
<Tooltip title={t('messages.modelCard.pricing.inputTokens', { amount: inputPrice })}>
|
128
|
+
<Flexbox gap={2} horizontal>
|
129
|
+
<Icon icon={ArrowUpFromDot} />
|
130
|
+
{inputPrice}
|
131
|
+
</Flexbox>
|
132
|
+
</Tooltip>
|
133
133
|
<Tooltip title={t('messages.modelCard.pricing.outputTokens', { amount: outputPrice })}>
|
134
134
|
<Flexbox gap={2} horizontal>
|
135
135
|
<Icon icon={ArrowDownToDot} />
|
@@ -138,7 +138,7 @@ const TokenDetail = memo<TokenDetailProps>(({ usage, model, provider }) => {
|
|
138
138
|
</Flexbox>
|
139
139
|
)}
|
140
140
|
{outputDetails.length > 1 && (
|
141
|
-
|
141
|
+
<Flexbox gap={4}>
|
142
142
|
<Flexbox
|
143
143
|
align={'center'}
|
144
144
|
gap={4}
|
@@ -146,12 +146,12 @@ const TokenDetail = memo<TokenDetailProps>(({ usage, model, provider }) => {
|
|
146
146
|
justify={'space-between'}
|
147
147
|
width={'100%'}
|
148
148
|
>
|
149
|
-
<div style={{ color: theme.colorTextDescription }}>
|
149
|
+
<div style={{ color: theme.colorTextDescription, fontSize: 12 }}>
|
150
150
|
{t('messages.tokenDetails.outputTitle')}
|
151
151
|
</div>
|
152
152
|
</Flexbox>
|
153
153
|
<TokenProgress data={outputDetails} showIcon />
|
154
|
-
|
154
|
+
</Flexbox>
|
155
155
|
)}
|
156
156
|
<Flexbox>
|
157
157
|
<TokenProgress data={totalDetail} showIcon />
|
@@ -1,8 +1,8 @@
|
|
1
|
+
import type { ChatModelCard } from '@/types/llm';
|
2
|
+
|
1
3
|
import { ModelProvider } from '../types';
|
2
4
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
3
5
|
|
4
|
-
import type { ChatModelCard } from '@/types/llm';
|
5
|
-
|
6
6
|
export interface MistralModelCard {
|
7
7
|
capabilities: {
|
8
8
|
function_calling: boolean;
|
@@ -16,6 +16,9 @@ export interface MistralModelCard {
|
|
16
16
|
export const LobeMistralAI = LobeOpenAICompatibleFactory({
|
17
17
|
baseURL: 'https://api.mistral.ai/v1',
|
18
18
|
chatCompletion: {
|
19
|
+
// Mistral API does not support stream_options: { include_usage: true }
|
20
|
+
// refs: https://github.com/lobehub/lobe-chat/issues/6825
|
21
|
+
excludeUsage: true,
|
19
22
|
handlePayload: (payload) => ({
|
20
23
|
...(payload.max_tokens !== undefined && { max_tokens: payload.max_tokens }),
|
21
24
|
messages: payload.messages as any,
|
@@ -33,12 +36,14 @@ export const LobeMistralAI = LobeOpenAICompatibleFactory({
|
|
33
36
|
models: async ({ client }) => {
|
34
37
|
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
35
38
|
|
36
|
-
const modelsPage = await client.models.list() as any;
|
39
|
+
const modelsPage = (await client.models.list()) as any;
|
37
40
|
const modelList: MistralModelCard[] = modelsPage.data;
|
38
41
|
|
39
42
|
return modelList
|
40
43
|
.map((model) => {
|
41
|
-
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
44
|
+
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
45
|
+
(m) => model.id.toLowerCase() === m.id.toLowerCase(),
|
46
|
+
);
|
42
47
|
|
43
48
|
return {
|
44
49
|
contextWindowTokens: model.max_context_length,
|
@@ -47,9 +52,7 @@ export const LobeMistralAI = LobeOpenAICompatibleFactory({
|
|
47
52
|
enabled: knownModel?.enabled || false,
|
48
53
|
functionCall: model.capabilities.function_calling,
|
49
54
|
id: model.id,
|
50
|
-
reasoning:
|
51
|
-
knownModel?.abilities?.reasoning
|
52
|
-
|| false,
|
55
|
+
reasoning: knownModel?.abilities?.reasoning || false,
|
53
56
|
vision: model.capabilities.vision,
|
54
57
|
};
|
55
58
|
})
|
@@ -92,6 +92,39 @@ describe('LobeOpenRouterAI', () => {
|
|
92
92
|
expect(result).toBeInstanceOf(Response);
|
93
93
|
});
|
94
94
|
|
95
|
+
it('should add reasoning field when thinking is enabled', async () => {
|
96
|
+
// Arrange
|
97
|
+
const mockStream = new ReadableStream();
|
98
|
+
const mockResponse = Promise.resolve(mockStream);
|
99
|
+
|
100
|
+
(instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);
|
101
|
+
|
102
|
+
// Act
|
103
|
+
const result = await instance.chat({
|
104
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
105
|
+
model: 'mistralai/mistral-7b-instruct:free',
|
106
|
+
temperature: 0.7,
|
107
|
+
thinking: {
|
108
|
+
type: 'enabled',
|
109
|
+
budget_tokens: 1500,
|
110
|
+
},
|
111
|
+
});
|
112
|
+
|
113
|
+
// Assert
|
114
|
+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
|
115
|
+
expect.objectContaining({
|
116
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
117
|
+
model: 'mistralai/mistral-7b-instruct:free',
|
118
|
+
reasoning: {
|
119
|
+
max_tokens: 1500,
|
120
|
+
},
|
121
|
+
temperature: 0.7,
|
122
|
+
}),
|
123
|
+
{ headers: { Accept: '*/*' } },
|
124
|
+
);
|
125
|
+
expect(result).toBeInstanceOf(Response);
|
126
|
+
});
|
127
|
+
|
95
128
|
describe('Error', () => {
|
96
129
|
it('should return OpenRouterBizError with an openai error response when OpenAI.APIError is thrown', async () => {
|
97
130
|
// Arrange
|
@@ -2,7 +2,7 @@ import type { ChatModelCard } from '@/types/llm';
|
|
2
2
|
|
3
3
|
import { ModelProvider } from '../types';
|
4
4
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
5
|
-
import { OpenRouterModelCard, OpenRouterModelExtraInfo } from './type';
|
5
|
+
import { OpenRouterModelCard, OpenRouterModelExtraInfo, OpenRouterReasoning } from './type';
|
6
6
|
|
7
7
|
const formatPrice = (price: string) => {
|
8
8
|
if (price === '-1') return undefined;
|
@@ -13,10 +13,19 @@ export const LobeOpenRouterAI = LobeOpenAICompatibleFactory({
|
|
13
13
|
baseURL: 'https://openrouter.ai/api/v1',
|
14
14
|
chatCompletion: {
|
15
15
|
handlePayload: (payload) => {
|
16
|
+
const { thinking } = payload;
|
17
|
+
|
18
|
+
let reasoning: OpenRouterReasoning = {};
|
19
|
+
if (thinking?.type === 'enabled') {
|
20
|
+
reasoning = {
|
21
|
+
max_tokens: thinking.budget_tokens,
|
22
|
+
};
|
23
|
+
}
|
24
|
+
|
16
25
|
return {
|
17
26
|
...payload,
|
18
|
-
include_reasoning: true,
|
19
27
|
model: payload.enabledSearch ? `${payload.model}:online` : payload.model,
|
28
|
+
reasoning,
|
20
29
|
stream: payload.stream ?? true,
|
21
30
|
} as any;
|
22
31
|
},
|
@@ -37,3 +37,22 @@ export interface OpenRouterModelExtraInfo {
|
|
37
37
|
endpoint?: OpenRouterModelEndpoint;
|
38
38
|
slug: string;
|
39
39
|
}
|
40
|
+
|
41
|
+
interface OpenRouterOpenAIReasoning {
|
42
|
+
effort: 'high' | 'medium' | 'low';
|
43
|
+
exclude?: boolean;
|
44
|
+
}
|
45
|
+
|
46
|
+
interface OpenRouterAnthropicReasoning {
|
47
|
+
exclude?: boolean;
|
48
|
+
max_tokens: number;
|
49
|
+
}
|
50
|
+
|
51
|
+
interface OpenRouterCommonReasoning {
|
52
|
+
exclude?: boolean;
|
53
|
+
}
|
54
|
+
|
55
|
+
export type OpenRouterReasoning =
|
56
|
+
| OpenRouterOpenAIReasoning
|
57
|
+
| OpenRouterAnthropicReasoning
|
58
|
+
| OpenRouterCommonReasoning;
|
@@ -57,6 +57,7 @@ interface OpenAICompatibleFactoryOptions<T extends Record<string, any> = any> {
|
|
57
57
|
apiKey?: string;
|
58
58
|
baseURL?: string;
|
59
59
|
chatCompletion?: {
|
60
|
+
excludeUsage?: boolean;
|
60
61
|
handleError?: (
|
61
62
|
error: any,
|
62
63
|
options: ConstructorOptions<T>,
|
@@ -224,12 +225,17 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
|
|
224
225
|
...postPayload,
|
225
226
|
messages,
|
226
227
|
...(chatCompletion?.noUserId ? {} : { user: options?.user }),
|
227
|
-
stream_options:
|
228
|
+
stream_options:
|
229
|
+
postPayload.stream && !chatCompletion?.excludeUsage
|
230
|
+
? { include_usage: true }
|
231
|
+
: undefined,
|
228
232
|
};
|
229
233
|
|
230
234
|
if (debug?.chatCompletion?.()) {
|
231
|
-
console.log('[requestPayload]
|
235
|
+
console.log('[requestPayload]');
|
236
|
+
console.log(JSON.stringify(finalPayload), '\n');
|
232
237
|
}
|
238
|
+
|
233
239
|
response = await this.client.chat.completions.create(finalPayload, {
|
234
240
|
// https://github.com/lobehub/lobe-chat/pull/318
|
235
241
|
headers: { Accept: '*/*', ...options?.requestHeaders },
|
package/src/services/chat.ts
CHANGED
@@ -17,13 +17,19 @@ import {
|
|
17
17
|
} from '@/libs/agent-runtime';
|
18
18
|
import { filesPrompts } from '@/prompts/files';
|
19
19
|
import { BuiltinSystemRolePrompts } from '@/prompts/systemRole';
|
20
|
-
import {
|
21
|
-
import {
|
22
|
-
import {
|
20
|
+
import { getAgentStoreState } from '@/store/agent';
|
21
|
+
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
22
|
+
import {
|
23
|
+
aiModelSelectors,
|
24
|
+
aiProviderSelectors,
|
25
|
+
getAiInfraStoreState,
|
26
|
+
useAiInfraStore,
|
27
|
+
} from '@/store/aiInfra';
|
28
|
+
import { getSessionStoreState } from '@/store/session';
|
23
29
|
import { sessionMetaSelectors } from '@/store/session/selectors';
|
24
|
-
import {
|
30
|
+
import { getToolStoreState } from '@/store/tool';
|
25
31
|
import { pluginSelectors, toolSelectors } from '@/store/tool/selectors';
|
26
|
-
import { useUserStore } from '@/store/user';
|
32
|
+
import { getUserStoreState, useUserStore } from '@/store/user';
|
27
33
|
import {
|
28
34
|
modelConfigSelectors,
|
29
35
|
modelProviderSelectors,
|
@@ -46,10 +52,10 @@ import { API_ENDPOINTS } from './_url';
|
|
46
52
|
const isCanUseFC = (model: string, provider: string) => {
|
47
53
|
// TODO: remove isDeprecatedEdition condition in V2.0
|
48
54
|
if (isDeprecatedEdition) {
|
49
|
-
return modelProviderSelectors.isModelEnabledFunctionCall(model)(
|
55
|
+
return modelProviderSelectors.isModelEnabledFunctionCall(model)(getUserStoreState());
|
50
56
|
}
|
51
57
|
|
52
|
-
return aiModelSelectors.isModelSupportToolUse(model, provider)(
|
58
|
+
return aiModelSelectors.isModelSupportToolUse(model, provider)(getAiInfraStoreState());
|
53
59
|
};
|
54
60
|
|
55
61
|
/**
|
@@ -169,7 +175,7 @@ class ChatService {
|
|
169
175
|
);
|
170
176
|
|
171
177
|
// =================== 0. process search =================== //
|
172
|
-
const chatConfig =
|
178
|
+
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
173
179
|
|
174
180
|
const enabledSearch = chatConfig.searchMode !== 'off';
|
175
181
|
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
@@ -200,7 +206,7 @@ class ChatService {
|
|
200
206
|
|
201
207
|
// ============ 2. preprocess tools ============ //
|
202
208
|
|
203
|
-
let filterTools = toolSelectors.enabledSchema(pluginIds)(
|
209
|
+
let filterTools = toolSelectors.enabledSchema(pluginIds)(getToolStoreState());
|
204
210
|
|
205
211
|
// check this model can use function call
|
206
212
|
const canUseFC = isCanUseFC(payload.model, payload.provider!);
|
@@ -378,7 +384,7 @@ class ChatService {
|
|
378
384
|
* @param options
|
379
385
|
*/
|
380
386
|
runPluginApi = async (params: PluginRequestPayload, options?: FetchOptions) => {
|
381
|
-
const s =
|
387
|
+
const s = getToolStoreState();
|
382
388
|
|
383
389
|
const settings = pluginSelectors.getPluginSettingsById(params.identifier)(s);
|
384
390
|
const manifest = pluginSelectors.getToolManifestById(params.identifier)(s);
|
@@ -537,7 +543,7 @@ class ChatService {
|
|
537
543
|
const hasTools = tools && tools?.length > 0;
|
538
544
|
const hasFC = hasTools && isCanUseFC(model, provider);
|
539
545
|
const toolsSystemRoles =
|
540
|
-
hasFC && toolSelectors.enabledSystemRoles(tools)(
|
546
|
+
hasFC && toolSelectors.enabledSystemRoles(tools)(getToolStoreState());
|
541
547
|
|
542
548
|
const injectSystemRoles = BuiltinSystemRolePrompts({
|
543
549
|
historySummary: options?.historySummary,
|
@@ -565,9 +571,9 @@ class ChatService {
|
|
565
571
|
};
|
566
572
|
|
567
573
|
private mapTrace = (trace?: TracePayload, tag?: TraceTagMap): TracePayload => {
|
568
|
-
const tags = sessionMetaSelectors.currentAgentMeta(
|
574
|
+
const tags = sessionMetaSelectors.currentAgentMeta(getSessionStoreState()).tags || [];
|
569
575
|
|
570
|
-
const enabled = preferenceSelectors.userAllowTrace(
|
576
|
+
const enabled = preferenceSelectors.userAllowTrace(getUserStoreState());
|
571
577
|
|
572
578
|
if (!enabled) return { ...trace, enabled: false };
|
573
579
|
|
package/src/store/agent/index.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
export type { AgentStore } from './store';
|
2
|
-
export { useAgentStore } from './store';
|
2
|
+
export { getAgentStoreState, useAgentStore } from './store';
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { contextCachingModels, thinkingWithToolClaudeModels } from '@/const/models';
|
2
|
+
import { DEFAULT_AGENT_CHAT_CONFIG } from '@/const/settings';
|
2
3
|
import { AgentStoreState } from '@/store/agent/initialState';
|
3
4
|
import { LobeAgentChatConfig } from '@/types/agent';
|
4
5
|
|
@@ -30,10 +31,10 @@ const enableHistoryCount = (s: AgentStoreState) => {
|
|
30
31
|
return chatConfig.enableHistoryCount;
|
31
32
|
};
|
32
33
|
|
33
|
-
const historyCount = (s: AgentStoreState) => {
|
34
|
+
const historyCount = (s: AgentStoreState): number => {
|
34
35
|
const chatConfig = currentAgentChatConfig(s);
|
35
36
|
|
36
|
-
return chatConfig.historyCount;
|
37
|
+
return chatConfig.historyCount || (DEFAULT_AGENT_CHAT_CONFIG.historyCount as number);
|
37
38
|
};
|
38
39
|
|
39
40
|
const displayMode = (s: AgentStoreState) => {
|
package/src/store/agent/store.ts
CHANGED
@@ -20,3 +20,5 @@ const createStore: StateCreator<AgentStore, [['zustand/devtools', never]]> = (..
|
|
20
20
|
const devtools = createDevtools('agent');
|
21
21
|
|
22
22
|
export const useAgentStore = createWithEqualityFn<AgentStore>()(devtools(createStore), shallow);
|
23
|
+
|
24
|
+
export const getAgentStoreState = () => useAgentStore.getState();
|
@@ -1,2 +1,2 @@
|
|
1
1
|
export * from './selectors';
|
2
|
-
export { useAiInfraStore } from './store';
|
2
|
+
export { getAiInfraStoreState,useAiInfraStore } from './store';
|
@@ -23,3 +23,5 @@ const createStore: StateCreator<AiInfraStore, [['zustand/devtools', never]]> = (
|
|
23
23
|
const devtools = createDevtools('aiInfra');
|
24
24
|
|
25
25
|
export const useAiInfraStore = createWithEqualityFn<AiInfraStore>()(devtools(createStore), shallow);
|
26
|
+
|
27
|
+
export const getAiInfraStoreState = () => useAiInfraStore.getState();
|
@@ -5,13 +5,14 @@ import { template } from 'lodash-es';
|
|
5
5
|
import { StateCreator } from 'zustand/vanilla';
|
6
6
|
|
7
7
|
import { LOADING_FLAT, MESSAGE_CANCEL_FLAT } from '@/const/message';
|
8
|
-
import { DEFAULT_AGENT_CHAT_CONFIG } from '@/const/settings';
|
9
8
|
import { TraceEventType, TraceNameMap } from '@/const/trace';
|
10
9
|
import { isServerMode } from '@/const/version';
|
11
10
|
import { knowledgeBaseQAPrompts } from '@/prompts/knowledgeBaseQA';
|
12
11
|
import { chatService } from '@/services/chat';
|
13
12
|
import { messageService } from '@/services/message';
|
14
13
|
import { useAgentStore } from '@/store/agent';
|
14
|
+
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
15
|
+
import { getAgentStoreState } from '@/store/agent/store';
|
15
16
|
import { chatHelpers } from '@/store/chat/helpers';
|
16
17
|
import { ChatStore } from '@/store/chat/store';
|
17
18
|
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
@@ -21,12 +22,6 @@ import { MessageSemanticSearchChunk } from '@/types/rag';
|
|
21
22
|
import { setNamespace } from '@/utils/storeDebug';
|
22
23
|
|
23
24
|
import { chatSelectors, topicSelectors } from '../../../selectors';
|
24
|
-
import {
|
25
|
-
getAgentChatConfig,
|
26
|
-
getAgentConfig,
|
27
|
-
getAgentEnableHistoryCount,
|
28
|
-
getAgentKnowledge,
|
29
|
-
} from './helpers';
|
30
25
|
|
31
26
|
const n = setNamespace('ai');
|
32
27
|
|
@@ -163,7 +158,7 @@ export const generateAIChat: StateCreator<
|
|
163
158
|
threadId: activeThreadId,
|
164
159
|
};
|
165
160
|
|
166
|
-
const agentConfig =
|
161
|
+
const agentConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
167
162
|
|
168
163
|
let tempMessageId: string | undefined = undefined;
|
169
164
|
let newTopicId: string | undefined = undefined;
|
@@ -288,7 +283,7 @@ export const generateAIChat: StateCreator<
|
|
288
283
|
// create a new array to avoid the original messages array change
|
289
284
|
const messages = [...originalMessages];
|
290
285
|
|
291
|
-
const { model, provider, chatConfig } =
|
286
|
+
const { model, provider, chatConfig } = agentSelectors.currentAgentConfig(getAgentStoreState());
|
292
287
|
|
293
288
|
let fileChunks: MessageSemanticSearchChunk[] | undefined;
|
294
289
|
let ragQueryId;
|
@@ -312,7 +307,7 @@ export const generateAIChat: StateCreator<
|
|
312
307
|
chunks,
|
313
308
|
userQuery: lastMsg.content,
|
314
309
|
rewriteQuery,
|
315
|
-
knowledge:
|
310
|
+
knowledge: agentSelectors.currentEnabledKnowledge(getAgentStoreState()),
|
316
311
|
});
|
317
312
|
|
318
313
|
// 3. add the retrieve context messages to the messages history
|
@@ -354,11 +349,10 @@ export const generateAIChat: StateCreator<
|
|
354
349
|
}
|
355
350
|
|
356
351
|
// 5. summary history if context messages is larger than historyCount
|
357
|
-
const historyCount =
|
358
|
-
chatConfig.historyCount || (DEFAULT_AGENT_CHAT_CONFIG.historyCount as number);
|
352
|
+
const historyCount = agentChatConfigSelectors.historyCount(getAgentStoreState());
|
359
353
|
|
360
354
|
if (
|
361
|
-
|
355
|
+
agentChatConfigSelectors.enableHistoryCount(getAgentStoreState()) &&
|
362
356
|
chatConfig.enableCompressHistory &&
|
363
357
|
originalMessages.length > historyCount
|
364
358
|
) {
|
@@ -387,7 +381,7 @@ export const generateAIChat: StateCreator<
|
|
387
381
|
n('generateMessage(start)', { messageId, messages }) as string,
|
388
382
|
);
|
389
383
|
|
390
|
-
const agentConfig =
|
384
|
+
const agentConfig = agentSelectors.currentAgentConfig(getAgentStoreState());
|
391
385
|
const chatConfig = agentConfig.chatConfig;
|
392
386
|
|
393
387
|
const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });
|
@@ -397,10 +391,13 @@ export const generateAIChat: StateCreator<
|
|
397
391
|
// ================================== //
|
398
392
|
|
399
393
|
// 1. slice messages with config
|
394
|
+
const historyCount = agentChatConfigSelectors.historyCount(getAgentStoreState());
|
395
|
+
const enableHistoryCount = agentChatConfigSelectors.enableHistoryCount(getAgentStoreState());
|
396
|
+
|
400
397
|
let preprocessMsgs = chatHelpers.getSlicedMessages(messages, {
|
401
398
|
includeNewUserMessage: true,
|
402
|
-
enableHistoryCount
|
403
|
-
historyCount
|
399
|
+
enableHistoryCount,
|
400
|
+
historyCount,
|
404
401
|
});
|
405
402
|
|
406
403
|
// 2. replace inputMessage template
|
@@ -1,2 +1,2 @@
|
|
1
1
|
export type { SessionStore } from './store';
|
2
|
-
export { useSessionStore } from './store';
|
2
|
+
export { getSessionStoreState,useSessionStore } from './store';
|
package/src/store/tool/index.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
export * from './helpers';
|
2
|
-
export { useToolStore } from './store';
|
2
|
+
export { getToolStoreState, useToolStore } from './store';
|
package/src/store/tool/store.ts
CHANGED
@@ -30,3 +30,5 @@ const createStore: StateCreator<ToolStore, [['zustand/devtools', never]]> = (...
|
|
30
30
|
const devtools = createDevtools('tools');
|
31
31
|
|
32
32
|
export const useToolStore = createWithEqualityFn<ToolStore>()(devtools(createStore), shallow);
|
33
|
+
|
34
|
+
export const getToolStoreState = () => useToolStore.getState();
|
@@ -118,22 +118,23 @@ export const createModelListSlice: StateCreator<
|
|
118
118
|
get().refreshModelProviderList({ trigger: 'refreshDefaultModelList' });
|
119
119
|
},
|
120
120
|
refreshModelProviderList: (params) => {
|
121
|
-
const modelProviderList = get().defaultModelProviderList.map((list) =>
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
121
|
+
const modelProviderList = get().defaultModelProviderList.map((list) => {
|
122
|
+
const enabledModels = modelProviderSelectors.getEnableModelsById(list.id)(get());
|
123
|
+
return {
|
124
|
+
...list,
|
125
|
+
chatModels: modelProviderSelectors
|
126
|
+
.getModelCardsById(list.id)(get())
|
127
|
+
?.map((model) => {
|
128
|
+
if (!enabledModels) return model;
|
129
|
+
|
130
|
+
return {
|
131
|
+
...model,
|
132
|
+
enabled: enabledModels?.some((m) => m === model.id),
|
133
|
+
};
|
134
|
+
}),
|
135
|
+
enabled: modelProviderSelectors.isProviderEnabled(list.id as any)(get()),
|
136
|
+
};
|
137
|
+
});
|
137
138
|
|
138
139
|
set({ modelProviderList }, false, `refreshModelList - ${params?.trigger}`);
|
139
140
|
},
|
package/src/store/user/store.ts
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
2
|
+
import { z } from 'zod';
|
3
|
+
|
4
|
+
import { SearchMode } from '@/types/search';
|
5
|
+
|
6
|
+
export interface WorkingModel {
|
7
|
+
model: string;
|
8
|
+
provider: string;
|
9
|
+
}
|
10
|
+
|
11
|
+
export interface LobeAgentChatConfig {
|
12
|
+
displayMode?: 'chat' | 'docs';
|
13
|
+
|
14
|
+
enableAutoCreateTopic?: boolean;
|
15
|
+
autoCreateTopicThreshold: number;
|
16
|
+
|
17
|
+
enableMaxTokens?: boolean;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* 是否开启推理
|
21
|
+
*/
|
22
|
+
enableReasoning?: boolean;
|
23
|
+
/**
|
24
|
+
* 自定义推理强度
|
25
|
+
*/
|
26
|
+
enableReasoningEffort?: boolean;
|
27
|
+
reasoningBudgetToken?: number;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* 禁用上下文缓存
|
31
|
+
*/
|
32
|
+
disableContextCaching?: boolean;
|
33
|
+
/**
|
34
|
+
* 历史消息条数
|
35
|
+
*/
|
36
|
+
historyCount?: number;
|
37
|
+
/**
|
38
|
+
* 开启历史记录条数
|
39
|
+
*/
|
40
|
+
enableHistoryCount?: boolean;
|
41
|
+
/**
|
42
|
+
* 历史消息长度压缩阈值
|
43
|
+
*/
|
44
|
+
enableCompressHistory?: boolean;
|
45
|
+
|
46
|
+
inputTemplate?: string;
|
47
|
+
|
48
|
+
searchMode?: SearchMode;
|
49
|
+
searchFCModel?: WorkingModel;
|
50
|
+
useModelBuiltinSearch?: boolean;
|
51
|
+
}
|
52
|
+
/* eslint-enable */
|
53
|
+
|
54
|
+
export const AgentChatConfigSchema = z.object({
|
55
|
+
autoCreateTopicThreshold: z.number().default(2),
|
56
|
+
displayMode: z.enum(['chat', 'docs']).optional(),
|
57
|
+
enableAutoCreateTopic: z.boolean().optional(),
|
58
|
+
enableCompressHistory: z.boolean().optional(),
|
59
|
+
enableHistoryCount: z.boolean().optional(),
|
60
|
+
enableMaxTokens: z.boolean().optional(),
|
61
|
+
enableReasoning: z.boolean().optional(),
|
62
|
+
enableReasoningEffort: z.boolean().optional(),
|
63
|
+
historyCount: z.number().optional(),
|
64
|
+
reasoningBudgetToken: z.number().optional(),
|
65
|
+
searchMode: z.enum(['off', 'on', 'auto']).optional(),
|
66
|
+
});
|
package/src/types/agent/index.ts
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
import { z } from 'zod';
|
2
|
-
|
3
1
|
import { FileItem } from '@/types/files';
|
4
2
|
import { KnowledgeBaseItem } from '@/types/knowledgeBase';
|
5
3
|
import { FewShots, LLMParams } from '@/types/llm';
|
6
|
-
|
4
|
+
|
5
|
+
import { LobeAgentChatConfig } from './chatConfig';
|
7
6
|
|
8
7
|
export type TTSServer = 'openai' | 'edge' | 'microsoft';
|
9
8
|
|
@@ -55,64 +54,8 @@ export interface LobeAgentConfig {
|
|
55
54
|
tts: LobeAgentTTSConfig;
|
56
55
|
}
|
57
56
|
|
58
|
-
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
59
|
-
|
60
|
-
export interface LobeAgentChatConfig {
|
61
|
-
displayMode?: 'chat' | 'docs';
|
62
|
-
|
63
|
-
enableAutoCreateTopic?: boolean;
|
64
|
-
autoCreateTopicThreshold: number;
|
65
|
-
|
66
|
-
enableMaxTokens?: boolean;
|
67
|
-
|
68
|
-
/**
|
69
|
-
* 是否开启推理
|
70
|
-
*/
|
71
|
-
enableReasoning?: boolean;
|
72
|
-
/**
|
73
|
-
* 自定义推理强度
|
74
|
-
*/
|
75
|
-
enableReasoningEffort?: boolean;
|
76
|
-
reasoningBudgetToken?: number;
|
77
|
-
|
78
|
-
/**
|
79
|
-
* 禁用上下文缓存
|
80
|
-
*/
|
81
|
-
disableContextCaching?: boolean;
|
82
|
-
/**
|
83
|
-
* 历史消息条数
|
84
|
-
*/
|
85
|
-
historyCount?: number;
|
86
|
-
/**
|
87
|
-
* 开启历史记录条数
|
88
|
-
*/
|
89
|
-
enableHistoryCount?: boolean;
|
90
|
-
/**
|
91
|
-
* 历史消息长度压缩阈值
|
92
|
-
*/
|
93
|
-
enableCompressHistory?: boolean;
|
94
|
-
|
95
|
-
inputTemplate?: string;
|
96
|
-
|
97
|
-
searchMode?: SearchMode;
|
98
|
-
useModelBuiltinSearch?: boolean;
|
99
|
-
}
|
100
|
-
/* eslint-enable */
|
101
|
-
|
102
|
-
export const AgentChatConfigSchema = z.object({
|
103
|
-
autoCreateTopicThreshold: z.number().default(2),
|
104
|
-
displayMode: z.enum(['chat', 'docs']).optional(),
|
105
|
-
enableAutoCreateTopic: z.boolean().optional(),
|
106
|
-
enableCompressHistory: z.boolean().optional(),
|
107
|
-
enableHistoryCount: z.boolean().optional(),
|
108
|
-
enableMaxTokens: z.boolean().optional(),
|
109
|
-
enableReasoning: z.boolean().optional(),
|
110
|
-
enableReasoningEffort: z.boolean().optional(),
|
111
|
-
historyCount: z.number().optional(),
|
112
|
-
reasoningBudgetToken: z.number().optional(),
|
113
|
-
searchMode: z.enum(['off', 'on', 'auto']).optional(),
|
114
|
-
});
|
115
|
-
|
116
57
|
export type LobeAgentConfigKeys =
|
117
58
|
| keyof LobeAgentConfig
|
118
59
|
| ['params', keyof LobeAgentConfig['params']];
|
60
|
+
|
61
|
+
export * from './chatConfig';
|
@@ -1,13 +0,0 @@
|
|
1
|
-
import { useAgentStore } from '@/store/agent';
|
2
|
-
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
3
|
-
|
4
|
-
export const getAgentConfig = () => agentSelectors.currentAgentConfig(useAgentStore.getState());
|
5
|
-
|
6
|
-
export const getAgentChatConfig = () =>
|
7
|
-
agentChatConfigSelectors.currentChatConfig(useAgentStore.getState());
|
8
|
-
|
9
|
-
export const getAgentEnableHistoryCount = () =>
|
10
|
-
agentChatConfigSelectors.enableHistoryCount(useAgentStore.getState());
|
11
|
-
|
12
|
-
export const getAgentKnowledge = () =>
|
13
|
-
agentSelectors.currentEnabledKnowledge(useAgentStore.getState());
|