Files
omniai-ds-code-package/src/api/generationRecordClient.ts
T

132 lines
4.2 KiB
TypeScript
Raw Normal View History

import { isOptionalApiRouteMissing } from "./apiErrorUtils";
import { serverRequest } from "./serverConnection";
const PENDING_RECORDS_KEY = "omniai:generation-records.pending";
const MAX_PENDING_RECORDS = 80;
export type GenerationRecordStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
export interface GenerationRecordAsset {
role: "source" | "reference" | "intermediate" | "result" | "thumbnail";
mediaType: "image" | "video" | "text" | "asset" | string;
url: string;
ossKey?: string | null;
scope?: string;
label?: string;
taskId?: string | null;
metadata?: Record<string, unknown>;
}
export interface SaveGenerationRecordInput {
clientRecordId: string;
tool: string;
mode?: string;
title: string;
status: GenerationRecordStatus;
prompt?: string;
taskIds?: string[];
assets?: GenerationRecordAsset[];
config?: Record<string, unknown>;
result?: Record<string, unknown>;
metadata?: Record<string, unknown>;
createdAt?: string;
updatedAt?: string;
}
export interface SaveGenerationRecordResult {
source: "server" | "local";
id: string;
}
function readPendingRecords(): SaveGenerationRecordInput[] {
try {
const raw = window.localStorage.getItem(PENDING_RECORDS_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.filter((item): item is SaveGenerationRecordInput => Boolean(item?.clientRecordId)) : [];
} catch {
return [];
}
}
function writePendingRecord(input: SaveGenerationRecordInput): void {
try {
const records = readPendingRecords();
const next = [input, ...records.filter((item) => item.clientRecordId !== input.clientRecordId)].slice(0, MAX_PENDING_RECORDS);
window.localStorage.setItem(PENDING_RECORDS_KEY, JSON.stringify(next));
} catch {
// Ignore storage quota failures; generation itself must not be blocked by history persistence.
}
}
export async function saveGenerationRecord(input: SaveGenerationRecordInput): Promise<SaveGenerationRecordResult> {
try {
const response = await serverRequest<{ id?: string | number }>("ai/generation-records", {
method: "POST",
body: input,
maxRetries: 0,
fallbackMessage: "Failed to save generation record",
});
return { source: "server", id: String(response.id ?? input.clientRecordId) };
} catch (error) {
if (!isOptionalApiRouteMissing(error)) {
// Keep a local recovery copy even when the route exists but the save fails.
writePendingRecord(input);
return { source: "local", id: input.clientRecordId };
}
writePendingRecord(input);
return { source: "local", id: input.clientRecordId };
}
}
export async function flushPendingGenerationRecords(): Promise<{ synced: number; remaining: number }> {
const pending = readPendingRecords();
if (!pending.length) return { synced: 0, remaining: 0 };
const remaining: SaveGenerationRecordInput[] = [];
let synced = 0;
for (const record of pending.slice().reverse()) {
try {
await serverRequest<{ id?: string | number }>("ai/generation-records", {
method: "POST",
body: record,
maxRetries: 0,
fallbackMessage: "Failed to sync generation record",
});
synced += 1;
} catch {
remaining.unshift(record);
}
}
try {
if (remaining.length) {
window.localStorage.setItem(PENDING_RECORDS_KEY, JSON.stringify(remaining.slice(0, MAX_PENDING_RECORDS)));
} else {
window.localStorage.removeItem(PENDING_RECORDS_KEY);
}
} catch {
// Keep runtime generation unaffected if browser storage is unavailable.
}
return { synced, remaining: remaining.length };
}
export async function deleteGenerationRecordByClientId(clientRecordId: string): Promise<void> {
await serverRequest<{ success: boolean }>(`ai/generation-records/by-client-id/${encodeURIComponent(clientRecordId)}`, {
method: "DELETE",
maxRetries: 0,
fallbackMessage: "Failed to delete generation record",
});
}
export function buildGenerationOssScope(parts: Array<string | number | null | undefined>): string {
return parts
.map((part) => String(part ?? "").trim().toLowerCase())
.filter(Boolean)
.map((part) => part.replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""))
.filter(Boolean)
.join("/");
}