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
@@ -5,6 +5,7 @@ import { Notice, PageHeader } from "../components";
|
||||
import { Button, EmptyState, Surface, Tag, type TagTone } from "../../components/primitives";
|
||||
import { sendUp } from "../../lib/ws";
|
||||
import { useAuditStore } from "../audit";
|
||||
import { Icon } from "../../components/Icon";
|
||||
|
||||
const statusTone: Record<AgentStatus, { label: string; tone: TagTone }> = {
|
||||
idle: { label: "空闲", tone: "neutral" },
|
||||
@@ -33,7 +34,9 @@ function AgentCard({ agent, onKill }: { agent: AgentInfo; onKill: (id: string) =
|
||||
<pre className="max-h-24 overflow-auto whitespace-pre-wrap rounded-[var(--mu-radius-sm)] bg-[var(--mu-surface-2)] px-2 py-1.5 text-xs text-[var(--mu-text-mid)]">{agent.thinking}</pre>
|
||||
) : null}
|
||||
{agent.tools?.map((t, i) => (
|
||||
<div key={i} className="text-xs text-[var(--mu-text-lo)]">⚙ {t.name}{t.ok === false ? " ✗" : ""}</div>
|
||||
<div key={i} className="inline-flex items-center gap-1 text-xs text-[var(--mu-text-lo)]">
|
||||
<Icon name="gear" size={12} />{t.name}{t.ok === false ? <Icon name="close" size={12} className="ml-0.5" /> : ""}
|
||||
</div>
|
||||
))}
|
||||
{agent.message ? <div className="text-xs text-[var(--mu-text-mid)]">{agent.message}</div> : null}
|
||||
<div className="flex gap-2">
|
||||
@@ -96,7 +99,7 @@ export function AgentsModule(): React.ReactNode {
|
||||
)}
|
||||
|
||||
{list.length === 0 ? (
|
||||
<EmptyState icon="🧩" title={tab === "main" ? "无主会话 Agent" : "暂无子代理"} />
|
||||
<EmptyState icon={<Icon name="agent" size={32} />} title={tab === "main" ? "无主会话 Agent" : "暂无子代理"} />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{list.map((a) => (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { adminListAutomations, isNotImplemented } from "../../lib/api";
|
||||
import { useAuditStore } from "../audit";
|
||||
import { Notice, PageHeader } from "../components";
|
||||
import { Button, Surface, Switch, Tag } from "../../components/primitives";
|
||||
import { Icon } from "../../components/Icon";
|
||||
|
||||
interface ReminderRow {
|
||||
id: string;
|
||||
@@ -67,7 +68,7 @@ export function AutomationsModule(): React.ReactNode {
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
{["消息队列事件", "后台任务(含终端任务)", "运行日志"].map((x) => (
|
||||
<div key={x} className="rounded-[var(--mu-radius)] border border-[var(--mu-glass-border)] bg-[var(--mu-surface-3)]/60 p-4 text-center text-xs text-[var(--mu-text-mid)]">
|
||||
<div className="mb-2 text-lg">📦</div>
|
||||
<div className="mb-2 flex justify-center"><Icon name="package" size={20} /></div>
|
||||
{x} 占位 —— 后端 M10+ 提供
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useAuditStore } from "../audit";
|
||||
import { Notice, PageHeader } from "../components";
|
||||
import { Button, EmptyState, Surface, Tag } from "../../components/primitives";
|
||||
import type { BackthinkItem } from "../../lib/types";
|
||||
import { Icon } from "../../components/Icon";
|
||||
|
||||
function BackthinkRow({ item }: { item: BackthinkItem }): React.ReactNode {
|
||||
const markPushed = useAppStore((s) => s.markBackthinkPushed);
|
||||
@@ -78,7 +79,7 @@ export function BackthinkModule(): React.ReactNode {
|
||||
</div>
|
||||
|
||||
{items.length === 0 ? (
|
||||
<EmptyState icon="🌙" title="暂无后台思考" hint="后台任务完成时按 WS 事件实时呈现(backthink / thinking)。" />
|
||||
<EmptyState icon={<Icon name="night" size={32} />} title="暂无后台思考" hint="后台任务完成时按 WS 事件实时呈现(backthink / thinking)。" />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{items.map((b) => (
|
||||
|
||||
@@ -11,12 +11,13 @@ import { useAppStore } from "../../store/useAppStore";
|
||||
import { navigateAdmin, type AdminSectionId } from "../router";
|
||||
import { CardGrid, StatCard } from "../components";
|
||||
import { Surface, Tag } from "../../components/primitives";
|
||||
import { Icon, type IconName } from "../../components/Icon";
|
||||
import { useAutoReload } from "../hooks";
|
||||
|
||||
interface DashCard {
|
||||
id: AdminSectionId;
|
||||
label: string;
|
||||
icon: string;
|
||||
icon: IconName;
|
||||
value: number | string;
|
||||
sub?: string;
|
||||
tone: "ok" | "warn" | "accent" | "neutral" | "err";
|
||||
@@ -52,16 +53,16 @@ export function DashboardModule(): React.ReactNode {
|
||||
const runningAgents = agents.filter((a) => a.status === "running").length;
|
||||
|
||||
const cards: DashCard[] = [
|
||||
{ id: "personas", label: "人格", icon: "🧬", value: nPersonas, sub: "启停/默认/路由", tone: "accent" },
|
||||
{ id: "sessions", label: "活跃会话", icon: "💬", value: nSessions, sub: "渠道 × actor", tone: "ok" },
|
||||
{ id: "memories", label: "记忆", icon: "🧠", value: nMemories, sub: "三类作用域隔离", tone: "accent" },
|
||||
{ id: "plugins", label: "插件与能力", icon: "🔌", value: nPlugins, sub: "含 MCP/渠道", tone: "neutral" },
|
||||
{ id: "channels", label: "渠道", icon: "📡", value: `${nChannels - offlineChannels}/${nChannels}`,
|
||||
{ id: "personas", label: "人格", icon: "dna", value: nPersonas, sub: "启停/默认/路由", tone: "accent" },
|
||||
{ id: "sessions", label: "活跃会话", icon: "chat", value: nSessions, sub: "渠道 × actor", tone: "ok" },
|
||||
{ id: "memories", label: "记忆", icon: "memory", value: nMemories, sub: "三类作用域隔离", tone: "accent" },
|
||||
{ id: "plugins", label: "插件与能力", icon: "plugin", value: nPlugins, sub: "含 MCP/渠道", tone: "neutral" },
|
||||
{ id: "channels", label: "渠道", icon: "channel", value: `${nChannels - offlineChannels}/${nChannels}`,
|
||||
sub: offlineChannels > 0 ? `${offlineChannels} 离线` : "全部在线", tone: offlineChannels > 0 ? "warn" : "ok" },
|
||||
{ id: "iot", label: "IoT 设备", icon: "📲", value: `${onlineDevices}/${nDevices}`,
|
||||
{ id: "iot", label: "IoT 设备", icon: "device", value: `${onlineDevices}/${nDevices}`,
|
||||
sub: onlineDevices === nDevices && nDevices > 0 ? "全部在线" : "设备状态", tone: nDevices > 0 && onlineDevices === nDevices ? "ok" : "neutral" },
|
||||
{ id: "agents", label: "Agent 运行", icon: "🧩", value: runningAgents, sub: `${agents.length} 个已登记`, tone: runningAgents > 0 ? "accent" : "neutral" },
|
||||
{ id: "backthink", label: "后台思考活跃度", icon: "🌙", value: backthink.length, sub: "实时 (WS)", tone: backthink.length > 0 ? "warn" : "neutral" },
|
||||
{ id: "agents", label: "Agent 运行", icon: "agent", value: runningAgents, sub: `${agents.length} 个已登记`, tone: runningAgents > 0 ? "accent" : "neutral" },
|
||||
{ id: "backthink", label: "后台思考活跃度", icon: "night", value: backthink.length, sub: "实时 (WS)", tone: backthink.length > 0 ? "warn" : "neutral" },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -82,7 +83,7 @@ export function DashboardModule(): React.ReactNode {
|
||||
<CardGrid>
|
||||
{cards.map((c) => (
|
||||
<button key={c.id} onClick={() => navigateAdmin(c.id)} className="text-left" title="点击下钻">
|
||||
<StatCard icon={c.icon} label={c.label} value={c.value} sub={c.sub} tone={c.tone} />
|
||||
<StatCard icon={<Icon name={c.icon} size={18} />} label={c.label} value={c.value} sub={c.sub} tone={c.tone} />
|
||||
</button>
|
||||
))}
|
||||
</CardGrid>
|
||||
|
||||
@@ -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 (
|
||||
<div className="space-y-4">
|
||||
<CardGrid>
|
||||
<StatCard icon="⚙" label="请求 QPS" value="—" sub="网关指标占位" tone="accent" />
|
||||
<StatCard icon="🤖" label="LLM 调用次数" value="—" sub="用量/费用占位" tone="neutral" />
|
||||
<StatCard icon="🧩" label="子代理事件" value="—" sub="编排指标占位" tone="ok" />
|
||||
<StatCard icon="🧠" label="工时/Token" value="—" sub="成本占位" tone="warn" />
|
||||
<StatCard icon={<Icon name="gear" size={18} />} label="请求 QPS" value="—" sub="网关指标占位" tone="accent" />
|
||||
<StatCard icon={<Icon name="robot" size={18} />} label="LLM 调用次数" value="—" sub="用量/费用占位" tone="neutral" />
|
||||
<StatCard icon={<Icon name="agent" size={18} />} label="子代理事件" value="—" sub="编排指标占位" tone="ok" />
|
||||
<StatCard icon={<Icon name="memory" size={18} />} label="工时/Token" value="—" sub="成本占位" tone="warn" />
|
||||
</CardGrid>
|
||||
<Notice>指标时序图 / 导出需后端 M10+ 提供。</Notice>
|
||||
</div>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<Surface className="space-y-2">
|
||||
<SectionTitle icon="🔐" title="安全 & 密钥" ownerOnly />
|
||||
<SectionTitle icon={<Icon name="shield" size={18} />} title="安全 & 密钥" ownerOnly />
|
||||
<KeyValueRow label="JWT 密钥" value="••••••••" mono />
|
||||
<KeyValueRow label="鉴权" value="JWT + RBAC + API key" />
|
||||
<KeyValueRow label="审计" value="who/what/when/result" />
|
||||
@@ -35,7 +36,7 @@ export function SystemModule(): React.ReactNode {
|
||||
</Surface>
|
||||
|
||||
<Surface className="space-y-2">
|
||||
<SectionTitle icon="🗃" title="存储" />
|
||||
<SectionTitle icon={<Icon name="storage" size={18} />} title="存储" />
|
||||
<KeyValueRow label="默认" value="SQLite" />
|
||||
<KeyValueRow label="可选增强" value="PG / pgvector(语义/向量路径)" />
|
||||
<KeyValueRow label="单二进制 / 单端口" value=":8080 · 零跨进程 IPC" />
|
||||
@@ -46,7 +47,7 @@ export function SystemModule(): React.ReactNode {
|
||||
</Surface>
|
||||
|
||||
<Surface className="space-y-2">
|
||||
<SectionTitle icon="📦" title="环境变量" />
|
||||
<SectionTitle icon={<Icon name="package" size={18} />} title="环境变量" />
|
||||
<KeyValueRow label="SERVER_HOST" value="0.0.0.0" mono />
|
||||
<KeyValueRow label="SERVER_PORT" value="8080" mono />
|
||||
<KeyValueRow label="AUTH_ADMIN_USER/PASS" value="••••••••" mono />
|
||||
@@ -56,7 +57,7 @@ export function SystemModule(): React.ReactNode {
|
||||
</Surface>
|
||||
|
||||
<Surface className="space-y-2">
|
||||
<SectionTitle icon="🔄" title="运行时" />
|
||||
<SectionTitle icon={<Icon name="refresh" size={18} />} title="运行时" />
|
||||
<div className="flex items-center justify-between py-1.5">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">日志级别</span>
|
||||
<span className="flex gap-1.5">
|
||||
@@ -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 (
|
||||
<h3 className="flex items-center gap-2 text-sm font-semibold">
|
||||
<span>{icon}</span>
|
||||
<span className="text-[var(--mu-accent)]">{icon}</span>
|
||||
<span>{title}</span>
|
||||
{ownerOnly ? <Tag tone="warn">owner</Tag> : null}
|
||||
</h3>
|
||||
|
||||
Reference in new issue
Block a user