@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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.0",
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",
@@ -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 ? (
@@ -1,6 +1,11 @@
1
1
  import { createStyles } from 'antd-style';
2
2
 
3
3
  export const useStyles = createStyles(({ css, token }) => ({
4
+ animatedContainer: css`
5
+ transition:
6
+ height 0.3s ease,
7
+ opacity 0.3s ease;
8
+ `,
4
9
  prompt: css`
5
10
  overflow: hidden auto;
6
11
 
@@ -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('vision')
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,