GlazedDon0t commited on
Commit
f9480ae
·
1 Parent(s): 455b327
Files changed (5) hide show
  1. Dockerfile +3 -1
  2. frontend/src/App.tsx +30 -8
  3. src/app.py +11 -3
  4. src/inference_logic.py +31 -10
  5. start.sh +13 -3
Dockerfile CHANGED
@@ -31,9 +31,11 @@ RUN go mod init vchat-server && \
31
  # ==========================================
32
  FROM pytorch/pytorch:2.2.1-cuda12.1-cudnn8-runtime
33
 
 
 
34
  ENV PYTHONUNBUFFERED=1 \
35
  DEBIAN_FRONTEND=noninteractive \
36
- LITE_MODE=false \
37
  PATH="/home/user/.local/bin:$PATH" \
38
  PIP_NO_CACHE_DIR=1
39
 
 
31
  # ==========================================
32
  FROM pytorch/pytorch:2.2.1-cuda12.1-cudnn8-runtime
33
 
34
+ # Default to LITE_MODE=true to ensure space starts without massive model errors.
35
+ # Set to false only if GPU is present and model files are included.
36
  ENV PYTHONUNBUFFERED=1 \
37
  DEBIAN_FRONTEND=noninteractive \
38
+ LITE_MODE=true \
39
  PATH="/home/user/.local/bin:$PATH" \
40
  PIP_NO_CACHE_DIR=1
41
 
frontend/src/App.tsx CHANGED
@@ -3,7 +3,7 @@ import {
3
  AlertCircle, Play, Upload, Layers, Terminal, Cpu, Activity,
4
  FileText, Zap, MessageSquare, Sliders, LayoutDashboard, FileJson,
5
  ChevronDown, ChevronRight, Bot, Database, Trash2, Eye, StopCircle, List,
6
- CheckCircle, XCircle, BrainCircuit
7
  } from 'lucide-react';
8
 
