Spaces:
Paused
Paused
Commit ·
bf340fa
1
Parent(s): ff97980
fix: Add safe response handling and error alerts for text and voice queries in web UI
Browse files- .agents/skills/huggingface-nexus/SKILL.md +1 -1
- Dockerfile +25 -11
- README.md +0 -12
- app.py +0 -8
- config.py +3 -6
- main.py +9 -17
- packages.txt +0 -3
- upload_to_hf.py +0 -22
- vector_search/embed.py +7 -4
- vector_search/rerank.py +2 -1
- web_ui/index.html +43 -46
.agents/skills/huggingface-nexus/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
name:
|
| 3 |
description: "Hugging Face Hub CLI (`hf`) for downloading, uploading, and managing models, datasets, spaces, buckets, repos, papers, jobs, and more on the Hugging Face Hub. Use when: handling authentication; managing local cache; managing Hugging Face Buckets; running or scheduling jobs on Hugging Face infrastructure; managing Hugging Face repos; discussions and pull requests; browsing models, datasets and spaces; reading, searching, or browsing academic papers; managing collections; querying datasets; configuring spaces; setting up webhooks; or deploying and managing HF Inference Endpoints. Make sure to use this skill whenever the user mentions 'hf', 'huggingface', 'Hugging Face', 'huggingface-cli', or 'hugging face cli', or wants to do anything related to the Hugging Face ecosystem and to AI and ML in general. Also use for cloud storage needs like training checkpoints, data pipelines, or agent traces. Use even if the user doesn't explicitly ask for a CLI command. Replaces the deprecated `huggingface-cli`."
|
| 4 |
---
|
| 5 |
|
|
|
|
| 1 |
---
|
| 2 |
+
name: hf-cli
|
| 3 |
description: "Hugging Face Hub CLI (`hf`) for downloading, uploading, and managing models, datasets, spaces, buckets, repos, papers, jobs, and more on the Hugging Face Hub. Use when: handling authentication; managing local cache; managing Hugging Face Buckets; running or scheduling jobs on Hugging Face infrastructure; managing Hugging Face repos; discussions and pull requests; browsing models, datasets and spaces; reading, searching, or browsing academic papers; managing collections; querying datasets; configuring spaces; setting up webhooks; or deploying and managing HF Inference Endpoints. Make sure to use this skill whenever the user mentions 'hf', 'huggingface', 'Hugging Face', 'huggingface-cli', or 'hugging face cli', or wants to do anything related to the Hugging Face ecosystem and to AI and ML in general. Also use for cloud storage needs like training checkpoints, data pipelines, or agent traces. Use even if the user doesn't explicitly ask for a CLI command. Replaces the deprecated `huggingface-cli`."
|
| 4 |
---
|
| 5 |
|
Dockerfile
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
FROM python:3.11-slim
|
| 2 |
|
| 3 |
# Set environment variables
|
|
@@ -5,16 +6,10 @@ ENV PYTHONUNBUFFERED=1 \
|
|
| 5 |
PYTHONDONTWRITEBYTECODE=1 \
|
| 6 |
PORT=7860 \
|
| 7 |
HOST=0.0.0.0 \
|
| 8 |
-
OMP_NUM_THREADS=1 \
|
| 9 |
-
MKL_NUM_THREADS=1 \
|
| 10 |
-
OPENBLAS_NUM_THREADS=1 \
|
| 11 |
-
VECLIB_MAXIMUM_THREADS=1 \
|
| 12 |
-
NUMEXPR_NUM_THREADS=1 \
|
| 13 |
-
TOKENIZERS_PARALLELISM=false \
|
| 14 |
DATA_DIR=/app/knowledge_base \
|
| 15 |
INDEX_DIR=/app/knowledge_base/indexes \
|
| 16 |
PROCESSED_DIR=/app/knowledge_base/processed \
|
| 17 |
-
HF_HOME=/
|
| 18 |
|
| 19 |
WORKDIR /app
|
| 20 |
|
|
@@ -32,11 +27,30 @@ COPY requirements.txt .
|
|
| 32 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 33 |
pip install --no-cache-dir -r requirements.txt
|
| 34 |
|
| 35 |
-
#
|
| 36 |
-
|
| 37 |
|
| 38 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
EXPOSE 7860
|
| 40 |
|
| 41 |
# Launch FastAPI server
|
| 42 |
-
CMD ["
|
|
|
|
| 1 |
+
# Hugging Face Spaces Docker SDK Image
|
| 2 |
FROM python:3.11-slim
|
| 3 |
|
| 4 |
# Set environment variables
|
|
|
|
| 6 |
PYTHONDONTWRITEBYTECODE=1 \
|
| 7 |
PORT=7860 \
|
| 8 |
HOST=0.0.0.0 \
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
DATA_DIR=/app/knowledge_base \
|
| 10 |
INDEX_DIR=/app/knowledge_base/indexes \
|
| 11 |
PROCESSED_DIR=/app/knowledge_base/processed \
|
| 12 |
+
HF_HOME=/app/.cache/huggingface
|
| 13 |
|
| 14 |
WORKDIR /app
|
| 15 |
|
|
|
|
| 27 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 28 |
pip install --no-cache-dir -r requirements.txt
|
| 29 |
|
| 30 |
+
# Pre-cache embedding model weights into Docker image at build time
|
| 31 |
+
RUN python -c "from transformers import AutoTokenizer, AutoModel; AutoTokenizer.from_pretrained('intfloat/multilingual-e5-small'); AutoModel.from_pretrained('intfloat/multilingual-e5-small')"
|
| 32 |
|
| 33 |
+
# Copy application source code and pre-built index/processed artifacts
|
| 34 |
+
COPY config.py .
|
| 35 |
+
COPY main.py .
|
| 36 |
+
COPY knowledge_base/ ./knowledge_base/
|
| 37 |
+
COPY doc_chunking/ ./doc_chunking/
|
| 38 |
+
COPY vector_search/ ./vector_search/
|
| 39 |
+
COPY voice_stt/ ./voice_stt/
|
| 40 |
+
COPY safety_guardrails/ ./safety_guardrails/
|
| 41 |
+
COPY llm_synthesis/ ./llm_synthesis/
|
| 42 |
+
COPY rag_pipeline/ ./rag_pipeline/
|
| 43 |
+
COPY api_endpoints/ ./api_endpoints/
|
| 44 |
+
COPY latency_benchmarks/ ./latency_benchmarks/
|
| 45 |
+
COPY web_ui/ ./web_ui/
|
| 46 |
+
|
| 47 |
+
# Create user for Hugging Face Spaces security
|
| 48 |
+
RUN useradd -m -u 1000 user && \
|
| 49 |
+
chown -R user:user /app
|
| 50 |
+
USER user
|
| 51 |
+
|
| 52 |
+
# Expose default Hugging Face Spaces port
|
| 53 |
EXPOSE 7860
|
| 54 |
|
| 55 |
# Launch FastAPI server
|
| 56 |
+
CMD ["python", "main.py"]
|
README.md
CHANGED
|
@@ -1,15 +1,3 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: VisionQuest Voice Indic RAG
|
| 3 |
-
emoji: ⚡
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: indigo
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 5.20.0
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
license: mit
|
| 11 |
-
---
|
| 12 |
-
|
| 13 |
# ⚡ VECTOR: Voice-Enabled Multilingual Indic RAG Engine
|
| 14 |
|
| 15 |
<div align="center">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# ⚡ VECTOR: Voice-Enabled Multilingual Indic RAG Engine
|
| 2 |
|
| 3 |
<div align="center">
|
app.py
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
import main
|
| 2 |
-
|
| 3 |
-
# Expose main.app for Hugging Face Spaces Gradio / FastAPI launcher
|
| 4 |
-
app = main.app
|
| 5 |
-
|
| 6 |
-
if __name__ == "__main__":
|
| 7 |
-
import uvicorn
|
| 8 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
config.py
CHANGED
|
@@ -12,13 +12,10 @@ import os
|
|
| 12 |
from pathlib import Path
|
| 13 |
from dotenv import load_dotenv
|
| 14 |
|
| 15 |
-
# Limit background thread creation
|
| 16 |
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
| 17 |
-
os.environ["OMP_NUM_THREADS"] = "
|
| 18 |
-
os.environ["MKL_NUM_THREADS"] = "
|
| 19 |
-
os.environ["OPENBLAS_NUM_THREADS"] = "1"
|
| 20 |
-
os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
|
| 21 |
-
os.environ["NUMEXPR_NUM_THREADS"] = "1"
|
| 22 |
|
| 23 |
# Load environment variables
|
| 24 |
load_dotenv()
|
|
|
|
| 12 |
from pathlib import Path
|
| 13 |
from dotenv import load_dotenv
|
| 14 |
|
| 15 |
+
# Limit background thread creation to preserve Windows system resources
|
| 16 |
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
| 17 |
+
os.environ["OMP_NUM_THREADS"] = "2"
|
| 18 |
+
os.environ["MKL_NUM_THREADS"] = "2"
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# Load environment variables
|
| 21 |
load_dotenv()
|
main.py
CHANGED
|
@@ -88,7 +88,13 @@ app.add_middleware(
|
|
| 88 |
# Preload models and perform full pipeline warmup asynchronously at startup
|
| 89 |
@app.on_event("startup")
|
| 90 |
async def startup_event():
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
|
| 94 |
@app.get("/", response_class=HTMLResponse)
|
|
@@ -128,15 +134,6 @@ async def serve_cap() -> FileResponse:
|
|
| 128 |
raise HTTPException(status_code=404, detail="Captain America image missing")
|
| 129 |
|
| 130 |
|
| 131 |
-
@app.get("/spider_gwen.png")
|
| 132 |
-
async def serve_spider_gwen() -> FileResponse:
|
| 133 |
-
"""Serve Spider-Gwen sprite image."""
|
| 134 |
-
img_path = config.BASE_DIR / "web_ui" / "spider_gwen.png"
|
| 135 |
-
if img_path.exists():
|
| 136 |
-
return FileResponse(img_path)
|
| 137 |
-
raise HTTPException(status_code=404, detail="Spider-Gwen image missing")
|
| 138 |
-
|
| 139 |
-
|
| 140 |
@app.get("/health", response_class=JSONResponse)
|
| 141 |
async def health_check() -> Dict[str, Any]:
|
| 142 |
"""Health check reporting system and index readiness."""
|
|
@@ -243,12 +240,7 @@ app = gr.mount_gradio_app(app, demo, path="/gradio")
|
|
| 243 |
|
| 244 |
|
| 245 |
if __name__ == "__main__":
|
| 246 |
-
import sys
|
| 247 |
port = int(os.getenv("PORT", "7860"))
|
| 248 |
host = os.getenv("HOST", "0.0.0.0")
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
if share:
|
| 252 |
-
demo.launch(server_name=host, server_port=port, share=True)
|
| 253 |
-
else:
|
| 254 |
-
uvicorn.run(app, host=host, port=port)
|
|
|
|
| 88 |
# Preload models and perform full pipeline warmup asynchronously at startup
|
| 89 |
@app.on_event("startup")
|
| 90 |
async def startup_event():
|
| 91 |
+
async def run_warmup():
|
| 92 |
+
print("[Space Startup] Preloading embedding model, FAISS indexes, and warming up pipeline...")
|
| 93 |
+
orchestrator = get_orchestrator()
|
| 94 |
+
await asyncio.to_thread(orchestrator.warmup_pipeline)
|
| 95 |
+
print("[Space Startup] Full RAG pipeline preloaded and warmed up successfully.")
|
| 96 |
+
|
| 97 |
+
asyncio.create_task(run_warmup())
|
| 98 |
|
| 99 |
|
| 100 |
@app.get("/", response_class=HTMLResponse)
|
|
|
|
| 134 |
raise HTTPException(status_code=404, detail="Captain America image missing")
|
| 135 |
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
@app.get("/health", response_class=JSONResponse)
|
| 138 |
async def health_check() -> Dict[str, Any]:
|
| 139 |
"""Health check reporting system and index readiness."""
|
|
|
|
| 240 |
|
| 241 |
|
| 242 |
if __name__ == "__main__":
|
|
|
|
| 243 |
port = int(os.getenv("PORT", "7860"))
|
| 244 |
host = os.getenv("HOST", "0.0.0.0")
|
| 245 |
+
print(f"[Space Startup] Starting VECTOR Command Center UI on http://{host}:{port}")
|
| 246 |
+
uvicorn.run(app, host=host, port=port)
|
|
|
|
|
|
|
|
|
|
|
|
packages.txt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
ffmpeg
|
| 2 |
-
libsndfile1
|
| 3 |
-
build-essential
|
|
|
|
|
|
|
|
|
|
|
|
upload_to_hf.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import sys
|
| 3 |
-
from huggingface_hub import HfApi
|
| 4 |
-
|
| 5 |
-
def upload():
|
| 6 |
-
token = input("Paste your Hugging Face Token (hf_...): ").strip()
|
| 7 |
-
if not token:
|
| 8 |
-
print("Error: Token is required.")
|
| 9 |
-
return
|
| 10 |
-
|
| 11 |
-
print("\nUploading full repository (including all subfolders) to Hugging Face Space...")
|
| 12 |
-
api = HfApi(token=token)
|
| 13 |
-
api.upload_folder(
|
| 14 |
-
folder_path=".",
|
| 15 |
-
repo_id="rishikvelagapudi/visionquest",
|
| 16 |
-
repo_type="space",
|
| 17 |
-
ignore_patterns=[".git*", "*.pyc", "__pycache__", ".env", "venv", ".gemini", ".agents"]
|
| 18 |
-
)
|
| 19 |
-
print("\nSUCCESS! All subdirectories and code files are uploaded to Hugging Face Spaces!")
|
| 20 |
-
|
| 21 |
-
if __name__ == "__main__":
|
| 22 |
-
upload()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vector_search/embed.py
CHANGED
|
@@ -12,10 +12,17 @@ import os
|
|
| 12 |
from pathlib import Path
|
| 13 |
from typing import List, Union
|
| 14 |
import numpy as np
|
|
|
|
| 15 |
import config
|
| 16 |
|
| 17 |
logger = logging.getLogger(__name__)
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
_EMBEDDER_INSTANCE = None
|
| 20 |
|
| 21 |
|
|
@@ -183,9 +190,6 @@ class PyTorchMultilingualE5Embedder:
|
|
| 183 |
PyTorch fallback wrapper for sentence-transformers multilingual-e5-small.
|
| 184 |
"""
|
| 185 |
def __init__(self, model_name: str = config.EMBEDDING_MODEL_NAME):
|
| 186 |
-
import torch
|
| 187 |
-
torch.set_grad_enabled(False)
|
| 188 |
-
torch.set_num_threads(1)
|
| 189 |
from sentence_transformers import SentenceTransformer
|
| 190 |
logger.info(f"Loading PyTorch fallback embedding model: '{model_name}'...")
|
| 191 |
self.model_name = model_name
|
|
@@ -193,7 +197,6 @@ class PyTorchMultilingualE5Embedder:
|
|
| 193 |
self.model = SentenceTransformer(model_name, local_files_only=True)
|
| 194 |
except Exception:
|
| 195 |
self.model = SentenceTransformer(model_name)
|
| 196 |
-
self.model.eval()
|
| 197 |
self.dim = config.EMBEDDING_DIM
|
| 198 |
logger.info(f"PyTorch embedding model loaded (dim={self.dim}).")
|
| 199 |
|
|
|
|
| 12 |
from pathlib import Path
|
| 13 |
from typing import List, Union
|
| 14 |
import numpy as np
|
| 15 |
+
import torch
|
| 16 |
import config
|
| 17 |
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
+
# Optimize PyTorch CPU parallelism
|
| 21 |
+
try:
|
| 22 |
+
torch.set_num_threads(max(1, torch.get_num_threads()))
|
| 23 |
+
except Exception:
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
_EMBEDDER_INSTANCE = None
|
| 27 |
|
| 28 |
|
|
|
|
| 190 |
PyTorch fallback wrapper for sentence-transformers multilingual-e5-small.
|
| 191 |
"""
|
| 192 |
def __init__(self, model_name: str = config.EMBEDDING_MODEL_NAME):
|
|
|
|
|
|
|
|
|
|
| 193 |
from sentence_transformers import SentenceTransformer
|
| 194 |
logger.info(f"Loading PyTorch fallback embedding model: '{model_name}'...")
|
| 195 |
self.model_name = model_name
|
|
|
|
| 197 |
self.model = SentenceTransformer(model_name, local_files_only=True)
|
| 198 |
except Exception:
|
| 199 |
self.model = SentenceTransformer(model_name)
|
|
|
|
| 200 |
self.dim = config.EMBEDDING_DIM
|
| 201 |
logger.info(f"PyTorch embedding model loaded (dim={self.dim}).")
|
| 202 |
|
vector_search/rerank.py
CHANGED
|
@@ -4,7 +4,9 @@ import re
|
|
| 4 |
from pathlib import Path
|
| 5 |
from typing import Any, Dict, List
|
| 6 |
import numpy as np
|
|
|
|
| 7 |
from rank_bm25 import BM25Okapi
|
|
|
|
| 8 |
import config
|
| 9 |
|
| 10 |
logger = logging.getLogger(__name__)
|
|
@@ -178,7 +180,6 @@ class PyTorchCrossEncoderRanker:
|
|
| 178 |
PyTorch fallback wrapper for sentence-transformers CrossEncoder.
|
| 179 |
"""
|
| 180 |
def __init__(self, model_name: str = config.CROSS_ENCODER_MODEL_NAME):
|
| 181 |
-
from sentence_transformers.cross_encoder import CrossEncoder
|
| 182 |
load_path = model_name
|
| 183 |
local_cache = getattr(config, "CROSS_ENCODER_LOCAL_CACHE", None)
|
| 184 |
if local_cache and Path(local_cache).exists():
|
|
|
|
| 4 |
from pathlib import Path
|
| 5 |
from typing import Any, Dict, List
|
| 6 |
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
from rank_bm25 import BM25Okapi
|
| 9 |
+
from sentence_transformers.cross_encoder import CrossEncoder
|
| 10 |
import config
|
| 11 |
|
| 12 |
logger = logging.getLogger(__name__)
|
|
|
|
| 180 |
PyTorch fallback wrapper for sentence-transformers CrossEncoder.
|
| 181 |
"""
|
| 182 |
def __init__(self, model_name: str = config.CROSS_ENCODER_MODEL_NAME):
|
|
|
|
| 183 |
load_path = model_name
|
| 184 |
local_cache = getattr(config, "CROSS_ENCODER_LOCAL_CACHE", None)
|
| 185 |
if local_cache and Path(local_cache).exists():
|
web_ui/index.html
CHANGED
|
@@ -766,13 +766,13 @@
|
|
| 766 |
</div>
|
| 767 |
</div>
|
| 768 |
|
| 769 |
-
<!-- RIGHT:
|
| 770 |
<div class="lg:col-span-6 shape-guardrail-matrix beach-sand-bg p-5 flex flex-col justify-between gap-4 relative z-20 bg-[#FFFBEA] opacity-100">
|
| 771 |
|
| 772 |
<!-- Header -->
|
| 773 |
<div class="flex items-center justify-between border-b-3 border-black pb-2.5">
|
| 774 |
<h4 class="font-comic font-black text-lg md:text-xl text-black tracking-wider flex items-center gap-2">
|
| 775 |
-
<span>🛡️
|
| 776 |
</h4>
|
| 777 |
<span class="comic-badge px-2.5 py-0.5 bg-black text-beachgreen text-xs shadow-brutal-sm">4-TIER ACTIVE</span>
|
| 778 |
</div>
|
|
@@ -964,6 +964,18 @@
|
|
| 964 |
|
| 965 |
try {
|
| 966 |
const response = await fetch("/query", { method: "POST", body: formData });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
const data = await response.json();
|
| 968 |
renderResults(data);
|
| 969 |
} catch (err) {
|
|
@@ -1029,6 +1041,18 @@
|
|
| 1029 |
|
| 1030 |
try {
|
| 1031 |
const response = await fetch("/query", { method: "POST", body: formData });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1032 |
const data = await response.json();
|
| 1033 |
renderResults(data);
|
| 1034 |
} catch (err) {
|
|
@@ -1429,26 +1453,8 @@
|
|
| 1429 |
let mainIronmanLoaded = false;
|
| 1430 |
mainIronmanImg.onload = () => { mainIronmanLoaded = true; };
|
| 1431 |
|
| 1432 |
-
const thorImg = new Image();
|
| 1433 |
-
thorImg.src = "/thor.png";
|
| 1434 |
-
let thorLoaded = false;
|
| 1435 |
-
thorImg.onload = () => { thorLoaded = true; };
|
| 1436 |
-
|
| 1437 |
-
const capImg = new Image();
|
| 1438 |
-
capImg.src = "/cap.png";
|
| 1439 |
-
let capLoaded = false;
|
| 1440 |
-
capImg.onload = () => { capLoaded = true; };
|
| 1441 |
-
|
| 1442 |
-
const gwenImg = new Image();
|
| 1443 |
-
gwenImg.src = "/spider_gwen.png";
|
| 1444 |
-
let gwenLoaded = false;
|
| 1445 |
-
gwenImg.onload = () => { gwenLoaded = true; };
|
| 1446 |
-
|
| 1447 |
let mainImX = -220;
|
| 1448 |
const mainThrusterParticles = [];
|
| 1449 |
-
const thorSparks = [];
|
| 1450 |
-
const capSparks = [];
|
| 1451 |
-
const gwenParticles = [];
|
| 1452 |
|
| 1453 |
function drawMainPageIronMan() {
|
| 1454 |
if (!mainCanvas || !mainCtx) return;
|
|
@@ -1773,35 +1779,26 @@
|
|
| 1773 |
mainCtx.restore();
|
| 1774 |
}
|
| 1775 |
|
| 1776 |
-
|
| 1777 |
-
|
| 1778 |
-
// ==========================================
|
| 1779 |
-
const gwenX = mainImX - 410;
|
| 1780 |
-
const gwenY = baseLeaderY;
|
| 1781 |
-
const gwenW = 150;
|
| 1782 |
-
const gwenH = 220;
|
| 1783 |
|
| 1784 |
-
|
| 1785 |
-
|
| 1786 |
-
|
| 1787 |
-
|
| 1788 |
-
|
| 1789 |
|
| 1790 |
-
|
| 1791 |
-
|
| 1792 |
-
const webGrad = mainCtx.createRadialGradient(gwenX + gwenW * 0.5, gwenY + gwenH * 0.4, 0, gwenX + gwenW * 0.5, gwenY + gwenH * 0.4, 24);
|
| 1793 |
-
webGrad.addColorStop(0, "rgba(255, 255, 255, 0.95)");
|
| 1794 |
-
webGrad.addColorStop(0.4, "rgba(255, 0, 150, 0.85)");
|
| 1795 |
-
webGrad.addColorStop(1, "rgba(255, 0, 150, 0)");
|
| 1796 |
-
mainCtx.fillStyle = webGrad;
|
| 1797 |
-
mainCtx.beginPath();
|
| 1798 |
-
mainCtx.arc(gwenX + gwenW * 0.5, gwenY + gwenH * 0.4, 24, 0, Math.PI * 2);
|
| 1799 |
-
mainCtx.fill();
|
| 1800 |
-
mainCtx.restore();
|
| 1801 |
-
}
|
| 1802 |
|
| 1803 |
-
|
| 1804 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1805 |
|
| 1806 |
function drawLightningArc(c, x1, y1, x2, y2, roughness = 14) {
|
| 1807 |
const dx = x2 - x1;
|
|
|
|
| 766 |
</div>
|
| 767 |
</div>
|
| 768 |
|
| 769 |
+
<!-- RIGHT: GUARDRAIL AUDIT MATRIX (SURFBOARD SHIELD SHAPE & BEACH SAND THEME) -->
|
| 770 |
<div class="lg:col-span-6 shape-guardrail-matrix beach-sand-bg p-5 flex flex-col justify-between gap-4 relative z-20 bg-[#FFFBEA] opacity-100">
|
| 771 |
|
| 772 |
<!-- Header -->
|
| 773 |
<div class="flex items-center justify-between border-b-3 border-black pb-2.5">
|
| 774 |
<h4 class="font-comic font-black text-lg md:text-xl text-black tracking-wider flex items-center gap-2">
|
| 775 |
+
<span>🛡️ GUARDRAIL AUDIT MATRIX 🏄♂️</span>
|
| 776 |
</h4>
|
| 777 |
<span class="comic-badge px-2.5 py-0.5 bg-black text-beachgreen text-xs shadow-brutal-sm">4-TIER ACTIVE</span>
|
| 778 |
</div>
|
|
|
|
| 964 |
|
| 965 |
try {
|
| 966 |
const response = await fetch("/query", { method: "POST", body: formData });
|
| 967 |
+
if (!response.ok) {
|
| 968 |
+
const errText = await response.text();
|
| 969 |
+
let msg = response.statusText || ("HTTP " + response.status);
|
| 970 |
+
try {
|
| 971 |
+
const parsed = JSON.parse(errText);
|
| 972 |
+
msg = parsed.detail || parsed.reason || msg;
|
| 973 |
+
} catch (_) {
|
| 974 |
+
if (errText) msg = errText;
|
| 975 |
+
}
|
| 976 |
+
alert("Query Error (" + response.status + "): " + msg);
|
| 977 |
+
return;
|
| 978 |
+
}
|
| 979 |
const data = await response.json();
|
| 980 |
renderResults(data);
|
| 981 |
} catch (err) {
|
|
|
|
| 1041 |
|
| 1042 |
try {
|
| 1043 |
const response = await fetch("/query", { method: "POST", body: formData });
|
| 1044 |
+
if (!response.ok) {
|
| 1045 |
+
const errText = await response.text();
|
| 1046 |
+
let msg = response.statusText || ("HTTP " + response.status);
|
| 1047 |
+
try {
|
| 1048 |
+
const parsed = JSON.parse(errText);
|
| 1049 |
+
msg = parsed.detail || parsed.reason || msg;
|
| 1050 |
+
} catch (_) {
|
| 1051 |
+
if (errText) msg = errText;
|
| 1052 |
+
}
|
| 1053 |
+
alert("Voice Query Error (" + response.status + "): " + msg);
|
| 1054 |
+
return;
|
| 1055 |
+
}
|
| 1056 |
const data = await response.json();
|
| 1057 |
renderResults(data);
|
| 1058 |
} catch (err) {
|
|
|
|
| 1453 |
let mainIronmanLoaded = false;
|
| 1454 |
mainIronmanImg.onload = () => { mainIronmanLoaded = true; };
|
| 1455 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1456 |
let mainImX = -220;
|
| 1457 |
const mainThrusterParticles = [];
|
|
|
|
|
|
|
|
|
|
| 1458 |
|
| 1459 |
function drawMainPageIronMan() {
|
| 1460 |
if (!mainCanvas || !mainCtx) return;
|
|
|
|
| 1779 |
mainCtx.restore();
|
| 1780 |
}
|
| 1781 |
|
| 1782 |
+
requestAnimationFrame(drawMainPageIronMan);
|
| 1783 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1784 |
|
| 1785 |
+
// Captain America Sprite Loader
|
| 1786 |
+
const capImg = new Image();
|
| 1787 |
+
capImg.src = "/cap.png";
|
| 1788 |
+
let capLoaded = false;
|
| 1789 |
+
capImg.onload = () => { capLoaded = true; };
|
| 1790 |
|
| 1791 |
+
let capX = -650;
|
| 1792 |
+
const capSparks = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1793 |
|
| 1794 |
+
// Thor Sprite & Lightning Helper
|
| 1795 |
+
const thorImg = new Image();
|
| 1796 |
+
thorImg.src = "/thor.png";
|
| 1797 |
+
let thorLoaded = false;
|
| 1798 |
+
thorImg.onload = () => { thorLoaded = true; };
|
| 1799 |
+
|
| 1800 |
+
let thorX = -450;
|
| 1801 |
+
const thorSparks = [];
|
| 1802 |
|
| 1803 |
function drawLightningArc(c, x1, y1, x2, y2, roughness = 14) {
|
| 1804 |
const dx = x2 - x1;
|