import { useEffect, useRef, useState } from "react"; import { useAppStore } from "../store/useAppStore"; import { Tag, type TagTone } from "./primitives"; import { Icon, type IconName } from "./Icon"; import type { AwakenState } from "../lib/types"; /** * Miuix 风格的状态呈现组件(用户端):唤醒式能力三态 + 会话编排 busy/pending。 * 全部从 useAppStore 的实时状态派生,纯展示、无副作用;未收到对应 WS 事件时 * 保持默认值(静息 / 不忙),UI 不报错。design/14 §5 + 15 §5 + 07 §3. */ /** 三态排序权重:静息 → 唤醒 → 全能(用于判断「唤醒上升」)。 */ const AWAKEN_RANK: Record = { quiet: 0, awake: 1, full: 2 }; const AWAKEN_ORDER: AwakenState[] = ["quiet", "awake", "full"]; const AWAKEN_META: Record< AwakenState, { label: string; icon: IconName; tone: TagTone; desc: string } > = { quiet: { label: "静息", icon: "moon", tone: "neutral", desc: "温柔陪伴" }, awake: { label: "唤醒", icon: "light", tone: "warn", desc: "办事中" }, full: { label: "全能", icon: "gear", tone: "accent", desc: "复杂任务" }, }; /** 顶部/状态栏的轻量状态徽标:显示当前唤醒态 + 最近点亮工具;唤醒上升时短暂脉动。 */ export function AwakenBadge(): React.ReactNode { const state = useAppStore((s) => s.awakenState); const from = useAppStore((s) => s.awakenFrom); const tool = useAppStore((s) => s.awakenTool); const [pulse, setPulse] = useState(false); const prevRef = useRef(state); // 唤醒上升(从较低态升到较高态)时短暂发光一下,提示「她认真起来了」。 useEffect(() => { const prev = prevRef.current; if (from !== null && AWAKEN_RANK[state] > AWAKEN_RANK[prev]) { setPulse(true); const t = window.setTimeout(() => setPulse(false), 1200); prevRef.current = state; return () => window.clearTimeout(t); } prevRef.current = state; }, [state, from]); const meta = AWAKEN_META[state]; return ( {meta.label} {tool ? · {tool} : null} ); } /** 聊天流里的「唤醒路径」节点序列(思维导图轻量版):静息 → 唤醒 → 全能,点亮到当前态; * 本轮点亮的工具以 ok 标签展示。仅在活跃(正在流式)消息气泡上渲染。 */ export function WakePath({ active = false, }: { active?: boolean; }): React.ReactNode | null { const state = useAppStore((s) => s.awakenState); const from = useAppStore((s) => s.awakenFrom); const tool = useAppStore((s) => s.awakenTool); if (!active) return null; const currentIdx = AWAKEN_RANK[state]; const ascended = from !== null && AWAKEN_RANK[state] > AWAKEN_RANK[from]; return (
唤醒路径
{AWAKEN_ORDER.map((key, i) => { const on = AWAKEN_RANK[key] <= currentIdx; const isCurrent = key === state; return ( {i > 0 ? ( ) : null} {AWAKEN_META[key].label} ); })}
{ascended ? ( 她认真起来了 ) : null} {tool ? ( 点亮 · {tool} ) : null}
); } /** 会话编排 busy/pending 轻状态提醒(design/15 §5)。她忙时显示在聊天区顶部; * **不阻塞输入框**(输入框可继续打字),挂起消息数可见。 */ export function SessionStatusHint(): React.ReactNode | null { const busy = useAppStore((s) => s.sessionBusy); const pendingCount = useAppStore((s) => s.pendingCount); if (!busy) return null; return (
她正在忙,有新消息稍后回 {pendingCount > 0 ? {pendingCount} 条待回 : null}
); }