feat(canvas): 优化 Agent 工具卡片与交互界面

- 精简工具调用卡片样式,提升对话流可读性
- 动态生成工具调用标题并友好化结果消息
- 将思考中指示器插入到最新用户消息之后
- 固定主要操作按钮和计划栏,改善滚动
This commit is contained in:
王志伟
2026-09-09 01:28:52 +08:00
parent 3ac07ff1fa
commit 7739de389d
4 changed files with 221 additions and 73 deletions

View File

@@ -1,12 +1,26 @@
.creative-agent-card { container-type: inline-size; padding: var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--background); color: var(--foreground); display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; overflow-wrap: anywhere; }
.creative-agent-card { container-type: inline-size; padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--background); color: var(--foreground); display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; overflow-wrap: anywhere; }
.creative-agent-card h3, .creative-agent-card p { margin: 0; }
.creative-agent-card-content { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.creative-agent-receipt { padding: var(--space-2) var(--space-3); color: var(--muted-foreground); font-size: var(--fs-caption); line-height: 1.7; overflow-wrap: anywhere; }
.creative-agent-receipt { padding: var(--space-1) var(--space-2); color: var(--muted-foreground); font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
.creative-agent-receipt summary { cursor: pointer; }
.creative-agent-receipt p { margin-block: var(--space-2); }
.creative-agent-next { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-2) var(--space-1); color: var(--muted-foreground); line-height: 1.7; }
.creative-agent-next { position: sticky; bottom: 0; z-index: 4; display: flex; flex-direction: column; gap: var(--space-1); margin: 0 -4px; padding: var(--space-1) var(--space-2) calc(var(--space-1) + env(safe-area-inset-bottom)); color: var(--muted-foreground); line-height: 1.5; font-size: var(--fs-caption); background: color-mix(in srgb, var(--background) 96%, transparent); border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); backdrop-filter: blur(10px); }
.creative-agent-primary-actions { position: sticky; bottom: 0; z-index: 3; margin-inline: calc(var(--space-3) * -1); padding: var(--space-2) var(--space-3) 0; background: color-mix(in srgb, var(--background) 96%, transparent); }
.creative-agent-next p { margin: 0; }
.creative-agent-next .creative-agent-actions:empty { display: none; }
.creative-agent-thinking-wrap { display: flex; align-items: center; min-height: 24px; padding: 0 2px; }
.creative-agent-thinking-wrap > [role="status"] { font-size: var(--fs-caption); gap: var(--space-1); }
.agent-tool-row { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); padding: 2px 4px; }
.agent-tool-message { margin: 0; }
.agent-tool-row:last-child { border-bottom: 0; }
.agent-tool-row summary { padding: 3px 0; }
.agent-tool-status { display: grid; width: 18px; height: 18px; place-items: center; flex-shrink: 0; }
.agent-tool-status svg { width: 15px; height: 15px; }
.agent-tool-label { flex-shrink: 0; font-size: var(--fs-caption); }
.agent-tool-row > .creative-agent-detail { margin: 4px 0 5px 26px; padding: 6px 8px; border-radius: var(--control-radius); background: color-mix(in srgb, var(--surface-card) 70%, transparent); font-size: var(--fs-caption); }
.agent-pending-tool .creative-agent-detail { margin-top: 6px; padding: 6px 8px; font-size: var(--fs-caption); }
.agent-pending-tool details { margin-top: 6px !important; padding-top: 0 !important; }
.agent-pending-tool .creative-agent-archive-row { gap: 6px; }
.creative-agent-receipt summary:focus-visible { outline: 2px solid var(--control-focus-ring); outline-offset: 2px; }
.creative-agent-archive-row { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
.creative-agent-archive-row > button { flex-shrink: 0; }
@@ -20,8 +34,8 @@
.creative-agent-step-dots { display: flex; gap: var(--space-1); flex-wrap: wrap; max-width: 45%; }
.creative-agent-step-dots i { width: var(--space-4); height: var(--space-1); border-radius: var(--control-radius); background: var(--border); }
.creative-agent-step-dots i[data-current="true"] { background: var(--foreground); }
.creative-agent-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-top: var(--space-1); }
.creative-agent-actions .ant-btn { max-width: 100%; height: auto; min-height: var(--space-8); white-space: normal; }
.creative-agent-actions { display: flex; flex-wrap: nowrap; gap: var(--space-2); padding-top: var(--space-1); }
.creative-agent-actions .ant-btn { max-width: 100%; height: 30px; min-height: 30px; padding-inline: 12px; white-space: nowrap; font-size: 13px; }
.creative-agent-options { display: flex; flex-direction: column; gap: var(--space-2); }
.creative-agent-option { display: flex; align-items: flex-start; gap: var(--space-2); padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--control-radius); cursor: pointer; }
.creative-agent-option[data-selected="true"] { background: var(--control-selected-bg); border-color: var(--control-selected-border); color: var(--control-selected-fg); }
@@ -47,6 +61,9 @@
.creative-agent-plan li p { color: var(--muted-foreground); margin: var(--space-1) 0 0; line-height: 1.6; }
.creative-agent-plan li[data-status="completed"]::marker { color: var(--muted-foreground); }
.creative-agent-plan-trigger { display: flex; width: 100%; gap: var(--space-2); white-space: normal; height: auto; min-height: var(--space-8); text-align: start; padding: var(--space-2) var(--space-3); background: var(--surface-card); border-color: var(--border); }
.creative-agent-plan-docked { position: relative; z-index: 3; width: 100%; margin: 0; padding: 0 12px; display: flex; justify-content: flex-end; }
.creative-agent-plan-docked .creative-agent-plan-trigger { width: auto; min-width: 0; max-width: 230px; min-height: 30px; padding: 5px 9px; border-radius: 999px; font-size: var(--fs-caption); box-shadow: 0 5px 16px var(--shadow-color, rgba(0,0,0,.12)); }
.creative-agent-plan-docked .creative-agent-plan-current { flex: 0 1 auto; }
.creative-agent-plan-count { flex-shrink: 0; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.creative-agent-plan-current { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.creative-agent-plan-chevron { color: var(--muted-foreground); flex-shrink: 0; }
@@ -61,6 +78,7 @@
.creative-agent-quote-total strong { text-align: end; font-size: var(--fs-heading-lg); font-variant-numeric: tabular-nums; }
.creative-agent-card :focus-visible, .creative-agent-plan :focus-visible, .creative-agent-plan-trigger:focus-visible { outline: 2px solid var(--control-focus-ring); outline-offset: 2px; }
@container (max-width: 280px) {
.creative-agent-actions { flex-wrap: wrap; }
.creative-agent-deliverables { grid-template-columns: minmax(0, 1fr); }
.creative-agent-quote-total { flex-wrap: wrap; }
}

View File

@@ -77,7 +77,7 @@ export function CreativeProposalCard({ proposal, onApprove, onModify, onRedirect
{!archived && <Button className="creative-agent-detail-toggle" aria-expanded={expanded} aria-controls={detailId} onClick={() => setExpanded(!expanded)}>{expanded ? "收起完整方案" : "查看完整方案"}</Button>}
{expanded && <div id={detailId} className="creative-agent-detail"><AIMessageMarkdown>{proposal.markdown}</AIMessageMarkdown></div>}
{!archived && <><div className="creative-agent-scope-note">确认后创建本阶段节点并准备参数;图片、视频等生成费用另行确认。</div>
<div className="creative-agent-actions"><Button type="primary" loading={busy} disabled={disabled || busy} onClick={onApprove}>确认方案</Button><Button disabled={disabled || busy} onClick={onModify}>修改</Button><Dropdown menu={{ items: [{ key: "redirect", label: "换个方向" }], onClick: onRedirect }} trigger={["click"]} disabled={disabled || busy}><Button disabled={disabled || busy}>更多</Button></Dropdown></div></>}
<div className="creative-agent-actions creative-agent-primary-actions"><Button type="primary" loading={busy} disabled={disabled || busy} onClick={onApprove}>确认方案</Button><Button disabled={disabled || busy} onClick={onModify}>修改</Button><Dropdown menu={{ items: [{ key: "redirect", label: "换个方向" }], onClick: onRedirect }} trigger={["click"]} disabled={disabled || busy}><Button disabled={disabled || busy}>更多</Button></Dropdown></div></>}
</>}
</section>;
}
@@ -96,7 +96,7 @@ export function CreativePlanBar({ plan, onLocateNode }: { plan?: CreativePlan |
return <Popover open={open} onOpenChange={(next) => { setOpen(next); if (!next) trigger.current?.focus(); }} afterOpenChange={(next) => { if (next) panel.current?.focus(); }} placement="top" trigger="click" content={<div id={panelId} ref={panel} tabIndex={-1} className="creative-agent-plan" data-canvas-no-zoom data-canvas-wheel-scroll onKeyDown={(event) => { if (event.key === "Escape") { event.stopPropagation(); close(); } }}>
<div className="creative-agent-card-row"><strong>制作计划</strong><Button size="small" onClick={close}>收起</Button></div>
<ol>{plan.steps.map((step) => <li key={step.id} data-status={step.status}><div className="creative-agent-card-row"><strong>{step.title}</strong><Tag>{stepLabels[step.status]}</Tag></div>{step.detail && <p>{step.detail}</p>}{onLocateNode && step.nodeIds?.map((id, index) => <Button key={id} type="link" onClick={() => onLocateNode(id)}>定位节点 {index + 1}</Button>)}</li>)}</ol>
</div>}><Button ref={trigger} className="creative-agent-plan-trigger" aria-expanded={open} aria-controls={panelId} onKeyDown={(event) => { if (event.key === "Escape") close(); }}><span className="creative-agent-plan-count">{completed}/{plan.steps.length}</span><span className="creative-agent-plan-current">{running > 1 ? `运行中 ${running} 项` : current ? current.title : completed === plan.steps.length ? "计划已完成" : "查看制作计划"}</span><span className="creative-agent-plan-chevron" aria-hidden="true">{open ? "⌄" : "⌃"}</span></Button></Popover>;
</div>}><Button ref={trigger} className="creative-agent-plan-trigger" aria-label="查看制作计划" aria-expanded={open} aria-controls={panelId} onKeyDown={(event) => { if (event.key === "Escape") close(); }}><span className="creative-agent-plan-count">{completed}/{plan.steps.length}</span><span className="creative-agent-plan-current">{running > 1 ? `运行中 ${running} 项` : current ? current.title : completed === plan.steps.length ? "计划已完成" : "查看制作计划"}</span><span className="creative-agent-plan-chevron" aria-hidden="true">{open ? "⌄" : "⌃"}</span></Button></Popover>;
}
export function CreativeQuoteCard({ quote, onApprove, onRefresh, busy, disabled }: { quote: CreativeQuote; onApprove: () => void; onRefresh?: () => void; busy?: boolean; disabled?: boolean }) {
@@ -108,6 +108,6 @@ export function CreativeQuoteCard({ quote, onApprove, onRefresh, busy, disabled
{quote.expiresAt && <p>有效期至 {quote.expiresAt}</p>}{quote.approvedQuantity !== undefined && <p>已批准 {quote.approvedQuantity} 项</p>}
{onRefresh && <Button className="creative-agent-detail-toggle" type="text" disabled={disabled || busy} onClick={onRefresh}>更新报价</Button>}</div></details>
{quote.externalBilling && <p>由外部渠道计费,平台无法封顶第三方账单。</p>}
<div className="creative-agent-actions"><Button type="primary" loading={busy} disabled={disabled || busy} onClick={onApprove}>确认生成 · {quote.amountLabel}</Button></div>
<div className="creative-agent-actions creative-agent-primary-actions"><Button type="primary" loading={busy} disabled={disabled || busy} onClick={onApprove}>确认生成 · {quote.amountLabel}</Button></div>
</section>;
}