feat(canvas): 云端 Agent - 缩减画布目录上下文并展示实时用量
This commit is contained in:
@@ -735,6 +735,7 @@ export function AgentChatComposer({
|
||||
onAddFiles,
|
||||
onRemoveAttachment,
|
||||
left,
|
||||
submitAccessory,
|
||||
onStop,
|
||||
stopping,
|
||||
references = [],
|
||||
@@ -755,6 +756,8 @@ export function AgentChatComposer({
|
||||
onAddFiles?: (files: FileList | File[] | null) => void | Promise<void>;
|
||||
onRemoveAttachment?: (id: string) => void;
|
||||
left?: ReactNode;
|
||||
/** 发送按钮左侧的附属控件,例如上下文用量环。 */
|
||||
submitAccessory?: ReactNode;
|
||||
/** 供「@」插入的画布节点/素材/技能引用候选(可选,默认空,缺省时退化为普通输入框) */
|
||||
references?: CanvasResourceReference[];
|
||||
/** 供「/」弹出的技能候选(可选) */
|
||||
@@ -1020,12 +1023,7 @@ export function AgentChatComposer({
|
||||
{left}
|
||||
</div>
|
||||
<div className="agent-composer-submit flex items-center gap-2">
|
||||
{disabled ? null : (
|
||||
<span className="agent-composer-send-hint">
|
||||
<span className="agent-composer-send-hint-full">{canStop ? "运行中:发送即插话,下一步生效" : "Enter 发送 · Shift+Enter 换行"}</span>
|
||||
<span className="agent-composer-send-hint-compact">{canStop ? "运行中可插话" : "Enter 发送"}</span>
|
||||
</span>
|
||||
)}
|
||||
{submitAccessory}
|
||||
{canStop ? (
|
||||
<motion.button
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type Dispatch, type SetStateAction } from "react";
|
||||
import { Button, Dropdown, Input } from "antd";
|
||||
import { Button, Dropdown, Input, Popover } from "antd";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
import { ArrowLeft, Check, ChevronRight, CircleDot, Clock3, Download, History, LoaderCircle, MessageSquarePlus, MoveDiagonal2, RotateCcw, Settings2, ShieldCheck, Trash2, Sparkles, X } from "lucide-react";
|
||||
import { saveAs } from "file-saver";
|
||||
@@ -7,6 +7,7 @@ import { buildAgentDebugExport } from "@/lib/canvas/agent-debug-export";
|
||||
import { markdownPlainText } from "@/lib/markdown-plain-text";
|
||||
import { agentToolRetry, mergeAgentToolRetry } from "@/lib/canvas/agent-tool-retry";
|
||||
import { agentPlanVisible, latestAgentPlanItems, pendingAgentQuestion } from "@/lib/canvas/cloud-agent-plan";
|
||||
import { emptyAgentContextUsage, presentAgentContextUsage, reduceAgentContextUsage, type AgentContextPhase, type AgentContextUsage, type AgentContextUsageView } from "@/lib/canvas/agent-context-usage";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { ModelPicker } from "@/components/model-picker";
|
||||
@@ -55,6 +56,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
const reducedMotion = useReducedMotion();
|
||||
const [view, setView] = useState<AgentPanelView>("chat");
|
||||
const [run, setRun] = useState<AgentRun | null>(null);
|
||||
const [contextUsage, setContextUsage] = useState<AgentContextUsage>(() => emptyAgentContextUsage(""));
|
||||
const [connectionStatus, setConnectionStatus] = useState<"connecting" | "connected" | "reconnecting" | "disconnected">("connecting");
|
||||
const [connectionEpoch, setConnectionEpoch] = useState(0);
|
||||
const [messages, setMessages] = useState<CloudAgentChatMessage[]>([]);
|
||||
@@ -462,6 +464,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
lastSeqRef.current = event.seq;
|
||||
}
|
||||
setMessages((current) => current.filter((item) => item.id !== `stream-error-${run.id}`));
|
||||
setContextUsage((current) => reduceAgentContextUsage(current, event));
|
||||
applyAgentEvent(event, setMessages, setRun, setApproval, setPrompt);
|
||||
canvasSyncRef.current?.receive(event);
|
||||
},
|
||||
@@ -570,7 +573,10 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
setMessages(nextMessages);
|
||||
const result = submission.parentRunId ? await sendAgentMessage(submission.parentRunId, request) : await createAgentRun(request);
|
||||
accepted = true;
|
||||
if (currentScope.current === scope) setRun(result.run);
|
||||
if (currentScope.current === scope) {
|
||||
setContextUsage(emptyAgentContextUsage(result.run.id));
|
||||
setRun(result.run);
|
||||
}
|
||||
await clearCloudAgentPendingSubmission(canvasId, activeConversationId);
|
||||
if (currentScope.current === scope) pendingSubmission.current = null;
|
||||
} catch (cause) {
|
||||
@@ -898,6 +904,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
references={[...references, ...buildSkillMentionReferences(installedSkills)]}
|
||||
slashSkills={installedSkills}
|
||||
includeAssetLibrary={false}
|
||||
submitAccessory={<AgentContextRing view={presentAgentContextUsage(contextUsage)} />}
|
||||
left={
|
||||
<ComposerControls
|
||||
reasoningMode={reasoningSupported ? reasoningMode : "off"}
|
||||
@@ -971,7 +978,7 @@ function AgentLauncher({ theme, statusColor, approvalPending, reducedMotion, onO
|
||||
whileTap={reducedMotion || dragging ? undefined : { scale: 0.96 }}
|
||||
transition={{ duration: reducedMotion ? 0 : 0.18 }}
|
||||
>
|
||||
{live ? <Live2DAvatar url={live2DModelURL(appearance.live2dResourceId, appearance.live2dEntry)} width={width} height={height} reducedMotion={reducedMotion} fallback={<FluidOrb size={62} color="#7164f6" />} /> : <FluidOrb size={62} color="#7164f6" />}
|
||||
{live ? <Live2DAvatar url={live2DModelURL(appearance.live2dResourceId, appearance.live2dEntry)} width={width} height={height} reducedMotion={reducedMotion} fallback={<FluidOrb size={60} color="#7164f6" />} /> : <FluidOrb size={60} color="#7164f6" />}
|
||||
{appearance.launcherLabel ? <span className="canvas-agent-launcher-label">{appearance.launcherLabel}</span> : null}
|
||||
<span className={cn("canvas-agent-launcher-status", approvalPending && "is-pending")} style={{ "--canvas-agent-status-color": statusColor } as CSSProperties} />
|
||||
{approvalPending ? <span className="canvas-agent-launcher-badge">待审批</span> : null}
|
||||
@@ -1005,6 +1012,165 @@ function AgentHeader({ theme, hasMessages, statusLabel, statusColor, nodeCount,
|
||||
);
|
||||
}
|
||||
|
||||
const CONTEXT_PHASE_LABEL: Record<AgentContextPhase, string> = {
|
||||
idle: "尚未测量",
|
||||
unknown: "窗口未知",
|
||||
ok: "上下文充足",
|
||||
watch: "接近压缩",
|
||||
compress: "即将压缩",
|
||||
compacting: "正在压缩",
|
||||
stale: "压缩后待刷新",
|
||||
};
|
||||
|
||||
function formatContextCount(tokens: number | undefined) {
|
||||
if (tokens === undefined) return "—";
|
||||
if (tokens >= 1_000_000) return `${Math.round(tokens / 100_000) / 10}M`;
|
||||
if (tokens >= 1_000) return `${Math.round(tokens / 100) / 10}K`;
|
||||
return Math.round(tokens).toLocaleString("zh-CN");
|
||||
}
|
||||
|
||||
|
||||
function formatContextBytes(bytes: number | undefined) {
|
||||
if (bytes === undefined) return "—";
|
||||
if (bytes >= 1_000_000) return `${Math.round(bytes / 100_000) / 10} MB`;
|
||||
if (bytes >= 1_000) return `${Math.round(bytes / 100) / 10} KB`;
|
||||
return `${Math.round(bytes).toLocaleString("zh-CN")} 字节`;
|
||||
}
|
||||
|
||||
function AgentContextRing({ view }: { view: AgentContextUsageView }) {
|
||||
const reducedMotion = useReducedMotion();
|
||||
const [open, setOpen] = useState(false);
|
||||
const radius = 15;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const marker = view.compactRatio && view.compactRatio > 0 && view.compactRatio < 1 ? view.compactRatio : undefined;
|
||||
const percent = view.ratio === undefined ? view.label : `${Math.round(view.ratio * 100)}%`;
|
||||
const meterLabel = view.ratio === undefined ? "—" : percent;
|
||||
const used = formatContextCount(view.inputTokens);
|
||||
const budget = formatContextCount(view.usableTokens);
|
||||
const remaining = formatContextCount(view.remainingTokens);
|
||||
const protocolBytes = formatContextBytes(view.protocolBytes);
|
||||
const usedRatio = view.ratio === undefined ? 0 : Math.max(0, Math.min(1, view.ratio));
|
||||
const phaseLabel = CONTEXT_PHASE_LABEL[view.phase];
|
||||
const sourceLabel = view.tokenSource === "provider" ? "模型实测校准" : view.estimate ? "本地估算" : "未测量";
|
||||
const usageHeading = view.ratio !== undefined
|
||||
? `上下文已用 ${percent}`
|
||||
: view.phase === "idle"
|
||||
? "上下文用量"
|
||||
: view.phase === "unknown"
|
||||
? "上下文窗口未知"
|
||||
: `上下文${view.label}`;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
arrow={false}
|
||||
overlayClassName="agent-context-popover"
|
||||
getPopupContainer={(trigger) => trigger.closest<HTMLElement>(".canvas-agent-panel") ?? document.body}
|
||||
content={(
|
||||
<div className="agent-context-panel" data-phase={view.phase}>
|
||||
<span className="agent-context-eyebrow">下一次请求</span>
|
||||
<div className="agent-context-panel-head">
|
||||
<strong>{usageHeading}</strong>
|
||||
{view.phase !== "ok" ? <span className={`agent-context-phase is-${view.phase}`}>{phaseLabel}</span> : null}
|
||||
</div>
|
||||
<div className="agent-context-summary">
|
||||
{view.remainingTokens !== undefined && view.usableTokens !== undefined ? (
|
||||
<>
|
||||
<strong>{used}</strong>
|
||||
<span>/ {budget} Token</span>
|
||||
<em>剩余 {remaining}</em>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>{used}</strong>
|
||||
<span>Token</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="agent-context-progress-head">
|
||||
<span>输入预算占用</span>
|
||||
<strong>{percent}</strong>
|
||||
</div>
|
||||
<div
|
||||
className="agent-context-progress"
|
||||
role="progressbar"
|
||||
aria-label={`上下文已用 ${percent}`}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={view.ratio === undefined ? undefined : Math.round(view.ratio * 100)}
|
||||
>
|
||||
<span style={{ width: `${usedRatio * 100}%` }} />
|
||||
{marker ? <i style={{ left: `${marker * 100}%` }} aria-hidden="true" /> : null}
|
||||
</div>
|
||||
<p className="agent-context-detail">{view.detail}</p>
|
||||
{view.breakdown.length || view.protocolBytes !== undefined || view.remainingTokens !== undefined ? (
|
||||
<ul className="agent-context-breakdown">
|
||||
{view.breakdown.map((item) => (
|
||||
<li key={item.key}>
|
||||
<span className="agent-context-breakdown-dot" aria-hidden="true" />
|
||||
<span className="agent-context-breakdown-label">{item.label}</span>
|
||||
<span className="agent-context-breakdown-value">{formatContextCount(item.tokens)}</span>
|
||||
</li>
|
||||
))}
|
||||
{view.protocolBytes !== undefined ? (
|
||||
<li className="is-secondary">
|
||||
<span className="agent-context-breakdown-dot" aria-hidden="true" />
|
||||
<span className="agent-context-breakdown-label">协议外壳</span>
|
||||
<span className="agent-context-breakdown-value">{protocolBytes}</span>
|
||||
</li>
|
||||
) : null}
|
||||
{view.remainingTokens !== undefined ? (
|
||||
<li className="is-muted">
|
||||
<span className="agent-context-breakdown-dot" aria-hidden="true" />
|
||||
<span className="agent-context-breakdown-label">未使用</span>
|
||||
<span className="agent-context-breakdown-value">{remaining}</span>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
) : null}
|
||||
<div className="agent-context-panel-foot">
|
||||
<span>{sourceLabel}{view.estimate ? " · 不是计费 Token" : " · 预计下次请求"}</span>
|
||||
{view.compactAtTokens ? <span>压缩线 {formatContextCount(view.compactAtTokens)}</span> : null}
|
||||
</div>
|
||||
{view.lastCompaction ? <p className="agent-context-note">本轮已完成一次上下文压缩,下一次读数会刷新。</p> : null}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`agent-context-ring is-${view.phase}`}
|
||||
aria-label={`${usageHeading},${phaseLabel}。点击查看明细`}
|
||||
aria-expanded={open}
|
||||
title="查看上下文用量"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<span className="agent-context-ring-visual" aria-hidden="true">
|
||||
<svg viewBox="0 0 40 40">
|
||||
<circle className="agent-context-ring-track" cx="20" cy="20" r={radius} />
|
||||
<circle
|
||||
className="agent-context-ring-value"
|
||||
cx="20"
|
||||
cy="20"
|
||||
r={radius}
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
strokeDashoffset={circumference * (1 - view.ring)}
|
||||
style={{ transition: reducedMotion ? "none" : undefined }}
|
||||
/>
|
||||
{marker ? <circle className="agent-context-ring-marker" cx="20" cy={20 - radius} r="1.35" transform={`rotate(${marker * 360} 20 20)`} /> : null}
|
||||
</svg>
|
||||
</span>
|
||||
<span className="agent-context-meter-copy">
|
||||
<strong>{meterLabel}</strong>
|
||||
<small>上下文</small>
|
||||
</span>
|
||||
</button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function AgentHistory({ conversations, activeConversationId, theme, onBack, onNew, onOpen, onDelete }: { conversations: CloudAgentConversation[]; activeConversationId: string; theme: CanvasTheme; onBack: () => void; onNew: () => void; onOpen: (conversation: CloudAgentConversation) => void; onDelete: (id: string) => void }) {
|
||||
return (
|
||||
<div className="canvas-agent-history-root flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
@@ -1153,14 +1319,15 @@ function ComposerControls({
|
||||
const permissionVisual = agentPermissionVisual(permissionMode);
|
||||
const PermissionIcon = permissionVisual.icon;
|
||||
return (
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-0.5">
|
||||
<div className="agent-composer-selection flex min-w-0 flex-1 flex-nowrap items-center gap-0.5">
|
||||
<ModelPicker
|
||||
config={config}
|
||||
value={selectedModel}
|
||||
capability="text"
|
||||
onChange={onModelChange}
|
||||
variant="creation"
|
||||
className="!h-8 !min-w-0 !w-52 !max-w-full !border-0 !bg-transparent !px-1.5 !shadow-none"
|
||||
fullWidth
|
||||
className="agent-composer-model-trigger !h-8 !min-w-0 !w-full !max-w-full !border-0 !bg-transparent !px-1.5 !shadow-none"
|
||||
popoverClassName="agent-model-picker-popover"
|
||||
showSelectedPrice={false}
|
||||
showOptionPrices
|
||||
|
||||
@@ -58,7 +58,7 @@ const permissionOptions: Array<{ value: AgentPermissionMode; label: string; desc
|
||||
];
|
||||
|
||||
const contextOptions: Array<{ value: AgentContextKey; label: string; description: string }> = [
|
||||
{ value: "canvas", label: "已保存画布摘要", description: "最多 80 个节点的标题与文本片段,不含媒体正文或未同步修改" },
|
||||
{ value: "canvas", label: "画布节点目录", description: "每次请求只带节点 id、类型和标题;正文按页读取,不含媒体或未同步修改" },
|
||||
];
|
||||
|
||||
const reasoningOptions: Array<{ value: AgentReasoningMode; label: string; description: string }> = [
|
||||
|
||||
@@ -50,6 +50,320 @@
|
||||
font-size: var(--fs-label);
|
||||
}
|
||||
|
||||
.agent-context-ring {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 68px;
|
||||
height: 32px;
|
||||
padding: 3px 8px 3px 4px;
|
||||
border: 1px solid color-mix(in srgb, var(--agent-ink) 10%, transparent);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--agent-ink) 4%, transparent);
|
||||
color: var(--agent-caption);
|
||||
cursor: pointer;
|
||||
transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease;
|
||||
}
|
||||
|
||||
.agent-context-ring:hover,
|
||||
.agent-context-ring[aria-expanded="true"] {
|
||||
border-color: color-mix(in srgb, var(--agent-accent) 48%, transparent);
|
||||
background: var(--agent-surface-raised);
|
||||
color: var(--agent-ink);
|
||||
}
|
||||
|
||||
.agent-context-ring:focus-visible {
|
||||
outline: 2px solid var(--agent-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.agent-context-ring-visual,
|
||||
.agent-context-ring-visual svg {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.agent-context-ring-visual svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.agent-context-ring circle {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.agent-context-ring-track {
|
||||
stroke: color-mix(in srgb, var(--agent-ink) 18%, transparent);
|
||||
stroke-width: 2.4;
|
||||
}
|
||||
|
||||
.agent-context-ring-value {
|
||||
stroke: var(--agent-accent, currentColor);
|
||||
stroke-width: 2.4;
|
||||
transition: stroke-dashoffset 240ms ease;
|
||||
}
|
||||
|
||||
.agent-context-ring-marker {
|
||||
fill: var(--agent-ink);
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.agent-context-ring.is-watch .agent-context-ring-value { stroke: #d9973b; }
|
||||
.agent-context-ring.is-compress .agent-context-ring-value,
|
||||
.agent-context-ring.is-compacting .agent-context-ring-value { stroke: #d4534e; }
|
||||
.agent-context-ring.is-compacting .agent-context-ring-visual svg { animation: agent-context-spin 1.4s linear infinite; }
|
||||
.agent-context-ring.is-unknown .agent-context-ring-value,
|
||||
.agent-context-ring.is-stale .agent-context-ring-value { stroke-dasharray: 4 6; }
|
||||
|
||||
.agent-context-meter-copy {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
line-height: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.agent-context-meter-copy strong {
|
||||
color: var(--agent-ink);
|
||||
font-size: 11px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agent-context-meter-copy small {
|
||||
margin-top: 2px;
|
||||
color: var(--agent-caption);
|
||||
font-size: 9px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes agent-context-spin {
|
||||
to { transform: rotate(270deg); }
|
||||
}
|
||||
|
||||
.agent-context-popover.ant-popover,
|
||||
.agent-context-popover .ant-popover-content,
|
||||
.agent-context-popover .ant-popover-inner {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.agent-context-popover .ant-popover-inner {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--agent-ink) 12%, transparent);
|
||||
border-radius: 16px;
|
||||
background: var(--agent-surface-raised, var(--background));
|
||||
}
|
||||
|
||||
.agent-context-popover .ant-popover-inner-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.agent-context-panel {
|
||||
width: min(348px, calc(100vw - 32px));
|
||||
padding: 14px 15px 12px;
|
||||
color: var(--agent-ink, inherit);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.agent-context-eyebrow {
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
color: var(--agent-caption);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.agent-context-panel-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.agent-context-panel-head strong {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.015em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.agent-context-phase {
|
||||
flex: none;
|
||||
border-radius: 999px;
|
||||
padding: 3px 8px;
|
||||
background: color-mix(in srgb, currentColor 12%, transparent);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.agent-context-phase.is-watch { color: #9a6416; }
|
||||
.agent-context-phase.is-compress,
|
||||
.agent-context-phase.is-compacting { color: #a33b36; }
|
||||
|
||||
.agent-context-summary {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
margin-top: 12px;
|
||||
color: var(--agent-caption);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.agent-context-summary strong {
|
||||
color: var(--agent-ink);
|
||||
font-size: 23px;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.agent-context-summary span {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.agent-context-summary em {
|
||||
margin-left: auto;
|
||||
color: var(--agent-caption);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.agent-context-progress-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 13px;
|
||||
color: var(--agent-caption);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.agent-context-progress-head strong {
|
||||
color: var(--agent-ink);
|
||||
font-size: 13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.agent-context-progress {
|
||||
position: relative;
|
||||
height: 12px;
|
||||
margin-top: 5px;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: rgb(255 255 255 / 14%);
|
||||
background: color-mix(in srgb, var(--agent-ink, #fff) 24%, var(--agent-surface-raised, #252525));
|
||||
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 10%);
|
||||
}
|
||||
|
||||
.agent-context-progress > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
min-width: 6px;
|
||||
border-radius: inherit;
|
||||
background: #8b93ff;
|
||||
background: linear-gradient(90deg, color-mix(in srgb, var(--agent-accent, #8b93ff) 58%, white), var(--agent-accent, #8b93ff));
|
||||
box-shadow: 0 0 12px color-mix(in srgb, var(--agent-accent, #8b93ff) 48%, transparent);
|
||||
transition: width 240ms ease;
|
||||
}
|
||||
|
||||
.agent-context-progress > i {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
bottom: -3px;
|
||||
width: 1px;
|
||||
background: color-mix(in srgb, var(--agent-ink) 64%, transparent);
|
||||
}
|
||||
|
||||
.agent-context-detail {
|
||||
margin: 10px 0 0;
|
||||
color: var(--agent-caption);
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.agent-context-breakdown {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 13px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.agent-context-breakdown li {
|
||||
display: grid;
|
||||
grid-template-columns: 9px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.agent-context-breakdown-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--agent-accent) 62%, var(--agent-ink));
|
||||
}
|
||||
|
||||
.agent-context-breakdown-label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--agent-ink);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-context-breakdown-value {
|
||||
min-width: 58px;
|
||||
color: var(--agent-ink);
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.agent-context-breakdown li.is-secondary .agent-context-breakdown-dot {
|
||||
background: color-mix(in srgb, var(--agent-ink) 42%, transparent);
|
||||
}
|
||||
|
||||
.agent-context-breakdown li.is-secondary .agent-context-breakdown-label,
|
||||
.agent-context-breakdown li.is-secondary .agent-context-breakdown-value {
|
||||
color: var(--agent-caption);
|
||||
}
|
||||
|
||||
.agent-context-breakdown li.is-muted .agent-context-breakdown-dot {
|
||||
background: color-mix(in srgb, var(--agent-ink) 24%, transparent);
|
||||
}
|
||||
|
||||
.agent-context-breakdown li.is-muted .agent-context-breakdown-label,
|
||||
.agent-context-breakdown li.is-muted .agent-context-breakdown-value {
|
||||
color: var(--agent-caption);
|
||||
}
|
||||
|
||||
.agent-context-panel-foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-top: 13px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--agent-ink) 9%, transparent);
|
||||
color: var(--agent-caption);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.agent-context-note {
|
||||
margin: 6px 0 0;
|
||||
color: var(--agent-caption);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-context-ring.is-compacting .agent-context-ring-visual svg { animation: none; }
|
||||
.agent-context-ring-value,
|
||||
.agent-context-progress > span { transition: none; }
|
||||
}
|
||||
|
||||
.agent-header-actions .ant-btn {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -335,6 +649,10 @@
|
||||
}
|
||||
.agent-panel-status { white-space: nowrap; }
|
||||
.agent-conversation { padding-inline: var(--space-4); }
|
||||
.agent-context-ring { min-width: 32px; padding-right: 4px; gap: 0; }
|
||||
.agent-context-meter-copy { display: none; }
|
||||
.agent-context-panel-foot { display: grid; }
|
||||
|
||||
.agent-welcome-actions button { padding-inline: var(--space-2); }
|
||||
}
|
||||
|
||||
@@ -613,15 +931,47 @@
|
||||
}
|
||||
|
||||
.agent-composer-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: end;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.agent-composer-submit {
|
||||
.agent-composer-controls {
|
||||
min-width: 0;
|
||||
justify-self: end;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-composer-selection {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-composer-selection > .w-full {
|
||||
min-width: 0;
|
||||
max-width: 13rem;
|
||||
flex: 1 1 13rem;
|
||||
}
|
||||
|
||||
.agent-composer-selection .agent-composer-model-trigger,
|
||||
.agent-composer-selection .canvas-model-picker-label {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-composer-selection > button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.agent-composer-submit {
|
||||
display: flex;
|
||||
min-width: max-content;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.canvas-agent-launcher {
|
||||
@@ -632,7 +982,7 @@
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
padding: 7px;
|
||||
padding: 0;
|
||||
box-shadow: 0 16px 38px color-mix(in srgb, var(--canvas-agent-launcher-shadow) 64%, transparent);
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
@@ -644,6 +994,15 @@
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.canvas-agent-launcher > .fluid-orb {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.canvas-agent-launcher::before {
|
||||
position: absolute;
|
||||
inset: 3px;
|
||||
@@ -720,27 +1079,6 @@
|
||||
.canvas-agent-launcher { transition: none !important; }
|
||||
}
|
||||
|
||||
.agent-composer-send-hint {
|
||||
color: color-mix(in srgb, currentColor 52%, transparent);
|
||||
font-size: var(--fs-tiny);
|
||||
line-height: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 面板宽度包含内边距;默认 448px 面板放不下完整快捷键说明,窄态降级为短文案而不是隐藏提示本身。 */
|
||||
.agent-composer-send-hint .agent-composer-send-hint-compact {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@container (max-width: 520px) {
|
||||
.agent-composer-send-hint .agent-composer-send-hint-full {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.agent-composer-send-hint .agent-composer-send-hint-compact {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.agent-tool-status {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user