@lobehub/chat 1.141.6 → 1.141.8

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.
Files changed (119) hide show
  1. package/.github/PULL_REQUEST_TEMPLATE.md +26 -0
  2. package/.github/workflows/e2e.yml +6 -6
  3. package/CHANGELOG.md +50 -0
  4. package/changelog/v1.json +18 -0
  5. package/docs/usage/features/{group-chat.mdx → agent-team.mdx} +14 -14
  6. package/docs/usage/features/agent-team.zh-CN.mdx +52 -0
  7. package/e2e/README.md +143 -0
  8. package/e2e/cucumber.config.js +20 -0
  9. package/e2e/package.json +24 -0
  10. package/e2e/src/features/discover/smoke.feature +11 -0
  11. package/e2e/src/features/routes/core-routes.feature +43 -0
  12. package/e2e/src/steps/common/navigation.steps.ts +36 -0
  13. package/e2e/src/steps/discover/smoke.steps.ts +34 -0
  14. package/e2e/src/steps/hooks.ts +69 -0
  15. package/e2e/src/steps/routes/routes.steps.ts +41 -0
  16. package/e2e/src/support/webServer.ts +96 -0
  17. package/e2e/src/support/world.ts +76 -0
  18. package/e2e/tsconfig.json +19 -0
  19. package/locales/ar/chat.json +17 -17
  20. package/locales/ar/setting.json +15 -19
  21. package/locales/ar/welcome.json +1 -1
  22. package/locales/bg-BG/chat.json +17 -17
  23. package/locales/bg-BG/setting.json +15 -19
  24. package/locales/de-DE/chat.json +17 -17
  25. package/locales/de-DE/setting.json +15 -19
  26. package/locales/de-DE/welcome.json +1 -1
  27. package/locales/en-US/chat.json +17 -17
  28. package/locales/en-US/setting.json +15 -19
  29. package/locales/en-US/welcome.json +1 -1
  30. package/locales/es-ES/chat.json +17 -17
  31. package/locales/es-ES/setting.json +15 -19
  32. package/locales/es-ES/welcome.json +1 -1
  33. package/locales/fa-IR/chat.json +17 -17
  34. package/locales/fa-IR/setting.json +15 -19
  35. package/locales/fa-IR/welcome.json +1 -1
  36. package/locales/fr-FR/chat.json +16 -16
  37. package/locales/fr-FR/setting.json +15 -19
  38. package/locales/fr-FR/welcome.json +1 -1
  39. package/locales/it-IT/chat.json +17 -17
  40. package/locales/it-IT/setting.json +15 -19
  41. package/locales/it-IT/welcome.json +1 -1
  42. package/locales/ja-JP/chat.json +17 -17
  43. package/locales/ja-JP/setting.json +15 -19
  44. package/locales/ja-JP/welcome.json +1 -1
  45. package/locales/ko-KR/chat.json +17 -17
  46. package/locales/ko-KR/setting.json +15 -19
  47. package/locales/ko-KR/welcome.json +1 -1
  48. package/locales/nl-NL/chat.json +17 -17
  49. package/locales/nl-NL/setting.json +15 -19
  50. package/locales/nl-NL/welcome.json +1 -1
  51. package/locales/pl-PL/chat.json +17 -17
  52. package/locales/pl-PL/setting.json +15 -19
  53. package/locales/pt-BR/chat.json +17 -17
  54. package/locales/pt-BR/setting.json +15 -19
  55. package/locales/pt-BR/welcome.json +1 -1
  56. package/locales/ru-RU/chat.json +17 -17
  57. package/locales/ru-RU/setting.json +15 -19
  58. package/locales/ru-RU/welcome.json +1 -1
  59. package/locales/tr-TR/chat.json +17 -17
  60. package/locales/tr-TR/setting.json +15 -19
  61. package/locales/vi-VN/chat.json +15 -15
  62. package/locales/vi-VN/setting.json +15 -19
  63. package/locales/zh-CN/chat.json +17 -17
  64. package/locales/zh-CN/setting.json +15 -19
  65. package/locales/zh-CN/welcome.json +1 -1
  66. package/locales/zh-TW/chat.json +17 -17
  67. package/locales/zh-TW/setting.json +15 -19
  68. package/locales/zh-TW/welcome.json +1 -1
  69. package/package.json +6 -3
  70. package/packages/const/src/layoutTokens.ts +1 -1
  71. package/packages/const/src/settings/systemAgent.ts +0 -1
  72. package/packages/database/src/models/__tests__/session.test.ts +108 -0
  73. package/packages/database/src/models/session.ts +41 -1
  74. package/packages/model-bank/src/aiModels/groq.ts +0 -17
  75. package/packages/model-bank/src/aiModels/novita.ts +2 -60
  76. package/packages/model-bank/src/aiModels/siliconcloud.ts +116 -17
  77. package/packages/types/src/user/settings/systemAgent.ts +0 -1
  78. package/pnpm-workspace.yaml +1 -0
  79. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/OrchestratorThinking.tsx +2 -3
  80. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +2 -2
  81. package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/GroupConfig/GroupMember.tsx +34 -2
  82. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +1 -1
  83. package/src/app/[variants]/(main)/chat/(workspace)/features/{GroupChatSettings → AgentTeamSettings}/index.tsx +4 -5
  84. package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +2 -2
  85. package/src/app/[variants]/(main)/chat/@session/_layout/Desktop/SessionHeader.tsx +2 -0
  86. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/CollapseGroup/Actions.tsx +18 -1
  87. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +1 -0
  88. package/src/app/[variants]/(main)/discover/DiscoverRouter.tsx +12 -10
  89. package/src/app/[variants]/(main)/discover/[[...path]]/page.tsx +7 -6
  90. package/src/app/[variants]/(main)/discover/features/Search.tsx +1 -0
  91. package/src/components/ChatGroupWizard/ChatGroupWizard.tsx +33 -5
  92. package/src/components/Loading/index.ts +1 -0
  93. package/src/components/MemberSelectionModal/MemberSelectionModal.tsx +170 -26
  94. package/src/features/AgentSetting/AgentModal/index.tsx +262 -35
  95. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +261 -50
  96. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +7 -2
  97. package/src/features/Conversation/Messages/User/Actions.tsx +8 -2
  98. package/src/features/GroupChatSettings/{ChatGroupSettings.tsx → AgentTeamChatSettings.tsx} +6 -5
  99. package/src/features/GroupChatSettings/{GroupMembers.tsx → AgentTeamMembersSettings.tsx} +64 -19
  100. package/src/features/GroupChatSettings/{ChatGroupMeta.tsx → AgentTeamMetaSettings.tsx} +2 -2
  101. package/src/features/GroupChatSettings/AgentTeamSettings.tsx +54 -0
  102. package/src/features/GroupChatSettings/index.ts +4 -5
  103. package/src/features/ModelParamsControl/FrequencyPenalty.tsx +8 -3
  104. package/src/features/ModelParamsControl/PresencePenalty.tsx +8 -3
  105. package/src/features/ModelParamsControl/Temperature.tsx +8 -5
  106. package/src/features/ModelParamsControl/TopP.tsx +8 -3
  107. package/src/locales/default/chat.ts +17 -17
  108. package/src/locales/default/setting.ts +15 -19
  109. package/src/locales/default/welcome.ts +1 -1
  110. package/src/services/chat/index.ts +6 -0
  111. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +2 -1
  112. package/src/store/chatGroup/action.ts +36 -1
  113. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -4
  114. package/src/store/user/slices/settings/selectors/systemAgent.ts +0 -2
  115. package/docs/usage/features/group-chat.zh-CN.mdx +0 -52
  116. package/e2e/routes.spec.ts +0 -73
  117. package/playwright.config.ts +0 -35
  118. package/src/features/GroupChatSettings/GroupSettings.tsx +0 -30
  119. package/src/features/GroupChatSettings/GroupSettingsContent.tsx +0 -24
