import type { ChatMessage, PersonaRecord } from "../../lib/types"; import { useAppStore } from "../../store/useAppStore"; import { useCurrentPersona } from "../../store/personaSelectors"; import { resolveMessagePersona } from "../../lib/persona"; import { DeliveryChip, TurnTag } from "../messages/TurnMeta"; /** Small muted source label for a persisted message (design/16 §4.5): shows the * concrete client the message came from, e.g. "来自 web:chrome:a1b2c3". Rendered * only when a deviceId is present, so legacy/unlabelled messages stay clean. The * Web client defaults to this label; other clients decide themselves. */ export function SourceLabel({ deviceId, role }: { deviceId?: string; role?: string }): React.ReactNode { if (!deviceId) return null; // A user message is received FROM a client; an assistant reply goes TO the // client that prompted it. Semantic direction differs — "来自" vs "发送至". const dir = role === "assistant" ? "发送至" : "来自"; return (
{dir} {deviceId}
); } /** Small sender label for a third-party message (design/16 §4.6): shows the * sender's display name, e.g. "第三方 · 张三". Blank for the owner's private chat * (no senderName), so owner bubbles stay unlabelled. */ export function SenderLabel({ name }: { name?: string }): React.ReactNode { if (!name) return null; return (
第三方 · {name}
); } /** The bubble-side binding of `lib/persona.ts#resolveMessagePersona` (design/22 * §7-5): which role spoke this message, and whether it differs from the role the * session speaks as now. */ export function useSpokenPersona(personaId?: string): { persona: PersonaRecord | null; switched: boolean; } { const personas = useAppStore((s) => s.personas); const active = useCurrentPersona(); return resolveMessagePersona(personaId, personas, active); } /** Muted "角色 · X" line for a bubble whose authoring role differs from the role * the session speaks as now. */ export function PersonaLabel({ name }: { name?: string }): React.ReactNode { if (!name) return null; return (
角色 · {name}
); } /** 用户气泡下面那行小字:回合小标 + 投递态角标 + 来源。两个用户气泡(单块 / 分段) * 共用一处,避免「四份渲染各写一遍、日后漂移」。 */ export function UserMeta({ message }: { message: ChatMessage }): React.ReactNode { return (
); }