140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import { and, eq, inArray, lt, sql } from "drizzle-orm";
|
|
|
|
import type { CanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command";
|
|
import { db } from "@/lib/db";
|
|
import { canvasCapabilityTask } from "@/lib/db/schema";
|
|
|
|
const STALE_RUNNING_TASK_MS = 15 * 60 * 1000;
|
|
const INTERRUPTED_TASK_MESSAGE = "任务执行中断,结果尚未确认。请先查看画布和历史记录,或联系管理员核对原任务,避免重复处理。";
|
|
|
|
export type CanvasCapabilityTaskResult = Record<string, unknown>;
|
|
|
|
export async function getCanvasCapabilityTask(params: {
|
|
requestId: string;
|
|
userId: string;
|
|
projectId: string;
|
|
}) {
|
|
const [task] = await db
|
|
.select()
|
|
.from(canvasCapabilityTask)
|
|
.where(
|
|
and(
|
|
eq(canvasCapabilityTask.requestId, params.requestId),
|
|
eq(canvasCapabilityTask.userId, params.userId),
|
|
eq(canvasCapabilityTask.projectId, params.projectId)
|
|
)
|
|
)
|
|
.limit(1);
|
|
if (!task) return null;
|
|
const staleBefore = new Date(Date.now() - STALE_RUNNING_TASK_MS);
|
|
if (["pending", "running"].includes(task.status) && task.updatedAt < staleBefore) {
|
|
const [interrupted] = await db.update(canvasCapabilityTask).set({
|
|
status: "failed",
|
|
error: INTERRUPTED_TASK_MESSAGE,
|
|
completedAt: new Date(),
|
|
updatedAt: new Date(),
|
|
}).where(and(
|
|
eq(canvasCapabilityTask.id, task.id),
|
|
inArray(canvasCapabilityTask.status, ["pending", "running"]),
|
|
lt(canvasCapabilityTask.updatedAt, staleBefore),
|
|
)).returning();
|
|
if (interrupted) return interrupted;
|
|
const [latest] = await db.select().from(canvasCapabilityTask)
|
|
.where(eq(canvasCapabilityTask.id, task.id)).limit(1);
|
|
return latest || task;
|
|
}
|
|
return task;
|
|
}
|
|
|
|
export async function createOrRecoverCanvasCapabilityTask(params: {
|
|
command: CanvasCapabilityClientCommand;
|
|
userId: string;
|
|
projectId: string;
|
|
}) {
|
|
await db
|
|
.insert(canvasCapabilityTask)
|
|
.values({
|
|
requestId: params.command.requestId,
|
|
userId: params.userId,
|
|
projectId: params.projectId,
|
|
capabilityId: params.command.capabilityId,
|
|
source: params.command.source,
|
|
request: params.command,
|
|
updatedAt: new Date(),
|
|
})
|
|
.onConflictDoNothing({ target: canvasCapabilityTask.requestId });
|
|
|
|
const taskLookup = {
|
|
requestId: params.command.requestId,
|
|
userId: params.userId,
|
|
projectId: params.projectId,
|
|
};
|
|
const task = await getCanvasCapabilityTask(taskLookup);
|
|
if (!task) {
|
|
throw new Error("无法创建画布能力任务");
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
export async function heartbeatCanvasCapabilityTask(taskId: string) {
|
|
await db.update(canvasCapabilityTask).set({ updatedAt: new Date() })
|
|
.where(and(eq(canvasCapabilityTask.id, taskId), inArray(canvasCapabilityTask.status, ["pending", "running"])));
|
|
}
|
|
|
|
export async function markCanvasCapabilityTaskRunning(taskId: string) {
|
|
const now = new Date();
|
|
const [task] = await db
|
|
.update(canvasCapabilityTask)
|
|
.set({
|
|
status: "running",
|
|
attempts: sql`${canvasCapabilityTask.attempts} + 1`,
|
|
startedAt: now,
|
|
completedAt: null,
|
|
error: null,
|
|
updatedAt: now,
|
|
})
|
|
.where(and(eq(canvasCapabilityTask.id, taskId), eq(canvasCapabilityTask.status, "pending")))
|
|
.returning();
|
|
|
|
return task || null;
|
|
}
|
|
|
|
export async function markCanvasCapabilityTaskSucceeded(
|
|
taskId: string,
|
|
result: CanvasCapabilityTaskResult
|
|
) {
|
|
const now = new Date();
|
|
const [task] = await db
|
|
.update(canvasCapabilityTask)
|
|
.set({
|
|
status: "succeeded",
|
|
result,
|
|
error: null,
|
|
completedAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.where(eq(canvasCapabilityTask.id, taskId))
|
|
.returning();
|
|
return task || null;
|
|
}
|
|
|
|
export async function markCanvasCapabilityTaskFailed(
|
|
taskId: string,
|
|
error: unknown
|
|
) {
|
|
const now = new Date();
|
|
const message = error instanceof Error ? error.message : String(error || "执行失败");
|
|
const [task] = await db
|
|
.update(canvasCapabilityTask)
|
|
.set({
|
|
status: "failed",
|
|
error: message.slice(0, 4000),
|
|
completedAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.where(eq(canvasCapabilityTask.id, taskId))
|
|
.returning();
|
|
return task || null;
|
|
}
|