@@ -1,7 +1,10 @@
1
1
  import { Form, type FormItemProps, Tag } from '@lobehub/ui';
2
+ import { Form as AntdForm, Checkbox } from 'antd';
3
+ import { createStyles } from 'antd-style';
2
4
  import isEqual from 'fast-deep-equal';
3
5
  import { debounce } from 'lodash-es';
4
- import { memo } from 'react';
6
+ import { memo, useCallback, useEffect, useRef } from 'react';
7
+ import type { ComponentType } from 'react';
5
8
  import { useTranslation } from 'react-i18next';
6
9
  import { Flexbox } from 'react-layout-kit';
7
10
 
@@ -20,75 +23,283 @@ interface ControlsProps {
20
23
  setUpdating: (updating: boolean) => void;
21
24
  updating: boolean;
22
25
  }
26
+
27
+ type ParamKey = 'temperature' | 'top_p' | 'presence_penalty' | 'frequency_penalty';
28
+
29
+ type ParamLabelKey =
30
+ | 'settingModel.temperature.title'
31
+ | 'settingModel.topP.title'
32
+ | 'settingModel.presencePenalty.title'
33
+ | 'settingModel.frequencyPenalty.title';
34
+
35
+ type ParamDescKey =
36
+ | 'settingModel.temperature.desc'
37
+ | 'settingModel.topP.desc'
38
+ | 'settingModel.presencePenalty.desc'
39
+ | 'settingModel.frequencyPenalty.desc';
40
+
41
+ const useStyles = createStyles(({ css, token }) => ({
42
+ checkbox: css`
43
+ .ant-checkbox-inner {
44
+ border-radius: 4px;
45
+ }
46
+
47
+ &:hover .ant-checkbox-inner {
48
+ border-color: ${token.colorPrimary};
49
+ }
50
+ `,
51
+ label: css`
52
+ user-select: none;
53
+ `,
54
+ sliderWrapper: css`
55
+ display: flex;
56
+ gap: 16px;
57
+ align-items: center;
58
+ width: 100%;
59
+ `,
60
+ }));
61
+
62
+ // Wrapper component to handle checkbox + slider
63
+ interface ParamControlWrapperProps {
64
+ Component: ComponentType<any>;
65
+ checked: boolean;
66
+ disabled: boolean;
67
+ onChange?: (value: number) => void;
68
+ onToggle: (checked: boolean) => void;
69
+ styles: any;
70
+ value?: number;
71
+ }
72
+
73
+ const ParamControlWrapper = memo<ParamControlWrapperProps>(
74
+ ({ Component, value, onChange, disabled, checked, onToggle, styles }) => {
75
+ return (
76
+ <div className={styles.sliderWrapper}>
77
+ <Checkbox
78
+ checked={checked}
79
+ className={styles.checkbox}
80
+ onChange={(e) => {
81
+ e.stopPropagation();
82
+ onToggle(e.target.checked);
83
+ }}
84
+ />
85
+ <div style={{ flex: 1 }}>
86
+ <Component disabled={disabled} onChange={onChange} value={value} />
87
+ </div>
88
+ </div>
89
+ );
90
+ },
91
+ );
92
+
93
+ const PARAM_NAME_MAP: Record<ParamKey, (string | number)[]> = {
94
+ frequency_penalty: ['params', 'frequency_penalty'],
95
+ presence_penalty: ['params', 'presence_penalty'],
96
+ temperature: ['params', 'temperature'],
97
+ top_p: ['params', 'top_p'],
98
+ };
99
+
100
+ const PARAM_DEFAULTS: Record<ParamKey, number> = {
101
+ frequency_penalty: 0,
102
+ presence_penalty: 0,
103
+ temperature: 0.7,
104
+ top_p: 1,
105
+ };
106
+
107
+ const PARAM_CONFIG = {
108
+ frequency_penalty: {
109
+ Component: FrequencyPenalty,
110
+ descKey: 'settingModel.frequencyPenalty.desc',
111
+ labelKey: 'settingModel.frequencyPenalty.title',
112
+ tag: 'frequency_penalty',
113
+ },
114
+ presence_penalty: {
115
+ Component: PresencePenalty,
116
+ descKey: 'settingModel.presencePenalty.desc',
117
+ labelKey: 'settingModel.presencePenalty.title',
118
+ tag: 'presence_penalty',
119
+ },
120
+ temperature: {
121
+ Component: Temperature,
122
+ descKey: 'settingModel.temperature.desc',
123
+ labelKey: 'settingModel.temperature.title',
124
+ tag: 'temperature',
125
+ },
126
+ top_p: {
127
+ Component: TopP,
128
+ descKey: 'settingModel.topP.desc',
129
+ labelKey: 'settingModel.topP.title',
130
+ tag: 'top_p',
131
+ },
132
+ } satisfies Record<
133
+ ParamKey,
134
+ {
135
+ Component: ComponentType<any>;
136
+ descKey: ParamDescKey;
137
+ labelKey: ParamLabelKey;
138
+ tag: string;
139
+ }
140
+ >;
141
+
23
142
  const Controls = memo<ControlsProps>(({ setUpdating }) => {
24
143
  const { t } = useTranslation('setting');
25
144
  const mobile = useServerConfigStore((s) => s.isMobile);
26
145
  const updateAgentConfig = useAgentStore((s) => s.updateAgentConfig);
146
+ const { styles } = useStyles();
27
147
 
28
148
  const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
149
+ const [form] = Form.useForm();
29
150
 
30
- const items: FormItemProps[] = [
31
- {
32
- children: <Temperature />,
33
- label: (
34
- <Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
35
- {t('settingModel.temperature.title')}
36
- <InfoTooltip title={t('settingModel.temperature.desc')} />
37
- </Flexbox>
38
- ),
39
- name: ['params', 'temperature'],
40
- tag: 'temperature',
41
- },
42
- {
43
- children: <TopP />,
44
- label: (
45
- <Flexbox gap={8} horizontal>
46
- {t('settingModel.topP.title')}
47
- <InfoTooltip title={t('settingModel.topP.desc')} />
48
- </Flexbox>
49
- ),
50
- name: ['params', 'top_p'],
51
- tag: 'top_p',
151
+ const { frequency_penalty, presence_penalty, temperature, top_p } = config.params ?? {};
152
+
153
+ const lastValuesRef = useRef<Record<ParamKey, number | undefined>>({
154
+ frequency_penalty,
155
+ presence_penalty,
156
+ temperature,
157
+ top_p,
158
+ });
159
+
160
+ useEffect(() => {
161
+ form.setFieldsValue(config);
162
+
163
+ if (typeof temperature === 'number') lastValuesRef.current.temperature = temperature;
164
+ if (typeof top_p === 'number') lastValuesRef.current.top_p = top_p;
165
+ if (typeof presence_penalty === 'number') {
166
+ lastValuesRef.current.presence_penalty = presence_penalty;
167
+ }
168
+ if (typeof frequency_penalty === 'number') {
169
+ lastValuesRef.current.frequency_penalty = frequency_penalty;
170
+ }
171
+ }, [config, form, frequency_penalty, presence_penalty, temperature, top_p]);
172
+
173
+ const temperatureValue = AntdForm.useWatch(PARAM_NAME_MAP.temperature, form);
174
+ const topPValue = AntdForm.useWatch(PARAM_NAME_MAP.top_p, form);
175
+ const presencePenaltyValue = AntdForm.useWatch(PARAM_NAME_MAP.presence_penalty, form);
176
+ const frequencyPenaltyValue = AntdForm.useWatch(PARAM_NAME_MAP.frequency_penalty, form);
177
+
178
+ useEffect(() => {
179
+ if (typeof temperatureValue === 'number') lastValuesRef.current.temperature = temperatureValue;
180
+ }, [temperatureValue]);
181
+
182
+ useEffect(() => {
183
+ if (typeof topPValue === 'number') lastValuesRef.current.top_p = topPValue;
184
+ }, [topPValue]);
185
+
186
+ useEffect(() => {
187
+ if (typeof presencePenaltyValue === 'number') {
188
+ lastValuesRef.current.presence_penalty = presencePenaltyValue;
189
+ }
190
+ }, [presencePenaltyValue]);
191
+
192
+ useEffect(() => {
193
+ if (typeof frequencyPenaltyValue === 'number') {
194
+ lastValuesRef.current.frequency_penalty = frequencyPenaltyValue;
195
+ }
196
+ }, [frequencyPenaltyValue]);
197
+
198
+ const enabledMap: Record<ParamKey, boolean> = {
199
+ frequency_penalty: typeof frequencyPenaltyValue === 'number',
200
+ presence_penalty: typeof presencePenaltyValue === 'number',
201
+ temperature: typeof temperatureValue === 'number',
202
+ top_p: typeof topPValue === 'number',
203
+ };
204
+
205
+ const handleToggle = useCallback(
206
+ async (key: ParamKey, enabled: boolean) => {
207
+ const namePath = PARAM_NAME_MAP[key];
208
+ let newValue: number | undefined;
209
+
210
+ if (!enabled) {
211
+ const currentValue = form.getFieldValue(namePath);
212
+ if (typeof currentValue === 'number') {
213
+ lastValuesRef.current[key] = currentValue;
214
+ }
215
+ newValue = undefined;
216
+ form.setFieldValue(namePath, undefined);
217
+ } else {
218
+ const fallback = lastValuesRef.current[key];
219
+ const nextValue = typeof fallback === 'number' ? fallback : PARAM_DEFAULTS[key];
220
+ lastValuesRef.current[key] = nextValue;
221
+ newValue = nextValue;
222
+ form.setFieldValue(namePath, nextValue);
223
+ }
224
+
225
+ // 立即保存变更 - 手动构造配置对象确保使用最新值
226
+ setUpdating(true);
227
+ const currentValues = form.getFieldsValue();
228
+ const prevParams = (currentValues.params ?? {}) as Record<ParamKey, number | undefined>;
229
+ const currentParams: Record<ParamKey, number | undefined> = { ...prevParams };
230
+
231
+ if (newValue === undefined) {
232
+ // 显式删除该属性,而不是设置为 undefined
233
+ // 这样可以确保 Form 表单状态同步
234
+ delete currentParams[key];
235
+ // 使用 null 作为禁用标记(数据库会保留 null,前端据此判断复选框状态)
236
+ currentParams[key] = null as any;
237
+ } else {
238
+ currentParams[key] = newValue;
239
+ }
240
+
241
+ const updatedConfig = {
242
+ ...currentValues,
243
+ params: currentParams,
244
+ };
245
+
246
+ await updateAgentConfig(updatedConfig);
247
+ setUpdating(false);
52
248
  },
53
- {
54
- children: <PresencePenalty />,
55
- label: (
56
- <Flexbox gap={8} horizontal>
57
- {t('settingModel.presencePenalty.title')}
58
- <InfoTooltip title={t('settingModel.presencePenalty.desc')} />
59
- </Flexbox>
249
+ [form, setUpdating, updateAgentConfig],
250
+ );
251
+
252
+ // 使用 useMemo 确保防抖函数只创建一次
253
+ const handleValuesChange = useCallback(
254
+ debounce(async (values) => {
255
+ setUpdating(true);
256
+ await updateAgentConfig(values);
257
+ setUpdating(false);
258
+ }, 500),
259
+ [updateAgentConfig, setUpdating],
260
+ );
261
+
262
+ const baseItems: FormItemProps[] = (Object.keys(PARAM_CONFIG) as ParamKey[]).map((key) => {
263
+ const meta = PARAM_CONFIG[key];
264
+ const Component = meta.Component;
265
+ const enabled = enabledMap[key];
266
+
267
+ return {
268
+ children: (
269
+ <ParamControlWrapper
270
+ Component={Component}
271
+ checked={enabled}
272
+ disabled={!enabled}
273
+ onToggle={(checked) => handleToggle(key, checked)}
274
+ styles={styles}
275
+ />
60
276
  ),
61
- name: ['params', 'presence_penalty'],
62
- tag: 'presence_penalty',
63
- },
64
- {
65
- children: <FrequencyPenalty />,
66
277
  label: (
67
- <Flexbox gap={8} horizontal>
68
- {t('settingModel.frequencyPenalty.title')}
69
- <InfoTooltip title={t('settingModel.frequencyPenalty.desc')} />
278
+ <Flexbox align={'center'} className={styles.label} gap={8} horizontal>
279
+ {t(meta.labelKey)}
280
+ <InfoTooltip title={t(meta.descKey)} />
70
281
  </Flexbox>
71
282
  ),
72
- name: ['params', 'frequency_penalty'],
73
- tag: 'frequency_penalty',
74
- },
75
- ];
283
+ name: PARAM_NAME_MAP[key],
284
+ tag: meta.tag,
285
+ } satisfies FormItemProps;
286
+ });
76
287
 
77
288
  return (
78
289
  <Form
290
+ form={form}
79
291
  initialValues={config}
80
- itemMinWidth={200}
292
+ itemMinWidth={220}
81
293
  items={
82
294
  mobile
83
- ? items
84
- : items.map(({ tag, ...item }) => ({ ...item, desc: <Tag size={'small'}>{tag}</Tag> }))
295
+ ? baseItems
296
+ : baseItems.map(({ tag, ...item }) => ({
297
+ ...item,
298
+ desc: <Tag size={'small'}>{tag}</Tag>,
299
+ }))
85
300
  }
86
301
  itemsType={'flat'}
87
- onValuesChange={debounce(async (values) => {
88
- setUpdating(true);
89
- await updateAgentConfig(values);
90
- setUpdating(false);
91
- }, 500)}
302
+ onValuesChange={handleValuesChange}
92
303
  styles={{
93
304
  group: {
94
305
  background: 'transparent',
@@ -8,6 +8,8 @@ import ShareMessageModal from '@/features/Conversation/components/ShareMessageMo
8
8
  import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
9
9
  import { useChatStore } from '@/store/chat';
10
10
  import { threadSelectors } from '@/store/chat/selectors';
11
+ import { useSessionStore } from '@/store/session';
12
+ import { sessionSelectors } from '@/store/session/selectors';
11
13
  import { ChatMessage } from '@/types/message';
12
14
 
13
15
  import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
@@ -25,6 +27,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
25
27
  !!s.activeThreadId,
26
28
  threadSelectors.hasThreadBySourceMsgId(id)(s),
27
29
  ]);
30
+ const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
28
31
  const [showShareModal, setShareModal] = useState(false);
29
32
 
30
33
  const {
@@ -49,8 +52,10 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
49
52
  const items = useMemo(() => {
50
53
  if (hasTools) return [delAndRegenerate, copy];
51
54
 
52
- return [edit, copy, inThread ? null : branching].filter(Boolean) as ActionIconGroupItemType[];
53
- }, [inThread, hasTools]);
55
+ return [edit, copy, inThread || isGroupSession ? null : branching].filter(
56
+ Boolean,
57
+ ) as ActionIconGroupItemType[];
58
+ }, [inThread, hasTools, isGroupSession]);
54
59
 
55
60
  const { t } = useTranslation('common');
56
61
  const searchParams = useSearchParams();
@@ -9,6 +9,8 @@ import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { useChatStore } from '@/store/chat';
11
11
  import { threadSelectors } from '@/store/chat/selectors';
12
+ import { useSessionStore } from '@/store/session';
13
+ import { sessionSelectors } from '@/store/session/selectors';
12
14
 
13
15
  import { VirtuosoContext } from '../../components/VirtualizedList/VirtuosoContext';
14
16
  import { InPortalThreadContext } from '../../context/InPortalThreadContext';
@@ -54,6 +56,8 @@ export const UserActionsBar = memo<UserActionsProps>(({ id, data, index }) => {
54
56
  s.delAndResendThreadMessage,
55
57
  ]);
56
58
 
59
+ const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
60
+
57
61
  const { regenerate, edit, copy, divider, del, branching, tts, translate } = useChatListActionsBar(
58
62
  { hasThread },
59
63
  );
@@ -63,8 +67,10 @@ export const UserActionsBar = memo<UserActionsProps>(({ id, data, index }) => {
63
67
 
64
68
  const items = useMemo(
65
69
  () =>
66
- [regenerate, edit, inThread ? null : branching].filter(Boolean) as ActionIconGroupItemType[],
67
- [inThread],
70
+ [regenerate, edit, inThread || isGroupSession ? null : branching].filter(
71
+ Boolean,
72
+ ) as ActionIconGroupItemType[],
73
+ [inThread, isGroupSession],
68
74
  );
69
75
 
70
76
  const { message } = App.useApp();
@@ -22,9 +22,9 @@ import { selectors, useStore } from './store';
22
22
  const { TextArea } = Input;
23
23
 
24
24
  /**
25
- * Chat Settings for Group Chat
25
+ * Chat Settings for Agent Team (Group Chat)
26
26
  */
27
- const ChatGroupSettings = memo(() => {
27
+ const AgentTeamChatSettings = memo(() => {
28
28
  const { t } = useTranslation(['setting', 'common']);
29
29
  const [form] = Form.useForm();
30
30
  const updateConfig = useStore((s) => s.updateGroupConfig);
@@ -171,8 +171,9 @@ const ChatGroupSettings = memo(() => {
171
171
  itemsType={'group'}
172
172
  onFinish={async ({ _modelConfig, ...rest }) => {
173
173
  await updateConfig({
174
- orchestratorModel: _modelConfig?.model,
175
- orchestratorProvider: _modelConfig?.provider,
174
+ // Preserve existing values when _modelConfig is undefined (enableSupervisor is false)
175
+ orchestratorModel: _modelConfig?.model ?? config?.orchestratorModel,
176
+ orchestratorProvider: _modelConfig?.provider ?? config?.orchestratorProvider,
176
177
  ...rest,
177
178
  });
178
179
 
@@ -184,4 +185,4 @@ const ChatGroupSettings = memo(() => {
184
185
  );
185
186
  });
186
187
 
187
- export default ChatGroupSettings;
188
+ export default AgentTeamChatSettings;
@@ -1,11 +1,13 @@
1
1
  'use client';
2
2
 
3
- import { Grid, Tag, Text } from '@lobehub/ui';
3
+ import { ActionIcon, Grid, Tag, Text } from '@lobehub/ui';
4
4
  import { createStyles } from 'antd-style';
5
+ import { Plus } from 'lucide-react';
5
6
  import { memo, useMemo, useState } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
7
8
  import { Flexbox } from 'react-layout-kit';
8
9
 
10
+ import { DEFAULT_AVATAR } from '@/const/meta';
9
11
  import { useChatGroupStore } from '@/store/chatGroup';
10
12
  import { chatGroupSelectors } from '@/store/chatGroup/selectors';
11
13
  import { useSessionStore } from '@/store/session';
@@ -23,10 +25,11 @@ const useStyles = createStyles(({ css }) => ({
23
25
 
24
26
  const HOST_MEMBER_ID = 'supervisor';
25
27
 
26
- const GroupMembers = memo(() => {
28
+ const AgentTeamMembersSettings = memo(() => {
27
29
  const { t } = useTranslation('setting');
28
30
  const { styles } = useStyles();
29
31
  const [loadingAgentId, setLoadingAgentId] = useState<string | null>(null);
32
+ const [isCreatingMember, setIsCreatingMember] = useState(false);
30
33
 
31
34
  const activeGroupId = useSessionStore((s) => s.activeId);
32
35
  const currentSession = useSessionStore(sessionSelectors.currentSession) as LobeGroupSession;
@@ -36,6 +39,7 @@ const GroupMembers = memo(() => {
36
39
  const removeAgentFromGroup = useChatGroupStore((s) => s.removeAgentFromGroup);
37
40
  const updateGroupConfig = useChatGroupStore((s) => s.updateGroupConfig);
38
41
  const refreshSessions = useSessionStore((s) => s.refreshSessions);
42
+ const createSession = useSessionStore((s) => s.createSession);
39
43
 
40
44
  // Get all agent sessions
41
45
  const agentSessions = useSessionStore((s) => {
@@ -79,8 +83,6 @@ const GroupMembers = memo(() => {
79
83
  return;
80
84
  }
81
85
 
82
- console.log(`Attempting to ${action} agent:`, { action, activeGroupId, agentId });
83
-
84
86
  // Check if this is the host member
85
87
  const isHostMember = agentId === HOST_MEMBER_ID;
86
88
 
@@ -92,19 +94,15 @@ const GroupMembers = memo(() => {
92
94
  if (isHostMember) {
93
95
  // Host toggle updates supervisor flag instead of modifying members
94
96
  await updateGroupConfig({ enableSupervisor: true });
95
- console.log('Enabled supervisor');
96
97
  } else {
97
98
  await addAgentsToGroup(activeGroupId, [agentId]);
98
- console.log(`Successfully added agent ${agentId} to group ${activeGroupId}`);
99
99
  }
100
100
  } else {
101
101
  if (isHostMember) {
102
102
  // Host toggle updates supervisor flag instead of modifying members
103
103
  await updateGroupConfig({ enableSupervisor: false });
104
- console.log('Disabled supervisor');
105
104
  } else {
106
105
  await removeAgentFromGroup(activeGroupId, agentId);
107
- console.log(`Successfully removed agent ${agentId} from group ${activeGroupId}`);
108
106
  }
109
107
  }
110
108
 
@@ -124,6 +122,49 @@ const GroupMembers = memo(() => {
124
122
  handleAgentAction(HOST_MEMBER_ID, checked ? 'add' : 'remove');
125
123
  };
126
124
 
125
+ const handleCreateMember = async () => {
126
+ if (!activeGroupId || isCreatingMember) return;
127
+
128
+ setIsCreatingMember(true);
129
+
130
+ try {
131
+ // Create a virtual assistant
132
+ const sessionId = await createSession(
133
+ {
134
+ config: {
135
+ virtual: true,
136
+ },
137
+ meta: {
138
+ avatar: DEFAULT_AVATAR,
139
+ description: '',
140
+ title: t('settingGroupMembers.defaultAgent'),
141
+ },
142
+ },
143
+ false, // Don't switch to the new session
144
+ );
145
+
146
+ // Refresh sessions to get the latest data
147
+ await refreshSessions();
148
+
149
+ // Get the agent ID from the created session
150
+ const session = sessionSelectors.getSessionById(sessionId)(useSessionStore.getState());
151
+ if (session && session.type === LobeSessionType.Agent) {
152
+ const agentSession = session as LobeAgentSession;
153
+ const agentId = agentSession.config?.id;
154
+
155
+ if (agentId) {
156
+ // Add the agent to the current group
157
+ await addAgentsToGroup(activeGroupId, [agentId]);
158
+ await refreshSessions();
159
+ }
160
+ }
161
+ } catch (error) {
162
+ console.error('Failed to create virtual member:', error);
163
+ } finally {
164
+ setIsCreatingMember(false);
165
+ }
166
+ };
167
+
127
168
  const groupMemberCount = agentsInGroup.length + (isSupervisorEnabled ? 1 : 0);
128
169
  const availableAgentCount = agentsNotInGroup.length + (isSupervisorEnabled ? 0 : 1);
129
170
 
@@ -131,19 +172,23 @@ const GroupMembers = memo(() => {
131
172
  <Flexbox className={styles.container} gap={40}>
132
173
  {/* Agents in Group Section */}
133
174
  <Flexbox gap={24}>
134
- <Flexbox align={'center'} gap={8} horizontal>
135
- <Text strong style={{ fontSize: 18 }}>
136
- {t('settingGroupMembers.groupMembers')}
137
- </Text>
138
- <Tag>{groupMemberCount}</Tag>
175
+ <Flexbox align={'center'} gap={8} horizontal justify="space-between">
176
+ <Flexbox align={'center'} gap={8} horizontal>
177
+ <Text strong style={{ fontSize: 18 }}>
178
+ {t('settingGroupMembers.groupMembers')}
179
+ </Text>
180
+ <Tag>{groupMemberCount}</Tag>
181
+ </Flexbox>
182
+ <ActionIcon
183
+ icon={Plus}
184
+ loading={isCreatingMember}
185
+ onClick={handleCreateMember}
186
+ title={t('settingGroupMembers.createMember')}
187
+ />
139
188
  </Flexbox>
140
189
  <Grid gap={16} rows={3}>
141
190
  {isSupervisorEnabled && (
142
- <HostMemberCard
143
- checked
144
- loading={hostOperationLoading}
145
- onToggle={handleHostToggle}
146
- />
191
+ <HostMemberCard checked loading={hostOperationLoading} onToggle={handleHostToggle} />
147
192
  )}
148
193
  {agentsInGroup.map((agent) => (
149
194
  <AgentCard
@@ -198,4 +243,4 @@ const GroupMembers = memo(() => {
198
243
  );
199
244
  });
200
245
 
201
- export default GroupMembers;
246
+ export default AgentTeamMembersSettings;
@@ -14,7 +14,7 @@ import { selectors, useStore } from './store';
14
14
 
15
15
  const { TextArea } = Input;
16
16
 
17
- const ChatGroupMeta = memo(() => {
17
+ const AgentTeamMetaSettings = memo(() => {
18
18
  const { t } = useTranslation(['setting', 'common']);
19
19
  const [form] = Form.useForm();
20
20
 
@@ -100,4 +100,4 @@ const ChatGroupMeta = memo(() => {
100
100
  );
101
101
  });
102
102
 
103
- export default ChatGroupMeta;
103
+ export default AgentTeamMetaSettings;
@@ -0,0 +1,54 @@
1
+ import { Skeleton } from 'antd';
2
+ import { ReactNode, Suspense, memo } from 'react';
3
+
4
+ import { GroupSettingsTabs } from '@/store/global/initialState';
5
+ import { useServerConfigStore } from '@/store/serverConfig';
6
+
7
+ import AgentTeamChatSettings from './AgentTeamChatSettings';
8
+ import AgentTeamMembersSettings from './AgentTeamMembersSettings';
9
+ import AgentTeamMetaSettings from './AgentTeamMetaSettings';
10
+ import { GroupChatSettingsProvider } from './GroupChatSettingsProvider';
11
+ import { StoreUpdaterProps } from './StoreUpdater';
12
+
13
+ export interface AgentTeamSettingsProps extends StoreUpdaterProps {
14
+ tab?: GroupSettingsTabs;
15
+ }
16
+
17
+ export interface AgentTeamSettingsContentProps {
18
+ loadingSkeleton?: ReactNode;
19
+ tab: GroupSettingsTabs;
20
+ }
21
+
22
+ const AgentTeamSettingsContent = memo<AgentTeamSettingsContentProps>(({ tab }) => {
23
+ return (
24
+ <>
25
+ {tab === GroupSettingsTabs.Settings && <AgentTeamMetaSettings />}
26
+ {tab === GroupSettingsTabs.Members && <AgentTeamMembersSettings />}
27
+ {tab === GroupSettingsTabs.Chat && <AgentTeamChatSettings />}
28
+ </>
29
+ );
30
+ });
31
+
32
+ const AgentTeamSettings = memo<AgentTeamSettingsProps>(
33
+ ({ tab = GroupSettingsTabs.Settings, ...rest }) => {
34
+ const isMobile = useServerConfigStore((s) => s.isMobile);
35
+ const loadingSkeleton = (
36
+ <Skeleton
37
+ active
38
+ paragraph={{ rows: 6 }}
39
+ style={{ padding: isMobile ? 16 : 0 }}
40
+ title={false}
41
+ />
42
+ );
43
+
44
+ return (
45
+ <GroupChatSettingsProvider {...rest}>
46
+ <Suspense fallback={loadingSkeleton}>
47
+ <AgentTeamSettingsContent tab={tab} />
48
+ </Suspense>
49
+ </GroupChatSettingsProvider>
50
+ );
51
+ },
52
+ );
53
+
54
+ export default AgentTeamSettings;
@@ -1,10 +1,9 @@
1
- export { default as ChatGroupMeta } from './ChatGroupMeta';
2
- export { default as ChatGroupSettings } from './ChatGroupSettings';
1
+ export { default as AgentTeamChatSettings } from './AgentTeamChatSettings';
2
+ export { default as GroupMembersConfig } from './AgentTeamMembersSettings';
3
+ export { default as ChatGroupMeta } from './AgentTeamMetaSettings';
4
+ export { default as AgentTeamSettings } from './AgentTeamSettings';
3
5
  export { default as GroupCategory } from './GroupCategory';
4
6
  export { GroupChatSettingsProvider } from './GroupChatSettingsProvider';
5
- export { default as GroupMembers } from './GroupMembers';
6
- export { default as GroupSettings } from './GroupSettings';
7
- export { default as GroupSettingsContent } from './GroupSettingsContent';
8
7
 
9
8
  // Hooks
10
9
  export type { GroupChatSettingsInstance } from './hooks/useGroupChatSettings';