From e848f32a11ef841340eb2f4b9e0828e0df8c71cd Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Thu, 7 May 2026 02:12:55 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E7=BE=8E=E5=9B=BE=20Agent=20=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=88=9B=E4=BD=9C=E7=95=8C=E9=9D=A2=E5=AE=9E=E6=96=BD?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- .../2026-05-07-pi-agent-video-workflow.md | 2879 +++++++++++++++++ 1 file changed, 2879 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-07-pi-agent-video-workflow.md diff --git a/docs/superpowers/plans/2026-05-07-pi-agent-video-workflow.md b/docs/superpowers/plans/2026-05-07-pi-agent-video-workflow.md new file mode 100644 index 0000000..651a623 --- /dev/null +++ b/docs/superpowers/plans/2026-05-07-pi-agent-video-workflow.md @@ -0,0 +1,2879 @@ +# 美图 Agent — 视频创作可视化界面 实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 构建基于 pi-agent 的视频创作 Web 可视化界面,三栏布局,支持账户管理、提示词编辑、聊天驱动 Pipeline、资产画廊。 + +**Architecture:** React SPA (Vite + Tailwind + shadcn/ui) 通过 REST + WebSocket 与 Express 后端通信。后端集成 pi-agent-core SDK 编排对话,直接调用 pipeline.js 执行视频创作流程。SQLite 存储会话、资产和配置。 + +**Tech Stack:** TypeScript, Vite, React 18, Tailwind CSS, shadcn/ui, Express, ws, better-sqlite3, pi-agent-core, Zustand + +--- + +## 文件结构(全量) + +``` +web/ +├── package.json +├── tsconfig.base.json +├── server/ +│ ├── tsconfig.json +│ ├── index.ts +│ ├── db/ +│ │ ├── index.ts +│ │ └── schema.ts +│ ├── routes/ +│ │ ├── accounts.ts +│ │ ├── prompts.ts +│ │ ├── pipeline.ts +│ │ ├── assets.ts +│ │ └── configs.ts +│ ├── agent/ +│ │ ├── index.ts +│ │ └── tools.ts +│ └── ws/ +│ └── chat.ts +├── client/ +│ ├── tsconfig.json +│ ├── index.html +│ ├── vite.config.ts +│ ├── tailwind.config.ts +│ ├── postcss.config.js +│ └── src/ +│ ├── main.tsx +│ ├── App.tsx +│ ├── index.css +│ ├── lib/ +│ │ ├── api.ts +│ │ └── websocket.ts +│ ├── hooks/ +│ │ ├── useAccounts.ts +│ │ ├── usePrompts.ts +│ │ ├── useAssets.ts +│ │ └── useChat.ts +│ ├── types/ +│ │ └── index.ts +│ ├── components/ +│ │ ├── ui/ # shadcn/ui (generated) +│ │ ├── layout/ +│ │ │ ├── AppLayout.tsx +│ │ │ ├── Sidebar.tsx +│ │ │ └── MiddlePanel.tsx +│ │ ├── chat/ +│ │ │ ├── ChatView.tsx +│ │ │ ├── ChatMessage.tsx +│ │ │ ├── ChatInput.tsx +│ │ │ └── PipelineProgress.tsx +│ │ ├── accounts/ +│ │ │ ├── AccountList.tsx +│ │ │ └── AccountForm.tsx +│ │ ├── prompts/ +│ │ │ └── PromptEditor.tsx +│ │ ├── assets/ +│ │ │ ├── AssetGallery.tsx +│ │ │ └── AssetPreview.tsx +│ │ └── config/ +│ │ └── ConfigForm.tsx +│ └── store/ +│ └── index.ts +``` + +--- + +## P0: 项目脚手架 + +### Task 0.1: 初始化项目与 package.json + +**Files:** +- Create: `web/package.json` +- Create: `web/tsconfig.base.json` +- Create: `web/server/tsconfig.json` +- Create: `web/client/tsconfig.json` + +- [ ] **Step 1: 创建 web/package.json** + +```bash +mkdir -p web/server web/client/src +``` + +```json +{ + "name": "meitu-agent", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", + "dev:server": "tsx watch server/index.ts", + "dev:client": "vite --config client/vite.config.ts", + "build": "vite build --config client/vite.config.ts", + "db:init": "tsx server/db/schema.ts" + }, + "dependencies": { + "express": "^4.21.0", + "ws": "^8.18.0", + "better-sqlite3": "^11.6.0", + "zod": "^3.23.0", + "cors": "^2.8.5", + "zustand": "^5.0.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "lucide-react": "^0.460.0", + "clsx": "^2.1.0", + "tailwind-merge": "^2.6.0" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/ws": "^8.5.0", + "@types/better-sqlite3": "^7.6.0", + "@types/cors": "^2.8.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "typescript": "^5.6.0", + "tsx": "^4.19.0", + "vite": "^5.4.0", + "@vitejs/plugin-react": "^4.3.0", + "tailwindcss": "^3.4.0", + "postcss": "^8.4.0", + "autoprefixer": "^10.4.0", + "concurrently": "^9.1.0" + } +} +``` + +- [ ] **Step 2: 创建 tsconfig.base.json** + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + } +} +``` + +- [ ] **Step 3: 创建 server/tsconfig.json** + +```json +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "outDir": "./dist", + "rootDir": "." + }, + "include": ["./**/*.ts"] +} +``` + +- [ ] **Step 4: 创建 client/tsconfig.json** + +```json +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./dist", + "rootDir": "./src", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["./src/**/*.ts", "./src/**/*.tsx"] +} +``` + +- [ ] **Step 5: 安装依赖** + +```bash +cd web && npm install +``` + +- [ ] **Step 6: Commit** + +```bash +git add web/package.json web/tsconfig.base.json web/server/tsconfig.json web/client/tsconfig.json web/package-lock.json +git commit -m "feat(web): init project scaffold with package.json and tsconfig" +``` + +--- + +### Task 0.2: 初始化 Vite + React + Tailwind + shadcn/ui + +**Files:** +- Create: `web/client/index.html` +- Create: `web/client/vite.config.ts` +- Create: `web/client/tailwind.config.ts` +- Create: `web/client/postcss.config.js` +- Create: `web/client/src/main.tsx` +- Create: `web/client/src/index.css` +- Create: `web/client/src/lib/utils.ts` + +- [ ] **Step 1: 创建 client/index.html** + +```html + + + + + + 美图 Agent + + +
+ + + +``` + +- [ ] **Step 2: 创建 client/vite.config.ts** + +```typescript +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + root: path.resolve(__dirname), + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': 'http://localhost:3001', + '/ws': { + target: 'ws://localhost:3001', + ws: true, + }, + }, + }, +}); +``` + +- [ ] **Step 3: 创建 client/tailwind.config.ts** + +```typescript +import type { Config } from 'tailwindcss'; + +export default { + content: ['./client/src/**/*.{ts,tsx}'], + darkMode: 'class', + theme: { + extend: { + colors: { + border: 'hsl(240 3.7% 15.9%)', + input: 'hsl(240 3.7% 15.9%)', + ring: 'hsl(240 4.9% 83.9%)', + background: 'hsl(240 10% 3.9%)', + foreground: 'hsl(0 0% 98%)', + primary: { + DEFAULT: 'hsl(0 0% 98%)', + foreground: 'hsl(240 5.9% 10%)', + }, + secondary: { + DEFAULT: 'hsl(240 3.7% 15.9%)', + foreground: 'hsl(0 0% 98%)', + }, + muted: { + DEFAULT: 'hsl(240 3.7% 15.9%)', + foreground: 'hsl(240 5% 64.9%)', + }, + accent: { + DEFAULT: 'hsl(240 3.7% 15.9%)', + foreground: 'hsl(0 0% 98%)', + }, + destructive: { + DEFAULT: 'hsl(0 62.8% 30.6%)', + foreground: 'hsl(0 0% 98%)', + }, + }, + borderRadius: { + lg: '0.5rem', + md: 'calc(0.5rem - 2px)', + sm: 'calc(0.5rem - 4px)', + }, + }, + }, + plugins: [], +} satisfies Config; +``` + +- [ ] **Step 4: 创建 client/postcss.config.js** + +```javascript +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; +``` + +- [ ] **Step 5: 创建 client/src/index.css** + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-zinc-950 text-zinc-50; + } +} +``` + +- [ ] **Step 6: 创建 client/src/lib/utils.ts** + +```typescript +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} +``` + +- [ ] **Step 7: 创建 client/src/main.tsx** + +```typescript +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); +``` + +- [ ] **Step 8: Commit** + +```bash +git add web/client/ +git commit -m "feat(web): init Vite + React + Tailwind client scaffold" +``` + +--- + +### Task 0.3: 初始化 shadcn/ui 基础组件 + +**Files:** +- Create: `web/components.json` +- Create: `web/client/src/components/ui/button.tsx` +- Create: `web/client/src/components/ui/input.tsx` +- Create: `web/client/src/components/ui/scroll-area.tsx` + +- [ ] **Step 1: 创建 components.json** + +```json +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "client/tailwind.config.ts", + "css": "client/src/index.css", + "baseColor": "zinc", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} +``` + +- [ ] **Step 2: 创建 button.tsx** + +```typescript +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10', + }, + }, + defaultVariants: { variant: 'default', size: 'default' }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps {} + +const Button = React.forwardRef( + ({ className, variant, size, ...props }, ref) => ( + + ))} + + ); +} +``` + +- [ ] **Step 5: 创建 MiddlePanel.tsx(对话列表占位)** + +```typescript +import { Plus } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { useAppStore } from '@/store'; + +export function MiddlePanel() { + const { conversations, activeConversationId, setActiveConversationId } = useAppStore(); + + return ( + + ); +} +``` + +- [ ] **Step 6: 创建 App.tsx** + +```typescript +import { AppLayout } from '@/components/layout/AppLayout'; +import { ChatView } from '@/components/chat/ChatView'; +import { AccountList } from '@/components/accounts/AccountList'; +import { AssetGallery } from '@/components/assets/AssetGallery'; +import { ConfigForm } from '@/components/config/ConfigForm'; +import { useAppStore } from '@/store'; +import { useEffect } from 'react'; + +function MainContent() { + const view = useAppStore((s) => s.activeView); + switch (view) { + case 'chat': return ; + case 'accounts': return ; + case 'assets': return ; + case 'config': return ; + } +} + +export default function App() { + useEffect(() => { + fetch('/api/configs') + .then((r) => r.json()) + .catch(() => {}); + }, []); + + return ( + + + + ); +} +``` + +- [ ] **Step 7: 创建占位组件(空壳,后续任务填充)** + +创建以下占位文件,每个只返回一个空 div: + +`chat/ChatView.tsx`: +```typescript +export function ChatView() { + return
选择或开始新对话
; +} +``` + +`accounts/AccountList.tsx`: +```typescript +export function AccountList() { + return
账户管理
; +} +``` + +`assets/AssetGallery.tsx`: +```typescript +export function AssetGallery() { + return
资产画廊
; +} +``` + +`config/ConfigForm.tsx`: +```typescript +export function ConfigForm() { + return
设置
; +} +``` + +- [ ] **Step 8: 验证: 启动 dev server** + +```bash +cd web && npm run dev +``` + +打开 http://localhost:5173,确认三栏布局渲染正常(左侧图标栏 56px + 中间列表 240px + 右侧主区域)。 + +- [ ] **Step 9: Commit** + +```bash +git add web/client/src/ +git commit -m "feat(web): add three-column layout shell with placeholder views" +``` + +--- + +## P1: 账户管理 + 配置 + +### Task 1.1: 类型定义 + API Client + +**Files:** +- Create: `web/client/src/lib/api.ts` + +- [ ] **Step 1: 创建 lib/api.ts** + +```typescript +import type { Account, Asset, Conversation, Message, ConfigItem } from '@/types'; + +const BASE = '/api'; + +async function request(path: string, options?: RequestInit): Promise { + const res = await fetch(`${BASE}${path}`, { + headers: { 'Content-Type': 'application/json' }, + ...options, + }); + if (!res.ok) throw new Error(await res.text()); + return res.json(); +} + +export const api = { + // Accounts + listAccounts: () => request('/accounts'), + getAccount: (id: string) => request(`/accounts/${id}`), + createAccount: (data: Partial) => + request('/accounts', { method: 'POST', body: JSON.stringify(data) }), + updateAccount: (id: string, data: Partial) => + request(`/accounts/${id}`, { method: 'PUT', body: JSON.stringify(data) }), + deleteAccount: (id: string) => + request(`/accounts/${id}`, { method: 'DELETE' }), + + // Prompts + getPrompt: (accountId: string, type: string) => + request<{ path: string; content: string }>(`/prompts/${accountId}/${type}`), + savePrompt: (accountId: string, type: string, content: string) => + request(`/prompts/${accountId}/${type}`, { method: 'PUT', body: JSON.stringify({ content }) }), + + // Conversations + listConversations: () => request('/pipeline/conversations'), + getMessages: (convId: string) => request(`/pipeline/conversations/${convId}/messages`), + + // Assets + listAssets: (params?: { accountId?: string; type?: string }) => { + const qs = new URLSearchParams(); + if (params?.accountId) qs.set('accountId', params.accountId); + if (params?.type) qs.set('type', params.type); + return request(`/assets?${qs}`); + }, + deleteAsset: (id: string) => request(`/assets/${id}`, { method: 'DELETE' }), + + // Configs + getConfigs: () => request('/configs'), + saveConfig: (key: string, value: Record) => + request(`/configs/${key}`, { method: 'PUT', body: JSON.stringify({ value }) }), +}; +``` + +- [ ] **Step 2: Commit** + +```bash +git add web/client/src/lib/api.ts +git commit -m "feat(web): add typed API client" +``` + +--- + +### Task 1.2: 账户 CRUD 后端路由 + +**Files:** +- Create: `web/server/routes/accounts.ts` + +- [ ] **Step 1: 创建 server/routes/accounts.ts** + +```typescript +import { Router } from 'express'; +import fs from 'fs'; +import path from 'path'; + +const ACCOUNTS_DIR = path.resolve(__dirname, '..', '..', '..', 'accounts'); + +export const accountsRouter = Router(); + +function readAccountJson(id: string): Record | null { + const p = path.join(ACCOUNTS_DIR, id, 'account.json'); + if (!fs.existsSync(p)) return null; + return JSON.parse(fs.readFileSync(p, 'utf-8')); +} + +function writeAccountJson(id: string, data: Record): void { + const dir = path.join(ACCOUNTS_DIR, id); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'account.json'), JSON.stringify(data, null, 2), 'utf-8'); +} + +// List +accountsRouter.get('/', (_req, res) => { + const dirs = fs.readdirSync(ACCOUNTS_DIR, { withFileTypes: true }) + .filter((d) => d.isDirectory() && !d.name.startsWith('_') && !d.name.startsWith('.')) + .map((d) => d.name); + + const accounts = dirs + .map((id) => readAccountJson(id)) + .filter(Boolean); + res.json(accounts); +}); + +// Get +accountsRouter.get('/:id', (req, res) => { + const data = readAccountJson(req.params.id); + if (!data) return res.status(404).json({ error: 'Account not found' }); + res.json(data); +}); + +// Create +accountsRouter.post('/', (req, res) => { + const { id, ...rest } = req.body; + if (!id) return res.status(400).json({ error: 'id is required' }); + + const dir = path.join(ACCOUNTS_DIR, id); + if (fs.existsSync(dir)) return res.status(409).json({ error: 'Account already exists' }); + + const data = { + id, + name: rest.name || id, + description: rest.description || '', + defaultFormat: rest.defaultFormat || '9:16', + imageModel: rest.imageModel || 'gemini', + videoModel: rest.videoModel || 'veo3-fast', + batchSize: rest.batchSize || 30, + ttsVoice: rest.ttsVoice || '', + ttsInstruction: rest.ttsInstruction || '', + storyboardPrompt: rest.storyboardPrompt || 'prompts/分镜.md', + imageStylePrompt: rest.imageStylePrompt || 'prompts/图片提示词.md', + videoStylePrompt: rest.videoStylePrompt || 'prompts/视频提示词.md', + references: rest.references || [], + capcut: rest.capcut || {}, + }; + + writeAccountJson(id, data); + res.status(201).json(data); +}); + +// Update +accountsRouter.put('/:id', (req, res) => { + const existing = readAccountJson(req.params.id); + if (!existing) return res.status(404).json({ error: 'Account not found' }); + + const merged = { ...existing, ...req.body, id: req.params.id }; + writeAccountJson(req.params.id, merged); + res.json(merged); +}); + +// Delete +accountsRouter.delete('/:id', (req, res) => { + const dir = path.join(ACCOUNTS_DIR, req.params.id); + if (!fs.existsSync(dir)) return res.status(404).json({ error: 'Account not found' }); + fs.rmSync(dir, { recursive: true }); + res.status(204).send(); +}); +``` + +- [ ] **Step 2: Commit** + +```bash +git add web/server/routes/accounts.ts +git commit -m "feat(web): add account CRUD API routes" +``` + +--- + +### Task 1.3: 配置 CRUD 后端路由 + +**Files:** +- Create: `web/server/routes/configs.ts` + +- [ ] **Step 1: 创建 server/routes/configs.ts** + +```typescript +import { Router } from 'express'; +import { getDb } from '../db'; + +export const configsRouter = Router(); + +configsRouter.get('/', (_req, res) => { + const rows = getDb().prepare('SELECT * FROM configs ORDER BY key').all(); + res.json(rows); +}); + +configsRouter.get('/:key', (req, res) => { + const row = getDb().prepare('SELECT * FROM configs WHERE key = ?').get(req.params.key); + if (!row) return res.status(404).json({ error: 'Config not found' }); + res.json(row); +}); + +configsRouter.put('/:key', (req, res) => { + const { value } = req.body; + getDb().prepare(` + INSERT INTO configs (id, key, value, updated_at) + VALUES (?, ?, ?, datetime('now')) + ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at + `).run(crypto.randomUUID(), req.params.key, JSON.stringify(value)); + res.json({ key: req.params.key, ok: true }); +}); +``` + +- [ ] **Step 2: Commit** + +```bash +git add web/server/routes/configs.ts +git commit -m "feat(web): add config CRUD API routes" +``` + +--- + +### Task 1.4: 账户列表 + 表单前端组件 + +**Files:** +- Replace: `web/client/src/components/accounts/AccountList.tsx` +- Create: `web/client/src/components/accounts/AccountForm.tsx` +- Create: `web/client/src/hooks/useAccounts.ts` + +- [ ] **Step 1: 创建 hooks/useAccounts.ts** + +```typescript +import { useState, useEffect, useCallback } from 'react'; +import { api } from '@/lib/api'; +import type { Account } from '@/types'; + +export function useAccounts() { + const [accounts, setAccounts] = useState([]); + const [loading, setLoading] = useState(true); + + const refresh = useCallback(() => { + api.listAccounts().then(setAccounts).finally(() => setLoading(false)); + }, []); + + useEffect(() => { refresh(); }, [refresh]); + + const create = (data: Partial) => api.createAccount(data).then(refresh); + const update = (id: string, data: Partial) => api.updateAccount(id, data).then(refresh); + const remove = (id: string) => api.deleteAccount(id).then(refresh); + + return { accounts, loading, refresh, create, update, remove }; +} +``` + +- [ ] **Step 2: 重写 AccountList.tsx** + +```typescript +import { Plus } from 'lucide-react'; +import { useState } from 'react'; +import { useAccounts } from '@/hooks/useAccounts'; +import { AccountForm } from './AccountForm'; +import { Button } from '@/components/ui/button'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import type { Account } from '@/types'; + +export function AccountList() { + const { accounts, create, update, remove } = useAccounts(); + const [editing, setEditing] = useState(null); + const [creating, setCreating] = useState(false); + + return ( +
+ +
+

账户列表

+ +
+ {accounts.map((a) => ( + + ))} +
+ +
+ {(editing || creating) ? ( + { + if (creating) { + create(data).then(() => setCreating(false)); + } else { + update(editing!.id, data).then(() => setEditing(null)); + } + }} + onDelete={editing ? () => { + if (confirm(`确定删除账户「${editing.name}」?`)) { + remove(editing.id).then(() => setEditing(null)); + } + } : undefined} + onCancel={() => { setEditing(null); setCreating(false); }} + /> + ) : ( +
+ 选择一个账户或创建新账户 +
+ )} +
+
+ ); +} +``` + +- [ ] **Step 3: 创建 AccountForm.tsx** + +```typescript +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import type { Account } from '@/types'; + +interface Props { + account?: Account; + onSave: (data: Partial) => void; + onDelete?: () => void; + onCancel: () => void; +} + +export function AccountForm({ account, onSave, onDelete, onCancel }: Props) { + const [form, setForm] = useState({ + id: account?.id || '', + name: account?.name || '', + description: account?.description || '', + defaultFormat: account?.defaultFormat || '9:16', + imageModel: account?.imageModel || 'gemini', + videoModel: account?.videoModel || 'veo3-fast', + ttsVoice: account?.ttsVoice || '', + ttsInstruction: account?.ttsInstruction || '', + }); + + const handleChange = (key: string, value: string) => setForm((f) => ({ ...f, [key]: value })); + + return ( +
+

+ {account ? `编辑账户: ${account.name}` : '创建新账户'} +

+ +
+ + handleChange('id', e.target.value)} + disabled={!!account} + placeholder="my-account" + className="mt-1 bg-zinc-900 border-zinc-800" + /> +
+ +
+ + handleChange('name', e.target.value)} + placeholder="账户名称" + className="mt-1 bg-zinc-900 border-zinc-800" + /> +
+ +
+ + handleChange('description', e.target.value)} + placeholder="简短描述..." + className="mt-1 bg-zinc-900 border-zinc-800" + /> +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + handleChange('ttsVoice', e.target.value)} + placeholder="cosyvoice-xxx" + className="mt-1 bg-zinc-900 border-zinc-800 font-mono text-xs" + /> +
+ +
+ +