@lobehub/chat 0.162.1 → 0.162.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +3 -3
- package/README.zh-CN.md +8 -8
- package/contributing/Basic/Feature-Development.md +11 -11
- package/contributing/Basic/Feature-Development.zh-CN.md +10 -10
- package/contributing/Basic/Intro.md +81 -13
- package/contributing/Basic/Intro.zh-CN.md +3 -0
- package/docs/self-hosting/advanced/upstream-sync.mdx +5 -5
- package/package.json +1 -1
- package/src/app/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +2 -1
- package/src/app/(main)/chat/settings/features/EditPage.tsx +1 -5
- package/src/app/@modal/chat/(.)settings/modal/layout.tsx +1 -5
- package/src/features/AgentSetting/StoreUpdater.tsx +2 -3
- package/src/features/AgentSetting/store/action.ts +3 -6
- package/src/features/AgentSetting/store/initialState.ts +1 -2
- package/src/layout/GlobalProvider/StoreInitialization.tsx +20 -17
- package/src/store/agent/slices/chat/action.test.ts +22 -20
- package/src/store/agent/slices/chat/action.ts +35 -35
- package/src/store/agent/slices/chat/initialState.ts +4 -6
- package/src/store/agent/slices/chat/selectors.test.ts +53 -49
- package/src/store/agent/slices/chat/selectors.ts +11 -7
- package/src/store/chat/slices/message/action.test.ts +8 -4
- package/src/store/chat/slices/message/selectors.test.ts +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.162.2](https://github.com/lobehub/lobe-chat/compare/v0.162.1...v0.162.2)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-05-28**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor agent store data.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Refactor agent store data, closes [#2690](https://github.com/lobehub/lobe-chat/issues/2690) ([e201937](https://github.com/lobehub/lobe-chat/commit/e201937))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 0.162.1](https://github.com/lobehub/lobe-chat/compare/v0.162.0...v0.162.1)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2024-05-27**</sup>
|
package/README.md
CHANGED
|
@@ -264,12 +264,12 @@ Our marketplace is not just a showcase platform but also a collaborative space.
|
|
|
264
264
|
|
|
265
265
|
| Recent Submits | Description |
|
|
266
266
|
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
267
|
+
| [Foreign Colleague Evaluation Assistant](https://chat-preview.lobehub.com/market?agent=praise-assistant)<br/><sup>By **[johnnyqian](https://github.com/johnnyqian)** on **2024-05-27**</sup> | Give positive feedback to your colleagues<br/>`foreign-company` `evaluate` `review` `software-engineer` `praise` |
|
|
268
|
+
| [SEO Optimization Expert](https://chat-preview.lobehub.com/market?agent=seo-helper)<br/><sup>By **[tutorial0](https://github.com/tutorial0)** on **2024-05-27**</sup> | Proficient in SEO terminology and optimization strategies, providing comprehensive SEO solutions and practical advice.<br/>`seo` `search-engine-optimization` `consulting` |
|
|
267
269
|
| [Chinese Text Refinement Master](https://chat-preview.lobehub.com/market?agent=chinese-touch-ups)<br/><sup>By **[S45618](https://github.com/S45618)** on **2024-05-24**</sup> | Proficient in Chinese proofreading and rhetoric, aiming to enhance the fluency and elegance of the text.<br/>`proofreading` `text-refinement` `rhetorical-improvement` `classical-literature` `language-editing` |
|
|
268
270
|
| [IT Systems Architect](https://chat-preview.lobehub.com/market?agent=it-system-architect)<br/><sup>By **[a562314](https://github.com/a562314)** on **2024-05-24**</sup> | Senior IT architect specializing in requirements analysis, system design, technology selection, and cross-platform system optimization. With over 5 years of experience, holding a bachelor's degree in computer science, proficient in Windows, macOS, and Linux operating systems, skilled in teamwork, continuous learning, troubleshooting, and security protection.<br/>`it-architecture-design` `problem-solving` `agile-development` `system-optimization` `cross-platform-skills` `teamwork` |
|
|
269
|
-
| [Minecraft Command Instructor](https://chat-preview.lobehub.com/market?agent=mcse-helper)<br/><sup>By **[CLOT-LIU](https://github.com/CLOT-LIU)** on **2024-05-24**</sup> | Specializes in explaining and demonstrating Minecraft commands<br/>`minecraft` `commands` `explanation` `examples` |
|
|
270
|
-
| [Philosophical Analysis Assistant](https://chat-preview.lobehub.com/market?agent=philosophical-analysis)<br/><sup>By **[epochaudio](https://github.com/epochaudio)** on **2024-05-24**</sup> | Specializes in consulting on Kant and Hegel's philosophical analysis, cultivating critical thinking<br/>`philosophical-analysis` `critical-thinking` `systematic-thinking` |
|
|
271
271
|
|
|
272
|
-
> 📊 Total agents: [<kbd>**
|
|
272
|
+
> 📊 Total agents: [<kbd>**275**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
273
273
|
|
|
274
274
|
<!-- AGENT LIST -->
|
|
275
275
|
|
package/README.zh-CN.md
CHANGED
|
@@ -250,14 +250,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
|
|
250
250
|
|
|
251
251
|
<!-- AGENT LIST -->
|
|
252
252
|
|
|
253
|
-
| 最近新增
|
|
254
|
-
|
|
|
255
|
-
| [
|
|
256
|
-
| [
|
|
257
|
-
| [
|
|
258
|
-
| [
|
|
259
|
-
|
|
260
|
-
> 📊 Total agents: [<kbd>**
|
|
253
|
+
| 最近新增 | 助手说明 |
|
|
254
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
255
|
+
| [外企同事评价助手](https://chat-preview.lobehub.com/market?agent=praise-assistant)<br/><sup>By **[johnnyqian](https://github.com/johnnyqian)** on **2024-05-27**</sup> | 给你的同事好评<br/>`foreign-company` `evaluate` `review` `software-engineer` `praise` |
|
|
256
|
+
| [SEO 优化专家](https://chat-preview.lobehub.com/market?agent=seo-helper)<br/><sup>By **[tutorial0](https://github.com/tutorial0)** on **2024-05-27**</sup> | 精通 SEO 术语和优化策略,提供全面 SEO 解决方案和实用建议。<br/>`seo` `搜索引擎优化` `咨询` |
|
|
257
|
+
| [中文润色大师](https://chat-preview.lobehub.com/market?agent=chinese-touch-ups)<br/><sup>By **[S45618](https://github.com/S45618)** on **2024-05-24**</sup> | 精通中文校对与修辞,旨在提升文本之流畅与雅致<br/>`校对` `文字润色` `修辞改进` `古典文学` `语言编辑` |
|
|
258
|
+
| [IT 系统架构师](https://chat-preview.lobehub.com/market?agent=it-system-architect)<br/><sup>By **[a562314](https://github.com/a562314)** on **2024-05-24**</sup> | 资深 IT 架构师,擅长需求分析、系统设计、技术选型和跨平台系统优化。5 年以上经验,具备计算机科学本科学历,精通 Windows、macOS 和 Linux 三大操作系统,善于团队合作,持续学习,具备故障排除和安全防护能力。<br/>`it架构设计` `问题解决` `敏捷开发` `系统优化` `跨平台技能` `团队合作` |
|
|
259
|
+
|
|
260
|
+
> 📊 Total agents: [<kbd>**275**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
261
261
|
|
|
262
262
|
<!-- AGENT LIST -->
|
|
263
263
|
|
|
@@ -4,14 +4,14 @@ This document aims to guide developers on how to develop a complete feature requ
|
|
|
4
4
|
|
|
5
5
|
We will use the implementation of sessionGroup as an example: [✨ feat: add session group manager](https://github.com/lobehub/lobe-chat/pull/1055), and explain the complete implementation process through the following six main sections:
|
|
6
6
|
|
|
7
|
-
1. Data Model / Database Definition
|
|
8
|
-
2. Service Implementation / Model Implementation
|
|
9
|
-
3. Frontend Data Flow Store Implementation
|
|
10
|
-
4. UI Implementation and Action Binding
|
|
11
|
-
5. Data Migration
|
|
12
|
-
6. Data Import and Export
|
|
7
|
+
1. [Data Model / Database Definition](#1-data-model--database-definition)
|
|
8
|
+
2. [Service Implementation / Model Implementation](#2-service-implementation--model-implementation)
|
|
9
|
+
3. [Frontend Data Flow Store Implementation](#3-frontend-data-flow-store-implementation)
|
|
10
|
+
4. [UI Implementation and Action Binding](#4-ui-implementation-and-action-binding)
|
|
11
|
+
5. [Data Migration](#5-data-migration)
|
|
12
|
+
6. [Data Import and Export](#6-data-import-and-export)
|
|
13
13
|
|
|
14
|
-
## 1. Database
|
|
14
|
+
## 1. Data Model / Database Definition
|
|
15
15
|
|
|
16
16
|
To implement the Session Group feature, it is necessary to define the relevant data model and indexes at the database level.
|
|
17
17
|
|
|
@@ -119,7 +119,7 @@ As a result, you can now view the `sessionGroups` table in the `LOBE_CHAT_DB` in
|
|
|
119
119
|
|
|
120
120
|

|
|
121
121
|
|
|
122
|
-
## 2.
|
|
122
|
+
## 2. Service Implementation / Model Implementation
|
|
123
123
|
|
|
124
124
|
### Define Model
|
|
125
125
|
|
|
@@ -176,7 +176,7 @@ class SessionService {
|
|
|
176
176
|
}
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
## 3. Store
|
|
179
|
+
## 3. Frontend Data Flow Store Implementation
|
|
180
180
|
|
|
181
181
|
In the LobeChat application, the Store module is used to manage the frontend state of the application. The Actions within it are functions that trigger state updates, usually by calling methods in the service layer to perform actual data processing operations and then updating the state in the Store. We use `zustand` as the underlying dependency for the Store module. For a detailed practical introduction to state management, you can refer to [📘 Best Practices for State Management](../State-Management/State-Management-Intro.zh-CN.md).
|
|
182
182
|
|
|
@@ -351,7 +351,7 @@ Since all data retrieval in the UI is implemented using syntax like `useSessionS
|
|
|
351
351
|
>
|
|
352
352
|
> If you are not familiar with the concept and functionality of selectors, you can refer to the section [📘 Data Storage and Retrieval Module](./State-Management-Selectors.en-US) for relevant information.
|
|
353
353
|
|
|
354
|
-
##
|
|
354
|
+
## 4. UI Implementation and Action Binding
|
|
355
355
|
|
|
356
356
|
Bind Store Action in the UI component to implement interactive logic, for example `CreateGroupModal`:
|
|
357
357
|
|
|
@@ -570,7 +570,7 @@ export class LocalDB extends Dexie {
|
|
|
570
570
|
|
|
571
571
|
This is our data migration strategy. When performing the migration, it is essential to ensure the correctness of the migration script and validate the migration results through thorough testing.
|
|
572
572
|
|
|
573
|
-
##
|
|
573
|
+
## 6. Data Import and Export
|
|
574
574
|
|
|
575
575
|
In LobeChat, the data import and export feature is designed to ensure that users can migrate their data between different devices. This includes session, topic, message, and settings data. In the implementation of the Session Group feature, we also need to handle data import and export to ensure that the complete exported data can be restored exactly the same on other devices.
|
|
576
576
|
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
我们将以 sessionGroup 的实现为示例:[✨ feat: add session group manager](https://github.com/lobehub/lobe-chat/pull/1055) , 通过以下六个主要部分来阐述完整的实现流程:
|
|
6
6
|
|
|
7
|
-
1. 数据模型 / 数据库定义
|
|
8
|
-
2. Service 实现 / Model 实现
|
|
9
|
-
3. 前端数据流 Store 实现
|
|
10
|
-
4. UI 实现与 action 绑定
|
|
11
|
-
5. 数据迁移
|
|
12
|
-
6. 数据导入导出
|
|
7
|
+
1. [数据模型 / 数据库定义](#一数据模型--数据库定义)
|
|
8
|
+
2. [Service 实现 / Model 实现](#二service-实现--model-实现)
|
|
9
|
+
3. [前端数据流 Store 实现](#三前端数据流-store-实现)
|
|
10
|
+
4. [UI 实现与 action 绑定](#四ui-实现与-action-绑定)
|
|
11
|
+
5. [数据迁移](#五数据迁移)
|
|
12
|
+
6. [数据导入导出](#六数据导入导出)
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 一、数据模型 / 数据库定义
|
|
15
15
|
|
|
16
16
|
为了实现 Session Group 功能,首先需要在数据库层面定义相关的数据模型和索引。
|
|
17
17
|
|
|
@@ -119,7 +119,7 @@ export class LocalDB extends Dexie {
|
|
|
119
119
|
|
|
120
120
|

|
|
121
121
|
|
|
122
|
-
## 二、
|
|
122
|
+
## 二、Service 实现 / Model 实现
|
|
123
123
|
|
|
124
124
|
### 定义 Model
|
|
125
125
|
|
|
@@ -176,7 +176,7 @@ class SessionService {
|
|
|
176
176
|
}
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
##
|
|
179
|
+
## 三、前端数据流 Store 实现
|
|
180
180
|
|
|
181
181
|
在 LobeChat 应用中,Store 是用于管理应用前端状态的模块。其中的 Action 是触发状态更新的函数,通常会调用服务层的方法来执行实际的数据处理操作,然后更新 Store 中的状态。我们采用了 `zustand` 作为 Store 模块的底层依赖,对于状态管理的详细实践介绍,可以查阅 [📘 状态管理最佳实践](../State-Management/State-Management-Intro.zh-CN.md)
|
|
182
182
|
|
|
@@ -351,7 +351,7 @@ const customSessionGroups = (s: SessionStore): CustomSessionGroup[] => s.customS
|
|
|
351
351
|
>
|
|
352
352
|
> 如果你对 Selectors 的概念和功能不太了解,可以查阅 [📘 数据存储取数模块](../State-Management/State-Management-Selectors.zh-CN.md) 部分了解相关内容。
|
|
353
353
|
|
|
354
|
-
## 四、UI
|
|
354
|
+
## 四、UI 实现与 action 绑定
|
|
355
355
|
|
|
356
356
|
在 UI 组件中绑定 Store Action 实现交互逻辑,例如 `CreateGroupModal`:
|
|
357
357
|
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# Technical Development Getting Started Guide
|
|
2
2
|
|
|
3
|
-
Welcome to the LobeChat
|
|
3
|
+
Welcome to the LobeChat Technical Development Getting Started Guide. LobeChat is an AI conversation application built on the Next.js framework, incorporating a range of technology stacks to achieve diverse functionalities and features. This guide will detail the main technical components of LobeChat and how to configure and use these technologies in your development environment.
|
|
4
4
|
|
|
5
5
|
#### TOC
|
|
6
6
|
|
|
7
7
|
- [Basic Technology Stack](#basic-technology-stack)
|
|
8
8
|
- [Folder Directory Structure](#folder-directory-structure)
|
|
9
|
+
- [Local Development Environment Setup](#local-development-environment-setup)
|
|
10
|
+
- [Code Style and Contribution Guide](#code-style-and-contribution-guide)
|
|
11
|
+
- [Internationalization Implementation Guide](#internationalization-implementation-guide)
|
|
12
|
+
- [Appendix: Resources and References](#appendix-resources-and-references)
|
|
9
13
|
|
|
10
14
|
## Basic Technology Stack
|
|
11
15
|
|
|
12
|
-
The core technology stack of LobeChat
|
|
16
|
+
The core technology stack of LobeChat is as follows:
|
|
13
17
|
|
|
14
|
-
- **Framework**: We
|
|
15
|
-
- **Component Library**: We use [Ant Design (antd)](https://ant.design/) as the basic component library,
|
|
16
|
-
- **State Management**: We
|
|
18
|
+
- **Framework**: We chose [Next.js](https://nextjs.org/), a powerful React framework that provides key features such as server-side rendering, routing framework, and Router Handler.
|
|
19
|
+
- **Component Library**: We use [Ant Design (antd)](https://ant.design/) as the basic component library, along with [lobe-ui](https://github.com/lobehub/lobe-ui) as our business component library.
|
|
20
|
+
- **State Management**: We selected [zustand](https://github.com/pmndrs/zustand), a lightweight and easy-to-use state management library.
|
|
17
21
|
- **Network Requests**: We use [swr](https://swr.vercel.app/), a React Hooks library for data fetching.
|
|
18
|
-
- **Routing**: For routing management, we directly use the solution provided by [Next.js](https://nextjs.org/)
|
|
19
|
-
- **Internationalization**: We use [i18next](https://www.i18next.com/) to
|
|
22
|
+
- **Routing**: For routing management, we directly use the solution provided by [Next.js](https://nextjs.org/).
|
|
23
|
+
- **Internationalization**: We use [i18next](https://www.i18next.com/) to support multiple languages in the application.
|
|
20
24
|
- **Styling**: We use [antd-style](https://github.com/ant-design/antd-style), a CSS-in-JS library that complements Ant Design.
|
|
21
25
|
- **Unit Testing**: We use [vitest](https://github.com/vitest-dev/vitest) for unit testing.
|
|
22
26
|
|
|
@@ -26,18 +30,82 @@ The folder directory structure of LobeChat is as follows:
|
|
|
26
30
|
|
|
27
31
|
```bash
|
|
28
32
|
src
|
|
29
|
-
├── app #
|
|
33
|
+
├── app # Code related to the main logic and state management of the application
|
|
30
34
|
├── components # Reusable UI components
|
|
31
|
-
├── config # Application configuration files, including client
|
|
35
|
+
├── config # Application configuration files, including client and server environment variables
|
|
32
36
|
├── const # Used to define constants, such as action types, route names, etc.
|
|
33
|
-
├── features #
|
|
34
|
-
├── hooks # Custom utility
|
|
37
|
+
├── features # Business-related feature modules, such as Agent settings, plugin development pop-ups, etc.
|
|
38
|
+
├── hooks # Custom utility Hooks reusable across the application
|
|
35
39
|
├── layout # Application layout components, such as navigation bars, sidebars, etc.
|
|
36
40
|
├── locales # Language files for internationalization
|
|
37
41
|
├── services # Encapsulated backend service interfaces, such as HTTP requests
|
|
38
42
|
├── store # Zustand store for state management
|
|
39
43
|
├── types # TypeScript type definition files
|
|
40
|
-
└── utils #
|
|
44
|
+
└── utils # General utility functions
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
For a detailed introduction to the directory structure,
|
|
47
|
+
For a detailed introduction to the directory structure, see: [Folder Directory Structure](Folder-Structure.zh-CN.md)
|
|
48
|
+
|
|
49
|
+
## Local Development Environment Setup
|
|
50
|
+
|
|
51
|
+
This section outlines setting up the development environment and local development. Before starting, please ensure that Node.js, Git, and your chosen package manager (Bun or PNPM) are installed in your local environment.
|
|
52
|
+
|
|
53
|
+
We recommend using WebStorm as your integrated development environment (IDE).
|
|
54
|
+
|
|
55
|
+
1. **Get the code**: Clone the LobeChat code repository locally:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/lobehub/lobe-chat.git
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. **Install dependencies**: Enter the project directory and install the required dependencies:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cd lobe-chat
|
|
65
|
+
# If you use Bun
|
|
66
|
+
bun install
|
|
67
|
+
# If you use PNPM
|
|
68
|
+
pnpm install
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. **Run and debug**: Start the local development server and begin your development journey:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Start the development server with Bun
|
|
75
|
+
bun run dev
|
|
76
|
+
# Visit http://localhost:3010 to view the application
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> \[!IMPORTANT]\
|
|
80
|
+
> If you encounter the error "Could not find 'stylelint-config-recommended'" when installing dependencies with `npm`, please reinstall the dependencies using `pnpm` or `bun`.
|
|
81
|
+
|
|
82
|
+
Now, you should be able to see the welcome page of LobeChat in your browser. For a detailed environment setup guide, please refer to [Development Environment Setup Guide](Setup-Development.zh-CN.md).
|
|
83
|
+
|
|
84
|
+
## Code Style and Contribution Guide
|
|
85
|
+
|
|
86
|
+
In the LobeChat project, we place great emphasis on the quality and consistency of the code. For this reason, we have established a series of code style standards and contribution processes to ensure that every developer can smoothly participate in the project. Here are the code style and contribution guidelines you need to follow as a developer.
|
|
87
|
+
|
|
88
|
+
- **Code Style**: We use `@lobehub/lint` to unify the code style, including ESLint, Prettier, remarklint, and stylelint configurations. Please adhere to our code standards to maintain code consistency and readability.
|
|
89
|
+
- **Contribution Process**: We use gitmoji and semantic release for code submission and release processes. Please use gitmoji to annotate your commit messages and ensure compliance with the semantic release standards so that our automation systems can correctly handle version control and releases.
|
|
90
|
+
|
|
91
|
+
All contributions will undergo code review. Maintainers may suggest modifications or requirements. Please respond actively to review comments and make timely adjustments. We look forward to your participation and contribution.
|
|
92
|
+
|
|
93
|
+
For detailed code style and contribution guidelines, please refer to [Code Style and Contribution Guide](Contributing-Guidelines.zh-CN.md).
|
|
94
|
+
|
|
95
|
+
## Internationalization Implementation Guide
|
|
96
|
+
|
|
97
|
+
LobeChat uses `i18next` and `lobe-i18n` to implement multilingual support, ensuring a global user experience.
|
|
98
|
+
|
|
99
|
+
Internationalization files are located in `src/locales`, containing the default language (Chinese). We generate other language JSON files automatically through `lobe-i18n`.
|
|
100
|
+
|
|
101
|
+
If you want to add a new language, follow specific steps detailed in [New Language Addition Guide](../Internationalization/Add-New-Locale.zh-CN.md). We encourage you to participate in our internationalization efforts to provide better services to global users.
|
|
102
|
+
|
|
103
|
+
For a detailed guide on internationalization implementation, please refer to [Internationalization Implementation Guide](../Internationalization/Internationalization-Implementation.zh-CN.md).
|
|
104
|
+
|
|
105
|
+
## Appendix: Resources and References
|
|
106
|
+
|
|
107
|
+
To support developers in better understanding and using the technology stack of LobeChat, we provide a comprehensive list of resources and references — [LobeChat Resources and References](https://github.com/lobehub/lobe-chat/wiki/Resources.zh-CN) - Visit our maintained list of resources, including tutorials, articles, and other useful links.
|
|
108
|
+
|
|
109
|
+
We encourage developers to utilize these resources to deepen their learning and enhance their skills, join community discussions through [LobeChat GitHub Discussions](https://github.com/lobehub/lobe-chat/discussions) or [Discord](https://discord.com/invite/AYFPHvv2jT), ask questions, or share your experiences.
|
|
110
|
+
|
|
111
|
+
If you have any questions or need further assistance, please do not hesitate to contact us through the above channels.
|
|
@@ -76,6 +76,9 @@ bun run dev
|
|
|
76
76
|
# 访问 http://localhost:3010 查看应用
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
> \[!IMPORTANT]\
|
|
80
|
+
> 如果使用`npm`安装依赖出现`Could not find "stylelint-config-recommended"`错误,请使用 `pnpm` 或者 `bun` 重新安装依赖。
|
|
81
|
+
|
|
79
82
|
现在,你应该可以在浏览器中看到 LobeChat 的欢迎页面。详细的环境配置指南,请参考 [开发环境设置指南](Setup-Development.zh-CN.md)。
|
|
80
83
|
|
|
81
84
|
## 代码风格与贡献指南
|
|
@@ -126,13 +126,13 @@ if [ $? -eq 0 ]; then
|
|
|
126
126
|
exit 0
|
|
127
127
|
fi
|
|
128
128
|
|
|
129
|
-
echo "Detected
|
|
129
|
+
echo "Detected lobe-chat update"
|
|
130
130
|
|
|
131
131
|
# Remove the old container
|
|
132
|
-
echo "Removed: $(docker rm -f
|
|
132
|
+
echo "Removed: $(docker rm -f lobe-chat)"
|
|
133
133
|
|
|
134
|
-
# Run the new container
|
|
135
|
-
echo "Started: $(docker run -d --network=host --env-file /path/to/lobe.env --name=
|
|
134
|
+
# Run the new container(Please change the path to the env file)
|
|
135
|
+
echo "Started: $(docker run -d --network=host --env-file /path/to/lobe.env --name=lobe-chat --restart=always lobehub/lobe-chat)"
|
|
136
136
|
|
|
137
137
|
# Print the update time and version
|
|
138
138
|
echo "Update time: $(date)"
|
|
@@ -158,4 +158,4 @@ The following command configures Crontab to execute scripts every 5 minutes, or
|
|
|
158
158
|
*/5 * * * * /path/to/auto-update-lobe-chat.sh >> /path/to/auto-update-lobe-chat.log 2>&1
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
</Steps>
|
|
161
|
+
</Steps>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.162.
|
|
3
|
+
"version": "0.162.2",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -4,6 +4,7 @@ import { shallow } from 'zustand/shallow';
|
|
|
4
4
|
|
|
5
5
|
import ModelTag from '@/components/ModelTag';
|
|
6
6
|
import { useAgentStore } from '@/store/agent';
|
|
7
|
+
import { agentSelectors } from '@/store/agent/selectors';
|
|
7
8
|
import { useChatStore } from '@/store/chat';
|
|
8
9
|
import { chatSelectors } from '@/store/chat/selectors';
|
|
9
10
|
import { useSessionStore } from '@/store/session';
|
|
@@ -21,7 +22,7 @@ interface SessionItemProps {
|
|
|
21
22
|
const SessionItem = memo<SessionItemProps>(({ id }) => {
|
|
22
23
|
const [open, setOpen] = useState(false);
|
|
23
24
|
const [createGroupModalOpen, setCreateGroupModalOpen] = useState(false);
|
|
24
|
-
const [defaultModel] = useAgentStore((s) => [s
|
|
25
|
+
const [defaultModel] = useAgentStore((s) => [agentSelectors.inboxAgentModel(s)]);
|
|
25
26
|
|
|
26
27
|
const [active] = useSessionStore((s) => [s.activeId === id]);
|
|
27
28
|
const [loading] = useChatStore((s) => [chatSelectors.isAIGenerating(s) && id === s.activeId]);
|
|
@@ -16,10 +16,7 @@ const EditPage = memo(() => {
|
|
|
16
16
|
const id = useSessionStore((s) => s.activeId);
|
|
17
17
|
const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
|
|
18
18
|
const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
|
|
19
|
-
const [updateAgentConfig
|
|
20
|
-
s.updateAgentConfig,
|
|
21
|
-
s.updateAgentChatConfig,
|
|
22
|
-
]);
|
|
19
|
+
const [updateAgentConfig] = useAgentStore((s) => [s.updateAgentConfig]);
|
|
23
20
|
|
|
24
21
|
const [updateAgentMeta, title] = useSessionStore((s) => [
|
|
25
22
|
s.updateSessionMeta,
|
|
@@ -33,7 +30,6 @@ const EditPage = memo(() => {
|
|
|
33
30
|
config={config}
|
|
34
31
|
id={id}
|
|
35
32
|
meta={meta}
|
|
36
|
-
onChatConfigChange={updateAgentChatConfig}
|
|
37
33
|
onConfigChange={updateAgentConfig}
|
|
38
34
|
onMetaChange={updateAgentMeta}
|
|
39
35
|
/>
|
|
@@ -28,10 +28,7 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
|
28
28
|
const id = useSessionStore((s) => s.activeId);
|
|
29
29
|
const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
|
|
30
30
|
const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
|
|
31
|
-
const [updateAgentConfig
|
|
32
|
-
s.updateAgentConfig,
|
|
33
|
-
s.updateAgentChatConfig,
|
|
34
|
-
]);
|
|
31
|
+
const [updateAgentConfig] = useAgentStore((s) => [s.updateAgentConfig]);
|
|
35
32
|
const [updateAgentMeta] = useSessionStore((s) => [
|
|
36
33
|
s.updateSessionMeta,
|
|
37
34
|
sessionMetaSelectors.currentAgentTitle(s),
|
|
@@ -49,7 +46,6 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
|
49
46
|
config={config}
|
|
50
47
|
id={id}
|
|
51
48
|
meta={meta}
|
|
52
|
-
onChatConfigChange={updateAgentChatConfig}
|
|
53
49
|
onConfigChange={updateAgentConfig}
|
|
54
50
|
onMetaChange={updateAgentMeta}
|
|
55
51
|
/>
|
|
@@ -6,11 +6,11 @@ import { createStoreUpdater } from 'zustand-utils';
|
|
|
6
6
|
import { State, useStoreApi } from './store';
|
|
7
7
|
|
|
8
8
|
export type StoreUpdaterProps = Partial<
|
|
9
|
-
Pick<State, 'onMetaChange' | '
|
|
9
|
+
Pick<State, 'onMetaChange' | 'onConfigChange' | 'meta' | 'config' | 'id'>
|
|
10
10
|
>;
|
|
11
11
|
|
|
12
12
|
const StoreUpdater = memo<StoreUpdaterProps>(
|
|
13
|
-
({ onConfigChange,
|
|
13
|
+
({ onConfigChange, id, onMetaChange, meta, config }) => {
|
|
14
14
|
const storeApi = useStoreApi();
|
|
15
15
|
const useStoreUpdater = createStoreUpdater(storeApi);
|
|
16
16
|
|
|
@@ -18,7 +18,6 @@ const StoreUpdater = memo<StoreUpdaterProps>(
|
|
|
18
18
|
useStoreUpdater('config', config);
|
|
19
19
|
useStoreUpdater('onConfigChange', onConfigChange);
|
|
20
20
|
useStoreUpdater('onMetaChange', onMetaChange);
|
|
21
|
-
useStoreUpdater('onChatConfigChange', onChatConfigChange);
|
|
22
21
|
useStoreUpdater('id', id);
|
|
23
22
|
|
|
24
23
|
return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DeepPartial } from 'utility-types';
|
|
1
2
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
3
|
|
|
3
4
|
import { chainPickEmoji } from '@/chains/pickEmoji';
|
|
@@ -50,7 +51,7 @@ export interface Action {
|
|
|
50
51
|
resetAgentConfig: () => void;
|
|
51
52
|
resetAgentMeta: () => void;
|
|
52
53
|
|
|
53
|
-
setAgentConfig: (config:
|
|
54
|
+
setAgentConfig: (config: DeepPartial<LobeAgentConfig>) => void;
|
|
54
55
|
setAgentMeta: (meta: Partial<MetaData>) => void;
|
|
55
56
|
setChatConfig: (config: Partial<LobeAgentChatConfig>) => void;
|
|
56
57
|
|
|
@@ -245,11 +246,7 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
|
|
|
245
246
|
get().dispatchMeta({ type: 'update', value: meta });
|
|
246
247
|
},
|
|
247
248
|
setChatConfig: (config) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
set({ config: { ...get().config, chatConfig: nextConfig } }, false, 'updateChatConfig');
|
|
251
|
-
|
|
252
|
-
get().onChatConfigChange?.(nextConfig);
|
|
249
|
+
get().setAgentConfig({ chatConfig: config });
|
|
253
250
|
},
|
|
254
251
|
|
|
255
252
|
streamUpdateMetaArray: (key: keyof MetaData) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_AGENT_META } from '@/const/meta';
|
|
2
2
|
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
|
3
|
-
import {
|
|
3
|
+
import { LobeAgentConfig } from '@/types/agent';
|
|
4
4
|
import { MetaData } from '@/types/meta';
|
|
5
5
|
|
|
6
6
|
export interface State {
|
|
@@ -9,7 +9,6 @@ export interface State {
|
|
|
9
9
|
id?: string;
|
|
10
10
|
meta: MetaData;
|
|
11
11
|
|
|
12
|
-
onChatConfigChange?: (config: LobeAgentChatConfig) => void;
|
|
13
12
|
onConfigChange?: (config: LobeAgentConfig) => void;
|
|
14
13
|
onMetaChange?: (meta: MetaData) => void;
|
|
15
14
|
}
|
|
@@ -26,10 +26,13 @@ const StoreInitialization = memo(() => {
|
|
|
26
26
|
|
|
27
27
|
const useInitSystemStatus = useGlobalStore((s) => s.useInitSystemStatus);
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const useInitAgentStore = useAgentStore((s) => s.useInitAgentStore);
|
|
30
|
+
|
|
30
31
|
// init the system preference
|
|
31
32
|
useInitSystemStatus();
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
// init inbox agent and default agent config
|
|
35
|
+
useInitAgentStore(serverConfig.defaultAgent?.config);
|
|
33
36
|
|
|
34
37
|
useInitUserState(isLogin, serverConfig, {
|
|
35
38
|
onSuccess: (state) => {
|
|
@@ -55,21 +58,21 @@ const StoreInitialization = memo(() => {
|
|
|
55
58
|
importSettings(searchParam);
|
|
56
59
|
}, [searchParam]);
|
|
57
60
|
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}, [router, mobile]);
|
|
61
|
+
// useEffect(() => {
|
|
62
|
+
// router.prefetch('/chat');
|
|
63
|
+
// router.prefetch('/market');
|
|
64
|
+
//
|
|
65
|
+
// if (mobile) {
|
|
66
|
+
// router.prefetch('/me');
|
|
67
|
+
// router.prefetch('/chat/settings');
|
|
68
|
+
// router.prefetch('/settings/common');
|
|
69
|
+
// router.prefetch('/settings/agent');
|
|
70
|
+
// router.prefetch('/settings/sync');
|
|
71
|
+
// } else {
|
|
72
|
+
// router.prefetch('/chat/settings/modal');
|
|
73
|
+
// router.prefetch('/settings/modal');
|
|
74
|
+
// }
|
|
75
|
+
// }, [router, mobile]);
|
|
73
76
|
|
|
74
77
|
return null;
|
|
75
78
|
});
|
|
@@ -2,6 +2,7 @@ import { act, renderHook, waitFor } from '@testing-library/react';
|
|
|
2
2
|
import { mutate } from 'swr';
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
|
5
6
|
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
|
6
7
|
import { globalService } from '@/services/global';
|
|
7
8
|
import { sessionService } from '@/services/session';
|
|
@@ -159,18 +160,17 @@ describe('AgentSlice', () => {
|
|
|
159
160
|
it('should update agentConfig and isAgentConfigInit when data changes and isAgentConfigInit is false', async () => {
|
|
160
161
|
const { result } = renderHook(() => useAgentStore());
|
|
161
162
|
|
|
162
|
-
act(() => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
163
|
+
// act(() => {
|
|
164
|
+
// result.current.agentMap = {};
|
|
165
|
+
// });
|
|
166
166
|
|
|
167
167
|
vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValueOnce({ model: 'gpt-4' } as any);
|
|
168
168
|
|
|
169
169
|
renderHook(() => result.current.useFetchAgentConfig('test-session-id'));
|
|
170
170
|
|
|
171
171
|
await waitFor(() => {
|
|
172
|
-
expect(result.current.
|
|
173
|
-
expect(result.current.isAgentConfigInit).toBe(true);
|
|
172
|
+
expect(result.current.agentMap['test-session-id']).toEqual({ model: 'gpt-4' });
|
|
173
|
+
// expect(result.current.isAgentConfigInit).toBe(true);
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
176
|
|
|
@@ -178,7 +178,11 @@ describe('AgentSlice', () => {
|
|
|
178
178
|
const { result } = renderHook(() => useAgentStore());
|
|
179
179
|
|
|
180
180
|
act(() => {
|
|
181
|
-
useAgentStore.setState({
|
|
181
|
+
useAgentStore.setState({
|
|
182
|
+
agentMap: {
|
|
183
|
+
'test-session-id': { model: 'gpt-3.5-turbo' },
|
|
184
|
+
},
|
|
185
|
+
});
|
|
182
186
|
});
|
|
183
187
|
|
|
184
188
|
vi.spyOn(useSessionStore, 'setState');
|
|
@@ -189,27 +193,25 @@ describe('AgentSlice', () => {
|
|
|
189
193
|
renderHook(() => result.current.useFetchAgentConfig('test-session-id'));
|
|
190
194
|
|
|
191
195
|
await waitFor(() => {
|
|
192
|
-
expect(result.current.
|
|
193
|
-
expect(result.current.isAgentConfigInit).toBe(true);
|
|
196
|
+
expect(result.current.agentMap['test-session-id']).toEqual({ model: 'gpt-3.5-turbo' });
|
|
194
197
|
|
|
195
198
|
expect(useSessionStore.setState).not.toHaveBeenCalled();
|
|
196
199
|
});
|
|
197
200
|
});
|
|
198
201
|
});
|
|
199
202
|
|
|
200
|
-
describe('
|
|
203
|
+
describe('useFetchInboxAgentConfig', () => {
|
|
201
204
|
it('should merge DEFAULT_AGENT_CONFIG and update defaultAgentConfig and isDefaultAgentConfigInit on success', async () => {
|
|
202
205
|
const { result } = renderHook(() => useAgentStore());
|
|
203
|
-
vi.spyOn(
|
|
206
|
+
vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValue({
|
|
207
|
+
model: 'gemini-pro',
|
|
208
|
+
} as any);
|
|
204
209
|
|
|
205
|
-
renderHook(() => result.current.
|
|
210
|
+
renderHook(() => result.current.useInitAgentStore());
|
|
206
211
|
|
|
207
212
|
await waitFor(async () => {
|
|
208
|
-
expect(result.current.
|
|
209
|
-
|
|
210
|
-
model: 'gemini-pro',
|
|
211
|
-
});
|
|
212
|
-
expect(result.current.isDefaultAgentConfigInit).toBe(true);
|
|
213
|
+
expect(result.current.agentMap[INBOX_SESSION_ID]).toEqual({ model: 'gemini-pro' });
|
|
214
|
+
expect(result.current.isInboxAgentConfigInit).toBe(true);
|
|
213
215
|
});
|
|
214
216
|
});
|
|
215
217
|
|
|
@@ -218,11 +220,11 @@ describe('AgentSlice', () => {
|
|
|
218
220
|
|
|
219
221
|
vi.spyOn(globalService, 'getDefaultAgentConfig').mockRejectedValueOnce(new Error());
|
|
220
222
|
|
|
221
|
-
renderHook(() => result.current.
|
|
223
|
+
renderHook(() => result.current.useInitAgentStore());
|
|
222
224
|
|
|
223
225
|
await waitFor(async () => {
|
|
224
|
-
expect(result.current.
|
|
225
|
-
expect(result.current.
|
|
226
|
+
expect(result.current.agentMap[INBOX_SESSION_ID]).toBeUndefined();
|
|
227
|
+
expect(result.current.isInboxAgentConfigInit).toBe(false);
|
|
226
228
|
});
|
|
227
229
|
});
|
|
228
230
|
});
|
|
@@ -4,9 +4,9 @@ import useSWR, { SWRResponse, mutate } from 'swr';
|
|
|
4
4
|
import { DeepPartial } from 'utility-types';
|
|
5
5
|
import { StateCreator } from 'zustand/vanilla';
|
|
6
6
|
|
|
7
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
|
7
8
|
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
|
8
9
|
import { useClientDataSWR } from '@/libs/swr';
|
|
9
|
-
import { globalService } from '@/services/global';
|
|
10
10
|
import { sessionService } from '@/services/session';
|
|
11
11
|
import { AgentState } from '@/store/agent/slices/chat/initialState';
|
|
12
12
|
import { useSessionStore } from '@/store/session';
|
|
@@ -23,10 +23,12 @@ export interface AgentChatAction {
|
|
|
23
23
|
removePlugin: (id: string) => void;
|
|
24
24
|
togglePlugin: (id: string, open?: boolean) => Promise<void>;
|
|
25
25
|
updateAgentChatConfig: (config: Partial<LobeAgentChatConfig>) => Promise<void>;
|
|
26
|
-
updateAgentConfig: (config:
|
|
26
|
+
updateAgentConfig: (config: DeepPartial<LobeAgentConfig>) => Promise<void>;
|
|
27
27
|
|
|
28
28
|
useFetchAgentConfig: (id: string) => SWRResponse<LobeAgentConfig>;
|
|
29
|
-
|
|
29
|
+
useInitAgentStore: (
|
|
30
|
+
defaultAgentConfig?: DeepPartial<LobeAgentConfig>,
|
|
31
|
+
) => SWRResponse<DeepPartial<LobeAgentConfig>>;
|
|
30
32
|
|
|
31
33
|
/* eslint-disable typescript-sort-keys/interface */
|
|
32
34
|
|
|
@@ -35,12 +37,12 @@ export interface AgentChatAction {
|
|
|
35
37
|
data: DeepPartial<LobeAgentConfig>,
|
|
36
38
|
signal?: AbortSignal,
|
|
37
39
|
) => Promise<void>;
|
|
38
|
-
internal_updateAgentChatConfig: (
|
|
39
|
-
id: string,
|
|
40
|
-
data: DeepPartial<LobeAgentChatConfig>,
|
|
41
|
-
signal?: AbortSignal,
|
|
42
|
-
) => Promise<void>;
|
|
43
40
|
internal_refreshAgentConfig: (id: string) => Promise<void>;
|
|
41
|
+
internal_dispatchAgentMap: (
|
|
42
|
+
id: string,
|
|
43
|
+
config: DeepPartial<LobeAgentConfig>,
|
|
44
|
+
actions?: string,
|
|
45
|
+
) => void;
|
|
44
46
|
internal_createAbortController: (key: keyof AgentState) => AbortController;
|
|
45
47
|
/* eslint-enable */
|
|
46
48
|
}
|
|
@@ -86,9 +88,7 @@ export const createChatSlice: StateCreator<
|
|
|
86
88
|
|
|
87
89
|
if (!activeId) return;
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
await get().internal_updateAgentChatConfig(activeId, config, controller.signal);
|
|
91
|
+
await get().updateAgentConfig({ chatConfig: config });
|
|
92
92
|
},
|
|
93
93
|
updateAgentConfig: async (config) => {
|
|
94
94
|
const { activeId } = get();
|
|
@@ -106,28 +106,28 @@ export const createChatSlice: StateCreator<
|
|
|
106
106
|
{
|
|
107
107
|
fallbackData: DEFAULT_AGENT_CONFIG,
|
|
108
108
|
onSuccess: (data) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
set({ agentConfig: data, isAgentConfigInit: true }, false, 'fetchAgentConfig');
|
|
109
|
+
get().internal_dispatchAgentMap(sessionId, data, 'fetch');
|
|
112
110
|
},
|
|
113
111
|
suspense: true,
|
|
114
112
|
},
|
|
115
113
|
),
|
|
116
|
-
|
|
114
|
+
useInitAgentStore: (defaultAgentConfig) =>
|
|
117
115
|
useSWR<DeepPartial<LobeAgentConfig>>(
|
|
118
|
-
'
|
|
119
|
-
|
|
116
|
+
'fetchInboxAgentConfig',
|
|
117
|
+
() => sessionService.getSessionConfig(INBOX_SESSION_ID),
|
|
120
118
|
{
|
|
121
119
|
onSuccess: (data) => {
|
|
122
120
|
if (data) {
|
|
123
121
|
set(
|
|
124
122
|
{
|
|
125
|
-
defaultAgentConfig: merge(
|
|
126
|
-
|
|
123
|
+
defaultAgentConfig: merge(get().defaultAgentConfig, defaultAgentConfig),
|
|
124
|
+
isInboxAgentConfigInit: true,
|
|
127
125
|
},
|
|
128
126
|
false,
|
|
129
|
-
'
|
|
127
|
+
'initStore',
|
|
130
128
|
);
|
|
129
|
+
|
|
130
|
+
get().internal_dispatchAgentMap(INBOX_SESSION_ID, data, 'initInbox');
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
133
|
revalidateOnFocus: false,
|
|
@@ -136,10 +136,24 @@ export const createChatSlice: StateCreator<
|
|
|
136
136
|
|
|
137
137
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
138
138
|
|
|
139
|
+
internal_dispatchAgentMap: (id, config, actions) => {
|
|
140
|
+
const agentMap = produce(get().agentMap, (draft) => {
|
|
141
|
+
if (!draft[id]) {
|
|
142
|
+
draft[id] = config;
|
|
143
|
+
} else {
|
|
144
|
+
draft[id] = merge(draft[id], config);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (isEqual(get().agentMap, agentMap)) return;
|
|
149
|
+
|
|
150
|
+
set({ agentMap }, false, 'dispatchAgent' + (actions ? `/${actions}` : ''));
|
|
151
|
+
},
|
|
152
|
+
|
|
139
153
|
internal_updateAgentConfig: async (id, data, signal) => {
|
|
140
154
|
const prevModel = agentSelectors.currentAgentModel(get());
|
|
141
155
|
// optimistic update at frontend
|
|
142
|
-
|
|
156
|
+
get().internal_dispatchAgentMap(id, data, 'optimistic_updateAgentConfig');
|
|
143
157
|
|
|
144
158
|
await sessionService.updateSessionConfig(id, data, signal);
|
|
145
159
|
await get().internal_refreshAgentConfig(id);
|
|
@@ -148,20 +162,6 @@ export const createChatSlice: StateCreator<
|
|
|
148
162
|
if (prevModel !== data.model) await useSessionStore.getState().refreshSessions();
|
|
149
163
|
},
|
|
150
164
|
|
|
151
|
-
internal_updateAgentChatConfig: async (id, data, signal) => {
|
|
152
|
-
// optimistic update at frontend
|
|
153
|
-
const chatConfig = merge(get().agentConfig.chatConfig, data);
|
|
154
|
-
|
|
155
|
-
set(
|
|
156
|
-
{ agentConfig: { ...get().agentConfig, chatConfig } },
|
|
157
|
-
false,
|
|
158
|
-
'optimistic_updateAgentConfig',
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
await sessionService.updateSessionChatConfig(id, data, signal);
|
|
162
|
-
await get().internal_refreshAgentConfig(id);
|
|
163
|
-
},
|
|
164
|
-
|
|
165
165
|
internal_refreshAgentConfig: async (id) => {
|
|
166
166
|
await mutate([FETCH_AGENT_CONFIG_KEY, id]);
|
|
167
167
|
},
|
|
@@ -5,18 +5,16 @@ import { LobeAgentConfig } from '@/types/agent';
|
|
|
5
5
|
|
|
6
6
|
export interface AgentState {
|
|
7
7
|
activeId: string;
|
|
8
|
-
|
|
8
|
+
agentMap: Record<string, DeepPartial<LobeAgentConfig>>;
|
|
9
9
|
defaultAgentConfig: LobeAgentConfig;
|
|
10
|
-
|
|
11
|
-
isDefaultAgentConfigInit: boolean;
|
|
10
|
+
isInboxAgentConfigInit: boolean;
|
|
12
11
|
updateAgentChatConfigSignal?: AbortController;
|
|
13
12
|
updateAgentConfigSignal?: AbortController;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export const initialAgentChatState: AgentState = {
|
|
17
16
|
activeId: 'inbox',
|
|
18
|
-
|
|
17
|
+
agentMap: {},
|
|
19
18
|
defaultAgentConfig: DEFAULT_AGENT_CONFIG,
|
|
20
|
-
|
|
21
|
-
isDefaultAgentConfigInit: false,
|
|
19
|
+
isInboxAgentConfigInit: false,
|
|
22
20
|
};
|
|
@@ -3,32 +3,41 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
4
4
|
import { DEFAULT_AGENT_CONFIG, DEFAUTT_AGENT_TTS_CONFIG } from '@/const/settings';
|
|
5
5
|
import { AgentStore } from '@/store/agent';
|
|
6
|
+
import { AgentState } from '@/store/agent/slices/chat/initialState';
|
|
7
|
+
import { merge } from '@/utils/merge';
|
|
6
8
|
|
|
9
|
+
import { initialState } from '../../initialState';
|
|
7
10
|
import { agentSelectors } from './selectors';
|
|
8
11
|
|
|
9
12
|
vi.mock('i18next', () => ({
|
|
10
13
|
t: vi.fn((key) => key), // Simplified mock return value
|
|
11
14
|
}));
|
|
12
15
|
|
|
13
|
-
const
|
|
16
|
+
const agentConfig = DEFAULT_AGENT_CONFIG;
|
|
17
|
+
|
|
18
|
+
const mockSessionStore = merge(initialState, {
|
|
14
19
|
activeId: '1',
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
agentMap: {
|
|
21
|
+
'1': agentConfig,
|
|
22
|
+
},
|
|
23
|
+
} as Partial<AgentState>) as unknown as AgentStore;
|
|
17
24
|
|
|
18
25
|
describe('agentSelectors', () => {
|
|
19
26
|
describe('defaultAgentConfig', () => {
|
|
20
27
|
it('should merge DEFAULT_AGENT_CONFIG and defaultAgent(s).config correctly', () => {
|
|
21
28
|
const s = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
agentMap: {
|
|
30
|
+
inbox: {
|
|
31
|
+
systemRole: 'user',
|
|
32
|
+
model: 'gpt-3.5-turbo',
|
|
33
|
+
params: {
|
|
34
|
+
temperature: 0.7,
|
|
35
|
+
},
|
|
27
36
|
},
|
|
28
37
|
},
|
|
29
38
|
} as unknown as AgentStore;
|
|
30
39
|
|
|
31
|
-
const result = agentSelectors.
|
|
40
|
+
const result = agentSelectors.inboxAgentConfig(s);
|
|
32
41
|
|
|
33
42
|
expect(result).toMatchSnapshot();
|
|
34
43
|
});
|
|
@@ -37,14 +46,14 @@ describe('agentSelectors', () => {
|
|
|
37
46
|
describe('currentAgentConfig', () => {
|
|
38
47
|
it('should return the merged default and session-specific agent config', () => {
|
|
39
48
|
const config = agentSelectors.currentAgentConfig(mockSessionStore);
|
|
40
|
-
expect(config).toEqual(expect.objectContaining(
|
|
49
|
+
expect(config).toEqual(expect.objectContaining(agentConfig));
|
|
41
50
|
});
|
|
42
51
|
});
|
|
43
52
|
|
|
44
53
|
describe('currentAgentModel', () => {
|
|
45
54
|
it('should return the model from the agent config', () => {
|
|
46
55
|
const model = agentSelectors.currentAgentModel(mockSessionStore);
|
|
47
|
-
expect(model).toBe(
|
|
56
|
+
expect(model).toBe(agentConfig.model);
|
|
48
57
|
});
|
|
49
58
|
});
|
|
50
59
|
|
|
@@ -55,13 +64,11 @@ describe('agentSelectors', () => {
|
|
|
55
64
|
});
|
|
56
65
|
|
|
57
66
|
it('should return false if the system role is not defined in the agent config', () => {
|
|
58
|
-
const modifiedSessionStore = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
...mockSessionStore.agentConfig,
|
|
62
|
-
systemRole: 'test',
|
|
67
|
+
const modifiedSessionStore = merge(mockSessionStore, {
|
|
68
|
+
agentMap: {
|
|
69
|
+
'1': { systemRole: 'test' },
|
|
63
70
|
},
|
|
64
|
-
};
|
|
71
|
+
} as Partial<AgentState>);
|
|
65
72
|
const hasRole = agentSelectors.hasSystemRole(modifiedSessionStore);
|
|
66
73
|
expect(hasRole).toBe(true);
|
|
67
74
|
});
|
|
@@ -70,7 +77,7 @@ describe('agentSelectors', () => {
|
|
|
70
77
|
describe('currentAgentTTS', () => {
|
|
71
78
|
it('should return the TTS config from the agent config', () => {
|
|
72
79
|
const ttsConfig = agentSelectors.currentAgentTTS(mockSessionStore);
|
|
73
|
-
expect(ttsConfig).toEqual(
|
|
80
|
+
expect(ttsConfig).toEqual(agentConfig.tts);
|
|
74
81
|
});
|
|
75
82
|
|
|
76
83
|
it('should return the default TTS config if none is defined in the agent config', () => {
|
|
@@ -78,9 +85,9 @@ describe('agentSelectors', () => {
|
|
|
78
85
|
...mockSessionStore,
|
|
79
86
|
sessions: [
|
|
80
87
|
{
|
|
81
|
-
...
|
|
88
|
+
...agentConfig,
|
|
82
89
|
config: {
|
|
83
|
-
...
|
|
90
|
+
...agentConfig,
|
|
84
91
|
tts: DEFAUTT_AGENT_TTS_CONFIG,
|
|
85
92
|
},
|
|
86
93
|
},
|
|
@@ -95,35 +102,30 @@ describe('agentSelectors', () => {
|
|
|
95
102
|
it('should return the appropriate TTS voice based on the service and language', () => {
|
|
96
103
|
const lang = 'en';
|
|
97
104
|
const ttsVoice = agentSelectors.currentAgentTTSVoice(lang)(mockSessionStore);
|
|
98
|
-
expect(ttsVoice).toBe(
|
|
105
|
+
expect(ttsVoice).toBe(agentConfig.tts?.voice?.openai);
|
|
99
106
|
});
|
|
100
107
|
|
|
101
108
|
it('should return the default voice for edge TTS service', () => {
|
|
102
|
-
const modifiedStore = {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
tts: {
|
|
107
|
-
...DEFAUTT_AGENT_TTS_CONFIG,
|
|
108
|
-
ttsService: 'edge',
|
|
109
|
+
const modifiedStore = merge(mockSessionStore, {
|
|
110
|
+
agentMap: {
|
|
111
|
+
'1': {
|
|
112
|
+
tts: { ttsService: 'edge' },
|
|
109
113
|
},
|
|
110
114
|
},
|
|
111
|
-
} as AgentStore;
|
|
115
|
+
} as Partial<AgentState>) as AgentStore;
|
|
112
116
|
const ttsVoice = agentSelectors.currentAgentTTSVoice('en')(modifiedStore);
|
|
113
117
|
expect(ttsVoice).toBe('ar-SA-HamedNeural');
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
it('should return the default voice for microsoft TTS service', () => {
|
|
117
|
-
const modifiedStore = {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
tts: {
|
|
122
|
-
...DEFAUTT_AGENT_TTS_CONFIG,
|
|
123
|
-
ttsService: 'microsoft',
|
|
121
|
+
const modifiedStore = merge(mockSessionStore, {
|
|
122
|
+
agentMap: {
|
|
123
|
+
'1': {
|
|
124
|
+
tts: { ttsService: 'microsoft' },
|
|
124
125
|
},
|
|
125
126
|
},
|
|
126
|
-
} as AgentStore;
|
|
127
|
+
} as Partial<AgentState>) as AgentStore;
|
|
128
|
+
|
|
127
129
|
const ttsVoice = agentSelectors.currentAgentTTSVoice('en')(modifiedStore);
|
|
128
130
|
expect(ttsVoice).toBe('ar-SA-HamedNeural');
|
|
129
131
|
});
|
|
@@ -133,7 +135,7 @@ describe('agentSelectors', () => {
|
|
|
133
135
|
const modifiedStore = {
|
|
134
136
|
...mockSessionStore,
|
|
135
137
|
agentConfig: {
|
|
136
|
-
...
|
|
138
|
+
...agentConfig,
|
|
137
139
|
tts: {
|
|
138
140
|
...DEFAUTT_AGENT_TTS_CONFIG,
|
|
139
141
|
ttsService: 'avc',
|
|
@@ -151,14 +153,14 @@ describe('agentSelectors', () => {
|
|
|
151
153
|
describe('currentAgentModelProvider', () => {
|
|
152
154
|
it('should return the provider from the agent config', () => {
|
|
153
155
|
const provider = agentSelectors.currentAgentModelProvider(mockSessionStore);
|
|
154
|
-
expect(provider).toBe(
|
|
156
|
+
expect(provider).toBe(agentConfig.provider);
|
|
155
157
|
});
|
|
156
158
|
|
|
157
159
|
it('should fallback to openai if provider is not defined in the agent config', () => {
|
|
158
160
|
const modifiedStore = {
|
|
159
161
|
...mockSessionStore,
|
|
160
162
|
agentConfig: {
|
|
161
|
-
...
|
|
163
|
+
...agentConfig,
|
|
162
164
|
provider: undefined,
|
|
163
165
|
},
|
|
164
166
|
};
|
|
@@ -170,14 +172,14 @@ describe('agentSelectors', () => {
|
|
|
170
172
|
describe('currentAgentPlugins', () => {
|
|
171
173
|
it('should return the plugins array from the agent config', () => {
|
|
172
174
|
const plugins = agentSelectors.currentAgentPlugins(mockSessionStore);
|
|
173
|
-
expect(plugins).toEqual(
|
|
175
|
+
expect(plugins).toEqual(agentConfig.plugins);
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
it('should return an empty array if plugins are not defined in the agent config', () => {
|
|
177
179
|
const modifiedStore = {
|
|
178
180
|
...mockSessionStore,
|
|
179
181
|
agentConfig: {
|
|
180
|
-
...
|
|
182
|
+
...agentConfig,
|
|
181
183
|
plugins: undefined,
|
|
182
184
|
},
|
|
183
185
|
};
|
|
@@ -189,17 +191,19 @@ describe('agentSelectors', () => {
|
|
|
189
191
|
describe('currentAgentSystemRole', () => {
|
|
190
192
|
it('should return the system role from the agent config', () => {
|
|
191
193
|
const systemRole = agentSelectors.currentAgentSystemRole(mockSessionStore);
|
|
192
|
-
expect(systemRole).toBe(
|
|
194
|
+
expect(systemRole).toBe(agentConfig.systemRole);
|
|
193
195
|
});
|
|
194
196
|
|
|
195
197
|
it('should return undefined if system role is not defined in the agent config', () => {
|
|
196
|
-
const modifiedStore = {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
const modifiedStore = merge({}, {
|
|
199
|
+
activeId: '1',
|
|
200
|
+
agentMap: {
|
|
201
|
+
'1': {
|
|
202
|
+
systemRole: undefined,
|
|
203
|
+
},
|
|
201
204
|
},
|
|
202
|
-
};
|
|
205
|
+
} as Partial<AgentState>) as AgentStore;
|
|
206
|
+
|
|
203
207
|
const systemRole = agentSelectors.currentAgentSystemRole(modifiedStore);
|
|
204
208
|
expect(systemRole).toBeUndefined();
|
|
205
209
|
});
|
|
@@ -2,8 +2,9 @@ import { VoiceList } from '@lobehub/tts';
|
|
|
2
2
|
|
|
3
3
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
4
4
|
import {
|
|
5
|
-
DEFAULT_AGENT_CHAT_CONFIG,
|
|
6
5
|
DEFAULT_AGENT_CONFIG,
|
|
6
|
+
DEFAULT_MODEL,
|
|
7
|
+
DEFAULT_PROVIDER,
|
|
7
8
|
DEFAUTT_AGENT_TTS_CONFIG,
|
|
8
9
|
} from '@/const/settings';
|
|
9
10
|
import { AgentStore } from '@/store/agent';
|
|
@@ -14,13 +15,15 @@ const isInboxSession = (s: AgentStore) => s.activeId === INBOX_SESSION_ID;
|
|
|
14
15
|
|
|
15
16
|
// ========== Config ============== //
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const inboxAgentConfig = (s: AgentStore) =>
|
|
19
|
+
merge(DEFAULT_AGENT_CONFIG, s.agentMap[INBOX_SESSION_ID]);
|
|
20
|
+
const inboxAgentModel = (s: AgentStore) => inboxAgentConfig(s).model;
|
|
18
21
|
|
|
19
22
|
const currentAgentConfig = (s: AgentStore): LobeAgentConfig =>
|
|
20
|
-
merge(s.defaultAgentConfig, s.
|
|
23
|
+
merge(s.defaultAgentConfig, s.agentMap[s.activeId]);
|
|
21
24
|
|
|
22
25
|
const currentAgentChatConfig = (s: AgentStore): LobeAgentChatConfig =>
|
|
23
|
-
currentAgentConfig(s).chatConfig ||
|
|
26
|
+
currentAgentConfig(s).chatConfig || {};
|
|
24
27
|
|
|
25
28
|
const currentAgentSystemRole = (s: AgentStore) => {
|
|
26
29
|
return currentAgentConfig(s).systemRole;
|
|
@@ -29,13 +32,13 @@ const currentAgentSystemRole = (s: AgentStore) => {
|
|
|
29
32
|
const currentAgentModel = (s: AgentStore): string => {
|
|
30
33
|
const config = currentAgentConfig(s);
|
|
31
34
|
|
|
32
|
-
return config?.model ||
|
|
35
|
+
return config?.model || DEFAULT_MODEL;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
const currentAgentModelProvider = (s: AgentStore) => {
|
|
36
39
|
const config = currentAgentConfig(s);
|
|
37
40
|
|
|
38
|
-
return config?.provider ||
|
|
41
|
+
return config?.provider || DEFAULT_PROVIDER;
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
const currentAgentPlugins = (s: AgentStore) => {
|
|
@@ -88,7 +91,8 @@ export const agentSelectors = {
|
|
|
88
91
|
currentAgentSystemRole,
|
|
89
92
|
currentAgentTTS,
|
|
90
93
|
currentAgentTTSVoice,
|
|
91
|
-
defaultAgentConfig,
|
|
92
94
|
hasSystemRole,
|
|
95
|
+
inboxAgentConfig,
|
|
96
|
+
inboxAgentModel,
|
|
93
97
|
isInboxSession,
|
|
94
98
|
};
|
|
@@ -419,13 +419,17 @@ describe('chatMessage actions', () => {
|
|
|
419
419
|
const { result } = renderHook(() => useChatStore());
|
|
420
420
|
act(() => {
|
|
421
421
|
useAgentStore.setState({
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
422
|
+
activeId: 'abc',
|
|
423
|
+
agentMap: {
|
|
424
|
+
abc: {
|
|
425
|
+
chatConfig: {
|
|
426
|
+
enableAutoCreateTopic: false,
|
|
427
|
+
autoCreateTopicThreshold: 1,
|
|
428
|
+
},
|
|
426
429
|
},
|
|
427
430
|
},
|
|
428
431
|
});
|
|
432
|
+
|
|
429
433
|
useChatStore.setState({
|
|
430
434
|
// Mock the currentChats selector to return a list that does not reach the threshold
|
|
431
435
|
messagesMap: {
|
|
@@ -147,13 +147,15 @@ describe('chatSelectors', () => {
|
|
|
147
147
|
act(() => {
|
|
148
148
|
useAgentStore.setState({
|
|
149
149
|
activeId: 'inbox',
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
150
|
+
agentMap: {
|
|
151
|
+
inbox: {
|
|
152
|
+
chatConfig: {
|
|
153
|
+
historyCount: 2,
|
|
154
|
+
enableHistoryCount: true,
|
|
155
|
+
},
|
|
156
|
+
model: 'abc',
|
|
157
|
+
} as LobeAgentConfig,
|
|
158
|
+
},
|
|
157
159
|
});
|
|
158
160
|
});
|
|
159
161
|
|