Compare commits

..

1 Commits

Author SHA1 Message Date
arvinxx 4fa6d64df2 add plugin settings 2025-07-29 14:48:46 +08:00
2462 changed files with 36814 additions and 215419 deletions
-183
View File
@@ -1,183 +0,0 @@
---
description: Complete guide for adding a new AI provider documentation to LobeChat
alwaysApply: false
---
# Adding New AI Provider Documentation
This document provides a step-by-step guide for adding documentation for a new AI provider to LobeChat, based on the complete workflow used for adding providers like BFL (Black Forest Labs) and FAL.
## Overview
Adding a new provider requires creating both user-facing documentation and technical configuration files. The process involves:
1. Creating usage documentation (EN + CN)
2. Adding environment variable documentation (EN + CN)
3. Updating Docker configuration files
4. Updating .env.example file
5. Preparing image resources
## Step 1: Create Provider Usage Documentation
Create user-facing documentation that explains how to use the new provider.
### Required Files
Create both English and Chinese versions:
- `docs/usage/providers/{provider-name}.mdx` (English)
- `docs/usage/providers/{provider-name}.zh-CN.mdx` (Chinese)
### Documentation Structure
Follow the structure and format used in existing provider documentation. For reference, see:
- `docs/usage/providers/fal.mdx` (English template)
- `docs/usage/providers/fal.zh-CN.mdx` (Chinese template)
### Key Requirements
- **Images**: Prepare 5-6 screenshots showing the process
- **Cover Image**: Create or obtain a cover image for the provider
- **Accurate URLs**: Use real registration and dashboard URLs
- **Service Type**: Specify whether it's for image generation, text generation, etc.
- **Pricing Warning**: Include pricing information callout
### Important Notes
- **🔒 API Key Security**: Never include real API keys in documentation. Always use placeholder format (e.g., `bfl-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
- **🖼️ Image Hosting**: Use LobeHub's CDN for all images: `hub-apac-1.lobeobjects.space`
## Step 2: Update Environment Variables Documentation
Add the new provider's environment variables to the self-hosting documentation.
### Files to Update
- `docs/self-hosting/environment-variables/model-provider.mdx` (English)
- `docs/self-hosting/environment-variables/model-provider.zh-CN.mdx` (Chinese)
### Content to Add
Add two sections for each provider:
```markdown
### `{PROVIDER}_API_KEY`
- Type: Required
- Description: This is the API key you applied for in the {Provider Name} service.
- Default: -
- Example: `{api-key-format-example}`
### `{PROVIDER}_MODEL_LIST`
- Type: Optional
- Description: Used to control the {Provider Name} model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model. Separate multiple entries with commas. The definition syntax follows the same rules as other providers' model lists.
- Default: `-`
- Example: `-all,+{model-id-1},+{model-id-2}={display-name}`
The above example disables all models first, then enables `{model-id-1}` and `{model-id-2}` (displayed as `{display-name}`).
[model-list]: /docs/self-hosting/advanced/model-list
```
### Important Notes
- **API Key Format**: Use proper UUID format for examples (e.g., `12345678-1234-1234-1234-123456789abc`)
- **Real Model IDs**: Use actual model IDs from the codebase, not placeholders
- **Consistent Naming**: Follow the pattern `{PROVIDER}_API_KEY` and `{PROVIDER}_MODEL_LIST`
## Step 3: Update Docker Configuration Files
Add environment variables to all Docker configuration files to ensure the provider works in containerized deployments.
### Files to Update
All Dockerfile variants must be updated:
- `Dockerfile`
- `Dockerfile.database`
- `Dockerfile.pglite`
### Changes Required
Add the new provider's environment variables at the **end** of the ENV section, just before the final line:
```dockerfile
# Previous providers...
# 302.AI
AI302_API_KEY="" AI302_MODEL_LIST="" \
# {New Provider 1}
{PROVIDER1}_API_KEY="" {PROVIDER1}_MODEL_LIST="" \
# {New Provider 2}
{PROVIDER2}_API_KEY="" {PROVIDER2}_MODEL_LIST=""
```
### Important Rules
- **Position**: Add new providers at the **end** of the list
- **Ordering**: When adding multiple providers, use alphabetical order (e.g., FAL before BFL)
- **Consistency**: Maintain identical ordering across all Dockerfile variants
- **Format**: Follow the pattern `{PROVIDER}_API_KEY="" {PROVIDER}_MODEL_LIST="" \`
## Step 4: Update .env.example File
Add example configuration entries to help users understand how to configure the provider locally.
### File to Update
- `.env.example`
### Content to Add
Add new sections before the "Market Service" section:
```bash
### {Provider Name} ###
# {PROVIDER}_API_KEY={provider-prefix}-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
### Format Guidelines
- **Section Header**: Use `### {Provider Name} ###` format
- **Commented Example**: Use `#` to comment out the example
- **Key Format**: Use appropriate prefix for the provider (e.g., `bfl-`, `fal-`, `sk-`)
- **Position**: Add before the Market Service section
- **Spacing**: Maintain consistent spacing with existing entries
## Step 5: Image Resources
Prepare all necessary image resources for the documentation.
### Required Images
1. **Cover Image**: Provider logo or branded image
2. **API Dashboard Screenshots**: 3-4 screenshots showing API key creation process
3. **LobeChat Configuration Screenshots**: 2-3 screenshots showing provider setup in LobeChat
### Image Guidelines
- **Quality**: Use high-resolution screenshots
- **Consistency**: Maintain consistent styling across all screenshots
- **Privacy**: Remove or blur any sensitive information
- **Format**: Use PNG format for screenshots
- **Hosting**: Use LobeHub's CDN (`hub-apac-1.lobeobjects.space`) for all images
## Checklist
Before submitting your provider documentation:
- [ ] Created both English and Chinese usage documentation
- [ ] Added environment variable documentation (EN + CN)
- [ ] Updated all 3 Dockerfile variants with consistent ordering
- [ ] Updated .env.example with proper key format
- [ ] Prepared all required screenshots and images
- [ ] Used actual model IDs from the codebase
- [ ] Verified no real API keys are included in documentation
- [ ] Used LobeHub CDN for all image hosting
- [ ] Tested the documentation for clarity and accuracy
## Reference
This guide was created based on the implementation of BFL (Black Forest Labs) provider documentation. For a complete example, refer to:
- Commits: `d2da03e1a` (documentation) and `6a2e95868` (environment variables)
- Files: `docs/usage/providers/bfl.mdx`, `docs/usage/providers/bfl.zh-CN.mdx`
- PR: Current branch `tj/feat/bfl-docs`
-175
View File
@@ -1,175 +0,0 @@
---
description: Guide for adding environment variables to configure user settings
alwaysApply: false
---
# Adding Environment Variable for User Settings
Add server-side environment variables to configure default values for user settings.
**Priority**: User Custom > Server Env Var > Hardcoded Default
## Steps
### 1. Define Environment Variable
Create `src/envs/<domain>.ts`:
```typescript
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const get<Domain>Config = () => {
return createEnv({
server: {
YOUR_ENV_VAR: z.coerce.number().min(MIN).max(MAX).optional(),
},
runtimeEnv: {
YOUR_ENV_VAR: process.env.YOUR_ENV_VAR,
},
});
};
export const <domain>Env = get<Domain>Config();
```
### 2. Update Type (Optional)
**Skip this step if the domain field already exists in `GlobalServerConfig`.**
Add to `packages/types/src/serverConfig.ts`:
```typescript
export interface GlobalServerConfig {
<domain>?: {
<settingName>?: <type>;
};
}
```
**Prefer reusing existing types** from `packages/types/src/user/settings` with `PartialDeep`:
```typescript
import { User<Domain>Config } from './user/settings';
export interface GlobalServerConfig {
<domain>?: PartialDeep<User<Domain>Config>;
}
```
### 3. Assemble Server Config (Optional)
**Skip this step if the domain field already exists in server config.**
In `src/server/globalConfig/index.ts`:
```typescript
import { <domain>Env } from '@/envs/<domain>';
import { cleanObject } from '@/utils/object';
export const getServerGlobalConfig = async () => {
const config: GlobalServerConfig = {
// ...
<domain>: cleanObject({
<settingName>: <domain>Env.YOUR_ENV_VAR,
}),
};
return config;
};
```
If the domain already exists, just add the new field to the existing `cleanObject()`:
```typescript
<domain>: cleanObject({
existingField: <domain>Env.EXISTING_VAR,
<settingName>: <domain>Env.YOUR_ENV_VAR, // Add this line
}),
```
### 4. Merge to User Store (Optional)
**Skip this step if the domain field already exists in `serverSettings`.**
In `src/store/user/slices/common/action.ts`, add to `serverSettings`:
```typescript
const serverSettings: PartialDeep<UserSettings> = {
defaultAgent: serverConfig.defaultAgent,
<domain>: serverConfig.<domain>, // Add this line
// ...
};
```
### 5. Update .env.example
```bash
# <Description> (range/options, default: X)
# YOUR_ENV_VAR=<example>
```
### 6. Update Documentation
Update both English and Chinese documentation:
- `docs/self-hosting/environment-variables/basic.mdx`
- `docs/self-hosting/environment-variables/basic.zh-CN.mdx`
Add new section or subsection with environment variable details (type, description, default, example, range/constraints).
## Type Reuse
**Prefer reusing existing types** from `packages/types/src/user/settings` instead of defining inline types in `serverConfig.ts`.
```typescript
// ✅ Good - reuse existing type
import { UserImageConfig } from './user/settings';
export interface GlobalServerConfig {
image?: PartialDeep<UserImageConfig>;
}
// ❌ Bad - inline type definition
export interface GlobalServerConfig {
image?: {
defaultImageNum?: number;
};
}
```
## Example: AI_IMAGE_DEFAULT_IMAGE_NUM
```typescript
// src/envs/image.ts
export const getImageConfig = () => {
return createEnv({
server: {
AI_IMAGE_DEFAULT_IMAGE_NUM: z.coerce.number().min(1).max(20).optional(),
},
runtimeEnv: {
AI_IMAGE_DEFAULT_IMAGE_NUM: process.env.AI_IMAGE_DEFAULT_IMAGE_NUM,
},
});
};
// packages/types/src/serverConfig.ts
import { UserImageConfig } from './user/settings';
export interface GlobalServerConfig {
image?: PartialDeep<UserImageConfig>;
}
// src/server/globalConfig/index.ts
image: cleanObject({
defaultImageNum: imageEnv.AI_IMAGE_DEFAULT_IMAGE_NUM,
}),
// src/store/user/slices/common/action.ts
const serverSettings: PartialDeep<UserSettings> = {
image: serverConfig.image,
// ...
};
// .env.example
# AI_IMAGE_DEFAULT_IMAGE_NUM=4
```
+176
View File
@@ -0,0 +1,176 @@
---
description:
globs: src/services/**/*,src/database/**/*,src/server/**/*
alwaysApply: false
---
# LobeChat 后端技术架构指南
本指南旨在阐述 LobeChat 项目的后端分层架构,重点介绍各核心目录的职责以及它们之间的协作方式。
## 目录结构映射
```
src/
├── server/
│ ├── routers/ # tRPC API 路由定义
│ └── services/ # 业务逻辑服务层
│ └── */impls/ # 平台特定实现
├── database/
│ ├── models/ # 数据模型 (单表 CRUD)
│ ├── repositories/ # 仓库层 (复杂查询/聚合)
│ └── schemas/ # Drizzle ORM 表定义
└── services/ # 客户端服务 (调用 tRPC 或直接访问 Model)
```
## 核心架构分层
LobeChat 的后端设计注重模块化、可测试性和灵活性,以适应不同的运行环境(如浏览器端 PGLite、服务端远程 PostgreSQL 以及 Electron 桌面应用)。
其主要分层如下:
1. 客户端服务层 (`src/services`):
- 位于 src/services/。
- 这是客户端业务逻辑的核心层,负责封装各种业务操作和数据处理逻辑。
- 环境适配: 根据不同的运行环境,服务层会选择合适的数据访问方式:
- 本地数据库模式: 直接调用 `Model` 层进行数据操作,适用于浏览器 PGLite 和本地 Electron 应用。
- 远程数据库模式: 通过 `tRPC` 客户端调用服务端 API,适用于需要云同步的场景。
- 类型转换: 对于简单的数据类型转换,直接在此层进行类型断言,如 `this.pluginModel.query() as Promise<LobeTool[]>`
- 每个服务模块通常包含 `client.ts`(本地模式)、`server.ts`(远程模式)和 `type.ts`(接口定义)文件,在实现时应该确保本地模式和远程模式业务逻辑实现一致,只是数据库不同。
2. API 接口层 (`TRPC`):
- 位于 src/server/routers/
- 使用 `tRPC` 构建类型安全的 API。Router 根据运行时环境(如 Edge Functions, Node.js Lambda)进行组织。
- 负责接收客户端请求,并将其路由到相应的 `Service` 层进行处理。
- 新建 lambda 端点时可以参考 src/server/routers/lambda/\_template.ts
3. 仓库层 (`Repositories`):
- 位于 src/database/repositories/。
- 主要处理复杂的跨表查询和数据聚合逻辑,特别是当需要从多个 `Model` 获取数据并进行组合时。
- 与 `Model` 层不同,`Repository` 层专注于复杂的业务查询场景,而不涉及简单的领域模型转换。
- 当业务逻辑涉及多表关联、复杂的数据统计或需要事务处理时,会使用 `Repository` 层。
- 如果数据操作简单(仅涉及单个 `Model`),则通常直接在 `src/services` 层调用 `Model` 并进行简单的类型断言。
4. 模型层 (`Models`):
- 位于 src/database/models/ (例如 src/database/models/plugin.ts 和 src/database/models/document.ts)。
- 提供对数据库中各个表(由 src/database/schemas/ 中的 Drizzle ORM schema 定义)的基本 CRUD (创建、读取、更新、删除) 操作和简单的查询能力。
- `Model` 类专注于单个数据表的直接操作,不涉及复杂的领域模型转换,这些转换通常在上层的 `src/services` 中通过类型断言完成。
- model(例如 Topic 层接口经常需要从对应的 schema 层导入 NewTopic 和 TopicItem
- 创建新的 model 时可以参考 src/database/models/\_template.ts
5. 数据库 (`Database`):
- 客户端模式 (浏览器/PWA): 使用 PGLite (基于 WASM 的 PostgreSQL),数据存储在用户浏览器本地。
- 服务端模式 (云部署): 使用远程 PostgreSQL 数据库。
- Electron 桌面应用:
- Electron 客户端会启动一个本地 Node.js 服务。
- 本地服务通过 `tRPC` 与 Electron 的渲染进程通信。
- 数据库选择依赖于是否开启云同步功能:
- 云同步开启: 连接到远程 PostgreSQL 数据库。
- 云同步关闭: 使用 PGLite (通过 Node.js 的 WASM 实现) 在本地存储数据。
## 数据流向说明
### 浏览器/PWA 模式
```
UI (React) → Zustand action -> Client Service → Model Layer → PGLite (本地数据库)
```
### 服务端模式
```
UI (React) → Zustand action → Client Service -> TRPC Client → TRPC Routers → Repositories/Models → Remote PostgreSQL
```
### Electron 桌面应用模式
```
UI (Electron Renderer) → Zustand action → Client Service -> TRPC Client → 本地 Node.js 服务 → TRPC Routers → Repositories/Models → PGLite/Remote PostgreSQL (取决于云同步设置)
```
## 服务层 (Server Services)
- 位于 src/server/services/。
- 核心职责是封装独立的、可复用的业务逻辑单元。这些服务应易于测试。
- 平台差异抽象: 一个关键特性是通过其内部的 `impls` 子目录(例如 src/server/services/file/impls 包含 s3.ts 和 local.ts)来抹平不同运行环境带来的差异(例如云端使用 S3 存储,桌面版使用本地文件系统)。这使得上层(如 `tRPC` routers)无需关心底层具体实现。
- 目标是使 `tRPC` router 层的逻辑尽可能纯粹,专注于请求处理和业务流程编排。
- 服务可能会调用 `Repository` 层或直接调用 `Model` 层进行数据持久化和检索,也可能调用其他服务。
## 最佳实践 (Best Practices)
### 数据库操作封装原则
**连续的数据库操作应该封装到 Model 层**
当业务逻辑涉及多个相关的数据库操作时,建议将这些操作封装到 Model 层中,而不是在上层(Service 或 Router 层)中进行多次数据库调用。
**优势:**
- **代码复用**: Client DB 环境的 service 实现和 Server DB 的 lambda 层实现可以复用相同的 Model 方法
- **事务一致性**: 相关的数据库操作可以在同一个方法中管理,便于维护数据一致性
- **性能优化**: 减少数据库连接次数,提高查询效率
- **职责清晰**: Model 层专注数据访问,上层专注业务协调
**示例:**
```typescript
// ✅ 推荐:在 Model 层封装连续的数据库操作
class GenerationBatchModel {
async delete(id: string): Promise<{ deletedBatch: BatchItem; thumbnailUrls: string[] }> {
// 1. 查询相关数据
const batchWithGenerations = await this.db.query.generationBatches.findFirst({...});
// 2. 收集需要处理的数据
const thumbnailUrls = [...];
// 3. 执行删除操作
const [deletedBatch] = await this.db.delete(generationBatches)...;
return { deletedBatch, thumbnailUrls };
}
}
// ✅ 上层使用简洁
const { thumbnailUrls } = await model.delete(id);
await fileService.deleteFiles(thumbnailUrls);
```
### 文件操作与数据库操作的执行顺序
**删除操作原则:数据库删除在前,文件删除在后**
当业务逻辑同时涉及数据库记录和文件系统操作时,应该遵循"数据库优先"的原则。
**原因:**
- **用户体验优先**: 如果先删除文件再删除数据库记录,可能出现文件已删除但数据库记录仍存在的情况,用户访问时会遇到文件不存在的错误
- **影响程度较小**: 如果先删除数据库记录再删除文件,即使文件删除失败,用户也看不到这个记录,只是造成一些存储空间浪费,对用户体验影响更小
- **数据一致性**: 数据库记录是业务逻辑的核心,应该优先保证其一致性
**示例:**
```typescript
// ✅ 推荐:先删除数据库记录,再删除文件
async deleteGeneration(id: string) {
// 1. 先删除数据库记录
const deletedGeneration = await generationModel.delete(id);
// 2. 再删除相关文件
if (deletedGeneration.asset?.thumbnailUrl) {
await fileService.deleteFile(deletedGeneration.asset.thumbnailUrl);
}
}
// ❌ 不推荐:先删除文件
async deleteGeneration(id: string) {
const generation = await generationModel.findById(id);
// 如果这里删除成功,但后面数据库删除失败,用户会遇到访问错误
await fileService.deleteFile(generation.asset.thumbnailUrl);
await generationModel.delete(id); // 可能失败
}
```
**创建操作原则:数据库创建在前,文件操作在后**
创建操作同样应该优先处理数据库记录,确保数据的一致性和完整性。
+75
View File
@@ -0,0 +1,75 @@
---
description: How to code review
globs:
alwaysApply: false
---
# Role Description
- You are a senior full-stack engineer skilled in performance optimization, security, and design systems.
- You excel at reviewing code and providing constructive feedback.
- Your task is to review submitted Git diffs **in Chinese** and return a structured review report.
- Review style: concise, direct, focused on what matters most, with actionable suggestions.
## Before the Review
Gather the modified code and context. Please strictly follow the process below:
1. Use `read_file` to read [package.json](mdc:package.json)
2. Use terminal to run command `git diff HEAD | cat` to obtain the diff and list the changed files. If you recieived empty result, run the same command once more.
3. Use `read_file` to open each changed file.
4. Use `read_file` to read [rules-attach.mdc](mdc:.cursor/rules/rules-attach.mdc). Even if you think it's unnecessary, you must read it.
5. combine changed files, step3 and `agent_requestable_workspace_rules`, list the rules which need to read
6. Use `read_file` to read the rules list in step 5
## Review
### Code Style
read [typescript.mdc](mdc:.cursor/rules/typescript.mdc) to learn the project's code style.
- Ensure JSDoc comments accurately reflect the implementation; update them when needed.
- Look for opportunities to simplify or modernize code with the latest JavaScript/TypeScript features.
- Prefer `async`/`await` over callbacks or chained `.then` promises.
- Use consistent, descriptive naming—avoid obscure abbreviations.
- Replace magic numbers or strings with well-named constants.
- Use semantically meaningful variable, function, and class names.
- Ignore purely formatting issues and other autofixable lint problems.
### Code Optimization
- Prefer `for…of` loops to index-based `for` loops when feasible.
- Decide whether callbacks should be **debounced** or **throttled**.
- Use components from `@lobehub/ui`, Ant Design, or the existing design system instead of raw HTML tags (e.g., `Button` vs. `button`).
- reuse npm packages already installed (e.g., `lodash/omit`) rather than reinventing the wheel.
- Design for dark mode and mobile responsiveness:
- Use the `antd-style` token system instead of hard-coded colors.
- Select the proper component variants.
- Performance considerations:
- Where safe, convert sequential async flows to concurrent ones with `Promise.all`, `Promise.race`, etc.
- Query only the required columns from a database rather than selecting entire rows.
### Obvious Bugs
- Do not silently swallow errors in `catch` blocks; at minimum, log them.
- Revert temporary code used only for testing (e.g., debug logs, temporary configs).
- Remove empty handlers (e.g., an empty `onClick`).
- Confirm the UI degrades gracefully for unauthenticated users.
- Don't leave any debug logs in the code (except when using the `debug` module properly).
- When using the `debug` module, avoid `import { log } from 'debug'` as it logs directly to console. Use proper debug namespaces instead.
- Check logs for sensitive information like api key, etc
## After the Review: output
1. Summary
- Start with a brief explanation of what the change set does.
- Summarize the changes for each modified file (or logical group).
2. Comments Issues
- List the most critical issues first.
- Use an ordered list, which will be convenient for me to reference later.
- For each issue:
- Mark severity tag (`❌ Must fix`, `⚠️ Should fix`, `💅 Nitpick`)
- Provode file path to the relevant file.
- Provide recommended fix
- End with a **git commit** command, instruct the author to run it.
- We use gitmoji to label commit messages, format: [emoji] <type>(<scope>): <subject>
+104
View File
@@ -0,0 +1,104 @@
---
description:
globs:
alwaysApply: true
---
# Guide to Optimize Output(Response) Rendering
## File Path and Code Symbol Rendering
- When rendering file paths, use backtick wrapping instead of markdown links so they can be parsed as clickable links in Cursor IDE.
- Good: `src/components/Button.tsx`
- Bad: [src/components/Button.tsx](src/components/Button.tsx)
- Don't use line and column number in file path, this will make file path not clickable in Cursor IDE.
- Good: `src/components/Button.tsx` `10:20` (add a space between the file path and the line and column number)
- Bad: `src/components/Button.tsx:10:20`
- When rendering functions, variables, or other code symbols, use backtick wrapping so they can be parsed as navigable links in Cursor IDE
- Good: The `useState` hook in `MyComponent`
- Bad: The useState hook in MyComponent
## Markdown Render
- don't use br tag to wrap in table cell
## Terminal Command Output
- If terminal commands don't produce output, it's likely due to paging issues. Try piping the command to `cat` to ensure full output is displayed.
- Good: `git show commit_hash -- file.txt | cat`
- Good: `git log --oneline | cat`
- Reason: Some git commands use pagers by default, which may prevent output from being captured properly
## Mermaid Diagram Generation: Strict Syntax Validation Checklist
Before producing any Mermaid diagram, you **must** compare your final code line-by-line against every rule in the following checklist to ensure 100% compliance. **This is a hard requirement and takes precedence over other stylistic suggestions.** Please follow these action steps:
1. Plan the Mermaid diagram logic in your mind.
2. Write the Mermaid code.
3. **Carefully review your code line-by-line against the entire checklist below.**
4. Fix any aspect of your code that doesn't comply.
5. Use the `validateMermaid` tool to check your code for syntax errors. Only proceed if validation passes.
6. Output the final, compliant, and copy-ready Mermaid code block.
7. Immediately after the Mermaid code block, output:
I have checked that the Mermaid syntax fully complies with the validation checklist.
---
### Checklist Details
#### Rule 1: Edge Labels Must Be Plain Text Only
> **Essence:** Anything inside `|...|` must contain pure, unformatted text. Absolutely NO Markdown, list markers, or parentheses/brackets allowed—these often cause rendering failures.
- **✅ Do:** `A -->|Process plain text data| B`
- **❌ Don't:** `A -->|1. Ordered list item| B` (No numbered lists)
- **❌ Don't:** `CC --"1. fetch('/api/...')"--> API` (No square brackets)
- **❌ Don't:** `A -->|- Unordered list item| B` (No hyphen lists)
- **❌ Don't:** `A -->|Transform (important)| B` (No parentheses)
- **❌ Don't:** `A -->|Transform [important]| B` (No square brackets)
#### Rule 2: Node Definition Handle Special Characters with Care
> **Essence:** When node text or subgraph titles contain special characters like `()` or `[]`, wrap the text in quotes to avoid conflicts with Mermaid shape syntax.
- **When your node text includes parentheses (e.g., 'React (JSX)'):**
- **✅ Do:** `I_REACT["<b>React component (JSX)</b>"]` (Quotes wrap all text)
- **❌ Don't:** `I_REACT(<b>React component (JSX)</b>)` (Wrong, Mermaid parses this as a shape)
- **❌ Don't:** `subgraph Plugin Features (Plugins)` (Wrong, subgraph titles with parentheses must also be wrapped in quotes)
#### Rule 3: Double Quotes in Text Must Be Escaped
> **Essence:** Use `&quot;` for double quotes **inside node text**.
- **✅ Do:** `A[This node contains &quot;quotes&quot;]`
- **❌ Don't:** `A[This node contains "quotes"]`
#### Rule 4: All Formatting Must Use HTML Tags (NOT Markdown!)
> **Essence:** For newlines, bold, and other text formatting in nodes, use HTML tags only. Markdown is not supported.
- **✅ Do (robust):** `A["<b>Bold</b> and <code>code</code><br>This is a new line"]`
- **❌ Don't (not rendered):** `C["# This is a heading"]`
- **❌ Don't (not rendered):** ``C["`const` means constant"]``
- **⚠️ Warning (unreliable):** `B["Markdown **bold** might sometimes work but DON'T rely on it"]`
#### Rule 5: No HTML Tags for Participants and Message Labels (Sequence Diagrams)
> **Important Addition:**
> In Mermaid sequence diagrams, you MUST NOT use any HTML tags (such as `<b>`, `<code>`, etc.) in:
>
> - `participant` display names (`as` part)
> - Message labels (the text after `:` in diagram flows)
>
> These tags are generally not rendered—they may appear as-is or cause compatibility issues.
- **✅ Do:** `participant A as Client`
- **❌ Don't:** `participant A as <b>Client</b>`
- **✅ Do:** `A->>B: 1. Establish connection`
- **❌ Don't:** `A->>B: 1. <code>Establish connection</code>`
---
**Validate each Mermaid code block by running it through the `validateMermaid` tool before delivering your output!**
-25
View File
@@ -1,25 +0,0 @@
---
globs: packages/database/migrations/**/*
alwaysApply: false
---
# Database Migrations Guide
## Defensive Programming - Use Idempotent Clauses
Always use defensive clauses to make migrations idempotent:
```sql
-- ✅ Good: Idempotent operations
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "avatar" text;
DROP TABLE IF EXISTS "old_table";
CREATE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
ALTER TABLE "posts" DROP COLUMN IF EXISTS "deprecated_field";
-- ❌ Bad: Non-idempotent operations
ALTER TABLE "users" ADD COLUMN "avatar" text;
DROP TABLE "old_table";
CREATE INDEX "users_email_idx" ON "users" ("email");
```
**Important**: After modifying migration SQL (e.g., adding `IF NOT EXISTS` clauses), run `bun run db:generate-client` to update the hash in `packages/database/src/core/migrations.json`.
+2 -2
View File
@@ -1,6 +1,6 @@
---
description: 包含添加 console.log 日志请求时
globs:
description: 包含添加 debug 日志请求时
globs:
alwaysApply: false
---
# Debug 包使用指南
+193
View File
@@ -0,0 +1,193 @@
---
description: Debug 调试指南
globs:
alwaysApply: false
---
# Debug 调试指南
## 💡 调试流程概览
当遇到问题时,请按照以下优先级进行处理:
1. **快速判断** - 对于熟悉的错误,直接提供解决方案
2. **信息收集** - 使用工具搜索相关代码和配置
3. **网络搜索** - 查找现有解决方案
4. **定位调试** - 添加日志进行问题定位
5. **临时方案** - 如果找不到根本解决方案,提供临时解决方案
6. **解决实施** - 提供可维护的最终解决方案
## 🔍 错误信息分析
### 错误来源识别
错误信息可能来自:
- **Terminal 输出** - 构建、运行时错误
- **浏览器控制台** - 前端 JavaScript 错误
- **开发工具** - ESLint、TypeScript、测试框架等
- **服务器日志** - API、数据库连接等后端错误
- **截图或文本** - 用户直接提供的错误信息
## 🛠️ 信息收集工具
### 代码搜索工具
使用以下工具收集相关信息,并根据场景选择最合适的工具:
- **`codebase_search` (语义搜索)**
- **何时使用**: 当你不确定具体的代码实现,想要寻找相关概念、功能或逻辑时。
- **示例**: `查询"文件上传"功能的实现`
- **`grep_search` (精确/正则搜索)**
- **何时使用**: 当你知道要查找的确切字符串、函数名、变量名或一个特定的模式时。
- **示例**: `查找所有使用了 'useState' 的地方`
- **`file_search` (文件搜索)**
- **何时使用**: 当你知道文件名的一部分,需要快速定位文件时。
- **示例**: `查找 'Button.tsx' 组件`
- **`read_file` (内容读取)**
- **何时使用**: 在定位到具体文件后,用于查看其完整内容和上下文。
- **`web_search` (网络搜索)**
- **何时使用**: 当错误信息可能与第三方库、API 或常见问题相关时,用于获取外部信息。
### 环境与依赖检查
- **检查 `package.json`**: 查看 `scripts` 了解项目如何运行、构建和测试。查看 `dependencies` 和 `devDependencies` 确认库版本,版本冲突有时是问题的根源。
- **运行测试**: 使用 `ni vitest` 运行单元测试和集成测试,这可以快速定位功能回归或组件错误。
### 项目特定搜索目标
针对 lobe-chat 项目,重点关注:
- **配置文件**: [package.json](mdc:package.json), [next.config.mjs](mdc:next.config.mjs)
- **核心功能**: `src/features/` 下的相关模块
- **状态管理**: `src/store/` 下的 Zustand stores
- **数据库**: `src/database/` 和 `src/migrations/`
- **类型定义**: `src/types/` 下的类型文件
- **服务层**: `src/services/` 下的 API 服务
- **启动流程**: [apps/desktop/src/main/core/App.ts](mdc:apps/desktop/src/main/core/App.ts) - 了解应用启动流程
## 🌐 网络搜索策略
### 搜索顺序优先级
1. **和问题相关的项目的 github issue**
2. **技术社区**
- Stack Overflow
- GitHub Discussions
- Reddit
3. **官方文档**
- 使用 `mcp_context7_resolve-library-id` 和 `mcp_context7_get-library-docs` 工具
- 查阅官方文档网站
### 搜索关键词策略
- **错误信息**: 完整的错误消息
- **技术栈**: "Next.js 15" + "error message"
- **上下文**: 添加功能相关的关键词
## 🔧 问题定位与结构化思考
如果问题比较复杂,我们要按照先定位问题,再解决问题的大方向进行。
### 结构化思考工具
对于复杂或多步骤的调试任务,使用 `mcp_sequential-thinking_sequentialthinking` 工具来结构化思考过程。这有助于:
- **分解问题**: 将大问题拆解成可管理的小步骤。
- **清晰追踪**: 记录每一步的发现和决策,避免遗漏。
- **自我修正**: 在过程中评估和调整调试路径。
### 日志调试
在问题产生的路径上添加日志,可以简单使用 `console.log` 或者参考 [debug-usage.mdc](mdc:.cursor/rules/debug-usage.mdc) 使用 `debug` 模块。添加完日志后,请求我运行相关的代码并提供关键输出和错误信息。
### 引导式交互调试
虽然我无法直接操作浏览器开发者工具,但我可以引导你进行交互式调试:
1. **设置断点**: 我会告诉你可以在哪些关键代码行设置断点。
2. **检查变量**: 我会请你在断点处检查特定变量的值或 `props`/`state`。
3. **分析调用栈**: 我会请你提供调用栈信息,以帮助理解代码执行流程。
## 💡 临时解决方案策略
当无法找到根本解决方案时,提供临时解决方案:
### 临时方案准则
- **快速修复** - 优先让功能可用
- **最小修改** - 减少对现有代码的影响
- **清晰标记** - 明确标注这是临时方案
- **后续计划** - 说明后续如何找到更好的解决方案
### 临时方案模板
```markdown
## 临时解决方案 ⚠️
**问题**: [简要描述问题]
**临时修复**:
[具体的临时修复步骤]
**风险说明**:
- [可能的副作用或限制]
- [需要注意的事项]
**后续计划**:
- [ ] 深入调研根本原因
- [ ] 寻找更优雅的解决方案
- [ ] 监控是否有其他影响
```
## ✅ 解决方案准则
### 方案质量标准
提供的解决方案应该:
- **✅ 低侵入性** - 最小化对现有代码的修改
- **✅ 可维护性** - 易于理解和后续维护
- **✅ 类型安全** - 符合 TypeScript 规范
- **✅ 最佳实践** - 遵循项目的编码规范
- **✅ 测试友好** - 便于编写和运行测试
- **❌ 避免长期 Hack** - 临时方案可以 hack,但要明确标注
### 解决方案模板
```markdown
## 问题原因
[简要说明问题产生的根本原因]
## 解决方案
[详细的解决步骤]
## 代码修改
[具体的代码变更]
## 验证方法
[如何验证问题已解决]
## 预防措施
[如何避免类似问题再次发生]
```
## 🔄 迭代调试流程
如果初次解决方案无效:
1. **重新收集信息** - 基于新的错误信息搜索
2. **深入代码分析** - 查看更多相关代码文件
3. **运行相关测试** - 编写或运行一个失败的测试来稳定复现问题。
4. **扩大搜索范围** - 搜索更广泛的相关问题
5. **请求更多日志** - 添加更详细的调试信息
6. **提供临时方案** - 如果根本解决方案复杂,先提供临时修复
7. **分解问题** - 将复杂问题拆解为更小的子问题
+8
View File
@@ -0,0 +1,8 @@
---
description:
globs: src/database/models/**/*
alwaysApply: false
---
1. first read [lobe-chat-backend-architecture.mdc](mdc:.cursor/rules/lobe-chat-backend-architecture.mdc)
2. refer to the [_template.ts](mdc:src/database/models/_template.ts) to create new model
3. if an operation involves multiple models or complex queries, consider defining it in the `repositories` layer under `src/database/repositories/`
-35
View File
@@ -1,35 +0,0 @@
---
description: Explain how group chat works in LobeHub (Multi-agent orchestratoin)
globs:
alwaysApply: false
---
This rule explains how group chat (multi-agent orchestration) works. Not confused with session group, which is a organization method to manage session.
## Key points
- A supervisor will devide who and how will speak next
- Each agent will speak just like in single chat (if was asked to speak)
- Not coufused with session group
## Related Files
- src/store/chat/slices/message/supervisor.ts
- src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts
- src/prompts/groupChat/index.ts (All prompts here)
## Snippets
```tsx
// Detect whether in group chat
const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
// Member actions
const addAgentsToGroup = useChatGroupStore((s) => s.addAgentsToGroup);
const removeAgentFromGroup = useChatGroupStore((s) => s.removeAgentFromGroup);
const persistReorder = useChatGroupStore((s) => s.reorderGroupMembers);
// Get group info
const groupConfig = useChatGroupStore(chatGroupSelectors.currentGroupConfig);
const currentGroupMemebers = useSessionStore(sessionSelectors.currentGroupAgents);
```
+95 -96
View File
@@ -1,116 +1,119 @@
---
globs: *.tsx
description: i18n workflow and troubleshooting
globs:
alwaysApply: false
---
# LobeChat Internationalization Guide
# LobeChat 国际化指南
## Key Points
## 架构概览
- Default language: Chinese (zh-CN) as the source language
- Supported languages: 18 languages including English, Japanese, Korean, Arabic, etc.
- Framework: react-i18next with Next.js app router
- Translation automation: @lobehub/i18n-cli for automatic translation, config file: .i18nrc.js
- Never manually modify any json file. You can only modify files in `default` folder
LobeChat 使用 react-i18next 进行国际化,采用良好的命名空间架构:
## Directory Structure
- 默认语言:中文(zh-CN),作为源语言
- 支持语言:18 种语言,包括英语、日语、韩语、阿拉伯语等
- 框架:react-i18next 配合 Next.js app router
- 翻译自动化:@lobehub/i18n-cli 用于自动翻译,配置文件:.i18nrc.js
## 目录结构
```
src/locales/
├── default/ # Source language files (zh-CN)
│ ├── index.ts # Namespace exports
│ ├── common.ts # Common translations
│ ├── chat.ts # Chat-related translations
│ ├── setting.ts # Settings translations
│ └── ... # Other namespace files
└── resources.ts # Type definitions and language configuration
├── default/ # 源语言文件(zh-CN
│ ├── index.ts # 命名空间导出
│ ├── common.ts # 通用翻译
│ ├── chat.ts # 聊天相关翻译
│ ├── setting.ts # 设置翻译
│ └── ... # 其他命名空间文件
└── resources.ts # 类型定义和语言配置
locales/ # Translation files
├── en-US/ # English translations
│ ├── common.json # Common translations
│ ├── chat.json # Chat translations
│ ├── setting.json # Settings translations
│ └── ... # Other namespace JSON files
├── ja-JP/ # Japanese translations
locales/ # 翻译文件
├── en-US/ # 英语翻译
│ ├── common.json # 通用翻译
│ ├── chat.json # 聊天翻译
│ ├── setting.json # 设置翻译
│ └── ... # 其他命名空间 JSON 文件
├── ja-JP/ # 日语翻译
│ ├── common.json
│ ├── chat.json
│ └── ...
└── ... # Other language folders
└── ... # 其他语言文件夹
```
## Workflow for Adding New Translations
## 添加新翻译的工作流程
### 1. Adding New Translation Keys
### 1. 添加新的翻译键
Step 1: Add translation keys in the corresponding namespace files under src/locales/default directory
第一步:在 src/locales/default 目录下的相应命名空间文件中添加翻译键
```typescript
// Example: src/locales/default/common.ts
// 示例:src/locales/default/common.ts
export default {
// ... existing keys
newFeature: {
title: '新功能标题',
description: '功能描述文案',
button: '操作按钮',
},
// ... 现有键
newFeature: {
title: "新功能标题",
description: "功能描述文案",
button: "操作按钮",
},
};
```
Step 2: If creating a new namespace, export it in src/locales/default/index.ts
第二步:如果创建新命名空间,需要在 src/locales/default/index.ts 中导出
```typescript
import newNamespace from './newNamespace';
import newNamespace from "./newNamespace";
const resources = {
// ... existing namespaces
newNamespace,
// ... 现有命名空间
newNamespace,
} as const;
```
### 2. Translation Process
### 2. 翻译过程
Development mode:
开发模式:
Generally, you don't need to help me run the automatic translation tool as it takes a long time. I'll run it myself when needed. However, to see immediate results, you still need to translate `locales/zh-CN/namespace.json` first, no need to translate other languages.
一般情况下不需要你帮我跑自动翻译工具,跑一次很久,需要的时候我会自己跑。
但是为了立马能看到效果,还是需要先翻译 `locales/zh-CN/namespace.json`,不需要翻译其它语言。
Production mode:
生产模式:
```bash
# Generate translations for all languages
# 为所有语言生成翻译
npm run i18n
```
## Usage in Components
## 在组件中使用
### Basic Usage
### 基本用法
```tsx
import { useTranslation } from 'react-i18next';
import { useTranslation } from "react-i18next";
const MyComponent = () => {
const { t } = useTranslation('common');
const { t } = useTranslation("common");
return (
<div>
<h1>{t('newFeature.title')}</h1>
<p>{t('newFeature.description')}</p>
<button>{t('newFeature.button')}</button>
</div>
);
return (
<div>
<h1>{t("newFeature.title")}</h1>
<p>{t("newFeature.description")}</p>
<button>{t("newFeature.button")}</button>
</div>
);
};
```
### Usage with Parameters
### 带参数的用法
```tsx
const { t } = useTranslation('common');
const { t } = useTranslation("common");
<p>{t('welcome.message', { name: 'John' })}</p>;
<p>{t("welcome.message", { name: "John" })}</p>;
// Corresponding language file:
// welcome: { message: 'Welcome {{name}}!' }
// 对应的语言文件:
// welcome: { message: '欢迎 {{name}} 使用!' }
```
### Multiple Namespaces
### 多个命名空间
```tsx
const { t } = useTranslation(['common', 'chat']);
@@ -119,63 +122,59 @@ const { t } = useTranslation(['common', 'chat']);
<span>{t('chat:typing')}</span>
```
## Type Safety
## 类型安全
The project uses TypeScript to implement type-safe translations, with types automatically generated from src/locales/resources.ts:
项目使用 TypeScript 实现类型安全的翻译,类型从 src/locales/resources.ts 自动生成:
```typescript
import type { DefaultResources, Locales, NS } from '@/locales/resources';
import type { DefaultResources, NS, Locales } from "@/locales/resources";
// Available types:
// - NS: Available namespace keys ('common' | 'chat' | 'setting' | ...)
// - Locales: Supported language codes ('en-US' | 'zh-CN' | 'ja-JP' | ...)
// 可用类型:
// - NS: 可用命名空间键 ('common' | 'chat' | 'setting' | ...)
// - Locales: 支持的语言代码 ('en-US' | 'zh-CN' | 'ja-JP' | ...)
const namespace: NS = 'common';
const locale: Locales = 'en-US';
const namespace: NS = "common";
const locale: Locales = "en-US";
```
## Best Practices
## 最佳实践
### 1. Namespace Organization
### 1. 命名空间组织
- common: Shared UI elements (buttons, labels, actions)
- chat: Chat-specific functionality
- setting: Configuration and settings
- error: Error messages and handling
- [feature]: Feature-specific or page-specific namespaces
- components: Reusable component text
- common: 共享 UI 元素(按钮、标签、操作)
- chat: 聊天特定功能
- setting: 配置和设置
- error: 错误消息和处理
- [feature]: 功能特定或页面特定的命名空间
- components: 可复用组件文案
### 2. Key Naming Conventions
### 2. 键命名约定
```typescript
// ✅ Good: Hierarchical structure
// ✅ 好:层次结构
export default {
modal: {
confirm: {
title: '确认操作',
message: '确定要执行此操作吗?',
actions: {
confirm: '确认',
cancel: '取消',
},
modal: {
confirm: {
title: "确认操作",
message: "确定要执行此操作吗?",
actions: {
confirm: "确认",
cancel: "取消",
},
},
},
},
};
// ❌ Avoid: Flat structure
// ❌ 避免:扁平结构
export default {
modalConfirmTitle: '确认操作',
modalConfirmMessage: '确定要执行此操作吗?',
modalConfirmTitle: "确认操作",
modalConfirmMessage: "确定要执行此操作吗?",
};
```
## Troubleshooting
## 故障排除
### Missing Translation Keys
- Check if the key exists in src/locales/default/namespace.ts
- Ensure the namespace is correctly imported in the component
- Ensure new namespaces are exported in src/locales/default/index.ts
### 缺少翻译键
- 检查键是否存在于 src/locales/default/namespace.ts 中
- 确保在组件中正确导入命名空间
+42 -20
View File
@@ -4,33 +4,55 @@ alwaysApply: true
## Project Description
You are developing an open-source, modern-design AI chat framework: lobehub(previous lobe-chat).
You are developing an open-source, modern-design AI chat framework: lobe chat.
Supported platforms:
- web desktop/mobile
- desktop(electron)
- mobile app(react native), coming soon
logo emoji: 🤯
Emoji logo: 🤯
## Project Technologies Stack
- Next.js 15
- react 19
- TypeScript
- `@lobehub/ui`, antd for component framework
read [package.json](mdc:package.json) to know all npm packages you can use. read [folder-structure.mdx](mdc:docs/development/basic/folder-structure.mdx) to learn project structure.
The project uses the following technologies:
- pnpm as package manager
- Next.js 15 for frontend and backend, using app router instead of pages router
- react 19, using hooks, functional components, react server components
- TypeScript programming language
- antd, @lobehub/ui for component framework
- antd-style for css-in-js framework
- lucide-react, `@ant-design/icons` for icons
- react-layout-kit for flex layout component
- react-layout-kit for flex layout
- react-i18next for i18n
- zustand for state management
- nuqs for search params management
- SWR for data fetch
- lucide-react, @ant-design/icons for icons
- @lobehub/icons for AI provider/model logo icon
- @formkit/auto-animate for react list animation
- zustand for global state management
- nuqs for type-safe search params state manager
- SWR for react data fetch
- aHooks for react hooks library
- dayjs for time library
- dayjs for date and time library
- lodash-es for utility library
- fast-deep-equal for deep comparison of JavaScript objects
- zod for data validation
- TRPC for type safe backend
- PGLite for client DB and Neon PostgreSQL for backend DB
- PGLite for client DB and PostgreSQL for backend DB
- Drizzle ORM
- Vitest for testing
- Vitest for testing, testing-library for react component test
- Prettier for code formatting
- ESLint for code linting
- Cursor AI for code editing and AI coding assistance
Note: All tools and libraries used are the latest versions. The application only needs to be compatible with the latest browsers;
## Often used npm scripts
```bash
# type check
bun type-check
# install dependencies
pnpm install
# !: don't any build script to check weather code can work after modify
```
check [testing guide](./testing-guide/testing-guide.mdc) to learn test scripts.
-122
View File
@@ -1,122 +0,0 @@
---
description: Project directory structure overview
alwaysApply: false
---
# LobeChat Project Structure
## Complete Project Structure
This project uses common monorepo structure. The workspace packages name use `@lobechat/` namespace.
**note**: some not very important files are not shown for simplicity.
```plaintext
lobe-chat/
├── apps/
│ └── desktop/
├── docs/
├── locales/
│ ├── en-US/
│ └── zh-CN/
├── packages/
│ ├── const/
│ ├── context-engine/
│ ├── database/
│ │ ├── src/
│ │ │ ├── models/
│ │ │ ├── schemas/
│ │ │ └── repositories/
│ ├── model-bank/
│ │ └── src/
│ │ └── aiModels/
│ ├── model-runtime/
│ │ └── src/
│ │ ├── core/
│ │ └── providers/
│ ├── types/
│ │ └── src/
│ │ ├── message/
│ │ └── user/
│ └── utils/
├── public/
├── scripts/
├── src/
│ ├── app/
│ │ ├── (backend)/
│ │ │ ├── api/
│ │ │ │ ├── auth/
│ │ │ │ └── webhooks/
│ │ │ ├── middleware/
│ │ │ ├── oidc/
│ │ │ ├── trpc/
│ │ │ └── webapi/
│ │ │ ├── chat/
│ │ │ └── tts/
│ │ ├── [variants]/
│ │ │ ├── (main)/
│ │ │ │ ├── chat/
│ │ │ │ └── settings/
│ │ │ └── @modal/
│ │ └── manifest.ts
│ ├── components/
│ ├── config/
│ ├── features/
│ │ └── ChatInput/
│ ├── hooks/
│ ├── layout/
│ │ ├── AuthProvider/
│ │ └── GlobalProvider/
│ ├── libs/
│ │ └── oidc-provider/
│ ├── locales/
│ │ └── default/
│ ├── server/
│ │ ├── modules/
│ │ ├── routers/
│ │ │ ├── async/
│ │ │ ├── desktop/
│ │ │ ├── edge/
│ │ │ └── lambda/
│ │ └── services/
│ ├── services/
│ │ ├── user/
│ │ │ ├── client.ts
│ │ │ └── server.ts
│ │ └── message/
│ ├── store/
│ │ ├── agent/
│ │ ├── chat/
│ │ └── user/
│ ├── styles/
│ └── utils/
└── package.json
```
## Architecture Map
- UI Components: `src/components`, `src/features`
- Global providers: `src/layout`
- Zustand stores: `src/store`
- Client Services: `src/services/` cross-platform services
- clientDB: `src/services/<domain>/client.ts`
- serverDB: `src/services/<domain>/server.ts`
- API Routers:
- `src/app/(backend)/webapi` (REST)
- `src/server/routers/{edge|lambda|async|desktop|tools}` (tRPC)
- Server:
- Services(can access serverDB): `src/server/services` server-used-only services
- Modules(can't access db): `src/server/modules` (Server only Third-party Service Module)
- Database:
- Schema (Drizzle): `packages/database/src/schemas`
- Model (CRUD): `packages/database/src/models`
- Repository (bff-queries): `packages/database/src/repositories`
- Third-party Integrations: `src/libs` — analytics, oidc etc.
## Data Flow Architecture
- **Web with ClientDB**: React UI → Client Service → Direct Model Access → PGLite (Web WASM)
- **Web with ServerDB**: React UI → Client Service → tRPC Lambda → Server Services → PostgreSQL (Remote)
- **Desktop**:
- Cloud sync disabled: Electron UI → Client Service → tRPC Lambda → Local Server Services → PGLite (Node WASM)
- Cloud sync enabled: Electron UI → Client Service → tRPC Lambda → Cloud Server Services → PostgreSQL (Remote)
+74 -94
View File
@@ -3,13 +3,11 @@ description:
globs: *.tsx
alwaysApply: false
---
# react component 编写指南
- 如果要写复杂样式的话用 antd-style ,简单的话可以用 style 属性直接写内联样式
- 如果需要 flex 布局或者居中布局应该使用 react-layout-kit 的 Flexbox 和 Center 组件
- 选择组件时优先顺序应该是 src/components > 安装的组件 package > lobe-ui > antd
- 使用 selector 访问 zustand store 的数据,而不是直接从 store 获取
## antd-style token system
@@ -22,20 +20,18 @@ import { useTheme } from 'antd-style';
const MyComponent = () => {
const theme = useTheme();
return (
<div
style={{
color: theme.colorPrimary,
backgroundColor: theme.colorBgContainer,
padding: theme.padding,
borderRadius: theme.borderRadius,
}}
>
<div style={{
color: theme.colorPrimary,
backgroundColor: theme.colorBgContainer,
padding: theme.padding,
borderRadius: theme.borderRadius
}}>
使用主题 token 的组件
</div>
);
};
}
```
#### 使用 antd-style 的 createStyles
@@ -57,13 +53,13 @@ const useStyles = createStyles(({ css, token }) => {
content: css`
font-size: ${token.fontSize}px;
line-height: ${token.lineHeight};
`,
`
};
});
const Card: FC<CardProps> = ({ title, content }) => {
const { styles } = useStyles();
return (
<Flexbox className={styles.container}>
<div className={styles.title}>{title}</div>
@@ -78,96 +74,80 @@ const Card: FC<CardProps> = ({ title, content }) => {
请注意使用下面的 token 而不是 css 字面值。可以访问 https://ant.design/docs/react/customize-theme-cn 了解所有 token
- 动画类
- token.motionDurationMid
- token.motionEaseInOut
- token.motionDurationMid
- token.motionEaseInOut
- 包围盒属性
- token.paddingSM
- token.marginLG
- token.paddingSM
- token.marginLG
## Lobe UI 包含的组件
- 不知道 `@lobehub/ui` 的组件怎么用,有哪些属性,就自己搜下这个项目其它地方怎么用的,不要瞎猜,大部分组件都是在 antd 的基础上扩展了属性
- 不知道 @lobehub/ui 的组件怎么用,有哪些属性,就自己搜下这个项目其它地方怎么用的,不要瞎猜,大部分组件都是在 antd 的基础上扩展了属性
- 具体用法不懂可以联网搜索,例如 ActionIcon 就爬取 https://ui.lobehub.com/components/action-icon
- 可以阅读 `node_modules/@lobehub/ui/es/index.js` 了解有哪些组件,每个组件的属性是什么
- General
- ActionIcon
- ActionIconGroup
- Block
- Button
- DownloadButton
- Icon
ActionIcon
ActionIconGroup
Block
Button
Icon
- Data Display
- Avatar
- AvatarGroup
- GroupAvatar
- Collapse
- FileTypeIcon
- FluentEmoji
- GuideCard
- Highlighter
- Hotkey
- Image
- List
- Markdown
- SearchResultCards
- MaterialFileTypeIcon
- Mermaid
- Typography
- Text
- Segmented
- Snippet
- SortableList
- Tag
- Tooltip
- Video
Avatar
Collapse
FileTypeIcon
FluentEmoji
GuideCard
Highlighter
Hotkey
Image
List
Markdown
MaterialFileTypeIcon
Mermaid
Segmented
Snippet
SortableList
Tag
Tooltip
Video
- Data Entry
- AutoComplete
- CodeEditor
- ColorSwatches
- CopyButton
- DatePicker
- EditableText
- EmojiPicker
- Form
- FormModal
- HotkeyInput
- ImageSelect
- Input
- SearchBar
- Select
- SliderWithInput
- ThemeSwitch
AutoComplete
CodeEditor
ColorSwatches
CopyButton
DatePicker
EditableText
EmojiPicker
Form
FormModal
HotkeyInput
ImageSelect
Input
SearchBar
Select
SliderWithInput
ThemeSwitch
- Feedback
- Alert
- Drawer
- Modal
Alert
Drawer
Modal
- Layout
- DraggablePanel
- DraggablePanelBody
- DraggablePanelContainer
- DraggablePanelFooter
- DraggablePanelHeader
- Footer
- Grid
- Header
- Layout
- LayoutFooter
- LayoutHeader
- LayoutMain
- LayoutSidebar
- LayoutSidebarInner
- LayoutToc
- MaskShadow
- ScrollShadow
DraggablePanel
Footer
Grid
Header
Layout
MaskShadow
ScrollShadow
- Navigation
- Burger
- Dropdown
- Menu
- SideNav
- Tabs
- Toc
Burger
Dropdown
Menu
SideNav
Tabs
Toc
- Theme
- ConfigProvider
- FontLoader
- ThemeProvider
ConfigProvider
FontLoader
ThemeProvider
+85
View File
@@ -0,0 +1,85 @@
---
description:
globs:
alwaysApply: true
---
# LobeChat Cursor Rules System Guide
This document explains how the LobeChat project's Cursor rules system works and serves as an index for manually accessible rules.
## 🎯 Core Principle
**All rules are equal** - there are no priorities or "recommendations" between different rule sources. You should follow all applicable rules simultaneously.
## 📚 Four Ways to Access Rules
### 1. **Always Applied Rules** - `always_applied_workspace_rules`
- **What**: Core project guidelines that are always active
- **Content**: Project tech stack, basic coding standards, output formatting rules
- **Access**: No tools needed - automatically provided in every conversation
### 2. **Dynamic Context Rules** - `cursor_rules_context`
- **What**: Rules automatically matched based on files referenced in the conversation
- **Trigger**: Only when user **explicitly @ mentions files** or **opens files in Cursor**
- **Content**: May include brief descriptions or full rule content, depending on relevance
- **Access**: No tools needed - automatically updated when files are referenced
### 3. **Agent Requestable Rules** - `agent_requestable_workspace_rules`
- **What**: Detailed operational guides that can be requested on-demand
- **Access**: Use `fetch_rules` tool with rule names
- **Examples**: `debug`, `i18n/i18n`, `code-review`
### 4. **Manual Rules Index** - This file + `read_file`
- **What**: Additional rules not covered by the above mechanisms
- **Why needed**: Cursor's rule system only supports "agent request" or "auto attach" modes
- **Access**: Use `read_file` tool to read specific `.mdc` files
## 🔧 When to Use `read_file` for Rules
Use `read_file` to access rules from the index below when:
1. **Gap identification**: You determine a rule is needed for the current task
2. **No auto-trigger**: The rule isn't provided in `cursor_rules_context` (because relevant files weren't @ mentioned)
3. **Not agent-requestable**: The rule isn't available via `fetch_rules`
## 📋 Available Rules Index
The following rules are available via `read_file` from the `.cursor/rules/` directory:
- `backend-architecture.mdc` Backend layer architecture and design guidelines
- `define-database-model.mdc` Database model definition guidelines
- `drizzle-schema-style-guide.mdc` Style guide for defining Drizzle ORM schemas
- `react-component.mdc` React component style guide and conventions
- `testing-guide.mdc` Comprehensive testing guide for Vitest environment
- `typescript.mdc` TypeScript code style guide
- `zustand-action-patterns.mdc` Recommended patterns for organizing Zustand actions
- `zustand-slice-organization.mdc` Best practices for structuring Zustand slices
## ❌ Common Misunderstandings to Avoid
1. **"Priority confusion"**: There's no hierarchy between rule sources - they're complementary, not competitive
2. **"Dynamic expectations"**: `cursor_rules_context` only updates when you @ files - it won't automatically include rules for tasks you're thinking about
3. **"Tool redundancy"**: Each access method serves a different purpose - they're not alternatives to choose from
## 🛠️ Practical Workflow
```
1. Start with always_applied_workspace_rules (automatic)
2. Check cursor_rules_context for auto-matched rules (automatic)
3. If you need specific guides: fetch_rules (manual)
4. If you identify gaps: consult this index → read_file (manual)
```
## Example Decision Flow
**Scenario**: Working on a new Zustand store slice
1. Follow always_applied_workspace_rules ✅
2. If store files were @ mentioned → use cursor_rules_context rules ✅
3. Need detailed Zustand guidance → `read_file('.cursor/rules/zustand-slice-organization.mdc')` ✅
4. All rules apply simultaneously - no conflicts ✅
-44
View File
@@ -1,44 +0,0 @@
---
description:
globs:
alwaysApply: true
---
# Available project rules index
All following rules are saved under `.cursor/rules/` directory:
## Backend
- `drizzle-schema-style-guide.mdc` Style guide for defining Drizzle ORM schemas
## Frontend
- `react-component.mdc` React component style guide and conventions
- `i18n.mdc` Internationalization guide using react-i18next
- `typescript.mdc` TypeScript code style guide
- `packages/react-layout-kit.mdc` Usage guide for react-layout-kit
## State Management
- `zustand-action-patterns.mdc` Recommended patterns for organizing Zustand actions
- `zustand-slice-organization.mdc` Best practices for structuring Zustand slices
## Desktop (Electron)
- `desktop-feature-implementation.mdc` Implementing new Electron desktop features
- `desktop-controller-tests.mdc` Desktop controller unit testing guide
- `desktop-local-tools-implement.mdc` Workflow to add new desktop local tools
- `desktop-menu-configuration.mdc` Desktop menu configuration guide
- `desktop-window-management.mdc` Desktop window management guide
## Debugging
- `debug-usage.mdc` Using the debug package and namespace conventions
## Testing
- `testing-guide/testing-guide.mdc` Comprehensive testing guide for Vitest
- `testing-guide/zustand-store-action-test.mdc` Zustand store action testing best practices
- `testing-guide/electron-ipc-test.mdc` Electron IPC interface testing strategy
- `testing-guide/db-model-test.mdc` Database Model testing guide
+43
View File
@@ -0,0 +1,43 @@
---
description:
globs:
alwaysApply: true
---
## System Role
You are an expert in full-stack Web development, proficient in JavaScript, TypeScript, CSS, React, Node.js, Next.js, Postgresql, all kinds of network protocols.
You are an expert in LLM and Ai art. In Ai image generation, you are proficient in Stable Diffusion and ComfyUI's architectural principles, workflows, model structures, parameter configurations, training methods, and inference optimization.
You are an expert in UI/UX design, proficient in web interaction patterns, responsive design, accessibility, and user behavior optimization. You excel at improving user retention and paid conversion rates through various interaction details.
## Problem Solving
- Before formulating any response, you must first gather context by using tools like codebase_search, grep_search, file_search, web_search, fetch_rules, context7, and read_file to avoid making assumptions.
- When modifying existing code, clearly describe the differences and reasons for the changes
- Provide alternative solutions that may be better overall or superior in specific aspects
- Provide optimization suggestions for deprecated API usage
- Cite sources whenever possible at the end, not inline
- When you provide multiple solutions, provide the recommended solution first, and note it as `Recommended`
- Express uncertainty when there might not be a correct answer
- Admit when you don't know something instead of guessing
## Code Implementation
- Write correct, up-to-date, bug-free, fully functional, secure, maintainable and efficient code
- First, think step-by-step: describe your plan in detailed pseudocode before implementation
- Confirm the plan before writing code
- Focus on maintainable over being performant
- Leave NO TODOs, placeholders, or missing pieces
- Be sure to reference file names
- When you notice I have manually modified the code, that was definitely on purpose and do not revert them
- If documentation links or required files are missing, ask for them before proceeding with the task rather than making assumptions
- If you're unable to access or retrieve content from websites, please inform me immediately and request the specific information needed rather than making assumptions
- You can use emojis, npm packages like `chalk`/`chalk-animation`/`terminal-link`/`gradient-string`/`log-symbols`/`boxen`/`consola`/`@clack/prompts` to create beautiful terminal output
- Don't run `tsc --noEmit` to check ts syntax error, because our project is very large and the validate very slow
## Some logging rules
- Never log user private information like api key, etc
- Don't use `import { log } from 'debug'` to log messages, because it will directly log the message to the console.
@@ -5,8 +5,6 @@ alwaysApply: false
## 🗃️ 数据库 Model 测试指南
测试 `packages/database` 下的数据库 Model 层。
### 测试环境选择 💡
数据库 Model 层通过环境变量控制数据库类型,在两种测试环境下有不同的数据库后端:客户端环境 (PGLite) 和 服务端环境 (PostgreSQL)
@@ -19,10 +17,10 @@ alwaysApply: false
```bash
# 1. 先在客户端环境测试(快速验证)
cd packages/database && TEST_SERVER_DB=0 bunx vitest run --silent='passed-only' src/database/models/__tests__/myModel.test.ts
npx vitest run --config vitest.config.ts src/database/models/__tests__/myModel.test.ts
# 2. 再在服务端环境测试(兼容性验证), 需要设置环境变量 `TEST_SERVER_DB=1`
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' src/database/models/__tests__/myModel.test.ts #
# 2. 再在服务端环境测试(兼容性验证)
npx vitest run --config vitest.config.server.ts src/database/models/__tests__/myModel.test.ts
```
### 创建新 Model 测试的最佳实践 📋
+168 -182
View File
@@ -5,11 +5,11 @@ alwaysApply: false
# 测试指南 - LobeChat Testing Guide
## 测试环境概览
## 🧪 测试环境概览
LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境:
### 客户端数据库测试环境 (DOM Environment)
### 客户端测试环境 (DOM Environment)
- **配置文件**: [vitest.config.ts](mdc:vitest.config.ts)
- **环境**: Happy DOM (浏览器环境模拟)
@@ -17,71 +17,60 @@ LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境:
- **用途**: 测试前端组件、客户端逻辑、React 组件等
- **设置文件**: [tests/setup.ts](mdc:tests/setup.ts)
### 服务端数据库测试环境 (Node Environment)
### 服务端测试环境 (Node Environment)
目前只有 `packages/database` 下的测试可以通过配置 `TEST_SERVER_DB=1` 环境变量来使用服务端数据库测试
- **配置文件**: [packages/database/vitest.config.mts](mdc:packages/database/vitest.config.mts) 并且设置环境变量 `TEST_SERVER_DB=1`
- **配置文件**: [vitest.config.server.ts](mdc:vitest.config.server.ts)
- **环境**: Node.js
- **数据库**: 真实的 PostgreSQL 数据库
- **并发限制**: 单线程运行 (`singleFork: true`)
- **用途**: 测试数据库模型、服务端逻辑、API 端点等
- **设置文件**: [packages/database/tests/setup-db.ts](mdc:packages/database/tests/setup-db.ts)
- **设置文件**: [tests/setup-db.ts](mdc:tests/setup-db.ts)
## 测试运行命令
## 🚀 测试运行命令
** 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。
**🚨 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。
### 正确的命令格式
### 正确的命令格式
```bash
# 运行所有客户端/服务端测试
bunx vitest run --silent='passed-only' # 客户端测试
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' # 服务端测试
npx vitest run --config vitest.config.ts # 客户端测试
npx vitest run --config vitest.config.server.ts # 服务端测试
# 运行特定测试文件 (支持模糊匹配)
bunx vitest run --silent='passed-only' user.test.ts
npx vitest run --config vitest.config.ts user.test.ts
# 运行特定测试用例名称 (使用 -t 参数)
bunx vitest run --silent='passed-only' -t "test case name"
npx vitest run --config vitest.config.ts -t "test case name"
# 组合使用文件和测试名称过滤
bunx vitest run --silent='passed-only' filename.test.ts -t "specific test"
npx vitest run --config vitest.config.ts filename.test.ts -t "specific test"
# 生成覆盖率报告 (使用 --coverage 参数)
bunx vitest run --silent='passed-only' --coverage
npx vitest run --config vitest.config.ts --coverage
```
### 避免的命令格式
### 避免的命令格式
```bash
# 这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟!
# 这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟!
npm test
npm test some-file.test.ts
# 不要使用裸 vitest (会进入 watch 模式)
# 不要使用裸 vitest (会进入 watch 模式)
vitest test-file.test.ts
```
## 测试修复原则
## 🔧 测试修复原则
### 核心原则
### 核心原则 ⚠️
1. **收集足够的上下文**
在修复测试之前,务必做到:
- 完整理解测试的意图和实现
- 强烈建议阅读当前的 git diff 和 PR diff
1. **充分阅读测试代码**: 在修复测试之前,必须完整理解测试的意图和实现
2. **测试优先修复**: 如果是测试本身写错了,修改测试而不是实现代码
3. **专注单一问题**: 只修复指定的测试,不要添加额外测试或功能
4. **不自作主张**: 不要因为发现其他问题就直接修改,先提出再讨论
2. **测试优先修复**
如果是测试本身写错了,应优先修改测试,而不是实现代码。
3. **专注单一问题**
只修复指定的测试,不要顺带添加额外测试。
4. **不自作主张**
发现其他问题时,不要直接修改,需先提出并讨论。
### 测试协作最佳实践
### 测试协作最佳实践 🤝
基于实际开发经验总结的重要协作原则:
@@ -95,10 +84,10 @@ vitest test-file.test.ts
- **避免陷阱**: 不要陷入"不断尝试相同或类似方法"的循环
```typescript
// 错误做法:连续失败后继续盲目尝试
// 错误做法:连续失败后继续盲目尝试
// 第3次、第4次仍在用相似的方法修复同一个问题
// 正确做法:失败1-2次后总结问题
// 正确做法:失败1-2次后总结问题
/*
问题总结:
1. 尝试过的方法:修改 mock 数据结构
@@ -117,7 +106,7 @@ vitest test-file.test.ts
- **保持稳定性**: 测试名称应该在代码重构后仍然有意义
```typescript
// 错误的测试命名
// 错误的测试命名
describe('User component coverage', () => {
it('covers line 45-50 in getUserData', () => {
// 为了覆盖第45-50行而写的测试
@@ -128,7 +117,7 @@ describe('User component coverage', () => {
});
});
// 正确的测试命名
// 正确的测试命名
describe('<UserAvatar />', () => {
it('should render fallback icon when image url is not provided', () => {
// 测试具体的业务场景,自然会覆盖相关代码分支
@@ -142,8 +131,8 @@ describe('<UserAvatar />', () => {
**覆盖率提升的正确思路**:
- 通过设计各种业务场景(正常流程、边缘情况、错误处理)来自然提升覆盖率
- 不要为了达到覆盖率数字而写测试,更不要在测试中注释"为了覆盖 xxx 行"
- 通过设计各种业务场景(正常流程、边缘情况、错误处理)来自然提升覆盖率
- 不要为了达到覆盖率数字而写测试,更不要在测试中注释"为了覆盖 xxx 行"
#### 3. 测试组织结构
@@ -154,7 +143,7 @@ describe('<UserAvatar />', () => {
- **避免碎片化**: 不要为了单个测试用例就创建新的顶级 `describe` 块
```typescript
// 错误的组织方式:创建过多顶级块
// 错误的组织方式:创建过多顶级块
describe('<UserProfile />', () => {
it('should render user name', () => {});
});
@@ -169,7 +158,7 @@ describe('UserProfile edge cases', () => {
it('should handle missing avatar', () => {});
});
// 正确的组织方式:合并相关测试
// 正确的组织方式:合并相关测试
describe('<UserProfile />', () => {
it('should render user name', () => {});
@@ -225,9 +214,9 @@ describe('<UserProfile />', () => {
**修复方法**: 更新了测试文件中的 mock 数据结构,使其与最新的 API 响应格式保持一致。具体修改了 `user.test.ts` 中的 `mockUserData` 对象结构。
```
## 测试编写最佳实践
## 🎯 测试编写最佳实践
### Mock 数据策略:追求"低成本的真实性"
### Mock 数据策略:追求"低成本的真实性" 📋
**核心原则**: 测试数据应默认追求真实性,只有在引入"高昂的测试成本"时才进行简化。
@@ -239,10 +228,10 @@ describe('<UserProfile />', () => {
- **网络请求**:HTTP 调用、数据库连接
- **系统调用**:获取系统时间、环境变量等
#### 推荐做法:Mock 依赖,保留真实数据
#### 推荐做法:Mock 依赖,保留真实数据
```typescript
// 好的做法:Mock I/O 操作,但使用真实的文件内容格式
// 好的做法:Mock I/O 操作,但使用真实的文件内容格式
describe('parseContentType', () => {
beforeEach(() => {
// Mock 文件读取操作(避免真实 I/O)
@@ -260,7 +249,7 @@ describe('parseContentType', () => {
});
});
// 过度简化:使用不真实的数据
// 过度简化:使用不真实的数据
describe('parseContentType', () => {
it('should detect PDF content type correctly', () => {
// 这种简化数据没有测试价值
@@ -270,116 +259,78 @@ describe('parseContentType', () => {
});
```
#### 真实标识符的价值
#### 🎯 真实标识符的价值
```typescript
// ✅ 使用真实标识符
const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo');
// ✅ 使用真实的提供商标识符
it('should parse OpenAI model list correctly', () => {
const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo');
expect(result.add).toHaveLength(2);
expect(result.add[0].id).toBe('gpt-4');
});
// ❌ 使用占位符(价值较低)
const result = parseModelString('test-provider', '+model1,+model2');
```
### 现代化Mock技巧:环境设置与Mock方法
**环境设置 + Mock方法结合使用**
客户端代码测试时,推荐使用环境注释配合现代化Mock方法:
```typescript
/**
* @vitest-environment happy-dom // 提供浏览器API
*/
import { beforeEach, vi } from 'vitest';
beforeEach(() => {
// 现代方法1:使用vi.stubGlobal替代global.xxx = ...
const mockImage = vi.fn().mockImplementation(() => ({
addEventListener: vi.fn(),
naturalHeight: 600,
naturalWidth: 800,
}));
vi.stubGlobal('Image', mockImage);
// 现代方法2:使用vi.spyOn保留原功能,只mock特定方法
vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url');
vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {});
// ❌ 使用占位符标识符(价值较低)
it('should parse model list correctly', () => {
const result = parseModelString('test-provider', '+model1,+model2');
expect(result.add).toHaveLength(2);
// 这种测试对理解真实场景帮助不大
});
```
**环境选择优先级**
1. **@vitest-environment happy-dom** (推荐) - 轻量、快速,项目已安装
2. **@vitest-environment jsdom** - 功能完整,但需要额外安装jsdom包
3. **不设置环境** - Node.js环境,需要手动mock所有浏览器API
**Mock方法对比**
```typescript
// ❌ 旧方法:直接操作global对象(类型问题)
global.Image = mockImage;
global.URL = { ...global.URL, createObjectURL: mockFn };
// ✅ 现代方法:类型安全的vi API
vi.stubGlobal('Image', mockImage); // 完全替换全局对象
vi.spyOn(URL, 'createObjectURL'); // 部分mock,保留其他功能
```
### 测试覆盖率原则:代码分支优于用例数量
**核心原则**: 优先覆盖所有代码分支,而非编写大量重复用例
```typescript
// ❌ 过度测试:29个测试用例都验证相同分支
describe('getImageDimensions', () => {
it('should reject .txt files');
it('should reject .pdf files');
// ... 25个类似测试,都走相同的验证分支
});
// ✅ 精简测试:4个核心用例覆盖所有分支
describe('getImageDimensions', () => {
it('should return dimensions for valid File object'); // 成功路径 - File
it('should return dimensions for valid data URI'); // 成功路径 - String
it('should return undefined for invalid inputs'); // 输入验证分支
it('should return undefined when image fails to load'); // 错误处理分支
});
```
**分支覆盖策略**
1. **成功路径** - 每种输入类型1个测试即可
2. **边界条件** - 合并类似场景到单个测试
3. **错误处理** - 测试代表性错误即可
4. **业务逻辑** - 覆盖所有if/else分支
**合理测试数量**
- 简单工具函数:2-5个测试
- 复杂业务逻辑:5-10个测试
- 核心安全功能:适当增加,但避免重复路径
### 错误处理测试:测试"行为"而非"文本"
### 错误处理测试:测试"行为"而非"文本" ⚠️
**核心原则**: 测试应该验证程序在错误发生时的行为是可预测的,而不是验证易变的错误信息文本。
#### 推荐的错误测试方式
#### 推荐的错误测试方式
```typescript
// ✅ 测试错误类型和属性
expect(() => validateUser({})).toThrow(ValidationError);
expect(() => processPayment({})).toThrow(
expect.objectContaining({
code: 'INVALID_PAYMENT_DATA',
statusCode: 400,
}),
);
// ✅ 测试是否抛出错误
it('should throw error when invalid input provided', () => {
expect(() => processInput(null)).toThrow();
});
// ❌ 避免测试具体错误文本
expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数');
// ✅ 测试错误类型(最推荐)
it('should throw ValidationError for invalid data', () => {
expect(() => validateUser({})).toThrow(ValidationError);
});
// ✅ 测试错误属性而非消息文本
it('should throw error with correct error code', () => {
expect(() => processPayment({})).toThrow(
expect.objectContaining({
code: 'INVALID_PAYMENT_DATA',
statusCode: 400,
}),
);
});
```
### 疑难解答:警惕模块污染
#### ❌ 应避免的做法
```typescript
// ❌ 过度依赖具体错误信息文本
it('should throw specific error message', () => {
expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数');
// 这种测试很脆弱,错误文案稍有修改就会失败
});
```
#### 🎯 例外情况:何时可以测试错误信息
```typescript
// ✅ 测试标准 API 错误(这是契约的一部分)
it('should return proper HTTP error for API', () => {
expect(response.statusCode).toBe(400);
expect(response.error).toBe('Bad Request');
});
// ✅ 测试错误信息的关键部分(使用正则)
it('should include field name in validation error', () => {
expect(() => validateField('email', '')).toThrow(/email/i);
});
```
### 疑难解答:警惕模块污染 🚨
**识别信号**: 当你的测试出现以下"灵异"现象时,优先怀疑模块污染:
@@ -390,25 +341,55 @@ expect(() => processUser({})).toThrow('用户数据不能为空,请检查输
#### 典型场景:动态 Mock 同一模块
```typescript
// ❌ 问题:动态Mock同一模块
it('dev mode', async () => {
vi.doMock('./config', () => ({ isDev: true }));
const { getSettings } = await import('./service'); // 可能使用缓存
// ❌ 容易出现模块污染的写法
describe('ConfigService', () => {
it('should work in development mode', async () => {
vi.doMock('./config', () => ({ isDev: true }));
const { getSettings } = await import('./configService'); // 第一次加载
expect(getSettings().debugMode).toBe(true);
});
it('should work in production mode', async () => {
vi.doMock('./config', () => ({ isDev: false }));
const { getSettings } = await import('./configService'); // 可能使用缓存的旧版本!
expect(getSettings().debugMode).toBe(false); // ❌ 可能失败
});
});
// ✅ 解决:清除模块缓存
beforeEach(() => {
vi.resetModules(); // 确保每个测试都是干净环境
// ✅ 使用 resetModules 解决模块污染
describe('ConfigService', () => {
beforeEach(() => {
vi.resetModules(); // 清除模块缓存,确保每个测试都是干净的环境
});
it('should work in development mode', async () => {
vi.doMock('./config', () => ({ isDev: true }));
const { getSettings } = await import('./configService');
expect(getSettings().debugMode).toBe(true);
});
it('should work in production mode', async () => {
vi.doMock('./config', () => ({ isDev: false }));
const { getSettings } = await import('./configService');
expect(getSettings().debugMode).toBe(false); // ✅ 测试通过
});
});
```
**记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器。
#### 🔧 排查和解决步骤
## 测试文件组织
1. **识别问题**: 测试失败时,首先问自己:"是否有多个测试在 Mock 同一个模块?"
2. **添加隔离**: 在 `beforeEach` 中添加 `vi.resetModules()`
3. **验证修复**: 重新运行测试,确认问题解决
**记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器,当常规调试方法都无效时,它往往能一针见血地解决问题。
## 📂 测试文件组织
### 文件命名约定
`*.test.ts`, `*.test.tsx` (任意位置)
- **客户端测试**: `*.test.ts`, `*.test.tsx` (任意位置)
- **服务端测试**: `src/database/models/**/*.test.ts`, `src/database/server/**/*.test.ts` (限定路径)
### 测试文件组织风格
@@ -425,10 +406,7 @@ src/components/Button/
└── index.test.tsx # 测试文件
```
- 也有少数情况会统一放到 `__tests__` 文件夹, 例如 `packages/database/src/models/__tests__`
- 测试使用的辅助文件放到 fixtures 文件夹
## 测试调试技巧
## 🛠️ 测试调试技巧
### 测试调试步骤
@@ -437,59 +415,67 @@ src/components/Button/
3. **分析错误**: 仔细阅读错误信息、堆栈跟踪和最近的文件修改记录
4. **添加调试**: 在测试中添加 `console.log` 了解执行流程
### TypeScript 类型处理
### TypeScript 类型处理 📝
在测试中,为了提高编写效率和可读性,可以适当放宽 TypeScript 类型检测:
#### 推荐的类型放宽策略
#### 推荐的类型放宽策略
```typescript
// 使用非空断言访问测试中确定存在的属性
// 使用非空断言访问测试中确定存在的属性
const result = await someFunction();
expect(result!.data).toBeDefined();
expect(result!.status).toBe('success');
// 使用 any 类型简化复杂的 Mock 设置
// 使用 any 类型简化复杂的 Mock 设置
const mockStream = new ReadableStream() as any;
mockStream.toReadableStream = () => mockStream;
// 访问私有成员
await instance['getFromCache']('key'); // 推荐中括号
await (instance as any).getFromCache('key'); // 避免as any
```
#### 适用场景
#### 🎯 适用场景
- **Mock 对象**: 对于测试用的 Mock 数据,使用 `as any` 避免复杂的类型定义
- **第三方库**: 处理复杂的第三方库类型时,适当使用 `any` 提高效率
- **测试断言**: 在确定对象存在的测试场景中,使用 `!` 非空断言
- **私有成员访问**: 优先使用中括号 `instance['privateMethod']()` 而不是 `(instance as any).privateMethod()`
- **临时调试**: 快速编写测试时,先用 `any` 保证功能,后续可选择性地优化类型
#### 注意事项
#### ⚠️ 注意事项
- **适度使用**: 不要过度依赖 `any`,核心业务逻辑的类型仍应保持严格
- **私有成员访问优先级**: 中括号访问 > `as any` 转换,保持更好的类型安全性
- **文档说明**: 对于使用 `any` 的复杂场景,添加注释说明原因
- **测试覆盖**: 确保即使使用了 `any`,测试仍能有效验证功能正确性
### 检查最近修改记录
### 检查最近修改记录 🔍
**核心原则**:测试突然失败时,优先检查最近的代码修改
系统性地检查相关文件的修改历史是问题定位的关键步骤
#### 快速检查
#### 三步检查法
**Step 1: 查看当前状态**
```bash
git status # 查看当前修改状态
git diff HEAD -- '*.test.*' # 查测试文件改
git diff main...HEAD # 对比主分支差异
gh pr diff # 查看PR中的所有改动
git status # 查看未提交的修改
git diff path/to/component.test.ts | cat # 查测试文件
git diff path/to/component.ts | cat # 查看实现文件修改
```
#### 常见原因与解决
**Step 2: 查看提交历史**
- **最新提交引入bug** → 检查并修复实现代码
- **分支代码滞后** → `git rebase main` 同步主分支
```bash
git log --pretty=format:"%h %ad %s" --date=relative -3 path/to/component.ts | cat
```
**Step 3: 查看具体修改内容**
```bash
git show HEAD -- path/to/component.ts | cat # 查看最新提交的修改
```
#### 时间相关性判断
- **24小时内的提交**: 🔴 **高度相关** - 很可能是直接原因
- **1-7天内的提交**: 🟡 **中等相关** - 需要仔细分析
- **超过1周的提交**: ⚪ **低相关性** - 除非重大重构
## 特殊场景的测试
@@ -498,9 +484,9 @@ gh pr diff # 查看PR中的所有改动
- [Electron IPC 接口测试策略](mdc:./electron-ipc-test.mdc)
- [数据库 Model 测试指南](mdc:./db-model-test.mdc)
## 核心要点
## 🎯 核心要点
- **命令格式**: 使用 `bunx vitest run --silent='passed-only'` 并指定文件过滤
- **命令格式**: 使用 `npx vitest run --config [config-file]` 并指定文件过滤
- **修复原则**: 失败1-2次后寻求帮助,测试命名关注行为而非实现细节
- **调试流程**: 复现 → 分析 → 假设 → 修复 → 验证 → 总结
- **文件组织**: 优先在现有 `describe` 块中添加测试,避免创建冗余顶级块
@@ -1,410 +0,0 @@
---
globs: src/store/**/__tests__/*.test.ts
alwaysApply: false
---
# 🏪 Zustand Store Action Testing Guide
Testing guide for Zustand store actions under `src/store`. This guide is based on lessons learned from the `generateAIChat` refactoring practice.
## Core Principles
### 1. Test Layering Principle 🎯
**Each layer should only test direct dependencies, never spy across layers**
```
❌ Bad Example - Cross-layer spying
describe('internal_coreProcessMessage', () => {
it('test', async () => {
// ❌ Skipping internal_fetchAIChatMessage, directly spying on lower-level service
const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream');
});
});
✅ Good Example - Spy on direct dependencies
describe('internal_coreProcessMessage', () => {
it('test', async () => {
// ✅ Only spy on directly called methods
const fetchSpy = vi.spyOn(result.current, 'internal_fetchAIChatMessage')
.mockResolvedValue({ isFunctionCall: false, content: 'response' });
});
});
```
### 2. Mocking Strategy 🎭
#### Per-Test Mocking (Recommended)
```typescript
// ✅ Spy on-demand in each test
describe('myAction', () => {
it('should do something', async () => {
// Only spy when needed in this specific test
const serviceSpy = vi.spyOn(someService, 'method').mockResolvedValue(result);
// Test logic...
serviceSpy.mockRestore(); // Optional: cleanup
});
});
```
#### Avoid Global Mocks
```typescript
// ❌ Avoid globally spying on everything in beforeEach
beforeEach(() => {
spyOnEverything(); // Creates implicit coupling between tests
});
// ✅ Only spy on base services that almost all tests need
beforeEach(() => {
spyOnMessageService(); // Most tests need this
// Other services should be spied on-demand within tests
});
```
## Action Test Templates 📝
### Basic Action Test
```typescript
import { act, renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useStore } from '../store';
// Mock zustand
vi.mock('zustand/traditional');
// Test constants
const TEST_IDS = {
DATA_ID: 'test-data-id',
} as const;
// Mock data factory
const createMockData = (overrides = {}) => ({
id: TEST_IDS.DATA_ID,
status: 'initial',
...overrides,
});
beforeEach(() => {
vi.clearAllMocks();
// Setup common mocks that most tests need
act(() => {
useStore.setState({
refreshData: vi.fn(),
internalMethod: vi.fn(),
});
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('myAction', () => {
describe('validation', () => {
it('should return early when conditions not met', async () => {
act(() => {
useStore.setState({ requiredData: undefined });
});
const { result } = renderHook(() => useStore());
await act(async () => {
await result.current.myAction();
});
expect(result.current.internalMethod).not.toHaveBeenCalled();
});
});
describe('main flow', () => {
it('should process data correctly', async () => {
const { result } = renderHook(() => useStore());
const mockData = createMockData();
await act(async () => {
await result.current.myAction(mockData);
});
expect(result.current.internalMethod).toHaveBeenCalledWith(
expect.objectContaining({
id: TEST_IDS.DATA_ID,
status: 'processed',
}),
);
});
});
describe('error handling', () => {
it('should handle errors gracefully', async () => {
const { result } = renderHook(() => useStore());
vi.spyOn(result.current, 'internalMethod').mockRejectedValue(
new Error('Test error'),
);
await act(async () => {
await result.current.myAction();
});
expect(result.current.errorState).toBeDefined();
});
});
});
```
### Testing Internal Methods
```typescript
// Save the real implementation for testing
const realInternalMethod = useStore.getState().internal_method;
describe('internal_method', () => {
it('should call correct dependencies', async () => {
// Restore the real implementation
act(() => {
useStore.setState({ internal_method: realInternalMethod });
});
const { result } = renderHook(() => useStore());
// ✅ Spy on direct dependencies
const dependencySpy = vi
.spyOn(result.current, 'internal_dependency')
.mockResolvedValue(expectedResult);
await act(async () => {
await result.current.internal_method(input);
});
expect(dependencySpy).toHaveBeenCalledWith(
expect.objectContaining({ /* expected params */ }),
);
});
});
```
### Testing Streaming/Async Flows
```typescript
describe('streamingAction', () => {
it('should handle streaming chunks', async () => {
const { result } = renderHook(() => useStore());
const dispatchSpy = vi.spyOn(result.current, 'internal_dispatch');
// Mock streaming service
const streamSpy = vi
.spyOn(streamService, 'stream')
.mockImplementation(async ({ onChunk, onFinish }) => {
await onChunk?.({ type: 'data', content: 'chunk1' });
await onChunk?.({ type: 'data', content: 'chunk2' });
await onFinish?.('complete');
});
await act(async () => {
await result.current.streamingAction();
});
expect(dispatchSpy).toHaveBeenCalledWith(
expect.objectContaining({
type: 'update',
value: expect.objectContaining({ content: 'chunk1' }),
}),
);
streamSpy.mockRestore();
});
});
```
### Testing Toggle/Loading States
```typescript
describe('internal_toggleLoading', () => {
it('should enable loading state with abort controller', () => {
const { result } = renderHook(() => useStore());
act(() => {
result.current.internal_toggleLoading(true, TEST_IDS.ITEM_ID, 'action');
});
const state = useStore.getState();
expect(state.loadingIds).toEqual([TEST_IDS.ITEM_ID]);
expect(state.abortController).toBeInstanceOf(AbortController);
});
it('should disable loading state and clear abort controller', () => {
const { result } = renderHook(() => useStore());
act(() => {
result.current.internal_toggleLoading(true, TEST_IDS.ITEM_ID, 'start');
result.current.internal_toggleLoading(false, undefined, 'stop');
});
const state = useStore.getState();
expect(state.loadingIds).toEqual([]);
expect(state.abortController).toBeUndefined();
});
});
```
## Common Issues and Solutions ⚠️
### Issue 1: React State Update Warning
```typescript
// ❌ Wrong: setState without wrapping
useStore.setState({ data: newData });
// ✅ Correct: Wrap all setState with act()
act(() => {
useStore.setState({ data: newData });
});
```
### Issue 2: Cross-Layer Spying
```typescript
// ❌ Wrong: Spy on lower-level services across layers
describe('highLevelAction', () => {
const lowLevelServiceSpy = vi.spyOn(lowLevelService, 'method');
});
// ✅ Correct: Spy on direct dependencies
describe('highLevelAction', () => {
const directDependencySpy = vi.spyOn(result.current, 'directMethod');
});
```
### Issue 3: Mock Type Mismatch
```typescript
// ❌ Wrong: Return type doesn't match
vi.spyOn(service, 'method').mockResolvedValue('string');
// But method returns Response
// ✅ Correct: Return correct type
vi.spyOn(service, 'method').mockResolvedValue(new Response('string'));
```
### Issue 4: Global Mock Pollution
```typescript
// ❌ Wrong: Spy on all services in beforeEach
beforeEach(() => {
spyOnServiceA();
spyOnServiceB();
spyOnServiceC(); // Creates coupling between tests
});
// ✅ Correct: Spy on-demand
beforeEach(() => {
spyOnCommonService(); // Only spy on common services
});
describe('specific test', () => {
it('test', () => {
const specificSpy = vi.spyOn(specificService, 'method'); // Spy on-demand
});
});
```
## Test Coverage Goals 📊
### Coverage Requirements
- **Minimum target**: 70%
- **Recommended target**: 85%+
- **Excellent target**: 90%+
### Check Coverage
```bash
# Run coverage for a single test file
bunx vitest run --coverage 'src/store/[domain]/__tests__/[action].test.ts'
# View coverage for a specific file
bunx vitest run --coverage --silent='passed-only' 'src/store/[domain]/__tests__/[action].test.ts' | grep "[action].ts"
```
### Priority Test Scenarios
1. ✅ **Main Flow**: Normal business flow
2. ✅ **Edge Cases**: Empty data, undefined values, boundary values
3. ✅ **Error Handling**: Exception scenarios, failure retries
4. ✅ **State Management**: Loading, Toggle, Abort
5. ⚠️ **Corner Cases**: Optional, but don't write meaningless tests just for coverage
## Real-World Case: generateAIChat Refactoring 🎓
### Problems Before Refactoring
```typescript
// ❌ Problem 1: Cross-layer spying
describe('internal_coreProcessMessage', () => {
const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream');
// Skipped the internal_fetchAIChatMessage layer
});
// ❌ Problem 2: Mocking wrong objects
describe('internal_fetchAIChatMessage', () => {
vi.stubGlobal('fetch', ...); // But doesn't actually call fetch
});
// ❌ Problem 3: Global spy pollution
beforeEach(() => {
spyOnChatService(); // All tests now have this spy
});
```
### Solutions After Refactoring
```typescript
// ✅ Solution 1: Spy on direct dependencies
describe('internal_coreProcessMessage', () => {
const fetchSpy = vi
.spyOn(result.current, 'internal_fetchAIChatMessage')
.mockResolvedValue({ isFunctionCall: false, content: 'response' });
});
// ✅ Solution 2: Mock correct service
describe('internal_fetchAIChatMessage', () => {
const streamSpy = vi
.spyOn(chatService, 'createAssistantMessageStream')
.mockImplementation(async ({ onMessageHandle, onFinish }) => {
await onMessageHandle?.({ type: 'text', text: 'response' });
await onFinish?.('response', {});
});
});
// ✅ Solution 3: Spy on-demand
beforeEach(() => {
spyOnMessageService(); // Only spy on common services
// Spy on chatService on-demand in tests
});
```
### Refactoring Results
- 📈 Coverage improvement: 54.44% → 82.03% (+27.59%)
- ✅ Test pass rate: 52/52 (100%)
- 🎯 Type errors: 6 → 0
- 📝 Clearer tests: Explicit test layering
## Best Practices Checklist ✅
Check before testing:
- [ ] Following test layering principle?
- [ ] Mock objects match actual calls?
- [ ] Avoiding global spy pollution?
- [ ] All setState wrapped with act()?
- [ ] Tests sufficiently atomic?
- [ ] Test descriptions clear?
- [ ] Coverage meets target?
+15 -52
View File
@@ -1,58 +1,21 @@
---
description: TypeScript code style and optimization guidelines
description:
globs: *.ts,*.tsx,*.mts
alwaysApply: false
---
# TypeScript Code Style Guide
TypeScript Code Style Guide:
## Types and Type Safety
- avoid explicit type annotations when TypeScript can infer types.
- avoid implicitly `any` variables; explicitly type when necessary (e.g., `let a: number` instead of `let a`).
- use the most accurate type possible (e.g., prefer `Record<PropertyKey, unknown>` over `object` and `any`).
- prefer `interface` over `type` for object shapes (e.g., React component props). Keep `type` for unions, intersections, and utility types.
- prefer `as const satisfies XyzInterface` over plain `as const` when suitable.
- prefer `@ts-expect-error` over `@ts-ignore` over `as any`
- Avoid meaningless null/undefined parameters; design strict function contracts.
## Imports and Modules
- When importing a directory module, prefer the explicit index path like `@/db/index` instead of `@/db`.
## Asynchronous Patterns and Concurrency
- Prefer `async`/`await` over callbacks or chained `.then` promises.
- Prefer async APIs over sync ones (avoid `*Sync`).
- Prefer promise-based variants (e.g., `import { readFile } from 'fs/promises'`) over callback-based APIs from `fs`.
- Where safe, convert sequential async flows to concurrent ones with `Promise.all`, `Promise.race`, etc.
## Code Structure and Readability
- Prefer object destructuring when accessing and using properties.
- Use consistent, descriptive naming; avoid obscure abbreviations.
- Use semantically meaningful variable, function, and class names.
- Replace magic numbers or strings with well-named constants.
- Defer formatting to tooling; ignore purely formatting-only issues and autofixable lint problems.
## UI and Theming
- Use components from `@lobehub/ui`, Ant Design, or existing design system components instead of raw HTML tags (e.g., `Button` vs. `button`).
- Design for dark mode and mobile responsiveness:
- Use the `antd-style` token system instead of hard-coded colors.
- Select appropriate component variants.
## Performance
- Prefer `for…of` loops to index-based `for` loops when feasible.
- Reuse existing utils inside `packages/utils` or installed npm packages rather than reinventing the wheel.
- Query only the required columns from a database rather than selecting entire rows.
## Time and Consistency
- Instead of calling `Date.now()` multiple times, assign it to a constant once and reuse it to ensure consistency and improve readability.
## Logging
- Never log user private information like api key, etc
- Don't use `import { log } from 'debug'` to log messages, because it will directly log the message to the console.
- Avoid explicit type annotations when TypeScript can infer types.
- Avoid defining `any` type variables (e.g., `let a: number;` instead of `let a;`).
- Use the most accurate type possible (e.g., use `Record<PropertyKey, unknown>` instead of `object`).
- Prefer `interface` over `type` (e.g., define react component props).
- Use `as const satisfies XyzInterface` instead of `as const` when suitable
- import index.ts module(directory module) like `@/db/index` instead of `@/db`
- Instead of calling Date.now() multiple times, assign it to a constant once and reuse it. This ensures consistency and improves readability
- Always refactor repeated logic into a reusable function
- Don't remove meaningful code comments, be sure to keep original comments when providing applied code
- Update the code comments when needed after you modify the related code
- Please respect my prettier preferences when you provide code
- Prefer object destructuring when accessing and using properties
- Prefer async version api than sync version, eg: use readFile from 'fs/promises' instead of 'fs'
+3 -4
View File
@@ -1,7 +1,6 @@
{
"image": "mcr.microsoft.com/devcontainers/typescript-node",
"features": {
"ghcr.io/devcontainer-community/devcontainer-features/bun.sh:1": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"image": "mcr.microsoft.com/devcontainers/typescript-node"
"ghcr.io/devcontainer-community/devcontainer-features/bun.sh:1": {}
}
}
-50
View File
@@ -4,14 +4,6 @@
# Specify your API Key selection method, currently supporting `random` and `turn`.
# API_KEY_SELECT_MODE=random
########################################
########### Security Settings ###########
########################################
# Control Content Security Policy headers
# Set to '1' to enable X-Frame-Options and Content-Security-Policy headers
# Default is '0' (enabled)
# ENABLED_CSP=1
########################################
########## AI Provider Service #########
@@ -148,48 +140,6 @@ OPENAI_API_KEY=sk-xxxxxxxxx
# INFINIAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### 302.AI ###
# AI302_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### ModelScope ###
# MODELSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### AiHubMix ###
# AIHUBMIX_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### BFL ###
# BFL_API_KEY=bfl-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### FAL ###
# FAL_API_KEY=fal-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
########################################
######### AI Image Settings ############
########################################
# Default image generation count (range: 1-20, default: 4)
# AI_IMAGE_DEFAULT_IMAGE_NUM=4
### Nebius ###
# NEBIUS_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### NewAPI Service ###
# NEWAPI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# NEWAPI_PROXY_URL=https://your-newapi-server.com
### Vercel AI Gateway ###
# VERCELAIGATEWAY_API_KEY=your_vercel_ai_gateway_api_key
########################################
############ Market Service ############
########################################
-122
View File
@@ -1,122 +0,0 @@
# LobeChat Development Server Configuration
# This file contains environment variables for both LobeChat server mode and Docker compose setup
COMPOSE_FILE="docker-compose.development.yml"
# ⚠️⚠️⚠️ DO NOT USE THE SECRETS BELOW IN PRODUCTION!
UNSAFE_SECRET="ww+0igxjGRAAR/eTNFQ55VmhQB5KE5trFZseuntThJs="
UNSAFE_PASSWORD="CHANGE_THIS_PASSWORD_IN_PRODUCTION"
# Core Server Configuration
# Service mode - set to 'server' for server-side deployment
NEXT_PUBLIC_SERVICE_MODE=server
# Service Ports Configuration
LOBE_PORT=3010
# Application URL - the base URL where LobeChat will be accessible
APP_URL=http://localhost:${LOBE_PORT}
# Secret key for encrypting vault data (generate with: openssl rand -base64 32)
KEY_VAULTS_SECRET=${UNSAFE_SECRET}
# Database Configuration
# Database name for LobeChat
LOBE_DB_NAME=lobechat
# PostgreSQL password
POSTGRES_PASSWORD=${UNSAFE_PASSWORD}
# PostgreSQL database connection URL
DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@localhost:5432/${LOBE_DB_NAME}
# Database driver type
DATABASE_DRIVER=node
# Authentication Configuration
# Enable NextAuth authentication
NEXT_PUBLIC_ENABLE_NEXT_AUTH=1
# NextAuth secret for JWT signing (generate with: openssl rand -base64 32)
NEXT_AUTH_SECRET=${UNSAFE_SECRET}
NEXTAUTH_URL=${APP_URL}
# Authentication URL
AUTH_URL=${APP_URL}/api/auth
# SSO providers configuration - using Casdoor for development
NEXT_AUTH_SSO_PROVIDERS=casdoor
# Casdoor Configuration
# Casdoor service port
CASDOOR_PORT=8000
# Casdoor OIDC issuer URL
AUTH_CASDOOR_ISSUER=http://localhost:${CASDOOR_PORT}
# Casdoor application client ID
AUTH_CASDOOR_ID=a387a4892ee19b1a2249 # DO NOT USE IN PROD
# Casdoor application client secret
AUTH_CASDOOR_SECRET=dbf205949d704de81b0b5b3603174e23fbecc354 # DO NOT USE IN PROD
# Origin URL for Casdoor internal configuration
origin=http://localhost:${CASDOOR_PORT}
# MinIO Storage Configuration
# MinIO service port
MINIO_PORT=9000
# MinIO root user (admin username)
MINIO_ROOT_USER=admin
# MinIO root password
MINIO_ROOT_PASSWORD=${UNSAFE_PASSWORD}
# MinIO bucket for LobeChat files
MINIO_LOBE_BUCKET=lobe
# S3/MinIO Configuration for LobeChat
# S3/MinIO access key ID
S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}
# S3/MinIO secret access key
S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
# S3/MinIO endpoint URL
S3_ENDPOINT=http://localhost:${MINIO_PORT}
# S3 bucket name for storing files
S3_BUCKET=${MINIO_LOBE_BUCKET}
# Public domain for S3 file access
S3_PUBLIC_DOMAIN=http://localhost:${MINIO_PORT}
# Enable path-style S3 requests (required for MinIO)
S3_ENABLE_PATH_STYLE=1
# Disable S3 ACL setting (for MinIO compatibility)
S3_SET_ACL=0
# Use base64 encoding for LLM vision images
LLM_VISION_IMAGE_USE_BASE64=1
# Search Service Configuration
# SearXNG search engine URL
SEARXNG_URL=http://searxng:8080
# Development Options
# Uncomment to skip authentication during development
# Proxy Configuration (Optional)
# Uncomment if you need proxy support (e.g., for GitHub auth or API access)
# HTTP_PROXY=http://localhost:7890
# HTTPS_PROXY=http://localhost:7890
# AI Model Configuration (Optional)
# Add your AI model API keys and configurations here
# ⚠️ WARNING: Never commit real API keys to version control!
# OPENAI_API_KEY=sk-NEVER_USE_REAL_API_KEYS_IN_CONFIG_FILES
# OPENAI_PROXY_URL=https://api.openai.com/v1
# OPENAI_MODEL_LIST=...
-17
View File
@@ -29,20 +29,3 @@ logs
# misc
# add other ignore file below
.next
# temporary directories
tmp
temp
.temp
.local
docs/.local
# cache directories
.cache
# AI coding tools directories
.claude
.serena
# MCP tools
/.serena/**
@@ -0,0 +1,87 @@
name: '🐛 反馈缺陷'
description: '反馈一个问题缺陷'
labels: ['unconfirm']
type: Bug
body:
- type: markdown
attributes:
value: |
在创建新的 Issue 之前,请先[搜索已有问题](https://github.com/lobehub/lobe-chat/issues),如果发现已有类似的问题,请给它 **👍 点赞**,这样可以帮助我们更快地解决问题。
如果你在使用过程中遇到问题,可以尝试以下方式获取帮助:
- 在 [GitHub Discussions](https://github.com/lobehub/lobe-chat/discussions) 的版块发起讨论。
- 在 [LobeChat 社区](https://discord.gg/AYFPHvv2jT) 提问,与其他用户交流。
- type: dropdown
attributes:
label: '📦 部署环境'
multiple: true
options:
- 'Official Preview'
- 'Official Cloud'
- 'Vercel'
- 'Zeabur'
- 'Sealos'
- 'Netlify'
- 'Docker'
- 'Other'
validations:
required: true
- type: dropdown
attributes:
label: '📦 部署模式'
multiple: true
options:
- '客户端模式(lobe-chat 镜像)'
- '客户端 Pglite 模式(lobe-chat-pglite 镜像)'
- '服务端模式(lobe-chat-database 镜像)'
validations:
required: true
- type: input
attributes:
label: '📌 软件版本'
validations:
required: true
- type: dropdown
attributes:
label: '💻 系统环境'
multiple: true
options:
- 'Windows'
- 'macOS'
- 'Ubuntu'
- 'Other Linux'
- 'iOS'
- 'Android'
- 'Other'
validations:
required: true
- type: dropdown
attributes:
label: '🌐 浏览器'
multiple: true
options:
- 'Chrome'
- 'Edge'
- 'Safari'
- 'Firefox'
- 'Other'
validations:
required: true
- type: textarea
attributes:
label: '🐛 问题描述'
description: 请提供一个清晰且简洁的问题描述,若上述选项为`Other`,也请详细说明。
validations:
required: true
- type: textarea
attributes:
label: '📷 复现步骤'
description: 请提供一个清晰且简洁的描述,说明如何复现问题。
- type: textarea
attributes:
label: '🚦 期望结果'
description: 请提供一个清晰且简洁的描述,说明您期望发生什么。
- type: textarea
attributes:
label: '📝 补充信息'
description: 如果您的问题需要进一步说明,或者您遇到的问题无法在一个简单的示例中复现,请在这里添加更多信息。
@@ -0,0 +1,21 @@
name: '🌠 功能需求'
description: '提出需求或建议'
title: '[Request] '
type: Feature
body:
- type: textarea
attributes:
label: '🥰 需求描述'
description: 请添加一个清晰且简洁的问题描述,阐述您希望通过这个功能需求解决的问题。
validations:
required: true
- type: textarea
attributes:
label: '🧐 解决方案'
description: 请清晰且简洁地描述您想要的解决方案。
validations:
required: true
- type: textarea
attributes:
label: '📝 补充信息'
description: 在这里添加关于问题的任何其他背景信息。
+4 -4
View File
@@ -1,7 +1,7 @@
contact_links:
- name: Ask a question for self-hosting
- name: Ask a question for self-hosting | 咨询自部署问题
url: https://github.com/lobehub/lobe-chat/discussions/new?category=self-hosting-%E7%A7%81%E6%9C%89%E5%8C%96%E9%83%A8%E7%BD%B2
about: Please post questions, and ideas in discussions.
- name: Questions and ideas
about: Please post questions, and ideas in discussions. | 请在讨论区发布问题和想法。
- name: Questions and ideas | 其他问题和想法
url: https://github.com/lobehub/lobe-chat/discussions/new/choose
about: Please post questions, and ideas in discussions.
about: Please post questions, and ideas in discussions. | 请在讨论区发布问题和想法。
+3 -4
View File
@@ -1,4 +1,4 @@
#### 💻 Change Type
#### 💻 变更类型 | Change Type
<!-- For change type, change [ ] to [x]. -->
@@ -8,14 +8,13 @@
- [ ] 💄 style
- [ ] 👷 build
- [ ] ⚡️ perf
- [ ] ✅ test
- [ ] 📝 docs
- [ ] 🔨 chore
#### 🔀 Description of Change
#### 🔀 变更说明 | Description of Change
<!-- Thank you for your Pull Request. Please provide a description above. -->
#### 📝 Additional Information
#### 📝 补充信息 | Additional Information
<!-- Add any other context about the Pull Request here. -->
-89
View File
@@ -1,89 +0,0 @@
/**
* Generate or update PR comment with Docker build info
*/
module.exports = async ({
github,
context,
dockerMetaJson,
image,
version,
dockerhubUrl,
platforms,
}) => {
const COMMENT_IDENTIFIER = '<!-- DOCKER-BUILD-COMMENT -->';
const parseTags = () => {
try {
if (dockerMetaJson) {
const parsed = JSON.parse(dockerMetaJson);
if (Array.isArray(parsed.tags) && parsed.tags.length > 0) {
return parsed.tags;
}
}
} catch (e) {
// ignore parsing error, fallback below
}
if (image && version) {
return [`${image}:${version}`];
}
return [];
};
const generateCommentBody = () => {
const tags = parseTags();
const buildTime = new Date().toISOString();
// Use the first tag as the main version
const mainTag = tags.length > 0 ? tags[0] : `${image}:${version}`;
const tagVersion = mainTag.includes(':') ? mainTag.split(':')[1] : version;
return [
COMMENT_IDENTIFIER,
'',
'### 🐳 Database Docker Build Completed!',
`**Version**: \`${tagVersion || 'N/A'}\``,
`**Build Time**: \`${buildTime}\``,
'',
dockerhubUrl ? `🔗 View all tags on Docker Hub: ${dockerhubUrl}` : '',
'',
'### Pull Image',
'Download the Docker image to your local machine:',
'',
'```bash',
`docker pull ${mainTag}`,
'```',
'> [!IMPORTANT]',
'> This build is for testing and validation purposes.',
]
.filter(Boolean)
.join('\n');
};
const body = generateCommentBody();
// List comments on the PR
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const existing = comments.find((c) => c.body && c.body.includes(COMMENT_IDENTIFIER));
if (existing) {
await github.rest.issues.updateComment({
comment_id: existing.id,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
return { updated: true, id: existing.id };
}
const result = await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
return { updated: false, id: result.data.id };
};
-78
View File
@@ -1,78 +0,0 @@
// @ts-check
/**
* Lock closed issues after 7 days of inactivity
* @param {object} github - GitHub API client
* @param {object} context - GitHub Actions context
*/
module.exports = async ({ github, context }) => {
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const lockComment = `This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.`;
let page = 1;
let hasMore = true;
let totalLocked = 0;
while (hasMore) {
// Get closed issues (pagination)
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
sort: 'updated',
direction: 'asc',
per_page: 100,
page: page,
});
if (issues.length === 0) {
hasMore = false;
break;
}
for (const issue of issues) {
// Skip if already locked
if (issue.locked) continue;
// Skip pull requests
if (issue.pull_request) continue;
// Check if updated more than 7 days ago
const updatedAt = new Date(issue.updated_at);
if (updatedAt > sevenDaysAgo) {
// Since issues are sorted by updated_at ascending,
// once we hit a recent issue, all remaining will be recent too
hasMore = false;
break;
}
try {
// Add comment before locking
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: lockComment,
});
// Lock the issue
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
lock_reason: 'resolved',
});
totalLocked++;
console.log(`Locked issue #${issue.number}: ${issue.title}`);
} catch (error) {
console.error(`Failed to lock issue #${issue.number}: ${error.message}`);
}
}
page++;
}
console.log(`Total issues locked: ${totalLocked}`);
};
+2 -11
View File
@@ -36,19 +36,10 @@ module.exports = async ({ github, context, releaseUrl, version, tag }) => {
// Generate combined download table
let assetTable = '| Platform | File | Size |\n| --- | --- | --- |\n';
// Add macOS assets with architecture detection
// Add macOS assets
macAssets.forEach((asset) => {
const sizeInMB = (asset.size / (1024 * 1024)).toFixed(2);
// Detect architecture from filename
let architecture = '';
if (asset.name.includes('arm64')) {
architecture = ' (Apple Silicon)';
} else if (asset.name.includes('x64') || asset.name.includes('-mac.')) {
architecture = ' (Intel)';
}
assetTable += `| macOS${architecture} | [${asset.name}](${asset.browser_download_url}) | ${sizeInMB} MB |\n`;
assetTable += `| macOS | [${asset.name}](${asset.browser_download_url}) | ${sizeInMB} MB |\n`;
});
// Add Windows assets
+2 -2
View File
@@ -21,12 +21,12 @@ jobs:
git config --global user.name "lobehubbot"
git config --global user.email "i@lobehub.com"
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@v1
with:
bun-version: ${{ secrets.BUN_VERSION }}
-120
View File
@@ -1,120 +0,0 @@
name: Claude Translator
concurrency:
group: translator-${{ github.event.comment.id || github.event.issue.number || github.event.review.id }}
cancel-in-progress: false
on:
issues:
types: [opened]
issue_comment:
types: [created, edited]
pull_request_review:
types: [submitted, edited]
pull_request_review_comment:
types: [created, edited]
jobs:
translate:
if: |
(github.event_name == 'issues') ||
(github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') ||
(github.event_name == 'pull_request_review' && github.event.sender.type != 'Bot') ||
(github.event_name == 'pull_request_review_comment' && github.event.sender.type != 'Bot')
runs-on: ubuntu-latest
permissions:
contents: read
# update issues/comments
issues: write
pull-requests: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude for translation
uses: anthropics/claude-code-action@main
id: claude
with:
# Warning: Permissions should have been controlled by workflow permission.
# Now `contents: read` is safe for files, but we could make a fine-grained token to control it.
# See: https://github.com/anthropics/claude-code-action/blob/main/docs/security.md
github_token: ${{ secrets.GH_TOKEN }}
allowed_non_write_users: "*"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:repos/*/issues:*),Bash(gh api:repos/*/pulls/*/reviews/*),Bash(gh api:repos/*/pulls/comments/*)"
prompt: |
You are a multilingual translation assistant. You need to respond to the following four types of GitHub Webhook events:
- issues
- issue_comment
- pull_request_review
- pull_request_review_comment
Please complete the following tasks:
1. Retrieve complete information for the current event.
- If the current event is 'issues', get the issue information.
- If the current event is 'issue_comment', get the comment information.
- If the current event is 'pull_request_review', get the review information.
- If the current event is 'pull_request_review_comment', get the comment information.
2. Intelligently detect content.
- If the retrieved information is already translated content following the format requirements, check if the translation matches the original content. If not, retranslate to match and follow the format requirements.
- If the retrieved information is untranslated content, check its language. If not in English, translate to English.
- If the retrieved information is partially translated to English, translate it completely to English.
- If the retrieved information contains references to already translated content, clean the referenced content to contain only English. Referenced content should not include "This xxx was translated by Claude" and "Original Content" etc.
- If the retrieved information contains other types of references (i.e., references to non-Claude translated content), keep them as-is without translation.
- If the retrieved information is email reply content, place email content references at the end during translation. Include only the reply content itself in both original and translated content, without email content references.
- If the retrieved information doesn't need any processing, skip the task.
3. Format requirements:
- Title: English translation (if non-English)
- Content format:
[Translated content]
---
> This issue/comment/review was translated by Claude.
<details>
<summary>Original Content</summary>
[Original content]
</details>
4. CRITICAL RULES to prevent hallucination and ensure accuracy:
- The "Original Content" section MUST contain the EXACT, UNMODIFIED original text byte-for-byte. NEVER add, remove, modify, or hallucinate ANY content in this section.
- Code blocks, error logs, JSON structures, and other technical content MUST appear in BOTH the translated section AND the original content section WITHOUT ANY MODIFICATION.
- When translating content with code/logs/JSON:
* Copy the code/logs/JSON blocks identically to both sections
* Only translate the natural language text (e.g., Chinese, Japanese) surrounding the code blocks
* Keep all technical content (URLs, variable names, error messages in English) unchanged
- ALWAYS verify the "Original Content" section matches the source text exactly before updating
- If you detect any discrepancy, retrieve the original content again to ensure accuracy
- Pay special attention to the end of comments - do not drop or hallucinate the last sentences
5. Update using gh tool:
- Choose the correct command based on the Event type in environment information:
- If Event is 'issues': gh issue edit [ISSUE_NUMBER] --title "[English title]" --body "[Translated content + Original content]"
- If Event is 'issue_comment': gh api -X PATCH /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }} -f body="[Translated content + Original content]"
- If Event is 'pull_request_review': gh api -X PUT /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${{ github.event.review.id }} -f body="[Translated content]"
- If Event is 'pull_request_review_comment': gh api -X PATCH /repos/${{ github.repository }}/pulls/comments/${{ github.event.comment.id }} -f body="[Translated content + Original content]"
<environment_context>
- Event: ${{ github.event_name }}
- Issue Number: ${{ github.event.issue.number }}
- Repository: ${{ github.repository }}
- (Review) Comment ID: ${{ github.event.comment.id || 'N/A' }}
- Pull Request Number: ${{ github.event.pull_request.number || 'N/A' }}
- Review ID: ${{ github.event.review.id || 'N/A' }}
</environment_context>
Use the following command to get complete information:
gh issue view ${{ github.event.issue.number }} --json title,body,comments
-64
View File
@@ -1,64 +0,0 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
# model: 'claude-opus-4-1-20250805'
allowed_bots: 'bot'
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
# Optional: Allow Claude to run specific commands
allowed_tools: 'Bash(bun run:*),Bash(pnpm run:*),Bash(npm run:*),Bash(npx:*),Bash(bunx:*),Bash(vitest:*),Bash(rg:*),Bash(find:*),Bash(sed:*),Bash(grep:*),Bash(awk:*),Bash(wc:*),Bash(xargs:*)'
# Optional: Add custom instructions for Claude to customize its behavior for your project
# custom_instructions: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Custom environment variables for Claude
# claude_env: |
# NODE_ENV: test
+36 -124
View File
@@ -23,28 +23,27 @@ jobs:
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
steps:
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
bun-version: 1.2.23
version: 9
- name: Install deps
run: bun i
run: pnpm install
env:
NODE_OPTIONS: --max-old-space-size=6144
- name: Lint
run: bun run lint
run: pnpm run lint
env:
NODE_OPTIONS: --max-old-space-size=6144
@@ -57,15 +56,14 @@ jobs:
# 输出版本信息,供后续 job 使用
version: ${{ steps.set_version.outputs.version }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
# 主要逻辑:确定构建版本号
- name: Set version
@@ -95,25 +93,24 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-15-intel, windows-2025, ubuntu-latest]
os: [macos-latest, windows-2025, ubuntu-latest]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
# node-linker=hoisted 模式将可以确保 asar 压缩可用
- name: Install dependencies
- name: Install deps
run: pnpm install --node-linker=hoisted
- name: Install deps on Desktop
@@ -129,11 +126,11 @@ jobs:
run: npm run desktop:build
env:
# 设置更新通道,PR构建为nightly,否则为stable
UPDATE_CHANNEL: "nightly"
UPDATE_CHANNEL: 'nightly'
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
# 默认添加一个加密 SECRET
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
# macOS 签名和公证配置
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -152,10 +149,10 @@ jobs:
run: npm run desktop:build
env:
# 设置更新通道,PR构建为nightly,否则为stable
UPDATE_CHANNEL: "nightly"
UPDATE_CHANNEL: 'nightly'
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_BASE_URL }}
# 将 TEMP 和 TMP 目录设置到 C 盘
@@ -168,35 +165,13 @@ jobs:
run: npm run desktop:build
env:
# 设置更新通道,PR构建为nightly,否则为stable
UPDATE_CHANNEL: "nightly"
UPDATE_CHANNEL: 'nightly'
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_BASE_URL }}
# 处理 macOS latest-mac.yml 重命名 (避免多架构覆盖)
- name: Rename macOS latest-mac.yml for multi-architecture support
if: runner.os == 'macOS'
run: |
cd apps/desktop/release
if [ -f "latest-mac.yml" ]; then
# 使用系统架构检测,与 electron-builder 输出保持一致
SYSTEM_ARCH=$(uname -m)
if [[ "$SYSTEM_ARCH" == "arm64" ]]; then
ARCH_SUFFIX="arm64"
else
ARCH_SUFFIX="x64"
fi
mv latest-mac.yml "latest-mac-${ARCH_SUFFIX}.yml"
echo "✅ Renamed latest-mac.yml to latest-mac-${ARCH_SUFFIX}.yml (detected: $SYSTEM_ARCH)"
ls -la latest-mac-*.yml
else
echo "⚠️ latest-mac.yml not found, skipping rename"
ls -la latest*.yml || echo "No latest*.yml files found"
fi
# 上传构建产物
- name: Upload artifact
uses: actions/upload-artifact@v4
@@ -208,70 +183,10 @@ jobs:
apps/desktop/release/*.zip*
apps/desktop/release/*.exe*
apps/desktop/release/*.AppImage
apps/desktop/release/*.deb*
apps/desktop/release/*.snap*
apps/desktop/release/*.rpm*
apps/desktop/release/*.tar.gz*
retention-days: 5
# 合并 macOS 多架构 latest-mac.yml 文件
merge-mac-files:
needs: [build, version]
name: Merge macOS Release Files for PR
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
# 下载所有平台的构建产物
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: release
pattern: release-*
merge-multiple: true
# 列出下载的构建产物
- name: List downloaded artifacts
run: ls -R release
# 仅为该步骤在脚本目录安装 yaml 单包,避免安装整个 monorepo 依赖
- name: Install yaml only for merge step
run: |
cd scripts/electronWorkflow
# 在脚本目录创建最小 package.json,防止 bun 向上寻找根 package.json
if [ ! -f package.json ]; then
echo '{"name":"merge-mac-release","private":true}' > package.json
fi
bun add --no-save yaml@2.8.1
# 合并 macOS YAML 文件 (使用 bun 运行 JavaScript)
- name: Merge latest-mac.yml files
run: bun run scripts/electronWorkflow/mergeMacReleaseFiles.js
# 上传合并后的构建产物
- name: Upload artifacts with merged macOS files
uses: actions/upload-artifact@v4
with:
name: merged-release-pr
path: release/
retention-days: 1
publish-pr:
needs: [merge-mac-files, version]
needs: [build, version]
name: Publish PR Build
runs-on: ubuntu-latest
# Grant write permissions for creating release and commenting on PR
@@ -281,25 +196,26 @@ jobs:
outputs:
artifact_path: ${{ steps.set_path.outputs.path }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
# 下载合并后的构建产物
- name: Download merged artifacts
# 下载所有平台的构建产物
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: merged-release-pr
path: release
pattern: release-*
merge-multiple: true
# 列出所有构建产物
- name: List final artifacts
- name: List artifacts
run: ls -R release
# 生成PR发布描述
- name: Generate PR Release Body
id: pr_release_body
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
result-encoding: string
script: |
@@ -329,16 +245,12 @@ jobs:
release/*.zip*
release/*.exe*
release/*.AppImage
release/*.deb*
release/*.snap*
release/*.rpm*
release/*.tar.gz*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 在 PR 上添加评论,包含构建信息和下载链接
- name: Comment on PR
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
+3 -23
View File
@@ -1,6 +1,4 @@
name: Publish Database Docker Image
permissions:
contents: read
on:
workflow_dispatch:
@@ -41,7 +39,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -82,7 +80,7 @@ jobs:
- name: Build and export
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
platforms: ${{ matrix.platform }}
context: .
@@ -113,7 +111,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -161,21 +159,3 @@ jobs:
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
- name: Comment on PR with Docker build info
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prComment = require('${{ github.workspace }}/.github/scripts/docker-pr-comment.js');
const result = await prComment({
github,
context,
dockerMetaJson: ${{ toJSON(steps.meta.outputs.json) }},
image: "${{ env.REGISTRY_IMAGE }}",
version: "${{ steps.meta.outputs.version }}",
dockerhubUrl: "https://hub.docker.com/r/${{ env.REGISTRY_IMAGE }}/tags",
platforms: "linux/amd64, linux/arm64",
});
core.info(`Status: ${result.updated ? 'Updated' : 'Created'}, ID: ${result.id}`);
+3 -5
View File
@@ -1,6 +1,4 @@
name: Publish Docker Pglite Image
permissions:
contents: read
on:
workflow_dispatch:
@@ -41,7 +39,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -82,7 +80,7 @@ jobs:
- name: Build and export
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
platforms: ${{ matrix.platform }}
context: .
@@ -113,7 +111,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
+3 -5
View File
@@ -1,6 +1,4 @@
name: Publish Docker Image
permissions:
contents: read
on:
workflow_dispatch:
@@ -41,7 +39,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -82,7 +80,7 @@ jobs:
- name: Build and export
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
platforms: ${{ matrix.platform }}
context: .
@@ -113,7 +111,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
-52
View File
@@ -1,52 +0,0 @@
name: E2E CI
permissions:
contents: read
on:
pull_request:
push:
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
e2e:
name: Test Web App
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies (bun)
run: bun install
- name: Install Playwright browsers (with system deps)
run: bunx playwright install --with-deps chromium
- name: Run E2E tests
env:
PORT: 3010
run: bun run e2e
- name: Upload Playwright HTML report (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report
if-no-files-found: ignore
- name: Upload Playwright traces (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results
if-no-files-found: ignore
+9 -4
View File
@@ -28,7 +28,8 @@ jobs:
👀 @{{ author }}
Thank you for raising an issue. We will investigate into the matter and get back to you as soon as possible.
Please make sure you have given us as much context as possible.
Please make sure you have given us as much context as possible.\
非常感谢您提交 issue。我们会尽快调查此事,并尽快回复您。 请确保您已经提供了尽可能多的背景信息。
- name: Auto Comment on Issues Closed
uses: wow-actions/auto-comment@v1
with:
@@ -36,7 +37,8 @@ jobs:
issuesClosed: |
✅ @{{ author }}
This issue is closed, If you have any questions, you can comment and reply.
This issue is closed, If you have any questions, you can comment and reply.\
此问题已经关闭。如果您有任何问题,可以留言并回复。
- name: Auto Comment on Pull Request Opened
uses: wow-actions/auto-comment@v1
with:
@@ -46,7 +48,9 @@ jobs:
Thank you for raising your pull request and contributing to our Community
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
If you encounter any problems, please feel free to connect with us.
If you encounter any problems, please feel free to connect with us.\
非常感谢您提出拉取请求并为我们的社区做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。
如果您遇到任何问题,请随时与我们联系。
- name: Auto Comment on Pull Request Merged
uses: actions-cool/pr-welcome@main
if: github.event.pull_request.merged == true
@@ -55,7 +59,8 @@ jobs:
comment: |
❤️ Great PR @${{ github.event.pull_request.user.login }} ❤️
The growth of project is inseparable from user feedback and contribution, thanks for your contribution! If you are interesting with the lobehub developer community, please join our [discord](https://discord.com/invite/AYFPHvv2jT) and then dm @arvinxx or @canisminor1990. They will invite you to our private developer channel. We are talking about the lobe-chat development or sharing ai newsletter around the world.
The growth of project is inseparable from user feedback and contribution, thanks for your contribution! If you are interesting with the lobehub developer community, please join our [discord](https://discord.com/invite/AYFPHvv2jT) and then dm @arvinxx or @canisminor1990. They will invite you to our private developer channel. We are talking about the lobe-chat development or sharing ai newsletter around the world.\
项目的成长离不开用户反馈和贡献,感谢您的贡献! 如果您对 LobeHub 开发者社区感兴趣,请加入我们的 [discord](https://discord.com/invite/AYFPHvv2jT),然后私信 @arvinxx 或 @canisminor1990。他们会邀请您加入我们的私密开发者频道。我们将会讨论关于 Lobe Chat 的开发,分享和讨论全球范围内的 AI 消息。
emoji: 'hooray'
pr-emoji: '+1, heart'
- name: Remove inactive
+6 -3
View File
@@ -38,7 +38,8 @@ jobs:
body: |
👋 @{{ author }}
<br/>
Since the issue was labeled with `✅ Fixed`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
Since the issue was labeled with `✅ Fixed`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\
由于该 issue 被标记为已修复,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
- name: need reproduce
uses: actions-cool/issues-helper@v3
with:
@@ -49,7 +50,8 @@ jobs:
body: |
👋 @{{ author }}
<br/>
Since the issue was labeled with `🤔 Need Reproduce`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
Since the issue was labeled with `🤔 Need Reproduce`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\
由于该 issue 被标记为需要更多信息,却 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
- name: need reproduce
uses: actions-cool/issues-helper@v3
with:
@@ -60,4 +62,5 @@ jobs:
body: |
👋 @{{ github.event.issue.user.login }}
<br/>
Since the issue was labeled with `🙅🏻‍♀️ WON'T DO`, and no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
Since the issue was labeled with `🙅🏻‍♀️ WON'T DO`, and no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\
由于该 issue 被标记为暂不处理,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
+14
View File
@@ -0,0 +1,14 @@
name: Issue Translate
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
BOT_GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
+2 -2
View File
@@ -42,12 +42,12 @@ jobs:
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
env:
REPO_BRANCH: ${{ matrix.REPO_BRANCH || env.REPO_BRANCH }}
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
repository: ${{ env.REPOSITORY }}
token: ${{ secrets[matrix.TOKEN_NAME] || secrets[env.TOKEN_NAME] }}
ref: ${{ env.BRANCH }}
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
repository: 'myactionway/lighthouse-badges'
path: temp_lighthouse_badges_nested
-26
View File
@@ -1,26 +0,0 @@
name: "Lock Stale Issues"
on:
schedule:
- cron: "0 1 * * *"
workflow_dispatch:
permissions:
issues: write
concurrency:
group: lock-threads
jobs:
lock-closed-issues:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Lock closed issues after 7 days of inactivity
uses: actions/github-script@v8
with:
script: |
const lockScript = require('./.github/scripts/lock-closed-issues.js');
await lockScript({ github, context });
+33 -118
View File
@@ -19,26 +19,25 @@ jobs:
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
steps:
- name: Checkout base
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
bun-version: 1.2.23
version: 9
- name: Install deps
run: bun i
run: pnpm install
- name: Lint
run: bun run lint
run: pnpm run lint
version:
name: Determine version
@@ -48,15 +47,14 @@ jobs:
version: ${{ steps.set_version.outputs.version }}
is_pr_build: ${{ steps.set_version.outputs.is_pr_build }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
# 主要逻辑:确定构建版本号
- name: Set version
@@ -82,25 +80,24 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-15-intel, windows-2025, ubuntu-latest]
os: [macos-latest, windows-2025, ubuntu-latest]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
# node-linker=hoisted 模式将可以确保 asar 压缩可用
- name: Install dependencies
- name: Install deps
run: pnpm install --node-linker=hoisted
- name: Install deps on Desktop
@@ -116,9 +113,9 @@ jobs:
run: npm run desktop:build
env:
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
# 默认添加一个加密 SECRET
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
# macOS 签名和公证配置
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -137,8 +134,8 @@ jobs:
run: npm run desktop:build
env:
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_BETA_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_BETA_DESKTOP_BASE_URL }}
@@ -152,34 +149,12 @@ jobs:
run: npm run desktop:build
env:
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_BETA_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_BETA_DESKTOP_BASE_URL }}
# 处理 macOS latest-mac.yml 重命名 (避免多架构覆盖)
- name: Rename macOS latest-mac.yml for multi-architecture support
if: runner.os == 'macOS'
run: |
cd apps/desktop/release
if [ -f "latest-mac.yml" ]; then
# 使用系统架构检测,与 electron-builder 输出保持一致
SYSTEM_ARCH=$(uname -m)
if [[ "$SYSTEM_ARCH" == "arm64" ]]; then
ARCH_SUFFIX="arm64"
else
ARCH_SUFFIX="x64"
fi
mv latest-mac.yml "latest-mac-${ARCH_SUFFIX}.yml"
echo "✅ Renamed latest-mac.yml to latest-mac-${ARCH_SUFFIX}.yml (detected: $SYSTEM_ARCH)"
ls -la latest-mac-*.yml
else
echo "⚠️ latest-mac.yml not found, skipping rename"
ls -la latest*.yml || echo "No latest*.yml files found"
fi
# 上传构建产物 (工作流处理重命名,不依赖 electron-builder 钩子)
# 上传构建产物,移除了 zip 相关部分
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
@@ -190,34 +165,19 @@ jobs:
apps/desktop/release/*.zip*
apps/desktop/release/*.exe*
apps/desktop/release/*.AppImage
apps/desktop/release/*.deb*
apps/desktop/release/*.snap*
apps/desktop/release/*.rpm*
apps/desktop/release/*.tar.gz*
retention-days: 5
# 合并 macOS 多架构 latest-mac.yml 文件
merge-mac-files:
# 正式版发布 job
publish-release:
needs: [build, version]
name: Merge macOS Release Files
name: Publish Beta Release
runs-on: ubuntu-latest
# Grant write permission to contents for uploading release assets
permissions:
contents: write
outputs:
artifact_path: ${{ steps.set_path.outputs.path }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
# 下载所有平台的构建产物
- name: Download artifacts
uses: actions/download-artifact@v4
@@ -226,52 +186,11 @@ jobs:
pattern: release-*
merge-multiple: true
# 列出下载的构建产物
- name: List downloaded artifacts
run: ls -R release
# 仅为该步骤在脚本目录安装 yaml 单包,避免安装整个 monorepo 依赖
- name: Install yaml only for merge step
run: |
cd scripts/electronWorkflow
# 在脚本目录创建最小 package.json,防止 bun 向上寻找根 package.json
if [ ! -f package.json ]; then
echo '{"name":"merge-mac-release","private":true}' > package.json
fi
bun add --no-save yaml@2.8.1
# 合并 macOS YAML 文件 (使用 bun 运行 JavaScript)
- name: Merge latest-mac.yml files
run: bun run scripts/electronWorkflow/mergeMacReleaseFiles.js
# 上传合并后的构建产物
- name: Upload artifacts with merged macOS files
uses: actions/upload-artifact@v4
with:
name: merged-release
path: release/
retention-days: 1
# 发布所有平台构建产物
publish-release:
needs: [merge-mac-files]
name: Publish Beta Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# 下载合并后的构建产物
- name: Download merged artifacts
uses: actions/download-artifact@v4
with:
name: merged-release
path: release
# 列出所有构建产物
- name: List final artifacts
- name: List artifacts
run: ls -R release
# 将构建产物上传到现有 release (现在包含合并后的 latest-mac.yml)
# 将构建产物上传到现有 release
- name: Upload to Release
uses: softprops/action-gh-release@v1
with:
@@ -282,9 +201,5 @@ jobs:
release/*.zip*
release/*.exe*
release/*.AppImage
release/*.deb*
release/*.snap*
release/*.rpm*
release/*.tar.gz*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+9 -20
View File
@@ -1,10 +1,4 @@
name: Release CI
permissions:
contents: write
issues: write
pull-requests: write
on:
push:
branches:
@@ -17,31 +11,26 @@ jobs:
services:
postgres:
image: paradedb/paradedb:latest
image: pgvector/pgvector:pg16
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v5
with:
token: ${{ secrets.GH_TOKEN }}
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.23
bun-version: ${{ secrets.BUN_VERSION }}
- name: Install deps
run: bun i
@@ -49,8 +38,8 @@ jobs:
- name: Lint
run: bun run lint
- name: Test Database Coverage
run: bun run --filter @lobechat/database test
- name: Test Server Coverage
run: bun run test-server:coverage
env:
DATABASE_TEST_URL: postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_DRIVER: node
@@ -59,8 +48,8 @@ jobs:
S3_PUBLIC_DOMAIN: https://example.com
APP_URL: https://home.com
- name: Test App
run: bun run test-app
- name: Test App Coverage
run: bun run test-app:coverage
- name: Release
run: bun run release
+5 -9
View File
@@ -1,6 +1,4 @@
name: Database Schema Visualization CI
permissions:
contents: read
on:
push:
@@ -13,15 +11,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ secrets.BUN_VERSION }}
- name: Install dbdocs
run: sudo npm install -g dbdocs
- name: Install deps
run: bun i
- name: Check dbdocs
run: dbdocs
- name: sync database schema to dbdocs
env:
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: ${{ github.event.repository.fork }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Clean issue notice
uses: actions-cool/issues-helper@v3
+21 -137
View File
@@ -2,126 +2,8 @@ name: Test CI
on: [push, pull_request]
permissions:
contents: read
jobs:
# Package tests - using each package's own test script
test-intenral-packages:
runs-on: ubuntu-latest
strategy:
matrix:
package:
- file-loaders
- prompts
- model-runtime
- web-crawler
- electron-server-ipc
- utils
- python-interpreter
- context-engine
- agent-runtime
name: Test package ${{ matrix.package }}
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ secrets.BUN_VERSION }}
- name: Install deps
run: bun i
- name: Test ${{ matrix.package }} package with coverage
run: bun run --filter @lobechat/${{ matrix.package }} test:coverage
- name: Upload ${{ matrix.package }} coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/${{ matrix.package }}/coverage/lcov.info
flags: packages/${{ matrix.package }}
test-packages:
runs-on: ubuntu-latest
strategy:
matrix:
package: [model-bank]
name: Test package ${{ matrix.package }}
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
- name: Install deps
run: bun i
- name: Test ${{ matrix.package }} package with coverage
run: bun run --filter ${{ matrix.package }} test:coverage
- name: Upload ${{ matrix.package }} coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/${{ matrix.package }}/coverage/lcov.info
flags: packages/${{ matrix.package }}
# App tests
test-website:
name: Test Website
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
- name: Install deps
run: bun i
- name: Test App Coverage
run: bun run test-app:coverage
- name: Upload App Coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/app/lcov.info
flags: app
test-databsae:
name: Test Database
test:
runs-on: ubuntu-latest
services:
@@ -137,18 +19,17 @@ jobs:
- 5432:5432
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.23
bun-version: ${{ secrets.BUN_VERSION }}
- name: Install deps
run: bun i
@@ -156,15 +37,8 @@ jobs:
- name: Lint
run: bun run lint
- name: Test Client DB
run: bun run --filter @lobechat/database test:client-db
env:
KEY_VAULTS_SECRET: LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=
S3_PUBLIC_DOMAIN: https://example.com
APP_URL: https://home.com
- name: Test Coverage
run: bun run --filter @lobechat/database test:coverage
- name: Test Server Coverage
run: bun run test-server:coverage
env:
DATABASE_TEST_URL: postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_DRIVER: node
@@ -173,9 +47,19 @@ jobs:
S3_PUBLIC_DOMAIN: https://example.com
APP_URL: https://home.com
- name: Upload Database coverage to Codecov
uses: codecov/codecov-action@v5
- name: Upload Server coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/database/coverage/lcov.info
flags: database
files: ./coverage/server/lcov.info
flags: server
- name: Test App Coverage
run: bun run test-app:coverage
- name: Upload App Coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/app/lcov.info
flags: app
+19
View File
@@ -0,0 +1,19 @@
name: Wiki Sync
on:
workflow_dispatch:
push:
paths:
- 'docs/wiki/**'
branches:
- main
jobs:
update-wiki:
runs-on: ubuntu-latest
name: Wiki sync
steps:
- uses: OrlovM/Wiki-Action@v1
with:
path: 'docs/wiki'
token: ${{ secrets.GH_TOKEN }}
+56 -98
View File
@@ -1,119 +1,77 @@
# Gitignore for LobeHub
################################################################
# System files
# general
.DS_Store
Thumbs.db
ehthumbs.db
Desktop.ini
# Linux/Ubuntu system files
*~
*.swp
*.swo
.fuse_hidden*
.directory
.Trash-*
.nfs*
.gvfs-fuse-daemon-*
# IDE and editors
.idea/
*.sublime-*
.history/
.windsurfrules
*.code-workspace
.vscode/sessions.json
# Temporary files
.temp/
temp/
tmp/
*.tmp
*.temp
*.log
*.cache
.cache/
# Environment files
.env
.idea
.vscode
.history
.temp
.env.local
.env*.local
.env.development
venv/
.venv/
venv
temp
tmp
.windsurfrules
# Dependencies
node_modules/
# dependencies
node_modules
*.log
*.lock
package-lock.json
bun.lockb
.pnpm-store/
# Build outputs
dist/
es/
lib/
.next/
logs/
test-output/
*.tsbuildinfo
next-env.d.ts
# Framework specific
# Umi
.umi/
.umi-production/
.umi-test/
.dumi/tmp*/
# Vercel
.vercel/
# Testing and CI
coverage/
.coverage/
.nyc_output/
# ci
coverage
.coverage
.eslintcache
.stylelintcache
# Service Worker
# production
dist
es
lib
logs
test-output
# umi
.umi
.umi-production
.umi-test
.dumi/tmp*
# husky
.husky/prepare-commit-msg
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.next
.env
public/*.js
public/sitemap.xml
public/sitemap-index.xml
bun.lockb
sitemap*.xml
robots.txt
# Serwist
public/sw*
public/swe-worker*
# Generated files
public/*.js
public/sitemap.xml
public/sitemap-index.xml
sitemap*.xml
robots.txt
# Git hooks
.husky/prepare-commit-msg
# Documents and media
*.patch
*.pdf
# Cloud service keys
vertex-ai-key.json
# AI coding tools
.local/
.claude/
.mcp.json
CLAUDE.local.md
# MCP tools
.serena/**
# Misc
.pnpm-store
./packages/lobe-ui
*.ppt*
*.doc*
*.xls*
prd
GEMINI.md
# local use ai coding files
docs/.prd
.claude
.mcp.json
CLAUDE.md
+1 -1
View File
@@ -25,7 +25,7 @@ module.exports = defineConfig({
],
temperature: 0,
saveImmediately: true,
modelName: 'chatgpt-4o-latest',
modelName: 'gpt-4.1-mini',
experimental: {
jsonMode: true,
},
-3
View File
@@ -4,9 +4,6 @@ resolution-mode=highest
ignore-workspace-root-check=true
enable-pre-post-scripts=true
# Load dotenv files for all the npm scripts
node-options="--require dotenv-expand/config"
public-hoist-pattern[]=*@umijs/lint*
public-hoist-pattern[]=*changelog*
public-hoist-pattern[]=*commitlint*
+1
View File
@@ -5,6 +5,7 @@
.DS_Store
.editorconfig
.idea
.vscode
.history
.temp
.env.local
View File
View File
View File
-39
View File
@@ -1,39 +0,0 @@
# Stylelintignore for LobeHub
################################################################
# dependencies
node_modules
# ci
coverage
.coverage
# production
dist
es
lib
logs
# framework specific
.next
.umi
.umi-production
.umi-test
.dumi/tmp*
# temporary directories
tmp
temp
.temp
.local
docs/.local
# cache directories
.cache
# AI coding tools directories
.claude
.serena
# MCP tools
/.serena/**
-13
View File
@@ -1,13 +0,0 @@
{
"recommendations": [
"Anthropic.claude-code",
"dbaeumer.vscode-eslint",
"jrr997.antd-docs",
"seatonjiang.gitmoji-vscode",
"styled-components.vscode-styled-components",
"stylelint.vscode-stylelint",
"unifiedjs.vscode-mdx",
"unifiedjs.vscode-remark",
"vitest.explorer",
]
}
-96
View File
@@ -1,96 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.addMissingImports": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"editor.formatOnSave": true,
// don't show errors, but fix when save and git pre commit
"eslint.rules.customizations": [
{ "rule": "import/order", "severity": "off" },
{ "rule": "prettier/prettier", "severity": "off" },
{ "rule": "react/jsx-sort-props", "severity": "off" },
{ "rule": "sort-keys-fix/sort-keys-fix", "severity": "off" },
{ "rule": "simple-import-sort/exports", "severity": "off" },
{ "rule": "typescript-sort-keys/interface", "severity": "off" }
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
// support mdx
"mdx"
],
"npm.packageManager": "pnpm",
"search.exclude": {
"**/node_modules": true,
// useless to search this big folder
"locales": true
},
"stylelint.validate": [
"css",
"postcss",
// make stylelint work with tsx antd-style css template string
"typescriptreact"
],
"vitest.maximumConfigs": 20,
"workbench.editor.customLabels.patterns": {
"**/app/**/[[]*[]]/[[]*[]]/page.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • page component",
"**/app/**/[[]*[]]/page.tsx": "${dirname(1)}/${dirname} • page component",
"**/app/**/page.tsx": "${dirname} • page component",
"**/app/**/[[]*[]]/[[]*[]]/layout.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • page layout",
"**/app/**/[[]*[]]/layout.tsx": "${dirname(1)}/${dirname} • page layout",
"**/app/**/layout.tsx": "${dirname} • page layout",
"**/app/**/[[]*[]]/[[]*[]]/default.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • slot default",
"**/app/**/[[]*[]]/default.tsx": "${dirname(1)}/${dirname} • slot default",
"**/app/**/default.tsx": "${dirname} • slot default",
"**/app/**/[[]*[]]/[[]*[]]/error.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • error component",
"**/app/**/[[]*[]]/error.tsx": "${dirname(1)}/${dirname} • error component",
"**/app/**/error.tsx": "${dirname} • error component",
"**/app/**/[[]*[]]/[[]*[]]/loading.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • loading component",
"**/app/**/[[]*[]]/loading.tsx": "${dirname(1)}/${dirname} • loading component",
"**/app/**/loading.tsx": "${dirname} • loading component",
"**/src/**/route.ts": "${dirname(1)}/${dirname} • route",
"**/src/**/index.tsx": "${dirname} • component",
"**/packages/database/src/repositories/*/index.ts": "${dirname} • db repository",
"**/packages/database/src/models/*.ts": "${filename} • db model",
"**/packages/database/src/schemas/*.ts": "${filename} • db schema",
"**/src/services/*.ts": "${filename} • service",
"**/src/services/*/client.ts": "${dirname} • client service",
"**/src/services/*/server.ts": "${dirname} • server service",
"**/src/store/*/action.ts": "${dirname} • action",
"**/src/store/*/slices/*/action.ts": "${dirname(2)}/${dirname} • action",
"**/src/store/*/slices/*/actions/*.ts": "${dirname(1)}/${dirname}/${filename} • action",
"**/src/store/*/initialState.ts": "${dirname} • state",
"**/src/store/*/slices/*/initialState.ts": "${dirname(2)}/${dirname} • state",
"**/src/store/*/selectors.ts": "${dirname} • selectors",
"**/src/store/*/slices/*/selectors.ts": "${dirname(2)}/${dirname} • selectors",
"**/src/store/*/reducer.ts": "${dirname} • reducer",
"**/src/store/*/slices/*/reducer.ts": "${dirname(2)}/${dirname} • reducer",
"**/src/config/modelProviders/*.ts": "${filename} • provider",
"**/packages/model-bank/src/aiModels/*.ts": "${filename} • model",
"**/packages/model-runtime/src/providers/*/index.ts": "${dirname} • runtime",
"**/src/server/services/*/index.ts": "${dirname} • server/service",
"**/src/server/routers/lambda/*.ts": "${filename} • lambda",
"**/src/server/routers/async/*.ts": "${filename} • async",
"**/src/server/routers/edge/*.ts": "${filename} • edge",
"**/src/locales/default/*.ts": "${filename} • locale",
"**/index.*": "${dirname}/${filename}.${extname}"
}
}
-109
View File
@@ -1,109 +0,0 @@
# LobeChat Development Guidelines
This document serves as a comprehensive guide for all team members when developing LobeChat.
## Tech Stack
Built with modern technologies:
- **Frontend**: Next.js 15, React 19, TypeScript
- **UI Components**: Ant Design, @lobehub/ui, antd-style
- **State Management**: Zustand, SWR
- **Database**: PostgreSQL, PGLite, Drizzle ORM
- **Testing**: Vitest, Testing Library
- **Package Manager**: pnpm (monorepo structure)
- **Build Tools**: Next.js (Turbopack in dev, Webpack in prod)
## Directory Structure
The project follows a well-organized monorepo structure:
- `apps/` - Main applications
- `packages/` - Shared packages and libraries
- `src/` - Main source code
- `docs/` - Documentation
- `.cursor/rules/` - Development rules and guidelines
## Development Workflow
### Git Workflow
- Use rebase for git pull
- Git commit messages should prefix with gitmoji
- Git branch name format: `username/feat/feature-name`
- Use `.github/PULL_REQUEST_TEMPLATE.md` for PR descriptions
### Package Management
- Use `pnpm` as the primary package manager
- Use `bun` to run npm scripts
- Use `bunx` to run executable npm packages
- Navigate to specific packages using `cd packages/<package-name>`
### Code Style Guidelines
#### TypeScript
- Prefer interfaces over types for object shapes
### Testing Strategy
**Required Rule**: `testing-guide/testing-guide.mdc`
**Commands**:
- Web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'`
- Packages: `cd packages/[package-name] && bunx vitest run --silent='passed-only' '[file-path-pattern]'` (each subpackage contains its own vitest.config.mts)
**Important Notes**:
- Wrap file paths in single quotes to avoid shell expansion
- Never run `bun run test` - this runs all tests and takes \~10 minutes
### Type Checking
- Use `bun run type-check` to check for type errors
### i18n
- **Keys**: Add to `src/locales/default/namespace.ts`
- **Dev**: Translate `locales/zh-CN/namespace.json` locale file only for preview
- DON'T run `pnpm i18n`, let CI auto handle it
## Project Rules Index
All following rules are saved under `.cursor/rules/` directory:
### Backend
- `drizzle-schema-style-guide.mdc` Style guide for defining Drizzle ORM schemas
### Frontend
- `react-component.mdc` React component style guide and conventions
- `i18n.mdc` Internationalization guide using react-i18next
- `typescript.mdc` TypeScript code style guide
- `packages/react-layout-kit.mdc` Usage guide for react-layout-kit
### State Management
- `zustand-action-patterns.mdc` Recommended patterns for organizing Zustand actions
- `zustand-slice-organization.mdc` Best practices for structuring Zustand slices
### Desktop (Electron)
- `desktop-feature-implementation.mdc` Implementing new Electron desktop features
- `desktop-controller-tests.mdc` Desktop controller unit testing guide
- `desktop-local-tools-implement.mdc` Workflow to add new desktop local tools
- `desktop-menu-configuration.mdc` Desktop menu configuration guide
- `desktop-window-management.mdc` Desktop window management guide
### Debugging
- `debug-usage.mdc` Using the debug package and namespace conventions
### Testing
- `testing-guide/testing-guide.mdc` Comprehensive testing guide for Vitest
- `testing-guide/electron-ipc-test.mdc` Electron IPC interface testing strategy
- `testing-guide/db-model-test.mdc` Database Model testing guide
-5354
View File
File diff suppressed because it is too large Load Diff
-59
View File
@@ -1,59 +0,0 @@
# CLAUDE.md
This document serves as a shared guideline for all team members when using Claude Code in this repository.
## Tech Stack
read @.cursor/rules/project-introduce.mdc
## Directory Structure
read @.cursor/rules/project-structure.mdc
## Development
### Git Workflow
- use rebase for git pull
- git commit message should prefix with gitmoji
- git branch name format example: tj/feat/feature-name
- use .github/PULL_REQUEST_TEMPLATE.md to generate pull request description
### Package Management
This repository adopts a monorepo structure.
- Use `pnpm` as the primary package manager for dependency management
- Use `bun` to run npm scripts
- Use `bunx` to run executable npm packages
### TypeScript Code Style Guide
see @.cursor/rules/typescript.mdc
### Testing
- **Required Rule**: read `@.cursor/rules/testing-guide/testing-guide.mdc` before writing tests
- **Command**:
- web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'`
- packages(eg: database): `cd packages/database && bunx vitest run --silent='passed-only' '[file-path-pattern]'`
**Important**:
- wrap the file path in single quotes to avoid shell expansion
- Never run `bun run test` etc to run tests, this will run all tests and cost about 10mins
- If trying to fix the same test twice, but still failed, stop and ask for help.
### Typecheck
- use `bun run type-check` to check type errors.
### i18n
- **Keys**: Add to `src/locales/default/namespace.ts`
- **Dev**: Translate `locales/zh-CN/namespace.json` and `locales/en-US/namespace.json` locales file only for dev preview
- DON'T run `pnpm i18n`, let CI auto handle it
## Rules Index
Some useful project rules are listed in @.cursor/rules/rules-index.mdc
+5 -20
View File
@@ -1,5 +1,5 @@
## Set global build ENV
ARG NODEJS_VERSION="24"
ARG NODEJS_VERSION="22"
## Base image for all building stages
FROM node:${NODEJS_VERSION}-slim AS base
@@ -66,7 +66,7 @@ ENV NEXT_PUBLIC_ANALYTICS_UMAMI="${NEXT_PUBLIC_ANALYTICS_UMAMI}" \
NEXT_PUBLIC_UMAMI_WEBSITE_ID="${NEXT_PUBLIC_UMAMI_WEBSITE_ID}"
# Node
ENV NODE_OPTIONS="--max-old-space-size=6144"
ENV NODE_OPTIONS="--max-old-space-size=8192"
WORKDIR /app
@@ -126,7 +126,7 @@ ENV NODE_ENV="production" \
NODE_OPTIONS="--dns-result-order=ipv4first --use-openssl-ca" \
NODE_EXTRA_CA_CERTS="" \
NODE_TLS_REJECT_UNAUTHORIZED="" \
SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"
SSL_CERT_DIR="/etc/ssl/certs/ca-certificates.crt"
# Make the middleware rewrite through local as default
# refs: https://github.com/lobehub/lobe-chat/issues/5876
@@ -142,8 +142,7 @@ ENV ACCESS_CODE="" \
DEFAULT_AGENT_CONFIG="" \
SYSTEM_AGENT="" \
FEATURE_FLAGS="" \
PROXY_URL="" \
ENABLE_AUTH_PROTECTION=""
PROXY_URL=""
# Model Variables
ENV \
@@ -151,8 +150,6 @@ ENV \
AI21_API_KEY="" AI21_MODEL_LIST="" \
# Ai360
AI360_API_KEY="" AI360_MODEL_LIST="" \
# AiHubMix
AIHUBMIX_API_KEY="" AIHUBMIX_MODEL_LIST="" \
# Anthropic
ANTHROPIC_API_KEY="" ANTHROPIC_MODEL_LIST="" ANTHROPIC_PROXY_URL="" \
# Amazon Bedrock
@@ -195,10 +192,6 @@ ENV \
MODELSCOPE_API_KEY="" MODELSCOPE_MODEL_LIST="" MODELSCOPE_PROXY_URL="" \
# Moonshot
MOONSHOT_API_KEY="" MOONSHOT_MODEL_LIST="" MOONSHOT_PROXY_URL="" \
# Nebius
NEBIUS_API_KEY="" NEBIUS_MODEL_LIST="" NEBIUS_PROXY_URL="" \
# NewAPI
NEWAPI_API_KEY="" NEWAPI_PROXY_URL="" \
# Novita
NOVITA_API_KEY="" NOVITA_MODEL_LIST="" \
# Nvidia NIM
@@ -252,15 +245,7 @@ ENV \
# Tencent Cloud
TENCENT_CLOUD_API_KEY="" TENCENT_CLOUD_MODEL_LIST="" \
# Infini-AI
INFINIAI_API_KEY="" INFINIAI_MODEL_LIST="" \
# 302.AI
AI302_API_KEY="" AI302_MODEL_LIST="" \
# FAL
ENABLED_FAL="" FAL_API_KEY="" FAL_MODEL_LIST="" \
# BFL
BFL_API_KEY="" BFL_MODEL_LIST="" \
# Vercel AI Gateway
VERCELAIGATEWAY_API_KEY="" VERCELAIGATEWAY_MODEL_LIST=""
INFINIAI_API_KEY="" INFINIAI_MODEL_LIST=""
USER nextjs
+7 -35
View File
@@ -1,5 +1,5 @@
## Set global build ENV
ARG NODEJS_VERSION="24"
ARG NODEJS_VERSION="22"
## Base image for all building stages
FROM node:${NODEJS_VERSION}-slim AS base
@@ -39,8 +39,6 @@ ARG USE_CN_MIRROR
ARG NEXT_PUBLIC_BASE_PATH
ARG NEXT_PUBLIC_SERVICE_MODE
ARG NEXT_PUBLIC_ENABLE_NEXT_AUTH
ARG NEXT_PUBLIC_ENABLE_CLERK_AUTH
ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
ARG NEXT_PUBLIC_SENTRY_DSN
ARG NEXT_PUBLIC_ANALYTICS_POSTHOG
ARG NEXT_PUBLIC_POSTHOG_HOST
@@ -55,9 +53,6 @@ ENV NEXT_PUBLIC_BASE_PATH="${NEXT_PUBLIC_BASE_PATH}" \
ENV NEXT_PUBLIC_SERVICE_MODE="${NEXT_PUBLIC_SERVICE_MODE:-server}" \
NEXT_PUBLIC_ENABLE_NEXT_AUTH="${NEXT_PUBLIC_ENABLE_NEXT_AUTH:-1}" \
NEXT_PUBLIC_ENABLE_CLERK_AUTH="${NEXT_PUBLIC_ENABLE_CLERK_AUTH:-0}" \
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}" \
CLERK_WEBHOOK_SECRET="whsec_xxx" \
APP_URL="http://app.com" \
DATABASE_DRIVER="node" \
DATABASE_URL="postgres://postgres:password@localhost:5432/postgres" \
@@ -79,7 +74,7 @@ ENV NEXT_PUBLIC_ANALYTICS_UMAMI="${NEXT_PUBLIC_ANALYTICS_UMAMI}" \
NEXT_PUBLIC_UMAMI_WEBSITE_ID="${NEXT_PUBLIC_UMAMI_WEBSITE_ID}"
# Node
ENV NODE_OPTIONS="--max-old-space-size=6144"
ENV NODE_OPTIONS="--max-old-space-size=8192"
WORKDIR /app
@@ -125,7 +120,7 @@ COPY --from=base /distroless/ /
COPY --from=builder /app/.next/standalone /app/
# Copy database migrations
COPY --from=builder /app/packages/database/migrations /app/migrations
COPY --from=builder /app/src/database/migrations /app/migrations
COPY --from=builder /app/scripts/migrateServerDB/docker.cjs /app/docker.cjs
COPY --from=builder /app/scripts/migrateServerDB/errorHint.js /app/errorHint.js
@@ -154,7 +149,7 @@ ENV NODE_ENV="production" \
NODE_OPTIONS="--dns-result-order=ipv4first --use-openssl-ca" \
NODE_EXTRA_CA_CERTS="" \
NODE_TLS_REJECT_UNAUTHORIZED="" \
SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"
SSL_CERT_DIR="/etc/ssl/certs/ca-certificates.crt"
# Make the middleware rewrite through local as default
# refs: https://github.com/lobehub/lobe-chat/issues/5876
@@ -171,8 +166,7 @@ ENV ACCESS_CODE="" \
DEFAULT_AGENT_CONFIG="" \
SYSTEM_AGENT="" \
FEATURE_FLAGS="" \
PROXY_URL="" \
ENABLE_AUTH_PROTECTION=""
PROXY_URL=""
# Database
ENV KEY_VAULTS_SECRET="" \
@@ -184,19 +178,13 @@ ENV NEXT_AUTH_SECRET="" \
NEXT_AUTH_SSO_PROVIDERS="" \
NEXTAUTH_URL=""
# Clerk
ENV CLERK_SECRET_KEY="" \
CLERK_WEBHOOK_SECRET=""
# S3
ENV NEXT_PUBLIC_S3_DOMAIN="" \
S3_PUBLIC_DOMAIN="" \
S3_ACCESS_KEY_ID="" \
S3_BUCKET="" \
S3_ENDPOINT="" \
S3_SECRET_ACCESS_KEY="" \
S3_ENABLE_PATH_STYLE="" \
S3_SET_ACL=""
S3_SECRET_ACCESS_KEY=""
# Model Variables
ENV \
@@ -204,8 +192,6 @@ ENV \
AI21_API_KEY="" AI21_MODEL_LIST="" \
# Ai360
AI360_API_KEY="" AI360_MODEL_LIST="" \
# AiHubMix
AIHUBMIX_API_KEY="" AIHUBMIX_MODEL_LIST="" \
# Anthropic
ANTHROPIC_API_KEY="" ANTHROPIC_MODEL_LIST="" ANTHROPIC_PROXY_URL="" \
# Amazon Bedrock
@@ -248,10 +234,6 @@ ENV \
MODELSCOPE_API_KEY="" MODELSCOPE_MODEL_LIST="" MODELSCOPE_PROXY_URL="" \
# Moonshot
MOONSHOT_API_KEY="" MOONSHOT_MODEL_LIST="" MOONSHOT_PROXY_URL="" \
# Nebius
NEBIUS_API_KEY="" NEBIUS_MODEL_LIST="" NEBIUS_PROXY_URL="" \
# NewAPI
NEWAPI_API_KEY="" NEWAPI_PROXY_URL="" \
# Novita
NOVITA_API_KEY="" NOVITA_MODEL_LIST="" \
# Nvidia NIM
@@ -305,17 +287,7 @@ ENV \
# Tencent Cloud
TENCENT_CLOUD_API_KEY="" TENCENT_CLOUD_MODEL_LIST="" \
# Infini-AI
INFINIAI_API_KEY="" INFINIAI_MODEL_LIST="" \
# 302.AI
AI302_API_KEY="" AI302_MODEL_LIST="" \
# FAL
ENABLED_FAL="" FAL_API_KEY="" FAL_MODEL_LIST="" \
# BFL
BFL_API_KEY="" BFL_MODEL_LIST="" \
# Vercel AI Gateway
VERCELAIGATEWAY_API_KEY="" VERCELAIGATEWAY_MODEL_LIST="" \
# Cerebras
CEREBRAS_API_KEY="" CEREBRAS_MODEL_LIST=""
INFINIAI_API_KEY="" INFINIAI_MODEL_LIST=""
USER nextjs
+5 -20
View File
@@ -1,5 +1,5 @@
## Set global build ENV
ARG NODEJS_VERSION="24"
ARG NODEJS_VERSION="22"
## Base image for all building stages
FROM node:${NODEJS_VERSION}-slim AS base
@@ -68,7 +68,7 @@ ENV NEXT_PUBLIC_ANALYTICS_UMAMI="${NEXT_PUBLIC_ANALYTICS_UMAMI}" \
NEXT_PUBLIC_UMAMI_WEBSITE_ID="${NEXT_PUBLIC_UMAMI_WEBSITE_ID}"
# Node
ENV NODE_OPTIONS="--max-old-space-size=6144"
ENV NODE_OPTIONS="--max-old-space-size=8192"
WORKDIR /app
@@ -128,7 +128,7 @@ ENV NODE_ENV="production" \
NODE_OPTIONS="--dns-result-order=ipv4first --use-openssl-ca" \
NODE_EXTRA_CA_CERTS="" \
NODE_TLS_REJECT_UNAUTHORIZED="" \
SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"
SSL_CERT_DIR="/etc/ssl/certs/ca-certificates.crt"
# Make the middleware rewrite through local as default
# refs: https://github.com/lobehub/lobe-chat/issues/5876
@@ -144,8 +144,7 @@ ENV ACCESS_CODE="" \
DEFAULT_AGENT_CONFIG="" \
SYSTEM_AGENT="" \
FEATURE_FLAGS="" \
PROXY_URL="" \
ENABLE_AUTH_PROTECTION=""
PROXY_URL=""
# Model Variables
ENV \
@@ -153,8 +152,6 @@ ENV \
AI21_API_KEY="" AI21_MODEL_LIST="" \
# Ai360
AI360_API_KEY="" AI360_MODEL_LIST="" \
# AiHubMix
AIHUBMIX_API_KEY="" AIHUBMIX_MODEL_LIST="" \
# Anthropic
ANTHROPIC_API_KEY="" ANTHROPIC_MODEL_LIST="" ANTHROPIC_PROXY_URL="" \
# Amazon Bedrock
@@ -197,10 +194,6 @@ ENV \
MODELSCOPE_API_KEY="" MODELSCOPE_MODEL_LIST="" MODELSCOPE_PROXY_URL="" \
# Moonshot
MOONSHOT_API_KEY="" MOONSHOT_MODEL_LIST="" MOONSHOT_PROXY_URL="" \
# Nebius
NEBIUS_API_KEY="" NEBIUS_MODEL_LIST="" NEBIUS_PROXY_URL="" \
# NewAPI
NEWAPI_API_KEY="" NEWAPI_PROXY_URL="" \
# Novita
NOVITA_API_KEY="" NOVITA_MODEL_LIST="" \
# Nvidia NIM
@@ -250,15 +243,7 @@ ENV \
# Tencent Cloud
TENCENT_CLOUD_API_KEY="" TENCENT_CLOUD_MODEL_LIST="" \
# Infini-AI
INFINIAI_API_KEY="" INFINIAI_MODEL_LIST="" \
# 302.AI
AI302_API_KEY="" AI302_MODEL_LIST="" \
# FAL
ENABLED_FAL="" FAL_API_KEY="" FAL_MODEL_LIST="" \
# BFL
BFL_API_KEY="" BFL_MODEL_LIST="" \
# Vercel AI Gateway
VERCELAIGATEWAY_API_KEY="" VERCELAIGATEWAY_MODEL_LIST=""
INFINIAI_API_KEY="" INFINIAI_MODEL_LIST=""
USER nextjs
+16 -2
View File
@@ -1,10 +1,10 @@
LobeHub Community License
Apache License Version 2.0
Copyright (c) 2024/06/17 - current LobeHub LLC. All rights reserved.
----------
From 1.0, LobeChat is licensed under the LobeHub Community License, based on Apache License 2.0 with the following additional conditions:
From 1.0, LobeChat is licensed under the Apache License 2.0, with the following additional conditions:
1. The commercial usage of LobeChat:
@@ -22,3 +22,17 @@ Please contact hello@lobehub.com by email to inquire about licensing matters.
b. Your contributed code may be used for commercial purposes, including but not limited to its cloud edition.
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
----------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+12 -13
View File
@@ -150,7 +150,7 @@ From productivity tools to development environments, discover new ways to extend
**Peak Performance, Zero Distractions**
Get the full LobeChat experience without browser limitations—comprehensive, focused, and always ready to go. Our desktop application provides a dedicated environment for your AI interactions, ensuring optimal performance and minimal distractions.
Get the full LobeChat experience without browser limitations—lightweight, focused, and always ready to go. Our desktop application provides a dedicated environment for your AI interactions, ensuring optimal performance and minimal distractions.
Experience faster response times, better resource management, and a more stable connection to your AI assistant. The desktop app is designed for users who demand the best performance from their AI tools.
@@ -245,17 +245,15 @@ We have implemented support for the following model service providers:
- **[Bedrock](https://lobechat.com/discover/provider/bedrock)**: Bedrock is a service provided by Amazon AWS, focusing on delivering advanced AI language and visual models for enterprises. Its model family includes Anthropic's Claude series, Meta's Llama 3.1 series, and more, offering a range of options from lightweight to high-performance, supporting tasks such as text generation, conversation, and image processing for businesses of varying scales and needs.
- **[Google](https://lobechat.com/discover/provider/google)**: Google's Gemini series represents its most advanced, versatile AI models, developed by Google DeepMind, designed for multimodal capabilities, supporting seamless understanding and processing of text, code, images, audio, and video. Suitable for various environments from data centers to mobile devices, it significantly enhances the efficiency and applicability of AI models.
- **[DeepSeek](https://lobechat.com/discover/provider/deepseek)**: DeepSeek is a company focused on AI technology research and application, with its latest model DeepSeek-V2.5 integrating general dialogue and code processing capabilities, achieving significant improvements in human preference alignment, writing tasks, and instruction following.
- **[Moonshot](https://lobechat.com/discover/provider/moonshot)**: Moonshot is an open-source platform launched by Beijing Dark Side Technology Co., Ltd., providing various natural language processing models with a wide range of applications, including but not limited to content creation, academic research, intelligent recommendations, and medical diagnosis, supporting long text processing and complex generation tasks.
- **[OpenRouter](https://lobechat.com/discover/provider/openrouter)**: OpenRouter is a service platform providing access to various cutting-edge large model interfaces, supporting OpenAI, Anthropic, LLaMA, and more, suitable for diverse development and application needs. Users can flexibly choose the optimal model and pricing based on their requirements, enhancing the AI experience.
- **[HuggingFace](https://lobechat.com/discover/provider/huggingface)**: The HuggingFace Inference API provides a fast and free way for you to explore thousands of models for various tasks. Whether you are prototyping for a new application or experimenting with the capabilities of machine learning, this API gives you instant access to high-performance models across multiple domains.
- **[OpenRouter](https://lobechat.com/discover/provider/openrouter)**: OpenRouter is a service platform providing access to various cutting-edge large model interfaces, supporting OpenAI, Anthropic, LLaMA, and more, suitable for diverse development and application needs. Users can flexibly choose the optimal model and pricing based on their requirements, enhancing the AI experience.
- **[Cloudflare Workers AI](https://lobechat.com/discover/provider/cloudflare)**: Run serverless GPU-powered machine learning models on Cloudflare's global network.
<details><summary><kbd>See more providers (+32)</kbd></summary>
- **[GitHub](https://lobechat.com/discover/provider/github)**: With GitHub Models, developers can become AI engineers and leverage the industry's leading AI models.
<details><summary><kbd>See more providers (+31)</kbd></summary>
- **[Novita](https://lobechat.com/discover/provider/novita)**: Novita AI is a platform providing a variety of large language models and AI image generation API services, flexible, reliable, and cost-effective. It supports the latest open-source models like Llama3 and Mistral, offering a comprehensive, user-friendly, and auto-scaling API solution for generative AI application development, suitable for the rapid growth of AI startups.
- **[PPIO](https://lobechat.com/discover/provider/ppio)**: PPIO supports stable and cost-efficient open-source LLM APIs, such as DeepSeek, Llama, Qwen etc.
- **[302.AI](https://lobechat.com/discover/provider/ai302)**: 302.AI is an on-demand AI application platform offering the most comprehensive AI APIs and online AI applications available on the market.
- **[Together AI](https://lobechat.com/discover/provider/togetherai)**: Together AI is dedicated to achieving leading performance through innovative AI models, offering extensive customization capabilities, including rapid scaling support and intuitive deployment processes to meet various enterprise needs.
- **[Fireworks AI](https://lobechat.com/discover/provider/fireworksai)**: Fireworks AI is a leading provider of advanced language model services, focusing on functional calling and multimodal processing. Its latest model, Firefunction V2, is based on Llama-3, optimized for function calling, conversation, and instruction following. The visual language model FireLLaVA-13B supports mixed input of images and text. Other notable models include the Llama series and Mixtral series, providing efficient multilingual instruction following and generation support.
- **[Groq](https://lobechat.com/discover/provider/groq)**: Groq's LPU inference engine has excelled in the latest independent large language model (LLM) benchmarks, redefining the standards for AI solutions with its remarkable speed and efficiency. Groq represents instant inference speed, demonstrating strong performance in cloud-based deployments.
@@ -274,6 +272,7 @@ We have implemented support for the following model service providers:
- **[Spark](https://lobechat.com/discover/provider/spark)**: iFlytek's Spark model provides powerful AI capabilities across multiple domains and languages, utilizing advanced natural language processing technology to build innovative applications suitable for smart hardware, smart healthcare, smart finance, and other vertical scenarios.
- **[SenseNova](https://lobechat.com/discover/provider/sensenova)**: SenseNova, backed by SenseTime's robust infrastructure, offers efficient and user-friendly full-stack large model services.
- **[Stepfun](https://lobechat.com/discover/provider/stepfun)**: StepFun's large model possesses industry-leading multimodal and complex reasoning capabilities, supporting ultra-long text understanding and powerful autonomous scheduling search engine functions.
- **[Moonshot](https://lobechat.com/discover/provider/moonshot)**: Moonshot is an open-source platform launched by Beijing Dark Side Technology Co., Ltd., providing various natural language processing models with a wide range of applications, including but not limited to content creation, academic research, intelligent recommendations, and medical diagnosis, supporting long text processing and complex generation tasks.
- **[Baichuan](https://lobechat.com/discover/provider/baichuan)**: Baichuan Intelligence is a company focused on the research and development of large AI models, with its models excelling in domestic knowledge encyclopedias, long text processing, and generative creation tasks in Chinese, surpassing mainstream foreign models. Baichuan Intelligence also possesses industry-leading multimodal capabilities, performing excellently in multiple authoritative evaluations. Its models include Baichuan 4, Baichuan 3 Turbo, and Baichuan 3 Turbo 128k, each optimized for different application scenarios, providing cost-effective solutions.
- **[Minimax](https://lobechat.com/discover/provider/minimax)**: MiniMax is a general artificial intelligence technology company established in 2021, dedicated to co-creating intelligence with users. MiniMax has independently developed general large models of different modalities, including trillion-parameter MoE text models, voice models, and image models, and has launched applications such as Conch AI.
- **[InternLM](https://lobechat.com/discover/provider/internlm)**: An open-source organization dedicated to the research and development of large model toolchains. It provides an efficient and user-friendly open-source platform for all AI developers, making cutting-edge large models and algorithm technologies easily accessible.
@@ -287,7 +286,7 @@ We have implemented support for the following model service providers:
</details>
> 📊 Total providers: [<kbd>**42**</kbd>](https://lobechat.com/discover/providers)
> 📊 Total providers: [<kbd>**41**</kbd>](https://lobechat.com/discover/providers)
<!-- PROVIDER LIST -->
@@ -384,12 +383,12 @@ In addition, these plugins are not limited to news aggregation, but can also ext
| Recent Submits | Description |
| ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-09-27**</sup> | Analyze stocks and get comprehensive real-time investment data and analytics.<br/>`stock` |
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-07-21**</sup> | Analyze stocks and get comprehensive real-time investment data and analytics.<br/>`stock` |
| [Speak](https://lobechat.com/discover/plugin/speak)<br/><sup>By **speak** on **2025-07-18**</sup> | Learn how to say anything in another language with Speak, your AI-powered language tutor.<br/>`education` `language` |
| [Web](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | Smart web search that reads and analyzes pages to deliver comprehensive answers from Google results.<br/>`web` `search` |
| [Bing_websearch](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | Search for information from the internet base BingApi<br/>`bingsearch` |
| [Google CSE](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | Searches Google through their official CSE API.<br/>`web` `search` |
> 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
> 📊 Total plugins: [<kbd>**43**</kbd>](https://lobechat.com/discover/plugins)
<!-- PLUGIN LIST -->
@@ -481,7 +480,7 @@ We deeply understand the importance of providing a seamless experience for users
Therefore, we have adopted Progressive Web Application ([PWA](https://support.google.com/chrome/answer/9658361)) technology,
a modern web technology that elevates web applications to an experience close to that of native apps.
Through PWA, LobeChat can offer a highly optimized user experience on both desktop and mobile devices while maintaining high-performance characteristics.
Through PWA, LobeChat can offer a highly optimized user experience on both desktop and mobile devices while maintaining its lightweight and high-performance characteristics.
Visually and in terms of feel, we have also meticulously designed the interface to ensure it is indistinguishable from native apps,
providing smooth animations, responsive layouts, and adapting to different device screen resolutions.
@@ -819,7 +818,7 @@ Every bit counts and your one-time donation sparkles in our galaxy of support! Y
</details>
Copyright © 2025 [LobeHub][profile-link]. <br />
This project is [LobeHub Community License](./LICENSE) licensed.
This project is [Apache 2.0](./LICENSE) licensed.
<!-- LINK GROUP -->
+10 -11
View File
@@ -245,17 +245,15 @@ LobeChat 支持文件上传与知识库功能,你可以上传文件、图片
- **[Bedrock](https://lobechat.com/discover/provider/bedrock)**: Bedrock 是亚马逊 AWS 提供的一项服务,专注于为企业提供先进的 AI 语言模型和视觉模型。其模型家族包括 Anthropic 的 Claude 系列、Meta 的 Llama 3.1 系列等,涵盖从轻量级到高性能的多种选择,支持文本生成、对话、图像处理等多种任务,适用于不同规模和需求的企业应用。
- **[Google](https://lobechat.com/discover/provider/google)**: Google 的 Gemini 系列是其最先进、通用的 AI 模型,由 Google DeepMind 打造,专为多模态设计,支持文本、代码、图像、音频和视频的无缝理解与处理。适用于从数据中心到移动设备的多种环境,极大提升了 AI 模型的效率与应用广泛性。
- **[DeepSeek](https://lobechat.com/discover/provider/deepseek)**: DeepSeek 是一家专注于人工智能技术研究和应用的公司,其最新模型 DeepSeek-V3 多项评测成绩超越 Qwen2.5-72B 和 Llama-3.1-405B 等开源模型,性能对齐领军闭源模型 GPT-4o 与 Claude-3.5-Sonnet。
- **[Moonshot](https://lobechat.com/discover/provider/moonshot)**: Moonshot 是由北京月之暗面科技有限公司推出的开源平台,提供多种自然语言处理模型,应用领域广泛,包括但不限于内容创作、学术研究、智能推荐、医疗诊断等,支持长文本处理和复杂生成任务。
- **[OpenRouter](https://lobechat.com/discover/provider/openrouter)**: OpenRouter 是一个提供多种前沿大模型接口的服务平台,支持 OpenAI、Anthropic、LLaMA 及更多,适合多样化的开发和应用需求。用户可根据自身需求灵活选择最优的模型和价格,助力 AI 体验的提升。
- **[HuggingFace](https://lobechat.com/discover/provider/huggingface)**: HuggingFace Inference API 提供了一种快速且免费的方式,让您可以探索成千上万种模型,适用于各种任务。无论您是在为新应用程序进行原型设计,还是在尝试机器学习的功能,这个 API 都能让您即时访问多个领域的高性能模型。
- **[OpenRouter](https://lobechat.com/discover/provider/openrouter)**: OpenRouter 是一个提供多种前沿大模型接口的服务平台,支持 OpenAI、Anthropic、LLaMA 及更多,适合多样化的开发和应用需求。用户可根据自身需求灵活选择最优的模型和价格,助力 AI 体验的提升。
- **[Cloudflare Workers AI](https://lobechat.com/discover/provider/cloudflare)**: 在 Cloudflare 的全球网络上运行由无服务器 GPU 驱动的机器学习模型。
<details><summary><kbd>See more providers (+32)</kbd></summary>
- **[GitHub](https://lobechat.com/discover/provider/github)**: 通过 GitHub 模型,开发人员可以成为 AI 工程师,并使用行业领先的 AI 模型进行构建。
<details><summary><kbd>See more providers (+31)</kbd></summary>
- **[Novita](https://lobechat.com/discover/provider/novita)**: Novita AI 是一个提供多种大语言模型与 AI 图像生成的 API 服务的平台,灵活、可靠且具有成本效益。它支持 Llama3、Mistral 等最新的开源模型,并为生成式 AI 应用开发提供了全面、用户友好且自动扩展的 API 解决方案,适合 AI 初创公司的快速发展。
- **[PPIO](https://lobechat.com/discover/provider/ppio)**: PPIO 派欧云提供稳定、高性价比的开源模型 API 服务,支持 DeepSeek 全系列、Llama、Qwen 等行业领先大模型。
- **[302.AI](https://lobechat.com/discover/provider/ai302)**: 302.AI 是一个按需付费的 AI 应用平台,提供市面上最全的 AI API 和 AI 在线应用
- **[Together AI](https://lobechat.com/discover/provider/togetherai)**: Together AI 致力于通过创新的 AI 模型实现领先的性能,提供广泛的自定义能力,包括快速扩展支持和直观的部署流程,满足企业的各种需求。
- **[Fireworks AI](https://lobechat.com/discover/provider/fireworksai)**: Fireworks AI 是一家领先的高级语言模型服务商,专注于功能调用和多模态处理。其最新模型 Firefunction V2 基于 Llama-3,优化用于函数调用、对话及指令跟随。视觉语言模型 FireLLaVA-13B 支持图像和文本混合输入。其他 notable 模型包括 Llama 系列和 Mixtral 系列,提供高效的多语言指令跟随与生成支持。
- **[Groq](https://lobechat.com/discover/provider/groq)**: Groq 的 LPU 推理引擎在最新的独立大语言模型(LLM)基准测试中表现卓越,以其惊人的速度和效率重新定义了 AI 解决方案的标准。Groq 是一种即时推理速度的代表,在基于云的部署中展现了良好的性能。
@@ -274,6 +272,7 @@ LobeChat 支持文件上传与知识库功能,你可以上传文件、图片
- **[Spark](https://lobechat.com/discover/provider/spark)**: 科大讯飞星火大模型提供多领域、多语言的强大 AI 能力,利用先进的自然语言处理技术,构建适用于智能硬件、智慧医疗、智慧金融等多种垂直场景的创新应用。
- **[SenseNova](https://lobechat.com/discover/provider/sensenova)**: 商汤日日新,依托商汤大装置的强大的基础支撑,提供高效易用的全栈大模型服务。
- **[Stepfun](https://lobechat.com/discover/provider/stepfun)**: 阶级星辰大模型具备行业领先的多模态及复杂推理能力,支持超长文本理解和强大的自主调度搜索引擎功能。
- **[Moonshot](https://lobechat.com/discover/provider/moonshot)**: Moonshot 是由北京月之暗面科技有限公司推出的开源平台,提供多种自然语言处理模型,应用领域广泛,包括但不限于内容创作、学术研究、智能推荐、医疗诊断等,支持长文本处理和复杂生成任务。
- **[Baichuan](https://lobechat.com/discover/provider/baichuan)**: 百川智能是一家专注于人工智能大模型研发的公司,其模型在国内知识百科、长文本处理和生成创作等中文任务上表现卓越,超越了国外主流模型。百川智能还具备行业领先的多模态能力,在多项权威评测中表现优异。其模型包括 Baichuan 4、Baichuan 3 Turbo 和 Baichuan 3 Turbo 128k 等,分别针对不同应用场景进行优化,提供高性价比的解决方案。
- **[Minimax](https://lobechat.com/discover/provider/minimax)**: MiniMax 是 2021 年成立的通用人工智能科技公司,致力于与用户共创智能。MiniMax 自主研发了不同模态的通用大模型,其中包括万亿参数的 MoE 文本大模型、语音大模型以及图像大模型。并推出了海螺 AI 等应用。
- **[InternLM](https://lobechat.com/discover/provider/internlm)**: 致力于大模型研究与开发工具链的开源组织。为所有 AI 开发者提供高效、易用的开源平台,让最前沿的大模型与算法技术触手可及
@@ -287,7 +286,7 @@ LobeChat 支持文件上传与知识库功能,你可以上传文件、图片
</details>
> 📊 Total providers: [<kbd>**42**</kbd>](https://lobechat.com/discover/providers)
> 📊 Total providers: [<kbd>**41**</kbd>](https://lobechat.com/discover/providers)
<!-- PROVIDER LIST -->
@@ -377,12 +376,12 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
| 最近新增 | 描述 |
| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-09-27**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-07-21**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
| [Speak](https://lobechat.com/discover/plugin/speak)<br/><sup>By **speak** on **2025-07-18**</sup> | 使用 Speak,您的 AI 语言导师,学习如何用另一种语言说任何事情。<br/>`教育` `语言` |
| [网页](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | 智能网页搜索,读取和分析页面,以提供来自 Google 结果的全面答案。<br/>`网页` `搜索` |
| [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
| [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎 API 搜索谷歌。<br/>`网络` `搜索` |
> 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
> 📊 Total plugins: [<kbd>**43**</kbd>](https://lobechat.com/discover/plugins)
<!-- PLUGIN LIST -->
@@ -840,7 +839,7 @@ $ pnpm run dev
</details>
Copyright © 2025 [LobeHub][profile-link]. <br />
This project is [LobeHub Community License](./LICENSE) licensed.
This project is [Apache 2.0](./LICENSE) licensed.
<!-- LINK GROUP -->
+1 -2
View File
@@ -23,9 +23,8 @@ module.exports = defineConfig({
'vi-VN',
'fa-IR',
],
saveImmediately: true,
temperature: 0,
modelName: 'gpt-4.1-mini',
modelName: 'gpt-4o-mini',
experimental: {
jsonMode: true,
},
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -84
View File
@@ -1,83 +1,21 @@
const dotenv = require('dotenv');
const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
dotenv.config();
const packageJSON = require('./package.json');
const channel = process.env.UPDATE_CHANNEL;
const arch = os.arch();
const hasAppleCertificate = Boolean(process.env.CSC_LINK);
console.log(`🚄 Build Version ${packageJSON.version}, Channel: ${channel}`);
console.log(`🏗️ Building for architecture: ${arch}`);
const isNightly = channel === 'nightly';
const isBeta = packageJSON.name.includes('beta');
// https://www.electron.build/code-signing-mac#how-to-disable-code-signing-during-the-build-process-on-macos
if (!hasAppleCertificate) {
// Disable auto discovery to keep electron-builder from searching unavailable signing identities
process.env.CSC_IDENTITY_AUTO_DISCOVERY = 'false';
console.log('⚠️ Apple certificate link not found, macOS artifacts will be unsigned.');
}
// 根据版本类型确定协议 scheme
const getProtocolScheme = () => {
if (isNightly) return 'lobehub-nightly';
if (isBeta) return 'lobehub-beta';
return 'lobehub';
};
const protocolScheme = getProtocolScheme();
// Determine icon file based on version type
const getIconFileName = () => {
if (isNightly) return 'Icon-nightly';
if (isBeta) return 'Icon-beta';
return 'Icon';
};
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration
*/
const config = {
/**
* AfterPack hook to copy pre-generated Liquid Glass Assets.car for macOS 26+
* @see https://github.com/electron-userland/electron-builder/issues/9254
* @see https://github.com/MultiboxLabs/flow-browser/pull/159
* @see https://github.com/electron/packager/pull/1806
*/
afterPack: async (context) => {
// Only process macOS builds
if (context.electronPlatformName !== 'darwin') {
return;
}
const iconFileName = getIconFileName();
const assetsCarSource = path.join(__dirname, 'build', `${iconFileName}.Assets.car`);
const resourcesPath = path.join(
context.appOutDir,
`${context.packager.appInfo.productFilename}.app`,
'Contents',
'Resources',
);
const assetsCarDest = path.join(resourcesPath, 'Assets.car');
try {
await fs.access(assetsCarSource);
await fs.copyFile(assetsCarSource, assetsCarDest);
console.log(`✅ Copied Liquid Glass icon: ${iconFileName}.Assets.car`);
} catch {
// Non-critical: Assets.car not found or copy failed
// App will use fallback .icns icon on all macOS versions
console.log(`⏭️ Skipping Assets.car (not found or copy failed)`);
}
},
appId: isNightly
? 'com.lobehub.lobehub-desktop-nightly'
: isBeta
@@ -116,19 +54,12 @@ const config = {
linux: {
category: 'Utility',
maintainer: 'electronjs.org',
target: ['AppImage', 'snap', 'deb', 'rpm', 'tar.gz'],
target: ['AppImage', 'snap', 'deb'],
},
mac: {
compression: 'maximum',
entitlementsInherit: 'build/entitlements.mac.plist',
extendInfo: {
CFBundleIconName: 'AppIcon',
CFBundleURLTypes: [
{
CFBundleURLName: 'LobeHub Protocol',
CFBundleURLSchemes: [protocolScheme],
},
],
NSCameraUsageDescription: "Application requests access to the device's camera.",
NSDocumentsFolderUsageDescription:
"Application requests access to the user's Documents folder.",
@@ -137,17 +68,15 @@ const config = {
NSMicrophoneUsageDescription: "Application requests access to the device's microphone.",
},
gatekeeperAssess: false,
hardenedRuntime: hasAppleCertificate,
notarize: hasAppleCertificate,
...(hasAppleCertificate ? {} : { identity: null }),
hardenedRuntime: true,
notarize: true,
target:
// 降低构建时间,nightly 只打 dmg
// 根据当前机器架构只构建对应架构的包
// 降低构建时间,nightly 只打 arm64
isNightly
? [{ arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'dmg' }]
? [{ arch: ['arm64'], target: 'dmg' }]
: [
{ arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'dmg' },
{ arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'zip' },
{ arch: ['x64', 'arm64'], target: 'dmg' },
{ arch: ['x64', 'arm64'], target: 'zip' },
],
},
npmRebuild: true,
@@ -162,12 +91,6 @@ const config = {
uninstallDisplayName: '${productName}',
uninstallerSidebar: './build/nsis-sidebar.bmp',
},
protocols: [
{
name: 'LobeHub Protocol',
schemes: [protocolScheme],
},
],
publish: [
{
owner: 'lobehub',
+4 -4
View File
@@ -20,10 +20,11 @@
"electron:dev": "electron-vite dev",
"electron:run-unpack": "electron .",
"format": "prettier --write ",
"i18n": "tsx scripts/i18nWorkflow/index.ts && lobe-i18n",
"i18n": "bun run scripts/i18nWorkflow/index.ts && lobe-i18n",
"postinstall": "electron-builder install-app-deps",
"install-isolated": "pnpm install",
"lint": "eslint --cache ",
"pg-server": "bun run scripts/pglite-server.ts",
"start": "electron-vite preview",
"test": "vitest --run",
"typecheck": "tsgo --noEmit -p tsconfig.json"
@@ -31,7 +32,6 @@
"dependencies": {
"electron-updater": "^6.6.2",
"electron-window-state": "^5.0.3",
"fetch-socks": "^1.3.2",
"get-port-please": "^3.1.2",
"pdfjs-dist": "4.10.38"
},
@@ -52,14 +52,14 @@
"@typescript/native-preview": "7.0.0-dev.20250711.1",
"consola": "^3.1.0",
"cookie": "^1.0.2",
"electron": "^38.0.0",
"electron": "~37.1.0",
"electron-builder": "^26.0.12",
"electron-is": "^3.0.0",
"electron-log": "^5.3.3",
"electron-store": "^8.2.0",
"electron-vite": "^3.0.0",
"execa": "^9.5.2",
"fix-path": "^5.0.0",
"fix-path": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"just-diff": "^6.0.2",
@@ -14,7 +14,6 @@
"edit": {
"copy": "نسخ",
"cut": "قص",
"delete": "حذف",
"paste": "لصق",
"redo": "إعادة",
"selectAll": "تحديد الكل",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Копиране",
"cut": "Изрязване",
"delete": "Изтрий",
"paste": "Поставяне",
"redo": "Повторно",
"selectAll": "Избери всичко",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Kopieren",
"cut": "Ausschneiden",
"delete": "Löschen",
"paste": "Einfügen",
"redo": "Wiederherstellen",
"selectAll": "Alles auswählen",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Copy",
"cut": "Cut",
"delete": "Delete",
"paste": "Paste",
"redo": "Redo",
"selectAll": "Select All",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Copiar",
"cut": "Cortar",
"delete": "Eliminar",
"paste": "Pegar",
"redo": "Rehacer",
"selectAll": "Seleccionar todo",
@@ -14,7 +14,6 @@
"edit": {
"copy": "کپی",
"cut": "برش",
"delete": "حذف",
"paste": "چسباندن",
"redo": "انجام مجدد",
"selectAll": "انتخاب همه",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Copier",
"cut": "Couper",
"delete": "Supprimer",
"paste": "Coller",
"redo": "Rétablir",
"selectAll": "Tout sélectionner",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Copia",
"cut": "Taglia",
"delete": "Elimina",
"paste": "Incolla",
"redo": "Ripeti",
"selectAll": "Seleziona tutto",
@@ -14,7 +14,6 @@
"edit": {
"copy": "コピー",
"cut": "切り取り",
"delete": "削除",
"paste": "貼り付け",
"redo": "やり直し",
"selectAll": "すべて選択",
@@ -14,7 +14,6 @@
"edit": {
"copy": "복사",
"cut": "잘라내기",
"delete": "삭제",
"paste": "붙여넣기",
"redo": "다시 실행",
"selectAll": "모두 선택",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Kopiëren",
"cut": "Knippen",
"delete": "Verwijderen",
"paste": "Plakken",
"redo": "Opnieuw doen",
"selectAll": "Alles selecteren",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Kopiuj",
"cut": "Wytnij",
"delete": "Usuń",
"paste": "Wklej",
"redo": "Ponów",
"selectAll": "Zaznacz wszystko",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Copiar",
"cut": "Cortar",
"delete": "Excluir",
"paste": "Colar",
"redo": "Refazer",
"selectAll": "Selecionar Tudo",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Копировать",
"cut": "Вырезать",
"delete": "Удалить",
"paste": "Вставить",
"redo": "Повторить",
"selectAll": "Выбрать все",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Kopyala",
"cut": "Kes",
"delete": "Sil",
"paste": "Yapıştır",
"redo": "Yinele",
"selectAll": "Tümünü Seç",
@@ -14,7 +14,6 @@
"edit": {
"copy": "Sao chép",
"cut": "Cắt",
"delete": "Xóa",
"paste": "Dán",
"redo": "Làm lại",
"selectAll": "Chọn tất cả",
@@ -14,7 +14,6 @@
"edit": {
"copy": "复制",
"cut": "剪切",
"delete": "删除",
"paste": "粘贴",
"redo": "重做",
"selectAll": "全选",
@@ -14,7 +14,6 @@
"edit": {
"copy": "複製",
"cut": "剪下",
"delete": "刪除",
"paste": "貼上",
"redo": "重做",
"selectAll": "全選",
-51
View File
@@ -46,55 +46,4 @@ export const appBrowsers = {
},
} satisfies Record<string, BrowserWindowOpts>;
// Window templates for multi-instance windows
export interface WindowTemplate {
allowMultipleInstances: boolean;
// Include common BrowserWindow options
autoHideMenuBar?: boolean;
baseIdentifier: string;
basePath: string;
devTools?: boolean;
height?: number;
keepAlive?: boolean;
minWidth?: number;
parentIdentifier?: string;
showOnInit?: boolean;
title?: string;
titleBarStyle?: 'hidden' | 'default' | 'hiddenInset' | 'customButtonsOnHover';
vibrancy?:
| 'appearance-based'
| 'content'
| 'fullscreen-ui'
| 'header'
| 'hud'
| 'menu'
| 'popover'
| 'selection'
| 'sheet'
| 'sidebar'
| 'titlebar'
| 'tooltip'
| 'under-page'
| 'under-window'
| 'window';
width?: number;
}
export const windowTemplates = {
chatSingle: {
allowMultipleInstances: true,
autoHideMenuBar: true,
baseIdentifier: 'chatSingle',
basePath: '/chat',
height: 600,
keepAlive: false, // Multi-instance windows don't need to stay alive
minWidth: 400,
parentIdentifier: 'chat',
titleBarStyle: 'hidden',
vibrancy: 'under-window',
width: 900,
},
} satisfies Record<string, WindowTemplate>;
export type AppBrowsersIdentifiers = keyof typeof appBrowsers;
export type WindowTemplateIdentifiers = keyof typeof windowTemplates;
@@ -1,7 +1,7 @@
import { InterceptRouteParams } from '@lobechat/electron-client-ipc';
import { extractSubPath, findMatchingRoute } from '~common/routes';
import { AppBrowsersIdentifiers, BrowsersIdentifiers, WindowTemplateIdentifiers } from '@/appBrowsers';
import { AppBrowsersIdentifiers, BrowsersIdentifiers } from '@/appBrowsers';
import { IpcClientEventSender } from '@/types/ipcClientEvent';
import { ControllerModule, ipcClientEvent, shortcut } from './index';
@@ -100,77 +100,6 @@ export default class BrowserWindowsCtr extends ControllerModule {
}
}
/**
* Create a new multi-instance window
*/
@ipcClientEvent('createMultiInstanceWindow')
async createMultiInstanceWindow(params: {
templateId: WindowTemplateIdentifiers;
path: string;
uniqueId?: string;
}) {
try {
console.log('[BrowserWindowsCtr] Creating multi-instance window:', params);
const result = this.app.browserManager.createMultiInstanceWindow(
params.templateId,
params.path,
params.uniqueId,
);
// Show the window
result.browser.show();
return {
success: true,
windowId: result.identifier,
};
} catch (error) {
console.error('[BrowserWindowsCtr] Failed to create multi-instance window:', error);
return {
error: error.message,
success: false,
};
}
}
/**
* Get all windows by template
*/
@ipcClientEvent('getWindowsByTemplate')
async getWindowsByTemplate(templateId: string) {
try {
const windowIds = this.app.browserManager.getWindowsByTemplate(templateId);
return {
success: true,
windowIds,
};
} catch (error) {
console.error('[BrowserWindowsCtr] Failed to get windows by template:', error);
return {
error: error.message,
success: false,
};
}
}
/**
* Close all windows by template
*/
@ipcClientEvent('closeWindowsByTemplate')
async closeWindowsByTemplate(templateId: string) {
try {
this.app.browserManager.closeWindowsByTemplate(templateId);
return { success: true };
} catch (error) {
console.error('[BrowserWindowsCtr] Failed to close windows by template:', error);
return {
error: error.message,
success: false,
};
}
}
/**
* Open target window and navigate to specified sub-path
*/

Some files were not shown because too many files have changed in this diff Show More