@lobehub/chat 1.79.10 → 1.80.0

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 (52) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/changelog/v1.json +15 -0
  3. package/docs/development/basic/feature-development-new.mdx +465 -0
  4. package/docs/development/basic/feature-development-new.zh-CN.mdx +465 -0
  5. package/docs/development/database-schema.dbml +2 -0
  6. package/locales/ar/setting.json +16 -0
  7. package/locales/bg-BG/setting.json +16 -0
  8. package/locales/de-DE/setting.json +16 -0
  9. package/locales/en-US/setting.json +16 -0
  10. package/locales/es-ES/setting.json +16 -0
  11. package/locales/fa-IR/setting.json +16 -0
  12. package/locales/fr-FR/setting.json +16 -0
  13. package/locales/it-IT/setting.json +16 -0
  14. package/locales/ja-JP/setting.json +16 -0
  15. package/locales/ko-KR/setting.json +16 -0
  16. package/locales/nl-NL/setting.json +16 -0
  17. package/locales/pl-PL/setting.json +16 -0
  18. package/locales/pt-BR/setting.json +16 -0
  19. package/locales/ru-RU/setting.json +16 -0
  20. package/locales/tr-TR/setting.json +16 -0
  21. package/locales/vi-VN/setting.json +16 -0
  22. package/locales/zh-CN/setting.json +16 -0
  23. package/locales/zh-TW/setting.json +16 -0
  24. package/package.json +1 -1
  25. package/scripts/generate-oidc-jwk.mjs +2 -1
  26. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/OpeningQuestions.tsx +78 -0
  27. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +24 -4
  28. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/CategoryContent/useCategory.tsx +6 -1
  29. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +2 -0
  30. package/src/const/settings/agent.ts +1 -0
  31. package/src/database/_deprecated/schemas/session.ts +2 -0
  32. package/src/database/client/migrations.json +9 -0
  33. package/src/database/migrations/0021_add_agent_opening_settings.sql +2 -0
  34. package/src/database/migrations/meta/0021_snapshot.json +4988 -0
  35. package/src/database/migrations/meta/_journal.json +7 -0
  36. package/src/database/repositories/dataImporter/deprecated/__tests__/fixtures/messages.json +2 -0
  37. package/src/database/repositories/dataImporter/deprecated/__tests__/index.test.ts +19 -0
  38. package/src/database/schemas/agent.ts +3 -0
  39. package/src/features/AgentSetting/AgentOpening/OpeningMessage.tsx +80 -0
  40. package/src/features/AgentSetting/AgentOpening/OpeningQuestions.tsx +144 -0
  41. package/src/features/AgentSetting/AgentOpening/index.tsx +52 -0
  42. package/src/features/AgentSetting/store/selectors.ts +3 -0
  43. package/src/locales/default/setting.ts +16 -0
  44. package/src/migrations/FromV5ToV6/types/v6.ts +2 -0
  45. package/src/server/routers/lambda/session.ts +8 -1
  46. package/src/services/import/client.test.ts +18 -0
  47. package/src/services/session/server.test.ts +2 -0
  48. package/src/store/agent/slices/chat/selectors/__snapshots__/agent.test.ts.snap +1 -0
  49. package/src/store/agent/slices/chat/selectors/agent.ts +7 -0
  50. package/src/store/global/initialState.ts +1 -0
  51. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +2 -0
  52. package/src/types/agent/index.ts +12 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 1.80.0](https://github.com/lobehub/lobe-chat/compare/v1.79.10...v1.80.0)
6
+
7
+ <sup>Released on **2025-04-16**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Rename new feature development doc to mdx, some code optimization.
12
+
13
+ #### ✨ Features
14
+
15
+ - **misc**: Opening settings copyright i18n, regenerate migrate sql for new agent opening settings.
16
+
17
+ #### 🐛 Bug Fixes
18
+
19
+ - **misc**: Opening questions should should be hidden when no setted, opening settings migration type error.
20
+
21
+ #### 💄 Styles
22
+
23
+ - **misc**: Opening message container add border.
24
+
25
+ <br/>
26
+
27
+ <details>
28
+ <summary><kbd>Improvements and Fixes</kbd></summary>
29
+
30
+ #### Code refactoring
31
+
32
+ - **misc**: Rename new feature development doc to mdx ([892a347](https://github.com/lobehub/lobe-chat/commit/892a347))
33
+ - **misc**: Some code optimization ([47e04fa](https://github.com/lobehub/lobe-chat/commit/47e04fa))
34
+
35
+ #### What's improved
36
+
37
+ - **misc**: Opening settings copyright i18n ([27c5b45](https://github.com/lobehub/lobe-chat/commit/27c5b45))
38
+ - **misc**: Regenerate migrate sql for new agent opening settings ([961d6a1](https://github.com/lobehub/lobe-chat/commit/961d6a1))
39
+
40
+ #### What's fixed
41
+
42
+ - **misc**: Opening questions should should be hidden when no setted ([211ee5e](https://github.com/lobehub/lobe-chat/commit/211ee5e))
43
+ - **misc**: Opening settings migration type error ([72cf00e](https://github.com/lobehub/lobe-chat/commit/72cf00e))
44
+
45
+ #### Styles
46
+
47
+ - **misc**: Opening message container add border ([63b96c7](https://github.com/lobehub/lobe-chat/commit/63b96c7))
48
+
49
+ </details>
50
+
51
+ <div align="right">
52
+
53
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
54
+
55
+ </div>
56
+
5
57
  ### [Version 1.79.10](https://github.com/lobehub/lobe-chat/compare/v1.79.9...v1.79.10)
6
58
 
7
59
  <sup>Released on **2025-04-15**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,19 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Opening message container add border."
6
+ ],
7
+ "features": [
8
+ "Opening settings copyright i18n, regenerate migrate sql for new agent opening settings."
9
+ ],
10
+ "fixes": [
11
+ "Opening questions should should be hidden when no setted, opening settings migration type error."
12
+ ]
13
+ },
14
+ "date": "2025-04-16",
15
+ "version": "1.80.0"
16
+ },
2
17
  {
3
18
  "children": {
4
19
  "improvements": [
@@ -0,0 +1,465 @@
1
+ # LobeChat Feature Development Complete Guide (New Version)
2
+
3
+ This document aims to guide developers on how to develop a complete feature in LobeChat.
4
+
5
+ We will use [RFC 021 - Custom Assistant Opening Guidance](https://github.com/lobehub/lobe-chat/discussions/891) as an example to illustrate the complete implementation process.
6
+
7
+ ## 1. Update Schema
8
+
9
+ lobe-chat uses a postgres database, with the browser-side local database using [pglite](https://pglite.dev/) (wasm version of postgres). The project also uses [drizzle](https://orm.drizzle.team/) ORM to operate the database.
10
+
11
+ Compared to the old solution where the browser side used indexDB, having both the browser side and server side use postgres has the advantage that the model layer code can be completely reused.
12
+
13
+ All schemas are uniformly placed in `src/database/schemas`. We need to adjust the `agents` table to add two fields corresponding to the configuration items:
14
+
15
+ ```diff
16
+ // src/database/schemas/agent.ts
17
+ export const agents = pgTable(
18
+ 'agents',
19
+ {
20
+ id: text('id')
21
+ .primaryKey()
22
+ .$defaultFn(() => idGenerator('agents'))
23
+ .notNull(),
24
+ avatar: text('avatar'),
25
+ backgroundColor: text('background_color'),
26
+ plugins: jsonb('plugins').$type<string[]>().default([]),
27
+ // ...
28
+ tts: jsonb('tts').$type<LobeAgentTTSConfig>(),
29
+
30
+ + openingMessage: text('opening_message'),
31
+ + openingQuestions: text('opening_questions').array().default([]),
32
+
33
+ ...timestamps,
34
+ },
35
+ (t) => ({
36
+ // ...
37
+ // !: update index here
38
+ }),
39
+ );
40
+
41
+ ```
42
+
43
+ Note that sometimes we may also need to update the index, but for this feature, we don't have any related performance bottleneck issues, so we don't need to update the index.
44
+
45
+ After adjusting the schema, we need to run `npm run db:generate` to use drizzle-kit's built-in database migration capability to generate the corresponding SQL code for migrating to the latest schema. After execution, four files will be generated:
46
+
47
+ - src/database/migrations/meta/\_journal.json: Saves information about each migration
48
+ - src/database/migrations/0021\_add\_agent\_opening\_settings.sql: SQL commands for this migration
49
+ - src/database/client/migrations.json: SQL commands for this migration used by pglite
50
+ - src/database/migrations/meta/0021\_snapshot.json: The current latest complete database snapshot
51
+
52
+ Note that the migration SQL filename generated by the script by default is not semantically clear like `0021_add_agent_opening_settings.sql`. You need to manually rename it and update `src/database/migrations/meta/_journal.json`.
53
+
54
+ Previously, client-side storage using indexDB made database migration relatively complicated. Now with pglite on the local side, database migration is a simple command, very quick and easy. You can also check if there's any room for optimization in the generated migration SQL and make manual adjustments.
55
+
56
+ ## 2. Update Data Model
57
+
58
+ Data models used in our project are defined in `src/types`. We don't directly use the types exported from the drizzle schema, such as `export type NewAgent = typeof agents.$inferInsert;`, but instead define corresponding data models based on frontend requirements and data types of the corresponding fields in the db schema definition.
59
+
60
+ Data model definitions are placed in the `src/types` folder. Update the `LobeAgentConfig` type in `src/types/agent/index.ts`:
61
+
62
+ ```diff
63
+ export interface LobeAgentConfig {
64
+ // ...
65
+ chatConfig: LobeAgentChatConfig;
66
+ /**
67
+ * The language model used by the agent
68
+ * @default gpt-4o-mini
69
+ */
70
+ model: string;
71
+
72
+ + /**
73
+ + * Opening message
74
+ + */
75
+ + openingMessage?: string;
76
+ + /**
77
+ + * Opening questions
78
+ + */
79
+ + openingQuestions?: string[];
80
+
81
+ /**
82
+ * Language model parameters
83
+ */
84
+ params: LLMParams;
85
+ /**
86
+ * Enabled plugins
87
+ */
88
+ plugins?: string[];
89
+
90
+ /**
91
+ * Model provider
92
+ */
93
+ provider?: string;
94
+
95
+ /**
96
+ * System role
97
+ */
98
+ systemRole: string;
99
+
100
+ /**
101
+ * Text-to-speech service
102
+ */
103
+ tts: LobeAgentTTSConfig;
104
+ }
105
+ ```
106
+
107
+ ## 3. Service Implementation / Model Implementation
108
+
109
+ - The `model` layer encapsulates reusable operations on the DB
110
+ - The `service` layer implements application business logic
111
+
112
+ Both have corresponding top-level folders in the `src` directory.
113
+
114
+ We need to check if we need to update their implementation. Agent configuration in the frontend is abstracted as session configuration. In `src/services/session/server.ts` we can see a service function `updateSessionConfig`:
115
+
116
+ ```typescript
117
+ export class ServerService implements ISessionService {
118
+ // ...
119
+ updateSessionConfig: ISessionService['updateSessionConfig'] = (id, config, signal) => {
120
+ return lambdaClient.session.updateSessionConfig.mutate({ id, value: config }, { signal });
121
+ };
122
+ }
123
+ ```
124
+
125
+ Jumping to the implementation of `lambdaClient.session.updateSessionConfig`, we find that it simply **merges** the new config with the old config.
126
+
127
+ ```typescript
128
+ export const sessionRouter = router({
129
+ // ..
130
+ updateSessionConfig: sessionProcedure
131
+ .input(
132
+ z.object({
133
+ id: z.string(),
134
+ value: z.object({}).passthrough().partial(),
135
+ }),
136
+ )
137
+ .mutation(async ({ input, ctx }) => {
138
+ const session = await ctx.sessionModel.findByIdOrSlug(input.id);
139
+ // ...
140
+ const mergedValue = merge(session.agent, input.value);
141
+ return ctx.sessionModel.updateConfig(session.agent.id, mergedValue);
142
+ }),
143
+ });
144
+ ```
145
+
146
+ We can anticipate that in the frontend, we will add two inputs, and when the user makes modifications, we'll call this `updateSessionConfig`. We just need to pass the new fields `openingMessage` and `openingQuestions` for merging.
147
+
148
+ Therefore, the service layer and model layer do not need to be modified.
149
+
150
+ ## 4. Frontend Implementation
151
+
152
+ ### Data Flow Store Implementation
153
+
154
+ lobe-chat uses [zustand](https://zustand.docs.pmnd.rs/getting-started/introduction) as the global state management framework. For detailed practices on state management, you can refer to [📘 State Management Best Practices](/docs/development/state-management/state-management-intro).
155
+
156
+ There are two stores related to the agent:
157
+
158
+ - `src/features/AgentSetting/store` serves the local store for agent settings
159
+ - `src/store/agent` is used to get the current session agent's store
160
+
161
+ The latter listens for and updates the current session's agent configuration through the `onConfigChange` in the `AgentSettings` component in `src/features/AgentSetting/AgentSettings.tsx`.
162
+
163
+ #### Update AgentSetting/store
164
+
165
+ First, we update the initialState. After reading `src/features/AgentSetting/store/initialState.ts`, we learn that the initial agent configuration is saved in `DEFAULT_AGENT_CONFIG` in `src/const/settings/agent.ts`:
166
+
167
+ ```diff
168
+ export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
169
+ chatConfig: DEFAULT_AGENT_CHAT_CONFIG,
170
+ model: DEFAULT_MODEL,
171
+ + openingQuestions: [],
172
+ params: {
173
+ frequency_penalty: 0,
174
+ presence_penalty: 0,
175
+ temperature: 1,
176
+ top_p: 1,
177
+ },
178
+ plugins: [],
179
+ provider: DEFAULT_PROVIDER,
180
+ systemRole: '',
181
+ tts: DEFAUTT_AGENT_TTS_CONFIG,
182
+ };
183
+ ```
184
+
185
+ Actually, you don't even need to update this since the `openingQuestions` type is already optional. I'm not updating `openingMessage` here.
186
+
187
+ Because we've added two new fields, to facilitate access by components in the `src/features/AgentSetting/AgentOpening` folder and for performance optimization, we add related selectors in `src/features/AgentSetting/store/selectors.ts`:
188
+
189
+ ```diff
190
+ import { DEFAULT_AGENT_CHAT_CONFIG } from '@/const/settings';
191
+ import { LobeAgentChatConfig } from '@/types/agent';
192
+
193
+ import { Store } from './action';
194
+
195
+ const chatConfig = (s: Store): LobeAgentChatConfig =>
196
+ s.config.chatConfig || DEFAULT_AGENT_CHAT_CONFIG;
197
+
198
+ +export const DEFAULT_OPENING_QUESTIONS: string[] = [];
199
+ export const selectors = {
200
+ chatConfig,
201
+ + openingMessage: (s: Store) => s.config.openingMessage,
202
+ + openingQuestions: (s: Store) => s.config.openingQuestions || DEFAULT_OPENING_QUESTIONS,
203
+ };
204
+ ```
205
+
206
+ We won't add additional actions to update the agent config here, as I've observed that other existing code also directly uses the unified `setChatConfig` in the existing code:
207
+
208
+ ```typescript
209
+ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, get) => ({
210
+ setAgentConfig: (config) => {
211
+ get().dispatchConfig({ config, type: 'update' });
212
+ },
213
+ });
214
+ ```
215
+
216
+ #### Update store/agent
217
+
218
+ In the component `src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx`, we use this store to get the current agent configuration to render user-customized opening messages and guiding questions.
219
+
220
+ Since we only need to read two configuration items, we'll simply add two selectors:
221
+
222
+ Update `src/store/agent/slices/chat/selectors/agent.ts`:
223
+
224
+ ```diff
225
+ // ...
226
+ +const openingQuestions = (s: AgentStoreState) =>
227
+ + currentAgentConfig(s).openingQuestions || DEFAULT_OPENING_QUESTIONS;
228
+ +const openingMessage = (s: AgentStoreState) => currentAgentConfig(s).openingMessage || '';
229
+
230
+ export const agentSelectors = {
231
+ // ...
232
+ isInboxSession,
233
+ + openingMessage,
234
+ + openingQuestions,
235
+ };
236
+ ```
237
+
238
+ ### UI Implementation and Action Binding
239
+
240
+ We're adding a new category of settings this time. In `src/features/AgentSetting`, various UI components for agent settings are defined. This time we're adding a setting type: opening settings. We'll add a folder `AgentOpening` to store opening settings-related components. The project uses:
241
+
242
+ - [ant-design](https://ant.design/) and [lobe-ui](https://github.com/lobehub/lobe-ui): component libraries
243
+ - [antd-style](https://ant-design.github.io/antd-style): css-in-js solution
244
+ - [react-layout-kit](https://github.com/arvinxx/react-layout-kit): responsive layout components
245
+ - [@ant-design/icons](https://ant.design/components/icon-cn) and [lucide](https://lucide.dev/icons/): icon libraries
246
+ - [react-i18next](https://github.com/i18next/react-i18next) and [lobe-i18n](https://github.com/lobehub/lobe-cli-toolbox/tree/master/packages/lobe-i18n): i18n framework and multi-language automatic translation tool
247
+
248
+ lobe-chat is an internationalized project, so newly added text needs to update the default `locale` file: `src/locales/default/setting.ts`.
249
+
250
+ Let's take the subcomponent `OpeningQuestion.tsx` as an example. Component implementation:
251
+
252
+ ```typescript
253
+ // src/features/AgentSetting/AgentOpening/OpeningQuestions.tsx
254
+ 'use client';
255
+
256
+ import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
257
+ import { SortableList } from '@lobehub/ui';
258
+ import { Button, Empty, Input } from 'antd';
259
+ import { createStyles } from 'antd-style';
260
+ import { memo, useCallback, useMemo, useState } from 'react';
261
+ import { useTranslation } from 'react-i18next';
262
+ import { Flexbox } from 'react-layout-kit';
263
+
264
+ import { useStore } from '../store';
265
+ import { selectors } from '../store/selectors';
266
+
267
+ const useStyles = createStyles(({ css, token }) => ({
268
+ empty: css`
269
+ margin-block: 24px;
270
+ margin-inline: 0;
271
+ `,
272
+ questionItemContainer: css`
273
+ margin-block-end: 8px;
274
+ padding-block: 2px;
275
+ padding-inline: 10px 0;
276
+ background: ${token.colorBgContainer};
277
+ `,
278
+ questionItemContent: css`
279
+ flex: 1;
280
+ `,
281
+ questionsList: css`
282
+ width: 100%;
283
+ margin-block-start: 16px;
284
+ `,
285
+ repeatError: css`
286
+ margin: 0;
287
+ color: ${token.colorErrorText};
288
+ `,
289
+ }));
290
+
291
+ interface QuestionItem {
292
+ content: string;
293
+ id: number;
294
+ }
295
+
296
+ const OpeningQuestions = memo(() => {
297
+ const { t } = useTranslation('setting');
298
+ const { styles } = useStyles();
299
+ const [questionInput, setQuestionInput] = useState('');
300
+
301
+ // Use selector to access corresponding configuration
302
+ const openingQuestions = useStore(selectors.openingQuestions);
303
+ // Use action to update configuration
304
+ const updateConfig = useStore((s) => s.setAgentConfig);
305
+ const setQuestions = useCallback(
306
+ (questions: string[]) => {
307
+ updateConfig({ openingQuestions: questions });
308
+ },
309
+ [updateConfig],
310
+ );
311
+
312
+ const addQuestion = useCallback(() => {
313
+ if (!questionInput.trim()) return;
314
+
315
+ setQuestions([...openingQuestions, questionInput.trim()]);
316
+ setQuestionInput('');
317
+ }, [openingQuestions, questionInput, setQuestions]);
318
+
319
+ const removeQuestion = useCallback(
320
+ (content: string) => {
321
+ const newQuestions = [...openingQuestions];
322
+ const index = newQuestions.indexOf(content);
323
+ newQuestions.splice(index, 1);
324
+ setQuestions(newQuestions);
325
+ },
326
+ [openingQuestions, setQuestions],
327
+ );
328
+
329
+ // Handle logic after drag and drop sorting
330
+ const handleSortEnd = useCallback(
331
+ (items: QuestionItem[]) => {
332
+ setQuestions(items.map((item) => item.content));
333
+ },
334
+ [setQuestions],
335
+ );
336
+
337
+ const items: QuestionItem[] = useMemo(() => {
338
+ return openingQuestions.map((item, index) => ({
339
+ content: item,
340
+ id: index,
341
+ }));
342
+ }, [openingQuestions]);
343
+
344
+ const isRepeat = openingQuestions.includes(questionInput.trim());
345
+
346
+ return (
347
+ <Flexbox gap={8}>
348
+ <Flexbox gap={4}>
349
+ <Input
350
+ addonAfter={
351
+ <Button
352
+ // don't allow repeat
353
+ disabled={openingQuestions.includes(questionInput.trim())}
354
+ icon={<PlusOutlined />}
355
+ onClick={addQuestion}
356
+ size="small"
357
+ type="text"
358
+ />
359
+ }
360
+ onChange={(e) => setQuestionInput(e.target.value)}
361
+ onPressEnter={addQuestion}
362
+ placeholder={t('settingOpening.openingQuestions.placeholder')}
363
+ value={questionInput}
364
+ />
365
+
366
+ {isRepeat && (
367
+ <p className={styles.repeatError}>{t('settingOpening.openingQuestions.repeat')}</p>
368
+ )}
369
+ </Flexbox>
370
+
371
+ <div className={styles.questionsList}>
372
+ {openingQuestions.length > 0 ? (
373
+ <SortableList
374
+ items={items}
375
+ onChange={handleSortEnd}
376
+ renderItem={(item) => (
377
+ <SortableList.Item className={styles.questionItemContainer} id={item.id}>
378
+ <SortableList.DragHandle />
379
+ <div className={styles.questionItemContent}>{item.content}</div>
380
+ <Button
381
+ icon={<DeleteOutlined />}
382
+ onClick={() => removeQuestion(item.content)}
383
+ type="text"
384
+ />
385
+ </SortableList.Item>
386
+ )}
387
+ />
388
+ ) : (
389
+ <Empty
390
+ className={styles.empty}
391
+ description={t('settingOpening.openingQuestions.empty')}
392
+ />
393
+ )}
394
+ </div>
395
+ </Flexbox>
396
+ );
397
+ });
398
+
399
+ export default OpeningQuestions;
400
+ ```
401
+
402
+ At the same time, we need to display the opening configuration set by the user, which is on the chat page. The corresponding component is in `src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx`:
403
+
404
+ ```typescript
405
+ const WelcomeMessage = () => {
406
+ const { t } = useTranslation('chat');
407
+
408
+ // Get current opening configuration
409
+ const openingMessage = useAgentStore(agentSelectors.openingMessage);
410
+ const openingQuestions = useAgentStore(agentSelectors.openingQuestions);
411
+
412
+ const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
413
+ const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
414
+ const activeId = useChatStore((s) => s.activeId);
415
+
416
+ const agentSystemRoleMsg = t('agentDefaultMessageWithSystemRole', {
417
+ name: meta.title || t('defaultAgent'),
418
+ systemRole: meta.description,
419
+ });
420
+
421
+ const agentMsg = t(isAgentEditable ? 'agentDefaultMessage' : 'agentDefaultMessageWithoutEdit', {
422
+ name: meta.title || t('defaultAgent'),
423
+ url: `/chat/settings?session=${activeId}`,
424
+ });
425
+
426
+ const message = useMemo(() => {
427
+ // Use user-set message if available
428
+ if (openingMessage) return openingMessage;
429
+ return !!meta.description ? agentSystemRoleMsg : agentMsg;
430
+ }, [openingMessage, agentSystemRoleMsg, agentMsg, meta.description]);
431
+
432
+ const chatItem = (
433
+ <ChatItem
434
+ avatar={meta}
435
+ editing={false}
436
+ message={message}
437
+ placement={'left'}
438
+ type={type === 'chat' ? 'block' : 'pure'}
439
+ />
440
+ );
441
+
442
+ return openingQuestions.length > 0 ? (
443
+ <Flexbox>
444
+ {chatItem}
445
+ {/* Render guiding questions */}
446
+ <OpeningQuestions mobile={mobile} questions={openingQuestions} />
447
+ </Flexbox>
448
+ ) : (
449
+ chatItem
450
+ );
451
+ };
452
+ export default WelcomeMessage;
453
+ ```
454
+
455
+ ## 5. Testing
456
+
457
+ The project uses vitest for unit testing.
458
+
459
+ Since our two new configuration fields are both optional, theoretically you could pass the tests without updating them. However, since we added the `openingQuestions` field to the `DEFAULT_AGENT_CONFIG` mentioned earlier, this causes many tests to calculate configurations that include this field, so we still need to update some test snapshots.
460
+
461
+ For the current scenario, I recommend running the tests locally to see which tests fail, and then update them as needed. For example, for the test file `src/store/agent/slices/chat/selectors/agent.test.ts`, you need to run `npx vitest -u src/store/agent/slices/chat/selectors/agent.test.ts` to update the snapshot.
462
+
463
+ ## Summary
464
+
465
+ The above is the complete implementation process for the LobeChat opening settings feature. Developers can refer to this document for the development and testing of related features.