@lobehub/chat 1.81.0 → 1.81.2
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/package.json +1 -1
- package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/SystemRole/SystemRoleContent.tsx +26 -7
- package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/SystemRole/style.ts +5 -0
- package/src/components/SidebarHeader/index.tsx +3 -1
- package/src/config/aiModels/sensenova.ts +120 -5
- package/src/libs/agent-runtime/sensenova/index.ts +17 -4
- package/src/libs/agent-runtime/utils/sensenovaHelpers.test.ts +108 -0
- package/src/libs/agent-runtime/utils/sensenovaHelpers.ts +30 -0
- package/src/store/global/actions/workspacePane.ts +16 -0
- package/src/store/global/initialState.ts +2 -0
- package/src/store/global/selectors/systemStatus.ts +8 -0
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.81.2](https://github.com/lobehub/lobe-chat/compare/v1.81.1...v1.81.2)
|
6
|
+
|
7
|
+
<sup>Released on **2025-04-18**</sup>
|
8
|
+
|
9
|
+
#### 💄 Styles
|
10
|
+
|
11
|
+
- **misc**: Allow folding SystemRole box.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Styles
|
19
|
+
|
20
|
+
- **misc**: Allow folding SystemRole box, closes [#7421](https://github.com/lobehub/lobe-chat/issues/7421) ([c147df7](https://github.com/lobehub/lobe-chat/commit/c147df7))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.81.1](https://github.com/lobehub/lobe-chat/compare/v1.81.0...v1.81.1)
|
31
|
+
|
32
|
+
<sup>Released on **2025-04-18**</sup>
|
33
|
+
|
34
|
+
#### 💄 Styles
|
35
|
+
|
36
|
+
- **misc**: Add `SenseNova-V6` series & `SenseChat-Vision` support.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### Styles
|
44
|
+
|
45
|
+
- **misc**: Add `SenseNova-V6` series & `SenseChat-Vision` support, closes [#7439](https://github.com/lobehub/lobe-chat/issues/7439) ([9c8597f](https://github.com/lobehub/lobe-chat/commit/9c8597f))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
## [Version 1.81.0](https://github.com/lobehub/lobe-chat/compare/v1.80.5...v1.81.0)
|
6
56
|
|
7
57
|
<sup>Released on **2025-04-17**</sup>
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
[
|
2
|
+
{
|
3
|
+
"children": {
|
4
|
+
"improvements": [
|
5
|
+
"Allow folding SystemRole box."
|
6
|
+
]
|
7
|
+
},
|
8
|
+
"date": "2025-04-18",
|
9
|
+
"version": "1.81.2"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"children": {
|
13
|
+
"improvements": [
|
14
|
+
"Add SenseNova-V6 series & SenseChat-Vision support."
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"date": "2025-04-18",
|
18
|
+
"version": "1.81.1"
|
19
|
+
},
|
2
20
|
{
|
3
21
|
"children": {
|
4
22
|
"features": [
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.81.
|
3
|
+
"version": "1.81.2",
|
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",
|
package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/SystemRole/SystemRoleContent.tsx
CHANGED
@@ -4,7 +4,7 @@ import { ActionIcon } from '@lobehub/ui';
|
|
4
4
|
import { EditableMessage } from '@lobehub/ui/chat';
|
5
5
|
import { Skeleton } from 'antd';
|
6
6
|
import { Edit } from 'lucide-react';
|
7
|
-
import { memo, useState } from 'react';
|
7
|
+
import { MouseEvent, memo, useState } from 'react';
|
8
8
|
import { useTranslation } from 'react-i18next';
|
9
9
|
import { Flexbox } from 'react-layout-kit';
|
10
10
|
import useMergeState from 'use-merge-value';
|
@@ -24,11 +24,12 @@ import { useStyles } from './style';
|
|
24
24
|
|
25
25
|
const SystemRole = memo(() => {
|
26
26
|
const [editing, setEditing] = useState(false);
|
27
|
-
const { styles } = useStyles();
|
27
|
+
const { styles, cx } = useStyles();
|
28
28
|
const openChatSettings = useOpenChatSettings(ChatSettingsTabs.Prompt);
|
29
|
-
const [init, meta] = useSessionStore((s) => [
|
29
|
+
const [init, meta, sessionId] = useSessionStore((s) => [
|
30
30
|
sessionSelectors.isSomeSessionActive(s),
|
31
31
|
sessionMetaSelectors.currentAgentMeta(s),
|
32
|
+
s.activeId,
|
32
33
|
]);
|
33
34
|
|
34
35
|
const [isAgentConfigLoading, systemRole, updateAgentConfig] = useAgentStore((s) => [
|
@@ -52,8 +53,10 @@ const SystemRole = memo(() => {
|
|
52
53
|
|
53
54
|
const isLoading = !init || isAgentConfigLoading;
|
54
55
|
|
55
|
-
const handleOpenWithEdit = () => {
|
56
|
+
const handleOpenWithEdit = (e: MouseEvent) => {
|
56
57
|
if (isLoading) return;
|
58
|
+
|
59
|
+
e.stopPropagation();
|
57
60
|
setEditing(true);
|
58
61
|
setOpen(true);
|
59
62
|
};
|
@@ -64,20 +67,36 @@ const SystemRole = memo(() => {
|
|
64
67
|
setOpen(true);
|
65
68
|
};
|
66
69
|
|
70
|
+
const [expanded, toggleAgentSystemRoleExpand] = useGlobalStore((s) => [
|
71
|
+
systemStatusSelectors.getAgentSystemRoleExpanded(sessionId)(s),
|
72
|
+
s.toggleAgentSystemRoleExpand,
|
73
|
+
]);
|
74
|
+
|
75
|
+
const toggleExpanded = () => {
|
76
|
+
toggleAgentSystemRoleExpand(sessionId);
|
77
|
+
};
|
78
|
+
|
67
79
|
return (
|
68
80
|
<Flexbox height={'fit-content'}>
|
69
81
|
<SidebarHeader
|
70
82
|
actions={
|
71
83
|
<ActionIcon icon={Edit} onClick={handleOpenWithEdit} size={'small'} title={t('edit')} />
|
72
84
|
}
|
85
|
+
onClick={toggleExpanded}
|
86
|
+
style={{ cursor: 'pointer' }}
|
73
87
|
title={t('settingAgent.prompt.title', { ns: 'setting' })}
|
74
88
|
/>
|
75
89
|
<Flexbox
|
76
|
-
className={styles.promptBox}
|
77
|
-
height={200}
|
90
|
+
className={cx(styles.promptBox, styles.animatedContainer)}
|
91
|
+
height={expanded ? 200 : 0}
|
78
92
|
onClick={handleOpen}
|
79
93
|
onDoubleClick={(e) => {
|
80
|
-
if (e.altKey) handleOpenWithEdit();
|
94
|
+
if (e.altKey) handleOpenWithEdit(e);
|
95
|
+
}}
|
96
|
+
style={{
|
97
|
+
opacity: expanded ? 1 : 0,
|
98
|
+
overflow: 'hidden',
|
99
|
+
transition: 'height 0.3s ease',
|
81
100
|
}}
|
82
101
|
>
|
83
102
|
{isLoading ? (
|
@@ -10,11 +10,12 @@ const useStyles = createStyles(({ css }) => ({
|
|
10
10
|
|
11
11
|
interface SidebarHeaderProps {
|
12
12
|
actions?: ReactNode;
|
13
|
+
onClick?: () => void;
|
13
14
|
style?: CSSProperties;
|
14
15
|
title: ReactNode;
|
15
16
|
}
|
16
17
|
|
17
|
-
const SidebarHeader = memo<SidebarHeaderProps>(({ title, style, actions }) => {
|
18
|
+
const SidebarHeader = memo<SidebarHeaderProps>(({ title, style, actions, onClick }) => {
|
18
19
|
const { styles } = useStyles();
|
19
20
|
|
20
21
|
return (
|
@@ -23,6 +24,7 @@ const SidebarHeader = memo<SidebarHeaderProps>(({ title, style, actions }) => {
|
|
23
24
|
className={styles.header}
|
24
25
|
distribution={'space-between'}
|
25
26
|
horizontal
|
27
|
+
onClick={onClick}
|
26
28
|
padding={14}
|
27
29
|
paddingInline={16}
|
28
30
|
style={style}
|
@@ -4,6 +4,62 @@ import { AIChatModelCard } from '@/types/aiModel';
|
|
4
4
|
// https://www.sensecore.cn/help/docs/model-as-a-service/nova/release
|
5
5
|
|
6
6
|
const sensenovaChatModels: AIChatModelCard[] = [
|
7
|
+
{
|
8
|
+
abilities: {
|
9
|
+
reasoning: true,
|
10
|
+
vision: true,
|
11
|
+
},
|
12
|
+
contextWindowTokens: 131_072,
|
13
|
+
description:
|
14
|
+
'兼顾视觉、语言深度推理,实现慢思考和深度推理,呈现完整的思维链过程。',
|
15
|
+
displayName: 'SenseNova V6 Reasoner',
|
16
|
+
enabled: true,
|
17
|
+
id: 'SenseNova-V6-Reasoner',
|
18
|
+
pricing: {
|
19
|
+
currency: 'CNY',
|
20
|
+
input: 4,
|
21
|
+
output: 16,
|
22
|
+
},
|
23
|
+
releasedAt: '2025-04-14',
|
24
|
+
type: 'chat',
|
25
|
+
},
|
26
|
+
{
|
27
|
+
abilities: {
|
28
|
+
reasoning: true,
|
29
|
+
vision: true,
|
30
|
+
},
|
31
|
+
contextWindowTokens: 131_072,
|
32
|
+
description:
|
33
|
+
'实现图片、文本、视频能力的原生统一,突破传统多模态分立局限,在多模基础能力、语言基础能力等核心维度全面领先,文理兼修,在多项测评中多次位列国内外第一梯队水平。',
|
34
|
+
displayName: 'SenseNova V6 Turbo',
|
35
|
+
enabled: true,
|
36
|
+
id: 'SenseNova-V6-Turbo',
|
37
|
+
pricing: {
|
38
|
+
currency: 'CNY',
|
39
|
+
input: 1.5,
|
40
|
+
output: 4.5,
|
41
|
+
},
|
42
|
+
releasedAt: '2025-04-14',
|
43
|
+
type: 'chat',
|
44
|
+
},
|
45
|
+
{
|
46
|
+
abilities: {
|
47
|
+
vision: true,
|
48
|
+
},
|
49
|
+
contextWindowTokens: 131_072,
|
50
|
+
description:
|
51
|
+
'实现图片、文本、视频能力的原生统一,突破传统多模态分立局限,在OpenCompass和SuperCLUE评测中斩获双冠军。',
|
52
|
+
displayName: 'SenseNova V6 Pro',
|
53
|
+
enabled: true,
|
54
|
+
id: 'SenseNova-V6-Pro',
|
55
|
+
pricing: {
|
56
|
+
currency: 'CNY',
|
57
|
+
input: 9,
|
58
|
+
output: 3,
|
59
|
+
},
|
60
|
+
releasedAt: '2025-04-14',
|
61
|
+
type: 'chat',
|
62
|
+
},
|
7
63
|
{
|
8
64
|
abilities: {
|
9
65
|
functionCall: true,
|
@@ -12,7 +68,6 @@ const sensenovaChatModels: AIChatModelCard[] = [
|
|
12
68
|
description:
|
13
69
|
'是基于V5.5的最新版本,较上版本在中英文基础能力,聊天,理科知识, 文科知识,写作,数理逻辑,字数控制 等几个维度的表现有显著提升。',
|
14
70
|
displayName: 'SenseChat 5.5 1202',
|
15
|
-
enabled: true,
|
16
71
|
id: 'SenseChat-5-1202',
|
17
72
|
pricing: {
|
18
73
|
currency: 'CNY',
|
@@ -30,7 +85,6 @@ const sensenovaChatModels: AIChatModelCard[] = [
|
|
30
85
|
description:
|
31
86
|
'是最新的轻量版本模型,达到全量模型90%以上能力,显著降低推理成本。',
|
32
87
|
displayName: 'SenseChat Turbo 1202',
|
33
|
-
enabled: true,
|
34
88
|
id: 'SenseChat-Turbo-1202',
|
35
89
|
pricing: {
|
36
90
|
currency: 'CNY',
|
@@ -48,7 +102,6 @@ const sensenovaChatModels: AIChatModelCard[] = [
|
|
48
102
|
description:
|
49
103
|
'最新版本模型 (V5.5),128K上下文长度,在数学推理、英文对话、指令跟随以及长文本理解等领域能力显著提升,比肩GPT-4o。',
|
50
104
|
displayName: 'SenseChat 5.5',
|
51
|
-
enabled: true,
|
52
105
|
id: 'SenseChat-5',
|
53
106
|
pricing: {
|
54
107
|
currency: 'CNY',
|
@@ -58,10 +111,12 @@ const sensenovaChatModels: AIChatModelCard[] = [
|
|
58
111
|
type: 'chat',
|
59
112
|
},
|
60
113
|
{
|
114
|
+
abilities: {
|
115
|
+
vision: true,
|
116
|
+
},
|
61
117
|
contextWindowTokens: 32_768,
|
62
118
|
description: '最新版本模型 (V5.5),支持多图的输入,全面实现模型基础能力优化,在对象属性识别、空间关系、动作事件识别、场景理解、情感识别、逻辑常识推理和文本理解生成上都实现了较大提升。',
|
63
119
|
displayName: 'SenseChat 5.5 Vision',
|
64
|
-
enabled: true,
|
65
120
|
id: 'SenseChat-Vision',
|
66
121
|
pricing: {
|
67
122
|
currency: 'CNY',
|
@@ -78,7 +133,6 @@ const sensenovaChatModels: AIChatModelCard[] = [
|
|
78
133
|
contextWindowTokens: 32_768,
|
79
134
|
description: '适用于快速问答、模型微调场景',
|
80
135
|
displayName: 'SenseChat 5.0 Turbo',
|
81
|
-
enabled: true,
|
82
136
|
id: 'SenseChat-Turbo',
|
83
137
|
pricing: {
|
84
138
|
currency: 'CNY',
|
@@ -160,6 +214,67 @@ const sensenovaChatModels: AIChatModelCard[] = [
|
|
160
214
|
},
|
161
215
|
type: 'chat',
|
162
216
|
},
|
217
|
+
{
|
218
|
+
contextWindowTokens: 32_768,
|
219
|
+
description:
|
220
|
+
'DeepSeek-V3 是一款由深度求索公司自研的MoE模型。DeepSeek-V3 多项评测成绩超越了 Qwen2.5-72B 和 Llama-3.1-405B 等其他开源模型,并在性能上和世界顶尖的闭源模型 GPT-4o 以及 Claude-3.5-Sonnet 不分伯仲。',
|
221
|
+
displayName: 'DeepSeek V3',
|
222
|
+
id: 'DeepSeek-V3',
|
223
|
+
pricing: {
|
224
|
+
currency: 'CNY',
|
225
|
+
input: 2,
|
226
|
+
output: 8,
|
227
|
+
},
|
228
|
+
type: 'chat',
|
229
|
+
},
|
230
|
+
{
|
231
|
+
abilities: {
|
232
|
+
reasoning: true,
|
233
|
+
},
|
234
|
+
contextWindowTokens: 32_768,
|
235
|
+
description:
|
236
|
+
'DeepSeek-R1 在后训练阶段大规模使用了强化学习技术,在仅有极少标注数据的情况下,极大提升了模型推理能力。在数学、代码、自然语言推理等任务上,性能比肩 OpenAI o1 正式版。',
|
237
|
+
displayName: 'DeepSeek R1',
|
238
|
+
id: 'DeepSeek-R1',
|
239
|
+
pricing: {
|
240
|
+
currency: 'CNY',
|
241
|
+
input: 4,
|
242
|
+
output: 16,
|
243
|
+
},
|
244
|
+
type: 'chat',
|
245
|
+
},
|
246
|
+
{
|
247
|
+
abilities: {
|
248
|
+
reasoning: true,
|
249
|
+
},
|
250
|
+
contextWindowTokens: 32_768,
|
251
|
+
description:
|
252
|
+
'DeepSeek-R1-Distill 模型是在开源模型的基础上通过微调训练得到的,训练过程中使用了由 DeepSeek-R1 生成的样本数据。',
|
253
|
+
displayName: 'DeepSeek R1 Distill Qwen 14B',
|
254
|
+
id: 'DeepSeek-R1-Distill-Qwen-14B',
|
255
|
+
pricing: {
|
256
|
+
currency: 'CNY',
|
257
|
+
input: 0,
|
258
|
+
output: 0,
|
259
|
+
},
|
260
|
+
type: 'chat',
|
261
|
+
},
|
262
|
+
{
|
263
|
+
abilities: {
|
264
|
+
reasoning: true,
|
265
|
+
},
|
266
|
+
contextWindowTokens: 8192,
|
267
|
+
description:
|
268
|
+
'DeepSeek-R1-Distill 模型是在开源模型的基础上通过微调训练得到的,训练过程中使用了由 DeepSeek-R1 生成的样本数据。',
|
269
|
+
displayName: 'DeepSeek R1 Distill Qwen 32B',
|
270
|
+
id: 'DeepSeek-R1-Distill-Qwen-32B',
|
271
|
+
pricing: {
|
272
|
+
currency: 'CNY',
|
273
|
+
input: 0,
|
274
|
+
output: 0,
|
275
|
+
},
|
276
|
+
type: 'chat',
|
277
|
+
},
|
163
278
|
];
|
164
279
|
|
165
280
|
export const allModels = [...sensenovaChatModels];
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import { ModelProvider } from '../types';
|
2
2
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
3
3
|
|
4
|
+
import { convertSenseNovaMessage } from '../utils/sensenovaHelpers';
|
5
|
+
|
4
6
|
import type { ChatModelCard } from '@/types/llm';
|
5
7
|
|
6
8
|
export interface SenseNovaModelCard {
|
@@ -11,7 +13,7 @@ export const LobeSenseNovaAI = LobeOpenAICompatibleFactory({
|
|
11
13
|
baseURL: 'https://api.sensenova.cn/compatible-mode/v1',
|
12
14
|
chatCompletion: {
|
13
15
|
handlePayload: (payload) => {
|
14
|
-
const { frequency_penalty, temperature, top_p, ...rest } = payload;
|
16
|
+
const { frequency_penalty, messages, model, temperature, top_p, ...rest } = payload;
|
15
17
|
|
16
18
|
return {
|
17
19
|
...rest,
|
@@ -19,6 +21,12 @@ export const LobeSenseNovaAI = LobeOpenAICompatibleFactory({
|
|
19
21
|
frequency_penalty !== undefined && frequency_penalty > 0 && frequency_penalty <= 2
|
20
22
|
? frequency_penalty
|
21
23
|
: undefined,
|
24
|
+
messages: messages.map((message) =>
|
25
|
+
message.role !== 'user' || !/^Sense(Nova-V6|Chat-Vision)/.test(model)
|
26
|
+
? message
|
27
|
+
: { ...message, content: convertSenseNovaMessage(message.content) }
|
28
|
+
) as any[],
|
29
|
+
model,
|
22
30
|
stream: true,
|
23
31
|
temperature:
|
24
32
|
temperature !== undefined && temperature > 0 && temperature <= 2
|
@@ -35,12 +43,17 @@ export const LobeSenseNovaAI = LobeOpenAICompatibleFactory({
|
|
35
43
|
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
36
44
|
|
37
45
|
const functionCallKeywords = [
|
38
|
-
'deepseek-v3',
|
39
46
|
'sensechat-5',
|
40
47
|
];
|
41
48
|
|
49
|
+
const visionKeywords = [
|
50
|
+
'vision',
|
51
|
+
'sensenova-v6',
|
52
|
+
];
|
53
|
+
|
42
54
|
const reasoningKeywords = [
|
43
|
-
'deepseek-r1'
|
55
|
+
'deepseek-r1',
|
56
|
+
'sensenova-v6',
|
44
57
|
];
|
45
58
|
|
46
59
|
client.baseURL = 'https://api.sensenova.cn/v1/llm';
|
@@ -66,7 +79,7 @@ export const LobeSenseNovaAI = LobeOpenAICompatibleFactory({
|
|
66
79
|
|| knownModel?.abilities?.reasoning
|
67
80
|
|| false,
|
68
81
|
vision:
|
69
|
-
model.id.toLowerCase().includes(
|
82
|
+
visionKeywords.some(keyword => model.id.toLowerCase().includes(keyword))
|
70
83
|
|| knownModel?.abilities?.vision
|
71
84
|
|| false,
|
72
85
|
};
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
import { convertSenseNovaMessage } from './sensenovaHelpers';
|
3
|
+
|
4
|
+
describe('convertSenseNovaMessage', () => {
|
5
|
+
it('should convert string content to text type array', () => {
|
6
|
+
const content = 'Hello world';
|
7
|
+
const result = convertSenseNovaMessage(content);
|
8
|
+
|
9
|
+
expect(result).toEqual([{ type: 'text', text: 'Hello world' }]);
|
10
|
+
});
|
11
|
+
|
12
|
+
it('should handle array content with text type', () => {
|
13
|
+
const content = [
|
14
|
+
{ type: 'text', text: 'Hello world' }
|
15
|
+
];
|
16
|
+
const result = convertSenseNovaMessage(content);
|
17
|
+
|
18
|
+
expect(result).toEqual([{ type: 'text', text: 'Hello world' }]);
|
19
|
+
});
|
20
|
+
|
21
|
+
it('should convert image_url with base64 format to image_base64', () => {
|
22
|
+
const content = [
|
23
|
+
{ type: 'image_url', image_url: { url: 'data:image/jpeg;base64,ABCDEF123456' } }
|
24
|
+
];
|
25
|
+
const result = convertSenseNovaMessage(content);
|
26
|
+
|
27
|
+
expect(result).toEqual([
|
28
|
+
{ type: 'image_base64', image_base64: 'ABCDEF123456' }
|
29
|
+
]);
|
30
|
+
});
|
31
|
+
|
32
|
+
it('should keep image_url format for non-base64 urls', () => {
|
33
|
+
const content = [
|
34
|
+
{ type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
|
35
|
+
];
|
36
|
+
const result = convertSenseNovaMessage(content);
|
37
|
+
|
38
|
+
expect(result).toEqual([
|
39
|
+
{ type: 'image_url', image_url: 'https://example.com/image.jpg' }
|
40
|
+
]);
|
41
|
+
});
|
42
|
+
|
43
|
+
it('should handle mixed content types', () => {
|
44
|
+
const content = [
|
45
|
+
{ type: 'text', text: 'Hello world' },
|
46
|
+
{ type: 'image_url', image_url: { url: 'data:image/jpeg;base64,ABCDEF123456' } },
|
47
|
+
{ type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
|
48
|
+
];
|
49
|
+
const result = convertSenseNovaMessage(content);
|
50
|
+
|
51
|
+
expect(result).toEqual([
|
52
|
+
{ type: 'text', text: 'Hello world' },
|
53
|
+
{ type: 'image_base64', image_base64: 'ABCDEF123456' },
|
54
|
+
{ type: 'image_url', image_url: 'https://example.com/image.jpg' }
|
55
|
+
]);
|
56
|
+
});
|
57
|
+
|
58
|
+
it('should filter out invalid items', () => {
|
59
|
+
const content = [
|
60
|
+
{ type: 'text', text: 'Hello world' },
|
61
|
+
{ type: 'unknown', value: 'should be filtered' },
|
62
|
+
{ type: 'image_url', image_url: { notUrl: 'missing url field' } }
|
63
|
+
];
|
64
|
+
const result = convertSenseNovaMessage(content);
|
65
|
+
|
66
|
+
expect(result).toEqual([
|
67
|
+
{ type: 'text', text: 'Hello world' }
|
68
|
+
]);
|
69
|
+
});
|
70
|
+
|
71
|
+
it('should handle the example input format correctly', () => {
|
72
|
+
const messages = [
|
73
|
+
{
|
74
|
+
content: [
|
75
|
+
{
|
76
|
+
content: "Hi",
|
77
|
+
role: "user"
|
78
|
+
},
|
79
|
+
{
|
80
|
+
image_url: {
|
81
|
+
detail: "auto",
|
82
|
+
url: "data:image/jpeg;base64,ABCDEF123456"
|
83
|
+
},
|
84
|
+
type: "image_url"
|
85
|
+
}
|
86
|
+
],
|
87
|
+
role: "user"
|
88
|
+
}
|
89
|
+
];
|
90
|
+
|
91
|
+
// This is simulating how you might use convertSenseNovaMessage with the example input
|
92
|
+
// Note: The actual function only converts the content part, not the entire messages array
|
93
|
+
const content = messages[0].content;
|
94
|
+
|
95
|
+
// This is how the function would be expected to handle a mixed array like this
|
96
|
+
// However, the actual test would need to be adjusted based on how your function
|
97
|
+
// is intended to handle this specific format with nested content objects
|
98
|
+
const result = convertSenseNovaMessage([
|
99
|
+
{ type: 'text', text: "Hi" },
|
100
|
+
{ type: 'image_url', image_url: { url: "data:image/jpeg;base64,ABCDEF123456" } }
|
101
|
+
]);
|
102
|
+
|
103
|
+
expect(result).toEqual([
|
104
|
+
{ type: 'text', text: "Hi" },
|
105
|
+
{ type: 'image_base64', image_base64: "ABCDEF123456" }
|
106
|
+
]);
|
107
|
+
});
|
108
|
+
});
|
@@ -0,0 +1,30 @@
|
|
1
|
+
export const convertSenseNovaMessage = (content: any) => {
|
2
|
+
|
3
|
+
// 如果为单条 string 类 content,则格式转换为 text 类
|
4
|
+
if (typeof content === 'string') {
|
5
|
+
return [{ text: content, type: 'text' }];
|
6
|
+
}
|
7
|
+
|
8
|
+
// 如果内容包含图片内容,则需要对 array 类 content,进行格式转换
|
9
|
+
return content
|
10
|
+
?.map((item: any) => {
|
11
|
+
// 如果为 content,则格式转换为 text 类
|
12
|
+
if (item.type === 'text') return item;
|
13
|
+
|
14
|
+
// 如果为 image_url,则格式转换为 image_url 类
|
15
|
+
if (item.type === 'image_url' && item.image_url?.url) {
|
16
|
+
const url = item.image_url.url;
|
17
|
+
|
18
|
+
// 如果 image_url 为 base64 格式,则返回 image_base64 类,否则返回 image_url 类
|
19
|
+
return url.startsWith('data:image/jpeg;base64')
|
20
|
+
? {
|
21
|
+
image_base64: url.split(',')[1],
|
22
|
+
type: 'image_base64',
|
23
|
+
}
|
24
|
+
: { image_url: url, type: 'image_url' };
|
25
|
+
}
|
26
|
+
|
27
|
+
return null;
|
28
|
+
})
|
29
|
+
.filter(Boolean);
|
30
|
+
};
|
@@ -10,6 +10,7 @@ const n = setNamespace('w');
|
|
10
10
|
|
11
11
|
export interface GlobalWorkspacePaneAction {
|
12
12
|
switchBackToChat: (sessionId?: string) => void;
|
13
|
+
toggleAgentSystemRoleExpand: (agentId: string, expanded?: boolean) => void;
|
13
14
|
toggleChatSideBar: (visible?: boolean) => void;
|
14
15
|
toggleExpandSessionGroup: (id: string, expand: boolean) => void;
|
15
16
|
toggleMobilePortal: (visible?: boolean) => void;
|
@@ -28,6 +29,21 @@ export const globalWorkspaceSlice: StateCreator<
|
|
28
29
|
get().router?.push(SESSION_CHAT_URL(sessionId || INBOX_SESSION_ID, get().isMobile));
|
29
30
|
},
|
30
31
|
|
32
|
+
toggleAgentSystemRoleExpand: (agentId, expanded) => {
|
33
|
+
const { status } = get();
|
34
|
+
const systemRoleExpandedMap = status.systemRoleExpandedMap || {};
|
35
|
+
const nextExpanded = typeof expanded === 'boolean' ? expanded : !systemRoleExpandedMap[agentId];
|
36
|
+
|
37
|
+
get().updateSystemStatus(
|
38
|
+
{
|
39
|
+
systemRoleExpandedMap: {
|
40
|
+
...systemRoleExpandedMap,
|
41
|
+
[agentId]: nextExpanded,
|
42
|
+
},
|
43
|
+
},
|
44
|
+
n('toggleAgentSystemRoleExpand', { agentId, expanded: nextExpanded }),
|
45
|
+
);
|
46
|
+
},
|
31
47
|
toggleChatSideBar: (newValue) => {
|
32
48
|
const showChatSideBar =
|
33
49
|
typeof newValue === 'boolean' ? newValue : !get().status.showChatSideBar;
|
@@ -66,6 +66,7 @@ export interface SystemStatus {
|
|
66
66
|
showHotkeyHelper?: boolean;
|
67
67
|
showSessionPanel?: boolean;
|
68
68
|
showSystemRole?: boolean;
|
69
|
+
systemRoleExpandedMap: Record<string, boolean>;
|
69
70
|
/**
|
70
71
|
* theme mode
|
71
72
|
*/
|
@@ -111,6 +112,7 @@ export const INITIAL_STATUS = {
|
|
111
112
|
showHotkeyHelper: false,
|
112
113
|
showSessionPanel: true,
|
113
114
|
showSystemRole: false,
|
115
|
+
systemRoleExpandedMap: {},
|
114
116
|
themeMode: 'auto',
|
115
117
|
threadInputHeight: 200,
|
116
118
|
zenMode: false,
|
@@ -50,8 +50,16 @@ const isPgliteInited = (s: GlobalState): boolean =>
|
|
50
50
|
// 这个变量控制 clientdb 是否完成初始化,正常来说,只有 pgliteDB 模式下,才会存在变化,其他时候都是 true
|
51
51
|
const isDBInited = (s: GlobalState): boolean => (isUsePgliteDB ? isPgliteInited(s) : true);
|
52
52
|
|
53
|
+
const getAgentSystemRoleExpanded =
|
54
|
+
(agentId: string) =>
|
55
|
+
(s: GlobalState): boolean => {
|
56
|
+
const map = s.status.systemRoleExpandedMap || {};
|
57
|
+
return map[agentId] !== false; // 角色设定默认为展开状态
|
58
|
+
};
|
59
|
+
|
53
60
|
export const systemStatusSelectors = {
|
54
61
|
filePanelWidth,
|
62
|
+
getAgentSystemRoleExpanded,
|
55
63
|
hidePWAInstaller,
|
56
64
|
inZenMode,
|
57
65
|
inputHeight,
|