Files
lobehub/docs/development/basic/feature-development.zh-CN.mdx
YuTengjing 463d6c8762 📝 docs: improve development guides to reflect current architecture (#12174)
* 🔧 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 commit 4a87b31. 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 commit 24073f83d3.
2026-02-07 22:29:14 +08:00

365 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 → 测试的全链路。开发者可以参考本文档进行相关功能的开发。