@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.
- package/CHANGELOG.md +52 -0
- package/changelog/v1.json +15 -0
- package/docs/development/basic/feature-development-new.mdx +465 -0
- package/docs/development/basic/feature-development-new.zh-CN.mdx +465 -0
- package/docs/development/database-schema.dbml +2 -0
- package/locales/ar/setting.json +16 -0
- package/locales/bg-BG/setting.json +16 -0
- package/locales/de-DE/setting.json +16 -0
- package/locales/en-US/setting.json +16 -0
- package/locales/es-ES/setting.json +16 -0
- package/locales/fa-IR/setting.json +16 -0
- package/locales/fr-FR/setting.json +16 -0
- package/locales/it-IT/setting.json +16 -0
- package/locales/ja-JP/setting.json +16 -0
- package/locales/ko-KR/setting.json +16 -0
- package/locales/nl-NL/setting.json +16 -0
- package/locales/pl-PL/setting.json +16 -0
- package/locales/pt-BR/setting.json +16 -0
- package/locales/ru-RU/setting.json +16 -0
- package/locales/tr-TR/setting.json +16 -0
- package/locales/vi-VN/setting.json +16 -0
- package/locales/zh-CN/setting.json +16 -0
- package/locales/zh-TW/setting.json +16 -0
- package/package.json +1 -1
- package/scripts/generate-oidc-jwk.mjs +2 -1
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/OpeningQuestions.tsx +78 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +24 -4
- package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/CategoryContent/useCategory.tsx +6 -1
- package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +2 -0
- package/src/const/settings/agent.ts +1 -0
- package/src/database/_deprecated/schemas/session.ts +2 -0
- package/src/database/client/migrations.json +9 -0
- package/src/database/migrations/0021_add_agent_opening_settings.sql +2 -0
- package/src/database/migrations/meta/0021_snapshot.json +4988 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/database/repositories/dataImporter/deprecated/__tests__/fixtures/messages.json +2 -0
- package/src/database/repositories/dataImporter/deprecated/__tests__/index.test.ts +19 -0
- package/src/database/schemas/agent.ts +3 -0
- package/src/features/AgentSetting/AgentOpening/OpeningMessage.tsx +80 -0
- package/src/features/AgentSetting/AgentOpening/OpeningQuestions.tsx +144 -0
- package/src/features/AgentSetting/AgentOpening/index.tsx +52 -0
- package/src/features/AgentSetting/store/selectors.ts +3 -0
- package/src/locales/default/setting.ts +16 -0
- package/src/migrations/FromV5ToV6/types/v6.ts +2 -0
- package/src/server/routers/lambda/session.ts +8 -1
- package/src/services/import/client.test.ts +18 -0
- package/src/services/session/server.test.ts +2 -0
- package/src/store/agent/slices/chat/selectors/__snapshots__/agent.test.ts.snap +1 -0
- package/src/store/agent/slices/chat/selectors/agent.ts +7 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +2 -0
- 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
|
+
[](#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.
|