162 lines
4.2 KiB
TypeScript
162 lines
4.2 KiB
TypeScript
import { and, eq, 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;
|
|
export const MAX_CANVAS_CAPABILITY_TASK_ATTEMPTS = 3;
|
|
|
|
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);
|
|
return task || null;
|
|
}
|
|
|
|
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,
|
|
})
|
|
.onConflictDoNothing({ target: canvasCapabilityTask.requestId });
|
|
|
|
const taskLookup = {
|
|
requestId: params.command.requestId,
|
|
userId: params.userId,
|
|
projectId: params.projectId,
|
|
};
|
|
let task = await getCanvasCapabilityTask(taskLookup);
|
|
if (!task) {
|
|
throw new Error("无法创建画布能力任务");
|
|
}
|
|
|
|
const staleBefore = new Date(Date.now() - STALE_RUNNING_TASK_MS);
|
|
if (
|
|
task.status === "failed" &&
|
|
task.attempts < MAX_CANVAS_CAPABILITY_TASK_ATTEMPTS
|
|
) {
|
|
await db
|
|
.update(canvasCapabilityTask)
|
|
.set({
|
|
status: "pending",
|
|
error: null,
|
|
completedAt: null,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(canvasCapabilityTask.id, task.id));
|
|
} else if (
|
|
task.status === "running" &&
|
|
task.updatedAt < staleBefore &&
|
|
task.attempts < MAX_CANVAS_CAPABILITY_TASK_ATTEMPTS
|
|
) {
|
|
await db
|
|
.update(canvasCapabilityTask)
|
|
.set({
|
|
status: "pending",
|
|
error: "恢复超时的执行中任务",
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(
|
|
and(
|
|
eq(canvasCapabilityTask.id, task.id),
|
|
lt(canvasCapabilityTask.updatedAt, staleBefore)
|
|
)
|
|
);
|
|
} else if (task.status === "running" && task.updatedAt < staleBefore) {
|
|
await db
|
|
.update(canvasCapabilityTask)
|
|
.set({
|
|
status: "failed",
|
|
error: "画布能力任务超过最大重试次数",
|
|
completedAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(canvasCapabilityTask.id, task.id));
|
|
}
|
|
|
|
task = (await getCanvasCapabilityTask(taskLookup)) || task;
|
|
return task;
|
|
}
|
|
|
|
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(eq(canvasCapabilityTask.id, taskId))
|
|
.returning();
|
|
|
|
if (!task) throw new Error("画布能力任务不存在");
|
|
return task;
|
|
}
|
|
|
|
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;
|
|
}
|