From 348cc0c5b9413e5000dd193c07686c8bc9ba82c3 Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Thu, 7 May 2026 03:48:14 +0800 Subject: [PATCH] refactor(web): router-based navigation, light theme, form config - Replace Zustand activeView with React Router (NavLink + Routes) - White/light modern theme with indigo accents - Sidebar with Chinese labels under icons - ConfigForm with individual form fields (no JSON textareas) - Account switching with context injection into chat - Fix duplicate conversation creation with useRef guard - Asset gallery: smaller 6-column grid with date labels - All components updated to light color scheme Co-Authored-By: Claude Opus 4.7 --- web/client/index.html | 2 +- web/client/package-lock.json | 89 ++++++++ web/client/package.json | 22 +- web/client/src/App.tsx | 22 +- .../src/components/accounts/AccountForm.tsx | 8 +- .../src/components/accounts/AccountList.tsx | 38 ++-- .../src/components/assets/AssetGallery.tsx | 32 ++- web/client/src/components/chat/ChatInput.tsx | 10 +- .../src/components/chat/ChatMessage.tsx | 8 +- web/client/src/components/chat/ChatView.tsx | 107 ++++++++-- .../src/components/config/ConfigForm.tsx | 201 +++++++++++++++--- .../src/components/layout/AppLayout.tsx | 11 +- .../src/components/layout/MiddlePanel.tsx | 25 ++- web/client/src/components/layout/Sidebar.tsx | 45 ++-- .../src/components/prompts/PromptEditor.tsx | 6 +- web/client/src/index.css | 55 ++--- web/client/src/main.tsx | 5 +- web/client/src/store/index.ts | 10 +- web/client/src/types/index.ts | 2 - web/client/vite.config.ts | 28 +-- web/data/meitu-agent.db-shm | Bin 32768 -> 32768 bytes web/data/meitu-agent.db-wal | Bin 123632 -> 3596792 bytes 22 files changed, 506 insertions(+), 220 deletions(-) diff --git a/web/client/index.html b/web/client/index.html index 4afb630..d9a760f 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -5,7 +5,7 @@ 美图 Agent - +
diff --git a/web/client/package-lock.json b/web/client/package-lock.json index c17709e..461bc97 100644 --- a/web/client/package-lock.json +++ b/web/client/package-lock.json @@ -14,6 +14,7 @@ "markdown-it": "^14.1.0", "react": "^18.3.0", "react-dom": "^18.3.0", + "react-router-dom": "^7.15.0", "tailwind-merge": "^2.6.0", "zustand": "^5.0.0" }, @@ -21,6 +22,7 @@ "@types/markdown-it": "^14.1.0", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", + "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^4.3.0", "autoprefixer": "^10.4.0", "postcss": "^8.4.0", @@ -1212,6 +1214,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1265,6 +1274,29 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1537,6 +1569,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2356,6 +2401,44 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz", + "integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz", + "integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.15.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -2500,6 +2583,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/web/client/package.json b/web/client/package.json index 5803b52..f4a9109 100644 --- a/web/client/package.json +++ b/web/client/package.json @@ -8,24 +8,26 @@ "build": "vite build" }, "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.0", + "lucide-react": "^0.460.0", + "markdown-it": "^14.1.0", "react": "^18.3.0", "react-dom": "^18.3.0", - "zustand": "^5.0.0", - "lucide-react": "^0.460.0", - "clsx": "^2.1.0", + "react-router-dom": "^7.15.0", "tailwind-merge": "^2.6.0", - "class-variance-authority": "^0.7.1", - "markdown-it": "^14.1.0" + "zustand": "^5.0.0" }, "devDependencies": { + "@types/markdown-it": "^14.1.0", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", - "@types/markdown-it": "^14.1.0", + "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^4.3.0", - "vite": "^5.4.0", - "tailwindcss": "^3.4.0", - "postcss": "^8.4.0", "autoprefixer": "^10.4.0", - "typescript": "^5.6.0" + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.6.0", + "vite": "^5.4.0" } } diff --git a/web/client/src/App.tsx b/web/client/src/App.tsx index 09fba56..f455084 100644 --- a/web/client/src/App.tsx +++ b/web/client/src/App.tsx @@ -1,24 +1,22 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; 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'; - -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() { return ( - + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + ); } diff --git a/web/client/src/components/accounts/AccountForm.tsx b/web/client/src/components/accounts/AccountForm.tsx index 1dce685..d248a01 100644 --- a/web/client/src/components/accounts/AccountForm.tsx +++ b/web/client/src/components/accounts/AccountForm.tsx @@ -37,7 +37,7 @@ export function AccountForm({ account, onSave, onDelete, onCancel }: Props) { onChange={(e) => handleChange('id', e.target.value)} disabled={!!account} placeholder="my-account" - className="mt-1 bg-zinc-900 border-zinc-800" + className="mt-1 bg-zinc-50 border-zinc-200" /> @@ -47,7 +47,7 @@ export function AccountForm({ account, onSave, onDelete, onCancel }: Props) { value={form.name} onChange={(e) => handleChange('name', e.target.value)} placeholder="账户名称" - className="mt-1 bg-zinc-900 border-zinc-800" + className="mt-1 bg-zinc-50 border-zinc-200" /> @@ -57,7 +57,7 @@ export function AccountForm({ account, onSave, onDelete, onCancel }: Props) { value={form.description} onChange={(e) => handleChange('description', e.target.value)} placeholder="简短描述..." - className="mt-1 bg-zinc-900 border-zinc-800" + className="mt-1 bg-zinc-50 border-zinc-200" /> @@ -108,7 +108,7 @@ export function AccountForm({ account, onSave, onDelete, onCancel }: Props) { value={form.ttsVoice} onChange={(e) => handleChange('ttsVoice', e.target.value)} placeholder="cosyvoice-xxx" - className="mt-1 bg-zinc-900 border-zinc-800 font-mono text-xs" + className="mt-1 bg-zinc-50 border-zinc-200 font-mono text-xs" /> diff --git a/web/client/src/components/accounts/AccountList.tsx b/web/client/src/components/accounts/AccountList.tsx index 83f2704..150166a 100644 --- a/web/client/src/components/accounts/AccountList.tsx +++ b/web/client/src/components/accounts/AccountList.tsx @@ -1,5 +1,6 @@ import { Plus } from 'lucide-react'; import { useState } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; import { useAccounts } from '@/hooks/useAccounts'; import { AccountForm } from './AccountForm'; import { PromptEditor } from '@/components/prompts/PromptEditor'; @@ -8,43 +9,50 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import type { Account } from '@/types'; export function AccountList() { + const { accountId } = useParams<{ accountId?: string }>(); + const navigate = useNavigate(); const { accounts, create, update, remove } = useAccounts(); - const [editing, setEditing] = useState(null); const [creating, setCreating] = useState(false); const [subTab, setSubTab] = useState<'info' | 'prompts'>('info'); + const editing = accounts.find((a) => a.id === accountId) || null; return (
- +
-

