Spaces:
Sleeping
Sleeping
Commit ·
2c5cb17
0
Parent(s):
Initial commit of GenAI VQA System
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .ipynb_checkpoints/ai_router-checkpoint.py +14 -0
- .ipynb_checkpoints/config-checkpoint.py +15 -0
- .ipynb_checkpoints/main-checkpoint.py +73 -0
- .ipynb_checkpoints/test-checkpoint.py +8 -0
- Dockerfile +17 -0
- README.md +11 -0
- __pycache__/ai_router.cpython-310.pyc +0 -0
- __pycache__/ai_router.cpython-311.pyc +0 -0
- __pycache__/ai_router.cpython-313.pyc +0 -0
- __pycache__/config.cpython-310.pyc +0 -0
- __pycache__/config.cpython-311.pyc +0 -0
- __pycache__/config.cpython-313.pyc +0 -0
- __pycache__/main.cpython-310.pyc +0 -0
- __pycache__/main.cpython-311.pyc +0 -0
- __pycache__/main.cpython-313.pyc +0 -0
- ai_router.py +37 -0
- backend_for_huggingface.zip +0 -0
- config.py +15 -0
- genai_vqa_full_project.tar.gz +0 -0
- logs/.ipynb_checkpoints/main-checkpoint.py +78 -0
- logs/.ipynb_checkpoints/requests-checkpoint.log +55 -0
- logs/main.py +78 -0
- logs/requests.log +199 -0
- main.py +116 -0
- models/.ipynb_checkpoints/blip_model-checkpoint.py +28 -0
- models/.ipynb_checkpoints/gemini_vision-checkpoint.py +74 -0
- models/.ipynb_checkpoints/reasoning_model-checkpoint.py +39 -0
- models/__pycache__/blip_model.cpython-310.pyc +0 -0
- models/__pycache__/blip_model.cpython-311.pyc +0 -0
- models/__pycache__/blip_model.cpython-313.pyc +0 -0
- models/__pycache__/external_model.cpython-310.pyc +0 -0
- models/__pycache__/gemini_vision.cpython-310.pyc +0 -0
- models/__pycache__/gemini_vision.cpython-311.pyc +0 -0
- models/__pycache__/gemini_vision.cpython-313.pyc +0 -0
- models/__pycache__/reasoning_model.cpython-310.pyc +0 -0
- models/__pycache__/reasoning_model.cpython-311.pyc +0 -0
- models/__pycache__/reasoning_model.cpython-313.pyc +0 -0
- models/blip_model.py +30 -0
- models/gemini_vision.py +68 -0
- models/reasoning_model.py +39 -0
- requirements.txt +8 -0
- static/app.js +429 -0
- static/css/.ipynb_checkpoints/style-checkpoint.css +14 -0
- static/css/style.css +14 -0
- static/js/.ipynb_checkpoints/app-checkpoint.js +91 -0
- static/js/app.js +91 -0
- static/style.css +611 -0
- templates/.ipynb_checkpoints/index-checkpoint.html +122 -0
- templates/index.html +266 -0
- test.py +0 -0
.ipynb_checkpoints/ai_router-checkpoint.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.blip_model import blip_answer
|
| 2 |
+
from models.reasoning_model import reasoning_answer
|
| 3 |
+
from models.gemini_vision import gemini_vision_answer
|
| 4 |
+
|
| 5 |
+
def route_model(model_choice, image, question):
|
| 6 |
+
|
| 7 |
+
if model_choice == "local":
|
| 8 |
+
return reasoning_answer(image, question)
|
| 9 |
+
|
| 10 |
+
elif model_choice == "gemini":
|
| 11 |
+
return gemini_vision_answer(image, question)
|
| 12 |
+
|
| 13 |
+
else:
|
| 14 |
+
return "Unknown", "Invalid", "Invalid"
|
.ipynb_checkpoints/config-checkpoint.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 5 |
+
|
| 6 |
+
# ===== ADMIN SETTINGS =====
|
| 7 |
+
ADMIN_MODE = True
|
| 8 |
+
ENABLE_EXTERNAL_AI = True # Toggle Gemini here
|
| 9 |
+
|
| 10 |
+
# ===== Gemini API Key =====
|
| 11 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyB1fMfnnp2etuOVWiLrecdMp3_0GbEWLaU")
|
| 12 |
+
|
| 13 |
+
# ===== Security Limits =====
|
| 14 |
+
MAX_IMAGE_SIZE_MB = 5
|
| 15 |
+
RATE_LIMIT_PER_MINUTE = 30
|
.ipynb_checkpoints/main-checkpoint.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
from fastapi import FastAPI, File, UploadFile, Form, Request
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
+
from PIL import Image
|
| 8 |
+
|
| 9 |
+
from ai_router import route_model
|
| 10 |
+
from utils.logger import log_request
|
| 11 |
+
from utils.security import check_rate_limit, validate_image_size
|
| 12 |
+
from config import DEVICE, ENABLE_EXTERNAL_AI
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# ================= CREATE APP FIRST =================
|
| 16 |
+
app = FastAPI(title="GenAI VQA System")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# ================= CORS =================
|
| 20 |
+
app.add_middleware(
|
| 21 |
+
CORSMiddleware,
|
| 22 |
+
allow_origins=["*"],
|
| 23 |
+
allow_credentials=True,
|
| 24 |
+
allow_methods=["*"],
|
| 25 |
+
allow_headers=["*"],
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# ================= STATIC + TEMPLATE =================
|
| 30 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 31 |
+
templates = Jinja2Templates(directory="templates")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# ================= HOME =================
|
| 35 |
+
@app.get("/")
|
| 36 |
+
async def home(request: Request):
|
| 37 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ================= API =================
|
| 41 |
+
@app.post("/ask")
|
| 42 |
+
async def ask_question(
|
| 43 |
+
file: UploadFile = File(...),
|
| 44 |
+
question: str = Form(...),
|
| 45 |
+
model_choice: str = Form("reasoning"),
|
| 46 |
+
user: str = Form("guest")
|
| 47 |
+
):
|
| 48 |
+
|
| 49 |
+
check_rate_limit(user)
|
| 50 |
+
|
| 51 |
+
image_bytes = await file.read()
|
| 52 |
+
validate_image_size(len(image_bytes))
|
| 53 |
+
|
| 54 |
+
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 55 |
+
|
| 56 |
+
caption, answer, explanation = route_model(
|
| 57 |
+
model_choice,
|
| 58 |
+
image,
|
| 59 |
+
question
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
log_request(user, model_choice, question)
|
| 65 |
+
|
| 66 |
+
return JSONResponse({
|
| 67 |
+
"device": DEVICE,
|
| 68 |
+
"model_used": model_choice,
|
| 69 |
+
"caption": caption,
|
| 70 |
+
"answer": answer,
|
| 71 |
+
"explanation": explanation,
|
| 72 |
+
"external_enabled": ENABLE_EXTERNAL_AI
|
| 73 |
+
})
|
.ipynb_checkpoints/test-checkpoint.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from google import genai
|
| 2 |
+
|
| 3 |
+
client = genai.Client(api_key="AIzaSyB1fMfnnp2etuOVWiLrecdMp3_0GbEWLaU")
|
| 4 |
+
|
| 5 |
+
models = client.models.list()
|
| 6 |
+
|
| 7 |
+
for m in models:
|
| 8 |
+
print(m.name)
|
Dockerfile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
RUN pip install "numpy<2"
|
| 8 |
+
|
| 9 |
+
# Download model locally during build to avoid downtime on first request
|
| 10 |
+
RUN python -c "from transformers import BlipProcessor; BlipProcessor.from_pretrained('Salesforce/blip-image-captioning-base')" || true
|
| 11 |
+
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# Expose port that HuggingFace Spaces/Render uses
|
| 15 |
+
EXPOSE 7860
|
| 16 |
+
|
| 17 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: GenAI Vision System
|
| 3 |
+
emoji: 🧠
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# GenAI Vision System
|
| 11 |
+
A multi-language Vision Question Answering (VQA) system powered by FastAPI, PyTorch (BLIP), and Google Gemini.
|
__pycache__/ai_router.cpython-310.pyc
ADDED
|
Binary file (574 Bytes). View file
|
|
|
__pycache__/ai_router.cpython-311.pyc
ADDED
|
Binary file (728 Bytes). View file
|
|
|
__pycache__/ai_router.cpython-313.pyc
ADDED
|
Binary file (1.33 kB). View file
|
|
|
__pycache__/config.cpython-310.pyc
ADDED
|
Binary file (447 Bytes). View file
|
|
|
__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (573 Bytes). View file
|
|
|
__pycache__/config.cpython-313.pyc
ADDED
|
Binary file (563 Bytes). View file
|
|
|
__pycache__/main.cpython-310.pyc
ADDED
|
Binary file (1.85 kB). View file
|
|
|
__pycache__/main.cpython-311.pyc
ADDED
|
Binary file (3.09 kB). View file
|
|
|
__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (4.43 kB). View file
|
|
|
ai_router.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.blip_model import blip_answer
|
| 2 |
+
from models.reasoning_model import reasoning_answer
|
| 3 |
+
from models.gemini_vision import gemini_vision_answer
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
from deep_translator import GoogleTranslator
|
| 7 |
+
except:
|
| 8 |
+
GoogleTranslator = None
|
| 9 |
+
|
| 10 |
+
def route_model(model_choice, image, question, lang="en"):
|
| 11 |
+
|
| 12 |
+
# Translate inbound question for all models
|
| 13 |
+
if lang != "en" and GoogleTranslator:
|
| 14 |
+
try:
|
| 15 |
+
question = GoogleTranslator(source='auto', target='en').translate(question)
|
| 16 |
+
except:
|
| 17 |
+
pass
|
| 18 |
+
|
| 19 |
+
# Model Execution
|
| 20 |
+
if model_choice == "local":
|
| 21 |
+
cap, ans, exp = reasoning_answer(image, question)
|
| 22 |
+
elif model_choice == "gemini":
|
| 23 |
+
cap, ans, exp = gemini_vision_answer(image, question, lang)
|
| 24 |
+
else:
|
| 25 |
+
cap, ans, exp = "Unknown", "Invalid", "Invalid"
|
| 26 |
+
|
| 27 |
+
# Translate outbound answer for local model (convert back to user's native language)
|
| 28 |
+
if lang != "en" and GoogleTranslator and model_choice == "local":
|
| 29 |
+
try:
|
| 30 |
+
out_trans = GoogleTranslator(source='en', target=lang)
|
| 31 |
+
cap = out_trans.translate(cap)
|
| 32 |
+
ans = out_trans.translate(ans)
|
| 33 |
+
exp = out_trans.translate(exp)
|
| 34 |
+
except:
|
| 35 |
+
pass
|
| 36 |
+
|
| 37 |
+
return cap, ans, exp
|
backend_for_huggingface.zip
ADDED
|
Binary file (39 kB). View file
|
|
|
config.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 5 |
+
|
| 6 |
+
# ===== ADMIN SETTINGS =====
|
| 7 |
+
ADMIN_MODE = True
|
| 8 |
+
ENABLE_EXTERNAL_AI = True # Toggle Gemini here
|
| 9 |
+
|
| 10 |
+
# ===== Gemini API Key =====
|
| 11 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyB1fMfnnp2etuOVWiLrecdMp3_0GbEWLaU")
|
| 12 |
+
|
| 13 |
+
# ===== Security Limits =====
|
| 14 |
+
MAX_IMAGE_SIZE_MB = 5
|
| 15 |
+
RATE_LIMIT_PER_MINUTE = 30
|
genai_vqa_full_project.tar.gz
ADDED
|
Binary file (45 Bytes). View file
|
|
|
logs/.ipynb_checkpoints/main-checkpoint.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
from fastapi import FastAPI, File, UploadFile, Form, Request
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
+
from PIL import Image
|
| 8 |
+
|
| 9 |
+
from ai_router import route_model
|
| 10 |
+
from models.external_model import external_answer
|
| 11 |
+
from utils.logger import log_request
|
| 12 |
+
from utils.security import check_rate_limit, validate_image_size
|
| 13 |
+
from config import DEVICE, ENABLE_EXTERNAL_AI
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# ================= CREATE APP FIRST =================
|
| 17 |
+
app = FastAPI(title="GenAI VQA System")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ================= CORS =================
|
| 21 |
+
app.add_middleware(
|
| 22 |
+
CORSMiddleware,
|
| 23 |
+
allow_origins=["*"],
|
| 24 |
+
allow_credentials=True,
|
| 25 |
+
allow_methods=["*"],
|
| 26 |
+
allow_headers=["*"],
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ================= STATIC + TEMPLATE =================
|
| 31 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 32 |
+
templates = Jinja2Templates(directory="templates")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# ================= HOME =================
|
| 36 |
+
@app.get("/")
|
| 37 |
+
async def home(request: Request):
|
| 38 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# ================= API =================
|
| 42 |
+
@app.post("/ask")
|
| 43 |
+
async def ask_question(
|
| 44 |
+
file: UploadFile = File(...),
|
| 45 |
+
question: str = Form(...),
|
| 46 |
+
model_choice: str = Form("reasoning"),
|
| 47 |
+
user: str = Form("guest")
|
| 48 |
+
):
|
| 49 |
+
|
| 50 |
+
check_rate_limit(user)
|
| 51 |
+
|
| 52 |
+
image_bytes = await file.read()
|
| 53 |
+
validate_image_size(len(image_bytes))
|
| 54 |
+
|
| 55 |
+
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 56 |
+
|
| 57 |
+
caption, answer, explanation = route_model(
|
| 58 |
+
model_choice,
|
| 59 |
+
image,
|
| 60 |
+
question
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# Gemini override
|
| 64 |
+
if model_choice == "external" and ENABLE_EXTERNAL_AI:
|
| 65 |
+
ext_response = external_answer(caption, question)
|
| 66 |
+
explanation = ext_response
|
| 67 |
+
answer = "Generated by Gemini"
|
| 68 |
+
|
| 69 |
+
log_request(user, model_choice, question)
|
| 70 |
+
|
| 71 |
+
return JSONResponse({
|
| 72 |
+
"device": DEVICE,
|
| 73 |
+
"model_used": model_choice,
|
| 74 |
+
"caption": caption,
|
| 75 |
+
"answer": answer,
|
| 76 |
+
"explanation": explanation,
|
| 77 |
+
"external_enabled": ENABLE_EXTERNAL_AI
|
| 78 |
+
})
|
logs/.ipynb_checkpoints/requests-checkpoint.log
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2026-03-09 22:18:07,836 - INFO - User: demo_user | Model: reasoning | Q: what s there in the image
|
| 2 |
+
2026-03-09 22:19:35,153 - INFO - User: demo_user | Model: reasoning | Q: what s there in the image
|
| 3 |
+
2026-03-09 22:19:47,705 - INFO - User: demo_user | Model: reasoning | Q: what is the color of his shirt
|
| 4 |
+
2026-03-09 22:20:05,270 - INFO - User: demo_user | Model: reasoning | Q: what is the color of the t shirt the man is wearing
|
| 5 |
+
2026-03-09 22:20:19,709 - INFO - User: demo_user | Model: reasoning | Q: what is the man doing ?
|
| 6 |
+
2026-03-09 22:20:36,089 - INFO - User: demo_user | Model: reasoning | Q: what is the man doing , what is the emotion in his face
|
| 7 |
+
2026-03-09 22:31:05,406 - INFO - User: demo_user | Model: reasoning | Q: who is he
|
| 8 |
+
2026-03-09 22:31:15,743 - INFO - User: demo_user | Model: blip | Q: who is he
|
| 9 |
+
2026-03-09 22:34:34,578 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 10 |
+
2026-03-09 22:34:34,895 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 11 |
+
2026-03-09 22:34:48,620 - INFO - User: vignesh | Model: reasoning | Q: whats this
|
| 12 |
+
2026-03-09 22:34:53,385 - INFO - User: vignesh | Model: reasoning | Q: whats this
|
| 13 |
+
2026-03-09 22:34:57,133 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 14 |
+
2026-03-09 22:36:11,525 - INFO - User: vignesh | Model: reasoning | Q: whats this soup color
|
| 15 |
+
2026-03-09 22:36:53,732 - INFO - User: hi | Model: blip | Q: what colors are here
|
| 16 |
+
2026-03-09 22:37:16,020 - INFO - User: hi | Model: reasoning | Q: what colors are here
|
| 17 |
+
2026-03-09 22:37:28,450 - INFO - User: hi | Model: reasoning | Q: what colors are here , number?
|
| 18 |
+
2026-03-09 23:20:45,973 - INFO - User: vignesh | Model: blip | Q: who is he
|
| 19 |
+
2026-03-09 23:20:55,493 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 20 |
+
2026-03-09 23:21:06,527 - INFO - User: vignesh | Model: external | Q: who is he
|
| 21 |
+
2026-03-09 23:22:43,844 - INFO - User: Hi | Model: blip | Q: Who is this
|
| 22 |
+
2026-03-09 23:23:00,446 - INFO - User: Bye | Model: blip | Q: Who is this
|
| 23 |
+
2026-03-09 23:23:07,642 - INFO - User: Bye | Model: reasoning | Q: Who is this
|
| 24 |
+
2026-03-09 23:23:20,167 - INFO - User: Bye | Model: external | Q: Who is this
|
| 25 |
+
2026-03-09 23:23:42,134 - INFO - User: Bye | Model: blip | Q: Is this whatsapp or pic
|
| 26 |
+
2026-03-09 23:24:01,340 - INFO - User: Bye | Model: blip | Q: Is this whatsapp or pic , gimme an answer with reason
|
| 27 |
+
2026-03-09 23:24:06,221 - INFO - User: Bye | Model: reasoning | Q: Is this whatsapp or pic , gimme an answer with reason
|
| 28 |
+
2026-03-09 23:24:39,297 - INFO - User: Bye | Model: reasoning | Q: Whats this
|
| 29 |
+
2026-03-09 23:24:52,539 - INFO - User: Bye | Model: reasoning | Q: Certificate for?
|
| 30 |
+
2026-03-09 23:25:03,802 - INFO - User: Bye | Model: reasoning | Q: Name of the guy
|
| 31 |
+
2026-03-09 23:26:35,437 - INFO - User: Bye | Model: reasoning | Q: Whats this
|
| 32 |
+
2026-03-09 23:27:48,389 - INFO - User: vignesh | Model: reasoning | Q: Whats this about
|
| 33 |
+
2026-03-09 23:27:50,344 - INFO - User: vignesh | Model: reasoning | Q: Whats this about
|
| 34 |
+
2026-03-09 23:28:06,294 - INFO - User: vignesh | Model: reasoning | Q: Whats this about
|
| 35 |
+
2026-03-09 23:28:26,274 - INFO - User: vignesh | Model: reasoning | Q: What are that red color
|
| 36 |
+
2026-03-09 23:32:56,112 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 37 |
+
2026-03-09 23:49:05,342 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 38 |
+
2026-03-09 23:49:10,353 - INFO - User: vignesh | Model: reasoning | Q: whats this
|
| 39 |
+
2026-03-09 23:49:15,431 - INFO - AFC is enabled with max remote calls: 10.
|
| 40 |
+
2026-03-09 23:49:15,670 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 41 |
+
2026-03-09 23:49:15,671 - INFO - AFC is enabled with max remote calls: 10.
|
| 42 |
+
2026-03-09 23:49:15,832 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 43 |
+
2026-03-09 23:49:15,834 - INFO - User: vignesh | Model: external | Q: whats this
|
| 44 |
+
2026-03-09 23:53:01,815 - INFO - User: vignesh | Model: blip | Q: who is he
|
| 45 |
+
2026-03-09 23:53:06,912 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 46 |
+
2026-03-09 23:53:20,055 - INFO - User: vignesh | Model: reasoning | Q: what is he doing?
|
| 47 |
+
2026-03-09 23:53:25,377 - INFO - AFC is enabled with max remote calls: 10.
|
| 48 |
+
2026-03-09 23:53:25,709 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 49 |
+
2026-03-09 23:53:25,709 - INFO - AFC is enabled with max remote calls: 10.
|
| 50 |
+
2026-03-09 23:53:26,013 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 51 |
+
2026-03-09 23:53:26,014 - INFO - User: vignesh | Model: external | Q: what is he doing?
|
| 52 |
+
2026-03-09 23:53:43,730 - INFO - User: vignesh | Model: reasoning | Q: what is he doing?
|
| 53 |
+
2026-03-10 00:27:54,001 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 54 |
+
2026-03-10 00:27:59,362 - INFO - User: vignesh | Model: blip | Q: who is he
|
| 55 |
+
2026-03-10 00:28:03,253 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
logs/main.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
from fastapi import FastAPI, File, UploadFile, Form, Request
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
+
from PIL import Image
|
| 8 |
+
|
| 9 |
+
from ai_router import route_model
|
| 10 |
+
from models.external_model import external_answer
|
| 11 |
+
from utils.logger import log_request
|
| 12 |
+
from utils.security import check_rate_limit, validate_image_size
|
| 13 |
+
from config import DEVICE, ENABLE_EXTERNAL_AI
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# ================= CREATE APP FIRST =================
|
| 17 |
+
app = FastAPI(title="GenAI VQA System")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ================= CORS =================
|
| 21 |
+
app.add_middleware(
|
| 22 |
+
CORSMiddleware,
|
| 23 |
+
allow_origins=["*"],
|
| 24 |
+
allow_credentials=True,
|
| 25 |
+
allow_methods=["*"],
|
| 26 |
+
allow_headers=["*"],
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ================= STATIC + TEMPLATE =================
|
| 31 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 32 |
+
templates = Jinja2Templates(directory="templates")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# ================= HOME =================
|
| 36 |
+
@app.get("/")
|
| 37 |
+
async def home(request: Request):
|
| 38 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# ================= API =================
|
| 42 |
+
@app.post("/ask")
|
| 43 |
+
async def ask_question(
|
| 44 |
+
file: UploadFile = File(...),
|
| 45 |
+
question: str = Form(...),
|
| 46 |
+
model_choice: str = Form("reasoning"),
|
| 47 |
+
user: str = Form("guest")
|
| 48 |
+
):
|
| 49 |
+
|
| 50 |
+
check_rate_limit(user)
|
| 51 |
+
|
| 52 |
+
image_bytes = await file.read()
|
| 53 |
+
validate_image_size(len(image_bytes))
|
| 54 |
+
|
| 55 |
+
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 56 |
+
|
| 57 |
+
caption, answer, explanation = route_model(
|
| 58 |
+
model_choice,
|
| 59 |
+
image,
|
| 60 |
+
question
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# Gemini override
|
| 64 |
+
if model_choice == "external" and ENABLE_EXTERNAL_AI:
|
| 65 |
+
ext_response = external_answer(caption, question)
|
| 66 |
+
explanation = ext_response
|
| 67 |
+
answer = "Generated by Gemini"
|
| 68 |
+
|
| 69 |
+
log_request(user, model_choice, question)
|
| 70 |
+
|
| 71 |
+
return JSONResponse({
|
| 72 |
+
"device": DEVICE,
|
| 73 |
+
"model_used": model_choice,
|
| 74 |
+
"caption": caption,
|
| 75 |
+
"answer": answer,
|
| 76 |
+
"explanation": explanation,
|
| 77 |
+
"external_enabled": ENABLE_EXTERNAL_AI
|
| 78 |
+
})
|
logs/requests.log
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2026-03-09 22:18:07,836 - INFO - User: demo_user | Model: reasoning | Q: what s there in the image
|
| 2 |
+
2026-03-09 22:19:35,153 - INFO - User: demo_user | Model: reasoning | Q: what s there in the image
|
| 3 |
+
2026-03-09 22:19:47,705 - INFO - User: demo_user | Model: reasoning | Q: what is the color of his shirt
|
| 4 |
+
2026-03-09 22:20:05,270 - INFO - User: demo_user | Model: reasoning | Q: what is the color of the t shirt the man is wearing
|
| 5 |
+
2026-03-09 22:20:19,709 - INFO - User: demo_user | Model: reasoning | Q: what is the man doing ?
|
| 6 |
+
2026-03-09 22:20:36,089 - INFO - User: demo_user | Model: reasoning | Q: what is the man doing , what is the emotion in his face
|
| 7 |
+
2026-03-09 22:31:05,406 - INFO - User: demo_user | Model: reasoning | Q: who is he
|
| 8 |
+
2026-03-09 22:31:15,743 - INFO - User: demo_user | Model: blip | Q: who is he
|
| 9 |
+
2026-03-09 22:34:34,578 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 10 |
+
2026-03-09 22:34:34,895 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 11 |
+
2026-03-09 22:34:48,620 - INFO - User: vignesh | Model: reasoning | Q: whats this
|
| 12 |
+
2026-03-09 22:34:53,385 - INFO - User: vignesh | Model: reasoning | Q: whats this
|
| 13 |
+
2026-03-09 22:34:57,133 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 14 |
+
2026-03-09 22:36:11,525 - INFO - User: vignesh | Model: reasoning | Q: whats this soup color
|
| 15 |
+
2026-03-09 22:36:53,732 - INFO - User: hi | Model: blip | Q: what colors are here
|
| 16 |
+
2026-03-09 22:37:16,020 - INFO - User: hi | Model: reasoning | Q: what colors are here
|
| 17 |
+
2026-03-09 22:37:28,450 - INFO - User: hi | Model: reasoning | Q: what colors are here , number?
|
| 18 |
+
2026-03-09 23:20:45,973 - INFO - User: vignesh | Model: blip | Q: who is he
|
| 19 |
+
2026-03-09 23:20:55,493 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 20 |
+
2026-03-09 23:21:06,527 - INFO - User: vignesh | Model: external | Q: who is he
|
| 21 |
+
2026-03-09 23:22:43,844 - INFO - User: Hi | Model: blip | Q: Who is this
|
| 22 |
+
2026-03-09 23:23:00,446 - INFO - User: Bye | Model: blip | Q: Who is this
|
| 23 |
+
2026-03-09 23:23:07,642 - INFO - User: Bye | Model: reasoning | Q: Who is this
|
| 24 |
+
2026-03-09 23:23:20,167 - INFO - User: Bye | Model: external | Q: Who is this
|
| 25 |
+
2026-03-09 23:23:42,134 - INFO - User: Bye | Model: blip | Q: Is this whatsapp or pic
|
| 26 |
+
2026-03-09 23:24:01,340 - INFO - User: Bye | Model: blip | Q: Is this whatsapp or pic , gimme an answer with reason
|
| 27 |
+
2026-03-09 23:24:06,221 - INFO - User: Bye | Model: reasoning | Q: Is this whatsapp or pic , gimme an answer with reason
|
| 28 |
+
2026-03-09 23:24:39,297 - INFO - User: Bye | Model: reasoning | Q: Whats this
|
| 29 |
+
2026-03-09 23:24:52,539 - INFO - User: Bye | Model: reasoning | Q: Certificate for?
|
| 30 |
+
2026-03-09 23:25:03,802 - INFO - User: Bye | Model: reasoning | Q: Name of the guy
|
| 31 |
+
2026-03-09 23:26:35,437 - INFO - User: Bye | Model: reasoning | Q: Whats this
|
| 32 |
+
2026-03-09 23:27:48,389 - INFO - User: vignesh | Model: reasoning | Q: Whats this about
|
| 33 |
+
2026-03-09 23:27:50,344 - INFO - User: vignesh | Model: reasoning | Q: Whats this about
|
| 34 |
+
2026-03-09 23:28:06,294 - INFO - User: vignesh | Model: reasoning | Q: Whats this about
|
| 35 |
+
2026-03-09 23:28:26,274 - INFO - User: vignesh | Model: reasoning | Q: What are that red color
|
| 36 |
+
2026-03-09 23:32:56,112 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 37 |
+
2026-03-09 23:49:05,342 - INFO - User: vignesh | Model: blip | Q: whats this
|
| 38 |
+
2026-03-09 23:49:10,353 - INFO - User: vignesh | Model: reasoning | Q: whats this
|
| 39 |
+
2026-03-09 23:49:15,431 - INFO - AFC is enabled with max remote calls: 10.
|
| 40 |
+
2026-03-09 23:49:15,670 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 41 |
+
2026-03-09 23:49:15,671 - INFO - AFC is enabled with max remote calls: 10.
|
| 42 |
+
2026-03-09 23:49:15,832 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 43 |
+
2026-03-09 23:49:15,834 - INFO - User: vignesh | Model: external | Q: whats this
|
| 44 |
+
2026-03-09 23:53:01,815 - INFO - User: vignesh | Model: blip | Q: who is he
|
| 45 |
+
2026-03-09 23:53:06,912 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 46 |
+
2026-03-09 23:53:20,055 - INFO - User: vignesh | Model: reasoning | Q: what is he doing?
|
| 47 |
+
2026-03-09 23:53:25,377 - INFO - AFC is enabled with max remote calls: 10.
|
| 48 |
+
2026-03-09 23:53:25,709 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 49 |
+
2026-03-09 23:53:25,709 - INFO - AFC is enabled with max remote calls: 10.
|
| 50 |
+
2026-03-09 23:53:26,013 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 51 |
+
2026-03-09 23:53:26,014 - INFO - User: vignesh | Model: external | Q: what is he doing?
|
| 52 |
+
2026-03-09 23:53:43,730 - INFO - User: vignesh | Model: reasoning | Q: what is he doing?
|
| 53 |
+
2026-03-10 00:27:54,001 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 54 |
+
2026-03-10 00:27:59,362 - INFO - User: vignesh | Model: blip | Q: who is he
|
| 55 |
+
2026-03-10 00:28:03,253 - INFO - User: vignesh | Model: reasoning | Q: who is he
|
| 56 |
+
2026-03-10 00:39:15,248 - INFO - User: vignesh | Model: blip | Q: whats in the soup
|
| 57 |
+
2026-03-10 00:39:19,957 - INFO - User: vignesh | Model: reasoning | Q: whats in the soup
|
| 58 |
+
2026-03-10 00:39:54,031 - INFO - User: vignesh | Model: reasoning | Q: whats in the soup
|
| 59 |
+
2026-03-10 00:39:58,700 - INFO - User: vignesh | Model: blip | Q: whats in the soup
|
| 60 |
+
2026-03-10 00:40:08,785 - INFO - User: vignesh | Model: reasoning | Q: whats in the soup
|
| 61 |
+
2026-03-10 00:57:21,878 - INFO - User: vignesh | Model: blip | Q: what is he doing?
|
| 62 |
+
2026-03-10 00:57:25,967 - INFO - User: vignesh | Model: reasoning | Q: what is he doing?
|
| 63 |
+
2026-03-10 00:57:29,509 - INFO - User: vignesh | Model: external | Q: what is he doing?
|
| 64 |
+
2026-03-10 01:09:14,799 - INFO - User: vignesh | Model: blip | Q: whats in the soup
|
| 65 |
+
2026-03-10 01:09:19,548 - INFO - User: vignesh | Model: reasoning | Q: whats in the soup
|
| 66 |
+
2026-03-10 01:09:23,999 - INFO - AFC is enabled with max remote calls: 10.
|
| 67 |
+
2026-03-10 01:09:26,541 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 68 |
+
2026-03-10 01:09:26,543 - INFO - User: vignesh | Model: external | Q: whats in the soup
|
| 69 |
+
2026-03-10 01:21:18,283 - INFO - User: vignesh | Model: blip | Q: whats in the soup
|
| 70 |
+
2026-03-10 01:24:00,513 - INFO - User: vignesh | Model: local | Q: whats in the soup
|
| 71 |
+
2026-03-10 01:24:06,605 - INFO - AFC is enabled with max remote calls: 10.
|
| 72 |
+
2026-03-10 01:24:12,471 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 73 |
+
2026-03-10 01:24:12,473 - INFO - User: vignesh | Model: gemini | Q: whats in the soup
|
| 74 |
+
2026-03-10 01:24:36,257 - INFO - User: vignesh | Model: local | Q: whats in the soup
|
| 75 |
+
2026-03-10 01:24:50,707 - INFO - User: vignesh | Model: local | Q: color of soup
|
| 76 |
+
2026-03-10 01:25:06,181 - INFO - User: vignesh | Model: local | Q: ingredients used?
|
| 77 |
+
2026-03-10 01:25:18,463 - INFO - User: vignesh | Model: local | Q: ingredients used for soup?
|
| 78 |
+
2026-03-10 01:25:33,965 - INFO - User: vignesh | Model: local | Q: ingredients used for soup? , list all , we need 4
|
| 79 |
+
2026-03-10 01:25:45,319 - INFO - User: vignesh | Model: local | Q: ingredients used for soup? , list all , we need 10 ingredients
|
| 80 |
+
2026-03-10 01:25:52,548 - INFO - User: vignesh | Model: local | Q: ingredients used for soup?
|
| 81 |
+
2026-03-10 01:26:34,844 - INFO - AFC is enabled with max remote calls: 10.
|
| 82 |
+
2026-03-10 01:26:38,232 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 83 |
+
2026-03-10 01:26:38,234 - INFO - User: vignesh | Model: gemini | Q: ingredients used for soup?
|
| 84 |
+
2026-03-10 01:48:14,487 - INFO - AFC is enabled with max remote calls: 10.
|
| 85 |
+
2026-03-10 01:48:19,645 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 86 |
+
2026-03-10 01:48:19,647 - INFO - User: vignesh | Model: gemini | Q: ingredients used for soup?
|
| 87 |
+
2026-03-10 02:48:53,073 - INFO - User: guest | Model: local | Q: whats happening in the image
|
| 88 |
+
2026-03-10 02:49:02,279 - INFO - AFC is enabled with max remote calls: 10.
|
| 89 |
+
2026-03-10 02:49:10,528 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 90 |
+
2026-03-10 02:49:10,532 - INFO - User: guest | Model: gemini | Q: whats happening in the image
|
| 91 |
+
2026-03-10 02:54:12,379 - INFO - User: vignesh | Model: local | Q: Describe the contents of this image in detail.
|
| 92 |
+
2026-03-10 02:54:27,489 - INFO - User: vignesh | Model: local | Q: color of the text
|
| 93 |
+
2026-03-10 03:01:20,760 - INFO - User: vignesh | Model: local | Q: whats happening in the image
|
| 94 |
+
2026-03-10 03:01:36,161 - INFO - AFC is enabled with max remote calls: 10.
|
| 95 |
+
2026-03-10 03:01:45,734 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 96 |
+
2026-03-10 03:01:45,746 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 97 |
+
2026-03-10 03:02:04,502 - INFO - User: vignesh | Model: local | Q: whats happening in the image
|
| 98 |
+
2026-03-10 03:05:21,987 - INFO - AFC is enabled with max remote calls: 10.
|
| 99 |
+
2026-03-10 03:05:32,205 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 100 |
+
2026-03-10 03:05:32,206 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 101 |
+
2026-03-10 03:07:29,952 - INFO - AFC is enabled with max remote calls: 10.
|
| 102 |
+
2026-03-10 03:07:35,865 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 103 |
+
2026-03-10 03:07:35,869 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 104 |
+
2026-03-10 03:07:56,178 - INFO - User: vignesh | Model: local | Q: whats happening in the image
|
| 105 |
+
2026-03-10 03:08:21,101 - INFO - AFC is enabled with max remote calls: 10.
|
| 106 |
+
2026-03-10 03:08:26,796 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 107 |
+
2026-03-10 03:08:26,798 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 108 |
+
2026-03-10 03:09:33,333 - INFO - AFC is enabled with max remote calls: 10.
|
| 109 |
+
2026-03-10 03:09:39,385 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 110 |
+
2026-03-10 03:09:39,386 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 111 |
+
2026-03-10 03:10:47,029 - INFO - AFC is enabled with max remote calls: 10.
|
| 112 |
+
2026-03-10 03:10:52,063 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 113 |
+
2026-03-10 03:10:52,065 - INFO - User: vignesh | Model: gemini | Q: what is to the rught and velocity of car?
|
| 114 |
+
2026-03-10 03:11:20,115 - INFO - AFC is enabled with max remote calls: 10.
|
| 115 |
+
2026-03-10 03:11:25,253 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 116 |
+
2026-03-10 03:11:25,254 - INFO - User: vignesh | Model: gemini | Q: how many cars present
|
| 117 |
+
2026-03-10 03:12:09,840 - INFO - AFC is enabled with max remote calls: 10.
|
| 118 |
+
2026-03-10 03:12:14,133 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 119 |
+
2026-03-10 03:12:14,134 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 120 |
+
2026-03-10 03:12:23,972 - INFO - AFC is enabled with max remote calls: 10.
|
| 121 |
+
2026-03-10 03:12:27,062 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 122 |
+
2026-03-10 03:12:27,064 - INFO - User: vignesh | Model: gemini | Q: color of specs?
|
| 123 |
+
2026-03-10 03:12:39,952 - INFO - AFC is enabled with max remote calls: 10.
|
| 124 |
+
2026-03-10 03:12:42,769 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 125 |
+
2026-03-10 03:12:42,771 - INFO - User: vignesh | Model: gemini | Q: is he wearing soecs
|
| 126 |
+
2026-03-10 03:12:49,344 - INFO - User: vignesh | Model: local | Q: is he wearing specs
|
| 127 |
+
2026-03-10 03:14:39,111 - INFO - User: vignesh | Model: local | Q: what are the ingredients in ths soup?
|
| 128 |
+
2026-03-10 03:15:41,071 - INFO - AFC is enabled with max remote calls: 10.
|
| 129 |
+
2026-03-10 03:15:46,102 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 130 |
+
2026-03-10 03:15:46,104 - INFO - User: vignesh | Model: gemini | Q: what are the ingredients in ths soup?
|
| 131 |
+
2026-03-10 03:16:04,651 - INFO - AFC is enabled with max remote calls: 10.
|
| 132 |
+
2026-03-10 03:16:07,521 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 133 |
+
2026-03-10 03:16:07,523 - INFO - User: vignesh | Model: gemini | Q: veg or non veg soup?
|
| 134 |
+
2026-03-10 03:16:11,772 - INFO - AFC is enabled with max remote calls: 10.
|
| 135 |
+
2026-03-10 03:16:14,435 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 136 |
+
2026-03-10 03:16:14,437 - INFO - User: vignesh | Model: gemini | Q: veg or non veg soup?
|
| 137 |
+
2026-03-10 03:16:42,178 - INFO - User: vignesh | Model: local | Q: veg or non veg soup?
|
| 138 |
+
2026-03-10 03:44:11,600 - INFO - AFC is enabled with max remote calls: 10.
|
| 139 |
+
2026-03-10 03:44:17,364 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 140 |
+
2026-03-10 03:44:17,375 - INFO - User: vignesh | Model: gemini | Q: What is happening in this image? Are there any text or numbers? Describe the overall theme.
|
| 141 |
+
2026-03-10 03:50:31,062 - INFO - User: vignesh | Model: local | Q: what is he doing
|
| 142 |
+
2026-03-10 03:50:50,464 - INFO - AFC is enabled with max remote calls: 10.
|
| 143 |
+
2026-03-10 03:51:00,293 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 144 |
+
2026-03-10 03:51:00,296 - INFO - User: vignesh | Model: gemini | Q: what is he doing
|
| 145 |
+
2026-03-10 03:51:42,234 - INFO - AFC is enabled with max remote calls: 10.
|
| 146 |
+
2026-03-10 03:51:51,293 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 200 OK"
|
| 147 |
+
2026-03-10 03:51:51,300 - INFO - User: vignesh | Model: gemini | Q: What is to the right of the beach
|
| 148 |
+
2026-03-10 03:52:10,719 - INFO - User: vignesh | Model: local | Q: What is to the right of the beach
|
| 149 |
+
2026-03-10 04:08:47,209 - INFO - User: v | Model: local | Q: whats happening in the image
|
| 150 |
+
2026-03-10 04:26:01,947 - INFO - User: v | Model: local | Q: whats happening in the image
|
| 151 |
+
2026-03-10 04:26:19,702 - INFO - User: v | Model: local | Q: whats happening in the image
|
| 152 |
+
2026-03-10 04:26:24,952 - INFO - AFC is enabled with max remote calls: 10.
|
| 153 |
+
2026-03-10 04:26:26,168 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 429 Too Many Requests"
|
| 154 |
+
2026-03-10 04:26:26,169 - INFO - User: v | Model: gemini | Q: whats happening in the image
|
| 155 |
+
2026-03-10 04:29:50,705 - INFO - User: vignesh | Model: local | Q: whats in soup
|
| 156 |
+
2026-03-10 04:30:01,330 - INFO - User: vignesh | Model: local | Q: whats in soup
|
| 157 |
+
2026-03-10 04:30:16,981 - INFO - User: vignesh | Model: local | Q: whats in soup
|
| 158 |
+
2026-03-10 04:30:23,317 - INFO - AFC is enabled with max remote calls: 10.
|
| 159 |
+
2026-03-10 04:30:24,432 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 429 Too Many Requests"
|
| 160 |
+
2026-03-10 04:30:24,434 - INFO - User: vignesh | Model: gemini | Q: whats in soup
|
| 161 |
+
2026-03-10 04:35:42,648 - INFO - User: hi | Model: local | Q: whats happening in the image
|
| 162 |
+
2026-03-10 04:35:45,520 - INFO - AFC is enabled with max remote calls: 10.
|
| 163 |
+
2026-03-10 04:35:46,552 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 429 Too Many Requests"
|
| 164 |
+
2026-03-10 04:35:46,553 - INFO - User: hi | Model: gemini | Q: whats happening in the image
|
| 165 |
+
2026-03-10 04:36:50,743 - INFO - AFC is enabled with max remote calls: 10.
|
| 166 |
+
2026-03-10 04:36:51,699 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 429 Too Many Requests"
|
| 167 |
+
2026-03-10 04:36:51,700 - INFO - User: hi | Model: gemini | Q: whats happening in the image
|
| 168 |
+
2026-03-10 04:41:49,255 - INFO - AFC is enabled with max remote calls: 10.
|
| 169 |
+
2026-03-10 04:41:50,262 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 429 Too Many Requests"
|
| 170 |
+
2026-03-10 04:41:50,264 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 171 |
+
2026-03-10 04:43:29,558 - INFO - AFC is enabled with max remote calls: 10.
|
| 172 |
+
2026-03-10 04:43:30,555 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent "HTTP/1.1 429 Too Many Requests"
|
| 173 |
+
2026-03-10 04:43:30,556 - INFO - User: vignesh | Model: gemini | Q: whats in the image
|
| 174 |
+
2026-03-10 04:50:25,279 - INFO - AFC is enabled with max remote calls: 10.
|
| 175 |
+
2026-03-10 04:50:25,787 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent "HTTP/1.1 404 Not Found"
|
| 176 |
+
2026-03-10 04:50:25,788 - INFO - User: vignesh | Model: gemini | Q: whats in the image
|
| 177 |
+
2026-03-10 04:53:43,008 - INFO - AFC is enabled with max remote calls: 10.
|
| 178 |
+
2026-03-10 04:53:43,606 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-8b:generateContent "HTTP/1.1 404 Not Found"
|
| 179 |
+
2026-03-10 04:53:43,607 - INFO - User: vignesgh | Model: gemini | Q: whats happening in the image
|
| 180 |
+
2026-03-10 04:57:55,049 - INFO - AFC is enabled with max remote calls: 10.
|
| 181 |
+
2026-03-10 04:57:57,479 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite-preview:generateContent "HTTP/1.1 200 OK"
|
| 182 |
+
2026-03-10 04:57:57,481 - INFO - User: vignesh | Model: gemini | Q: whats happening in the image
|
| 183 |
+
2026-03-10 05:03:18,184 - INFO - AFC is enabled with max remote calls: 10.
|
| 184 |
+
2026-03-10 05:03:21,380 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite-preview:generateContent "HTTP/1.1 200 OK"
|
| 185 |
+
2026-03-10 05:03:21,382 - INFO - User: vignesh | Model: gemini | Q: intha padathil enna ullathu?
|
| 186 |
+
2026-03-10 05:03:44,902 - INFO - User: vignesh | Model: local | Q: intha padathil enna ullathu?
|
| 187 |
+
2026-03-10 05:04:18,721 - INFO - User: vignesh | Model: local | Q: sattaiyin niram enna?
|
| 188 |
+
2026-03-10 05:04:27,943 - INFO - AFC is enabled with max remote calls: 10.
|
| 189 |
+
2026-03-10 05:04:30,190 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite-preview:generateContent "HTTP/1.1 200 OK"
|
| 190 |
+
2026-03-10 05:04:30,192 - INFO - User: vignesh | Model: gemini | Q: sattaiyin niram enna?
|
| 191 |
+
2026-03-10 05:06:54,247 - INFO - User: vignesh | Model: local | Q: \u0b87\u0ba8\u0bcd\u0ba4\u0baa\u0bcd \u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0b8e\u0ba9\u0bcd\u0ba9 \u0b89\u0bb3\u0bcd\u0bb3\u0ba4\u0bc1
|
| 192 |
+
2026-03-10 05:07:09,965 - INFO - AFC is enabled with max remote calls: 10.
|
| 193 |
+
2026-03-10 05:07:19,377 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite-preview:generateContent "HTTP/1.1 200 OK"
|
| 194 |
+
2026-03-10 05:07:19,379 - INFO - User: vignesh | Model: gemini | Q: \u0b87\u0ba8\u0bcd\u0ba4\u0baa\u0bcd \u0baa\u0b9f\u0ba4\u0bcd\u0ba4\u0bbf\u0bb2\u0bcd \u0b8e\u0ba9\u0bcd\u0ba9 \u0b89\u0bb3\u0bcd\u0bb3\u0ba4\u0bc1
|
| 195 |
+
2026-03-10 05:11:59,642 - INFO - User: sharu | Model: local | Q: Color of shirt
|
| 196 |
+
2026-03-10 05:12:21,174 - INFO - User: sharu | Model: local | Q: Color of pant?
|
| 197 |
+
2026-03-10 05:12:39,130 - INFO - AFC is enabled with max remote calls: 10.
|
| 198 |
+
2026-03-10 05:12:53,394 - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite-preview:generateContent "HTTP/1.1 200 OK"
|
| 199 |
+
2026-03-10 05:12:53,397 - INFO - User: sharu | Model: gemini | Q: Color of pant?
|
main.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
from fastapi import FastAPI, File, UploadFile, Form, Request
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
+
from PIL import Image
|
| 8 |
+
|
| 9 |
+
from ai_router import route_model
|
| 10 |
+
from utils.logger import log_request
|
| 11 |
+
from utils.security import check_rate_limit, validate_image_size
|
| 12 |
+
from config import DEVICE, ENABLE_EXTERNAL_AI
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# ================= CREATE APP FIRST =================
|
| 16 |
+
app = FastAPI(title="GenAI VQA System")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# ================= CORS =================
|
| 20 |
+
app.add_middleware(
|
| 21 |
+
CORSMiddleware,
|
| 22 |
+
allow_origins=["*"],
|
| 23 |
+
allow_credentials=True,
|
| 24 |
+
allow_methods=["*"],
|
| 25 |
+
allow_headers=["*"],
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# ================= STATIC + TEMPLATE =================
|
| 30 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 31 |
+
templates = Jinja2Templates(directory="templates")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# ================= HOME =================
|
| 35 |
+
@app.get("/")
|
| 36 |
+
async def home(request: Request):
|
| 37 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ================= API =================
|
| 41 |
+
@app.get("/api/logs")
|
| 42 |
+
async def get_logs_endpoint(user: str):
|
| 43 |
+
import os
|
| 44 |
+
logs = []
|
| 45 |
+
if os.path.exists("logs/requests.log"):
|
| 46 |
+
with open("logs/requests.log", "r") as f:
|
| 47 |
+
for line in f:
|
| 48 |
+
if " - User: " in line:
|
| 49 |
+
try:
|
| 50 |
+
parts = line.split(" - INFO - User: ")
|
| 51 |
+
timestamp = parts[0]
|
| 52 |
+
rest = parts[1].split(" | Model: ")
|
| 53 |
+
log_user = rest[0].strip()
|
| 54 |
+
model_q = rest[1].split(" | Q: ")
|
| 55 |
+
model = model_q[0].strip()
|
| 56 |
+
question = model_q[1].strip()
|
| 57 |
+
|
| 58 |
+
# Admin sees all, User sees only their own
|
| 59 |
+
if user == "vignesh" or log_user == user:
|
| 60 |
+
logs.append({
|
| 61 |
+
"timestamp": timestamp,
|
| 62 |
+
"user": log_user,
|
| 63 |
+
"model": model,
|
| 64 |
+
"question": question
|
| 65 |
+
})
|
| 66 |
+
except Exception as e:
|
| 67 |
+
pass
|
| 68 |
+
logs.reverse()
|
| 69 |
+
return {"logs": logs}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@app.post("/ask")
|
| 73 |
+
async def ask_question(
|
| 74 |
+
file: UploadFile = File(...),
|
| 75 |
+
question: str = Form(...),
|
| 76 |
+
model_choice: str = Form("reasoning"),
|
| 77 |
+
user: str = Form("guest"),
|
| 78 |
+
lang: str = Form("en")
|
| 79 |
+
):
|
| 80 |
+
|
| 81 |
+
check_rate_limit(user)
|
| 82 |
+
|
| 83 |
+
image_bytes = await file.read()
|
| 84 |
+
validate_image_size(len(image_bytes))
|
| 85 |
+
|
| 86 |
+
try:
|
| 87 |
+
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| 88 |
+
except Exception as e:
|
| 89 |
+
return JSONResponse({
|
| 90 |
+
"device": "Error",
|
| 91 |
+
"model_used": model_choice,
|
| 92 |
+
"caption": "Image Load Error",
|
| 93 |
+
"answer": f"The supplied image was empty or an invalid format. Size received: {len(image_bytes)} bytes.",
|
| 94 |
+
"explanation": str(e),
|
| 95 |
+
"external_enabled": False
|
| 96 |
+
})
|
| 97 |
+
|
| 98 |
+
caption, answer, explanation = route_model(
|
| 99 |
+
model_choice,
|
| 100 |
+
image,
|
| 101 |
+
question,
|
| 102 |
+
lang
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
log_request(user, model_choice, question)
|
| 108 |
+
|
| 109 |
+
return JSONResponse({
|
| 110 |
+
"device": DEVICE,
|
| 111 |
+
"model_used": model_choice,
|
| 112 |
+
"caption": caption,
|
| 113 |
+
"answer": answer,
|
| 114 |
+
"explanation": explanation,
|
| 115 |
+
"external_enabled": ENABLE_EXTERNAL_AI
|
| 116 |
+
})
|
models/.ipynb_checkpoints/blip_model-checkpoint.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import BlipProcessor, BlipForQuestionAnswering
|
| 3 |
+
from config import DEVICE
|
| 4 |
+
|
| 5 |
+
processor = BlipProcessor.from_pretrained("Salesforce/blip-vqa-base")
|
| 6 |
+
model = BlipForQuestionAnswering.from_pretrained(
|
| 7 |
+
"Salesforce/blip-vqa-base"
|
| 8 |
+
).to(DEVICE)
|
| 9 |
+
|
| 10 |
+
model.eval()
|
| 11 |
+
|
| 12 |
+
def blip_answer(image, question):
|
| 13 |
+
|
| 14 |
+
inputs = processor(
|
| 15 |
+
images=image,
|
| 16 |
+
text=question,
|
| 17 |
+
return_tensors="pt"
|
| 18 |
+
).to(DEVICE)
|
| 19 |
+
|
| 20 |
+
with torch.no_grad():
|
| 21 |
+
output = model.generate(**inputs, max_new_tokens=5)
|
| 22 |
+
|
| 23 |
+
answer = processor.decode(output[0], skip_special_tokens=True)
|
| 24 |
+
|
| 25 |
+
# BLIP VQA does not generate caption
|
| 26 |
+
caption = "BLIP1 Direct VQA (no separate caption)"
|
| 27 |
+
|
| 28 |
+
return caption, answer
|
models/.ipynb_checkpoints/gemini_vision-checkpoint.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
from google import genai
|
| 3 |
+
from config import GEMINI_API_KEY
|
| 4 |
+
|
| 5 |
+
client = genai.Client(api_key=GEMINI_API_KEY)
|
| 6 |
+
|
| 7 |
+
def gemini_vision_answer(image, question):
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
# Convert PIL image to bytes
|
| 11 |
+
import io
|
| 12 |
+
buffer = io.BytesIO()
|
| 13 |
+
image.save(buffer, format="JPEG")
|
| 14 |
+
image_bytes = buffer.getvalue()
|
| 15 |
+
|
| 16 |
+
response = client.models.generate_content(
|
| 17 |
+
model="gemini-2.5-flash",
|
| 18 |
+
contents=[
|
| 19 |
+
{
|
| 20 |
+
"role": "user",
|
| 21 |
+
"parts": [
|
| 22 |
+
{
|
| 23 |
+
"inline_data": {
|
| 24 |
+
"mime_type": "image/jpeg",
|
| 25 |
+
"data": base64.b64encode(image_bytes).decode()
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"text": f"""
|
| 30 |
+
Analyze this image carefully.
|
| 31 |
+
|
| 32 |
+
Question:
|
| 33 |
+
{question}
|
| 34 |
+
|
| 35 |
+
Respond in this exact format:
|
| 36 |
+
|
| 37 |
+
Caption:
|
| 38 |
+
<short caption>
|
| 39 |
+
|
| 40 |
+
Final Answer:
|
| 41 |
+
<direct answer>
|
| 42 |
+
|
| 43 |
+
Explanation:
|
| 44 |
+
<detailed reasoning>
|
| 45 |
+
"""
|
| 46 |
+
}
|
| 47 |
+
]
|
| 48 |
+
}
|
| 49 |
+
]
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
text = response.text
|
| 53 |
+
|
| 54 |
+
# Parse response
|
| 55 |
+
caption = extract_section(text, "Caption:")
|
| 56 |
+
answer = extract_section(text, "Final Answer:")
|
| 57 |
+
explanation = extract_section(text, "Explanation:")
|
| 58 |
+
|
| 59 |
+
return caption, answer, explanation
|
| 60 |
+
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print("GEMINI ERROR:", str(e))
|
| 63 |
+
return "Gemini Error", "Error", str(e)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def extract_section(text, section_name):
|
| 67 |
+
try:
|
| 68 |
+
start = text.index(section_name) + len(section_name)
|
| 69 |
+
end = text.find("\n", start)
|
| 70 |
+
if end == -1:
|
| 71 |
+
end = len(text)
|
| 72 |
+
return text[start:end].strip()
|
| 73 |
+
except:
|
| 74 |
+
return "Not found"
|
models/.ipynb_checkpoints/reasoning_model-checkpoint.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.blip_model import blip_answer
|
| 2 |
+
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 3 |
+
import torch
|
| 4 |
+
from config import DEVICE
|
| 5 |
+
|
| 6 |
+
tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base")
|
| 7 |
+
model = AutoModelForSeq2SeqLM.from_pretrained(
|
| 8 |
+
"google/flan-t5-base"
|
| 9 |
+
).to(DEVICE)
|
| 10 |
+
|
| 11 |
+
model.eval()
|
| 12 |
+
|
| 13 |
+
def reasoning_answer(image, question):
|
| 14 |
+
|
| 15 |
+
caption, base_answer = blip_answer(image, question)
|
| 16 |
+
|
| 17 |
+
prompt = f"""
|
| 18 |
+
Scene:
|
| 19 |
+
{caption}
|
| 20 |
+
|
| 21 |
+
Question:
|
| 22 |
+
{question}
|
| 23 |
+
|
| 24 |
+
Base Answer:
|
| 25 |
+
{base_answer}
|
| 26 |
+
|
| 27 |
+
Provide:
|
| 28 |
+
Final Answer:
|
| 29 |
+
Explanation:
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
|
| 33 |
+
|
| 34 |
+
with torch.no_grad():
|
| 35 |
+
outputs = model.generate(**inputs, max_new_tokens=100)
|
| 36 |
+
|
| 37 |
+
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 38 |
+
|
| 39 |
+
return caption, base_answer, text
|
models/__pycache__/blip_model.cpython-310.pyc
ADDED
|
Binary file (901 Bytes). View file
|
|
|
models/__pycache__/blip_model.cpython-311.pyc
ADDED
|
Binary file (2.36 kB). View file
|
|
|
models/__pycache__/blip_model.cpython-313.pyc
ADDED
|
Binary file (2.06 kB). View file
|
|
|
models/__pycache__/external_model.cpython-310.pyc
ADDED
|
Binary file (828 Bytes). View file
|
|
|
models/__pycache__/gemini_vision.cpython-310.pyc
ADDED
|
Binary file (1.55 kB). View file
|
|
|
models/__pycache__/gemini_vision.cpython-311.pyc
ADDED
|
Binary file (2.08 kB). View file
|
|
|
models/__pycache__/gemini_vision.cpython-313.pyc
ADDED
|
Binary file (3.44 kB). View file
|
|
|
models/__pycache__/reasoning_model.cpython-310.pyc
ADDED
|
Binary file (1.04 kB). View file
|
|
|
models/__pycache__/reasoning_model.cpython-311.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
models/__pycache__/reasoning_model.cpython-313.pyc
ADDED
|
Binary file (1.49 kB). View file
|
|
|
models/blip_model.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import BlipProcessor, BlipForQuestionAnswering, BlipForConditionalGeneration
|
| 3 |
+
from config import DEVICE
|
| 4 |
+
|
| 5 |
+
processor_vqa = BlipProcessor.from_pretrained("Salesforce/blip-vqa-base")
|
| 6 |
+
model_vqa = BlipForQuestionAnswering.from_pretrained("Salesforce/blip-vqa-base").to(DEVICE)
|
| 7 |
+
model_vqa.eval()
|
| 8 |
+
|
| 9 |
+
processor_cap = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base")
|
| 10 |
+
model_cap = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base").to(DEVICE)
|
| 11 |
+
model_cap.eval()
|
| 12 |
+
|
| 13 |
+
def blip_answer(image, question):
|
| 14 |
+
# Fix numpy tensor casting issues in HF deployment
|
| 15 |
+
if image.mode != "RGB":
|
| 16 |
+
image = image.convert("RGB")
|
| 17 |
+
|
| 18 |
+
# 1. Generate Caption
|
| 19 |
+
cap_inputs = processor_cap(images=image, return_tensors="pt").to(DEVICE)
|
| 20 |
+
with torch.no_grad():
|
| 21 |
+
cap_output = model_cap.generate(**cap_inputs, max_new_tokens=20)
|
| 22 |
+
caption = processor_cap.decode(cap_output[0], skip_special_tokens=True)
|
| 23 |
+
|
| 24 |
+
# 2. Generate Base Answer
|
| 25 |
+
vqa_inputs = processor_vqa(images=image, text=question, return_tensors="pt").to(DEVICE)
|
| 26 |
+
with torch.no_grad():
|
| 27 |
+
vqa_output = model_vqa.generate(**vqa_inputs, max_new_tokens=10)
|
| 28 |
+
answer = processor_vqa.decode(vqa_output[0], skip_special_tokens=True)
|
| 29 |
+
|
| 30 |
+
return caption, answer
|
models/gemini_vision.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
from google import genai
|
| 3 |
+
from config import GEMINI_API_KEY
|
| 4 |
+
import re
|
| 5 |
+
|
| 6 |
+
client = genai.Client(api_key=GEMINI_API_KEY)
|
| 7 |
+
|
| 8 |
+
def gemini_vision_answer(image, question, lang="en"):
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
prompt = f"""Analyze this image carefully.
|
| 12 |
+
|
| 13 |
+
Question:
|
| 14 |
+
{question}
|
| 15 |
+
|
| 16 |
+
You MUST respond strictly in the language code: {lang}. All text in your output must be translated to '{lang}'.
|
| 17 |
+
However, keep the structural English keys intact (Caption, Final Answer, Explanation).
|
| 18 |
+
|
| 19 |
+
Respond strictly following this exact structure without any deviations or markdown blocks.
|
| 20 |
+
|
| 21 |
+
Caption: <short caption>
|
| 22 |
+
Final Answer: <direct answer>
|
| 23 |
+
Explanation: <detailed reasoning>"""
|
| 24 |
+
|
| 25 |
+
response = client.models.generate_content(
|
| 26 |
+
model="gemini-3.1-flash-lite-preview",
|
| 27 |
+
contents=[image, prompt]
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
text = response.text
|
| 31 |
+
|
| 32 |
+
# Parse response using regex for better stability since LLMs can sometimes alter spacing
|
| 33 |
+
caption_match = re.search(r'Caption:\s*(.*?)(?=Final Answer:|$)', text, re.IGNORECASE | re.DOTALL)
|
| 34 |
+
answer_match = re.search(r'Final Answer:\s*(.*?)(?=Explanation:|$)', text, re.IGNORECASE | re.DOTALL)
|
| 35 |
+
explanation_match = re.search(r'Explanation:\s*(.*)', text, re.IGNORECASE | re.DOTALL)
|
| 36 |
+
|
| 37 |
+
caption = caption_match.group(1).strip() if caption_match else "Caption not generated correctly."
|
| 38 |
+
answer = answer_match.group(1).strip() if answer_match else "Answer not generated correctly."
|
| 39 |
+
explanation = explanation_match.group(1).strip() if explanation_match else "Explanation not generated correctly."
|
| 40 |
+
|
| 41 |
+
# Filter out markdown bolds if they leaked from the LLM prompt completion
|
| 42 |
+
caption = caption.replace("**", "")
|
| 43 |
+
answer = answer.replace("**", "")
|
| 44 |
+
|
| 45 |
+
return caption, answer, explanation
|
| 46 |
+
|
| 47 |
+
except Exception as e:
|
| 48 |
+
error_msg = str(e)
|
| 49 |
+
if "429" in error_msg or "RESOURCE_EXHAUSTED" in error_msg:
|
| 50 |
+
return (
|
| 51 |
+
"Rate Limit Exceeded",
|
| 52 |
+
"You have hit the free tier quota limit for Google Gemini.",
|
| 53 |
+
"Please wait or check your Google AI Studio dashboard for quota resets."
|
| 54 |
+
)
|
| 55 |
+
print("GEMINI ERROR:", error_msg)
|
| 56 |
+
return "Gemini Error", "Error", error_msg
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def extract_section(text, section_name):
|
| 61 |
+
try:
|
| 62 |
+
start = text.index(section_name) + len(section_name)
|
| 63 |
+
end = text.find("\n", start)
|
| 64 |
+
if end == -1:
|
| 65 |
+
end = len(text)
|
| 66 |
+
return text[start:end].strip()
|
| 67 |
+
except:
|
| 68 |
+
return "Not found"
|
models/reasoning_model.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.blip_model import blip_answer
|
| 2 |
+
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 3 |
+
import torch
|
| 4 |
+
from config import DEVICE
|
| 5 |
+
|
| 6 |
+
tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-base")
|
| 7 |
+
model = AutoModelForSeq2SeqLM.from_pretrained(
|
| 8 |
+
"google/flan-t5-base"
|
| 9 |
+
).to(DEVICE)
|
| 10 |
+
|
| 11 |
+
model.eval()
|
| 12 |
+
|
| 13 |
+
def reasoning_answer(image, question):
|
| 14 |
+
|
| 15 |
+
caption, base_answer = blip_answer(image, question)
|
| 16 |
+
|
| 17 |
+
prompt = f"""
|
| 18 |
+
Scene:
|
| 19 |
+
{caption}
|
| 20 |
+
|
| 21 |
+
Question:
|
| 22 |
+
{question}
|
| 23 |
+
|
| 24 |
+
Base Answer:
|
| 25 |
+
{base_answer}
|
| 26 |
+
|
| 27 |
+
Provide:
|
| 28 |
+
Final Answer:
|
| 29 |
+
Explanation:
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
|
| 33 |
+
|
| 34 |
+
with torch.no_grad():
|
| 35 |
+
outputs = model.generate(**inputs, max_new_tokens=100)
|
| 36 |
+
|
| 37 |
+
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 38 |
+
|
| 39 |
+
return caption, base_answer, text
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.110.0
|
| 2 |
+
uvicorn==0.29.0
|
| 3 |
+
python-multipart>=0.0.18
|
| 4 |
+
pillow==10.2.0
|
| 5 |
+
torch==2.2.1
|
| 6 |
+
transformers==4.38.2
|
| 7 |
+
google-genai==0.3.0
|
| 8 |
+
deep-translator==1.11.4
|
static/app.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// State
|
| 2 |
+
let appState = {
|
| 3 |
+
username: '',
|
| 4 |
+
logs: [],
|
| 5 |
+
stats: { total: 0, local: 0, gemini: 0 },
|
| 6 |
+
geminiEnabled: true
|
| 7 |
+
};
|
| 8 |
+
|
| 9 |
+
// UI Elements
|
| 10 |
+
const els = {
|
| 11 |
+
tabs: document.querySelectorAll('.tab-content'),
|
| 12 |
+
menuBtns: document.querySelectorAll('.menu-btn'),
|
| 13 |
+
adminTabBtn: document.getElementById('admin-tab-btn'),
|
| 14 |
+
|
| 15 |
+
// Login
|
| 16 |
+
loginOverlay: document.getElementById('login-overlay'),
|
| 17 |
+
loginInput: document.getElementById('login-input'),
|
| 18 |
+
loginBtn: document.getElementById('login-btn'),
|
| 19 |
+
mainApp: document.getElementById('main-app'),
|
| 20 |
+
displayUsername: document.getElementById('display-username'),
|
| 21 |
+
logoutBtn: document.getElementById('logout-btn'),
|
| 22 |
+
|
| 23 |
+
// Image Modal
|
| 24 |
+
imageModal: document.getElementById('image-modal'),
|
| 25 |
+
modalImg: document.getElementById('modal-img'),
|
| 26 |
+
closeModal: document.getElementById('close-modal'),
|
| 27 |
+
|
| 28 |
+
// VQA Elements
|
| 29 |
+
dropZone: document.getElementById('drop-zone'),
|
| 30 |
+
imageUpload: document.getElementById('image-upload'),
|
| 31 |
+
imagePreview: document.getElementById('image-preview'),
|
| 32 |
+
clearImageBtn: document.getElementById('clear-image-btn'),
|
| 33 |
+
uploadPlaceholder: document.getElementById('upload-placeholder'),
|
| 34 |
+
modelSelector: document.getElementById('model-selector'),
|
| 35 |
+
langSelector: document.getElementById('lang-selector'),
|
| 36 |
+
geminiOption: document.getElementById('gemini-option'),
|
| 37 |
+
questionInput: document.getElementById('question-input'),
|
| 38 |
+
askBtn: document.getElementById('ask-btn'),
|
| 39 |
+
|
| 40 |
+
// Output Elements
|
| 41 |
+
welcomeState: document.getElementById('welcome-state'),
|
| 42 |
+
loadingIndicator: document.getElementById('loading-indicator'),
|
| 43 |
+
resultsContainer: document.getElementById('results-container'),
|
| 44 |
+
outCaption: document.getElementById('out-caption'),
|
| 45 |
+
outAnswer: document.getElementById('out-answer'),
|
| 46 |
+
outExplanation: document.getElementById('out-explanation'),
|
| 47 |
+
|
| 48 |
+
// Stats & Admin
|
| 49 |
+
statTotal: document.getElementById('stat-total'),
|
| 50 |
+
statModels: document.getElementById('stat-models'),
|
| 51 |
+
logsBody: document.getElementById('logs-body'),
|
| 52 |
+
toggleGemini: document.getElementById('toggle-gemini'),
|
| 53 |
+
logSearch: document.getElementById('log-search'),
|
| 54 |
+
refreshLogsBtn: document.getElementById('refresh-logs-btn'),
|
| 55 |
+
|
| 56 |
+
// Mobile
|
| 57 |
+
mobileMenuBtn: document.getElementById('mobile-menu-btn'),
|
| 58 |
+
sidebar: document.getElementById('sidebar')
|
| 59 |
+
};
|
| 60 |
+
|
| 61 |
+
// --- Initialization ---
|
| 62 |
+
function init() {
|
| 63 |
+
setupEventListeners();
|
| 64 |
+
initChart();
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// --- Event Listeners ---
|
| 68 |
+
function setupEventListeners() {
|
| 69 |
+
// Login System
|
| 70 |
+
els.loginBtn.addEventListener('click', handleLogin);
|
| 71 |
+
els.loginInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleLogin(); });
|
| 72 |
+
els.logoutBtn.addEventListener('click', handleLogout);
|
| 73 |
+
|
| 74 |
+
// Tabs
|
| 75 |
+
els.menuBtns.forEach(btn => {
|
| 76 |
+
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
// Image Upload & Zoom Modal
|
| 80 |
+
els.dropZone.addEventListener('click', (e) => {
|
| 81 |
+
// Stop click from uploading if they clicked the clear button
|
| 82 |
+
if(e.target.closest('#clear-image-btn')) {
|
| 83 |
+
clearImage();
|
| 84 |
+
return;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
// If they click the image directly, open zoom modal
|
| 88 |
+
if (e.target.id === 'image-preview' && !els.imagePreview.classList.contains('hidden')) {
|
| 89 |
+
els.modalImg.src = els.imagePreview.src;
|
| 90 |
+
els.imageModal.classList.remove('hidden');
|
| 91 |
+
} else {
|
| 92 |
+
els.imageUpload.click();
|
| 93 |
+
}
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
els.closeModal.addEventListener('click', () => {
|
| 97 |
+
els.imageModal.classList.add('hidden');
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
els.imageModal.addEventListener('click', (e) => {
|
| 101 |
+
if(e.target === els.imageModal) els.imageModal.classList.add('hidden');
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
// Drag and Drop
|
| 105 |
+
els.dropZone.addEventListener('dragover', (e) => {
|
| 106 |
+
e.preventDefault(); els.dropZone.style.borderColor = 'var(--accent)';
|
| 107 |
+
});
|
| 108 |
+
els.dropZone.addEventListener('dragleave', () => {
|
| 109 |
+
els.dropZone.style.borderColor = 'var(--border-color)';
|
| 110 |
+
});
|
| 111 |
+
els.dropZone.addEventListener('drop', (e) => {
|
| 112 |
+
e.preventDefault(); els.dropZone.style.borderColor = 'var(--border-color)';
|
| 113 |
+
if (e.dataTransfer.files.length) handleImageUpload(e.dataTransfer.files[0]);
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
els.imageUpload.addEventListener('change', (e) => {
|
| 117 |
+
if (e.target.files.length) handleImageUpload(e.target.files[0]);
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
// Suggestions API (New Cards)
|
| 121 |
+
document.querySelectorAll('.sug-card').forEach(card => {
|
| 122 |
+
card.addEventListener('click', (e) => {
|
| 123 |
+
const text = card.querySelector('span').innerText;
|
| 124 |
+
els.questionInput.value = text;
|
| 125 |
+
});
|
| 126 |
+
});
|
| 127 |
+
|
| 128 |
+
// Ask Button
|
| 129 |
+
els.askBtn.addEventListener('click', handleAskAI);
|
| 130 |
+
els.questionInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleAskAI(); });
|
| 131 |
+
|
| 132 |
+
// Voice Input & Output
|
| 133 |
+
setupVoiceFeatures();
|
| 134 |
+
|
| 135 |
+
// Admin Toggle
|
| 136 |
+
els.toggleGemini.addEventListener('change', (e) => {
|
| 137 |
+
appState.geminiEnabled = e.target.checked;
|
| 138 |
+
els.geminiOption.disabled = !appState.geminiEnabled;
|
| 139 |
+
if (!appState.geminiEnabled && els.modelSelector.value === 'gemini') {
|
| 140 |
+
els.modelSelector.value = 'local';
|
| 141 |
+
}
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
// Log search & Refresh
|
| 145 |
+
els.logSearch.addEventListener('input', (e) => renderLogs(e.target.value));
|
| 146 |
+
els.refreshLogsBtn.addEventListener('click', fetchLogs);
|
| 147 |
+
|
| 148 |
+
// Mobile Sidebar
|
| 149 |
+
if (els.mobileMenuBtn) {
|
| 150 |
+
els.mobileMenuBtn.addEventListener('click', () => {
|
| 151 |
+
els.sidebar.classList.toggle('open');
|
| 152 |
+
});
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
// Close sidebar on mobile when clicking a tab
|
| 156 |
+
els.menuBtns.forEach(btn => {
|
| 157 |
+
btn.addEventListener('click', () => {
|
| 158 |
+
if (window.innerWidth <= 900) {
|
| 159 |
+
els.sidebar.classList.remove('open');
|
| 160 |
+
}
|
| 161 |
+
});
|
| 162 |
+
});
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
// --- Login System ---
|
| 166 |
+
function handleLogin() {
|
| 167 |
+
const val = els.loginInput.value.trim().toLowerCase();
|
| 168 |
+
if (!val) return alert("Please enter a username.");
|
| 169 |
+
|
| 170 |
+
appState.username = val;
|
| 171 |
+
els.loginOverlay.classList.add('hidden');
|
| 172 |
+
els.mainApp.classList.remove('hidden');
|
| 173 |
+
els.displayUsername.innerText = appState.username;
|
| 174 |
+
|
| 175 |
+
if (appState.username === 'vignesh') {
|
| 176 |
+
els.adminTabBtn.style.display = 'flex';
|
| 177 |
+
} else {
|
| 178 |
+
els.adminTabBtn.style.display = 'none';
|
| 179 |
+
if (document.querySelector('.menu-btn.active').dataset.tab === 'admin') {
|
| 180 |
+
switchTab('dashboard');
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
fetchLogs();
|
| 184 |
+
|
| 185 |
+
// Small welcome speech
|
| 186 |
+
speak(`Welcome to GenAI Vision System, ${appState.username}`);
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
function handleLogout() {
|
| 190 |
+
appState.username = '';
|
| 191 |
+
els.mainApp.classList.add('hidden');
|
| 192 |
+
els.loginOverlay.classList.remove('hidden');
|
| 193 |
+
els.loginInput.value = '';
|
| 194 |
+
switchTab('dashboard');
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// --- Voice Features (STT & TTS) ---
|
| 198 |
+
function setupVoiceFeatures() {
|
| 199 |
+
// Text To Speech
|
| 200 |
+
document.querySelectorAll('.tts-btn').forEach(btn => {
|
| 201 |
+
btn.addEventListener('click', (e) => {
|
| 202 |
+
const targetId = e.currentTarget.dataset.target;
|
| 203 |
+
const textToRead = document.getElementById(targetId).innerText;
|
| 204 |
+
speak(textToRead);
|
| 205 |
+
});
|
| 206 |
+
});
|
| 207 |
+
|
| 208 |
+
// Speech To Text
|
| 209 |
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 210 |
+
const micBtn = document.getElementById('mic-btn');
|
| 211 |
+
if (SpeechRecognition) {
|
| 212 |
+
const recognition = new SpeechRecognition();
|
| 213 |
+
recognition.continuous = false;
|
| 214 |
+
recognition.interimResults = false;
|
| 215 |
+
|
| 216 |
+
micBtn.addEventListener('click', () => {
|
| 217 |
+
const lang = document.getElementById('lang-selector').value;
|
| 218 |
+
recognition.lang = lang === 'en' ? 'en-US' : lang; // Convert locale code natively
|
| 219 |
+
micBtn.classList.add('recording');
|
| 220 |
+
recognition.start();
|
| 221 |
+
});
|
| 222 |
+
|
| 223 |
+
recognition.onresult = (event) => {
|
| 224 |
+
const transcript = event.results[0][0].transcript;
|
| 225 |
+
els.questionInput.value = transcript;
|
| 226 |
+
micBtn.classList.remove('recording');
|
| 227 |
+
};
|
| 228 |
+
recognition.onerror = () => micBtn.classList.remove('recording');
|
| 229 |
+
recognition.onend = () => micBtn.classList.remove('recording');
|
| 230 |
+
} else {
|
| 231 |
+
micBtn.addEventListener('click', () => alert("Microphone not supported in this browser."));
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
function speak(text) {
|
| 236 |
+
if(!text) return;
|
| 237 |
+
window.speechSynthesis.cancel();
|
| 238 |
+
const utterance = new SpeechSynthesisUtterance(text);
|
| 239 |
+
const lang = document.getElementById('lang-selector').value;
|
| 240 |
+
utterance.lang = lang === 'en' ? 'en-US' : lang;
|
| 241 |
+
utterance.rate = window.navigator.userAgent.includes("Mac OS") ? 1.0 : 1.1;
|
| 242 |
+
window.speechSynthesis.speak(utterance);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
// --- Tab System ---
|
| 246 |
+
function switchTab(tabId) {
|
| 247 |
+
els.menuBtns.forEach(btn => btn.classList.remove('active'));
|
| 248 |
+
document.querySelector(`.menu-btn[data-tab="${tabId}"]`).classList.add('active');
|
| 249 |
+
|
| 250 |
+
els.tabs.forEach(tab => {
|
| 251 |
+
tab.style.display = 'none';
|
| 252 |
+
tab.classList.remove('active');
|
| 253 |
+
});
|
| 254 |
+
|
| 255 |
+
const target = document.getElementById(tabId);
|
| 256 |
+
target.style.display = 'block';
|
| 257 |
+
requestAnimationFrame(() => target.classList.add('active'));
|
| 258 |
+
|
| 259 |
+
if (tabId === 'analytics') updateChart();
|
| 260 |
+
if (tabId === 'logs') fetchLogs();
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
// --- Image Handling ---
|
| 264 |
+
let currentFile = null;
|
| 265 |
+
function handleImageUpload(file) {
|
| 266 |
+
if (!file.type.startsWith('image/')) return alert("Please upload an image file.");
|
| 267 |
+
currentFile = file;
|
| 268 |
+
const reader = new FileReader();
|
| 269 |
+
reader.onload = (e) => {
|
| 270 |
+
els.imagePreview.src = e.target.result;
|
| 271 |
+
els.imagePreview.classList.remove('hidden');
|
| 272 |
+
if (els.clearImageBtn) els.clearImageBtn.classList.remove('hidden');
|
| 273 |
+
els.uploadPlaceholder.classList.add('hidden');
|
| 274 |
+
};
|
| 275 |
+
reader.readAsDataURL(file);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
function clearImage() {
|
| 279 |
+
currentFile = null;
|
| 280 |
+
els.imageUpload.value = "";
|
| 281 |
+
els.imagePreview.src = "";
|
| 282 |
+
els.imagePreview.classList.add('hidden');
|
| 283 |
+
if (els.clearImageBtn) els.clearImageBtn.classList.add('hidden');
|
| 284 |
+
els.uploadPlaceholder.classList.remove('hidden');
|
| 285 |
+
|
| 286 |
+
// Reset answers
|
| 287 |
+
els.resultsContainer.classList.add('hidden');
|
| 288 |
+
els.welcomeState.classList.remove('hidden');
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
// --- AI Interaction ---
|
| 292 |
+
async function handleAskAI() {
|
| 293 |
+
const question = els.questionInput.value.trim();
|
| 294 |
+
if (!currentFile) return alert("Please upload an image first.");
|
| 295 |
+
if (!question) return alert("Please ask a question.");
|
| 296 |
+
|
| 297 |
+
els.askBtn.disabled = true;
|
| 298 |
+
els.askBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Processing...';
|
| 299 |
+
els.welcomeState.classList.add('hidden');
|
| 300 |
+
els.resultsContainer.classList.add('hidden');
|
| 301 |
+
els.outCaption.innerHTML = ''; els.outAnswer.innerHTML = ''; els.outExplanation.innerHTML = '';
|
| 302 |
+
els.loadingIndicator.classList.remove('hidden');
|
| 303 |
+
|
| 304 |
+
const formData = new FormData();
|
| 305 |
+
formData.append('file', currentFile);
|
| 306 |
+
formData.append('question', question);
|
| 307 |
+
formData.append('model_choice', els.modelSelector.value);
|
| 308 |
+
formData.append('lang', els.langSelector.value);
|
| 309 |
+
formData.append('user', appState.username || "guest");
|
| 310 |
+
|
| 311 |
+
try {
|
| 312 |
+
const response = await fetch('/ask', { method: 'POST', body: formData });
|
| 313 |
+
const data = await response.json();
|
| 314 |
+
|
| 315 |
+
els.loadingIndicator.classList.add('hidden');
|
| 316 |
+
els.resultsContainer.classList.remove('hidden');
|
| 317 |
+
|
| 318 |
+
const cards = document.querySelectorAll('.result-card');
|
| 319 |
+
cards.forEach(card => {
|
| 320 |
+
card.classList.remove('fade-in');
|
| 321 |
+
void card.offsetWidth;
|
| 322 |
+
card.classList.add('fade-in');
|
| 323 |
+
});
|
| 324 |
+
|
| 325 |
+
typeWriter(els.outCaption, data.caption || "No caption provided");
|
| 326 |
+
setTimeout(() => typeWriter(els.outAnswer, data.answer || "No answer provided"), 500);
|
| 327 |
+
setTimeout(() => typeWriter(els.outExplanation, data.explanation || "No explanation provided"), 1000);
|
| 328 |
+
|
| 329 |
+
fetchLogs();
|
| 330 |
+
|
| 331 |
+
} catch (error) {
|
| 332 |
+
console.error(error);
|
| 333 |
+
alert("Error connecting to backend API.");
|
| 334 |
+
els.loadingIndicator.classList.add('hidden');
|
| 335 |
+
} finally {
|
| 336 |
+
els.askBtn.disabled = false;
|
| 337 |
+
els.askBtn.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles"></i> Ask AI';
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
function typeWriter(element, text) {
|
| 342 |
+
let formattedText = text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
|
| 343 |
+
element.innerHTML = formattedText;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
// --- Analytics & Logs API ---
|
| 347 |
+
async function fetchLogs() {
|
| 348 |
+
if(!appState.username) return;
|
| 349 |
+
try {
|
| 350 |
+
const url = `/api/logs?user=${encodeURIComponent(appState.username)}`;
|
| 351 |
+
const res = await fetch(url);
|
| 352 |
+
const data = await res.json();
|
| 353 |
+
|
| 354 |
+
appState.logs = data.logs || [];
|
| 355 |
+
appState.stats.total = appState.logs.length;
|
| 356 |
+
appState.stats.local = appState.logs.filter(l => ['local', 'blip', 'reasoning'].includes(l.model.toLowerCase())).length;
|
| 357 |
+
appState.stats.gemini = appState.logs.filter(l => ['gemini', 'external'].includes(l.model.toLowerCase())).length;
|
| 358 |
+
|
| 359 |
+
updateDashboardView();
|
| 360 |
+
} catch(err) {
|
| 361 |
+
console.error("Failed fetching logs:", err);
|
| 362 |
+
}
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
function updateDashboardView() {
|
| 366 |
+
els.statTotal.innerText = appState.stats.total;
|
| 367 |
+
els.statModels.innerText = `${appState.stats.local} Local / ${appState.stats.gemini} Cloud`;
|
| 368 |
+
renderLogs();
|
| 369 |
+
updateChart();
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
function renderLogs(filter = '') {
|
| 373 |
+
els.logsBody.innerHTML = '';
|
| 374 |
+
appState.logs
|
| 375 |
+
.filter(log => log.question.toLowerCase().includes(filter.toLowerCase()) || log.model.includes(filter))
|
| 376 |
+
.forEach(log => {
|
| 377 |
+
const tr = document.createElement('tr');
|
| 378 |
+
tr.innerHTML = `
|
| 379 |
+
<td>${log.timestamp}</td>
|
| 380 |
+
<td><span class="user-badge" style="color: var(--text-secondary);"><i class="fa-solid fa-user"></i> ${log.user}</span></td>
|
| 381 |
+
<td><span style="color: ${log.model.includes('gemini') ? 'var(--accent)' : 'var(--success)'}">${log.model.toUpperCase()}</span></td>
|
| 382 |
+
<td>${log.question.length > 50 ? log.question.substring(0, 50) + '...' : log.question}</td>
|
| 383 |
+
`;
|
| 384 |
+
els.logsBody.appendChild(tr);
|
| 385 |
+
});
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
// --- Chart.js ---
|
| 389 |
+
let myChart;
|
| 390 |
+
function initChart() {
|
| 391 |
+
const ctx = document.getElementById('modelChart').getContext('2d');
|
| 392 |
+
Chart.defaults.color = '#94a3b8';
|
| 393 |
+
Chart.defaults.font.family = 'Inter';
|
| 394 |
+
|
| 395 |
+
myChart = new Chart(ctx, {
|
| 396 |
+
type: 'bar',
|
| 397 |
+
data: getChartData(),
|
| 398 |
+
options: {
|
| 399 |
+
responsive: true, maintainAspectRatio: false,
|
| 400 |
+
plugins: { legend: { display: false } },
|
| 401 |
+
scales: {
|
| 402 |
+
y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,0.1)' } },
|
| 403 |
+
x: { grid: { display: false } }
|
| 404 |
+
}
|
| 405 |
+
}
|
| 406 |
+
});
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
function getChartData() {
|
| 410 |
+
return {
|
| 411 |
+
labels: ['BLIP + FLAN (Local)', 'Gemini AI (Cloud)'],
|
| 412 |
+
datasets: [{
|
| 413 |
+
label: 'Queries',
|
| 414 |
+
data: [appState.stats.local, appState.stats.gemini],
|
| 415 |
+
backgroundColor: ['rgba(16, 185, 129, 0.6)', 'rgba(99, 102, 241, 0.6)'],
|
| 416 |
+
borderColor: ['rgba(16, 185, 129, 1)', 'rgba(99, 102, 241, 1)'],
|
| 417 |
+
borderWidth: 1, borderRadius: 6
|
| 418 |
+
}]
|
| 419 |
+
};
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
function updateChart() {
|
| 423 |
+
if (myChart) {
|
| 424 |
+
myChart.data = getChartData();
|
| 425 |
+
myChart.update();
|
| 426 |
+
}
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
init();
|
static/css/.ipynb_checkpoints/style-checkpoint.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.loader {
|
| 2 |
+
border: 6px solid #1e293b;
|
| 3 |
+
border-top: 6px solid #3b82f6;
|
| 4 |
+
border-radius: 50%;
|
| 5 |
+
width: 40px;
|
| 6 |
+
height: 40px;
|
| 7 |
+
animation: spin 1s linear infinite;
|
| 8 |
+
margin: 20px auto;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
@keyframes spin {
|
| 12 |
+
0% { transform: rotate(0deg); }
|
| 13 |
+
100% { transform: rotate(360deg); }
|
| 14 |
+
}
|
static/css/style.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.loader {
|
| 2 |
+
border: 6px solid #1e293b;
|
| 3 |
+
border-top: 6px solid #3b82f6;
|
| 4 |
+
border-radius: 50%;
|
| 5 |
+
width: 40px;
|
| 6 |
+
height: 40px;
|
| 7 |
+
animation: spin 1s linear infinite;
|
| 8 |
+
margin: 20px auto;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
@keyframes spin {
|
| 12 |
+
0% { transform: rotate(0deg); }
|
| 13 |
+
100% { transform: rotate(360deg); }
|
| 14 |
+
}
|
static/js/.ipynb_checkpoints/app-checkpoint.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
let currentUser = "";
|
| 2 |
+
let requestCount = 0;
|
| 3 |
+
|
| 4 |
+
function login() {
|
| 5 |
+
const user = document.getElementById("username").value;
|
| 6 |
+
if (!user) return alert("Enter username");
|
| 7 |
+
|
| 8 |
+
currentUser = user;
|
| 9 |
+
|
| 10 |
+
if (user === "vignesh") {
|
| 11 |
+
document.getElementById("adminMenu").classList.remove("hidden");
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
document.getElementById("loginPage").classList.add("hidden");
|
| 15 |
+
document.getElementById("appContainer").classList.remove("hidden");
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function showPage(page) {
|
| 19 |
+
["dashboard", "analytics", "logs", "admin"].forEach(p => {
|
| 20 |
+
document.getElementById(p + "Page").classList.add("hidden");
|
| 21 |
+
});
|
| 22 |
+
document.getElementById(page + "Page").classList.remove("hidden");
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
document.getElementById("imageInput").addEventListener("change", function () {
|
| 26 |
+
const file = this.files[0];
|
| 27 |
+
if (file) {
|
| 28 |
+
const img = document.getElementById("previewImage");
|
| 29 |
+
img.src = URL.createObjectURL(file);
|
| 30 |
+
img.classList.remove("hidden");
|
| 31 |
+
}
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
document.getElementById("micBtn").addEventListener("click", function () {
|
| 35 |
+
const recognition = new webkitSpeechRecognition();
|
| 36 |
+
recognition.lang = "en-US";
|
| 37 |
+
recognition.start();
|
| 38 |
+
recognition.onresult = function (event) {
|
| 39 |
+
document.getElementById("questionInput").value =
|
| 40 |
+
event.results[0][0].transcript;
|
| 41 |
+
};
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
function typeWriter(element, text, speed = 15) {
|
| 45 |
+
element.innerHTML = "";
|
| 46 |
+
let i = 0;
|
| 47 |
+
function typing() {
|
| 48 |
+
if (i < text.length) {
|
| 49 |
+
element.innerHTML += text.charAt(i);
|
| 50 |
+
i++;
|
| 51 |
+
setTimeout(typing, speed);
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
typing();
|
| 55 |
+
}
|
| 56 |
+
typeWriter(document.getElementById("answerText"), data.answer);
|
| 57 |
+
typeWriter(document.getElementById("explanationText"), data.explanation);
|
| 58 |
+
|
| 59 |
+
async function askQuestion() {
|
| 60 |
+
|
| 61 |
+
const file = document.getElementById("imageInput").files[0];
|
| 62 |
+
const question = document.getElementById("questionInput").value;
|
| 63 |
+
const model = document.getElementById("modelSelect").value;
|
| 64 |
+
|
| 65 |
+
if (!file || !question) return alert("Upload image + ask question");
|
| 66 |
+
|
| 67 |
+
const formData = new FormData();
|
| 68 |
+
formData.append("file", file);
|
| 69 |
+
formData.append("question", question);
|
| 70 |
+
formData.append("model_choice", model);
|
| 71 |
+
formData.append("user", currentUser);
|
| 72 |
+
|
| 73 |
+
document.getElementById("loadingSpinner").classList.remove("hidden");
|
| 74 |
+
|
| 75 |
+
const response = await fetch("/ask", {
|
| 76 |
+
method: "POST",
|
| 77 |
+
body: formData
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
const data = await response.json();
|
| 81 |
+
|
| 82 |
+
document.getElementById("captionText").innerText = data.caption;
|
| 83 |
+
document.getElementById("answerText").innerText = data.answer;
|
| 84 |
+
document.getElementById("explanationText").innerText = data.explanation;
|
| 85 |
+
|
| 86 |
+
document.getElementById("resultsSection").classList.remove("hidden");
|
| 87 |
+
document.getElementById("loadingSpinner").classList.add("hidden");
|
| 88 |
+
|
| 89 |
+
requestCount++;
|
| 90 |
+
document.getElementById("totalRequests").innerText = requestCount;
|
| 91 |
+
}
|
static/js/app.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
let currentUser = "";
|
| 2 |
+
let requestCount = 0;
|
| 3 |
+
|
| 4 |
+
function login() {
|
| 5 |
+
const user = document.getElementById("username").value;
|
| 6 |
+
if (!user) return alert("Enter username");
|
| 7 |
+
|
| 8 |
+
currentUser = user;
|
| 9 |
+
|
| 10 |
+
if (user === "vignesh") {
|
| 11 |
+
document.getElementById("adminMenu").classList.remove("hidden");
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
document.getElementById("loginPage").classList.add("hidden");
|
| 15 |
+
document.getElementById("appContainer").classList.remove("hidden");
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function showPage(page) {
|
| 19 |
+
["dashboard", "analytics", "logs", "admin"].forEach(p => {
|
| 20 |
+
document.getElementById(p + "Page").classList.add("hidden");
|
| 21 |
+
});
|
| 22 |
+
document.getElementById(page + "Page").classList.remove("hidden");
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
document.getElementById("imageInput").addEventListener("change", function () {
|
| 26 |
+
const file = this.files[0];
|
| 27 |
+
if (file) {
|
| 28 |
+
const img = document.getElementById("previewImage");
|
| 29 |
+
img.src = URL.createObjectURL(file);
|
| 30 |
+
img.classList.remove("hidden");
|
| 31 |
+
}
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
document.getElementById("micBtn").addEventListener("click", function () {
|
| 35 |
+
const recognition = new webkitSpeechRecognition();
|
| 36 |
+
recognition.lang = "en-US";
|
| 37 |
+
recognition.start();
|
| 38 |
+
recognition.onresult = function (event) {
|
| 39 |
+
document.getElementById("questionInput").value =
|
| 40 |
+
event.results[0][0].transcript;
|
| 41 |
+
};
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
function typeWriter(element, text, speed = 15) {
|
| 45 |
+
element.innerHTML = "";
|
| 46 |
+
let i = 0;
|
| 47 |
+
function typing() {
|
| 48 |
+
if (i < text.length) {
|
| 49 |
+
element.innerHTML += text.charAt(i);
|
| 50 |
+
i++;
|
| 51 |
+
setTimeout(typing, speed);
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
typing();
|
| 55 |
+
}
|
| 56 |
+
typeWriter(document.getElementById("answerText"), data.answer);
|
| 57 |
+
typeWriter(document.getElementById("explanationText"), data.explanation);
|
| 58 |
+
|
| 59 |
+
async function askQuestion() {
|
| 60 |
+
|
| 61 |
+
const file = document.getElementById("imageInput").files[0];
|
| 62 |
+
const question = document.getElementById("questionInput").value;
|
| 63 |
+
const model = document.getElementById("modelSelect").value;
|
| 64 |
+
|
| 65 |
+
if (!file || !question) return alert("Upload image + ask question");
|
| 66 |
+
|
| 67 |
+
const formData = new FormData();
|
| 68 |
+
formData.append("file", file);
|
| 69 |
+
formData.append("question", question);
|
| 70 |
+
formData.append("model_choice", model);
|
| 71 |
+
formData.append("user", currentUser);
|
| 72 |
+
|
| 73 |
+
document.getElementById("loadingSpinner").classList.remove("hidden");
|
| 74 |
+
|
| 75 |
+
const response = await fetch("/ask", {
|
| 76 |
+
method: "POST",
|
| 77 |
+
body: formData
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
const data = await response.json();
|
| 81 |
+
|
| 82 |
+
document.getElementById("captionText").innerText = data.caption;
|
| 83 |
+
document.getElementById("answerText").innerText = data.answer;
|
| 84 |
+
document.getElementById("explanationText").innerText = data.explanation;
|
| 85 |
+
|
| 86 |
+
document.getElementById("resultsSection").classList.remove("hidden");
|
| 87 |
+
document.getElementById("loadingSpinner").classList.add("hidden");
|
| 88 |
+
|
| 89 |
+
requestCount++;
|
| 90 |
+
document.getElementById("totalRequests").innerText = requestCount;
|
| 91 |
+
}
|
static/style.css
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg-dark: #0f1115;
|
| 3 |
+
--bg-panel: rgba(255, 255, 255, 0.03);
|
| 4 |
+
--bg-panel-hover: rgba(255, 255, 255, 0.05);
|
| 5 |
+
--border-color: rgba(255, 255, 255, 0.1);
|
| 6 |
+
--text-primary: #ffffff;
|
| 7 |
+
--text-secondary: #94a3b8;
|
| 8 |
+
--accent: #6366f1;
|
| 9 |
+
--accent-hover: #4f46e5;
|
| 10 |
+
--danger: #ef4444;
|
| 11 |
+
--success: #10b981;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
* {
|
| 15 |
+
margin: 0;
|
| 16 |
+
padding: 0;
|
| 17 |
+
box-sizing: border-box;
|
| 18 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
body {
|
| 22 |
+
background-color: var(--bg-dark);
|
| 23 |
+
color: var(--text-primary);
|
| 24 |
+
min-height: 100vh;
|
| 25 |
+
overflow-x: hidden;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/* Mobile Header */
|
| 29 |
+
.mobile-header {
|
| 30 |
+
display: none;
|
| 31 |
+
justify-content: space-between;
|
| 32 |
+
align-items: center;
|
| 33 |
+
padding: 15px 20px;
|
| 34 |
+
background: rgba(15, 17, 21, 0.95);
|
| 35 |
+
border-bottom: 1px solid var(--border-color);
|
| 36 |
+
position: sticky;
|
| 37 |
+
top: 0;
|
| 38 |
+
z-index: 90;
|
| 39 |
+
}
|
| 40 |
+
.mobile-header .logo {
|
| 41 |
+
margin-bottom: 0;
|
| 42 |
+
padding: 0;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* Glassmorphism Panel */
|
| 46 |
+
.glass-panel {
|
| 47 |
+
background: var(--bg-panel);
|
| 48 |
+
backdrop-filter: blur(12px);
|
| 49 |
+
-webkit-backdrop-filter: blur(12px);
|
| 50 |
+
border: 1px solid var(--border-color);
|
| 51 |
+
border-radius: 16px;
|
| 52 |
+
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.app-container {
|
| 56 |
+
display: flex;
|
| 57 |
+
height: 100vh;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/* Sidebar */
|
| 61 |
+
.sidebar {
|
| 62 |
+
width: 250px;
|
| 63 |
+
background: rgba(15, 17, 21, 0.8);
|
| 64 |
+
border-right: 1px solid var(--border-color);
|
| 65 |
+
display: flex;
|
| 66 |
+
flex-direction: column;
|
| 67 |
+
padding: 20px 0;
|
| 68 |
+
backdrop-filter: blur(20px);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.logo {
|
| 72 |
+
display: flex;
|
| 73 |
+
align-items: center;
|
| 74 |
+
gap: 12px;
|
| 75 |
+
padding: 0 24px;
|
| 76 |
+
margin-bottom: 40px;
|
| 77 |
+
color: var(--text-primary);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.logo i {
|
| 81 |
+
font-size: 24px;
|
| 82 |
+
color: var(--accent);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.menu {
|
| 86 |
+
display: flex;
|
| 87 |
+
flex-direction: column;
|
| 88 |
+
gap: 8px;
|
| 89 |
+
padding: 0 16px;
|
| 90 |
+
flex-grow: 1;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.menu-btn {
|
| 94 |
+
background: transparent;
|
| 95 |
+
border: none;
|
| 96 |
+
color: var(--text-secondary);
|
| 97 |
+
padding: 12px 16px;
|
| 98 |
+
text-align: left;
|
| 99 |
+
border-radius: 8px;
|
| 100 |
+
cursor: pointer;
|
| 101 |
+
font-size: 15px;
|
| 102 |
+
display: flex;
|
| 103 |
+
align-items: center;
|
| 104 |
+
gap: 12px;
|
| 105 |
+
transition: all 0.2s ease;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.menu-btn:hover {
|
| 109 |
+
background: var(--bg-panel);
|
| 110 |
+
color: var(--text-primary);
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.menu-btn.active {
|
| 114 |
+
background: var(--accent);
|
| 115 |
+
color: white;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.user-profile {
|
| 119 |
+
padding: 16px 24px;
|
| 120 |
+
border-top: 1px solid var(--border-color);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.user-info {
|
| 124 |
+
display: flex;
|
| 125 |
+
align-items: center;
|
| 126 |
+
gap: 12px;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.user-info i {
|
| 130 |
+
font-size: 20px;
|
| 131 |
+
color: var(--text-secondary);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.user-info input {
|
| 135 |
+
background: transparent;
|
| 136 |
+
border: none;
|
| 137 |
+
color: var(--text-primary);
|
| 138 |
+
font-size: 14px;
|
| 139 |
+
outline: none;
|
| 140 |
+
width: 100%;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/* Main Content */
|
| 144 |
+
.main-content {
|
| 145 |
+
flex-grow: 1;
|
| 146 |
+
padding: 40px;
|
| 147 |
+
overflow-y: auto;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
header {
|
| 151 |
+
margin-bottom: 30px;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
header h1 {
|
| 155 |
+
font-size: 28px;
|
| 156 |
+
font-weight: 600;
|
| 157 |
+
margin-bottom: 8px;
|
| 158 |
+
background: linear-gradient(135deg, #fff, #94a3b8);
|
| 159 |
+
-webkit-background-clip: text;
|
| 160 |
+
-webkit-text-fill-color: transparent;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
header p {
|
| 164 |
+
color: var(--text-secondary);
|
| 165 |
+
font-size: 15px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
/* Tab Management */
|
| 169 |
+
.tab-content {
|
| 170 |
+
display: none;
|
| 171 |
+
animation: fadeIn 0.4s ease forwards;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.tab-content.active {
|
| 175 |
+
display: block;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
/* VQA Layout */
|
| 179 |
+
.vqa-container {
|
| 180 |
+
display: grid;
|
| 181 |
+
grid-template-columns: 1fr 1fr;
|
| 182 |
+
gap: 30px;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/* Upload Area */
|
| 186 |
+
.input-section {
|
| 187 |
+
padding: 24px;
|
| 188 |
+
display: flex;
|
| 189 |
+
flex-direction: column;
|
| 190 |
+
gap: 24px;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.upload-area {
|
| 194 |
+
width: 100%;
|
| 195 |
+
height: 250px;
|
| 196 |
+
border: 2px dashed var(--border-color);
|
| 197 |
+
border-radius: 12px;
|
| 198 |
+
display: flex;
|
| 199 |
+
align-items: center;
|
| 200 |
+
justify-content: center;
|
| 201 |
+
cursor: pointer;
|
| 202 |
+
transition: all 0.3s ease;
|
| 203 |
+
overflow: hidden;
|
| 204 |
+
position: relative;
|
| 205 |
+
background: rgba(0,0,0,0.2);
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.upload-area:hover {
|
| 209 |
+
border-color: var(--accent);
|
| 210 |
+
background: rgba(99, 102, 241, 0.05);
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.upload-placeholder {
|
| 214 |
+
text-align: center;
|
| 215 |
+
color: var(--text-secondary);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.upload-placeholder i {
|
| 219 |
+
font-size: 48px;
|
| 220 |
+
margin-bottom: 16px;
|
| 221 |
+
color: var(--accent);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
#image-preview {
|
| 225 |
+
width: 100%;
|
| 226 |
+
height: 100%;
|
| 227 |
+
object-fit: cover;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
.hidden {
|
| 231 |
+
display: none !important;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/* Controls */
|
| 235 |
+
.controls {
|
| 236 |
+
display: flex;
|
| 237 |
+
flex-direction: column;
|
| 238 |
+
gap: 16px;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.control-group {
|
| 242 |
+
display: flex;
|
| 243 |
+
flex-direction: column;
|
| 244 |
+
gap: 8px;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
.control-group label {
|
| 248 |
+
font-size: 14px;
|
| 249 |
+
font-weight: 500;
|
| 250 |
+
color: var(--text-secondary);
|
| 251 |
+
display: flex;
|
| 252 |
+
align-items: center;
|
| 253 |
+
gap: 8px;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
select, input[type="text"] {
|
| 257 |
+
width: 100%;
|
| 258 |
+
background: rgba(0,0,0,0.3);
|
| 259 |
+
border: 1px solid var(--border-color);
|
| 260 |
+
color: var(--text-primary);
|
| 261 |
+
padding: 12px 16px;
|
| 262 |
+
border-radius: 8px;
|
| 263 |
+
font-size: 14px;
|
| 264 |
+
outline: none;
|
| 265 |
+
transition: border-color 0.2s;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
select:focus, input[type="text"]:focus {
|
| 269 |
+
border-color: var(--accent);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
.input-wrapper {
|
| 273 |
+
display: flex;
|
| 274 |
+
gap: 8px;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.input-wrapper input {
|
| 278 |
+
flex-grow: 1;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.icon-btn {
|
| 282 |
+
background: rgba(0,0,0,0.3);
|
| 283 |
+
border: 1px solid var(--border-color);
|
| 284 |
+
color: var(--text-primary);
|
| 285 |
+
width: 44px;
|
| 286 |
+
border-radius: 8px;
|
| 287 |
+
cursor: pointer;
|
| 288 |
+
transition: all 0.2s;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.icon-btn:hover {
|
| 292 |
+
background: var(--bg-panel-hover);
|
| 293 |
+
color: var(--accent);
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.primary-btn {
|
| 297 |
+
background: var(--accent);
|
| 298 |
+
color: white;
|
| 299 |
+
border: none;
|
| 300 |
+
padding: 14px;
|
| 301 |
+
border-radius: 8px;
|
| 302 |
+
font-size: 15px;
|
| 303 |
+
font-weight: 600;
|
| 304 |
+
cursor: pointer;
|
| 305 |
+
display: flex;
|
| 306 |
+
align-items: center;
|
| 307 |
+
justify-content: center;
|
| 308 |
+
gap: 8px;
|
| 309 |
+
transition: all 0.2s;
|
| 310 |
+
margin-top: 8px;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
.primary-btn:hover {
|
| 314 |
+
background: var(--accent-hover);
|
| 315 |
+
transform: translateY(-2px);
|
| 316 |
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.primary-btn:disabled {
|
| 320 |
+
background: #334155;
|
| 321 |
+
cursor: not-allowed;
|
| 322 |
+
transform: none;
|
| 323 |
+
box-shadow: none;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
/* Results Section */
|
| 327 |
+
.output-section {
|
| 328 |
+
display: flex;
|
| 329 |
+
flex-direction: column;
|
| 330 |
+
gap: 16px;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
.result-card {
|
| 334 |
+
padding: 20px;
|
| 335 |
+
border-left: 4px solid var(--accent);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.result-card h3 {
|
| 339 |
+
font-size: 14px;
|
| 340 |
+
color: var(--text-secondary);
|
| 341 |
+
margin-bottom: 8px;
|
| 342 |
+
display: flex;
|
| 343 |
+
align-items: center;
|
| 344 |
+
gap: 8px;
|
| 345 |
+
text-transform: uppercase;
|
| 346 |
+
letter-spacing: 0.5px;
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
.result-card p {
|
| 350 |
+
font-size: 15px;
|
| 351 |
+
line-height: 1.6;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
/* Animations */
|
| 355 |
+
@keyframes fadeIn {
|
| 356 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 357 |
+
to { opacity: 1; transform: translateY(0); }
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
.fade-in {
|
| 361 |
+
opacity: 0;
|
| 362 |
+
animation: fadeIn 0.5s ease forwards;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
.delay-1 { animation-delay: 0.2s; }
|
| 366 |
+
.delay-2 { animation-delay: 0.4s; }
|
| 367 |
+
|
| 368 |
+
/* Loading Spinner */
|
| 369 |
+
#loading-indicator {
|
| 370 |
+
padding: 40px;
|
| 371 |
+
display: flex;
|
| 372 |
+
flex-direction: column;
|
| 373 |
+
align-items: center;
|
| 374 |
+
justify-content: center;
|
| 375 |
+
gap: 16px;
|
| 376 |
+
height: 100%;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.spinner {
|
| 380 |
+
width: 40px;
|
| 381 |
+
height: 40px;
|
| 382 |
+
border: 3px solid rgba(255, 255, 255, 0.1);
|
| 383 |
+
border-radius: 50%;
|
| 384 |
+
border-top-color: var(--accent);
|
| 385 |
+
animation: spin 1s ease-in-out infinite;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
@keyframes spin {
|
| 389 |
+
to { transform: rotate(360deg); }
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
/* Typewriter Effect Container */
|
| 393 |
+
.typewriter-container {
|
| 394 |
+
min-height: 24px;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
/* Toggle Switch */
|
| 398 |
+
.switch {
|
| 399 |
+
position: relative;
|
| 400 |
+
display: inline-block;
|
| 401 |
+
width: 50px;
|
| 402 |
+
height: 26px;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
.switch input { opacity: 0; width: 0; height: 0; }
|
| 406 |
+
|
| 407 |
+
.slider {
|
| 408 |
+
position: absolute;
|
| 409 |
+
cursor: pointer;
|
| 410 |
+
top: 0; left: 0; right: 0; bottom: 0;
|
| 411 |
+
background-color: var(--border-color);
|
| 412 |
+
transition: .4s;
|
| 413 |
+
border-radius: 34px;
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
.slider:before {
|
| 417 |
+
position: absolute;
|
| 418 |
+
content: "";
|
| 419 |
+
height: 20px;
|
| 420 |
+
width: 20px;
|
| 421 |
+
left: 3px;
|
| 422 |
+
bottom: 3px;
|
| 423 |
+
background-color: white;
|
| 424 |
+
transition: .4s;
|
| 425 |
+
border-radius: 50%;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
input:checked + .slider {
|
| 429 |
+
background-color: var(--success);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
input:checked + .slider:before {
|
| 433 |
+
transform: translateX(24px);
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
/* Analytics Grid */
|
| 437 |
+
.stats-grid {
|
| 438 |
+
display: grid;
|
| 439 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 440 |
+
gap: 20px;
|
| 441 |
+
margin-bottom: 30px;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
.stat-card {
|
| 445 |
+
padding: 24px;
|
| 446 |
+
text-align: center;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.stat-card h3 {
|
| 450 |
+
font-size: 14px;
|
| 451 |
+
color: var(--text-secondary);
|
| 452 |
+
margin-bottom: 12px;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
.stat-value {
|
| 456 |
+
font-size: 32px;
|
| 457 |
+
font-weight: 700;
|
| 458 |
+
color: var(--accent);
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.chart-container {
|
| 462 |
+
padding: 24px;
|
| 463 |
+
height: 400px;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
/* Admin Grid */
|
| 467 |
+
.admin-grid {
|
| 468 |
+
display: grid;
|
| 469 |
+
grid-template-columns: 1fr;
|
| 470 |
+
gap: 20px;
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
.admin-card {
|
| 474 |
+
padding: 24px;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.admin-card h3 {
|
| 478 |
+
margin-bottom: 20px;
|
| 479 |
+
padding-bottom: 12px;
|
| 480 |
+
border-bottom: 1px solid var(--border-color);
|
| 481 |
+
color: var(--text-primary);
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
.setting-row {
|
| 485 |
+
display: flex;
|
| 486 |
+
justify-content: space-between;
|
| 487 |
+
align-items: center;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
.danger-btn {
|
| 491 |
+
background: rgba(239, 68, 68, 0.1);
|
| 492 |
+
color: var(--danger);
|
| 493 |
+
border: 1px solid var(--danger);
|
| 494 |
+
padding: 10px 16px;
|
| 495 |
+
border-radius: 8px;
|
| 496 |
+
cursor: pointer;
|
| 497 |
+
font-weight: 500;
|
| 498 |
+
transition: all 0.2s;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.danger-btn:hover {
|
| 502 |
+
background: var(--danger);
|
| 503 |
+
color: white;
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
/* Logs Table */
|
| 507 |
+
.table-container {
|
| 508 |
+
overflow-x: auto;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.logs-table {
|
| 512 |
+
width: 100%;
|
| 513 |
+
border-collapse: collapse;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.logs-table th, .logs-table td {
|
| 517 |
+
padding: 14px 16px;
|
| 518 |
+
text-align: left;
|
| 519 |
+
border-bottom: 1px solid var(--border-color);
|
| 520 |
+
font-size: 14px;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.logs-table th {
|
| 524 |
+
color: var(--text-secondary);
|
| 525 |
+
font-weight: 500;
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
.filter-bar {
|
| 529 |
+
margin-bottom: 16px;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
.filter-bar input {
|
| 533 |
+
width: 300px;
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
/* Suggestions Grid */
|
| 537 |
+
.suggestions-grid {
|
| 538 |
+
display: grid;
|
| 539 |
+
grid-template-columns: repeat(2, 1fr);
|
| 540 |
+
gap: 15px;
|
| 541 |
+
margin-top: 20px;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.sug-card {
|
| 545 |
+
background: rgba(255, 255, 255, 0.05);
|
| 546 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 547 |
+
border-radius: 12px;
|
| 548 |
+
padding: 16px;
|
| 549 |
+
display: flex;
|
| 550 |
+
align-items: center;
|
| 551 |
+
gap: 12px;
|
| 552 |
+
color: var(--text-primary);
|
| 553 |
+
cursor: pointer;
|
| 554 |
+
font-size: 14px;
|
| 555 |
+
text-align: left;
|
| 556 |
+
transition: all 0.2s ease;
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
.sug-card:hover {
|
| 560 |
+
background: rgba(255, 255, 255, 0.1);
|
| 561 |
+
transform: translateY(-2px);
|
| 562 |
+
border-color: rgba(255, 255, 255, 0.2);
|
| 563 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.sug-card i {
|
| 567 |
+
font-size: 20px;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
/* Responsive */
|
| 571 |
+
@media (max-width: 900px) {
|
| 572 |
+
.suggestions-grid {
|
| 573 |
+
grid-template-columns: 1fr;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.app-container {
|
| 577 |
+
flex-direction: column;
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
.mobile-header {
|
| 581 |
+
display: flex;
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
.vqa-container {
|
| 585 |
+
grid-template-columns: 1fr;
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
.sidebar {
|
| 589 |
+
position: fixed;
|
| 590 |
+
left: -250px;
|
| 591 |
+
top: 0;
|
| 592 |
+
height: 100vh;
|
| 593 |
+
z-index: 100;
|
| 594 |
+
transition: left 0.3s;
|
| 595 |
+
box-shadow: 5px 0 15px rgba(0,0,0,0.5);
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
.sidebar.open {
|
| 599 |
+
left: 0;
|
| 600 |
+
}
|
| 601 |
+
|
| 602 |
+
.main-content {
|
| 603 |
+
padding: 15px;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
/* Login fix for mobile */
|
| 607 |
+
.login-box {
|
| 608 |
+
margin: 20px;
|
| 609 |
+
padding: 30px 20px;
|
| 610 |
+
}
|
| 611 |
+
}
|
templates/.ipynb_checkpoints/index-checkpoint.html
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>GenAI VQA Dashboard</title>
|
| 6 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 7 |
+
</head>
|
| 8 |
+
|
| 9 |
+
<body class="bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-white min-h-screen">
|
| 10 |
+
|
| 11 |
+
<!-- LOGIN -->
|
| 12 |
+
<div id="loginPage" class="flex items-center justify-center h-screen">
|
| 13 |
+
<div class="bg-gray-800 p-8 rounded-2xl shadow-xl w-96 animate-fade-in">
|
| 14 |
+
<h2 class="text-2xl font-bold mb-6 text-blue-400">GenAI VQA Login</h2>
|
| 15 |
+
<input id="username" placeholder="Username"
|
| 16 |
+
class="w-full p-3 mb-4 rounded bg-gray-700">
|
| 17 |
+
<button onclick="login()"
|
| 18 |
+
class="w-full bg-blue-600 p-3 rounded hover:bg-blue-700 transition">
|
| 19 |
+
Login
|
| 20 |
+
</button>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<!-- MAIN -->
|
| 25 |
+
<div id="appContainer" class="hidden flex">
|
| 26 |
+
|
| 27 |
+
<!-- Sidebar -->
|
| 28 |
+
<div class="w-64 bg-gray-800 p-6 shadow-xl">
|
| 29 |
+
<h2 class="text-2xl font-bold mb-8 text-blue-400">GenAI VQA</h2>
|
| 30 |
+
<ul class="space-y-4">
|
| 31 |
+
<li onclick="showPage('dashboard')" class="cursor-pointer hover:text-blue-400">Dashboard</li>
|
| 32 |
+
<li onclick="showPage('analytics')" class="cursor-pointer hover:text-blue-400">Analytics</li>
|
| 33 |
+
<li onclick="showPage('logs')" class="cursor-pointer hover:text-blue-400">Logs</li>
|
| 34 |
+
<li id="adminMenu" onclick="showPage('admin')" class="cursor-pointer hover:text-blue-400 hidden">Admin</li>
|
| 35 |
+
</ul>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<!-- Content -->
|
| 39 |
+
<div class="flex-1 p-8">
|
| 40 |
+
|
| 41 |
+
<!-- DASHBOARD -->
|
| 42 |
+
<div id="dashboardPage">
|
| 43 |
+
<h1 class="text-3xl font-bold mb-6">Vision Question Answering</h1>
|
| 44 |
+
|
| 45 |
+
<input type="file" id="imageInput" class="mb-4">
|
| 46 |
+
<img id="previewImage" class="max-h-64 rounded mb-4 hidden">
|
| 47 |
+
|
| 48 |
+
<input id="questionInput"
|
| 49 |
+
placeholder="Ask a question..."
|
| 50 |
+
class="w-full p-3 rounded bg-gray-700 mb-4">
|
| 51 |
+
|
| 52 |
+
<div class="flex gap-4 mb-4">
|
| 53 |
+
|
| 54 |
+
<select id="modelSelect"
|
| 55 |
+
class="p-3 bg-gray-700 rounded">
|
| 56 |
+
<option value="local">BLIP + FLAN</option>
|
| 57 |
+
<option value="gemini">Gemini AI</option>
|
| 58 |
+
|
| 59 |
+
</select>
|
| 60 |
+
|
| 61 |
+
<button id="micBtn"
|
| 62 |
+
class="bg-purple-600 px-4 py-2 rounded hover:bg-purple-700 transition">
|
| 63 |
+
🎤
|
| 64 |
+
</button>
|
| 65 |
+
|
| 66 |
+
<button onclick="askQuestion()"
|
| 67 |
+
class="bg-blue-600 px-6 py-2 rounded hover:bg-blue-700 transition">
|
| 68 |
+
Ask
|
| 69 |
+
</button>
|
| 70 |
+
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<!-- LOADING -->
|
| 74 |
+
<div id="loadingSpinner" class="hidden">
|
| 75 |
+
<div class="animate-spin h-8 w-8 border-4 border-blue-400 border-t-transparent rounded-full"></div>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<!-- RESULTS -->
|
| 79 |
+
<div id="resultsSection" class="space-y-4 hidden mt-6">
|
| 80 |
+
|
| 81 |
+
<div class="bg-gray-800 p-4 rounded-xl shadow-lg">
|
| 82 |
+
<h2 class="text-blue-400 font-semibold">Caption</h2>
|
| 83 |
+
<p id="captionText"></p>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div class="bg-gray-800 p-4 rounded-xl shadow-lg">
|
| 87 |
+
<h2 class="text-green-400 font-semibold">Answer</h2>
|
| 88 |
+
<p id="answerText"></p>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<div class="bg-gray-800 p-4 rounded-xl shadow-lg">
|
| 92 |
+
<h2 class="text-yellow-400 font-semibold">Explanation</h2>
|
| 93 |
+
<p id="explanationText"></p>
|
| 94 |
+
</div>
|
| 95 |
+
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<!-- ANALYTICS -->
|
| 100 |
+
<div id="analyticsPage" class="hidden">
|
| 101 |
+
<h1 class="text-3xl font-bold mb-6">Analytics</h1>
|
| 102 |
+
<p>Total Requests: <span id="totalRequests">0</span></p>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<!-- LOGS -->
|
| 106 |
+
<div id="logsPage" class="hidden">
|
| 107 |
+
<h1 class="text-3xl font-bold mb-6">Logs</h1>
|
| 108 |
+
<p>Logs saved in backend/logs</p>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<!-- ADMIN -->
|
| 112 |
+
<div id="adminPage" class="hidden">
|
| 113 |
+
<h1 class="text-3xl font-bold mb-6">Admin Control</h1>
|
| 114 |
+
<p>External AI enabled</p>
|
| 115 |
+
</div>
|
| 116 |
+
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<script src="/static/js/app.js"></script>
|
| 121 |
+
</body>
|
| 122 |
+
</html>
|
templates/index.html
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>GenAI VQA System</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css?v=3">
|
| 8 |
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 10 |
+
</head>
|
| 11 |
+
<body class="dark-theme">
|
| 12 |
+
|
| 13 |
+
<!-- Login Overlay -->
|
| 14 |
+
<div id="login-overlay" class="login-overlay active">
|
| 15 |
+
<div class="login-box glass-panel fade-in">
|
| 16 |
+
<i class="fa-solid fa-brain" style="font-size: 50px; color: var(--accent); margin-bottom: 24px;"></i>
|
| 17 |
+
<h2>GenAI VQA Portal</h2>
|
| 18 |
+
<p style="margin-bottom: 24px; color: var(--text-secondary);">Enter your username to access the system</p>
|
| 19 |
+
<input type="text" id="login-input" placeholder="Username (e.g. vignesh)" autocomplete="off">
|
| 20 |
+
<button id="login-btn" class="primary-btn" style="width: 100%;">Enter System <i class="fa-solid fa-arrow-right"></i></button>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<!-- Image Zoom Modal -->
|
| 25 |
+
<div id="image-modal" class="modal hidden">
|
| 26 |
+
<span class="close-modal" id="close-modal">×</span>
|
| 27 |
+
<img class="modal-content" id="modal-img">
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div class="app-container hidden" id="main-app">
|
| 31 |
+
<!-- Mobile Header -->
|
| 32 |
+
<div class="mobile-header">
|
| 33 |
+
<div class="logo">
|
| 34 |
+
<i class="fa-solid fa-brain"></i>
|
| 35 |
+
<h2>GenAI VQA</h2>
|
| 36 |
+
</div>
|
| 37 |
+
<button id="mobile-menu-btn" class="icon-btn"><i class="fa-solid fa-bars"></i></button>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<!-- Sidebar -->
|
| 41 |
+
<aside class="sidebar" id="sidebar">
|
| 42 |
+
<div class="logo">
|
| 43 |
+
<i class="fa-solid fa-brain"></i>
|
| 44 |
+
<h2>GenAI VQA</h2>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<nav class="menu">
|
| 48 |
+
<button class="menu-btn active" data-tab="dashboard">
|
| 49 |
+
<i class="fa-solid fa-gauge"></i> Dashboard
|
| 50 |
+
</button>
|
| 51 |
+
<button class="menu-btn" data-tab="analytics">
|
| 52 |
+
<i class="fa-solid fa-chart-line"></i> Analytics
|
| 53 |
+
</button>
|
| 54 |
+
<button class="menu-btn" data-tab="logs">
|
| 55 |
+
<i class="fa-solid fa-list"></i> Logs
|
| 56 |
+
</button>
|
| 57 |
+
<button class="menu-btn" data-tab="admin" id="admin-tab-btn" style="display: none;">
|
| 58 |
+
<i class="fa-solid fa-shield-halved"></i> Admin
|
| 59 |
+
</button>
|
| 60 |
+
</nav>
|
| 61 |
+
|
| 62 |
+
<div class="user-profile">
|
| 63 |
+
<div class="user-info">
|
| 64 |
+
<i class="fa-solid fa-user-circle"></i>
|
| 65 |
+
<span id="display-username" style="font-weight: 600;">guest</span>
|
| 66 |
+
<button id="logout-btn" class="icon-btn" style="margin-left: auto; width: 32px; height: 32px; text-align: center;" title="Logout"><i class="fa-solid fa-right-from-bracket"></i></button>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
</aside>
|
| 70 |
+
|
| 71 |
+
<!-- Main Content -->
|
| 72 |
+
<main class="main-content">
|
| 73 |
+
<!-- Dashboard Tab -->
|
| 74 |
+
<section id="dashboard" class="tab-content active">
|
| 75 |
+
<header>
|
| 76 |
+
<h1>Vision Question Answering</h1>
|
| 77 |
+
<p>Upload an image and ask any question to our AI models</p>
|
| 78 |
+
</header>
|
| 79 |
+
|
| 80 |
+
<div class="vqa-container">
|
| 81 |
+
<!-- Left Column: Input -->
|
| 82 |
+
<div class="input-section glass-panel">
|
| 83 |
+
<div class="upload-area" id="drop-zone" title="Click to upload. If image exists, click it to zoom.">
|
| 84 |
+
<input type="file" id="image-upload" accept="image/*" hidden>
|
| 85 |
+
<div class="upload-placeholder" id="upload-placeholder">
|
| 86 |
+
<i class="fa-solid fa-cloud-arrow-up"></i>
|
| 87 |
+
<p>Drag & Drop or Click to Upload Image</p>
|
| 88 |
+
</div>
|
| 89 |
+
<img id="image-preview" class="hidden" alt="Preview" style="cursor: zoom-in;">
|
| 90 |
+
<button id="clear-image-btn" class="hidden" title="Clear Image" style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.6); color: white; border: none; border-radius: 50%; width: 32px; height: 32px; cursor: pointer; z-index: 10; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); transition: background 0.2s;"><i class="fa-solid fa-xmark"></i></button>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div class="controls">
|
| 94 |
+
<div style="display: flex; gap: 16px;">
|
| 95 |
+
<div class="control-group" style="flex: 1;">
|
| 96 |
+
<label><i class="fa-solid fa-microchip"></i> Select Model</label>
|
| 97 |
+
<select id="model-selector">
|
| 98 |
+
<option value="local">BLIP + FLAN (Local)</option>
|
| 99 |
+
<option value="gemini" id="gemini-option">Gemini 3.1 Flash Vision (High Quota)</option>
|
| 100 |
+
</select>
|
| 101 |
+
</div>
|
| 102 |
+
<div class="control-group" style="flex: 1;">
|
| 103 |
+
<label><i class="fa-solid fa-language"></i> Language</label>
|
| 104 |
+
<select id="lang-selector">
|
| 105 |
+
<option value="en">English</option>
|
| 106 |
+
<option value="ta">Tamil (தமிழ்)</option>
|
| 107 |
+
<option value="te">Telugu (తెలుగు)</option>
|
| 108 |
+
<option value="hi">Hindi (हिन्दी)</option>
|
| 109 |
+
<option value="es">Spanish (Español)</option>
|
| 110 |
+
<option value="fr">French (Français)</option>
|
| 111 |
+
<option value="zh-cn">Chinese (中文)</option>
|
| 112 |
+
</select>
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<div class="control-group">
|
| 117 |
+
<label><i class="fa-solid fa-circle-question"></i> Your Question</label>
|
| 118 |
+
<div class="input-wrapper">
|
| 119 |
+
<input type="text" id="question-input" placeholder="Type your question here...">
|
| 120 |
+
<button id="mic-btn" class="icon-btn" title="Click to speak"><i class="fa-solid fa-microphone"></i></button>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<button id="ask-btn" class="primary-btn">
|
| 125 |
+
<i class="fa-solid fa-wand-magic-sparkles"></i> Ask AI
|
| 126 |
+
</button>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<!-- Right Column: Output -->
|
| 131 |
+
<div class="output-section">
|
| 132 |
+
<!-- Welcome / Suggestions State -->
|
| 133 |
+
<div id="welcome-state" class="glass-panel" style="padding: 30px; text-align: center;">
|
| 134 |
+
<i class="fa-solid fa-wand-magic-sparkles" style="font-size: 44px; color: var(--accent); margin-bottom: 20px;"></i>
|
| 135 |
+
<h2 style="margin-bottom: 12px; font-weight: 600;">AI Vision Explorer</h2>
|
| 136 |
+
<p style="color: var(--text-secondary); margin-bottom: 30px; font-size: 15px;">Upload an image, select your language, and ask anything. Or, try one of the prompts below:</p>
|
| 137 |
+
|
| 138 |
+
<div class="suggestions-grid">
|
| 139 |
+
<button class="sug-card">
|
| 140 |
+
<i class="fa-solid fa-magnifying-glass" style="color: #10b981;"></i>
|
| 141 |
+
<span>What is happening in this image?</span>
|
| 142 |
+
</button>
|
| 143 |
+
<button class="sug-card">
|
| 144 |
+
<i class="fa-solid fa-language" style="color: #3b82f6;"></i>
|
| 145 |
+
<span>Translate any text in the image.</span>
|
| 146 |
+
</button>
|
| 147 |
+
<button class="sug-card">
|
| 148 |
+
<i class="fa-solid fa-face-smile" style="color: #f59e0b;"></i>
|
| 149 |
+
<span>Describe the overall theme and mood.</span>
|
| 150 |
+
</button>
|
| 151 |
+
<button class="sug-card">
|
| 152 |
+
<i class="fa-solid fa-shapes" style="color: #8b5cf6;"></i>
|
| 153 |
+
<span>Identify all objects and people.</span>
|
| 154 |
+
</button>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<!-- Loading State -->
|
| 159 |
+
<div id="loading-indicator" class="hidden glass-panel">
|
| 160 |
+
<div class="spinner"></div>
|
| 161 |
+
<p>AI is analyzing the image...</p>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<!-- Results -->
|
| 165 |
+
<div id="results-container" class="hidden">
|
| 166 |
+
<div class="result-card glass-panel fade-in">
|
| 167 |
+
<div class="card-header">
|
| 168 |
+
<h3><i class="fa-solid fa-quote-left"></i> Caption</h3>
|
| 169 |
+
<button class="tts-btn" data-target="out-caption" title="Read Aloud"><i class="fa-solid fa-volume-up"></i></button>
|
| 170 |
+
</div>
|
| 171 |
+
<div id="out-caption" class="typewriter-container"></div>
|
| 172 |
+
</div>
|
| 173 |
+
|
| 174 |
+
<div class="result-card glass-panel fade-in delay-1">
|
| 175 |
+
<div class="card-header">
|
| 176 |
+
<h3><i class="fa-solid fa-check-double"></i> Final Answer</h3>
|
| 177 |
+
<button class="tts-btn" data-target="out-answer" title="Read Aloud"><i class="fa-solid fa-volume-up"></i></button>
|
| 178 |
+
</div>
|
| 179 |
+
<div id="out-answer" class="typewriter-container"></div>
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
<div class="result-card glass-panel fade-in delay-2">
|
| 183 |
+
<div class="card-header">
|
| 184 |
+
<h3><i class="fa-solid fa-lightbulb"></i> Explanation</h3>
|
| 185 |
+
<button class="tts-btn" data-target="out-explanation" title="Read Aloud"><i class="fa-solid fa-volume-up"></i></button>
|
| 186 |
+
</div>
|
| 187 |
+
<div id="out-explanation" class="typewriter-container"></div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
</section>
|
| 193 |
+
|
| 194 |
+
<!-- Analytics Tab -->
|
| 195 |
+
<section id="analytics" class="tab-content" style="display: none;">
|
| 196 |
+
<header>
|
| 197 |
+
<h1 id="analytics-title">Analytics Dashboard</h1>
|
| 198 |
+
<p>System usage and model statistics</p>
|
| 199 |
+
</header>
|
| 200 |
+
<div class="stats-grid">
|
| 201 |
+
<div class="stat-card glass-panel">
|
| 202 |
+
<h3>Total Queries</h3>
|
| 203 |
+
<div class="stat-value" id="stat-total">0</div>
|
| 204 |
+
</div>
|
| 205 |
+
<div class="stat-card glass-panel">
|
| 206 |
+
<h3>Models Used</h3>
|
| 207 |
+
<div class="stat-value" id="stat-models">0/0</div>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
<div class="chart-container glass-panel">
|
| 211 |
+
<canvas id="modelChart"></canvas>
|
| 212 |
+
</div>
|
| 213 |
+
</section>
|
| 214 |
+
|
| 215 |
+
<!-- Logs Tab -->
|
| 216 |
+
<section id="logs" class="tab-content" style="display: none;">
|
| 217 |
+
<header>
|
| 218 |
+
<h1 id="logs-title">System Logs</h1>
|
| 219 |
+
<p>Recent API requests and activity</p>
|
| 220 |
+
</header>
|
| 221 |
+
<div class="filter-bar">
|
| 222 |
+
<input type="text" id="log-search" placeholder="Search logs...">
|
| 223 |
+
<button id="refresh-logs-btn" class="icon-btn" style="width: auto; padding: 0 15px; margin-left: 10px;"><i class="fa-solid fa-rotate-right"></i> Refresh</button>
|
| 224 |
+
</div>
|
| 225 |
+
<div class="glass-panel table-container">
|
| 226 |
+
<table class="logs-table">
|
| 227 |
+
<thead>
|
| 228 |
+
<tr>
|
| 229 |
+
<th>Timestamp</th>
|
| 230 |
+
<th>User</th>
|
| 231 |
+
<th>Model</th>
|
| 232 |
+
<th>Question</th>
|
| 233 |
+
</tr>
|
| 234 |
+
</thead>
|
| 235 |
+
<tbody id="logs-body">
|
| 236 |
+
<!-- Logs injected via JS -->
|
| 237 |
+
</tbody>
|
| 238 |
+
</table>
|
| 239 |
+
</div>
|
| 240 |
+
</section>
|
| 241 |
+
|
| 242 |
+
<!-- Admin Tab -->
|
| 243 |
+
<section id="admin" class="tab-content" style="display: none;">
|
| 244 |
+
<header>
|
| 245 |
+
<h1>Admin Control Panel</h1>
|
| 246 |
+
<p>System configuration and overrides</p>
|
| 247 |
+
</header>
|
| 248 |
+
<div class="admin-grid">
|
| 249 |
+
<div class="admin-card glass-panel">
|
| 250 |
+
<h3><i class="fa-solid fa-cloud"></i> AI Models</h3>
|
| 251 |
+
<div class="setting-row">
|
| 252 |
+
<span>Enable Gemini Vision AI</span>
|
| 253 |
+
<label class="switch">
|
| 254 |
+
<input type="checkbox" id="toggle-gemini" checked>
|
| 255 |
+
<span class="slider round"></span>
|
| 256 |
+
</label>
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
</section>
|
| 261 |
+
</main>
|
| 262 |
+
</div>
|
| 263 |
+
|
| 264 |
+
<script src="/static/app.js?v=3"></script>
|
| 265 |
+
</body>
|
| 266 |
+
</html>
|
test.py
ADDED
|
Binary file (44 Bytes). View file
|
|
|