Archived
M6(1/n): 工作台任务要素 —— 原型 1:1 平移的第一屏
样式整文件搬(README 平移规则 1):app.css 2767 行 + proto.vendor.css + theme.css + console.css 原样搬,设计令牌与全部 jc-* / g-* 类名不动。 前端(按 §9 重排,渲染输出 1:1): - shared/:Ico / ScrollArea / useChatWidth 自原型搬,去掉 "use client" 等 Next 专有件; 失效的 react-hooks disable 指令按 §17 清掉(不改渲染) - shared/typed-fetch.ts:服务端交互唯一通道,路由契约来自 @contracts/api; 不引 Hono RPC 客户端(那要求前端 import 后端类型,违反 §5.1) - app/query.ts:TanStack Query;缓存失效由订阅层驱动,组件不轮询 - features/desk-task:任务列表(一条 = 一个活动的一次运行,不是一整条工作流)/ 标题区 + 工作区四页签 / 对话区(气泡二元制 + 卡族 + **composer 锁**) - @ui:ViewContainer 用**沙箱 iframe(sandbox 不含 allow-scripts)** —— 结构上无脚本执行权, 不依赖净化算法的完备性;ViewBlocks 渲染十一种视图块 feature 不得自行 dangerouslySetInnerHTML,eslint 有规则守着 后端:工作台路由 /api/desk/*(任务清单 / 任务详情 / 工单回传 / 自由输入 / 消息); 回传与后端校验**同 schema**;任务详情是服务端投影 —— 前端零过程状态,刷新即重建 已验证(真机):任务卡「A1 RFQ需求分析 · 序位 1/7 · awaiting-event」、 预算 2/40 步、1 张工单(LLM 步失败后转人兜底那张)、7 行带归侧标签的任务日志 358 个测试通过,门禁五道全绿;web 构建通过 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
co-authored by
Claude Opus 5
parent
3df8becf67
commit
3cfaa04098
@@ -0,0 +1,112 @@
|
||||
// 共享内联 lucide 图标(会话面板 / 工作区驾驶舱 / 设置等共用)。
|
||||
export function Ico({ name, s = 18 }: { name: string; s?: number }) {
|
||||
const p: Record<string, React.ReactNode> = {
|
||||
"chevron-right": (<path d="m9 18 6-6-6-6" />),
|
||||
"chevron-down": (<path d="m6 9 6 6 6-6" />),
|
||||
check: (<path d="M20 6 9 17l-5-5" />),
|
||||
"circle-check": (<><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></>),
|
||||
star: (<><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></>),
|
||||
sparkles: (<><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .962 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.582a.5.5 0 0 1 0 .962L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.962 0z" /><path d="M20 3v4" /><path d="M22 5h-4" /><path d="M4 17v2" /><path d="M5 18H3" /></>),
|
||||
terminal: (<><polyline points="4 17 10 11 4 5" /><line x1="12" x2="20" y1="19" y2="19" /></>),
|
||||
clock: (<><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></>),
|
||||
info: (<><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></>),
|
||||
"clipboard-check": (<><rect width="8" height="4" x="8" y="2" rx="1" ry="1" /><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" /><path d="m9 14 2 2 4-4" /></>),
|
||||
target: (<><circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" /></>),
|
||||
"list-checks": (<><path d="m3 17 2 2 4-4" /><path d="m3 7 2 2 4-4" /><path d="M13 6h8" /><path d="M13 12h8" /><path d="M13 18h8" /></>),
|
||||
plus: (<><path d="M5 12h14" /><path d="M12 5v14" /></>),
|
||||
"panel-left-close": (<><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M9 3v18" /><path d="m16 15-3-3 3-3" /></>),
|
||||
"sliders-horizontal": (<><line x1="21" x2="14" y1="4" y2="4" /><line x1="10" x2="3" y1="4" y2="4" /><line x1="21" x2="12" y1="12" y2="12" /><line x1="8" x2="3" y1="12" y2="12" /><line x1="21" x2="16" y1="20" y2="20" /><line x1="12" x2="3" y1="20" y2="20" /><line x1="14" x2="14" y1="2" y2="6" /><line x1="8" x2="8" y1="10" y2="14" /><line x1="16" x2="16" y1="18" y2="22" /></>),
|
||||
filter: (<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />),
|
||||
"arrow-left": (<><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></>),
|
||||
"file-spreadsheet": (<><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><path d="M8 13h2" /><path d="M14 13h2" /><path d="M8 17h2" /><path d="M14 17h2" /></>),
|
||||
"file-type": (<><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><path d="M9 13v-1h6v1" /><path d="M11 18h2" /><path d="M12 12v6" /></>),
|
||||
wrench: (<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />),
|
||||
"git-branch": (<><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></>),
|
||||
search: (<><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></>),
|
||||
bell: (<><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></>),
|
||||
settings: (<><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></>),
|
||||
user: (<><circle cx="12" cy="12" r="10" /><circle cx="12" cy="10" r="3" /><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" /></>),
|
||||
"user-round": (<><circle cx="12" cy="8" r="5" /><path d="M20 21a8 8 0 0 0-16 0" /></>),
|
||||
bot: (<><path d="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" /></>),
|
||||
dashboard: (<><rect width="7" height="9" x="3" y="3" rx="1" /><rect width="7" height="5" x="14" y="3" rx="1" /><rect width="7" height="9" x="14" y="12" rx="1" /><rect width="7" height="5" x="3" y="16" rx="1" /></>),
|
||||
mail: (<><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></>),
|
||||
"mail-open": (<><path d="M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0z" /><path d="m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10" /></>),
|
||||
inbox: (<><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></>),
|
||||
"shield-alert": (<><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="M12 8v4" /><path d="M12 16h.01" /></>),
|
||||
send: (<><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" /><path d="m21.854 2.147-10.94 10.939" /></>),
|
||||
archive: (<><rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /></>),
|
||||
"file-text": (<><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><path d="M10 9H8" /><path d="M16 13H8" /><path d="M16 17H8" /></>),
|
||||
"pen-line": (<><path d="M12 20h9" /><path d="M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z" /></>),
|
||||
"messages-square": (<><path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z" /><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" /></>),
|
||||
"notebook-pen": (<><path d="M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4" /><path d="M2 6h4" /><path d="M2 10h4" /><path d="M2 14h4" /><path d="M2 18h4" /><path d="M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z" /></>),
|
||||
"calendar-days": (<><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /><path d="M8 14h.01" /><path d="M12 14h.01" /><path d="M16 14h.01" /><path d="M8 18h.01" /><path d="M12 18h.01" /><path d="M16 18h.01" /></>),
|
||||
hourglass: (<><path d="M5 22h14" /><path d="M5 2h14" /><path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22" /><path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2" /></>),
|
||||
play: (<polygon points="6 3 20 12 6 21 6 3" />),
|
||||
paperclip: (<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" />),
|
||||
"building-2": (<><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" /><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2" /><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2" /><path d="M10 6h4" /><path d="M10 10h4" /><path d="M10 14h4" /><path d="M10 18h4" /></>),
|
||||
"panel-left-open": (<><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M9 3v18" /><path d="m14 9 3 3-3 3" /></>),
|
||||
"trash-2": (<><path d="M3 6h18" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" x2="10" y1="11" y2="17" /><line x1="14" x2="14" y1="11" y2="17" /></>),
|
||||
calculator: (<><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></>),
|
||||
/* —— 治理中心扩充图标 —— */
|
||||
gauge: (<><path d="m12 14 4-4" /><path d="M3.34 19a10 10 0 1 1 17.32 0" /></>),
|
||||
database: (<><ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M3 5v14a9 3 0 0 0 18 0V5" /><path d="M3 12a9 3 0 0 0 18 0" /></>),
|
||||
layers: (<><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" /><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65" /><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65" /></>),
|
||||
users: (<><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M22 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></>),
|
||||
"share-2": (<><circle cx="18" cy="5" r="3" /><circle cx="6" cy="12" r="3" /><circle cx="18" cy="19" r="3" /><line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /><line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></>),
|
||||
cpu: (<><rect width="16" height="16" x="4" y="4" rx="2" /><rect width="6" height="6" x="9" y="9" rx="1" /><path d="M15 2v2" /><path d="M15 20v2" /><path d="M2 15h2" /><path d="M2 9h2" /><path d="M20 15h2" /><path d="M20 9h2" /><path d="M9 2v2" /><path d="M9 20v2" /></>),
|
||||
zap: (<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" />),
|
||||
"git-merge": (<><circle cx="18" cy="18" r="3" /><circle cx="6" cy="6" r="3" /><path d="M6 21V9a9 9 0 0 0 9 9" /></>),
|
||||
"refresh-cw": (<><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /><path d="M21 3v5h-5" /><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /><path d="M8 16H3v5" /></>),
|
||||
"rotate-ccw": (<><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /></>),
|
||||
"alert-triangle": (<><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></>),
|
||||
"shield-check": (<><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="m9 12 2 2 4-4" /></>),
|
||||
scan: (<><path d="M3 7V5a2 2 0 0 1 2-2h2" /><path d="M17 3h2a2 2 0 0 1 2 2v2" /><path d="M21 17v2a2 2 0 0 1-2 2h-2" /><path d="M7 21H5a2 2 0 0 1-2-2v-2" /><path d="M7 12h10" /></>),
|
||||
"file-check": (<><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><path d="m9 15 2 2 4-4" /></>),
|
||||
"trending-up": (<><path d="M16 7h6v6" /><path d="m22 7-8.5 8.5-5-5L2 17" /></>),
|
||||
ban: (<><circle cx="12" cy="12" r="10" /><path d="m4.9 4.9 14.2 14.2" /></>),
|
||||
key: (<><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4" /><path d="m21 2-9.6 9.6" /><circle cx="7.5" cy="15.5" r="5.5" /></>),
|
||||
workflow: (<><rect width="8" height="8" x="3" y="3" rx="2" /><path d="M7 11v4a2 2 0 0 0 2 2h4" /><rect width="8" height="8" x="13" y="13" rx="2" /></>),
|
||||
"arrow-right": (<><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></>),
|
||||
"circle-dot": (<><circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="3" /></>),
|
||||
/* —— 治理中心补充图标(运行 / 连接 / 岗位 / 事件 / 度量)—— */
|
||||
activity: (<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />),
|
||||
route: (<><circle cx="6" cy="19" r="3" /><path d="M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15" /><circle cx="18" cy="5" r="3" /></>),
|
||||
plug: (<><path d="M12 22v-5" /><path d="M9 8V2" /><path d="M15 8V2" /><path d="M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z" /></>),
|
||||
radio: (<><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9" /><path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5" /><circle cx="12" cy="12" r="2" /><path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5" /><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19" /></>),
|
||||
"git-commit": (<><circle cx="12" cy="12" r="3" /><line x1="3" x2="9" y1="12" y2="12" /><line x1="15" x2="21" y1="12" y2="12" /></>),
|
||||
history: (<><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M12 7v5l4 2" /></>),
|
||||
"user-check": (<><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><polyline points="16 11 18 13 22 9" /></>),
|
||||
"user-plus": (<><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><line x1="19" x2="19" y1="8" y2="14" /><line x1="22" x2="16" y1="11" y2="11" /></>),
|
||||
power: (<><path d="M12 2v10" /><path d="M18.4 6.6a9 9 0 1 1-12.77.04" /></>),
|
||||
"alert-circle": (<><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></>),
|
||||
"bar-chart": (<><line x1="12" x2="12" y1="20" y2="10" /><line x1="18" x2="18" y1="20" y2="4" /><line x1="6" x2="6" y1="20" y2="16" /></>),
|
||||
shuffle: (<><path d="M2 18h1.4c1.3 0 2.5-.6 3.3-1.7l6.1-8.6c.7-1.1 2-1.7 3.3-1.7H22" /><path d="m18 2 4 4-4 4" /><path d="M2 6h1.9c1.5 0 2.9.9 3.6 2.2" /><path d="M22 18h-5.9c-1.3 0-2.6-.7-3.3-1.8l-.5-.8" /><path d="m18 14 4 4-4 4" /></>),
|
||||
pin: (<><path d="M12 17v5" /><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z" /></>),
|
||||
timer: (<><line x1="10" x2="14" y1="2" y2="2" /><line x1="12" x2="15" y1="14" y2="11" /><circle cx="12" cy="14" r="8" /></>),
|
||||
repeat: (<><path d="m17 2 4 4-4 4" /><path d="M3 11v-1a4 4 0 0 1 4-4h14" /><path d="m7 22-4-4 4-4" /><path d="M21 13v1a4 4 0 0 1-4 4H3" /></>),
|
||||
briefcase: (<><path d="M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" /><rect width="20" height="14" x="2" y="6" rx="2" /></>),
|
||||
fingerprint: (<><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" /><path d="M14 13.12c0 2.38 0 6.38-1 8.88" /><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02" /><path d="M2 12a10 10 0 0 1 18-6" /><path d="M2 16h.01" /><path d="M21.8 16c.2-2 .131-5.354 0-6" /><path d="M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2" /><path d="M8.65 22c.21-.66.45-1.32.57-2" /><path d="M9 6.8a6 6 0 0 1 9 5.2v2" /></>),
|
||||
server: (<><rect width="20" height="8" x="2" y="2" rx="2" /><rect width="20" height="8" x="2" y="14" rx="2" /><line x1="6" x2="6.01" y1="6" y2="6" /><line x1="6" x2="6.01" y1="18" y2="18" /></>),
|
||||
network: (<><rect x="16" y="16" width="6" height="6" rx="1" /><rect x="2" y="16" width="6" height="6" rx="1" /><rect x="9" y="2" width="6" height="6" rx="1" /><path d="M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3" /><path d="M12 12V8" /></>),
|
||||
eye: (<><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" /><circle cx="12" cy="12" r="3" /></>),
|
||||
flag: (<><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" /><line x1="4" x2="4" y1="22" y2="15" /></>),
|
||||
lock: (<><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></>),
|
||||
percent: (<><line x1="19" x2="5" y1="5" y2="19" /><circle cx="6.5" cy="6.5" r="2.5" /><circle cx="17.5" cy="17.5" r="2.5" /></>),
|
||||
hash: (<><line x1="4" x2="20" y1="9" y2="9" /><line x1="4" x2="20" y1="15" y2="15" /><line x1="10" x2="8" y1="3" y2="21" /><line x1="16" x2="14" y1="3" y2="21" /></>),
|
||||
"external-link": (<><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></>),
|
||||
upload: (<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="17 8 12 3 7 8" /><line x1="12" x2="12" y1="3" y2="15" /></>),
|
||||
puzzle: (<path d="M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z" />),
|
||||
"circle-x": (<><circle cx="12" cy="12" r="10" /><path d="m15 9-6 6" /><path d="m9 9 6 6" /></>),
|
||||
"chevron-left": (<path d="m15 18-6-6 6-6" />),
|
||||
"calendar-clock": (<><path d="M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5" /><path d="M16 2v4" /><path d="M8 2v4" /><path d="M3 10h5" /><circle cx="16" cy="16" r="6" /><path d="M16 14v2l1 1" /></>),
|
||||
"file-search": (<><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h5" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><circle cx="16" cy="17" r="3" /><path d="m21 22-1.5-1.5" /></>),
|
||||
boxes: (<><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /><path d="m7 16.5-4.74-2.85" /><path d="m7 16.5 5-3" /><path d="M7 16.5v5.17" /><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /><path d="m17 16.5-5-3" /><path d="m17 16.5 4.74-2.85" /><path d="M17 16.5v5.17" /><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" /><path d="M12 8 7.26 5.15" /><path d="m12 8 4.74-2.85" /><path d="M12 13.5V8" /></>),
|
||||
"square-pen": (<><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" /></>),
|
||||
"log-out": (<><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" x2="9" y1="12" y2="12" /></>),
|
||||
};
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
{p[name]}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// 自绘滚动条容器:原生滚动条彻底隐藏(零占位——出现/消失永不挤版面,也绕开 Blink
|
||||
// 对滚动条伪元素动态切换不重绘的 quirk),自绘 thumb 悬浮在内衬/列间隔上。
|
||||
// 行为契约:鼠标移入滚动区立即现身;移出 / 停止滚动 800ms 后淡出;thumb 可拖拽。
|
||||
// 用法:外层 .jc-sa 接管原容器的布局角色(className 传 jc-sa--fill / jc-sa--full),
|
||||
// 原滚动容器的类名整体挪到 vpClassName(视口 = 真正滚动的元素,样式不变);
|
||||
// inset = thumb 距右缘 px(负值 = 伸出容器、悬进列间 gutter)。
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
const HIDE_MS = 800;
|
||||
|
||||
export default function ScrollArea({ className = "", vpClassName = "", inset = 3, scrollRef, children }: {
|
||||
className?: string;
|
||||
vpClassName?: string;
|
||||
inset?: number;
|
||||
scrollRef?: React.Ref<HTMLElement | null>; // 上层需要直接操作滚动位置时(如会话流自动滚底)
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const vpRef = useRef<HTMLDivElement | null>(null);
|
||||
const thRef = useRef<HTMLDivElement | null>(null);
|
||||
const hideT = useRef(0);
|
||||
const st = useRef({ inside: false, dragging: false, startY: 0, startTop: 0 });
|
||||
|
||||
// 按滚动几何同步 thumb 高度与位置;不可滚时整体隐藏
|
||||
const sync = useCallback(() => {
|
||||
const vp = vpRef.current, th = thRef.current;
|
||||
if (!vp || !th) return;
|
||||
const { scrollTop, scrollHeight, clientHeight } = vp;
|
||||
if (scrollHeight <= clientHeight + 1) { th.style.display = "none"; return; }
|
||||
th.style.display = "";
|
||||
const h = Math.max(28, Math.round((clientHeight / scrollHeight) * clientHeight));
|
||||
const y = Math.round((scrollTop / (scrollHeight - clientHeight)) * (clientHeight - h));
|
||||
th.style.height = `${h}px`;
|
||||
th.style.transform = `translateY(${y}px)`;
|
||||
}, []);
|
||||
|
||||
// 移出/停止滚动后延时淡出(悬停内 / 拖拽中不隐藏)
|
||||
const hideSoon = useCallback(() => {
|
||||
window.clearTimeout(hideT.current);
|
||||
hideT.current = window.setTimeout(() => {
|
||||
if (!st.current.inside && !st.current.dragging) thRef.current?.classList.remove("on");
|
||||
}, HIDE_MS);
|
||||
}, []);
|
||||
|
||||
const show = useCallback(() => {
|
||||
sync();
|
||||
thRef.current?.classList.add("on");
|
||||
hideSoon();
|
||||
}, [sync, hideSoon]);
|
||||
|
||||
useEffect(() => {
|
||||
const vp = vpRef.current;
|
||||
if (!vp) return;
|
||||
const ro = new ResizeObserver(sync); // 面板拖宽/视口变化时校准
|
||||
ro.observe(vp);
|
||||
sync();
|
||||
return () => { ro.disconnect(); window.clearTimeout(hideT.current); };
|
||||
}, [sync]);
|
||||
|
||||
// 标准 ref 转发(写入外部 ref.current 是 ref 的本职),compiler 规则误报
|
||||
const setVp = useCallback((el: HTMLDivElement | null) => {
|
||||
vpRef.current = el;
|
||||
if (typeof scrollRef === "function") scrollRef(el);
|
||||
else if (scrollRef) (scrollRef as React.MutableRefObject<HTMLElement | null>).current = el;
|
||||
}, [scrollRef]);
|
||||
|
||||
// thumb 拖拽滚动
|
||||
const onThumbDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
const vp = vpRef.current;
|
||||
if (!vp) return;
|
||||
e.preventDefault();
|
||||
st.current = { ...st.current, dragging: true, startY: e.clientY, startTop: vp.scrollTop };
|
||||
thRef.current?.classList.add("on");
|
||||
const move = (ev: PointerEvent) => {
|
||||
const { scrollHeight, clientHeight } = vp;
|
||||
const h = thRef.current?.offsetHeight ?? 28;
|
||||
vp.scrollTop = st.current.startTop + (ev.clientY - st.current.startY) * ((scrollHeight - clientHeight) / Math.max(1, clientHeight - h));
|
||||
};
|
||||
const up = () => {
|
||||
st.current.dragging = false;
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
hideSoon();
|
||||
};
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={"jc-sa " + className}
|
||||
onPointerEnter={() => { st.current.inside = true; show(); }}
|
||||
onPointerLeave={() => { st.current.inside = false; hideSoon(); }}
|
||||
>
|
||||
<div ref={setVp} className={"jc-sa-vp " + vpClassName} onScroll={show}>
|
||||
{children}
|
||||
</div>
|
||||
<div ref={thRef} className="jc-sa-thumb" style={{ right: inset, display: "none" }} onPointerDown={onThumbDown} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 服务端交互的唯一通道(编码规范 §9.2):组件内**禁止裸 fetch**。
|
||||
* 路由契约来自 `@contracts/api`;**禁止 Hono RPC 客户端** —— 那要求前端 import 后端类型(§5.1)。
|
||||
*/
|
||||
import type { RouteContract } from '@contracts';
|
||||
|
||||
export class ApiCallError extends Error {
|
||||
constructor(
|
||||
readonly status: number,
|
||||
readonly code: string,
|
||||
message: string,
|
||||
readonly detail?: unknown,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ApiCallError';
|
||||
}
|
||||
}
|
||||
|
||||
type CallOptions = {
|
||||
readonly params?: Readonly<Record<string, string>>;
|
||||
readonly body?: unknown;
|
||||
};
|
||||
|
||||
/** 路径参数按 `:name` 就地替换 */
|
||||
function resolvePath(path: string, params: Readonly<Record<string, string>> = {}): string {
|
||||
return path.replace(/:([A-Za-z_][\w]*)/g, (_, key: string) => encodeURIComponent(params[key] ?? ''));
|
||||
}
|
||||
|
||||
export async function call<T>(route: RouteContract, options: CallOptions = {}): Promise<T> {
|
||||
const response = await fetch(resolvePath(route.path, options.params), {
|
||||
method: route.method,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
||||
});
|
||||
|
||||
const payload: unknown = await response.json().catch(() => undefined);
|
||||
if (!response.ok) {
|
||||
const error = (payload ?? {}) as { code?: string; message?: string; detail?: unknown };
|
||||
throw new ApiCallError(
|
||||
response.status,
|
||||
error.code ?? 'internal',
|
||||
error.message ?? `${response.status} ${response.statusText}`,
|
||||
error.detail,
|
||||
);
|
||||
}
|
||||
return payload as T;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 对话区/视图区可调比例:workspace 上的分隔柄拖拽写入 --jc-chat-w(CSS 变量,
|
||||
// 各断点的对话区宽度均以它优先),localStorage 记忆,双击复位回默认配比。
|
||||
// 任务工作台与消息模块共用同一 storage key —— 两处对话区宽度保持一致。
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
const MIN_W = 340;
|
||||
// 上限随视口收敛:给 视图区(≥480) + 清单列 + 间距 留足空间,避免拖到中栏塌陷
|
||||
const maxW = () => Math.max(MIN_W, Math.min(720, window.innerWidth - 830));
|
||||
|
||||
export function useChatWidth(storageKey = "jc-chat-w") {
|
||||
const [w, setW] = useState<number | null>(null); // null = 未拖拽,走断点默认
|
||||
|
||||
useEffect(() => {
|
||||
const v = Number(localStorage.getItem(storageKey));
|
||||
if (v) setW(Math.min(maxW(), Math.max(MIN_W, v)));
|
||||
}, [storageKey]);
|
||||
|
||||
const onSplitDown = useCallback((e: React.PointerEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
const handle = e.currentTarget;
|
||||
const chatEl = handle.parentElement?.querySelector<HTMLElement>(".session-chat");
|
||||
const startX = e.clientX;
|
||||
const startW = chatEl?.getBoundingClientRect().width ?? 440;
|
||||
let cur = startW;
|
||||
handle.classList.add("dragging");
|
||||
document.body.style.cursor = "col-resize";
|
||||
document.body.style.userSelect = "none";
|
||||
const move = (ev: PointerEvent) => {
|
||||
cur = Math.min(maxW(), Math.max(MIN_W, Math.round(startW + (startX - ev.clientX))));
|
||||
setW(cur);
|
||||
};
|
||||
const up = () => {
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
handle.classList.remove("dragging");
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
localStorage.setItem(storageKey, String(cur));
|
||||
};
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
}, [storageKey]);
|
||||
|
||||
const onSplitReset = useCallback(() => {
|
||||
setW(null);
|
||||
localStorage.removeItem(storageKey);
|
||||
}, [storageKey]);
|
||||
|
||||
const style = (w ? { "--jc-chat-w": `${w}px` } : undefined) as React.CSSProperties | undefined;
|
||||
return { style, onSplitDown, onSplitReset };
|
||||
}
|
||||
Reference in New Issue
Block a user