feat(agent): 助手交互 - 优化推理展示、工具分组与审批反馈
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { agentCanvasActions, agentCanvasActionLabel } from "@/lib/canvas/agent-canvas-actions";
|
||||
import { Button } from "antd";
|
||||
import { Tooltip } from "@/components/ui/base/tooltip";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ClipboardEvent as ReactClipboardEvent, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ClipboardEvent as ReactClipboardEvent, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
|
||||
|
||||
import { motion, useReducedMotion } from "motion/react";
|
||||
import { ArrowUp, AtSign, CheckCircle2, CircleAlert, CircleDot, ImagePlus, LoaderCircle, RotateCcw, Sparkles, Square, X, XCircle } from "lucide-react";
|
||||
import { ArrowUp, AtSign, CheckCircle2, ChevronDown, CircleAlert, CircleDot, Eye, ImagePlus, LoaderCircle, Pencil, Plus, RotateCcw, Sparkles, Square, X, XCircle } from "lucide-react";
|
||||
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { AIMessageMarkdown } from "@/components/ai/ai-message-markdown";
|
||||
@@ -13,7 +13,7 @@ import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textare
|
||||
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
|
||||
import type { Skill } from "@/services/api/skills";
|
||||
import { buildSkillMentionReferences } from "@/services/skill-runtime";
|
||||
import { agentToolStatus, friendlyAgentToolSummary } from "@/lib/canvas/agent-tool-presentation";
|
||||
import { agentToolCategory, agentToolCategoryLabel, agentToolStatus, friendlyAgentToolSummary } from "@/lib/canvas/agent-tool-presentation";
|
||||
|
||||
export type CloudAgentChatAttachment = { id: string; name: string; url: string };
|
||||
type CloudAgentOperationImpact = {
|
||||
@@ -93,10 +93,28 @@ export function AgentChatMessage({
|
||||
const isSystem = item.role === "system";
|
||||
const color = item.role === "error" ? "#ef4444" : theme.node.text;
|
||||
if (item.reasoning) {
|
||||
return <details className="min-w-0 text-xs" style={{ color: theme.node.muted }}>
|
||||
<summary className="cursor-pointer py-1 focus-visible:outline-auto">{item.streaming ? "模型推理中" : "模型推理摘要"}</summary>
|
||||
<div className="max-h-64 overflow-auto whitespace-pre-wrap break-words py-2" data-canvas-wheel-scroll>{item.text}</div>
|
||||
</details>;
|
||||
return (
|
||||
<div className="agent-reasoning" style={{ "--agent-reasoning-accent": theme.accent.primary } as CSSProperties}>
|
||||
<details className={`agent-reasoning-card${item.streaming ? " is-streaming" : ""}`} open={item.streaming || undefined}>
|
||||
<summary className="agent-reasoning-summary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/20">
|
||||
<span className="agent-reasoning-icon" aria-hidden="true"><Sparkles className="size-3.5" /></span>
|
||||
<span className="agent-reasoning-copy">
|
||||
<span className="agent-reasoning-title">{item.streaming ? "模型正在思考" : "模型思考"}</span>
|
||||
<span className="agent-reasoning-subtitle">{item.streaming ? "整理目标与下一步" : "推理摘要"}</span>
|
||||
</span>
|
||||
<span className={`agent-reasoning-status${item.streaming ? " is-live" : ""}`}>
|
||||
{item.streaming ? <span className="agent-reasoning-status-dot" aria-hidden="true" /> : null}
|
||||
{item.streaming ? "实时" : "查看"}
|
||||
</span>
|
||||
<ChevronDown className="agent-reasoning-chevron" aria-hidden="true" />
|
||||
</summary>
|
||||
<div className="agent-reasoning-content" data-canvas-wheel-scroll>
|
||||
<span className="agent-reasoning-rail" aria-hidden="true" />
|
||||
<div className="agent-reasoning-text">{item.text || (item.streaming ? "正在整理思路…" : "暂无可展示的推理摘要")}</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isSystem) {
|
||||
return (
|
||||
@@ -278,26 +296,39 @@ function agentImpactFromDetail(detail: unknown) {
|
||||
export function AgentToolCard({ title, text, detail, theme, references = [], onFocusNode }: { title: string; text: string; detail?: unknown; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; references?: CanvasResourceReference[]; onFocusNode?: (nodeId: string) => void }) {
|
||||
const state = toolCardState(title, text, detail);
|
||||
const toolName = agentToolName(title, detail);
|
||||
const category = agentToolCategory(toolName, detail);
|
||||
const categoryLabel = agentToolCategoryLabel(toolName, category);
|
||||
const summary = friendlyAgentToolSummary(toolName, text, detail);
|
||||
const actions = agentCanvasActions(toolName, detail, references);
|
||||
const visibleActions = actions.slice(0, 8);
|
||||
const readNodeCount = toolName === "canvas_get_state" && !state.isError ? readCanvasNodeCount(detail) : 0;
|
||||
const hiddenReadNodeCount = Math.max(0, readNodeCount - visibleActions.length);
|
||||
const isNodeRead = toolName === "canvas_get_state" && category === "read";
|
||||
const [readExpanded, setReadExpanded] = useState(false);
|
||||
const visibleActions = actions.slice(0, isNodeRead && !readExpanded ? 1 : 8);
|
||||
const collapsedReadNodeCount = isNodeRead ? Math.max(0, actions.length - visibleActions.length) : 0;
|
||||
const isPlain = !actions.length && !state.isError;
|
||||
const conciseError = text.length > 180 ? `${text.slice(0, 180)}…` : text;
|
||||
const categoryIcon = category === "read" ? <Eye className="size-3.5" /> : category === "create" ? <Plus className="size-3.5" /> : <Pencil className="size-3.5" />;
|
||||
return (
|
||||
<div data-agent-tool-card className="agent-tool-row flex min-w-0 flex-1 items-start gap-2 text-left" style={{ color: theme.node.text }}>
|
||||
<div data-agent-tool-card className={`agent-tool-row agent-tool-row--${category}${isPlain ? " agent-tool-row--plain" : ""} flex min-w-0 flex-1 items-start gap-2.5 text-left`} style={{ color: theme.node.text }}>
|
||||
<span className="agent-tool-status shrink-0" style={{ color: state.color }} aria-hidden="true">{state.icon}</span>
|
||||
<div className="min-w-0 flex-1 break-words text-xs leading-5" style={{ color: state.isError ? state.color : theme.node.muted }}>
|
||||
{actions.length ? <div className="flex flex-col items-start gap-0.5">
|
||||
{visibleActions.map((action) => <button key={`${action.action}-${action.nodeId}`} type="button" data-agent-node-id={action.nodeId} disabled={!onFocusNode} onClick={() => onFocusNode?.(action.nodeId)} aria-label={`在画布中定位${action.title}`} className="max-w-full cursor-pointer break-words rounded text-left underline decoration-dotted underline-offset-4 focus-visible:outline focus-visible:outline-2 disabled:cursor-default disabled:no-underline">{agentCanvasActionLabel(action)}</button>)}
|
||||
{hiddenReadNodeCount > 0 ? <span className="pt-0.5 opacity-70">已读取 {readNodeCount} 个节点,已折叠其余 {hiddenReadNodeCount} 个</span> : null}
|
||||
</div> : summary}
|
||||
{state.isError && actions.length ? <span className="block">{summary}</span> : null}
|
||||
{state.isError && text && text !== summary ? <span className="mt-0.5 block whitespace-pre-wrap break-words" style={{ color: theme.node.muted }}>{conciseError}</span> : null}
|
||||
<div className="agent-tool-header">
|
||||
<span className="agent-tool-category">{categoryIcon}<span>{categoryLabel}</span></span>
|
||||
<span className="agent-tool-summary">{summary}</span>
|
||||
{state.label !== "已完成" ? <span className="agent-tool-label" style={{ color: state.color }}>{state.label}</span> : null}
|
||||
</div>
|
||||
{actions.length ? <div className="agent-tool-action-list">
|
||||
{visibleActions.map((action) => <button key={`${action.action}-${action.nodeId}`} type="button" data-agent-node-id={action.nodeId} disabled={!onFocusNode} onClick={() => onFocusNode?.(action.nodeId)} aria-label={`在画布中定位${action.title}`} className="agent-tool-action-link">{agentCanvasActionLabel(action)}</button>)}
|
||||
{isNodeRead && (collapsedReadNodeCount > 0 || readExpanded) ? (
|
||||
<button type="button" className="agent-tool-more" aria-expanded={readExpanded} onClick={() => setReadExpanded((current) => !current)}>
|
||||
{readExpanded ? `收起其余 ${Math.max(0, actions.length - 1)} 个节点` : `已折叠 ${collapsedReadNodeCount} 个节点,展开查看`}
|
||||
</button>
|
||||
) : null}
|
||||
</div> : null}
|
||||
{state.isError && text && text !== summary ? <span className="mt-1 block whitespace-pre-wrap break-words" style={{ color: theme.node.muted }}>{conciseError}</span> : null}
|
||||
{state.isError && text.length > 180 ? <details className="mt-1" style={{ color: theme.node.muted }}><summary className="cursor-pointer">查看完整错误详情</summary><p className="whitespace-pre-wrap break-words">{text}</p></details> : null}
|
||||
{state.isError && objectField(objectField(detail, "result"), "taskId") ? <span className="mt-0.5 block break-all" style={{ color: theme.node.muted }}>任务 ID:{String(objectField(objectField(detail, "result"), "taskId"))}</span> : null}
|
||||
{state.isError && objectField(objectField(detail, "result"), "taskId") ? <span className="mt-1 block break-all" style={{ color: theme.node.muted }}>任务 ID:{String(objectField(objectField(detail, "result"), "taskId"))}</span> : null}
|
||||
</div>
|
||||
{state.label !== "已完成" ? <span className="agent-tool-label shrink-0 text-[var(--fs-label)]" style={{ color: state.color }}>{state.label}</span> : <span className="sr-only">已完成</span>}
|
||||
{state.label === "已完成" ? <span className="sr-only">已完成</span> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -755,9 +786,3 @@ function agentToolName(title: string, detail?: unknown) {
|
||||
function objectField(value: unknown, key: string) {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>)[key] : undefined;
|
||||
}
|
||||
|
||||
function readCanvasNodeCount(detail: unknown) {
|
||||
const result = objectField(detail, "result");
|
||||
const nodes = objectField(result, "nodes");
|
||||
return Array.isArray(nodes) ? nodes.length : 0;
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
const installedSkills = useMemo(() => skills.filter((skill) => skill.isAdded), [skills]);
|
||||
const enabledSkills = useMemo(() => installedSkills.filter((skill) => selectedSkillIds.includes(skill.skillId)), [installedSkills, selectedSkillIds]);
|
||||
const status = run?.status || "idle";
|
||||
const statusLabel = status === "waiting_approval" ? "等待审批" : status === "running" || status === "queued" ? "运行中" : status === "completed" ? "已完成" : status === "failed" ? "异常" : "待命";
|
||||
const statusColor = status === "failed" ? "#e66b6b" : status === "waiting_approval" ? "#d6a24a" : status === "running" || status === "queued" ? "#69c29b" : theme.node.muted;
|
||||
const statusLabel = status === "waiting_approval" ? "等待审批" : status === "running" || status === "queued" ? "运行中" : status === "completed" ? "已完成" : status === "failed" ? "异常" : status === "cancelled" ? "已停止" : status === "rejected" ? "已拒绝" : "待命";
|
||||
const statusColor = status === "failed" ? "#e66b6b" : status === "rejected" || status === "cancelled" ? theme.node.muted : status === "waiting_approval" ? "#d6a24a" : status === "running" || status === "queued" ? "#69c29b" : theme.node.muted;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || view !== "chat") setSkillsOpen(false);
|
||||
@@ -447,7 +447,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
await decideAgentApproval(runId, approvalId, decision, approval.reason, AbortSignal.timeout(15_000));
|
||||
if (currentScope.current === scope) {
|
||||
setApproval((current) => current?.approvalId === approvalId ? null : current);
|
||||
setRun((current) => current?.id === runId && current.status === "waiting_approval" && (!current.approval || current.approval.approvalId === approvalId) ? { ...current, status: "running", approval: undefined } : current);
|
||||
setRun((current) => current?.id === runId && current.status === "waiting_approval" && (!current.approval || current.approval.approvalId === approvalId) ? { ...current, status: decision === "reject" ? "rejected" : "running", approval: undefined } : current);
|
||||
}
|
||||
} catch (cause) {
|
||||
if (currentScope.current !== scope) return;
|
||||
@@ -1021,7 +1021,7 @@ function ApprovalCard({ approval, theme, submitting, onFocusNode, onReasonChange
|
||||
}
|
||||
|
||||
function ApprovalPreviewItemView({ item, theme, onFocusNode }: { item: ReturnType<typeof agentApprovalPresentation>["items"][number]; theme: CanvasTheme; onFocusNode?: (nodeId: string) => void }) {
|
||||
const operationLabel = item.operation === "add_node" ? "新增" : item.operation === "update_node" ? "修改" : item.operation === "connect_nodes" ? "连线" : "生成";
|
||||
const operationLabel = item.operation === "add_node" ? "新增" : item.operation === "update_node" ? "修改" : item.operation === "connect_nodes" ? "连线" : item.operation === "create_storyboard" ? "创建分镜" : item.operation === "edit_storyboard" ? "修改分镜" : "生成";
|
||||
const renderNode = (title: string | undefined, id: string | undefined, typeLabel: string | undefined, role: "source" | "target" | "node") => {
|
||||
if (!title) return null;
|
||||
const content = <><span className="canvas-agent-approval-node-title">{title}</span>{typeLabel ? <span className="canvas-agent-approval-node-type">{typeLabel}</span> : null}</>;
|
||||
@@ -1081,7 +1081,17 @@ function applyAgentEvent(event: AgentEvent, setMessages: Dispatch<SetStateAction
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.type === "approval_decided") { setApproval(null); return; }
|
||||
if (event.type === "approval_decided") {
|
||||
setApproval(null);
|
||||
if (payload.decision === "reject") {
|
||||
setMessages((current) => appendUniqueMessage(current, {
|
||||
id: event.eventId,
|
||||
role: "system",
|
||||
text: text || "已拒绝本次操作,未写入画布。你可以告诉 Agent 修改方向后重新申请。",
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.type === "progress_summary") {
|
||||
setMessages((current) => appendUniqueMessage(current, { id: event.eventId, role: "system", text: text || "Agent 正在整理执行计划" }));
|
||||
return;
|
||||
|
||||
@@ -91,9 +91,140 @@
|
||||
}
|
||||
|
||||
.agent-tool-row {
|
||||
/* 行为用图标和文案区分,颜色只留给真实的成功/失败状态。 */
|
||||
--agent-tool-accent: color-mix(in srgb, var(--foreground) 62%, transparent);
|
||||
min-height: 42px;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 11%, transparent);
|
||||
border-radius: 14px;
|
||||
background: color-mix(in srgb, var(--foreground) 2.5%, var(--background));
|
||||
padding: 9px 10px;
|
||||
transition: border-color 160ms ease, background-color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.agent-tool-row:hover {
|
||||
border-color: color-mix(in srgb, var(--foreground) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--foreground) 4%, var(--background));
|
||||
}
|
||||
|
||||
/* 没有节点明细的“读取信息 / 操作画布”只保留时间线语义,不再撑成一张空卡片。 */
|
||||
.agent-tool-row--plain {
|
||||
min-height: 28px;
|
||||
border-color: transparent;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
padding: 3px 2px;
|
||||
}
|
||||
|
||||
.agent-tool-row--plain:hover {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.agent-tool-row--read {
|
||||
--agent-tool-accent: color-mix(in srgb, var(--foreground) 62%, transparent);
|
||||
}
|
||||
|
||||
.agent-tool-row--create {
|
||||
--agent-tool-accent: color-mix(in srgb, var(--foreground) 62%, transparent);
|
||||
}
|
||||
|
||||
.agent-tool-row--operate {
|
||||
--agent-tool-accent: color-mix(in srgb, var(--foreground) 62%, transparent);
|
||||
}
|
||||
|
||||
.agent-tool-header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.agent-tool-category {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 16%, transparent);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--foreground) 5%, transparent);
|
||||
padding: 2px 7px 2px 6px;
|
||||
color: color-mix(in srgb, var(--foreground) 76%, transparent);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.agent-tool-summary {
|
||||
min-width: 0;
|
||||
flex: 1 1 180px;
|
||||
overflow: hidden;
|
||||
color: color-mix(in srgb, currentColor 82%, transparent);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-tool-action-list {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.agent-tool-action-link {
|
||||
min-width: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
||||
border-radius: 8px;
|
||||
padding: 2px 6px;
|
||||
background: color-mix(in srgb, var(--foreground) 3%, transparent);
|
||||
padding: 4px 7px;
|
||||
color: color-mix(in srgb, currentColor 88%, transparent);
|
||||
font-size: 11px;
|
||||
line-height: 17px;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: left;
|
||||
transition: border-color 140ms ease, background-color 140ms ease, color 140ms ease;
|
||||
}
|
||||
|
||||
.agent-tool-action-link:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.agent-tool-action-link:not(:disabled):hover {
|
||||
border-color: color-mix(in srgb, var(--foreground) 26%, transparent);
|
||||
background: color-mix(in srgb, var(--foreground) 7%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.agent-tool-action-link:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--foreground) 56%, transparent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.agent-tool-more {
|
||||
align-self: flex-start;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 2px 2px 0;
|
||||
color: color-mix(in srgb, currentColor 64%, transparent);
|
||||
font-size: 11px;
|
||||
line-height: 17px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.agent-tool-more:hover {
|
||||
color: var(--foreground);
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-tool-row,
|
||||
.agent-tool-action-link { transition: none; }
|
||||
}
|
||||
|
||||
.agent-composer-prompt-scroll {
|
||||
@@ -169,6 +300,181 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/*
|
||||
* 推理摘要不是普通的折叠文本:它是 Agent 工作流中的“过程层”,需要
|
||||
* 比工具日志更安静、比正文更有层次。使用当前画布主题的 accent,避免
|
||||
* 固定紫色的 AI 默认样式,同时把实时状态、摘要内容和展开动作分开。
|
||||
*/
|
||||
.agent-reasoning {
|
||||
min-width: 0;
|
||||
color: color-mix(in srgb, var(--foreground) 64%, transparent);
|
||||
}
|
||||
|
||||
.agent-reasoning-card {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 9%, transparent);
|
||||
border-radius: 14px;
|
||||
background: color-mix(in srgb, var(--foreground) 2.5%, var(--background));
|
||||
box-shadow: 0 5px 18px color-mix(in srgb, var(--foreground) 3%, transparent);
|
||||
transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.agent-reasoning-card[open] {
|
||||
border-color: color-mix(in srgb, var(--agent-reasoning-accent) 28%, transparent);
|
||||
background: color-mix(in srgb, var(--agent-reasoning-accent) 3.5%, var(--background));
|
||||
box-shadow: 0 8px 24px color-mix(in srgb, var(--agent-reasoning-accent) 7%, transparent);
|
||||
}
|
||||
|
||||
.agent-reasoning-summary {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
padding: 9px 10px;
|
||||
color: color-mix(in srgb, var(--foreground) 72%, transparent);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.agent-reasoning-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.agent-reasoning-summary:hover {
|
||||
background: color-mix(in srgb, var(--agent-reasoning-accent) 4%, transparent);
|
||||
}
|
||||
|
||||
.agent-reasoning-icon {
|
||||
display: grid;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
flex: 0 0 25px;
|
||||
place-items: center;
|
||||
border: 1px solid color-mix(in srgb, var(--agent-reasoning-accent) 24%, transparent);
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--agent-reasoning-accent) 11%, transparent);
|
||||
color: var(--agent-reasoning-accent);
|
||||
}
|
||||
|
||||
.agent-reasoning-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.agent-reasoning-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: color-mix(in srgb, var(--foreground) 78%, transparent);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
line-height: 17px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-reasoning-subtitle {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: color-mix(in srgb, var(--foreground) 43%, transparent);
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-reasoning-status {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--foreground) 4%, transparent);
|
||||
padding: 2px 7px;
|
||||
color: color-mix(in srgb, var(--foreground) 45%, transparent);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.agent-reasoning-status.is-live {
|
||||
border-color: color-mix(in srgb, var(--agent-reasoning-accent) 22%, transparent);
|
||||
background: color-mix(in srgb, var(--agent-reasoning-accent) 9%, transparent);
|
||||
color: var(--agent-reasoning-accent);
|
||||
}
|
||||
|
||||
.agent-reasoning-status-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 13%, transparent);
|
||||
animation: agent-reasoning-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.agent-reasoning-chevron {
|
||||
display: grid;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
place-items: center;
|
||||
color: color-mix(in srgb, var(--foreground) 42%, transparent);
|
||||
font-size: 17px;
|
||||
line-height: 12px;
|
||||
transform: translateY(-1px);
|
||||
transition: color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.agent-reasoning-card[open] .agent-reasoning-chevron {
|
||||
color: var(--agent-reasoning-accent);
|
||||
transform: rotate(180deg) translateY(1px);
|
||||
}
|
||||
|
||||
.agent-reasoning-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
max-height: min(232px, 30vh);
|
||||
gap: 10px;
|
||||
overflow: auto;
|
||||
border-top: 1px solid color-mix(in srgb, var(--foreground) 7%, transparent);
|
||||
padding: 10px 12px 12px 14px;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.agent-reasoning-rail {
|
||||
width: 2px;
|
||||
flex: 0 0 2px;
|
||||
align-self: stretch;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--agent-reasoning-accent) 72%, transparent), color-mix(in srgb, var(--agent-reasoning-accent) 12%, transparent));
|
||||
}
|
||||
|
||||
.agent-reasoning-text {
|
||||
min-width: 0;
|
||||
color: color-mix(in srgb, var(--foreground) 58%, transparent);
|
||||
font-size: 12px;
|
||||
line-height: 1.75;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
@keyframes agent-reasoning-pulse {
|
||||
0%, 100% { opacity: .55; transform: scale(.82); }
|
||||
50% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-reasoning-card,
|
||||
.agent-reasoning-chevron { transition: none; }
|
||||
|
||||
.agent-reasoning-status-dot { animation: none; }
|
||||
}
|
||||
|
||||
.agent-working-indicator {
|
||||
display: inline-flex;
|
||||
min-height: 28px;
|
||||
|
||||
Reference in New Issue
Block a user