import type { Contact } from '../types'; import { ArchiveIcon, ComposeIcon, ChevronRightIcon, GaugeIcon, PersonIcon, BotIcon } from './icons'; import PermissionChip from './PermissionChip'; /** * 工作卡片:中间栏的另一种呈现。 * * 与列表行(ContactPanel 的 ContactRow)的分工: * 列表答「跟谁在聊」,卡片答「在聊什么、进展如何」。 * 一条线索是一件正在进行的工作,卡片上要能直接看出: * - 主题(多由 Agent 平台的模型生成的摘要) * - 最新一封说了什么、谁说的 * - 往返预算还剩多少 —— 预算是任务的属性,快跑满的任务需要人介入 * * 容器(列表/滚动/空态)由 ContactPanel 负责:两种视图共用同一份数据与同一套 * 打开/写信/归档动作,只有单项的渲染不同。竖向堆叠而非网格 —— * 卡片在中间栏里,320~400px 放不下多列。 */ export function WorkCard({ contact: c, active, onOpen, onCompose, onArchive }: { contact: Contact; active: boolean; onOpen: () => void; onCompose: () => void; onArchive: () => void; }) { const time = new Date(c.last_activity).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); const fromHuman = c.last_from !== c.agent_name; return (
); } /** * 预算指示条。 * * 0 = 不限,此时不显示 —— 一个「0/0」或「不限」的徽标对每张卡片都成立, * 等于纯噪声。只在真正设了上限时才占位置。 * 剩 1 个来回时转红:那是需要人介入的时刻(要么加预算,要么让它收尾)。 * * 导出供测试单独渲染 —— 它是纯展示件,而通过 WorkCard 渲染要先造一整个 Contact。 */ export function BudgetChip({ max, used }: { max: number; used: number }) { if (!max || max <= 0) return null; const remaining = Math.max(max - used, 0); const tone = remaining === 0 ? 'bg-red-100 text-red-700' : remaining <= 1 ? 'bg-orange-100 text-orange-700' : 'bg-gray-100 text-gray-500'; return ( {remaining}/{max} ); }