diff --git a/.cursor/rules/db-migrations.mdc b/.cursor/rules/db-migrations.mdc index b8d537bc72..2db3e42112 100644 --- a/.cursor/rules/db-migrations.mdc +++ b/.cursor/rules/db-migrations.mdc @@ -5,20 +5,26 @@ alwaysApply: false # Database Migrations Guide -## Step1: Generate migrations: +## Step1: Generate migrations ```bash bun run db:generate ``` -this step will generate or update the following files: +this step will generate following files: + +- packages/database/migrations/0046_meaningless_file_name.sql +- packages/database/migrations/0046_meaningless_file_name.sql + +and update the following files: -- packages/database/migrations/0046_xxx.sql - packages/database/migrations/meta/\_journal.json +- packages/database/src/core/migrations.json +- docs/development/database-schema.dbml ## Step2: optimize the migration sql fileName -the migration sql file name is randomly generated, we need to optimize the file name to make it more readable and meaningful. For example, `0046_xxx.sql` -> `0046_better_auth.sql` +the migration sql file name is randomly generated, we need to optimize the file name to make it more readable and meaningful. For example, `0046_meaningless_file_name.sql` -> `0046_user_add_avatar_column.sql` ## Step3: Defensive Programming - Use Idempotent Clauses diff --git a/.cursor/rules/drizzle-schema-style-guide.mdc b/.cursor/rules/drizzle-schema-style-guide.mdc index 086a92f8fb..545c935b37 100644 --- a/.cursor/rules/drizzle-schema-style-guide.mdc +++ b/.cursor/rules/drizzle-schema-style-guide.mdc @@ -1,8 +1,9 @@ --- -description: +description: globs: src/database/schemas/* alwaysApply: false --- + # Drizzle ORM Schema Style Guide for lobe-chat This document outlines the conventions and best practices for defining PostgreSQL Drizzle ORM schemas within the lobe-chat project. @@ -16,7 +17,8 @@ This document outlines the conventions and best practices for defining PostgreSQ ## Helper Functions -Commonly used column definitions, especially for timestamps, are centralized in [src/database/schemas/_helpers.ts](mdc:src/database/schemas/_helpers.ts): +Commonly used column definitions, especially for timestamps, are centralized in [src/database/schemas/\_helpers.ts](mdc:src/database/schemas/_helpers.ts): + - `timestamptz(name: string)`: Creates a timestamp column with timezone - `createdAt()`, `updatedAt()`, `accessedAt()`: Helper functions for standard timestamp columns - `timestamps`: An object `{ createdAt, updatedAt, accessedAt }` for easy inclusion in table definitions @@ -29,6 +31,7 @@ Commonly used column definitions, especially for timestamps, are centralized in ## Column Definitions ### Primary Keys (PKs) + - Typically `text('id')` (or `varchar('id')` for some OIDC tables) - Often use `.$defaultFn(() => idGenerator('table_name'))` for automatic ID generation with meaningful prefixes - **ID Prefix Purpose**: Makes it easy for users and developers to distinguish different entity types at a glance @@ -36,24 +39,29 @@ Commonly used column definitions, especially for timestamps, are centralized in - Composite PKs are defined using `primaryKey({ columns: [t.colA, t.colB] })` ### Foreign Keys (FKs) + - Defined using `.references(() => otherTable.id, { onDelete: 'cascade' | 'set null' | 'no action' })` - FK columns are usually named `related_table_singular_name_id` (e.g., `user_id` references `users.id`) - Most tables include a `user_id` column referencing `users.id` with `onDelete: 'cascade'` ### Timestamps -- Consistently use the `...timestamps` spread from [_helpers.ts](mdc:src/database/schemas/_helpers.ts) for `created_at`, `updated_at`, and `accessed_at` columns + +- Consistently use the `...timestamps` spread from [\_helpers.ts](mdc:src/database/schemas/_helpers.ts) for `created_at`, `updated_at`, and `accessed_at` columns ### Default Values + - `.$defaultFn(() => expression)` for dynamic defaults (e.g., `idGenerator()`, `randomSlug()`) - `.default(staticValue)` for static defaults (e.g., `boolean('enabled').default(true)`) ### Indexes + - Defined in the table's second argument: `pgTable('name', {...columns}, (t) => ({ indexName: indexType().on(...) }))` - Use `uniqueIndex()` for unique constraints and `index()` for non-unique indexes - Naming pattern: `table_name_column(s)_idx` or `table_name_column(s)_unique` - Many tables feature a `clientId: text('client_id')` column, often part of a composite unique index with `user_id` ### Data Types + - Common types: `text`, `varchar`, `jsonb`, `boolean`, `integer`, `uuid`, `pgTable` - For `jsonb` fields, specify the TypeScript type using `.$type()` for better type safety @@ -97,9 +105,7 @@ export const agents = pgTable( ...timestamps, }, // return array instead of object, the object style is deprecated - (t) => [ - uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId), - ], + (t) => [uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId)], ); export const insertAgentSchema = createInsertSchema(agents); @@ -110,6 +116,7 @@ export type AgentItem = typeof agents.$inferSelect; ## Common Patterns ### 1. userId + clientId Pattern (Legacy) + Some existing tables include both fields for different purposes: ```typescript @@ -129,6 +136,7 @@ clientIdUnique: uniqueIndex('agents_client_id_user_id_unique').on(t.clientId, t. - **Note**: This pattern is being phased out for new features to simplify the schema ### 2. Junction Tables (Many-to-Many Relationships) + Use composite primary keys for relationship tables: ```typescript @@ -136,21 +144,26 @@ Use composite primary keys for relationship tables: export const agentsKnowledgeBases = pgTable( 'agents_knowledge_bases', { - agentId: text('agent_id').references(() => agents.id, { onDelete: 'cascade' }).notNull(), - knowledgeBaseId: text('knowledge_base_id').references(() => knowledgeBases.id, { onDelete: 'cascade' }).notNull(), - userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + agentId: text('agent_id') + .references(() => agents.id, { onDelete: 'cascade' }) + .notNull(), + knowledgeBaseId: text('knowledge_base_id') + .references(() => knowledgeBases.id, { onDelete: 'cascade' }) + .notNull(), + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), enabled: boolean('enabled').default(true), ...timestamps, }, - (t) => [ - primaryKey({ columns: [t.agentId, t.knowledgeBaseId] }), - ], + (t) => [primaryKey({ columns: [t.agentId, t.knowledgeBaseId] })], ); ``` **Pattern**: `{entity1}Id` + `{entity2}Id` as composite PK, plus `userId` for ownership ### 3. OIDC Tables Special Patterns + OIDC tables use `varchar` IDs instead of `text` with custom generators: ```typescript @@ -166,6 +179,7 @@ export const oidcAuthorizationCodes = pgTable('oidc_authorization_codes', { **Reason**: OIDC standards expect specific ID formats and lengths ### 4. File Processing with Async Tasks + File-related tables reference async task IDs for background processing: ```typescript @@ -173,17 +187,21 @@ File-related tables reference async task IDs for background processing: export const files = pgTable('files', { // ... other fields chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }), - embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }), + embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, { + onDelete: 'set null', + }), // ... }); ``` -**Purpose**: +**Purpose**: + - Track file chunking progress (breaking files into smaller pieces) - Track embedding generation progress (converting text to vectors) - Allow querying task status and handling failures ### 5. Slug Pattern (Legacy) + Some entities include auto-generated slugs - this is legacy code: ```typescript @@ -195,8 +213,6 @@ slug: varchar('slug', { length: 100 }) slugUserIdUnique: uniqueIndex('slug_user_id_unique').on(t.slug, t.userId), ``` -**Current usage**: Only used to identify default agents/sessions (legacy pattern) -**Future refactor**: Will likely be replaced with `isDefault: boolean()` field -**Note**: Avoid using slugs for new features - prefer explicit boolean flags for status tracking +**Current usage**: Only used to identify default agents/sessions (legacy pattern) **Future refactor**: Will likely be replaced with `isDefault: boolean()` field **Note**: Avoid using slugs for new features - prefer explicit boolean flags for status tracking By following these guidelines, maintain consistency, type safety, and maintainability across database schema definitions. diff --git a/.cursor/rules/group-chat.mdc b/.cursor/rules/group-chat.mdc deleted file mode 100644 index bf042fb5e7..0000000000 --- a/.cursor/rules/group-chat.mdc +++ /dev/null @@ -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); -``` diff --git a/.cursor/rules/hotkey.mdc b/.cursor/rules/hotkey.mdc new file mode 100644 index 0000000000..867d27499b --- /dev/null +++ b/.cursor/rules/hotkey.mdc @@ -0,0 +1,161 @@ +--- +alwaysApply: false +--- +# 如何添加新的快捷键:开发者指南 + +本指南将带您一步步地向 LobeChat 添加一个新的快捷键功能。我们将通过一个完整示例,演示从定义到实现的整个过程。 + +## 示例场景 + +假设我们要添加一个新的快捷键功能:**快速清空聊天记录**,快捷键为 `Mod+Shift+Backspace`。 + +## 步骤 1:更新快捷键常量定义 + +首先,在 `src/types/hotkey.ts` 中更新 `HotkeyEnum`: + +```typescript +export const HotkeyEnum = { + // 已有的快捷键... + AddUserMessage: 'addUserMessage', + EditMessage: 'editMessage', + + // 新增快捷键 + ClearChat: 'clearChat', // 添加这一行 + + // 其他已有快捷键... +} as const; +``` + +## 步骤 2:注册默认快捷键 + +在 `src/const/hotkeys.ts` 中添加快捷键的默认配置: + +```typescript +import { KeyMapEnum as Key, combineKeys } from '@lobehub/ui'; + +// ...现有代码 + +export const HOTKEYS_REGISTRATION: HotkeyRegistration = [ + // 现有的快捷键配置... + + // 添加新的快捷键配置 + { + group: HotkeyGroupEnum.Conversation, // 归类到会话操作组 + id: HotkeyEnum.ClearChat, + keys: combineKeys([Key.Mod, Key.Shift, Key.Backspace]), + scopes: [HotkeyScopeEnum.Chat], // 在聊天作用域下生效 + }, + + // 其他现有快捷键... +]; +``` + +## 步骤 3:添加国际化翻译 + +在 `src/locales/default/hotkey.ts` 中添加对应的文本描述: + +```typescript +import { HotkeyI18nTranslations } from '@/types/hotkey'; + +const hotkey: HotkeyI18nTranslations = { + // 现有翻译... + + // 添加新快捷键的翻译 + clearChat: { + desc: '清空当前会话的所有消息记录', + title: '清空聊天记录', + }, + + // 其他现有翻译... +}; + +export default hotkey; +``` + +如需支持其他语言,还需要在相应的语言文件中添加对应翻译。 + +## 步骤 4:创建并注册快捷键 Hook + +在 `src/hooks/useHotkeys/chatScope.ts` 中添加新的 Hook: + +```typescript +export const useClearChatHotkey = () => { + const clearMessages = useChatStore((s) => s.clearMessages); + const { t } = useTranslation(); + + return useHotkeyById(HotkeyEnum.ClearChat, showConfirm); +}; + +// 注册聚合 + +export const useRegisterChatHotkeys = () => { + const { enableScope, disableScope } = useHotkeysContext(); + + useOpenChatSettingsHotkey(); + // ...其他快捷键 + useClearChatHotkey(); + + useEffect(() => { + enableScope(HotkeyScopeEnum.Chat); + return () => disableScope(HotkeyScopeEnum.Chat); + }, []); + + return null; +}; +``` + +## 步骤 5:给相应 UI 元素添加 Tooltip 提示(可选) + +如果有对应的 UI 按钮,可以添加快捷键提示: + +```tsx +import { DeleteOutlined } from '@ant-design/icons'; +import { Tooltip } from '@lobehub/ui'; +import { Button } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import { useUserStore } from '@/store/user'; +import { settingsSelectors } from '@/store/user/selectors'; +import { HotkeyEnum } from '@/types/hotkey'; + +const ClearChatButton = () => { + const { t } = useTranslation(['hotkey', 'chat']); + const clearChatHotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ClearChat)); + + // 获取清空聊天的方法 + const clearMessages = useChatStore((s) => s.clearMessages); + + return ( + +