/** * 工作台后四要素的共用小件 —— 自 `~/coding/JCAI_PRD/workbench/_components/ModuleWorkspace.tsx` * 第 21–160 行平移。**DOM 与 `jc-*` 类名原样保留**(README 平移规则 1/2/4)。 * * 四要素同款三栏布局:左清单 `task-sidebar` / 中文档卡 `execution-panel` / 右会话 `session-chat`。 */ import type { ReactNode } from 'react'; import { Ico } from '@web-shared/Ico.tsx'; import ScrollArea from '@web-shared/ScrollArea.tsx'; /** 首字头像:名字 hash 取渐变色;外文名取首字母大写 */ const AVA_GRAD: ReadonlyArray = [ ['#2bc7d4', '#0e9fd8'], ['#3370ff', '#245bdb'], ['#7f3bf5', '#5b21cf'], ['#ff8800', '#de7802'], ['#0aa294', '#0a6a64'], ['#f14ba9', '#c92d86'], ]; function avaOf(name: string): { ch: string; grad: readonly [string, string] } { let h = 0; for (const c of name) h = (h * 31 + c.charCodeAt(0)) >>> 0; const first = name[0] ?? '·'; const ch = /[A-Za-z]/.test(first) ? first.toUpperCase() : first; return { ch, grad: AVA_GRAD[h % AVA_GRAD.length]! }; } export function Avatar({ name, size = 30 }: { name: string; size?: number }) { const { ch, grad } = avaOf(name); return ( {ch} ); } export function AvatarStack({ names, size = 26 }: { names: readonly string[]; size?: number }) { const show = names.slice(0, 5); return ( {show.map((n, i) => ( ))} {names.length > show.length && +{names.length - show.length}} ); } export type Tone = 'todo' | 'doing' | 'done' | 'warn' | 'brand' | 'ok'; export function Chip({ tone, label, pulse }: { tone: Tone; label: string; pulse?: boolean }) { return ( {pulse && } {label} ); } /** 文档头(渐变图标块 + 标题 + 元信息 + 右上状态章)—— 统一容器,彻底告别贴边 */ export function Hero({ grad, icon, kicker, title, meta, chip, }: { grad: readonly [string, string]; icon: string; kicker?: string; title: string; meta: ReactNode; chip?: ReactNode; }) { return (
{kicker && {kicker}}

{title}

{meta}
{chip &&
{chip}
}
); } export function Section({ icon, title, extra, children, }: { icon: string; title: string; extra?: ReactNode; children: ReactNode; }) { return (
{title} {extra && {extra}}
{children}
); } /** 侧栏表头 —— 四要素共用 */ export function SidebarHead({ icon, title, sub }: { icon: string; title: string; sub: string }) { return (

{title}

{sub}
); } export function ListEmpty({ icon, children }: { icon: string; children: ReactNode }) { return (
{children}
); } /** 中栏文档卡的滚动容器 */ export function DocPanel({ scrollKey, children }: { scrollKey: string; children: ReactNode }) { return (
{children}
); } /** * 说明带 —— 原型里用来点明规范口径的那条浅色横条。 * 保留它不是为了好看:这些句子是「为什么这么设计」的现场注解。 */ export function NoteBand({ children }: { children: ReactNode }) { return (
{children}
); }