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 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
- const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8000'
 
 
 
 
 
 
 
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 res = await fetch(`${API_BASE}/api/upload`, { method: 'POST', body: form })
 
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
- const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8000'
 
 
 
 
 
 
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 wsUrl = `${API_BASE.replace('https','wss').replace('http','ws')}/ws/${jobId}`
42
- const ws = new WebSocket(wsUrl)
 
 
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 d = await fetch(`${API_BASE}/api/status/${jobId}`).then(r=>r.json())
 
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
- const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8000'
 
 
 
 
 
 
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 res = await fetch(`${API_BASE}/api/status/${jobId}`)
 
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' })