feat(web): 前端收敛为「共享消息流 + 按消息 mode 渲染」,并补全 emoji→MD3 图标
架构变更(主人要求,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 替换为 <Icon name=... />;正文说明箭头保留。 npm run build 绿(tsc 0 错,79→71 模块,JS 284KB/gzip 86KB)。
This commit is contained in:
1 parent
265b207699
commit
4aab0f501a
28 files changed
+539
-950
No files matched your search
@@ -1,32 +1,37 @@
|
||||
import { useModeStore } from "../store/useModeStore";
|
||||
import { sendUp } from "../lib/ws";
|
||||
import { MODES, type Mode } from "../lib/types";
|
||||
import { Icon, type IconName } from "./Icon";
|
||||
|
||||
const modeMeta: Record<Mode, { label: string; icon: string }> = {
|
||||
chat: { label: "对话", icon: "💬" },
|
||||
code: { label: "代码", icon: "🖥" },
|
||||
home: { label: "家居", icon: "🏠" },
|
||||
work: { label: "工作", icon: "🗂" },
|
||||
docs: { label: "知识库", icon: "📚" },
|
||||
voice: { label: "语音", icon: "🎙" },
|
||||
vision: { label: "视觉", icon: "👁" },
|
||||
agents: { label: "子代理", icon: "🧩" },
|
||||
settings: { label: "设置", icon: "⚙" },
|
||||
const NAV_MODES: ReadonlyArray<Mode> = [
|
||||
"chat", "code", "home", "work", "docs", "voice", "vision", "agents",
|
||||
];
|
||||
|
||||
const modeMeta: Record<Mode, { label: string; icon: IconName }> = {
|
||||
chat: { label: "对话", icon: "chat" },
|
||||
code: { label: "代码", icon: "terminal" },
|
||||
home: { label: "家居", icon: "home" },
|
||||
work: { label: "工作", icon: "folder" },
|
||||
docs: { label: "知识库", icon: "docs" },
|
||||
voice: { label: "语音", icon: "mic" },
|
||||
vision: { label: "视觉", icon: "vision" },
|
||||
agents: { label: "子代理", icon: "agent" },
|
||||
settings: { label: "设置", icon: "settings" },
|
||||
};
|
||||
|
||||
/** Sets the input mode — the mode of the NEXT message (design/07 §1). In the
|
||||
* single-page design this no longer switches pages; it only shapes the next
|
||||
* bubble and notifies the backend for a mode-aware reply. */
|
||||
function useSwitchMode(): (m: Mode) => void {
|
||||
const setMode = useModeStore((s) => s.setMode);
|
||||
const setSelfMode = useModeStore((s) => s.setSelfMode);
|
||||
return (m: Mode) => {
|
||||
setMode(m);
|
||||
setSelfMode(null);
|
||||
sendUp({ type: "mode_switch", data: { mode: m } });
|
||||
};
|
||||
}
|
||||
|
||||
export function Sidebar(): React.ReactNode {
|
||||
const mode = useModeStore((s) => s.mode);
|
||||
const selfMode = useModeStore((s) => s.selfMode);
|
||||
const switchMode = useSwitchMode();
|
||||
|
||||
return (
|
||||
@@ -34,7 +39,7 @@ export function Sidebar(): React.ReactNode {
|
||||
<div className="px-2 py-2 text-xs font-semibold uppercase tracking-wider text-[var(--mu-text-lo)]">
|
||||
行为模式
|
||||
</div>
|
||||
{MODES.map((m) => {
|
||||
{NAV_MODES.map((m) => {
|
||||
const meta = modeMeta[m];
|
||||
const active = m === mode;
|
||||
return (
|
||||
@@ -47,13 +52,8 @@ export function Sidebar(): React.ReactNode {
|
||||
: "text-[var(--mu-text-mid)] hover:bg-[var(--mu-surface-4)]"
|
||||
}`}
|
||||
>
|
||||
<span className="text-base">{meta.icon}</span>
|
||||
<Icon name={meta.icon} size={18} />
|
||||
<span>{meta.label}</span>
|
||||
{m === selfMode ? (
|
||||
<span className="ml-auto rounded-full bg-[var(--mu-warn)]/15 px-1.5 py-0.5 text-[10px] text-[var(--mu-warn)]">
|
||||
自切
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -66,7 +66,7 @@ export function BottomNav(): React.ReactNode {
|
||||
const switchMode = useSwitchMode();
|
||||
return (
|
||||
<nav className="glass fixed inset-x-0 bottom-0 z-30 flex h-[var(--mu-bottomnav-h)] items-stretch border-t border-[var(--mu-glass-border)] md:hidden">
|
||||
{MODES.slice(0, 5).map((m) => {
|
||||
{NAV_MODES.slice(0, 5).map((m) => {
|
||||
const meta = modeMeta[m];
|
||||
const active = m === mode;
|
||||
return (
|
||||
@@ -77,7 +77,7 @@ export function BottomNav(): React.ReactNode {
|
||||
active ? "text-[var(--mu-accent-hi)]" : "text-[var(--mu-text-lo)]"
|
||||
}`}
|
||||
>
|
||||
<span className="text-base">{meta.icon}</span>
|
||||
<Icon name={meta.icon} size={18} />
|
||||
<span>{meta.label}</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user