Files
20260512-skg-tk/web/lib/api.ts
2026-05-13 09:37:15 +08:00

188 lines
5.0 KiB
TypeScript

const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:4291"
export type JobStatus =
| "created"
| "downloading"
| "downloaded"
| "splitting"
| "frames_extracted"
| "transcribing"
| "transcribed"
| "failed"
export interface FrameObject {
name: string
position?: string
color?: string
extract_prompt?: string
}
export interface FrameDescription {
scene?: string
objects?: FrameObject[]
style?: string
suggested_prompt?: string
}
export interface GeneratedImage {
id: string
prompt: string
model: string
mode: string
url: string
selected: boolean
created_at: number
}
export interface KeyFrame {
index: number
timestamp: number
url: string
description?: FrameDescription | null
generated_images?: GeneratedImage[]
}
export interface TranscriptSegment {
index: number
start: number
end: number
en: string
zh: string
}
export interface Job {
id: string
url: string
status: JobStatus
progress: number
message?: string
video_url?: string
duration?: number
width?: number
height?: number
frames: KeyFrame[]
transcript: TranscriptSegment[]
error?: string
}
export async function createJob(tkUrl: string): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: tkUrl }),
})
if (!res.ok) throw new Error(`createJob ${res.status}`)
return res.json()
}
export async function uploadJob(file: File): Promise<Job> {
const fd = new FormData()
fd.append("file", file)
const res = await fetch(`${API_BASE}/jobs/upload`, {
method: "POST",
body: fd,
})
if (!res.ok) {
const text = await res.text().catch(() => "")
throw new Error(`uploadJob ${res.status} ${text.slice(0, 200)}`)
}
return res.json()
}
export async function getJob(id: string): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${id}`)
if (!res.ok) throw new Error(`getJob ${res.status}`)
return res.json()
}
export async function triggerTranscribe(id: string): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${id}/transcribe`, { method: "POST" })
if (!res.ok) throw new Error(`transcribe ${res.status}`)
return res.json()
}
export async function analyzeJob(id: string, frames = 5): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${id}/analyze?frames=${frames}`, { method: "POST" })
if (!res.ok) {
const t = await res.text().catch(() => "")
throw new Error(`analyze ${res.status} ${t.slice(0, 200)}`)
}
return res.json()
}
export async function addManualFrame(id: string, t: number): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${id}/frames?t=${encodeURIComponent(t.toFixed(2))}`, { method: "POST" })
if (!res.ok) {
const txt = await res.text().catch(() => "")
throw new Error(`addFrame ${res.status} ${txt.slice(0, 200)}`)
}
return res.json()
}
export async function describeFrame(jobId: string, frameIdx: number): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/describe`, { method: "POST" })
if (!res.ok) {
const txt = await res.text().catch(() => "")
throw new Error(`describe ${res.status} ${txt.slice(0, 200)}`)
}
return res.json()
}
export async function translateText(text: string, target: "en" | "zh" = "en"): Promise<string> {
const res = await fetch(`${API_BASE}/translate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, target }),
})
if (!res.ok) {
const txt = await res.text().catch(() => "")
throw new Error(`translate ${res.status} ${txt.slice(0, 200)}`)
}
const j = await res.json()
return (j.text || "").toString()
}
export async function generateImage(
jobId: string,
frameIdx: number,
body: { prompt: string; extra_prompt?: string; negative_prompt?: string; model?: string; mode?: "edit" | "text" },
): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/generate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
})
if (!res.ok) {
const txt = await res.text().catch(() => "")
throw new Error(`generate ${res.status} ${txt.slice(0, 300)}`)
}
return res.json()
}
export async function selectGenerated(
jobId: string,
frameIdx: number,
genId: string,
selected: boolean,
): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/gen/${genId}/select`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ selected }),
})
if (!res.ok) throw new Error(`select ${res.status}`)
return res.json()
}
export function generatedImageUrl(jobId: string, frameIdx: number, genId: string): string {
return `${API_BASE}/jobs/${jobId}/frames/${frameIdx}/gen/${genId}.jpg`
}
export function frameUrl(jobId: string, frameIndex: number): string {
return `${API_BASE}/jobs/${jobId}/frames/${frameIndex}.jpg`
}
export function videoUrl(jobId: string): string {
return `${API_BASE}/jobs/${jobId}/video.mp4`
}