账户列表

-
{accounts.map((a) => ( ))}
-
-
+
+
@@ -59,19 +67,19 @@ export function AccountList() { if (creating) { create(data).then(() => setCreating(false)); } else { - update(editing!.id, data).then(() => setEditing(null)); + update(editing!.id, data).then(() => {}); } }} onDelete={editing ? () => { if (confirm(`确定删除账户「${editing.name}」?`)) { - remove(editing.id).then(() => setEditing(null)); + remove(editing.id).then(() => navigate('/accounts')); } } : undefined} - onCancel={() => { setEditing(null); setCreating(false); }} + onCancel={() => { setCreating(false); }} />
) : ( -
+
选择一个账户或创建新账户
) diff --git a/web/client/src/components/assets/AssetGallery.tsx b/web/client/src/components/assets/AssetGallery.tsx index 07227a9..ce6aa66 100644 --- a/web/client/src/components/assets/AssetGallery.tsx +++ b/web/client/src/components/assets/AssetGallery.tsx @@ -26,11 +26,11 @@ export function AssetGallery() { return (
-
+
setTypeFilter(e.target.value)} - className="h-8 rounded-md border border-zinc-800 bg-zinc-900 px-2 text-xs" + className="h-8 rounded-md border border-zinc-200 bg-zinc-50 px-2 text-xs" > +
+
+
+ + {asset.type === 'image' ? '🖼' : '🎬'} #{asset.shot_index || '?'} + + + {asset.created_at ? new Date(asset.created_at).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }) : ''} + +
+
))}
diff --git a/web/client/src/components/chat/ChatInput.tsx b/web/client/src/components/chat/ChatInput.tsx index 42a3bb0..4056b3a 100644 --- a/web/client/src/components/chat/ChatInput.tsx +++ b/web/client/src/components/chat/ChatInput.tsx @@ -21,8 +21,8 @@ export function ChatInput({ onSend, disabled }: { onSend: (content: string) => v }; return ( -
-
+
+