Files
human-adapter-react/.trae/documents/tech-architecture.md
T

460 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. 架构设计
```mermaid
flowchart TB
subgraph Frontend["前端 (React + Vite)"]
UI["shadcn/ui 组件层"]
Pages["页面层"]
Hooks["Hooks 层"]
Store["Zustand 状态管理"]
SDK["array-sdk 通信层"]
end
subgraph Backend["后端 (FastAPI + SQLite)"]
API["REST API"]
WS["WebSocket"]
end
UI --> Pages
Pages --> Hooks
Hooks --> Store
Hooks --> SDK
Store --> SDK
SDK --> API
SDK --> WS
```
## 2. 技术说明
- **前端框架**:React 18 + TypeScript + Vite 6
- **UI 组件库**:shadcn/ui(Radix UI + Tailwind CSS)
- **状态管理**:Zustand(轻量、无 boilerplate、支持中间件)
- **样式方案**:Tailwind CSS 4 + CSS 变量主题系统
- **图标**:Lucide React
- **动画**:Framer Motion
- **Markdown 渲染**:react-markdown + remark-gfm + rehype-highlight
- **HTTP/WebSocket**:array-sdk(从旧项目迁移,纯 JS 无框架依赖)
- **路由**:React Router DOM v7(Hash 模式,兼容静态部署)
- **国际化**:自定义 i18n hook(中/英双语)
- **PWA**:vite-plugin-pwa
- **项目初始化**:vite-init react-ts 模板
## 3. 路由定义
| 路由 | 用途 |
|------|------|
| `/` | 登录页(未登录时)或主工作台(已登录时) |
| `/task/:taskId` | 主工作台,自动选中指定任务 |
## 4. API 定义
本项目为纯前端,所有 API 调用通过 array-sdk 与后端通信。SDK 封装层提供以下接口:
### 4.1 认证
```typescript
interface AuthAPI {
login(serverUrl: string, apiKey: string): Promise<Worker>
loginByPassword(serverUrl: string, username: string, password: string): Promise<Worker>
register(serverUrl: string, name: string, username: string, password: string, profile?: RegisterProfile): Promise<Worker>
autoLogin(): Promise<Worker | null>
logout(): void
}
```
### 4.2 Worker
```typescript
interface WorkerAPI {
getWorkers(params?: { orgId?: string }): Promise<Worker[]>
getWorkerProfile(workerId: string): Promise<Worker>
updateProfile(data: Partial<WorkerProfile>): Promise<Worker>
getWorkerWorkload(workerId: string): Promise<Workload>
discoverWorkers(options: DiscoverOptions): Promise<DiscoverResult>
}
```
### 4.3 Task
```typescript
interface TaskAPI {
getTasks(params?: { orgId?: string; categoryId?: string }): Promise<Task[]>
getTask(taskId: string): Promise<Task>
createTask(title: string, participantIds: string[], priority?: string, extra?: CreateTaskExtra): Promise<Task>
updateTaskStatus(taskId: string, status: TaskStatus): Promise<Task>
updateTaskTitle(taskId: string, title: string): Promise<Task>
addParticipant(taskId: string, workerId: string): Promise<Participant[]>
removeParticipant(taskId: string, workerId: string): Promise<void>
}
```
### 4.4 Message
```typescript
interface MessageAPI {
getMessages(taskId: string, scope?: string, includePrivate?: boolean, limit?: number, page?: number): Promise<Message[]>
sendMessage(taskId: string, content: string, options?: SendMessageOptions): Promise<Message>
sendMessageWithAttachments(taskId: string, content: string, fileIds: string[], options?: SendMessageOptions): Promise<Message>
}
```
### 4.5 Attachment
```typescript
interface AttachmentAPI {
uploadAttachment(taskId: string, file: File): Promise<Attachment>
listAttachments(taskId: string): Promise<{ attachments: Attachment[]; total: number }>
deleteAttachment(taskId: string, attachmentId: string): Promise<void>
getAttachmentUrl(taskId: string, attachmentId: string): string
}
```
### 4.6 Organization
```typescript
interface OrganizationAPI {
listMyOrganizations(): Promise<Organization[]>
getOrganization(orgId: string): Promise<Organization>
createOrganization(name: string, description?: string): Promise<Organization>
updateOrganization(orgId: string, data: Partial<Organization>): Promise<Organization>
createInvite(orgId: string, options?: InviteOptions): Promise<OrgInvite>
listInvites(orgId: string): Promise<OrgInvite[]>
joinOrganization(inviteCode: string): Promise<JoinResult>
listOrgMembers(orgId: string): Promise<OrgMember[]>
removeOrgMember(orgId: string, workerId: string): Promise<void>
updateMemberRole(orgId: string, workerId: string, role: 'admin' | 'member'): Promise<OrgMember>
renameOrgMember(orgId: string, workerId: string, name: string): Promise<OrgMember>
}
```
### 4.7 Category
```typescript
interface CategoryAPI {
createCategory(name: string, orgId?: string): Promise<Category>
listCategories(orgId?: string): Promise<Category[]>
updateCategory(categoryId: string, opts: Partial<Category>): Promise<Category>
deleteCategory(categoryId: string): Promise<boolean>
}
```
### 4.8 Review
```typescript
interface ReviewAPI {
createReview(workerId: string, params: CreateReviewParams): Promise<Review>
getWorkerReviews(workerId: string, options?: PaginationOptions): Promise<ReviewListResult>
getReviewSummary(workerId: string): Promise<ReviewSummary>
}
```
### 4.9 Notification
```typescript
interface NotificationAPI {
getNotifications(isRead?: boolean): Promise<Notification[]>
markNotificationRead(notificationId: string): Promise<void>
markAllNotificationsRead(): Promise<void>
}
```
### 4.10 WebSocket
```typescript
interface WebSocketAPI {
startWebSocket(callbacks: WSCallbacks): () => void
stopWebSocket(): void
}
interface WSCallbacks {
onEvent: (event: WSEvent) => void
onConnected: () => void
onDisconnected: () => void
onError: (err: Error) => void
}
type WSEventType = 'task.message' | 'participant.joined' | 'task.updated' | 'worker_status_changed' | 'tasks.pending_recovery'
```
## 5. 数据模型
```mermaid
erDiagram
Organization ||--o{ Category : "has"
Organization ||--o{ OrgMember : "has"
Organization ||--o{ OrgInvite : "has"
Worker ||--o{ Task : "creates"
Worker ||--o{ Review : "receives"
Task ||--o{ Participant : "has"
Task ||--o{ Message : "has"
Task ||--o{ Attachment : "has"
Task }o--|| Category : "belongs_to"
Message }o--o| Message : "reply_to"
Message }o--o| Worker : "private_to"
Worker {
string id PK
string name
string type
string status
string organization
string location
string description
string[] tags
object capabilities
number maxConcurrentTasks
}
Task {
string id PK
string title
string description
string status
string priority
string creator_id FK
string org_id FK
string category_id FK
datetime created_at
datetime last_message_at
}
Message {
string id PK
string task_id FK
string sender_id FK
string content
string type
string reply_to_id FK
string private_to_id FK
datetime created_at
}
Attachment {
string id PK
string task_id FK
string filename
string content_type
number size
string url
string thumbnail_url
number width
number height
number duration
}
Review {
string id PK
string worker_id FK
string task_id FK
string reviewer_id FK
number rating
string[] capability_tags
string comment
string suggestion
}
```
## 6. 项目目录结构
```
human-adapter-react/
├── src/
│ ├── components/ # UI 组件
│ │ ├── ui/ # shadcn/ui 基础组件
│ │ ├── layout/ # 布局组件
│ │ │ ├── TopBar.tsx
│ │ │ ├── WorkersPanel.tsx
│ │ │ ├── TaskList.tsx
│ │ │ └── Resizer.tsx
│ │ ├── chat/ # 聊天相关组件
│ │ │ ├── ChatDetail.tsx
│ │ │ ├── MessageList.tsx
│ │ │ ├── MessageBubble.tsx
│ │ │ ├── MessageInput.tsx
│ │ │ ├── MentionPopup.tsx
│ │ │ ├── ReplyPreview.tsx
│ │ │ ├── AttachmentRenderer.tsx
│ │ │ ├── DMTab.tsx
│ │ │ └── ScrollToBottom.tsx
│ │ ├── task/ # 任务相关组件
│ │ │ ├── TaskCard.tsx
│ │ │ ├── TaskComposer.tsx
│ │ │ ├── TaskDrawer.tsx
│ │ │ ├── StatusSelector.tsx
│ │ │ └── ParticipantManager.tsx
│ │ ├── org/ # 组织相关组件
│ │ │ ├── OrgSwitcher.tsx
│ │ │ ├── OrgDetailSheet.tsx
│ │ │ └── InviteManager.tsx
│ │ ├── profile/ # 个人中心组件
│ │ │ ├── ProfileDialog.tsx
│ │ │ └── ShortcutSettings.tsx
│ │ ├── review/ # 评价组件
│ │ │ ├── ReviewDialog.tsx
│ │ │ └── ReviewSummary.tsx
│ │ ├── auth/ # 登录组件
│ │ │ └── LoginPage.tsx
│ │ └── shared/ # 共享组件
│ │ ├── LedLogo.tsx
│ │ ├── LedMatrix.tsx
│ │ ├── RobotIcon.tsx
│ │ ├── WorkerAvatar.tsx
│ │ ├── Toast.tsx
│ │ └── PwaInstallBanner.tsx
│ ├── hooks/ # 自定义 Hooks
│ │ ├── useAuth.ts
│ │ ├── useMessages.ts
│ │ ├── useWorkers.ts
│ │ ├── useTasks.ts
│ │ ├── useOrganization.ts
│ │ ├── useNotifications.ts
│ │ ├── useReviews.ts
│ │ ├── useProfile.ts
│ │ ├── useWebSocket.ts
│ │ ├── useI18n.ts
│ │ ├── useTheme.ts
│ │ ├── useAutoScroll.ts
│ │ └── useDrafts.ts
│ ├── stores/ # Zustand Store
│ │ ├── authStore.ts
│ │ ├── appStore.ts
│ │ └── uiStore.ts
│ ├── lib/ # 工具库
│ │ ├── sdk.ts # array-sdk 封装
│ │ ├── api.ts # API 薄封装层
│ │ ├── i18n.ts # 国际化字典
│ │ ├── utils.ts # 通用工具函数
│ │ └── constants.ts # 常量定义
│ ├── pages/ # 页面
│ │ ├── LoginPage.tsx
│ │ └── WorkbenchPage.tsx
│ ├── App.tsx
│ ├── main.tsx
│ └── index.css # Tailwind 入口 + CSS 变量
├── public/
├── sdk-js/ # array-sdk 源码(从旧项目迁移)
│ ├── src/
│ │ ├── client.ts
│ │ ├── adapter.ts
│ │ └── index.ts
│ └── package.json
├── .trae/documents/
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
├── components.json # shadcn/ui 配置
└── postcss.config.js
```
## 7. 关键架构决策
### 7.1 状态管理:Zustand vs Context
选择 Zustand 的理由:
- 无 Provider 包裹,任何组件直接 `useStore()` 即可
- 支持 `subscribeWithSelector` 中间件,精确控制重渲染
- 支持 `persist` 中间件,自动持久化到 localStorage
- 比 Redux 少 90% boilerplate,比 Jotai 更适合复杂状态
```typescript
// appStore.ts 示例
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface AppState {
workers: Worker[]
tasks: Task[]
selectedTaskId: string | null
wsConnected: boolean
setWorkers: (workers: Worker[]) => void
setTasks: (tasks: Task[]) => void
selectTask: (id: string | null) => void
}
export const useAppStore = create<AppState>()(
persist(
(set) => ({
workers: [],
tasks: [],
selectedTaskId: null,
wsConnected: false,
setWorkers: (workers) => set({ workers }),
setTasks: (tasks) => set({ tasks }),
selectTask: (id) => set({ selectedTaskId: id }),
}),
{ name: 'array-app-store', partialize: (s) => ({ selectedTaskId: s.selectedTaskId }) }
)
)
```
### 7.2 消息系统:自定义 Hook
不使用 Vercel AI SDK 的 `useChat()`,因为:
- 本项目的消息模型更复杂(私密消息、@提及、引用回复、附件)
- WebSocket 事件格式是后端自定义的
- 需要精确控制乐观消息的生命周期
自定义 `useMessages` Hook 借鉴 AI SDK 的状态机思想:
```typescript
type MessageStatus = 'idle' | 'sending' | 'streaming' | 'error'
interface UseMessagesReturn {
messages: Message[]
pendingMessages: PendingMessage[]
isLoading: boolean
isAtBottom: boolean
sendMessage: (content: string, options?: SendOptions) => Promise<void>
retryMessage: (pendingId: string) => Promise<void>
cancelMessage: (pendingId: string) => void
loadMore: () => Promise<void>
hasMore: boolean
scrollToBottom: () => void
}
```
### 7.3 主题系统:Tailwind CSS 变量
```css
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--accent: 221.2 83.2% 53.3%;
/* ... shadcn 默认变量 */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--accent: 217.2 91.2% 59.8%;
}
```
切换主题只需在 `<html>` 上添加/移除 `dark` 类,所有组件自动响应。
### 7.4 SDK 迁移策略
array-sdk 是纯 JavaScript,无框架依赖,直接迁移:
- `sdk-js/` 目录整体复制到新项目
- `package.json` 中 `"array-sdk": "file:./sdk-js"` 保持不变
- API 封装层 `lib/api.ts` 从旧项目 `api/index.js` 翻译为 TypeScript
- 所有方法签名添加 TypeScript 类型
### 7.5 组件拆分原则
旧项目 TaskDetail.vue 72KB → 拆分为 8+ 个子组件:
| 旧代码位置 | 新组件 | 职责 |
|------------|--------|------|
| 消息流模板 | `MessageList.tsx` | 消息列表渲染 + 时间分割线 |
| 单条消息 | `MessageBubble.tsx` | 消息气泡 + 头像 + 引用 + 附件 |
| 回复输入栏 | `MessageInput.tsx` | 输入框 + 发送按钮 + 快捷键 |
| @提及弹窗 | `MentionPopup.tsx` | 搜索 + 选择参与者 |
| 私密消息 | `DMTab.tsx` | 私聊通道列表 + 消息 |
| 附件渲染 | `AttachmentRenderer.tsx` | 图片/视频/音频/文件智能渲染 |
| 任务抽屉 | `TaskDrawer.tsx` | 参与者 + 详情 + 评价 |
| 状态选择器 | `StatusSelector.tsx` | 状态下拉切换 |