mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
* 🔧 chore(vscode): add typescript.tsdk and disable mdx server Fix MDX extension crash caused by Cursor's bundled TypeScript version * 🔧 chore(claude): add skills symlink to .claude directory * 📝 docs: update development guides with current tech stack and architecture - Update tech stack: Next.js 16 + React 19, hybrid routing (App Router + React Router DOM), tRPC, Drizzle ORM + PostgreSQL, react-i18next - Update directory structure to reflect monorepo layout (apps/, packages/, e2e/, locales/) - Expand src/server/ with detailed subdirectory descriptions - Add complete SPA routing architecture with desktop and mobile route tables - Add tRPC router grouping details (lambda, async, tools, mobile) - Add data flow diagram - Simplify dev setup section to link to setup-development guide - Fix i18n default language description (English, not Chinese) - Sync all changes between zh-CN and English versions * 📝 docs: expand data flow diagram in folder structure guide Replace the single-line data flow with a detailed layer-by-layer flow diagram showing each layer's location and responsibility. * 📝 docs: modernize feature development guide - Remove outdated clientDB/pglite/indexDB references - Update schema path to packages/database/src/schemas/ - Update types path to packages/types/src/ - Replace inline migration steps with link to db-migrations guide - Add complete layered architecture table (Client Service, WebAPI, tRPC Router, Server Service, Server Module, Repository, DB Model) - Clarify Client Service as frontend code - Add i18n handling section with workflow and key naming convention - Remove verbose CSS style code, keep core business logic only - Expand testing section with commands, skill refs, and CI tip * 🔥 docs: remove outdated frontend feature development guide Content is superseded by the comprehensive feature-development guide which covers the full chain from schema to testing. * 📝 docs: add LobeHub ecosystem and community resources Add official ecosystem packages (LobeUI, LobeIcons, LobeCharts, LobeEditor, LobeTTS, LobeLint, Lobe i18n, MCP Mark) and community platforms (Agent Market, MCP Market, YouTube, X, Discord). * 📝 docs: improve contributing guidelines and resources - Clarify semantic release triggers (feat/fix vs style/chore) - Add testing section with Vitest/E2E/CI requirements - Update contribution steps to include CI check - Add LobeHub ecosystem packages and community platforms to resources * 📝 docs: rewrite architecture guide to reflect current platform design * 📝 docs: add code quality tools to architecture guide * 📝 docs: rewrite chat-api guide to reflect current architecture - Update sequence diagram with Agent Runtime loop as core execution engine - Replace PluginGateway with ToolExecution layer (Builtin/MCP/Plugin) - Update all path references (model-runtime, agent-runtime, fetch-sse packages) - Split old AgentRuntime section into Model Runtime + Agent Runtime - Add tool calling taxonomy: Builtin, MCP, and Plugin (deprecated) - Add client-side vs server-side execution section - Remove outdated adapter pseudo-code examples * 📝 docs: update file paths in add-new-image-model guide - src/libs/standard-parameters/ → packages/model-bank/src/standard-parameters/ - src/config/aiModels/ → packages/model-bank/src/aiModels/ - src/libs/model-runtime/ → packages/model-runtime/src/providers/ * 📝 docs: restore S3_PUBLIC_DOMAIN in deployment guides The S3_PUBLIC_DOMAIN env var was incorrectly removed from all documentation in commit4a87b31. This variable is still required by the code (src/server/services/file/impls/s3.ts) to generate public URLs for uploaded files. Without it, image URLs sent to vision models are just S3 keys instead of full URLs. Closes #12161 * 📦 chore: pin @lobehub/ui to 4.33.4 to fix SortableList type errors @lobehub/ui 4.34.0 introduced breaking type changes in SortableList where SortableListItem became strict, causing type incompatibility in onChange and renderItem callbacks across 6 files. Pin to 4.33.4 via pnpm overrides to enforce consistent version across monorepo. * 🐛 fix: correct ReadableStream type annotations and add dom.asynciterable - Add dom.asynciterable to tsconfig lib for ReadableStream async iteration - Fix createCallbacksTransformer return type: TransformStream<string, Uint8Array> - Update stream function return types from ReadableStream<string> to ReadableStream<Uint8Array> (llama.ts, ollama.ts, claude.ts) - Remove @ts-ignore from for-await loops in test files - Add explicit string[] type for chunks arrays * Revert "📝 docs: restore S3_PUBLIC_DOMAIN in deployment guides" This reverts commit24073f83d3.
365 lines
13 KiB
Plaintext
365 lines
13 KiB
Plaintext
---
|
||
title: LobeHub 功能开发完全指南
|
||
description: 了解如何在 LobeHub 中开发完整的功能需求,提升开发效率。
|
||
tags:
|
||
- LobeHub
|
||
- 功能开发
|
||
- 开发指南
|
||
- 开场设置
|
||
---
|
||
|
||
# LobeHub 功能开发完全指南
|
||
|
||
本文档旨在指导开发者了解如何在 LobeHub 中开发一块完整的功能需求。
|
||
|
||
我们将以 [RFC 021 - 自定义助手开场引导](https://github.com/lobehub/lobehub/discussions/891) 为例,阐述完整的实现流程。
|
||
|
||
## 一、更新 Schema
|
||
|
||
LobeHub 使用 PostgreSQL 数据库,项目使用 [Drizzle ORM](https://orm.drizzle.team/) 来操作数据库。
|
||
|
||
Schemas 统一放在 `packages/database/src/schemas/` 下,我们需要调整 `agents` 表增加两个配置项对应的字段:
|
||
|
||
```diff
|
||
// packages/database/src/schemas/agent.ts
|
||
export const agents = pgTable(
|
||
'agents',
|
||
{
|
||
id: text('id')
|
||
.primaryKey()
|
||
.$defaultFn(() => idGenerator('agents'))
|
||
.notNull(),
|
||
avatar: text('avatar'),
|
||
backgroundColor: text('background_color'),
|
||
plugins: jsonb('plugins').$type<string[]>().default([]),
|
||
// ...
|
||
tts: jsonb('tts').$type<LobeAgentTTSConfig>(),
|
||
|
||
+ openingMessage: text('opening_message'),
|
||
+ openingQuestions: text('opening_questions').array().default([]),
|
||
|
||
...timestamps,
|
||
},
|
||
(t) => ({
|
||
// ...
|
||
// !: update index here
|
||
}),
|
||
);
|
||
|
||
```
|
||
|
||
需要注意的是,有些时候我们可能还需要更新索引,但对于这个需求我们没有相关的性能瓶颈问题,所以不需要更新索引。
|
||
|
||
### 数据库迁移
|
||
|
||
调整完 schema 后需要生成并优化迁移文件,详细步骤请参阅 [数据库迁移指南](https://github.com/lobehub/lobe-chat/blob/main/.agents/skills/drizzle/references/db-migrations.md)。
|
||
|
||
## 二、更新数据模型
|
||
|
||
数据模型定义在 `packages/types/src/` 下,我们并没有直接使用 Drizzle schema 导出的类型(例如 `typeof agents.$inferInsert`),而是根据前端需求定义了独立的数据模型。
|
||
|
||
更新 `packages/types/src/agent/index.ts` 中 `LobeAgentConfig` 类型:
|
||
|
||
```diff
|
||
export interface LobeAgentConfig {
|
||
// ...
|
||
chatConfig: LobeAgentChatConfig;
|
||
/**
|
||
* 角色所使用的语言模型
|
||
* @default gpt-4o-mini
|
||
*/
|
||
model: string;
|
||
|
||
+ /**
|
||
+ * 开场白
|
||
+ */
|
||
+ openingMessage?: string;
|
||
+ /**
|
||
+ * 开场问题
|
||
+ */
|
||
+ openingQuestions?: string[];
|
||
|
||
/**
|
||
* 语言模型参数
|
||
*/
|
||
params: LLMParams;
|
||
// ...
|
||
}
|
||
```
|
||
|
||
## 三、Service / Model 各层实现
|
||
|
||
项目按职责分为前端和后端多层,完整的分层如下:
|
||
|
||
```plaintext
|
||
+-------------------+--------------------------------------+------------------------------------------------------+
|
||
| Layer | Location | Responsibility |
|
||
+-------------------+--------------------------------------+------------------------------------------------------+
|
||
| Client Service | src/services/ | 封装前端可复用的业务逻辑,一般涉及多个后端请求(tRPC) |
|
||
| WebAPI | src/app/(backend)/webapi/ | REST API 端点 |
|
||
| tRPC Router | src/server/routers/ | tRPC 入口,校验输入,路由到 service |
|
||
| Server Service | src/server/services/ | 服务端业务逻辑,可访问数据库 |
|
||
| Server Module | src/server/modules/ | 服务端模块,不直接访问数据库 |
|
||
| Repository | packages/database/src/repositories/ | 封装复杂查询、跨表操作 |
|
||
| DB Model | packages/database/src/models/ | 封装单表的 CRUD 操作 |
|
||
+-------------------+--------------------------------------+------------------------------------------------------+
|
||
```
|
||
|
||
**Client Service** 是前端代码,封装可复用的业务逻辑,通过 tRPC 客户端调用后端。例如 `src/services/session/index.ts`:
|
||
|
||
```typescript
|
||
export class SessionService {
|
||
updateSessionConfig = (id: string, config: PartialDeep<LobeAgentConfig>, signal?: AbortSignal) => {
|
||
return lambdaClient.session.updateSessionConfig.mutate({ id, value: config }, { signal });
|
||
};
|
||
}
|
||
```
|
||
|
||
**tRPC Router** 是后端入口(`src/server/routers/lambda/`),校验输入后调用 Server Service 处理业务逻辑:
|
||
|
||
```typescript
|
||
export const sessionRouter = router({
|
||
updateSessionConfig: sessionProcedure
|
||
.input(
|
||
z.object({
|
||
id: z.string(),
|
||
value: z.object({}).passthrough().partial(),
|
||
}),
|
||
)
|
||
.mutation(async ({ input, ctx }) => {
|
||
const session = await ctx.sessionModel.findByIdOrSlug(input.id);
|
||
// ...
|
||
const mergedValue = merge(session.agent, input.value);
|
||
return ctx.sessionModel.updateConfig(session.agent.id, mergedValue);
|
||
}),
|
||
});
|
||
```
|
||
|
||
对于本次需求,`updateSessionConfig` 只是简单 merge config,并没有细粒度到具体字段,因此各层都不需要修改。
|
||
|
||
## 四、前端实现
|
||
|
||
### 数据流 Store 实现
|
||
|
||
LobeHub 使用 [zustand](https://zustand.docs.pmnd.rs/getting-started/introduction) 作为全局状态管理框架,对于状态管理的详细实践介绍,可以查阅 [📘 状态管理最佳实践](/zh/docs/development/state-management/state-management-intro)。
|
||
|
||
和 agent 相关的 store 有两个:
|
||
|
||
- `src/features/AgentSetting/store` 服务于 agent 设置的局部 store
|
||
- `src/store/agent` 用于获取当前会话 agent 的 store
|
||
|
||
后者通过 `src/features/AgentSetting/AgentSettings.tsx` 中 `AgentSettings` 组件的 `onConfigChange` 监听并更新当前会话的 agent 配置。
|
||
|
||
#### 更新 AgentSetting/store
|
||
|
||
首先我们更新 initialState,阅读 `src/features/AgentSetting/store/initialState.ts` 后得知初始 agent 配置保存在 `src/const/settings/agent.ts` 中的 `DEFAULT_AGENT_CONFIG`:
|
||
|
||
```diff
|
||
export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
|
||
chatConfig: DEFAULT_AGENT_CHAT_CONFIG,
|
||
model: DEFAULT_MODEL,
|
||
+ openingQuestions: [],
|
||
params: {
|
||
frequency_penalty: 0,
|
||
presence_penalty: 0,
|
||
temperature: 1,
|
||
top_p: 1,
|
||
},
|
||
plugins: [],
|
||
provider: DEFAULT_PROVIDER,
|
||
systemRole: '',
|
||
tts: DEFAUTT_AGENT_TTS_CONFIG,
|
||
};
|
||
```
|
||
|
||
其实你这里不更新都可以,因为 `openingQuestions` 类型本来就是可选的,`openingMessage` 我这里就不更新了。
|
||
|
||
因为我们增加了两个新字段,为了方便在 `src/features/AgentSetting/AgentOpening` 文件夹中组件访问和性能优化,我们在 `src/features/AgentSetting/store/selectors.ts` 增加相关的 selectors:
|
||
|
||
```diff
|
||
+export const DEFAULT_OPENING_QUESTIONS: string[] = [];
|
||
export const selectors = {
|
||
chatConfig,
|
||
+ openingMessage: (s: Store) => s.config.openingMessage,
|
||
+ openingQuestions: (s: Store) => s.config.openingQuestions || DEFAULT_OPENING_QUESTIONS,
|
||
};
|
||
```
|
||
|
||
这里我们就不增加额外的 action 用于更新 agent config 了,因为已有的代码也是直接使用统一的 `setAgentConfig`:
|
||
|
||
```typescript
|
||
export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, get) => ({
|
||
setAgentConfig: (config) => {
|
||
get().dispatchConfig({ config, type: 'update' });
|
||
},
|
||
});
|
||
```
|
||
|
||
#### 更新 store/agent
|
||
|
||
在展示组件中我们使用 `src/store/agent` 获取当前 agent 配置,简单加两个 selectors:
|
||
|
||
更新 `src/store/agent/slices/chat/selectors/agent.ts`:
|
||
|
||
```diff
|
||
+const openingQuestions = (s: AgentStoreState) =>
|
||
+ currentAgentConfig(s).openingQuestions || DEFAULT_OPENING_QUESTIONS;
|
||
+const openingMessage = (s: AgentStoreState) => currentAgentConfig(s).openingMessage || '';
|
||
|
||
export const agentSelectors = {
|
||
// ...
|
||
isInboxSession,
|
||
+ openingMessage,
|
||
+ openingQuestions,
|
||
};
|
||
```
|
||
|
||
### i18n 处理
|
||
|
||
LobeHub 是国际化项目,使用 [react-i18next](https://github.com/i18next/react-i18next) 作为 i18n 框架。新增的 UI 文案需要:
|
||
|
||
1. 在 `src/locales/default/` 对应的 namespace 文件中添加 key(默认语言为英文):
|
||
|
||
```typescript
|
||
// src/locales/default/setting.ts
|
||
export default {
|
||
// ...
|
||
'settingOpening.title': 'Opening Settings',
|
||
'settingOpening.openingMessage.title': 'Opening Message',
|
||
'settingOpening.openingMessage.placeholder': 'Enter a custom opening message...',
|
||
'settingOpening.openingQuestions.title': 'Opening Questions',
|
||
'settingOpening.openingQuestions.placeholder': 'Enter a guiding question',
|
||
'settingOpening.openingQuestions.empty': 'No opening questions yet',
|
||
'settingOpening.openingQuestions.repeat': 'Question already exists',
|
||
};
|
||
```
|
||
|
||
2. 如果新增了 namespace,需要在 `src/locales/default/index.ts` 中导出
|
||
3. 开发预览时手动翻译 `locales/zh-CN/` 和 `locales/en-US/` 对应的 JSON 文件
|
||
4. CI 会自动运行 `pnpm i18n` 生成其他语言的翻译
|
||
|
||
key 的命名规范为扁平的 dot notation:`{feature}.{context}.{action|status}`。
|
||
|
||
### UI 实现和 action 绑定
|
||
|
||
我们这次要新增一个类别的设置。在 `src/features/AgentSetting` 中定义了 agent 的各种设置 UI 组件,增加一个文件夹 `AgentOpening` 存放开场设置相关的组件。
|
||
|
||
以子组件 `OpeningQuestions.tsx` 为例,展示关键逻辑(省略样式代码):
|
||
|
||
```typescript
|
||
// src/features/AgentSetting/AgentOpening/OpeningQuestions.tsx
|
||
'use client';
|
||
|
||
import { memo, useCallback, useMemo, useState } from 'react';
|
||
import { useTranslation } from 'react-i18next';
|
||
|
||
import { useStore } from '../store';
|
||
import { selectors } from '../store/selectors';
|
||
|
||
const OpeningQuestions = memo(() => {
|
||
const { t } = useTranslation('setting');
|
||
const [questionInput, setQuestionInput] = useState('');
|
||
|
||
// 使用 selector 访问对应配置
|
||
const openingQuestions = useStore(selectors.openingQuestions);
|
||
// 使用 action 更新配置
|
||
const updateConfig = useStore((s) => s.setAgentConfig);
|
||
const setQuestions = useCallback(
|
||
(questions: string[]) => {
|
||
updateConfig({ openingQuestions: questions });
|
||
},
|
||
[updateConfig],
|
||
);
|
||
|
||
const addQuestion = useCallback(() => {
|
||
if (!questionInput.trim()) return;
|
||
setQuestions([...openingQuestions, questionInput.trim()]);
|
||
setQuestionInput('');
|
||
}, [openingQuestions, questionInput, setQuestions]);
|
||
|
||
const removeQuestion = useCallback(
|
||
(content: string) => {
|
||
const newQuestions = [...openingQuestions];
|
||
const index = newQuestions.indexOf(content);
|
||
newQuestions.splice(index, 1);
|
||
setQuestions(newQuestions);
|
||
},
|
||
[openingQuestions, setQuestions],
|
||
);
|
||
|
||
// 渲染 Input + SortableList,具体 UI 参考组件库文档
|
||
// ...
|
||
});
|
||
```
|
||
|
||
关键点:
|
||
- 通过 `selectors` 读取 store 中的配置
|
||
- 通过 `setAgentConfig` action 更新配置
|
||
- 使用 `useTranslation('setting')` 获取 i18n 文案
|
||
|
||
同时我们需要将用户设置的开场配置展示出来,这个是在 chat 页面,对应组件在 `src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx`:
|
||
|
||
```typescript
|
||
const WelcomeMessage = () => {
|
||
const { t } = useTranslation('chat');
|
||
|
||
// 从 store/agent 获取当前开场配置
|
||
const openingMessage = useAgentStore(agentSelectors.openingMessage);
|
||
const openingQuestions = useAgentStore(agentSelectors.openingQuestions);
|
||
|
||
const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
|
||
|
||
const message = useMemo(() => {
|
||
// 用户设置了就用用户设置的
|
||
if (openingMessage) return openingMessage;
|
||
return !!meta.description ? agentSystemRoleMsg : agentMsg;
|
||
}, [openingMessage, agentSystemRoleMsg, agentMsg, meta.description]);
|
||
|
||
return openingQuestions.length > 0 ? (
|
||
<Flexbox>
|
||
<ChatItem avatar={meta} message={message} placement="left" />
|
||
{/* 渲染引导性问题 */}
|
||
<OpeningQuestions questions={openingQuestions} />
|
||
</Flexbox>
|
||
) : (
|
||
<ChatItem avatar={meta} message={message} placement="left" />
|
||
);
|
||
};
|
||
```
|
||
|
||
## 五、测试
|
||
|
||
项目使用 Vitest 进行单元测试,相关指南详见 [测试技能文档](https://github.com/lobehub/lobe-chat/blob/main/.agents/skills/testing/SKILL.md)。
|
||
|
||
**运行测试:**
|
||
|
||
```bash
|
||
# 运行指定测试文件(不要运行 bun run test,全量测试耗时很长)
|
||
bunx vitest run --silent='passed-only' '[file-path]'
|
||
|
||
# database 包的测试
|
||
cd packages/database && bunx vitest run --silent='passed-only' '[file]'
|
||
```
|
||
|
||
**添加新功能的测试建议:**
|
||
|
||
由于我们目前两个新的配置字段都是可选的,理论上不更新测试也能跑通。但如果修改了默认配置(如 `DEFAULT_AGENT_CONFIG` 增加了 `openingQuestions` 字段),可能导致一些测试快照不匹配,需要更新。
|
||
|
||
建议先本地跑下相关测试,看哪些失败了再针对性更新。例如:
|
||
|
||
```bash
|
||
bunx vitest run --silent='passed-only' 'src/store/agent/slices/chat/selectors/agent.test.ts'
|
||
```
|
||
|
||
如果只是想确认现有测试是否通过而不想本地跑,也可以直接查看 GitHub Actions 的测试结果。
|
||
|
||
**更多测试场景指南:**
|
||
|
||
- DB Model 测试:`.agents/skills/testing/references/db-model-test.md`
|
||
- Zustand Store Action 测试:`.agents/skills/testing/references/zustand-store-action-test.md`
|
||
- Electron IPC 测试:`.agents/skills/testing/references/electron-ipc-test.md`
|
||
|
||
## 总结
|
||
|
||
以上就是 LobeHub 开场设置功能的完整实现流程,涵盖了从数据库 schema → 数据模型 → Service/Model → Store → i18n → UI → 测试的全链路。开发者可以参考本文档进行相关功能的开发。
|