Spaces:
Sleeping
Sleeping
Indrajit Ari commited on
Commit ·
34c09b7
1
Parent(s): 12c4b57
fix: resolve 'Failed to fetch' by hardening API URL detection and using same-origin relative paths
Browse files- backend/app_hf.py +1 -0
- frontend/src/app/page.tsx +10 -2
- frontend/src/app/processing/page.tsx +13 -4
- frontend/src/app/result/page.tsx +10 -3
backend/app_hf.py
CHANGED
|
@@ -99,6 +99,7 @@ def _run_inference(job_id: str, input_path: str, output_path: str):
|
|
| 99 |
|
| 100 |
@app.post("/api/upload")
|
| 101 |
async def upload_video(file: UploadFile = File(...)):
|
|
|
|
| 102 |
ext = Path(file.filename or "x.mp4").suffix.lower()
|
| 103 |
if ext not in ALLOWED_EXTENSIONS:
|
| 104 |
raise HTTPException(400, f"Unsupported format '{ext}'.")
|
|
|
|
| 99 |
|
| 100 |
@app.post("/api/upload")
|
| 101 |
async def upload_video(file: UploadFile = File(...)):
|
| 102 |
+
logger.info(f"Incoming upload request: filename='{file.filename}', content_type='{file.content_type}'")
|
| 103 |
ext = Path(file.filename or "x.mp4").suffix.lower()
|
| 104 |
if ext not in ALLOWED_EXTENSIONS:
|
| 105 |
raise HTTPException(400, f"Unsupported format '{ext}'.")
|
frontend/src/app/page.tsx
CHANGED
|
@@ -3,7 +3,14 @@
|
|
| 3 |
import { useState, useRef, useCallback, useEffect, DragEvent } from 'react'
|
| 4 |
import { useRouter } from 'next/navigation'
|
| 5 |
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
const VOC_CLASSES = [
|
| 9 |
{ name: 'aeroplane', color: '#87CEEB' }, { name: 'bicycle', color: '#FFA500' },
|
|
@@ -106,7 +113,8 @@ export default function HomePage() {
|
|
| 106 |
setUploading(true); setError(null)
|
| 107 |
try {
|
| 108 |
const form = new FormData(); form.append('file', file)
|
| 109 |
-
const
|
|
|
|
| 110 |
if (!res.ok) { const d = await res.json(); throw new Error(d.detail ?? 'Upload failed') }
|
| 111 |
const data = await res.json()
|
| 112 |
router.push(`/processing?id=${data.job_id}`)
|
|
|
|
| 3 |
import { useState, useRef, useCallback, useEffect, DragEvent } from 'react'
|
| 4 |
import { useRouter } from 'next/navigation'
|
| 5 |
|
| 6 |
+
// Determine API_BASE: if the baked-in env var is defined, use it.
|
| 7 |
+
// Otherwise, in the browser, default to empty string (same-origin).
|
| 8 |
+
const getApiBase = () => {
|
| 9 |
+
if (process.env.NEXT_PUBLIC_API_URL) return process.env.NEXT_PUBLIC_API_URL;
|
| 10 |
+
if (typeof window !== 'undefined') return ''; // Production same-origin
|
| 11 |
+
return 'http://localhost:8000'; // SSR fallback
|
| 12 |
+
};
|
| 13 |
+
const API_BASE = getApiBase();
|
| 14 |
|
| 15 |
const VOC_CLASSES = [
|
| 16 |
{ name: 'aeroplane', color: '#87CEEB' }, { name: 'bicycle', color: '#FFA500' },
|
|
|
|
| 113 |
setUploading(true); setError(null)
|
| 114 |
try {
|
| 115 |
const form = new FormData(); form.append('file', file)
|
| 116 |
+
const endpoint = API_BASE ? `${API_BASE}/api/upload` : 'api/upload'
|
| 117 |
+
const res = await fetch(endpoint, { method: 'POST', body: form })
|
| 118 |
if (!res.ok) { const d = await res.json(); throw new Error(d.detail ?? 'Upload failed') }
|
| 119 |
const data = await res.json()
|
| 120 |
router.push(`/processing?id=${data.job_id}`)
|
frontend/src/app/processing/page.tsx
CHANGED
|
@@ -3,7 +3,13 @@
|
|
| 3 |
import { useEffect, useState, useRef, Suspense } from 'react'
|
| 4 |
import { useRouter, useSearchParams } from 'next/navigation'
|
| 5 |
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
const VOC_COLORS: Record<string, string> = {
|
| 9 |
aeroplane:'#87CEEB', bicycle:'#FFA500', bird:'#FFD700', boat:'#00BFFF',
|
|
@@ -38,8 +44,10 @@ function ProcessingContent() {
|
|
| 38 |
const start = Date.now()
|
| 39 |
timerRef.current = setInterval(() => setElapsed(Math.floor((Date.now()-start)/1000)), 1000)
|
| 40 |
|
| 41 |
-
const
|
| 42 |
-
const
|
|
|
|
|
|
|
| 43 |
|
| 44 |
ws.onmessage = (evt) => {
|
| 45 |
const data = JSON.parse(evt.data)
|
|
@@ -59,7 +67,8 @@ function ProcessingContent() {
|
|
| 59 |
const pollFallback = () => {
|
| 60 |
const iv = setInterval(async () => {
|
| 61 |
try {
|
| 62 |
-
const
|
|
|
|
| 63 |
setStatus(d.status)
|
| 64 |
if (d.pct !== undefined) setPct(d.pct)
|
| 65 |
if (d.detected) setDetected(d.detected)
|
|
|
|
| 3 |
import { useEffect, useState, useRef, Suspense } from 'react'
|
| 4 |
import { useRouter, useSearchParams } from 'next/navigation'
|
| 5 |
|
| 6 |
+
// Determine API_BASE: if the baked-in env var is defined, use it.
|
| 7 |
+
const getApiBase = () => {
|
| 8 |
+
if (process.env.NEXT_PUBLIC_API_URL) return process.env.NEXT_PUBLIC_API_URL;
|
| 9 |
+
if (typeof window !== 'undefined') return ''; // Production same-origin
|
| 10 |
+
return 'http://localhost:8000'; // SSR fallback
|
| 11 |
+
};
|
| 12 |
+
const API_BASE = getApiBase();
|
| 13 |
|
| 14 |
const VOC_COLORS: Record<string, string> = {
|
| 15 |
aeroplane:'#87CEEB', bicycle:'#FFA500', bird:'#FFD700', boat:'#00BFFF',
|
|
|
|
| 44 |
const start = Date.now()
|
| 45 |
timerRef.current = setInterval(() => setElapsed(Math.floor((Date.now()-start)/1000)), 1000)
|
| 46 |
|
| 47 |
+
const apiOrigin = API_BASE || (typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : '');
|
| 48 |
+
const wsUrl = jobId ? `${apiOrigin.replace('http','ws')}/ws/${jobId}` : '';
|
| 49 |
+
const ws = wsUrl ? new WebSocket(wsUrl) : null;
|
| 50 |
+
if (!ws) return;
|
| 51 |
|
| 52 |
ws.onmessage = (evt) => {
|
| 53 |
const data = JSON.parse(evt.data)
|
|
|
|
| 67 |
const pollFallback = () => {
|
| 68 |
const iv = setInterval(async () => {
|
| 69 |
try {
|
| 70 |
+
const endpoint = API_BASE ? `${API_BASE}/api/status/${jobId}` : `api/status/${jobId}`
|
| 71 |
+
const d = await fetch(endpoint).then(r=>r.json())
|
| 72 |
setStatus(d.status)
|
| 73 |
if (d.pct !== undefined) setPct(d.pct)
|
| 74 |
if (d.detected) setDetected(d.detected)
|
frontend/src/app/result/page.tsx
CHANGED
|
@@ -3,7 +3,13 @@
|
|
| 3 |
import { useEffect, useState, useRef, Suspense } from 'react'
|
| 4 |
import { useRouter, useSearchParams } from 'next/navigation'
|
| 5 |
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
function ResultContent() {
|
| 9 |
const router = useRouter()
|
|
@@ -24,13 +30,14 @@ function ResultContent() {
|
|
| 24 |
const checkStatus = async () => {
|
| 25 |
try {
|
| 26 |
// 1. Check API status
|
| 27 |
-
const
|
|
|
|
| 28 |
if (!res.ok) throw new Error('Not found')
|
| 29 |
const data = await res.json()
|
| 30 |
|
| 31 |
if (data.status === 'done') {
|
| 32 |
setDetected(data.detected || [])
|
| 33 |
-
const url = `${API_BASE}/api/video/${jobId}`
|
| 34 |
|
| 35 |
// 2. Verify file exists with HEAD request
|
| 36 |
const head = await fetch(url, { method: 'HEAD' })
|
|
|
|
| 3 |
import { useEffect, useState, useRef, Suspense } from 'react'
|
| 4 |
import { useRouter, useSearchParams } from 'next/navigation'
|
| 5 |
|
| 6 |
+
// Determine API_BASE: if the baked-in env var is defined, use it.
|
| 7 |
+
const getApiBase = () => {
|
| 8 |
+
if (process.env.NEXT_PUBLIC_API_URL) return process.env.NEXT_PUBLIC_API_URL;
|
| 9 |
+
if (typeof window !== 'undefined') return ''; // Production same-origin
|
| 10 |
+
return 'http://localhost:8000'; // SSR fallback
|
| 11 |
+
};
|
| 12 |
+
const API_BASE = getApiBase();
|
| 13 |
|
| 14 |
function ResultContent() {
|
| 15 |
const router = useRouter()
|
|
|
|
| 30 |
const checkStatus = async () => {
|
| 31 |
try {
|
| 32 |
// 1. Check API status
|
| 33 |
+
const statusEndpoint = API_BASE ? `${API_BASE}/api/status/${jobId}` : `api/status/${jobId}`
|
| 34 |
+
const res = await fetch(statusEndpoint)
|
| 35 |
if (!res.ok) throw new Error('Not found')
|
| 36 |
const data = await res.json()
|
| 37 |
|
| 38 |
if (data.status === 'done') {
|
| 39 |
setDetected(data.detected || [])
|
| 40 |
+
const url = API_BASE ? `${API_BASE}/api/video/${jobId}` : `api/video/${jobId}`
|
| 41 |
|
| 42 |
// 2. Verify file exists with HEAD request
|
| 43 |
const head = await fetch(url, { method: 'HEAD' })
|