import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { type SubtitleApplyRequest, SubtitleApplyResult, SubtitleParseResponse, SubtitleTargetsResponse, SubtitleWritesResponse, SubtitleWriteView, } from "@umt/contracts"; import { api } from "~/lib/api.ts"; import { getParsed, parseOrThrow } from "./library.ts"; // 补字幕(docs/plan/subtitles.md):目标视频、解析上传、写入、历史与撤销 export const subtitleKeys = { targets: (entityId: string) => ["subtitles", "targets", entityId] as const, writes: (entityId: string) => ["subtitles", "writes", entityId] as const, }; export function useSubtitleTargets(entityId: string, enabled = true) { return useQuery({ queryKey: subtitleKeys.targets(entityId), enabled, queryFn: ({ signal }) => getParsed(SubtitleTargetsResponse, "/subtitles/targets", { entityId }, signal), }); } export function useSubtitleWrites(entityId: string, enabled = true) { return useQuery({ queryKey: subtitleKeys.writes(entityId), enabled, queryFn: ({ signal }) => getParsed(SubtitleWritesResponse, "/subtitles/writes", { entityId }, signal), }); } /** 读本地文件为 base64(字幕与 zip 都很小,直接放进 JSON) */ async function fileBase64(f: File): Promise { const buf = new Uint8Array(await f.arrayBuffer()); let s = ""; for (let i = 0; i < buf.length; i += 0x8000) s += String.fromCharCode(...buf.subarray(i, i + 0x8000)); return btoa(s); } export function useParseSubtitles() { return useMutation({ mutationFn: async ({ entityId, files }: { entityId: string; files: File[] }) => parseOrThrow( SubtitleParseResponse, await api.post("/subtitles/parse", { entityId, files: await Promise.all(files.map(async (f) => ({ name: f.name, base64: await fileBase64(f) }))), }), "/subtitles/parse", ), }); } export function useApplySubtitles() { const qc = useQueryClient(); return useMutation({ mutationFn: async (body: SubtitleApplyRequest) => parseOrThrow(SubtitleApplyResult, await api.post("/subtitles/apply", body), "/subtitles/apply"), onSuccess: (_r, body) => { qc.invalidateQueries({ queryKey: subtitleKeys.targets(body.entityId) }); qc.invalidateQueries({ queryKey: subtitleKeys.writes(body.entityId) }); }, }); } export function useUndoSubtitle(entityId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (id: string) => parseOrThrow( SubtitleWriteView, await api.post(`/subtitles/writes/${id}/undo`), "/subtitles/writes/undo", ), onSuccess: () => { qc.invalidateQueries({ queryKey: subtitleKeys.targets(entityId) }); qc.invalidateQueries({ queryKey: subtitleKeys.writes(entityId) }); }, }); }