All checks were successful
Release / Test backend (push) Successful in 40s
Release / Check ui (push) Successful in 1m36s
Release / Docker / caddy (push) Successful in 45s
Release / Docker / backend (push) Successful in 4m27s
Release / Docker / runner (push) Successful in 3m32s
Release / Upload source maps (push) Successful in 44s
Release / Docker / ui (push) Successful in 2m46s
Release / Gitea Release (push) Successful in 58s
- Add /admin/ai-jobs page with live-polling jobs table, status badges, progress bars, and cancel action - Add listAIJobs() helper in pocketbase.ts; AIJob type - Add POST /api/admin/ai-jobs/[id]/cancel SvelteKit proxy - Add ai-jobs link to admin sidebar nav (all 5 locales) - Cache image-gen and text-gen model lists in Valkey (10 min TTL) via scraper.ts helpers - Cache changelog Gitea API response (5 min TTL) - Improve 502/504 error message on image-gen page with CF AI timeout hint - Show CF AI timeout warning in advanced options when a Cloudflare/FLUX model is selected
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
/**
|
|
* Backend API helper.
|
|
*
|
|
* Centralises the BACKEND_URL constant and provides a thin fetch wrapper that:
|
|
* - Resolves paths relative to BACKEND_API_URL.
|
|
* - Throws 502 on network errors (unreachable backend).
|
|
* - Re-throws SvelteKit `error()` objects so callers can still short-circuit.
|
|
* - Passes a RequestInit through verbatim so callers keep full control.
|
|
*
|
|
* Import only from server-side modules (`+server.ts`, `*.server.ts`).
|
|
*/
|
|
|
|
import { error } from '@sveltejs/kit';
|
|
import { env } from '$env/dynamic/private';
|
|
import * as cache from '$lib/server/cache';
|
|
|
|
export const BACKEND_URL = env.BACKEND_API_URL ?? 'http://localhost:8080';
|
|
|
|
/**
|
|
* Fetch a path on the backend, throwing a 502 on network failures.
|
|
*
|
|
* The `path` must start with `/` (e.g. `/api/voices`).
|
|
*
|
|
* SvelteKit `error()` exceptions are always re-thrown so callers can
|
|
* short-circuit correctly inside their own catch blocks.
|
|
*/
|
|
export async function backendFetch(path: string, init?: RequestInit): Promise<Response> {
|
|
try {
|
|
return await fetch(`${BACKEND_URL}${path}`, init);
|
|
} catch (e) {
|
|
// Re-throw SvelteKit HTTP errors so they propagate to the framework.
|
|
if (e instanceof Error && 'status' in e) throw e;
|
|
throw error(502, 'Could not reach backend');
|
|
}
|
|
}
|
|
|
|
// ─── Cached admin model lists ─────────────────────────────────────────────────
|
|
|
|
const MODELS_CACHE_TTL = 10 * 60; // 10 minutes — model lists rarely change
|
|
|
|
/**
|
|
* Fetch image-gen model list from the Go backend with a 10-minute cache.
|
|
* Returns an empty array on error (callers should surface a warning).
|
|
*/
|
|
export async function listImageModels<T>(): Promise<T[]> {
|
|
const key = 'backend:models:image-gen';
|
|
const cached = await cache.get<T[]>(key);
|
|
if (cached) return cached;
|
|
try {
|
|
const res = await backendFetch('/api/admin/image-gen/models');
|
|
if (!res.ok) return [];
|
|
const data = await res.json();
|
|
const models = (data.models ?? []) as T[];
|
|
await cache.set(key, models, MODELS_CACHE_TTL);
|
|
return models;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch text-gen model list from the Go backend with a 10-minute cache.
|
|
* Returns an empty array on error.
|
|
*/
|
|
export async function listTextModels<T>(): Promise<T[]> {
|
|
const key = 'backend:models:text-gen';
|
|
const cached = await cache.get<T[]>(key);
|
|
if (cached) return cached;
|
|
try {
|
|
const res = await backendFetch('/api/admin/text-gen/models');
|
|
if (!res.ok) return [];
|
|
const data = await res.json();
|
|
const models = (data.models ?? []) as T[];
|
|
await cache.set(key, models, MODELS_CACHE_TTL);
|
|
return models;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ─── Response types ───────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Metadata shape returned inside the 200 response from GET /api/book-preview/{slug}.
|
|
* Used in both the SSR page load and the API proxy to avoid duplicating the inline type.
|
|
*/
|
|
export interface BookPreviewMeta {
|
|
slug: string;
|
|
title: string;
|
|
author: string;
|
|
cover: string;
|
|
status: string;
|
|
genres: string[];
|
|
summary: string;
|
|
total_chapters: number;
|
|
source_url: string;
|
|
}
|
|
|
|
/** Full 200 response from GET /api/book-preview/{slug}. */
|
|
export interface BookPreviewResponse {
|
|
in_lib: boolean;
|
|
meta: BookPreviewMeta;
|
|
chapters: { number: number; title: string; date?: string }[];
|
|
}
|
|
|