mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 17:04:27 +08:00
feat(agent): add task status and client-scoped operations
This commit is contained in:
@@ -4,6 +4,7 @@ import { fetchPrompts } from "@/services/api/prompts";
|
||||
import { uploadImage } from "@/services/image-storage";
|
||||
import { imageAspectOptions, imageQualityOptions } from "@/components/image-settings-panel";
|
||||
import { videoResolutionOptions, videoSecondOptions, videoSizeOptions } from "@/components/video-settings-panel";
|
||||
import type { CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops";
|
||||
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
|
||||
import { useAssetStore } from "@/stores/use-asset-store";
|
||||
import { modelOptionLabel, modelOptionName, normalizeModelOptionValue, selectableModelsByCapability, useConfigStore } from "@/stores/use-config-store";
|
||||
@@ -14,6 +15,7 @@ import { useWorkbenchAgentStore } from "@/stores/use-workbench-agent-store";
|
||||
|
||||
export const SITE_TOOL_NAMES = [
|
||||
"canvas_list_projects",
|
||||
"generation_get_status",
|
||||
"workbench_image_get_config",
|
||||
"workbench_image_generate",
|
||||
"workbench_video_get_config",
|
||||
@@ -31,6 +33,7 @@ export function isSiteTool(name: string): name is SiteToolName {
|
||||
|
||||
export const SITE_TOOL_LABELS: Record<SiteToolName, string> = {
|
||||
canvas_list_projects: "画布列表",
|
||||
generation_get_status: "生成任务状态",
|
||||
workbench_image_get_config: "生图配置",
|
||||
workbench_image_generate: "生图工作台生成",
|
||||
workbench_video_get_config: "视频配置",
|
||||
@@ -41,11 +44,16 @@ export const SITE_TOOL_LABELS: Record<SiteToolName, string> = {
|
||||
};
|
||||
|
||||
type SiteToolInput = Record<string, unknown>;
|
||||
type SiteToolContext = { canvasSnapshot?: CanvasAgentSnapshot | null };
|
||||
type GenerationStatus = "idle" | "queued" | "running" | "succeeded" | "failed";
|
||||
type GenerationStatusItem = { id: string; source: "canvas" | "image" | "video"; status: GenerationStatus; kind?: string; title?: string; prompt?: string; projectId?: string; createdAt?: string; updatedAt?: string; successCount?: number; failCount?: number; error?: string };
|
||||
|
||||
export async function runSiteTool(name: SiteToolName, input: SiteToolInput, navigate: NavigateFunction): Promise<unknown> {
|
||||
export async function runSiteTool(name: SiteToolName, input: SiteToolInput, navigate: NavigateFunction, context: SiteToolContext = {}): Promise<unknown> {
|
||||
switch (name) {
|
||||
case "canvas_list_projects":
|
||||
return listCanvasProjects(input);
|
||||
case "generation_get_status":
|
||||
return getGenerationStatus(input, context.canvasSnapshot);
|
||||
case "workbench_image_get_config":
|
||||
return getImageConfig();
|
||||
case "workbench_image_generate":
|
||||
@@ -65,6 +73,56 @@ export async function runSiteTool(name: SiteToolName, input: SiteToolInput, navi
|
||||
}
|
||||
}
|
||||
|
||||
function getGenerationStatus(input: SiteToolInput, canvasSnapshot?: CanvasAgentSnapshot | null) {
|
||||
const scope = input.scope === "canvas" || input.scope === "image" || input.scope === "video" ? input.scope : "all";
|
||||
const taskId = typeof input.taskId === "string" ? input.taskId : "";
|
||||
const nodeIds = new Set(Array.isArray(input.nodeIds) ? input.nodeIds.filter((id): id is string => typeof id === "string") : []);
|
||||
const limit = Math.max(1, Math.min(100, Math.floor(Number(input.limit)) || 20));
|
||||
const tasks: GenerationStatusItem[] = [];
|
||||
const includeCanvas = (scope === "all" || scope === "canvas") && (!taskId || nodeIds.size > 0);
|
||||
const includeWorkbench = !nodeIds.size || Boolean(taskId);
|
||||
|
||||
if (includeCanvas && canvasSnapshot) {
|
||||
canvasSnapshot.nodes.forEach((node) => {
|
||||
const status = normalizeCanvasGenerationStatus(node.metadata?.status);
|
||||
if (!status || (nodeIds.size && !nodeIds.has(node.id))) return;
|
||||
const metadata = node.metadata || {};
|
||||
if (!nodeIds.size && node.type !== "config" && status !== "running" && status !== "failed" && !metadata.generationMode && !metadata.generationType && !metadata.model) return;
|
||||
tasks.push({ id: node.id, source: "canvas", status, kind: metadata.generationMode || node.type, title: node.title, prompt: compactPrompt(metadata.prompt || metadata.composerContent), projectId: canvasSnapshot.projectId, error: metadata.errorDetails });
|
||||
});
|
||||
}
|
||||
|
||||
if (includeWorkbench) {
|
||||
useWorkbenchAgentStore.getState().tasks.forEach((task) => {
|
||||
if ((scope === "image" || scope === "video") && task.kind !== scope) return;
|
||||
if (scope === "canvas" || (taskId && task.id !== taskId)) return;
|
||||
tasks.push({ ...task, source: task.kind, prompt: compactPrompt(task.prompt) });
|
||||
});
|
||||
}
|
||||
|
||||
tasks.sort((a, b) => generationStatusOrder(a.status) - generationStatusOrder(b.status) || (b.updatedAt || "").localeCompare(a.updatedAt || ""));
|
||||
const summary: Record<GenerationStatus, number> = { idle: 0, queued: 0, running: 0, succeeded: 0, failed: 0 };
|
||||
tasks.forEach((task) => (summary[task.status] += 1));
|
||||
return { total: tasks.length, summary, tasks: tasks.slice(0, limit) };
|
||||
}
|
||||
|
||||
function generationStatusOrder(status: GenerationStatus) {
|
||||
return status === "running" ? 0 : status === "queued" ? 1 : 2;
|
||||
}
|
||||
|
||||
function normalizeCanvasGenerationStatus(status: unknown): GenerationStatus | null {
|
||||
if (status === "idle") return "idle";
|
||||
if (status === "loading") return "running";
|
||||
if (status === "success") return "succeeded";
|
||||
if (status === "error") return "failed";
|
||||
return null;
|
||||
}
|
||||
|
||||
function compactPrompt(prompt: unknown) {
|
||||
const value = typeof prompt === "string" ? prompt.trim() : "";
|
||||
return value ? `${value.slice(0, 200)}${value.length > 200 ? "..." : ""}` : undefined;
|
||||
}
|
||||
|
||||
function listCanvasProjects(input: SiteToolInput) {
|
||||
const { projects, hydrated } = useCanvasStore.getState();
|
||||
if (!hydrated) throw new Error("画布还在加载中,请稍后重试");
|
||||
@@ -118,8 +176,8 @@ function runImageWorkbench(input: SiteToolInput, navigate: NavigateFunction) {
|
||||
const prompt = typeof input.prompt === "string" ? input.prompt : undefined;
|
||||
const run = input.run !== false;
|
||||
navigate("/image");
|
||||
useWorkbenchAgentStore.getState().dispatchImage({ prompt, run });
|
||||
return { ok: true, navigated: "/image", prompt, run, applied, note: run ? "已跳转生图工作台并触发生成,结果请稍后在工作台查看" : "已跳转生图工作台并填入参数,未触发生成" };
|
||||
const taskId = useWorkbenchAgentStore.getState().dispatchImage({ prompt, run });
|
||||
return { ok: true, navigated: "/image", prompt, run, taskId, applied, note: run ? "已跳转生图工作台并触发生成,可用 generation_get_status 查询任务" : "已跳转生图工作台并填入参数,未触发生成" };
|
||||
}
|
||||
|
||||
function getVideoConfig() {
|
||||
@@ -173,8 +231,8 @@ function runVideoWorkbench(input: SiteToolInput, navigate: NavigateFunction) {
|
||||
const prompt = typeof input.prompt === "string" ? input.prompt : undefined;
|
||||
const run = input.run !== false;
|
||||
navigate("/video");
|
||||
useWorkbenchAgentStore.getState().dispatchVideo({ prompt, run });
|
||||
return { ok: true, navigated: "/video", prompt, run, applied, note: run ? "已跳转视频创作台并触发生成,结果请稍后在工作台查看" : "已跳转视频创作台并填入参数,未触发生成" };
|
||||
const taskId = useWorkbenchAgentStore.getState().dispatchVideo({ prompt, run });
|
||||
return { ok: true, navigated: "/video", prompt, run, taskId, applied, note: run ? "已跳转视频创作台并触发生成,可用 generation_get_status 查询任务" : "已跳转视频创作台并填入参数,未触发生成" };
|
||||
}
|
||||
|
||||
async function searchPrompts(input: SiteToolInput) {
|
||||
|
||||
Reference in New Issue
Block a user