diff --git a/docs/Development/Feature-Development.md b/docs/Development/Feature-Development-Frontend.md similarity index 100% rename from docs/Development/Feature-Development.md rename to docs/Development/Feature-Development-Frontend.md diff --git a/docs/Development/Feature-Development-Frontend.zh-CN.md b/docs/Development/Feature-Development-Frontend.zh-CN.md new file mode 100644 index 000000000000..4a4c4111d923 --- /dev/null +++ b/docs/Development/Feature-Development-Frontend.zh-CN.md @@ -0,0 +1,126 @@ +# 如何开发一个新功能:前端实现 + +LobeChat 基于 Next.js 框架构建,使用 TypeScript 作为主要开发语言。在开发新功能时,我们需要遵循一定的开发流程,以确保代码的质量和稳定性。大致的流程分为以下五步: + +1. 路由:定义路由 (`src/app`) +2. 数据结构: 定义数据结构 ( `src/types` ) +3. 业务功能实现: zustand store (`src/store`) +4. 页面展示:书写静态组件 / 页面 (`src/app//features/.tsx`) +5. 功能绑定:绑定 store 与页面的触发 (`const [state,function]= useNewStore(s=>[s.state,s.function])`) + +我们以 "会话消息" 功能为例,以下是实现这个功能的简要步骤: + +#### TOC + +- [1. 定义路由](#1-定义路由) +- [2. 定义数据结构](#2-定义数据结构) +- [3. 创建 Zustand Store](#3-创建-zustand-store) +- [4. 创建页面与组件](#4-创建页面与组件) +- [5. 功能绑定](#5-功能绑定) + +## 1. 定义路由 + +在 `src/app` 目录下,我们需要定义一个新的路由来承载 "会话消息" 页面。一般来说,我们会在 `src/app` 下创建一个新的文件夹,例如 `chat`,并且在这个文件夹中创建 `page.tsx`文件,在该文件中导出 React 组件作为页面的主体。 + +```tsx +// src/app/chat/page.tsx +import ChatPage from './features/chat'; + +export default ChatPage; +``` + +## 2. 定义数据结构 + +在 `src/types` 目录下,我们需要定义 "会话消息" 的数据结构。例如,我们创建一个 `chat.ts` 文件,并在其中定义 `ChatMessage` 类型: + +```ts +// src/types/chat.ts + +export type ChatMessage = { + id: string; + content: string; + timestamp: number; + sender: 'user' | 'bot'; +}; +``` + +## 3. 创建 Zustand Store + +在 `src/store` 目录下,我们需要创建一个新的 Zustand Store 来管理 "会话消息" 的状态。例如,我们创建一个 `chatStore.ts` 文件,并在其中定义一个 Zustand Store: + +```ts +// src/store/chatStore.ts +import create from 'zustand'; + +type ChatState = { + messages: ChatMessage[]; + addMessage: (message: ChatMessage) => void; +}; + +export const useChatStore = create((set) => ({ + messages: [], + addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })), +})); +``` + +## 4. 创建页面与组件 + +在 `src/app//features/.tsx` 中,我们需要创建一个新的页面或组件来显示 "会话消息"。在这个文件中,我们可以使用上面创建的 Zustand Store,以及 Ant Design 的组件来构建 UI: + +```jsx +// src/features/chat/index.tsx +import { List, Typography } from 'antd'; +import { useChatStore } from 'src/store/chatStore'; + +const ChatPage = () => { + const messages = useChatStore((state) => state.messages); + + return ( + ( + + {message.content} + + )} + /> + ); +}; + +export default ChatPage; +``` + +## 5. 功能绑定 + +在页面或组件中,我们需要将 Zustand Store 的状态和方法绑定到 UI 上。在上面的示例中,我们已经将 `messages` 状态绑定到了列表的 `dataSource` 属性上。现在,我们还需要一个方法来添加新的消息。我们可以在 Zustand Store 中定义这个方法,然后在页面或组件中使用它: + +```jsx +import { Button } from 'antd'; + +const ChatPage = () => { + const messages = useChatStore((state) => state.messages); + const addMessage = useChatStore((state) => state.addMessage); + + const handleSend = () => { + addMessage({ id: '1', content: 'Hello, world!', timestamp: Date.now(), sender: 'user' }); + }; + + return ( + <> + ( + + {message.content} + + )} + /> + + + ); +}; + +export default ChatPage; +``` + +以上就是在 LobeChat 中实现 "会话消息" 功能的步骤。当然,在 LobeChat 的实际开发中,真实场景所面临的业务诉求和场景远比上述 demo 复杂,请根据实际情况进行开发。 diff --git a/docs/Development/Feature-Development.zh-CN.md b/docs/Development/Feature-Development.zh-CN.md index 13a3fd251ef1..1395c55371cb 100644 --- a/docs/Development/Feature-Development.zh-CN.md +++ b/docs/Development/Feature-Development.zh-CN.md @@ -1,126 +1,707 @@ -# 如何开发一个新功能 +# LobeChat 功能开发完全指南 -LobeChat 基于 Next.js 框架构建,使用 TypeScript 作为主要开发语言。在开发新功能时,我们需要遵循一定的开发流程,以确保代码的质量和稳定性。大致的流程分为以下五步: +本文档旨在指导开发者了解如何在 LobeChat 中开发一块完整的功能需求。 -1. 路由:定义路由 (`src/app`) -2. 数据结构: 定义数据结构 ( `src/types` ) -3. 业务功能实现: zustand store (`src/store`) -4. 页面展示:书写静态组件 / 页面 (`src/app//features/.tsx`) -5. 功能绑定:绑定 store 与页面的触发 (`const [state,function]= useNewStore(s=>[s.state,s.function])`) +我们将以 sessionGroup 的实现为示例:[✨ feat: add session group manager](https://github.com/lobehub/lobe-chat/pull/1055) , 通过以下六个主要部分来阐述完整的实现流程: -我们以 "会话消息" 功能为例,以下是实现这个功能的简要步骤: +1. 数据模型 / 数据库定义 +2. Service 实现 / Model 实现 +3. 前端数据流 Store 实现 +4. UI 实现与 action 绑定 +5. 数据迁移 +6. 数据导入导出 -#### TOC +## 一、数据库部分 -- [1. 定义路由](#1-定义路由) -- [2. 定义数据结构](#2-定义数据结构) -- [3. 创建 Zustand Store](#3-创建-zustand-store) -- [4. 创建页面与组件](#4-创建页面与组件) -- [5. 功能绑定](#5-功能绑定) +为了实现 Session Group 功能,首先需要在数据库层面定义相关的数据模型和索引。 -## 1. 定义路由 +定义一个新的 sessionGroup 表,分 3 步: -在 `src/app` 目录下,我们需要定义一个新的路由来承载 "会话消息" 页面。一般来说,我们会在 `src/app` 下创建一个新的文件夹,例如 `chat`,并且在这个文件夹中创建 `page.tsx`文件,在该文件中导出 React 组件作为页面的主体。 +### 1. 建立数据模型 schema -```tsx -// src/app/chat/page.tsx -import ChatPage from './features/chat'; +在 `src/database/schema/sessionGroup.ts` 中定义 `DB_SessionGroup` 的数据模型: + +```typescript +import { z } from 'zod'; + +export const DB_SessionGroupSchema = z.object({ + name: z.string(), + sort: z.number().optional(), +}); -export default ChatPage; +export type DB_SessionGroup = z.infer; ``` -## 2. 定义数据结构 +### 2. 创建数据库索引 -在 `src/types` 目录下,我们需要定义 "会话消息" 的数据结构。例如,我们创建一个 `chat.ts` 文件,并在其中定义 `ChatMessage` 类型: +由于要新增一个表,所以需要在在数据库 Schema 中,为 `sessionGroup` 表添加索引。 -```ts -// src/types/chat.ts +在 `src/database/core/schema.ts` 中添加 `dbSchemaV4`: + +```diff +// ... 前面的一些实现 + +// ************************************** // +// ******* Version 3 - 2023-12-06 ******* // +// ************************************** // +// - Added `plugin` table + +export const dbSchemaV3 = { + ...dbSchemaV2, + plugins: + '&identifier, type, manifest.type, manifest.meta.title, manifest.meta.description, manifest.meta.author, createdAt, updatedAt', +}; + ++ // ************************************** // ++ // ******* Version 4 - 2024-01-21 ******* // ++ // ************************************** // ++ // - Added `sessionGroup` table -export type ChatMessage = { - id: string; - content: string; - timestamp: number; - sender: 'user' | 'bot'; ++ export const dbSchemaV4 = { ++ ...dbSchemaV3, ++ sessionGroups: '&id, name, sort, createdAt, updatedAt', ++ sessions: '&id, type, group, pinned, meta.title, meta.description, meta.tags, createdAt, updatedAt', }; ``` -## 3. 创建 Zustand Store +> \[!Note] +> +> 除了 `sessionGroups` 外,此处也修改了 `sessions` 的定义,原因是存在数据迁移的情况。但由于本节只关注 schema 定义,不展开数据迁移部分实现,详情可见第五节。 + +> \[!Important] +> +> 如果你不了解为何此处需要创建索引,以及不了解此处的 schema 的定义语法。你可能需要提前了解下 Dexie.js 相关的基础知识,可以查阅 [📘 本地数据库](./Local-Database.zh-CN) 部分了解相关内容。 + +### 3. 在本地 DB 中加入 sessionGroups 表 + +扩展本地数据库类以包含新的 `sessionGroups` 表: + +```diff + +import { dbSchemaV1, dbSchemaV2, dbSchemaV3, dbSchemaV4 } from './schemas'; + +interface LobeDBSchemaMap { + files: DB_File; + messages: DB_Message; + plugins: DB_Plugin; ++ sessionGroups: DB_SessionGroup; + sessions: DB_Session; + topics: DB_Topic; +} + +// Define a local DB +export class LocalDB extends Dexie { + public files: LobeDBTable<'files'>; + public sessions: LobeDBTable<'sessions'>; + public messages: LobeDBTable<'messages'>; + public topics: LobeDBTable<'topics'>; + public plugins: LobeDBTable<'plugins'>; ++ public sessionGroups: LobeDBTable<'sessionGroups'>; + + constructor() { + super(LOBE_CHAT_LOCAL_DB_NAME); + this.version(1).stores(dbSchemaV1); + this.version(2).stores(dbSchemaV2); + this.version(3).stores(dbSchemaV3); ++ this.version(4).stores(dbSchemaV4); + + this.files = this.table('files'); + this.sessions = this.table('sessions'); + this.messages = this.table('messages'); + this.topics = this.table('topics'); + this.plugins = this.table('plugins'); ++ this.sessionGroups = this.table('sessionGroups'); + } +} +``` + +如此一来,你就可以通过在 `Application` -> `Storage` -> `IndexedDB` 中查看到 `LOBE_CHAT_DB` 里的 `sessionGroups` 表了。 + +![](https://github.com/lobehub/lobe-chat/assets/28616219/aea50f66-4060-4a32-88c8-b3c672d05be8) + +## 二、Model 与 Service 部分 + +### 定义 Model + +在构建 LobeChat 应用时,Model 负责与数据库的交互,它定义了如何读取、插入、更新和删除数据库的数据,定义具体的业务逻辑。 + +在 `src/database/model/sessionGroup.ts` 中定义 `SessionGroupModel`: + +```typescript +import { BaseModel } from '@/database/core'; +import { DB_SessionGroup, DB_SessionGroupSchema } from '@/database/schemas/sessionGroup'; +import { nanoid } from '@/utils/uuid'; + +class _SessionGroupModel extends BaseModel { + constructor() { + super('sessions', DB_SessionGroupSchema); + } + + async create(name: string, sort?: number, id = nanoid()) { + return this._add({ name, sort }, id); + } + + // ... 其他 CRUD 方法的实现 +} + +export const SessionGroupModel = new _SessionGroupModel(); +``` + +### Service 实现 + +在 LobeChat 中,Service 层主要负责与后端服务进行通信,封装业务逻辑,并提供数据给前端的其他层使用。`SessionService` 是一个专门处理与会话(Session)相关业务逻辑的服务类,它封装了创建会话、查询会话、更新会话等操作。 + +为了保持代码的可维护性和可扩展性,我们将会话分组相关的服务逻辑放在 `SessionService` 中,这样可以使会话领域的业务逻辑保持内聚。当业务需求增加或变化时,我们可以更容易地在这个领域内进行修改和扩展。 + +`SessionService` 通过调用 `SessionGroupModel` 的方法来实现对会话分组的管理。 在 `sessionService` 中实现 Session Group 相关的请求逻辑: + +```typescript +class SessionService { + // ... 省略 session 业务逻辑 + + // ************************************** // + // *********** SessionGroup *********** // + // ************************************** // + + async createSessionGroup(name: string, sort?: number) { + const item = await SessionGroupModel.create(name, sort); + if (!item) { + throw new Error('session group create Error'); + } + + return item.id; + } + + // ... 其他 SessionGroup 相关的实现 +} +``` + +## 三、Store Action 部分 + +在 LobeChat 应用中,Store 是用于管理应用前端状态的模块。其中的 Action 是触发状态更新的函数,通常会调用服务层的方法来执行实际的数据处理操作,然后更新 Store 中的状态。我们采用了 `zustand` 作为 Store 模块的底层依赖,对于状态管理的详细实践介绍,可以查阅 [📘 状态管理最佳实践](./State-Management-Intro.zh-CN) + +### sessionGroup CRUD -在 `src/store` 目录下,我们需要创建一个新的 Zustand Store 来管理 "会话消息" 的状态。例如,我们创建一个 `chatStore.ts` 文件,并在其中定义一个 Zustand Store: +会话组的 CRUD 操作是管理会话组数据的核心行为。在 `src/store/session/slice/sessionGroup` 中,我们将实现与会话组相关的状态逻辑,包括添加、删除、更新会话组及其排序。 + +以下是 `action.ts` 文件中需要实现的 `SessionGroupAction` 接口方法: ```ts -// src/store/chatStore.ts -import create from 'zustand'; +export interface SessionGroupAction { + // 增加会话组 + addSessionGroup: (name: string) => Promise; + // 删除会话组 + removeSessionGroup: (id: string) => Promise; + // 更新会话的会话组 ID + updateSessionGroupId: (sessionId: string, groupId: string) => Promise; + // 更新会话组名称 + updateSessionGroupName: (id: string, name: string) => Promise; + // 更新会话组排序 + updateSessionGroupSort: (items: SessionGroupItem[]) => Promise; +} +``` -type ChatState = { - messages: ChatMessage[]; - addMessage: (message: ChatMessage) => void; -}; +以 `addSessionGroup` 方法为例,我们首先调用 `sessionService` 的 `createSessionGroup` 方法来创建新的会话组,然后使用 `refreshSessions` 方法来刷新 sessions 状态: + +```ts +export const createSessionGroupSlice: StateCreator< + SessionStore, + [['zustand/devtools', never]], + [], + SessionGroupAction +> = (set, get) => ({ + // 实现添加会话组的逻辑 + addSessionGroup: async (name) => { + // 调用服务层的 createSessionGroup 方法并传入会话组名称 + const id = await sessionService.createSessionGroup(name); + // 调用 get 方法获取当前的 Store 状态并执行 refreshSessions 方法刷新会话数据 + await get().refreshSessions(); + // 返回新创建的会话组 ID + return id; + }, + // ... 其他 action 实现 +}); +``` + +通过以上的实现,我们可以确保在添加新的会话组后,应用的状态会及时更新,且相关的组件会收到最新的状态并重新渲染。这种方式提高了数据流的可预测性和可维护性,同时也简化了组件之间的通信。 + +### Sessions 分组逻辑改造 + +本次需求改造需要对 Sessions 进行升级,从原来的单一列表变成了三个不同的分组:`pinnedSessions`(置顶列表)、`customSessionGroups`(自定义分组)和 `defaultSessions`(默认列表)。 + +为了处理这些分组,我们需要改造 `useFetchSessions` 的实现逻辑。以下是关键的改动点: + +1. 使用 `sessionService.getSessionsWithGroup` 方法负责调用后端接口来获取分组后的会话数据; +2. 将获取后的数据保存为三到不同的状态字段中:`pinnedSessions`、`customSessionGroups` 和 `defaultSessions`; + +#### `useFetchSessions` 方法 + +该方法在 `createSessionSlice` 中定义,如下所示: + +```typescript +export const createSessionSlice: StateCreator< + SessionStore, + [['zustand/devtools', never]], + [], + SessionAction +> = (set, get) => ({ + // ... 其他方法 + useFetchSessions: () => + useSWR(FETCH_SESSIONS_KEY, sessionService.getSessionsWithGroup, { + onSuccess: (data) => { + set( + { + customSessionGroups: data.customGroup, + defaultSessions: data.default, + isSessionsFirstFetchFinished: true, + pinnedSessions: data.pinned, + sessions: data.all, + }, + false, + n('useFetchSessions/onSuccess', data), + ); + }, + }), +}); +``` + +在成功获取数据后,我们使用 `set` 方法来更新 `customSessionGroups`、`defaultSessions`、`pinnedSessions` 和 `sessions` 状态。这将保证状态与最新的会话数据同步。 + +#### getSessionsWithGroup + +使用 `sessionService.getSessionsWithGroup` 方法负责调用后端接口 `SessionModel.queryWithGroups()` + +```typescript +class SessionService { + // ... 其他 SessionGroup 相关的实现 + + async getSessionsWithGroup(): Promise { + return SessionModel.queryWithGroups(); + } +} +``` + +#### `SessionModel.queryWithGroups` 方法 + +此方法是 `sessionService.getSessionsWithGroup` 调用的核心方法,它负责查询和组织会话数据,代码如下: + +```typescript +class _SessionModel extends BaseModel { + // ... 其他方法 + + /** + * 查询会话数据,并根据会话组将会话分类。 + * @returns {Promise} 返回一个对象,其中包含所有会话以及分为不同组的会话列表。 + */ + async queryWithGroups(): Promise { + // 查询会话组数据 + const groups = await SessionGroupModel.query(); + // 根据会话组ID查询自定义会话组 + const customGroups = await this.queryByGroupIds(groups.map((item) => item.id)); + // 查询默认会话列表 + const defaultItems = await this.querySessionsByGroupId(SessionDefaultGroup.Default); + // 查询置顶的会话 + const pinnedItems = await this.getPinnedSessions(); + + // 查询所有会话 + const all = await this.query(); + // 组合并返回所有会话及其分组信息 + return { + all, // 包含所有会话的数组 + customGroup: groups.map((group) => ({ ...group, children: customGroups[group.id] })), // 自定义分组 + default: defaultItems, // 默认会话列表 + pinned: pinnedItems, // 置顶会话列表 + }; + } +} +``` + +方法 `queryWithGroups` 首先查询所有会话组,然后基于这些组的 ID 查询自定义会话组,同时查询默认和固定的会话。最后,它返回一个包含所有会话和按组分类的会话列表对象。 -export const useChatStore = create((set) => ({ - messages: [], - addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })), -})); +### sessions selectors 调整 + +由于 sessions 中关于分组的逻辑发生了变化,因此我们需要调整 `sessions` 的 selectors 逻辑,以确保它们能够正确地处理新的数据结构。 + +原有的 selectors: + +```ts +// 默认分组 +const defaultSessions = (s: SessionStore): LobeSessions => s.sessions; + +// 置顶分组 +const pinnedSessionList = (s: SessionStore) => + defaultSessions(s).filter((s) => s.group === SessionGroupDefaultKeys.Pinned); + +// 未置顶分组 +const unpinnedSessionList = (s: SessionStore) => + defaultSessions(s).filter((s) => s.group === SessionGroupDefaultKeys.Default); ``` -## 4. 创建页面与组件 +修改后: + +```ts +const defaultSessions = (s: SessionStore): LobeSessions => s.defaultSessions; +const pinnedSessions = (s: SessionStore): LobeSessions => s.pinnedSessions; +const customSessionGroups = (s: SessionStore): CustomSessionGroup[] => s.customSessionGroups; +``` -在 `src/app//features/.tsx` 中,我们需要创建一个新的页面或组件来显示 "会话消息"。在这个文件中,我们可以使用上面创建的 Zustand Store,以及 Ant Design 的组件来构建 UI: +由于在 UI 中的取数全部是通过 `useSessionStore(sessionSelectors.defaultSessions)` 这样的写法实现的,因此我们只需要修改 `defaultSessions` 的选择器实现,即可完成数据结构的变更。 UI 层的取数代码完全不用变更,可以大大降低重构的成本和风险。 -```jsx -// src/features/chat/index.tsx -import { List, Typography } from 'antd'; -import { useChatStore } from 'src/store/chatStore'; +> !\[Important] +> +> 如果你对 Selectors 的概念和功能不太了解,可以查阅 [📘 数据存储取数模块](./State-Management-Selectors.zh-CN) 部分了解相关内容。 -const ChatPage = () => { - const messages = useChatStore((state) => state.messages); +## 四、UI 部分 + +在 UI 组件中绑定 Store Action 实现交互逻辑,例如 `CreateGroupModal`: + +```tsx +const CreateGroupModal = () => { + // ... 其他逻辑 + + const [updateSessionGroup, addCustomGroup] = useSessionStore((s) => [ + s.updateSessionGroupId, + s.addSessionGroup, + ]); return ( - ( - - {message.content} - - )} - /> + { + // ... 其他逻辑 + const groupId = await addCustomGroup(name); + await updateSessionGroup(sessionId, groupId); + }} + > + {/* ... */} + ); }; +``` + +## 五、数据迁移 + +在软件开发过程中,数据迁移是一个不可避免的问题,尤其是当现有的数据结构无法满足新的业务需求时。对于本次 SessionGroup 的迭代,我们需要处理 `session` 的 `group` 字段的迁移,这是一个典型的数据迁移案例。 + +### 旧数据结构的问题 -export default ChatPage; +在旧的数据结构中,`group` 字段被用来标记会话是否为 `pinned`(置顶)或属于某个 `default`(默认)分组。但是当需要支持多个会话分组时,原有的数据结构就显得不够灵活了。 + +例如: + +``` +before pin: group = abc +after pin: group = pinned +after unpin: group = default ``` -## 5. 功能绑定 +从上述示例中可以看出,一旦会话从置顶状态(`pinned`)取消置顶(`unpin`),`group` 字段将无法恢复为原来的 `abc` 值。这是因为我们没有一个独立的字段来维护置顶状态。因此,我们引入了一个新的字段 `pinned` 来表示会话是否被置顶,而 `group` 字段将仅用于标识会话分组。 + +### 迁移策略 + +本次迁移的核心逻辑只有一条: + +- 当用户的 `group` 字段为 `pinned` 时,将其 `pinned` 字段置为 `true`,同时将 group 设为 `default`; + +但 LobeChat 中的数据迁移通常涉及到 **配置文件迁移** 和 **数据库迁移** 两个部分。所以上述逻辑会需要分别在两块实现迁移。 + +#### 配置文件迁移 + +对于配置文件迁移,我们建议先于数据库迁移进行,因为配置文件迁移通常更容易进行测试和验证。LobeChat 的文件迁移配置位于 `src/migrations/index.ts` 文件中,其中定义了配置文件迁移的各个版本及对应的迁移脚本。 -在页面或组件中,我们需要将 Zustand Store 的状态和方法绑定到 UI 上。在上面的示例中,我们已经将 `messages` 状态绑定到了列表的 `dataSource` 属性上。现在,我们还需要一个方法来添加新的消息。我们可以在 Zustand Store 中定义这个方法,然后在页面或组件中使用它: +```diff +// 当前最新的版本号 +- export const CURRENT_CONFIG_VERSION = 2; ++ export const CURRENT_CONFIG_VERSION = 3; -```jsx -import { Button } from 'antd'; +// 历史记录版本升级模块 +const ConfigMigrations = [ ++ /** ++ * 2024.01.22 ++ * from `group = pinned` to `pinned:true` ++ */ ++ MigrationV2ToV3, + /** + * 2023.11.27 + * 从单 key 数据库转换为基于 dexie 的关系型结构 + */ + MigrationV1ToV2, + /** + * 2023.07.11 + * just the first version, Nothing to do + */ + MigrationV0ToV1, +]; +``` -const ChatPage = () => { - const messages = useChatStore((state) => state.messages); - const addMessage = useChatStore((state) => state.addMessage); +本次的配置文件迁移逻辑定义在 `src/migrations/FromV2ToV3/index.ts` 中,简化如下: - const handleSend = () => { - addMessage({ id: '1', content: 'Hello, world!', timestamp: Date.now(), sender: 'user' }); +```ts +export class MigrationV2ToV3 implements Migration { + // 指定从该版本开始向上升级 + version = 2; + + migrate(data: MigrationData): MigrationData { + const { sessions } = data.state; + + return { + ...data, + state: { + ...data.state, + sessions: sessions.map((s) => this.migrateSession(s)), + }, + }; + } + + migrateSession = (session: V2Session): V3Session => { + return { + ...session, + group: 'default', + pinned: session.group === 'pinned', + }; }; +} +``` - return ( - <> - ( - - {message.content} - - )} - /> - - - ); -}; +可以看到迁移的实现非常简单。但重要的是,我们需要保证迁移的正确性,因此需要编写对应的测试用例 `src/migrations/FromV2ToV3/migrations.test.ts`: + +```ts +import { MigrationData, VersionController } from '@/migrations/VersionController'; + +import { MigrationV1ToV2 } from '../FromV1ToV2'; +import inputV1Data from '../FromV1ToV2/fixtures/input-v1-session.json'; +import inputV2Data from './fixtures/input-v2-session.json'; +import outputV3DataFromV1 from './fixtures/output-v3-from-v1.json'; +import outputV3Data from './fixtures/output-v3.json'; +import { MigrationV2ToV3 } from './index'; -export default ChatPage; +describe('MigrationV2ToV3', () => { + let migrations; + let versionController: VersionController; + + beforeEach(() => { + migrations = [MigrationV2ToV3]; + versionController = new VersionController(migrations, 3); + }); + + it('should migrate data correctly through multiple versions', () => { + const data: MigrationData = inputV2Data; + + const migratedData = versionController.migrate(data); + + expect(migratedData.version).toEqual(outputV3Data.version); + expect(migratedData.state.sessions).toEqual(outputV3Data.state.sessions); + expect(migratedData.state.topics).toEqual(outputV3Data.state.topics); + expect(migratedData.state.messages).toEqual(outputV3Data.state.messages); + }); + + it('should work correct from v1 to v3', () => { + const data: MigrationData = inputV1Data; + + versionController = new VersionController([MigrationV2ToV3, MigrationV1ToV2], 3); + + const migratedData = versionController.migrate(data); + + expect(migratedData.version).toEqual(outputV3DataFromV1.version); + expect(migratedData.state.sessions).toEqual(outputV3DataFromV1.state.sessions); + expect(migratedData.state.topics).toEqual(outputV3DataFromV1.state.topics); + expect(migratedData.state.messages).toEqual(outputV3DataFromV1.state.messages); + }); +}); ``` -以上就是在 LobeChat 中实现 "会话消息" 功能的步骤。当然,在 LobeChat 的实际开发中,真实场景所面临的业务诉求和场景远比上述 demo 复杂,请根据实际情况进行开发。 +单测需要使用 `fixtures` 来固定测试数据,测试用例包含了两个部分的验证逻辑: 1) 单次迁移(v2 -> v3)和 2) 完整迁移(v1 -> v3)的正确性。 + +> \[!Important] +> +> 配置文件的版本号可能与数据库版本号不一致,因为数据库版本的更新不总是伴随数据结构的变化(如新增表或字段),而配置文件的版本更新则通常涉及到数据迁移。 + +#### 数据库迁移 + +数据库迁移则需要在 `LocalDB` 类中实施,该类定义在 `src/database/core/db.ts` 文件中。迁移过程涉及到为 `sessions` 表的每条记录添加新的 `pinned` 字段,并重置 `group` 字段: + +```diff +export class LocalDB extends Dexie { + public files: LobeDBTable<'files'>; + public sessions: LobeDBTable<'sessions'>; + public messages: LobeDBTable<'messages'>; + public topics: LobeDBTable<'topics'>; + public plugins: LobeDBTable<'plugins'>; + public sessionGroups: LobeDBTable<'sessionGroups'>; + + constructor() { + super(LOBE_CHAT_LOCAL_DB_NAME); + this.version(1).stores(dbSchemaV1); + this.version(2).stores(dbSchemaV2); + this.version(3).stores(dbSchemaV3); + this.version(4) + .stores(dbSchemaV4) ++ .upgrade((trans) => this.upgradeToV4(trans)); + + this.files = this.table('files'); + this.sessions = this.table('sessions'); + this.messages = this.table('messages'); + this.topics = this.table('topics'); + this.plugins = this.table('plugins'); + this.sessionGroups = this.table('sessionGroups'); + } + ++ /** ++ * 2024.01.22 ++ * ++ * DB V3 to V4 ++ * from `group = pinned` to `pinned:true` ++ */ ++ upgradeToV4 = async (trans: Transaction) => { ++ const sessions = trans.table('sessions'); ++ await sessions.toCollection().modify((session) => { ++ // translate boolean to number ++ session.pinned = session.group === 'pinned' ? 1 : 0; ++ session.group = 'default'; ++ }); ++ }; +} +``` + +以上就是我们的数据迁移策略。在进行迁移时,务必确保迁移脚本的正确性,并通过充分的测试验证迁移结果。 + +## 六、数据导入导出 + +在 LobeChat 中,数据导入导出功能是为了确保用户可以在不同设备之间迁移他们的数据。这包括会话、话题、消息和设置等数据。在本次的 Session Group 功能实现中,我们也需要对数据导入导出进行处理,以确保当完整导出的数据在其他设备上可以一模一样恢复。 + +数据导入导出的核心实现在 `src/service/config.ts` 的 `ConfigService` 中,其中的关键方法如下: + +| 方法名称 | 描述 | +| --------------------- | ---------------- | +| `importConfigState` | 导入配置数据 | +| `exportAgents` | 导出所有助理数据 | +| `exportSessions` | 导出所有会话数据 | +| `exportSingleSession` | 导出单个会话数据 | +| `exportSingleAgent` | 导出单个助理数据 | +| `exportSettings` | 导出设置数据 | +| `exportAll` | 导出所有数据 | + +### 数据导出 + +在 LobeChat 中,当用户选择导出数据时,会将当前的会话、话题、消息和设置等数据打包成一个 JSON 文件并提供给用户下载。这个 JSON 文件的标准结构如下: + +```json +{ + "exportType": "sessions", + "state": { + "sessions": [], + "topics": [], + "messages": [] + }, + "version": 3 +} +``` + +其中: + +- `exportType`: 标识导出数据的类型,目前有 `sessions`、 `agent` 、 `settings` 和 `all` 四种; +- `state`: 存储实际的数据,不同 `exportType` 的数据类型也不同; +- `version`: 标识数据的版本。 + +在 Session Group 功能实现中,我们需要在 `state` 字段中添加 `sessionGroups` 数据。这样,当用户导出数据时,他们的 Session Group 数据也会被包含在内。 + +以导出 sessions 为例,导出数据的相关实现代码修改如下: + +```diff +class ConfigService { + // ... 省略其他 + + exportSessions = async () => { + const sessions = await sessionService.getSessions(); ++ const sessionGroups = await sessionService.getSessionGroups(); + const messages = await messageService.getAllMessages(); + const topics = await topicService.getAllTopics(); + +- const config = createConfigFile('sessions', { messages, sessions, topics }); ++ const config = createConfigFile('sessions', { messages, sessionGroups, sessions, topics }); + + exportConfigFile(config, 'sessions'); + }; +} +``` + +### 数据导入 + +数据导入的功能是通过 `ConfigService.importConfigState` 来实现的。当用户选择导入数据时,他们需要提供一个由 符合上述结构规范的 JSON 文件。`importConfigState` 方法接受配置文件的数据,并将其导入到应用中。 + +在 Session Group 功能实现中,我们需要在导入数据的过程中处理 `sessionGroups` 数据。这样,当用户导入数据时,他们的 Session Group 数据也会被正确地导入。 + +以下是 `importConfigState` 中导入实现的变更代码: + +```diff +class ConfigService { + // ... 省略其他代码 + ++ importSessionGroups = async (sessionGroups: SessionGroupItem[]) => { ++ return sessionService.batchCreateSessionGroups(sessionGroups); ++ }; + + importConfigState = async (config: ConfigFile): Promise => { + switch (config.exportType) { + case 'settings': { + await this.importSettings(config.state.settings); + + break; + } + + case 'agents': { ++ const sessionGroups = await this.importSessionGroups(config.state.sessionGroups); + + const data = await this.importSessions(config.state.sessions); + return { ++ sessionGroups: this.mapImportResult(sessionGroups), + sessions: this.mapImportResult(data), + }; + } + + case 'all': { + await this.importSettings(config.state.settings); + ++ const sessionGroups = await this.importSessionGroups(config.state.sessionGroups); + + const [sessions, messages, topics] = await Promise.all([ + this.importSessions(config.state.sessions), + this.importMessages(config.state.messages), + this.importTopics(config.state.topics), + ]); + + return { + messages: this.mapImportResult(messages), ++ sessionGroups: this.mapImportResult(sessionGroups), + sessions: this.mapImportResult(sessions), + topics: this.mapImportResult(topics), + }; + } + + case 'sessions': { ++ const sessionGroups = await this.importSessionGroups(config.state.sessionGroups); + + const [sessions, messages, topics] = await Promise.all([ + this.importSessions(config.state.sessions), + this.importMessages(config.state.messages), + this.importTopics(config.state.topics), + ]); + + return { + messages: this.mapImportResult(messages), ++ sessionGroups: this.mapImportResult(sessionGroups), + sessions: this.mapImportResult(sessions), + topics: this.mapImportResult(topics), + }; + } + } + }; +} +``` + +上述修改的一个要点是先进行 sessionGroup 的导入,因为如果先导入 session 时,如果没有在当前数据库中查到相应的 SessionGroup Id,那么这个 session 的 group 会兜底修改为默认值。这样就无法正确地将 sessionGroup 的 ID 与 session 进行关联。 + +以上就是 LobeChat Session Group 功能在数据导入导出部分的实现。通过这种方式,我们可以确保用户的 Session Group 数据在导入导出过程中能够被正确地处理。 + +## 总结 + +以上就是 LobeChat Session Group 功能的完整实现流程。开发者可以参考本文档进行相关功能的开发和测试。 diff --git a/docs/Development/State-Management/State-Management-Intro.md b/docs/Development/State-Management-Intro.md similarity index 100% rename from docs/Development/State-Management/State-Management-Intro.md rename to docs/Development/State-Management-Intro.md diff --git a/docs/Development/State-Management/State-Management-Intro.zh-CN.md b/docs/Development/State-Management-Intro.zh-CN.md similarity index 95% rename from docs/Development/State-Management/State-Management-Intro.zh-CN.md rename to docs/Development/State-Management-Intro.zh-CN.md index 9fdd53a37e6c..5310cb096b45 100644 --- a/docs/Development/State-Management/State-Management-Intro.zh-CN.md +++ b/docs/Development/State-Management-Intro.zh-CN.md @@ -125,16 +125,16 @@ src/store/session ├── helpers.ts # 辅助函数 └── slices # 各个独立的功能切片    ├── agent # 助理 Slice -    │   ├── action.ts -    │   ├── index.ts -    │   └── selectors.ts +    │   ├── action.ts +    │   ├── index.ts +    │   └── selectors.ts    └── session # 会话 Slice -       ├── action.ts -       ├── helpers.ts -       ├── initialState.ts -       └── selectors -          ├── export.ts -          ├── list.ts +       ├── action.ts +       ├── helpers.ts +       ├── initialState.ts +       └── selectors +          ├── export.ts +          ├── list.ts          └── index.ts ``` diff --git a/docs/Development/State-Management/Selectors.md b/docs/Development/State-Management-Selectors.md similarity index 100% rename from docs/Development/State-Management/Selectors.md rename to docs/Development/State-Management-Selectors.md diff --git a/docs/Development/State-Management/Selectors.zh-CN.md b/docs/Development/State-Management-Selectors.zh-CN.md similarity index 100% rename from docs/Development/State-Management/Selectors.zh-CN.md rename to docs/Development/State-Management-Selectors.zh-CN.md diff --git a/docs/Home.md b/docs/Home.md index 1c4fd37fd273..7accfe325bab 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -44,6 +44,7 @@ LobeChat is a open-source, extensible ([Function Calling][fc-url]), high-perform - [Data Store Selector](https://github.com/lobehub/lobe-chat/wiki/State-Management-Selector) | [数据存储取数模块](https://github.com/lobehub/lobe-chat/wiki/State-Management-Selector.zh-CN) - [Conversation API Implementation Logic](https://github.com/lobehub/lobe-chat/wiki/Chat-API) | [会话 API 实现逻辑](https://github.com/lobehub/lobe-chat/wiki/Chat-API.zh-CN) - [How to Develop a New Feature](https://github.com/lobehub/lobe-chat/wiki/Feature-Development) | [如何开发一个新功能](https://github.com/lobehub/lobe-chat/wiki/Feature-Development.zh-CN) + - [Frontend](https://github.com/lobehub/lobe-chat/wiki/Feature-Development-Frontend) | [前端实现](https://github.com/lobehub/lobe-chat/wiki/Feature-Development-Frontend.zh-CN) - [Internationalization Implementation Guide](https://github.com/lobehub/lobe-chat/wiki/Internationalization-Implementation) | [国际化实现指南](https://github.com/lobehub/lobe-chat/wiki/Internationalization-Implementation.zh-CN) - [New Locale Guide](https://github.com/lobehub/lobe-chat/wiki/Add-New-Locale) | [新语种添加指南](https://github.com/lobehub/lobe-chat/wiki/Add-New-Locale.zh-CN) - [Testing Guide](https://github.com/lobehub/lobe-chat/wiki/Test) | [测试指南](https://github.com/lobehub/lobe-chat/wiki/Test.zh-CN) diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index c833809bb3a8..0a0d2a54c0bf 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -32,6 +32,7 @@ - [Data Store Selector](https://github.com/lobehub/lobe-chat/wiki/State-Management-Selector) | [数据存储取数模块](https://github.com/lobehub/lobe-chat/wiki/State-Management-Selector.zh-CN) - [Conversation API Implementation Logic](https://github.com/lobehub/lobe-chat/wiki/Chat-API) | [会话 API 实现逻辑](https://github.com/lobehub/lobe-chat/wiki/Chat-API.zh-CN) - [How to Develop a New Feature](https://github.com/lobehub/lobe-chat/wiki/Feature-Development) | [如何开发一个新功能](https://github.com/lobehub/lobe-chat/wiki/Feature-Development.zh-CN) + - [Frontend](https://github.com/lobehub/lobe-chat/wiki/Feature-Development-Frontend) | [前端实现](https://github.com/lobehub/lobe-chat/wiki/Feature-Development-Frontend.zh-CN) - [Internationalization Implementation Guide](https://github.com/lobehub/lobe-chat/wiki/Internationalization-Implementation) | [国际化实现指南](https://github.com/lobehub/lobe-chat/wiki/Internationalization-Implementation.zh-CN) - [New Locale Guide](https://github.com/lobehub/lobe-chat/wiki/Add-New-Locale) | [新语种添加指南](https://github.com/lobehub/lobe-chat/wiki/Add-New-Locale.zh-CN) - [Testing Guide](https://github.com/lobehub/lobe-chat/wiki/Test) | [测试指南](https://github.com/lobehub/lobe-chat/wiki/Test.zh-CN) diff --git a/locales/ar/chat.json b/locales/ar/chat.json index 6367e3d785b6..539018b4f396 100644 --- a/locales/ar/chat.json +++ b/locales/ar/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "سيتم مسح رسائل الجلسة الحالية قريبًا، وبمجرد المسح لن يمكن استعادتها، يرجى تأكيد الإجراء الخاص بك", "confirmRemoveSessionItemAlert": "سيتم حذف هذا المساعد قريبًا، وبمجرد الحذف لن يمكن استعادته، يرجى تأكيد الإجراء الخاص بك", "defaultAgent": "المساعد الافتراضي", + "defaultList": "القائمة الافتراضية", "defaultSession": "المساعد الافتراضي", "duplicateTitle": "{{title}} نسخة", "historyRange": "نطاق التاريخ", @@ -34,7 +35,18 @@ "roleAndArchive": "الدور والأرشيف", "searchAgentPlaceholder": "البحث عن مساعد ومحادثة...", "sendPlaceholder": "أدخل محتوى الدردشة...", - "sessionList": "قائمة المساعدين", + "sessionGroup": { + "config": "إدارة المجموعات", + "confirmRemoveGroupAlert": "سيتم حذف هذه المجموعة قريبًا، وبعد الحذف، سيتم نقل مساعدي هذه المجموعة إلى القائمة الافتراضية، يرجى تأكيد إجراءك", + "createGroup": "إضافة مجموعة جديدة", + "createSuccess": "تم الإنشاء بنجاح", + "inputPlaceholder": "الرجاء إدخال اسم المجموعة...", + "moveGroup": "نقل إلى مجموعة", + "newGroup": "مجموعة جديدة", + "rename": "إعادة تسمية المجموعة", + "renameSuccess": "تمت إعادة التسمية بنجاح", + "tooLong": "يجب أن يكون طول اسم المجموعة بين 1 و 20" + }, "shareModal": { "download": "تحميل اللقطة", "imageType": "نوع الصورة", diff --git a/locales/ar/common.json b/locales/ar/common.json index 4f289ffff8c0..b7290c6c10d1 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -44,6 +44,7 @@ "added": "تمت الإضافة بنجاح", "errors": "حدثت أخطاء أثناء الاستيراد", "messages": "الرسائل", + "sessionGroups": "مجموعات الجلسة", "sessions": "الجلسات", "skips": "التخطيات", "topics": "المواضيع", diff --git a/locales/de-DE/chat.json b/locales/de-DE/chat.json index 89e224c7cda3..b83dd89d8ef9 100644 --- a/locales/de-DE/chat.json +++ b/locales/de-DE/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Möchtest du wirklich die aktuellen Nachrichten löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "confirmRemoveSessionItemAlert": "Möchtest du diesen Assistenten wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "defaultAgent": "Standardassistent", + "defaultList": "Standardliste", "defaultSession": "Standardassistent", "duplicateTitle": "{{title}} Kopie", "historyRange": "Verlaufsbereich", @@ -34,7 +35,18 @@ "roleAndArchive": "Rolle und Archiv", "searchAgentPlaceholder": "Assistenten und Unterhaltungen durchsuchen...", "sendPlaceholder": "Chat-Nachricht eingeben...", - "sessionList": "Assistentenliste", + "sessionGroup": { + "config": "Gruppenkonfiguration", + "confirmRemoveGroupAlert": "Die Gruppe wird bald gelöscht. Nach dem Löschen werden die Assistenten in die Standardliste verschoben. Bitte bestätigen Sie Ihre Aktion.", + "createGroup": "Neue Gruppe erstellen", + "createSuccess": "Erstellung erfolgreich", + "inputPlaceholder": "Geben Sie den Gruppennamen ein...", + "moveGroup": "In Gruppe verschieben", + "newGroup": "Neue Gruppe", + "rename": "Gruppe umbenennen", + "renameSuccess": "Umbenennung erfolgreich", + "tooLong": "Gruppenname muss zwischen 1 und 20 Zeichen lang sein" + }, "shareModal": { "download": "Screenshot herunterladen", "imageType": "Bildformat", diff --git a/locales/de-DE/common.json b/locales/de-DE/common.json index d9d66b5748f5..724e10b6b1bb 100644 --- a/locales/de-DE/common.json +++ b/locales/de-DE/common.json @@ -44,6 +44,7 @@ "added": "Erfolgreich importiert", "errors": "Fehler beim Import", "messages": "Nachrichten", + "sessionGroups": "Sitzungsgruppen", "sessions": "Assistenten", "skips": "Übersprungen (doppelt)", "topics": "Themen", diff --git a/locales/en-US/chat.json b/locales/en-US/chat.json index 42e7f02aeb3c..6c148749fb4c 100644 --- a/locales/en-US/chat.json +++ b/locales/en-US/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "You are about to clear the current session messages. Once cleared, they cannot be retrieved. Please confirm your action.", "confirmRemoveSessionItemAlert": "You are about to delete this agent. Once deleted, it cannot be retrieved. Please confirm your action.", "defaultAgent": "Default Agent", + "defaultList": "Default List", "defaultSession": "Default Agent", "duplicateTitle": "{{title}} Copy", "historyRange": "History Range", @@ -34,7 +35,18 @@ "roleAndArchive": "Role and Archive", "searchAgentPlaceholder": "Search agents and conversations...", "sendPlaceholder": "Type your message here...", - "sessionList": "Agent List", + "sessionGroup": { + "config": "Group Management", + "confirmRemoveGroupAlert": "This group is about to be deleted. After deletion, the assistants in this group will be moved to the default list. Please confirm your operation.", + "createGroup": "Add New Group", + "createSuccess": "Created successfully", + "inputPlaceholder": "Please enter group name...", + "moveGroup": "Move to Group", + "newGroup": "New Group", + "rename": "Rename Group", + "renameSuccess": "Renamed successfully", + "tooLong": "Group name length should be between 1-20" + }, "shareModal": { "download": "Download Screenshot", "imageType": "Image Format", diff --git a/locales/en-US/common.json b/locales/en-US/common.json index 0bb69850024c..7f39eda271b2 100644 --- a/locales/en-US/common.json +++ b/locales/en-US/common.json @@ -44,6 +44,7 @@ "added": "Imported successfully", "errors": "Import errors", "messages": "Messages", + "sessionGroups": "Groups", "sessions": "Agents", "skips": "Duplicates skipped", "topics": "Topics", diff --git a/locales/es-ES/chat.json b/locales/es-ES/chat.json index 3f170f9cf134..2d72259f73ed 100644 --- a/locales/es-ES/chat.json +++ b/locales/es-ES/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Estás a punto de borrar los mensajes de esta sesión. Una vez borrados, no se podrán recuperar. Por favor, confirma tu acción.", "confirmRemoveSessionItemAlert": "Estás a punto de eliminar este asistente. Una vez eliminado, no se podrá recuperar. Por favor, confirma tu acción.", "defaultAgent": "Asistente predeterminado", + "defaultList": "Lista predeterminada", "defaultSession": "Asistente predeterminado", "duplicateTitle": "{{title}} Copia", "historyRange": "Rango de historial", @@ -34,7 +35,18 @@ "roleAndArchive": "Rol y archivo", "searchAgentPlaceholder": "Buscar asistentes y conversaciones...", "sendPlaceholder": "Escribe tu mensaje...", - "sessionList": "Lista de asistentes", + "sessionGroup": { + "config": "Gestión de grupos", + "confirmRemoveGroupAlert": "Estás a punto de eliminar este grupo. Una vez eliminado, los asistentes de este grupo se moverán a la lista predeterminada. Por favor, confirma tu acción.", + "createGroup": "Crear nuevo grupo", + "createSuccess": "Grupo creado con éxito", + "inputPlaceholder": "Introduce el nombre del grupo...", + "moveGroup": "Mover al grupo", + "newGroup": "Nuevo grupo", + "rename": "Renombrar grupo", + "renameSuccess": "Grupo renombrado con éxito", + "tooLong": "El nombre del grupo debe tener entre 1 y 20 caracteres" + }, "shareModal": { "download": "Descargar captura de pantalla", "imageType": "Tipo de imagen", diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index c5e3818ab75c..8df4f44c717f 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -44,6 +44,7 @@ "added": "Importación exitosa", "errors": "Errores de importación", "messages": "Mensajes", + "sessionGroups": "Grupos de sesión", "sessions": "Asistentes", "skips": "Saltos de duplicados", "topics": "Temas", diff --git a/locales/fr-FR/chat.json b/locales/fr-FR/chat.json index 5bc6afeab4a0..0f31e51e323f 100644 --- a/locales/fr-FR/chat.json +++ b/locales/fr-FR/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Vous êtes sur le point d'effacer les messages de cette session. Cette action est irréversible. Veuillez confirmer.", "confirmRemoveSessionItemAlert": "Vous êtes sur le point de supprimer cet agent. Cette action est irréversible. Veuillez confirmer.", "defaultAgent": "Agent par défaut", + "defaultList": "Liste par défaut", "defaultSession": "Session par défaut", "duplicateTitle": "{{title}} Copie", "historyRange": "Plage d'historique", @@ -34,7 +35,18 @@ "roleAndArchive": "Rôle et archivage", "searchAgentPlaceholder": "Rechercher des agents et des conversations...", "sendPlaceholder": "Saisissez votre message...", - "sessionList": "Liste des agents", + "sessionGroup": { + "config": "Gestion des groupes", + "confirmRemoveGroupAlert": "Vous êtes sur le point de supprimer ce groupe. Une fois supprimé, les assistants de ce groupe seront déplacés vers la liste par défaut. Veuillez confirmer votre action.", + "createGroup": "Créer un nouveau groupe", + "createSuccess": "Création réussie", + "inputPlaceholder": "Veuillez saisir le nom du groupe...", + "moveGroup": "Déplacer vers un groupe", + "newGroup": "Nouveau groupe", + "rename": "Renommer le groupe", + "renameSuccess": "Renommage réussi", + "tooLong": "Le nom du groupe doit comporter entre 1 et 20 caractères" + }, "shareModal": { "download": "Télécharger la capture d'écran", "imageType": "Type d'image", diff --git a/locales/fr-FR/common.json b/locales/fr-FR/common.json index 97685ba96f74..d47aee87d00d 100644 --- a/locales/fr-FR/common.json +++ b/locales/fr-FR/common.json @@ -44,6 +44,7 @@ "added": "Importation réussie", "errors": "Erreurs d'importation", "messages": "Messages", + "sessionGroups": "Groupes de session", "sessions": "Agents", "skips": "Éléments ignorés en double", "topics": "Sujets", diff --git a/locales/it-IT/chat.json b/locales/it-IT/chat.json index 79deea61cd5a..875e7f305751 100644 --- a/locales/it-IT/chat.json +++ b/locales/it-IT/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Stai per cancellare i messaggi attuali, questa operazione non potrà essere annullata. Confermi?", "confirmRemoveSessionItemAlert": "Stai per rimuovere questo assistente, l'operazione non potrà essere annullata. Confermi?", "defaultAgent": "Assistente predefinito", + "defaultList": "Lista predefinita", "defaultSession": "Sessione predefinita", "duplicateTitle": "{{title}} Copia", "historyRange": "Intervallo cronologico", @@ -34,7 +35,18 @@ "roleAndArchive": "Ruolo e archivio", "searchAgentPlaceholder": "Cerca assistente e conversazioni...", "sendPlaceholder": "Inserisci il testo della chat...", - "sessionList": "Elenco sessioni", + "sessionGroup": { + "config": "Gestione gruppi", + "confirmRemoveGroupAlert": "Stai per rimuovere questo gruppo. Dopo la rimozione, gli assistenti di questo gruppo verranno spostati nella lista predefinita. Confermi l'operazione?", + "createGroup": "Aggiungi nuovo gruppo", + "createSuccess": "Creazione riuscita", + "inputPlaceholder": "Inserisci il nome del gruppo...", + "moveGroup": "Sposta nel gruppo", + "newGroup": "Nuovo gruppo", + "rename": "Rinomina gruppo", + "renameSuccess": "Rinominazione riuscita", + "tooLong": "Il nome del gruppo deve essere lungo 1-20 caratteri" + }, "shareModal": { "download": "Scarica screenshot", "imageType": "Tipo di immagine", diff --git a/locales/it-IT/common.json b/locales/it-IT/common.json index 7ce69cbb8628..7e4de01f1451 100644 --- a/locales/it-IT/common.json +++ b/locales/it-IT/common.json @@ -44,6 +44,7 @@ "added": "Importazione riuscita", "errors": "Errori di importazione", "messages": "Messaggi", + "sessionGroups": "Gruppi di sessione", "sessions": "Sessioni", "skips": "Elementi saltati", "topics": "Argomenti", diff --git a/locales/ja-JP/chat.json b/locales/ja-JP/chat.json index b0143996dcb9..bd0d412e3af3 100644 --- a/locales/ja-JP/chat.json +++ b/locales/ja-JP/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "現在の会話をクリアします。クリアした後は元に戻すことはできません。操作を確認してください。", "confirmRemoveSessionItemAlert": "このエージェントを削除します。削除した後は元に戻すことはできません。操作を確認してください。", "defaultAgent": "デフォルトエージェント", + "defaultList": "デフォルトリスト", "defaultSession": "デフォルトセッション", "duplicateTitle": "{{title}} のコピー", "historyRange": "履歴範囲", @@ -34,7 +35,18 @@ "roleAndArchive": "役割とアーカイブ", "searchAgentPlaceholder": "エージェントや会話を検索...", "sendPlaceholder": "チャット内容を入力してください...", - "sessionList": "エージェントリスト", + "sessionGroup": { + "config": "グループ設定", + "confirmRemoveGroupAlert": "このグループを削除します。削除後、このグループのアシスタントはデフォルトリストに移動されます。操作を確認してください。", + "createGroup": "新しいグループを作成", + "createSuccess": "作成が成功しました", + "inputPlaceholder": "グループ名を入力してください...", + "moveGroup": "グループに移動", + "newGroup": "新しいグループ", + "rename": "グループ名を変更", + "renameSuccess": "名前の変更が成功しました", + "tooLong": "グループ名は1〜20文字で入力してください" + }, "shareModal": { "download": "スクリーンショットをダウンロード", "imageType": "画像形式", diff --git a/locales/ja-JP/common.json b/locales/ja-JP/common.json index a5ab5ae4e72d..d4fb6ccc522e 100644 --- a/locales/ja-JP/common.json +++ b/locales/ja-JP/common.json @@ -44,6 +44,7 @@ "added": "インポートが成功しました", "errors": "インポートエラー", "messages": "メッセージ", + "sessionGroups": "セッショングループ", "sessions": "セッション", "skips": "重複スキップ", "topics": "トピック", diff --git a/locales/ko-KR/chat.json b/locales/ko-KR/chat.json index c8f7f1cfcd8c..c2eafa9922d6 100644 --- a/locales/ko-KR/chat.json +++ b/locales/ko-KR/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "현재 대화를 지우시면 되돌릴 수 없습니다. 작업을 확인하시겠습니까?", "confirmRemoveSessionItemAlert": "이 도우미를 삭제하시면 되돌릴 수 없습니다. 작업을 확인하시겠습니까?", "defaultAgent": "기본 도우미", + "defaultList": "기본 목록", "defaultSession": "기본 도우미", "duplicateTitle": "{{title}} 복사본", "historyRange": "대화 기록 범위", @@ -34,7 +35,18 @@ "roleAndArchive": "역할 및 아카이브", "searchAgentPlaceholder": "도우미 및 대화 검색...", "sendPlaceholder": "채팅 내용 입력...", - "sessionList": "도우미 목록", + "sessionGroup": { + "config": "그룹 설정", + "confirmRemoveGroupAlert": "이 그룹을 삭제하려고 합니다. 삭제 후 이 그룹의 도우미는 기본 목록으로 이동됩니다. 작업을 확인하십시오.", + "createGroup": "새 그룹 추가", + "createSuccess": "생성 성공", + "inputPlaceholder": "그룹 이름을 입력하세요...", + "moveGroup": "그룹으로 이동", + "newGroup": "새 그룹", + "rename": "그룹 이름 변경", + "renameSuccess": "이름 변경 성공", + "tooLong": "그룹 이름은 1-20자여야 합니다" + }, "shareModal": { "download": "스크린샷 다운로드", "imageType": "이미지 형식", diff --git a/locales/ko-KR/common.json b/locales/ko-KR/common.json index b3fc0d1c7872..1ab0d9b17e7f 100644 --- a/locales/ko-KR/common.json +++ b/locales/ko-KR/common.json @@ -44,6 +44,7 @@ "added": "가져오기 성공", "errors": "가져오기 오류", "messages": "메시지", + "sessionGroups": "세션 그룹", "sessions": "에이전트", "skips": "중복 건너뛰기", "topics": "주제", diff --git a/locales/nl-NL/chat.json b/locales/nl-NL/chat.json index dc59cc4cd4b9..4dad69d748ec 100644 --- a/locales/nl-NL/chat.json +++ b/locales/nl-NL/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Huidige berichten worden gewist en kunnen niet worden hersteld. Bevestig je actie.", "confirmRemoveSessionItemAlert": "Deze assistent wordt verwijderd en kan niet worden hersteld. Bevestig je actie.", "defaultAgent": "Standaard assistent", + "defaultList": "Standaardlijst", "defaultSession": "Standaard assistent", "duplicateTitle": "{{title}} Kopie", "historyRange": "Geschiedenisbereik", @@ -34,7 +35,18 @@ "roleAndArchive": "Rol en archief", "searchAgentPlaceholder": "Zoek assistenten en gesprekken...", "sendPlaceholder": "Voer chatbericht in...", - "sessionList": "Lijst met assistenten", + "sessionGroup": { + "config": "Groepsbeheer", + "confirmRemoveGroupAlert": "Je staat op het punt deze groep te verwijderen. Na verwijdering zullen de assistenten van deze groep worden verplaatst naar de standaardlijst. Bevestig je actie.", + "createGroup": "Nieuwe groep toevoegen", + "createSuccess": "Succesvol aangemaakt", + "inputPlaceholder": "Voer de naam van de groep in...", + "moveGroup": "Verplaatsen naar groep", + "newGroup": "Nieuwe groep", + "rename": "Groepsnaam wijzigen", + "renameSuccess": "Naam succesvol gewijzigd", + "tooLong": "De groepsnaam moet tussen 1 en 20 tekens lang zijn" + }, "shareModal": { "download": "Screenshot downloaden", "imageType": "Afbeeldingstype", diff --git a/locales/nl-NL/common.json b/locales/nl-NL/common.json index 977a271c2bdc..2f650bcde44e 100644 --- a/locales/nl-NL/common.json +++ b/locales/nl-NL/common.json @@ -44,6 +44,7 @@ "added": "Succesvol geïmporteerd", "errors": "Fouten bij importeren", "messages": "Berichten", + "sessionGroups": "Sessiegroepen", "sessions": "Assistenten", "skips": "Overslaan van duplicaten", "topics": "Onderwerpen", diff --git a/locales/pl-PL/chat.json b/locales/pl-PL/chat.json index 4c995b265932..9538f0091778 100644 --- a/locales/pl-PL/chat.json +++ b/locales/pl-PL/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Czy na pewno chcesz wyczyścić bieżącą rozmowę? Tej operacji nie można cofnąć.", "confirmRemoveSessionItemAlert": "Czy na pewno chcesz usunąć tego asystenta? Tej operacji nie można cofnąć.", "defaultAgent": "Domyślny asystent", + "defaultList": "Domyślna lista", "defaultSession": "Domyślna sesja", "duplicateTitle": "{{title}} kopia", "historyRange": "Zakres historii", @@ -34,7 +35,18 @@ "roleAndArchive": "Rola i archiwum", "searchAgentPlaceholder": "Szukaj asystentów i rozmów...", "sendPlaceholder": "Wpisz treść rozmowy...", - "sessionList": "Lista sesji", + "sessionGroup": { + "config": "Zarządzanie grupami", + "confirmRemoveGroupAlert": "Czy na pewno chcesz usunąć tę grupę? Po usunięciu asystenci z tej grupy zostaną przeniesieni do domyślnej listy. Potwierdź swoje działanie.", + "createGroup": "Dodaj nową grupę", + "createSuccess": "Utworzono pomyślnie", + "inputPlaceholder": "Wprowadź nazwę grupy...", + "moveGroup": "Przenieś do grupy", + "newGroup": "Nowa grupa", + "rename": "Zmień nazwę grupy", + "renameSuccess": "Zmiana nazwy pomyślna", + "tooLong": "Nazwa grupy musi mieć od 1 do 20 znaków" + }, "shareModal": { "download": "Pobierz zrzut ekranu", "imageType": "Typ obrazu", diff --git a/locales/pl-PL/common.json b/locales/pl-PL/common.json index 7bca85279c0e..8eec4732739e 100644 --- a/locales/pl-PL/common.json +++ b/locales/pl-PL/common.json @@ -44,6 +44,7 @@ "added": "Pomyślnie zaimportowano", "errors": "Błędy importu", "messages": "Wiadomości", + "sessionGroups": "Grupy sesji", "sessions": "Sesje", "skips": "Pominięcia duplikatów", "topics": "Tematy", diff --git a/locales/pt-BR/chat.json b/locales/pt-BR/chat.json index 977772784264..b680c7a0ac13 100644 --- a/locales/pt-BR/chat.json +++ b/locales/pt-BR/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Você está prestes a limpar as mensagens desta sessão. Depois de limpar, não será possível recuperá-las. Por favor, confirme sua ação.", "confirmRemoveSessionItemAlert": "Você está prestes a remover este assistente. Depois de remover, não será possível recuperá-lo. Por favor, confirme sua ação.", "defaultAgent": "Assistente Padrão", + "defaultList": "Lista padrão", "defaultSession": "Sessão Padrão", "duplicateTitle": "{{title}} Cópia", "historyRange": "Intervalo de Histórico", @@ -34,7 +35,18 @@ "roleAndArchive": "Função e Arquivo", "searchAgentPlaceholder": "Pesquisar assistentes e conversas...", "sendPlaceholder": "Digite a mensagem...", - "sessionList": "Lista de Assistentes", + "sessionGroup": { + "config": "Gerenciar grupos", + "confirmRemoveGroupAlert": "Você está prestes a excluir este grupo. Após a exclusão, os assistentes deste grupo serão movidos para a lista padrão. Por favor, confirme sua operação.", + "createGroup": "Criar novo grupo", + "createSuccess": "Criado com sucesso", + "inputPlaceholder": "Digite o nome do grupo...", + "moveGroup": "Mover para o grupo", + "newGroup": "Novo grupo", + "rename": "Renomear grupo", + "renameSuccess": "Renomeado com sucesso", + "tooLong": "O nome do grupo deve ter entre 1 e 20 caracteres" + }, "shareModal": { "download": "Baixar Captura de Tela", "imageType": "Tipo de Imagem", diff --git a/locales/pt-BR/common.json b/locales/pt-BR/common.json index fd791df2d0fc..d3f28461cd80 100644 --- a/locales/pt-BR/common.json +++ b/locales/pt-BR/common.json @@ -44,6 +44,7 @@ "added": "Importação bem-sucedida", "errors": "Erros na importação", "messages": "Mensagens", + "sessionGroups": "Grupos de sessão", "sessions": "Assistentes", "skips": "Ignorados", "topics": "Tópicos", diff --git a/locales/ru-RU/chat.json b/locales/ru-RU/chat.json index 973ac35fa46d..8ff9cea690a9 100644 --- a/locales/ru-RU/chat.json +++ b/locales/ru-RU/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Вы уверены, что хотите очистить текущий разговор? После этого его нельзя будет восстановить.", "confirmRemoveSessionItemAlert": "Вы уверены, что хотите удалить этого помощника? После этого его нельзя будет восстановить.", "defaultAgent": "Пользовательский помощник", + "defaultList": "Список по умолчанию", "defaultSession": "Пользовательский помощник", "duplicateTitle": "{{title}} Копия", "historyRange": "История сообщений", @@ -34,7 +35,18 @@ "roleAndArchive": "Роль и архив", "searchAgentPlaceholder": "Поиск помощников и разговоров...", "sendPlaceholder": "Введите сообщение...", - "sessionList": "Список помощников", + "sessionGroup": { + "config": "Управление группами", + "confirmRemoveGroupAlert": "Вы уверены, что хотите удалить эту группу? После удаления помощники из этой группы будут перемещены в список по умолчанию.", + "createGroup": "Создать новую группу", + "createSuccess": "Создание успешно", + "inputPlaceholder": "Введите название группы...", + "moveGroup": "Переместить в группу", + "newGroup": "Новая группа", + "rename": "Переименовать группу", + "renameSuccess": "Переименование успешно", + "tooLong": "Название группы должно содержать от 1 до 20 символов" + }, "shareModal": { "download": "Скачать скриншот", "imageType": "Тип изображения", diff --git a/locales/ru-RU/common.json b/locales/ru-RU/common.json index 78a26848df77..c43885218c6b 100644 --- a/locales/ru-RU/common.json +++ b/locales/ru-RU/common.json @@ -44,6 +44,7 @@ "added": "Успешно импортировано", "errors": "Ошибка импорта", "messages": "Сообщения", + "sessionGroups": "Группы сессий", "sessions": "Агенты", "skips": "Пропущено дубликатов", "topics": "Темы", diff --git a/locales/tr-TR/chat.json b/locales/tr-TR/chat.json index aefe6af1d2ea..6689c775d413 100644 --- a/locales/tr-TR/chat.json +++ b/locales/tr-TR/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Mevcut oturum mesajlarını temizlemek üzeresiniz. Temizlendikten sonra geri alınamazlar. Lütfen eyleminizi onaylayın.", "confirmRemoveSessionItemAlert": "Bu asistanı silmek üzeresiniz. Silindikten sonra geri alınamaz. Lütfen eyleminizi onaylayın.", "defaultAgent": "Varsayılan Asistan", + "defaultList": "Varsayılan Liste", "defaultSession": "Varsayılan Asistan", "duplicateTitle": "{{title}} Kopya", "historyRange": "Geçmiş Aralığı", @@ -34,7 +35,18 @@ "roleAndArchive": "Rol ve Arşiv", "searchAgentPlaceholder": "Arama yardımcıları ve konuşmalar...", "sendPlaceholder": "Mesajınızı buraya yazın...", - "sessionList": "Asistanlar", + "sessionGroup": { + "config": "Grup Yönetimi", + "confirmRemoveGroupAlert": "Bu grup silinecek, silindikten sonra bu grubun yardımcıları varsayılan listeye taşınacak, işleminizi onaylıyor musunuz?", + "createGroup": "Yeni Grup Ekle", + "createSuccess": "Oluşturma Başarılı", + "inputPlaceholder": "Grup adını girin...", + "moveGroup": "Gruba Taşı", + "newGroup": "Yeni Grup", + "rename": "Grup Adını Değiştir", + "renameSuccess": "Yeniden Adlandırma Başarılı", + "tooLong": "Grup adı 1-20 karakter arasında olmalıdır" + }, "shareModal": { "download": "Ekran Görüntüsünü İndir", "imageType": "Format", diff --git a/locales/tr-TR/common.json b/locales/tr-TR/common.json index afffc50f4660..2f4f7fbcd1eb 100644 --- a/locales/tr-TR/common.json +++ b/locales/tr-TR/common.json @@ -44,6 +44,7 @@ "added": "Başarıyla içe aktarıldı", "errors": "İçe Aktarma Hataları", "messages": "Mesajlar", + "sessionGroups": "Gruplar", "sessions": "Asistanlar", "skips": "Geç", "topics": "Konular", diff --git a/locales/vi-VN/chat.json b/locales/vi-VN/chat.json index a2ce0287346e..52730035f61b 100644 --- a/locales/vi-VN/chat.json +++ b/locales/vi-VN/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "Bạn sắp xóa tin nhắn hiện tại. Hành động này không thể hoàn tác, vui lòng xác nhận.", "confirmRemoveSessionItemAlert": "Bạn sắp xóa trợ lý này. Hành động này không thể hoàn tác, vui lòng xác nhận.", "defaultAgent": "Trợ lý mặc định", + "defaultList": "Danh sách mặc định", "defaultSession": "Trợ lý mặc định", "duplicateTitle": "{{title}} Bản sao", "historyRange": "Phạm vi lịch sử", @@ -34,7 +35,18 @@ "roleAndArchive": "Vai trò và lưu trữ", "searchAgentPlaceholder": "Tìm kiếm trợ lý và cuộc trò chuyện...", "sendPlaceholder": "Nhập nội dung trò chuyện...", - "sessionList": "Danh sách trợ lý", + "sessionGroup": { + "config": "Quản lý nhóm", + "confirmRemoveGroupAlert": "Bạn sẽ xóa nhóm này, sau khi xóa, trợ lý của nhóm sẽ được di chuyển vào danh sách mặc định, vui lòng xác nhận hành động của bạn", + "createGroup": "Thêm nhóm mới", + "createSuccess": "Tạo thành công", + "inputPlaceholder": "Vui lòng nhập tên nhóm...", + "moveGroup": "Di chuyển vào nhóm", + "newGroup": "Nhóm mới", + "rename": "Đổi tên nhóm", + "renameSuccess": "Đổi tên thành công", + "tooLong": "Tên nhóm phải có độ dài từ 1-20 ký tự" + }, "shareModal": { "download": "Tải xuống ảnh chụp màn hình", "imageType": "Định dạng ảnh", diff --git a/locales/vi-VN/common.json b/locales/vi-VN/common.json index 1c254c541f45..a8bd44dc6954 100644 --- a/locales/vi-VN/common.json +++ b/locales/vi-VN/common.json @@ -44,6 +44,7 @@ "added": "Nhập thành công", "errors": "Lỗi nhập", "messages": "Tin nhắn", + "sessionGroups": "Nhóm phiên", "sessions": "Trợ lý", "skips": "Bỏ qua trùng lặp", "topics": "Chủ đề", diff --git a/locales/zh-CN/chat.json b/locales/zh-CN/chat.json index ebea6f355981..260a6b0e450f 100644 --- a/locales/zh-CN/chat.json +++ b/locales/zh-CN/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "即将清空当前会话消息,清空后将无法找回,请确认你的操作", "confirmRemoveSessionItemAlert": "即将删除该助手,删除后该将无法找回,请确认你的操作", "defaultAgent": "自定义助手", + "defaultList": "默认列表", "defaultSession": "自定义助手", "duplicateTitle": "{{title}} 副本", "historyRange": "历史范围", @@ -34,7 +35,18 @@ "roleAndArchive": "角色与记录", "searchAgentPlaceholder": "搜索助手和对话...", "sendPlaceholder": "输入聊天内容...", - "sessionList": "助手列表", + "sessionGroup": { + "config": "分组管理", + "confirmRemoveGroupAlert": "即将删除该分组,删除后该分组的助手将移动到默认列表,请确认你的操作", + "createGroup": "添加新分组", + "createSuccess": "创建成功", + "inputPlaceholder": "请输入分组名称...", + "moveGroup": "移动到分组", + "newGroup": "新分组", + "rename": "重命名分组", + "renameSuccess": "重命名成功", + "tooLong": "分组名称长度需在 1-20 之内" + }, "shareModal": { "download": "下载截图", "imageType": "图片格式", diff --git a/locales/zh-CN/common.json b/locales/zh-CN/common.json index aae1295dabcb..aab3a17206bb 100644 --- a/locales/zh-CN/common.json +++ b/locales/zh-CN/common.json @@ -44,6 +44,7 @@ "added": "导入成功", "errors": "导入出错", "messages": "消息", + "sessionGroups": "分组", "sessions": "助手", "skips": "重复跳过", "topics": "话题", diff --git a/locales/zh-TW/chat.json b/locales/zh-TW/chat.json index f6fb06d2b83a..2ed6a11c37a5 100644 --- a/locales/zh-TW/chat.json +++ b/locales/zh-TW/chat.json @@ -6,6 +6,7 @@ "confirmClearCurrentMessages": "即將清空當前對話,清空後將無法找回,請確認你的操作", "confirmRemoveSessionItemAlert": "即將刪除該助手,刪除後將無法找回,請確認你的操作", "defaultAgent": "自定義助手", + "defaultList": "預設清單", "defaultSession": "自定義助手", "duplicateTitle": "{{title}} 副本", "historyRange": "歷史範圍", @@ -34,7 +35,18 @@ "roleAndArchive": "角色與記錄", "searchAgentPlaceholder": "搜索助手和對話...", "sendPlaceholder": "輸入聊天內容...", - "sessionList": "助手列表", + "sessionGroup": { + "config": "分組管理", + "confirmRemoveGroupAlert": "即將刪除該分組,刪除後該分組的助手將移動到預設清單,請確認你的操作", + "createGroup": "新增新分組", + "createSuccess": "創建成功", + "inputPlaceholder": "請輸入分組名稱...", + "moveGroup": "移動到分組", + "newGroup": "新分組", + "rename": "重新命名分組", + "renameSuccess": "重新命名成功", + "tooLong": "分組名稱長度需在 1-20 之內" + }, "shareModal": { "download": "下載截圖", "imageType": "圖片格式", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 73997da24ab7..e5576c8156eb 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -44,6 +44,7 @@ "added": "匯入成功", "errors": "匯入出錯", "messages": "訊息", + "sessionGroups": "會話分組", "sessions": "助手", "skips": "重複跳過", "topics": "話題", diff --git a/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx b/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx index ae5b09505418..75417a8c39c3 100644 --- a/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +++ b/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx @@ -4,12 +4,13 @@ import { createStyles } from 'antd-style'; import { ChevronUp, CornerDownLeft, - Loader2, LucideCheck, LucideChevronDown, LucideCommand, LucidePlus, + StopCircle, } from 'lucide-react'; +import { rgba } from 'polished'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Center, Flexbox } from 'react-layout-kit'; @@ -25,7 +26,7 @@ import { isMacOS } from '@/utils/platform'; import { LocalFiles } from './LocalFiles'; -const useStyles = createStyles(({ css, prefixCls }) => { +const useStyles = createStyles(({ css, prefixCls, token }) => { return { arrow: css` &.${prefixCls}-btn.${prefixCls}-btn-icon-only { @@ -38,13 +39,19 @@ const useStyles = createStyles(({ css, prefixCls }) => { align-items: center; justify-content: center; } + + .${prefixCls}-btn.${prefixCls}-dropdown-trigger { + &::before { + background-color: ${rgba(token.colorBgLayout, 0.1)} !important; + } + } `, }; }); const isMac = isMacOS(); -const Footer = memo(() => { +const Footer = memo<{ setExpand?: (expand: boolean) => void }>(({ setExpand }) => { const { t } = useTranslation('chat'); const { theme, styles } = useStyles(); @@ -104,12 +111,18 @@ const Footer = memo(() => { {loading ? ( - ) : ( - { >