feat(frontend): M9 模态 UI(mode 系统+共享 store+WS 客户端 + chat(思维链/工具回显/情绪徽标)/code/Agent 监控/后台思考页 + Miuix 设计 token + PWA);tsc+vite build 通过

This commit is contained in:
Aska committed 2026-09-06 07:44:40 +08:00
1 parent b3ffd51736
commit 4c48e94659
40 files changed
+5321

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
import { useModeStore } from "../store/useModeStore";
import { sendUp } from "../lib/ws";
import { MODES, type Mode } from "../lib/types";
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: "⚙" },
};
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 (
<nav className="glass hidden h-full w-[var(--mu-sidebar-w)] shrink-0 flex-col gap-1 border-r border-[var(--mu-glass-border)] p-3 md:flex">
<div className="px-2 py-2 text-xs font-semibold uppercase tracking-wider text-[var(--mu-text-lo)]">
行为模式
</div>
{MODES.map((m) => {
const meta = modeMeta[m];
const active = m === mode;
return (
<button
key={m}
onClick={() => switchMode(m)}
className={`flex items-center gap-2.5 rounded-[var(--mu-radius)] px-3 py-2 text-sm transition-all duration-200 ease-[var(--mu-spring)] active:scale-[0.98] ${
active
? "bg-[var(--mu-accent)]/15 font-medium text-[var(--mu-accent-hi)]"
: "text-[var(--mu-text-mid)] hover:bg-[var(--mu-surface-4)]"
}`}
>
<span className="text-base">{meta.icon}</span>
<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>
);
})}
</nav>
);
}
export function BottomNav(): React.ReactNode {
const mode = useModeStore((s) => s.mode);
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) => {
const meta = modeMeta[m];
const active = m === mode;
return (
<button
key={m}
onClick={() => switchMode(m)}
className={`flex flex-1 flex-col items-center justify-center gap-0.5 text-[11px] transition-colors duration-200 ${
active ? "text-[var(--mu-accent-hi)]" : "text-[var(--mu-text-lo)]"
}`}
>
<span className="text-base">{meta.icon}</span>
<span>{meta.label}</span>
</button>
);
})}
</nav>
);
}