feat(*): 云端 Agent - 增强画布上下文读取并接入 Gemini 缓存

This commit is contained in:
ddcat666 committed 2026-09-26 02:21:47 +08:00
1 parent 2ce50bbb9d
commit 2cedc4c66b
35 files changed
+1927 -266

No files matched your search

@@ -6,7 +6,7 @@ import { saveAs } from "file-saver";
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 { agentPlanVisible, latestAgentPlanItems, latestAgentPlanTerminal, 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";
@@ -43,11 +43,11 @@ import { live2DModelURL } from "@/services/api/appearance";
import { Live2DAvatar } from "./live2d-avatar";
import "./canvas-cloud-agent.css";
type CloudAgentPanelProps = { canvasId: string; domainProjectId?: string; nodeCount: number; references: CanvasResourceReference[]; open: boolean; prefillPrompt?: string; onOpen: () => void; onCollapse: () => void; onFocusNode?: (nodeId: string) => void };
type CloudAgentPanelProps = { canvasId: string; domainProjectId?: string; nodeCount: number; selectedNodeIds: string[]; references: CanvasResourceReference[]; open: boolean; prefillPrompt?: string; onOpen: () => void; onCollapse: () => void; onFocusNode?: (nodeId: string) => void };
type ApprovalState = { approvalId: string; detail: Record<string, unknown>; reason: string };
type AgentPanelView = "chat" | "history" | "settings";
export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, references, open, prefillPrompt, onOpen, onCollapse, onFocusNode }: CloudAgentPanelProps) {
export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, selectedNodeIds, references, open, prefillPrompt, onOpen, onCollapse, onFocusNode }: CloudAgentPanelProps) {
const userId = useUserStore((state) => state.user?.id);
const appearance = useAppearanceStore((state) => state.appearance.canvas) || DEFAULT_CANVAS_APPEARANCE;
const theme = canvasThemes[useActiveTheme()];
@@ -95,6 +95,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
const [pendingHydrated, setPendingHydrated] = useState(false);
const [planMinimized, setPlanMinimized] = useState(false);
const planItems = useMemo(() => latestAgentPlanItems(messages), [messages]);
const planTerminal = useMemo(() => latestAgentPlanTerminal(messages), [messages]);
const planVisible = agentPlanVisible(planItems);
const pendingQuestion = useMemo(() => pendingAgentQuestion(messages), [messages]);
const [scenePresets, setScenePresets] = useState<SkillPreset[]>([]);
@@ -543,6 +544,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
model: modelOptionName(selectedModel) || undefined,
...(logicalModelId ? { logicalModelId } : requestConfig.channelId ? { channelId: requestConfig.channelId, channelModelKey: modelOptionName(selectedModel) || undefined } : {}),
skillIds: [...new Set([...selectedSkillIds, ...resolveSkillMentions(value, installedSkills).map((skill) => skill.skillId)])],
focusNodeIds: selectedNodeIds.length <= 8 ? selectedNodeIds : [],
permissionMode, contextScope,
budget: { maxCredits: positiveNumber(maxCredits), maxGenerationTasks: permissionMode === "read_only" ? 0 : Number(maxGenerationTasks), maxVideoSeconds: permissionMode === "read_only" ? 0 : Number(maxVideoSeconds) },
};
@@ -871,7 +873,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
onApprove={(settings) => void submitApproval("approve", settings)}
onReject={() => void submitApproval("reject")}
/>
{planVisible ? <AgentPlanBar items={planItems} theme={theme} minimized={planMinimized} onToggle={() => setPlanMinimized((value) => !value)} /> : null}
{planVisible ? <AgentPlanBar items={planItems} theme={theme} minimized={planMinimized} terminal={planTerminal || Boolean(run && ["completed", "failed", "cancelled", "rejected"].includes(run.status))} onToggle={() => setPlanMinimized((value) => !value)} /> : null}
{historyHydrated && !messages.some((message) => message.role === "user" || message.role === "assistant") && !run ? (
<AgentSceneCapsules
buckets={sceneBuckets}
@@ -1457,7 +1459,12 @@ function applyAgentEvent(event: AgentEvent, setMessages: Dispatch<SetStateAction
const text = String(payload.text || payload.summary || payload.message || "");
if (event.type === "run_status") {
const snapshotApproval = payload.approval && typeof payload.approval === "object" ? payload.approval as AgentRun["approval"] : undefined;
setRun((current) => (current ? { ...current, status: String(payload.status || current.status) as AgentRun["status"], updatedAt: event.createdAt, revision: Number(payload.revision || 0), cleanupPending: Boolean(payload.cleanupPending), failureMessage: String(payload.failureMessage || ""), skills: payload.skills as AgentRun["skills"], spentCredits: Number(payload.spentCredits || 0), step: Number(payload.step || 0), approval: snapshotApproval } : current));
const nextStatus = String(payload.status || "") as AgentRun["status"];
const terminal = ["completed", "failed", "cancelled", "rejected"].includes(nextStatus);
setRun((current) => (current ? { ...current, status: nextStatus || current.status, updatedAt: event.createdAt, revision: Number(payload.revision || 0), cleanupPending: Boolean(payload.cleanupPending), failureMessage: String(payload.failureMessage || ""), skills: payload.skills as AgentRun["skills"], spentCredits: Number(payload.spentCredits || 0), step: Number(payload.step || 0), approval: snapshotApproval } : current));
if (terminal) {
setMessages((current) => current.map((message) => message.id === `plan-${event.runId}` && message.planItems?.length ? { ...message, planTerminal: true, streaming: false } : message));
}
if (payload.failureMessage) setMessages((current) => appendAgentError(current, `terminal-${event.runId}`, String(payload.failureMessage)));
if (snapshotApproval && !snapshotApproval.decision && snapshotApproval.approvalId) {
setApproval((current) => ({ approvalId: snapshotApproval.approvalId, detail: snapshotApproval, reason: current?.approvalId === snapshotApproval.approvalId ? current.reason : snapshotApproval.reason || "" }));