9
  function App() {
@@ -45,15 +45,28 @@ function App() {
45
  appendLog(`[SYSTEM] Uploading ${file.name} to queue...\n`);
46
  try {
47
  const res = await fetch('/queue/upload_csv', { method: 'POST', body: fd });
 
 
 
 
 
 
48
  const data = await res.json();
49
  if(data.error) throw new Error(data.error);
50
  appendLog(`[SYSTEM] Upload complete. Added ${data.added} links.\n`);
51
  setRefreshTrigger(prev => prev + 1);
52
  } catch (err: any) {
53
  appendLog(`[ERROR] Upload failed: ${err.message}\n`);
 
 
 
54
  }
55
  };
56
 
 
 
 
 
57
  const handleStartQueue = async (e: React.FormEvent) => {
58
  e.preventDefault();
59
  setIsProcessing(true);
@@ -64,17 +77,21 @@ function App() {
64
 
65
  try {
66
  const response = await fetch('/queue/run', { method: 'POST', body: formData });
67
- if (!response.body) throw new Error("No response");
68
  const reader = response.body.getReader();
69
  const decoder = new TextDecoder();
70
  while (true) {
71
  const { done, value } = await reader.read();
72
  if (done) break;
73
  const chunk = decoder.decode(value, { stream: true });
74
- chunk.split('\n\n').forEach(line => {
75
- if (line.startsWith('data:')) appendLog(line.replace('data:', '').trim() + '\n');
76
- if (line.startsWith('event: close')) setIsProcessing(false);
77
- });
 
 
 
 
78
  }
79
  } catch (err: any) {
80
  appendLog(`\n[ERROR]: ${err.message}\n`);
@@ -153,7 +170,7 @@ function App() {
153
  {model === 'gemini' && (
154
  <div className="space-y-2">
155
  <input type="password" name="gemini_api_key" placeholder="Gemini API Key" className="w-full bg-black/20 border border-slate-800 rounded px-3 py-2 text-xs" />
156
- <input type="text" name="gemini_model_name" defaultValue="models/gemini-2.5-flash-lite" className="w-full bg-black/20 border border-slate-800 rounded px-3 py-2 text-xs" />
157
  </div>
158
  )}
159
  {model === 'vertex' && (
@@ -230,7 +247,12 @@ function App() {
230
  )}
231
  </div>
232
  ) : activeTab === 'moderation' ? (
233
- <button onClick={() => setRefreshTrigger(x=>x+1)} className="w-full py-3 bg-slate-800 hover:bg-slate-700 rounded-lg text-xs font-bold text-white">Refresh List</button>
 
 
 
 
 
234
  ) : (
235
  <button type="submit" form="control-form" className="w-full py-3 bg-indigo-600 hover:bg-indigo-500 rounded-lg text-xs font-bold text-white flex justify-center gap-2"><Play className="w-4 h-4"/> Run Labeler</button>
236
  )}
 
3
  AlertCircle, Play, Upload, Layers, Terminal, Cpu, Activity,
4
  FileText, Zap, MessageSquare, Sliders, LayoutDashboard, FileJson,
5
  ChevronDown, ChevronRight, Bot, Database, Trash2, Eye, StopCircle, List,
6
+ CheckCircle, XCircle, BrainCircuit, Download
7
  } from 'lucide-react';
8
 
9
  function App() {
 
45
  appendLog(`[SYSTEM] Uploading ${file.name} to queue...\n`);
46
  try {
47
  const res = await fetch('/queue/upload_csv', { method: 'POST', body: fd });
48
+
49
+ if (!res.ok) {
50
+ const errText = await res.text();
51
+ throw new Error(`Server responded with ${res.status}: ${errText.substring(0, 100)}`);
52
+ }
53
+
54
  const data = await res.json();
55
  if(data.error) throw new Error(data.error);
56
  appendLog(`[SYSTEM] Upload complete. Added ${data.added} links.\n`);
57
  setRefreshTrigger(prev => prev + 1);
58
  } catch (err: any) {
59
  appendLog(`[ERROR] Upload failed: ${err.message}\n`);
60
+ } finally {
61
+ // Reset input so same file can be selected again if needed
62
+ if (fileInputRef.current) fileInputRef.current.value = '';
63
  }
64
  };
65
 
66
+ const handleDownloadDataset = () => {
67
+ window.location.href = "/download-dataset";
68
+ };
69
+
70
  const handleStartQueue = async (e: React.FormEvent) => {
71
  e.preventDefault();
72
  setIsProcessing(true);
 
77
 
78
  try {
79
  const response = await fetch('/queue/run', { method: 'POST', body: formData });
80
+ if (!response.body) throw new Error("No response body");
81
  const reader = response.body.getReader();
82
  const decoder = new TextDecoder();
83
  while (true) {
84
  const { done, value } = await reader.read();
85
  if (done) break;
86
  const chunk = decoder.decode(value, { stream: true });
87
+ const lines = chunk.split('\n\n');
88
+ for (const line of lines) {
89
+ if (line.startsWith('data:')) {
90
+ const msg = line.replace('data:', '').trim();
91
+ if (msg) appendLog(msg + '\n');
92
+ }
93
+ if (line.includes('event: close')) setIsProcessing(false);
94
+ }
95
  }
96
  } catch (err: any) {
97
  appendLog(`\n[ERROR]: ${err.message}\n`);
 
170
  {model === 'gemini' && (
171
  <div className="space-y-2">
172
  <input type="password" name="gemini_api_key" placeholder="Gemini API Key" className="w-full bg-black/20 border border-slate-800 rounded px-3 py-2 text-xs" />
173
+ <input type="text" name="gemini_model_name" defaultValue="models/gemini-2.0-flash-exp" className="w-full bg-black/20 border border-slate-800 rounded px-3 py-2 text-xs" />
174
  </div>
175
  )}
176
  {model === 'vertex' && (
 
247
  )}
248
  </div>
249
  ) : activeTab === 'moderation' ? (
250
+ <div className="flex flex-col gap-2">
251
+ <button onClick={() => setRefreshTrigger(x=>x+1)} className="w-full py-3 bg-slate-800 hover:bg-slate-700 rounded-lg text-xs font-bold text-white">Refresh List</button>
252
+ <button onClick={handleDownloadDataset} className="w-full py-3 bg-indigo-900 hover:bg-indigo-800 rounded-lg text-xs font-bold text-white flex items-center justify-center gap-2">
253
+ <Download className="w-4 h-4"/> Download CSV
254
+ </button>
255
+ </div>
256
  ) : (
257
  <button type="submit" form="control-form" className="w-full py-3 bg-indigo-600 hover:bg-indigo-500 rounded-lg text-xs font-bold text-white flex justify-center gap-2"><Play className="w-4 h-4"/> Run Labeler</button>
258
  )}
src/app.py CHANGED
@@ -76,7 +76,7 @@ async def startup_event():
76
  except Exception as e:
77
  logging.fatal(f"Could not load models. Error: {e}", exc_info=True)
78
  else:
79
- logging.info("Running in LITE mode.")
80
 
81
  @app.get("/", response_class=HTMLResponse)
82
  async def read_root(request: Request):
@@ -370,7 +370,11 @@ async def stop_queue_processing():
370
  async def upload_csv_to_queue(file: UploadFile = File(...)):
371
  try:
372
  content = await file.read()
373
- decoded = content.decode('utf-8').splitlines()
 
 
 
 
374
  reader = csv.reader(decoded)
375
  links_to_add = []
376
  header = next(reader, None)
@@ -378,9 +382,12 @@ async def upload_csv_to_queue(file: UploadFile = File(...)):
378
 
379
  link_idx = 0
380
  header_lower = [h.lower() for h in header]
 
 
381
  if "link" in header_lower: link_idx = header_lower.index("link")
382
  elif "url" in header_lower: link_idx = header_lower.index("url")
383
- elif "http" in header[0]:
 
384
  links_to_add.append(header[0])
385
  link_idx = 0
386
 
@@ -413,6 +420,7 @@ async def upload_csv_to_queue(file: UploadFile = File(...)):
413
 
414
  return {"status": "success", "added": added_count}
415
  except Exception as e:
 
416
  return JSONResponse(status_code=400, content={"error": str(e), "status": "failed"})
417
 
418
  @app.post("/queue/run")
 
76
  except Exception as e:
77
  logging.fatal(f"Could not load models. Error: {e}", exc_info=True)
78
  else:
79
+ logging.info("Running in LITE mode (API Only).")
80
 
81
  @app.get("/", response_class=HTMLResponse)
82
  async def read_root(request: Request):
 
370
  async def upload_csv_to_queue(file: UploadFile = File(...)):
371
  try:
372
  content = await file.read()
373
+ try:
374
+ decoded = content.decode('utf-8').splitlines()
375
+ except UnicodeDecodeError:
376
+ decoded = content.decode('latin-1').splitlines()
377
+
378
  reader = csv.reader(decoded)
379
  links_to_add = []
380
  header = next(reader, None)
 
382
 
383
  link_idx = 0
384
  header_lower = [h.lower() for h in header]
385
+
386
+ # Determine column index
387
  if "link" in header_lower: link_idx = header_lower.index("link")
388
  elif "url" in header_lower: link_idx = header_lower.index("url")
389
+ elif len(header) > 0 and header[0].strip().startswith("http"):
390
+ # Headerless, first row is data
391
  links_to_add.append(header[0])
392
  link_idx = 0
393
 
 
420
 
421
  return {"status": "success", "added": added_count}
422
  except Exception as e:
423
+ logging.error(f"Upload CSV error: {e}")
424
  return JSONResponse(status_code=400, content={"error": str(e), "status": "failed"})
425
 
426
  @app.post("/queue/run")
src/inference_logic.py CHANGED
@@ -16,6 +16,7 @@ from labeling_logic import (
16
  )
17
  from toon_parser import parse_veracity_toon
18
 
 
19
  try:
20
  import google.generativeai as genai_legacy
21
  from google.generativeai.types import generation_types
@@ -23,6 +24,7 @@ except ImportError:
23
  genai_legacy = None
24
 
25
  try:
 
26
  from google import genai
27
  from google.genai.types import (
28
  GenerateContentConfig,
@@ -47,16 +49,27 @@ logger = logging.getLogger(__name__)
47
 
48
  def load_models():
49
  if LITE_MODE:
50
- logger.info("LITE_MODE is enabled. Skipping local model loading.")
51
  return
 
52
  global processor, base_model, peft_model, active_model
53
  if base_model is not None: return
 
 
 
 
 
 
 
 
 
54
  if not torch.cuda.is_available():
55
- raise RuntimeError("CUDA is not available. This application requires a GPU for local models.")
 
 
56
 
57
  device = torch.device("cuda")
58
  logger.info(f"CUDA is available. Initializing models on {device}...")
59
- local_model_path = "/app/local_model"
60
 
61
  try:
62
  import flash_attn
@@ -65,11 +78,16 @@ def load_models():
65
  attn_implementation = "sdpa"
66
 
67
  logger.info(f"Loading base model from {local_model_path}...")
68
- base_model = Qwen3VLForConditionalGeneration.from_pretrained(
69
- local_model_path, dtype=torch.bfloat16, device_map="auto", attn_implementation=attn_implementation
70
- ).eval()
71
- processor = AutoProcessor.from_pretrained(local_model_path)
72
- active_model = base_model
 
 
 
 
 
73
 
74
  def switch_active_model(model_name: str):
75
  global active_model, base_model, peft_model
@@ -79,6 +97,9 @@ def switch_active_model(model_name: str):
79
  active_model = base_model
80
 
81
  def inference_step(video_path, prompt, generation_kwargs, sampling_fps, pred_glue=None):
 
 
 
82
  global processor, active_model
83
  if active_model is None: raise RuntimeError("Models not loaded.")
84
 
@@ -139,7 +160,7 @@ async def attempt_toon_repair(original_text: str, schema: str, client, model_typ
139
  repaired_text = ""
140
 
141
  if model_type == 'gemini':
142
- model = genai_legacy.GenerativeModel("models/gemini-2.5-flash-lite")
143
  response = await loop.run_in_executor(
144
  None,
145
  lambda: model.generate_content(repair_prompt, generation_config={"temperature": 0.0})
@@ -178,7 +199,7 @@ async def run_gemini_labeling_pipeline(video_path: str, caption: str, transcript
178
  uploaded_file = await loop.run_in_executor(None, lambda: genai_legacy.upload_file(path=video_path))
179
  while uploaded_file.state.name == "PROCESSING": await asyncio.sleep(2)
180
 
181
- model = genai_legacy.GenerativeModel("models/gemini-2.5-flash-lite")
182
  toon_schema = SCHEMA_REASONING if include_comments else SCHEMA_SIMPLE
183
  score_instructions = SCORE_INSTRUCTIONS_REASONING if include_comments else SCORE_INSTRUCTIONS_SIMPLE
184
 
 
16
  )
17
  from toon_parser import parse_veracity_toon
18
 
19
+ # Google GenAI Imports
20
  try:
21
  import google.generativeai as genai_legacy
22
  from google.generativeai.types import generation_types
 
24
  genai_legacy = None
25
 
26
  try:
27
+ # Modern Google GenAI SDK (v1)
28
  from google import genai
29
  from google.genai.types import (
30
  GenerateContentConfig,
 
49
 
50
  def load_models():
51
  if LITE_MODE:
52
+ logger.info("LITE_MODE is enabled via Env. Skipping local model loading.")
53
  return
54
+
55
  global processor, base_model, peft_model, active_model
56
  if base_model is not None: return
57
+
58
+ local_model_path = "/app/local_model"
59
+ if not os.path.exists(local_model_path) or not os.listdir(local_model_path):
60
+ logger.warning(f"Local model path {local_model_path} does not exist or is empty.")
61
+ logger.warning("Forcing LITE_MODE (API only) to prevent crash.")
62
+ global LITE_MODE
63
+ LITE_MODE = True
64
+ return
65
+
66
  if not torch.cuda.is_available():
67
+ logger.warning("CUDA is not available. Forcing LITE_MODE (API only).")
68
+ LITE_MODE = True
69
+ return
70
 
71
  device = torch.device("cuda")
72
  logger.info(f"CUDA is available. Initializing models on {device}...")
 
73
 
74
  try:
75
  import flash_attn
 
78
  attn_implementation = "sdpa"
79
 
80
  logger.info(f"Loading base model from {local_model_path}...")
81
+ try:
82
+ base_model = Qwen3VLForConditionalGeneration.from_pretrained(
83
+ local_model_path, dtype=torch.bfloat16, device_map="auto", attn_implementation=attn_implementation
84
+ ).eval()
85
+ processor = AutoProcessor.from_pretrained(local_model_path)
86
+ active_model = base_model
87
+ except Exception as e:
88
+ logger.error(f"Failed to load local model: {e}")
89
+ logger.warning("Falling back to LITE_MODE.")
90
+ LITE_MODE = True
91
 
92
  def switch_active_model(model_name: str):
93
  global active_model, base_model, peft_model
 
97
  active_model = base_model
98
 
99
  def inference_step(video_path, prompt, generation_kwargs, sampling_fps, pred_glue=None):
100
+ if LITE_MODE:
101
+ return "Local inference not available in LITE_MODE."
102
+
103
  global processor, active_model
104
  if active_model is None: raise RuntimeError("Models not loaded.")
105
 
 
160
  repaired_text = ""
161
 
162
  if model_type == 'gemini':
163
+ model = genai_legacy.GenerativeModel("models/gemini-2.0-flash-exp")
164
  response = await loop.run_in_executor(
165
  None,
166
  lambda: model.generate_content(repair_prompt, generation_config={"temperature": 0.0})
 
199
  uploaded_file = await loop.run_in_executor(None, lambda: genai_legacy.upload_file(path=video_path))
200
  while uploaded_file.state.name == "PROCESSING": await asyncio.sleep(2)
201
 
202
+ model = genai_legacy.GenerativeModel("models/gemini-2.0-flash-exp")
203
  toon_schema = SCHEMA_REASONING if include_comments else SCHEMA_SIMPLE
204
  score_instructions = SCORE_INSTRUCTIONS_REASONING if include_comments else SCORE_INSTRUCTIONS_SIMPLE
205
 
start.sh CHANGED
@@ -3,10 +3,20 @@
3
  # 1. Start Python FastAPI in the background (Internal Port 8001)
4
  echo "Starting Python Inference Engine..."
5
  export PYTHONPATH=$PYTHONPATH:/app/src
6
- python -m uvicorn src.app:app --host 127.0.0.1 --port 8001 &
 
7
 
8
- # Wait for Python to initialize
9
- sleep 5
 
 
 
 
 
 
 
 
 
10
 
11
  # 2. Start Golang Web Server (Public Port 7860)
12
  echo "Starting Go Web Server..."
 
3
  # 1. Start Python FastAPI in the background (Internal Port 8001)
4
  echo "Starting Python Inference Engine..."
5
  export PYTHONPATH=$PYTHONPATH:/app/src
6
+ # Use --log-level info to see startup issues
7
+ python -m uvicorn src.app:app --host 127.0.0.1 --port 8001 --log-level info &
8
 
9
+ # Wait longer for Python to initialize, or until port is open
10
+ echo "Waiting for Python backend to initialize..."
11
+ timeout=30
12
+ while ! curl -s http://127.0.0.1:8001/ > /dev/null; do
13
+ sleep 2
14
+ timeout=$((timeout-2))
15
+ if [ $timeout -le 0 ]; then
16
+ echo "Python backend failed to start on time. Logs might show why."
17
+ break
18
+ fi
19
+ done
20
 
21
  # 2. Start Golang Web Server (Public Port 7860)
22
  echo "Starting Go Web Server..."