import "server-only"; import { upsertSystemSetting } from "@/lib/db/queries"; import { AGENT_PSD_TEMPLATES_KEY, type AgentPsdTemplate, } from "@/lib/agents/psd-template-config"; import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings"; const DEFAULT_PARSE_HEARTBEAT_INTERVAL_MS = 25_000; function clampProgress(value: number) { return Math.max(0, Math.min(99, Math.round(Number(value || 0)))); } export async function updateAgentPsdTemplateById(params: { templateId: string; updatedBy: string; updater: (template: AgentPsdTemplate) => AgentPsdTemplate; }) { const templates = await readAgentPsdTemplates(); const currentTemplate = templates.find((entry) => entry.id === params.templateId); if (!currentTemplate) { return null; } const nextTemplate = params.updater(currentTemplate); await upsertSystemSetting({ key: AGENT_PSD_TEMPLATES_KEY, value: templates.map((entry) => entry.id === params.templateId ? nextTemplate : entry ), updatedBy: params.updatedBy, }); return nextTemplate; } export async function updateAgentPsdTemplateParseProgress(params: { templateId: string; updatedBy: string; progress: number; stage: string; error?: string; restart?: boolean; }) { const now = new Date().toISOString(); return updateAgentPsdTemplateById({ templateId: params.templateId, updatedBy: params.updatedBy, updater: (template) => { if (!params.restart && template.parseStatus !== "processing") { return template; } return { ...template, parseStatus: "processing", parseProgress: clampProgress(params.progress), parseStage: params.stage, parseError: params.error, parseStartedAt: params.restart || !template.parseStartedAt ? now : template.parseStartedAt, parseHeartbeatAt: now, updatedAt: now, }; }, }); } export function createAgentPsdTemplateParseHeartbeat(params: { templateId: string; updatedBy: string; getSnapshot: () => { progress: number; stage: string; error?: string; }; intervalMs?: number; }) { let stopped = false; let inFlight = false; let failedBeats = 0; const interval = setInterval(() => { if (stopped || inFlight) { return; } inFlight = true; const snapshot = params.getSnapshot(); void updateAgentPsdTemplateParseProgress({ templateId: params.templateId, updatedBy: params.updatedBy, progress: snapshot.progress, stage: snapshot.stage, error: snapshot.error, }) .then(() => { failedBeats = 0; }) .catch((error) => { failedBeats += 1; if (failedBeats <= 3) { console.warn("[PSD Template] parse heartbeat update failed:", { templateId: params.templateId, failedBeats, error, }); } }) .finally(() => { inFlight = false; }); }, params.intervalMs || DEFAULT_PARSE_HEARTBEAT_INTERVAL_MS); return { stop() { stopped = true; clearInterval(interval); }, }; }