diff --git a/frontend/apps/video-info-website/src/api/client.ts b/frontend/apps/video-info-website/src/api/client.ts index 8cf1514225..63600cfdf3 100644 --- a/frontend/apps/video-info-website/src/api/client.ts +++ b/frontend/apps/video-info-website/src/api/client.ts @@ -1,10 +1,12 @@ -import type { VideoInfoReadOnlyResponse } from './types'; +import type { VideoInfoUploadResponse } from './types'; // Dev: same-origin `/v1/...`, proxied to http://localhost:12345 by Vite (see // vite.config.ts). Prod: call the public API host directly. const API_BASE = import.meta.env.DEV ? '' : 'https://api.storyteller.ai'; -const READ_ONLY_PATH = '/v1/video_info/read_only'; +// The upload endpoint persists a record (and returns its token) in addition to +// returning the detected provenance. +const UPLOAD_PATH = '/v1/video_info/upload'; export class VideoInfoApiError extends Error { constructor( @@ -16,16 +18,19 @@ export class VideoInfoApiError extends Error { } } -/** Upload a video file and return the detected provenance. */ -export async function readVideoInfo( +/** + * Upload a video file, persist a record, and return the detected provenance + * along with the stored record's token. + */ +export async function uploadVideo( file: File, -): Promise { +): Promise { const form = new FormData(); form.append('file', file, file.name || 'upload.mp4'); let response: Response; try { - response = await fetch(`${API_BASE}${READ_ONLY_PATH}`, { + response = await fetch(`${API_BASE}${UPLOAD_PATH}`, { method: 'POST', body: form, }); @@ -52,5 +57,5 @@ export async function readVideoInfo( ); } - return (await response.json()) as VideoInfoReadOnlyResponse; + return (await response.json()) as VideoInfoUploadResponse; } diff --git a/frontend/apps/video-info-website/src/api/types.ts b/frontend/apps/video-info-website/src/api/types.ts index e742a3d4db..b38c600289 100644 --- a/frontend/apps/video-info-website/src/api/types.ts +++ b/frontend/apps/video-info-website/src/api/types.ts @@ -98,3 +98,12 @@ export interface VideoInfoReadOnlyResponse { maybe_dreamina: DreaminaVideoInfo | null; maybe_kling: KlingVideoInfo | null; } + +/** + * Response from `POST /v1/video_info/upload` — the same detected provenance as + * the read-only endpoint, plus the persisted record token (kept for follow-up + * requests like attaching a note). + */ +export interface VideoInfoUploadResponse extends VideoInfoReadOnlyResponse { + uploaded_video_token: string; +} diff --git a/frontend/apps/video-info-website/src/app/App.tsx b/frontend/apps/video-info-website/src/app/App.tsx index 8ac1a15113..92b4fb0365 100644 --- a/frontend/apps/video-info-website/src/app/App.tsx +++ b/frontend/apps/video-info-website/src/app/App.tsx @@ -1,8 +1,8 @@ import { useCallback, useState } from 'react'; import { ArrowDown, RotateCcw, ScanSearch, Sparkles } from 'lucide-react'; -import { readVideoInfo, VideoInfoApiError } from '../api/client'; -import type { VideoInfoReadOnlyResponse } from '../api/types'; +import { uploadVideo, VideoInfoApiError } from '../api/client'; +import type { VideoInfoUploadResponse } from '../api/types'; import { classify } from '../lib/classify'; import type { Verdict } from '../lib/classify'; import { ThemeToggle } from '../components/ThemeToggle'; @@ -14,7 +14,9 @@ type Status = 'idle' | 'loading' | 'done' | 'error'; interface DoneState { fileName: string; - response: VideoInfoReadOnlyResponse; + response: VideoInfoUploadResponse; + /** Persisted record token — kept for upcoming follow-up requests. */ + uploadedVideoToken: string; verdict: Verdict; } @@ -29,8 +31,13 @@ export function App() { setStatus('loading'); setError(null); try { - const response = await readVideoInfo(file); - setResult({ fileName: file.name, response, verdict: classify(response) }); + const response = await uploadVideo(file); + setResult({ + fileName: file.name, + response, + uploadedVideoToken: response.uploaded_video_token, + verdict: classify(response), + }); setStatus('done'); } catch (err) { const message =