mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
191f3fe776
* 📝 docs: fix outdated content across the docs site Audited docs/ against the current codebase and fixed stale content that drifted after major refactors (Clerk/NextAuth → Better Auth, src/server → apps/server, src/{config,envs,locales} → packages/*, model-bank updates). - self-hosting/auth: mark removed NextAuth/Clerk pages with deprecation warnings; fix microsoft/zitadel alias claims and the Logto callback path - environment-variables & advanced: correct wrong defaults (S3_SET_ACL, AI_IMAGE_DEFAULT_IMAGE_NUM, index/proxy URLs), drop non-existent env vars and fictional feature flags, fix OIDC config guidance - development: update refactored paths, Better Auth, .mjs configs, remove the MSW section (msw is not a dependency) - providers & model examples: replace retired model ids with in-bank ones; fix comfyui frontmatter corruption - usage: rename Moderator→Orchestrator, GTD→Task, correct shortcut keys, channel capability tables, Feishu/Lark WebSocket mode - platform/migration: fix dead links, docker service name, npx/npm → bun EN and zh-CN pairs updated together. * 📝 docs: fix Logto callback path and AUTH_SECRET deprecation guidance - Revert Logto/Sealos callback URLs to /api/auth/callback/logto to match the runtime SSO redirectURI override - Narrow legacy auth Callout: AUTH_SECRET remains required for Better Auth; mark NEXT_AUTH_SECRET as deprecated in zh-CN * 📝 docs: refresh agent skills for locales, env, auth, and agentRun paths Align OSS agent guidance with post-refactor layout: - packages/locales/src/default + bun run i18n - packages/env/src for env schemas (@/envs alias) - spa-auth shell + src/routes/auth segments - chat agentRun/transports/hetero instead of aiChat * 📝 docs: document APP_URL as required for Better Auth base URL Self-hosted auth uses appEnv.APP_URL as baseURL, not request-header auto-detection. NEXT_PUBLIC_AUTH_URL remains deprecated. * 📝 docs: note self-host search provider setup for web search Built-in lobe-web-browsing needs no client plugin, but self-hosted servers still require SEARCH_PROVIDERS / SEARXNG_URL (default SearXNG). * 📝 docs: point feature guide welcome UI at AgentHome Opening message/questions on the agent chat page live in src/features/AgentHome, not Onboarding WelcomeMessage.
360 lines
14 KiB
Plaintext
360 lines
14 KiB
Plaintext
---
|
|
title: LobeHub Feature Development Complete Guide
|
|
description: A comprehensive guide for developers on implementing features in LobeHub.
|
|
tags:
|
|
- LobeHub
|
|
- Feature Development
|
|
- Developer Guide
|
|
- Postgres
|
|
- Drizzle ORM
|
|
---
|
|
|
|
# LobeHub Feature Development Complete Guide
|
|
|
|
This document aims to guide developers on how to develop a complete feature in LobeHub.
|
|
|
|
We will use [RFC 021 - Custom Assistant Opening Guidance](https://github.com/lobehub/lobehub/discussions/891) as an example to illustrate the complete implementation process.
|
|
|
|
## 1. Update Schema
|
|
|
|
LobeHub uses a PostgreSQL database, with [Drizzle ORM](https://orm.drizzle.team/) to operate the database.
|
|
|
|
All schemas are located in `packages/database/src/schemas/`. We need to adjust the `agents` table to add two fields corresponding to the configuration items:
|
|
|
|
```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
|
|
}),
|
|
);
|
|
|
|
```
|
|
|
|
Note that sometimes we may also need to update the index, but for this feature, we don't have any related performance bottleneck issues, so we don't need to update the index.
|
|
|
|
### Database Migration
|
|
|
|
After adjusting the schema, you need to generate and optimize migration files. See the [Database Migration Guide](https://github.com/lobehub/lobehub/blob/main/.agents/skills/drizzle/references/db-migrations.md) for detailed steps.
|
|
|
|
## 2. Update Data Model
|
|
|
|
Data models are defined in `packages/types/src/`. We don't directly use the types exported from the Drizzle schema (e.g., `typeof agents.$inferInsert`), but instead define independent data models based on frontend requirements.
|
|
|
|
Update the `LobeAgentConfig` type in `packages/types/src/agent/index.ts`:
|
|
|
|
```diff
|
|
export interface LobeAgentConfig {
|
|
// ...
|
|
chatConfig: LobeAgentChatConfig;
|
|
/**
|
|
* The language model used by the agent
|
|
* @default gpt-4o-mini
|
|
*/
|
|
model: string;
|
|
|
|
+ /**
|
|
+ * Opening message
|
|
+ */
|
|
+ openingMessage?: string;
|
|
+ /**
|
|
+ * Opening questions
|
|
+ */
|
|
+ openingQuestions?: string[];
|
|
|
|
/**
|
|
* Language model parameters
|
|
*/
|
|
params: LLMParams;
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## 3. Service / Model Layer Implementation
|
|
|
|
The project is divided into multiple frontend and backend layers by responsibility:
|
|
|
|
```plaintext
|
|
+-------------------+--------------------------------------+------------------------------------------------------+
|
|
| Layer | Location | Responsibility |
|
|
+-------------------+--------------------------------------+------------------------------------------------------+
|
|
| Client Service | src/services/ | Reusable frontend business logic, often multiple tRPC |
|
|
| WebAPI | src/app/(backend)/webapi/ | REST API endpoints |
|
|
| tRPC Router | apps/server/src/routers/ | tRPC entry, validates input, routes to service |
|
|
| Server Service | apps/server/src/services/ | Server-side business logic, with DB access |
|
|
| Server Module | apps/server/src/modules/ | Server-side modules, no direct DB access |
|
|
| Repository | packages/database/src/repositories/ | Complex queries, cross-table operations |
|
|
| DB Model | packages/database/src/models/ | Single-table CRUD operations |
|
|
+-------------------+--------------------------------------+------------------------------------------------------+
|
|
```
|
|
|
|
**Client Service** is frontend code that encapsulates reusable business logic, calling the backend via tRPC client. For example, `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** is the backend entry point (`apps/server/src/routers/lambda/`), validates input and calls Server Service for business logic:
|
|
|
|
```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);
|
|
}),
|
|
});
|
|
```
|
|
|
|
For this feature, `updateSessionConfig` simply merges config without field-level granularity, so none of the layers need modification.
|
|
|
|
## 4. Frontend Implementation
|
|
|
|
### Data Flow Store Implementation
|
|
|
|
LobeHub uses [zustand](https://zustand.docs.pmnd.rs/getting-started/introduction) as the global state management framework. For detailed practices on state management, refer to [State Management Best Practices](/docs/development/state-management/state-management-intro).
|
|
|
|
There are two stores related to the agent:
|
|
|
|
- `src/features/AgentSetting/store` serves the local store for agent settings
|
|
- `src/store/agent` is used to get the current session agent's store
|
|
|
|
The latter listens for and updates the current session's agent configuration through the `onConfigChange` in the `AgentSettings` component in `src/features/AgentSetting/AgentSettings.tsx`.
|
|
|
|
#### Update AgentSetting/store
|
|
|
|
First, we update the initialState. After reading `src/features/AgentSetting/store/initialState.ts`, we learn that the initial agent configuration is saved in `DEFAULT_AGENT_CONFIG` in `packages/const/src/settings/agent.ts`:
|
|
|
|
```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,
|
|
};
|
|
```
|
|
|
|
Actually, you don't even need to update this since the `openingQuestions` type is already optional. I'm not updating `openingMessage` here.
|
|
|
|
Because we've added two new fields, to facilitate component access in `src/features/AgentSetting/AgentOpening` and for performance optimization, we add related selectors in `src/features/AgentSetting/store/selectors.ts`:
|
|
|
|
```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,
|
|
};
|
|
```
|
|
|
|
We won't add additional actions to update the agent config here, as existing code also directly uses the unified `setAgentConfig`:
|
|
|
|
```typescript
|
|
export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, get) => ({
|
|
setAgentConfig: (config) => {
|
|
get().dispatchConfig({ config, type: 'update' });
|
|
},
|
|
});
|
|
```
|
|
|
|
#### Update store/agent
|
|
|
|
In the display component we use `src/store/agent` to get the current agent configuration. Simply add two selectors:
|
|
|
|
Update `src/store/agent/selectors/selectors.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 Handling
|
|
|
|
LobeHub is an internationalized project using [react-i18next](https://github.com/i18next/react-i18next). Newly added UI text needs to:
|
|
|
|
1. Add keys to the corresponding namespace file in `packages/locales/src/default/` (default language is English):
|
|
|
|
```typescript
|
|
// packages/locales/src/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. If a new namespace is added, export it in `packages/locales/src/default/index.ts`
|
|
3. For dev preview: manually translate the corresponding JSON files in `locales/zh-CN/` and `locales/en-US/`
|
|
4. CI will automatically run `bun run i18n` to generate translations for other languages
|
|
|
|
Key naming convention uses flat dot notation: `{feature}.{context}.{action|status}`.
|
|
|
|
### UI Implementation and Action Binding
|
|
|
|
We're adding a new category of settings. In `src/features/AgentSetting`, various UI components for agent settings are defined. We'll add an `AgentOpening` folder for opening settings components.
|
|
|
|
Taking the subcomponent `OpeningQuestions.tsx` as an example, here's the key logic (style code omitted):
|
|
|
|
```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('');
|
|
|
|
// Use selector to access corresponding configuration
|
|
const openingQuestions = useStore(selectors.openingQuestions);
|
|
// Use action to update configuration
|
|
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],
|
|
);
|
|
|
|
// Render Input + SortableList, see component library docs for UI details
|
|
// ...
|
|
});
|
|
```
|
|
|
|
Key points:
|
|
|
|
- Read store config via `selectors`
|
|
- Update config via the `setAgentConfig` action
|
|
- Use `useTranslation('setting')` for i18n text
|
|
|
|
We also need to display the opening configuration set by the user on the chat page. Agent chat uses `src/features/AgentHome` (`AgentInfo.tsx` for `openingMessage`, `OpeningQuestions.tsx` for questions). Group chat has a parallel welcome under `src/routes/(main)/group/features/Conversation/AgentWelcome`. Do **not** use `src/features/Onboarding/Agent/WelcomeMessage.tsx` — that is the onboarding flow only.
|
|
|
|
```typescript
|
|
// src/features/AgentHome/AgentInfo.tsx (simplified)
|
|
const AgentInfo = () => {
|
|
const { t } = useTranslation(['chat', 'welcome']);
|
|
|
|
// Prefer conversation-scoped agent config (openingMessage on agent config)
|
|
const openingMessage = useAgentStore(
|
|
(s) => agentSelectors.getAgentConfigById(agentId)(s)?.openingMessage || '',
|
|
);
|
|
|
|
const message = useMemo(() => {
|
|
if (openingMessage) return openingMessage;
|
|
return t('agentDefaultMessageWithSystemRole', { name: displayTitle });
|
|
}, [openingMessage, displayTitle, t]);
|
|
|
|
// render avatar + Markdown(message) ...
|
|
};
|
|
|
|
// src/features/AgentHome/index.tsx wires AgentInfo + OpeningQuestions
|
|
```
|
|
|
|
## 5. Testing
|
|
|
|
The project uses Vitest for unit testing. See the [Testing Skill Guide](https://github.com/lobehub/lobehub/blob/main/.agents/skills/testing/SKILL.md) for details.
|
|
|
|
**Running tests:**
|
|
|
|
```bash
|
|
# Run specific test file (never run bun run test — full suite is very slow)
|
|
bunx vitest run --silent='passed-only' '[file-path]'
|
|
|
|
# Database package tests
|
|
cd packages/database && bunx vitest run --silent='passed-only' '[file]'
|
|
```
|
|
|
|
**Testing suggestions for new features:**
|
|
|
|
Since our two new configuration fields are both optional, theoretically tests would pass without updates. However, if you modified default config (e.g., added `openingQuestions` to `DEFAULT_AGENT_CONFIG`), some test snapshots may become stale and need updating.
|
|
|
|
It's recommended to run related tests locally first to see which fail, then update as needed. For example:
|
|
|
|
```bash
|
|
bunx vitest run --silent='passed-only' 'src/store/agent/selectors/selectors.test.ts'
|
|
```
|
|
|
|
If you just want to check whether existing tests pass without running locally, you can also check the GitHub Actions test results directly.
|
|
|
|
**More testing scenario guides:**
|
|
|
|
- DB Model testing: `.agents/skills/testing/references/db-model-test.md`
|
|
- Zustand Store Action testing: `.agents/skills/testing/references/zustand-store-action-test.md`
|
|
- Electron IPC testing: `.agents/skills/testing/references/electron-ipc-test.md`
|
|
|
|
## Summary
|
|
|
|
The above is the complete implementation process for the LobeHub opening settings feature, covering the full chain from database schema → data model → Service/Model → Store → i18n → UI → testing. Developers can refer to this document for developing related features.
|