From 4aab0f501af030b9decc641339c37e536f8a4ef9 Mon Sep 17 00:00:00 2001 From: Aska Date: Sun, 6 Sep 2026 13:32:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=89=8D=E7=AB=AF=E6=94=B6?= =?UTF-8?q?=E6=95=9B=E4=B8=BA=E3=80=8C=E5=85=B1=E4=BA=AB=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=B5=81=20+=20=E6=8C=89=E6=B6=88=E6=81=AF=20mode=20=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E3=80=8D=EF=BC=8C=E5=B9=B6=E8=A1=A5=E5=85=A8=20emoji?= =?UTF-8?q?=E2=86=92MD3=20=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 架构变更(主人要求,design/07 §1/§2 已改定稿):把「9 个行为模式 = 9 个独立页面」改为 「一个共享页面 + 每条消息绑 mode,消息框按对应 mode 渲染」。 - ChatMessage 增 mode?(types.ts);useAppStore addUserMessage/beginAssistant 带 mode; useSendChat.send(text, mode) / chatStream body 带 mode(通知后端按 mode 回复)。 - MessageBubble 按 message.mode 分发到 components/messages/ 各形态渲染器 (chat 气泡 / code 等宽代码块 / home 设备卡 / docs 文档卡 / work 任务卡 / vision 视觉卡)。 - ChatInput 顶部加「行为模式」chip 切换下一条消息的 mode(默认 chat);输入框上方显示当前模式。 - ModeRouter 收敛为只渲染 ChatShell;删除 9 个旧独立 mode 页面;Nav/TopBar 的 mode 改为 「输入模式」语义;settings 归管理面板(design/10)。 同时补全上一轮 emoji→MD3 图标(约 23 文件):新增 Icon.tsx(MD3 SVG、IconName、零依赖、 离线优先),全部界面 emoji 替换为 ;正文说明箭头保留。 npm run build 绿(tsc 0 错,79→71 模块,JS 284KB/gzip 86KB)。 --- frontend/web/src/admin/AdminApp.tsx | 13 +- frontend/web/src/admin/components.tsx | 3 +- frontend/web/src/admin/modules/Agents.tsx | 7 +- .../web/src/admin/modules/Automations.tsx | 3 +- frontend/web/src/admin/modules/Backthink.tsx | 3 +- frontend/web/src/admin/modules/Dashboard.tsx | 21 +- .../web/src/admin/modules/Observability.tsx | 9 +- frontend/web/src/admin/modules/System.tsx | 13 +- frontend/web/src/components/ChatInput.tsx | 39 +++- frontend/web/src/components/Icon.tsx | 152 ++++++++++++++ frontend/web/src/components/Nav.tsx | 44 ++-- frontend/web/src/components/TopBar.tsx | 23 ++- frontend/web/src/components/chat.tsx | 82 ++++++-- .../web/src/components/messages/index.tsx | 158 +++++++++++++++ frontend/web/src/lib/api.ts | 5 +- frontend/web/src/lib/types.ts | 3 + frontend/web/src/lib/useChat.ts | 18 +- frontend/web/src/modes/AgentsMode.tsx | 144 ------------- .../src/modes/{ChatMode.tsx => ChatShell.tsx} | 21 +- frontend/web/src/modes/CodeMode.tsx | 74 ------- frontend/web/src/modes/DocsMode.tsx | 61 ------ frontend/web/src/modes/HomeMode.tsx | 93 --------- frontend/web/src/modes/ModeRouter.tsx | 44 +--- frontend/web/src/modes/SettingsMode.tsx | 190 ------------------ frontend/web/src/modes/VisionMode.tsx | 82 -------- frontend/web/src/modes/VoiceMode.tsx | 72 ------- frontend/web/src/modes/WorkMode.tsx | 101 ---------- frontend/web/src/store/useAppStore.ts | 11 +- 28 files changed, 539 insertions(+), 950 deletions(-) create mode 100644 frontend/web/src/components/Icon.tsx create mode 100644 frontend/web/src/components/messages/index.tsx delete mode 100644 frontend/web/src/modes/AgentsMode.tsx rename frontend/web/src/modes/{ChatMode.tsx => ChatShell.tsx} (62%) delete mode 100644 frontend/web/src/modes/CodeMode.tsx delete mode 100644 frontend/web/src/modes/DocsMode.tsx delete mode 100644 frontend/web/src/modes/HomeMode.tsx delete mode 100644 frontend/web/src/modes/SettingsMode.tsx delete mode 100644 frontend/web/src/modes/VisionMode.tsx delete mode 100644 frontend/web/src/modes/VoiceMode.tsx delete mode 100644 frontend/web/src/modes/WorkMode.tsx diff --git a/frontend/web/src/admin/AdminApp.tsx b/frontend/web/src/admin/AdminApp.tsx index 91eff12..7f486e9 100644 --- a/frontend/web/src/admin/AdminApp.tsx +++ b/frontend/web/src/admin/AdminApp.tsx @@ -4,6 +4,7 @@ import { getAuthToken, getStoredRole } from "../lib/api"; import { ADMIN_GROUPS, ADMIN_SECTIONS } from "./sections"; import { navigateAdmin, type AdminSectionId } from "./router"; import { Button, Tag } from "../components/primitives"; +import { Icon } from "../components/Icon"; import { DashboardModule } from "./modules/Dashboard"; import { PersonasModule } from "./modules/Personas"; import { SessionsModule } from "./modules/Sessions"; @@ -57,7 +58,7 @@ function AdminSidebar({ active }: { active: AdminSectionId }): React.ReactNode { : "text-[var(--mu-text-mid)] hover:bg-[var(--mu-surface-4)]" }`} > - {s.icon} + {s.label} ))} @@ -93,7 +94,7 @@ function AdminTopBar({ section }: { section: AdminSectionId }): React.ReactNode aria-label="切换主题" className="inline-flex h-8 w-8 items-center justify-center rounded-[var(--mu-radius)] bg-[var(--mu-surface-4)] text-sm transition-all duration-200 ease-[var(--mu-spring)] hover:bg-[var(--mu-surface-5)] active:scale-95" > - {theme === "dark" ? "☀" : "☾"} + ))} diff --git a/frontend/web/src/admin/modules/Observability.tsx b/frontend/web/src/admin/modules/Observability.tsx index 549019f..f136a2e 100644 --- a/frontend/web/src/admin/modules/Observability.tsx +++ b/frontend/web/src/admin/modules/Observability.tsx @@ -3,6 +3,7 @@ import { useAppStore } from "../../store/useAppStore"; import { useAuditStore } from "../audit"; import { CardGrid, Notice, PageHeader, StatCard } from "../components"; import { Button, Surface, Tag } from "../../components/primitives"; +import { Icon } from "../../components/Icon"; type Tab = "logs" | "trace" | "metrics" | "audit"; @@ -84,10 +85,10 @@ function MetricsView(): React.ReactNode { return (
- - - - + } label="请求 QPS" value="—" sub="网关指标占位" tone="accent" /> + } label="LLM 调用次数" value="—" sub="用量/费用占位" tone="neutral" /> + } label="子代理事件" value="—" sub="编排指标占位" tone="ok" /> + } label="工时/Token" value="—" sub="成本占位" tone="warn" /> 指标时序图 / 导出需后端 M10+ 提供。
diff --git a/frontend/web/src/admin/modules/System.tsx b/frontend/web/src/admin/modules/System.tsx index 1a5e9b5..08b6d5e 100644 --- a/frontend/web/src/admin/modules/System.tsx +++ b/frontend/web/src/admin/modules/System.tsx @@ -3,6 +3,7 @@ import { getStoredRole } from "../../lib/api"; import { useAuditStore } from "../audit"; import { KeyValueRow, Notice, PageHeader } from "../components"; import { Button, Surface, Tag } from "../../components/primitives"; +import { Icon } from "../../components/Icon"; export function SystemModule(): React.ReactNode { const isOwner = getStoredRole() === "owner"; @@ -24,7 +25,7 @@ export function SystemModule(): React.ReactNode {
- + } title="安全 & 密钥" ownerOnly /> @@ -35,7 +36,7 @@ export function SystemModule(): React.ReactNode { - + } title="存储" /> @@ -46,7 +47,7 @@ export function SystemModule(): React.ReactNode { - + } title="环境变量" /> @@ -56,7 +57,7 @@ export function SystemModule(): React.ReactNode { - + } title="运行时" />
日志级别 @@ -85,10 +86,10 @@ export function SystemModule(): React.ReactNode { ); } -function SectionTitle({ icon, title, ownerOnly = false }: { icon: string; title: string; ownerOnly?: boolean }): React.ReactNode { +function SectionTitle({ icon, title, ownerOnly = false }: { icon: React.ReactNode; title: string; ownerOnly?: boolean }): React.ReactNode { return (

- {icon} + {icon} {title} {ownerOnly ? owner : null}

diff --git a/frontend/web/src/components/ChatInput.tsx b/frontend/web/src/components/ChatInput.tsx index 63683b6..b395af9 100644 --- a/frontend/web/src/components/ChatInput.tsx +++ b/frontend/web/src/components/ChatInput.tsx @@ -1,13 +1,25 @@ import { useEffect, useRef, useState } from "react"; import { Button } from "./primitives"; +import { Icon, type IconName } from "./Icon"; +import { useModeStore } from "../store/useModeStore"; +import type { Mode } from "../lib/types"; export interface ChatInputProps { - onSend: (text: string) => void; + onSend: (text: string, mode: Mode) => void; sending: boolean; onVoice: () => void; placeholder?: string; } +const INPUT_MODES: ReadonlyArray<{ mode: Mode; label: string; icon: IconName }> = [ + { mode: "chat", label: "对话", icon: "chat" }, + { mode: "code", label: "代码", icon: "terminal" }, + { mode: "home", label: "家居", icon: "home" }, + { mode: "work", label: "工作", icon: "folder" }, + { mode: "docs", label: "知识库", icon: "docs" }, + { mode: "vision", label: "视觉", icon: "vision" }, +]; + export function ChatInput({ onSend, sending, @@ -16,6 +28,8 @@ export function ChatInput({ }: ChatInputProps): React.ReactNode { const [value, setValue] = useState(""); const taRef = useRef(null); + const mode = useModeStore((s) => s.mode); + const setMode = useModeStore((s) => s.setMode); // auto-grow the textarea up to a max height useEffect(() => { @@ -28,12 +42,31 @@ export function ChatInput({ const submit = () => { const t = value.trim(); if (!t || sending) return; - onSend(t); + onSend(t, mode); setValue(""); }; return (
+ {/* input-mode switcher: sets the mode for the NEXT message (design/07 §1). */} +
+ 行为模式 + {INPUT_MODES.map(({ mode: m, label, icon }) => ( + + ))} +
+