VigneshVS2005 commited on
Commit
2c5cb17
·
0 Parent(s):

Initial commit of GenAI VQA System

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .ipynb_checkpoints/ai_router-checkpoint.py +14 -0
  2. .ipynb_checkpoints/config-checkpoint.py +15 -0
  3. .ipynb_checkpoints/main-checkpoint.py +73 -0
  4. .ipynb_checkpoints/test-checkpoint.py +8 -0
  5. Dockerfile +17 -0
  6. README.md +11 -0
  7. __pycache__/ai_router.cpython-310.pyc +0 -0
  8. __pycache__/ai_router.cpython-311.pyc +0 -0
  9. __pycache__/ai_router.cpython-313.pyc +0 -0
  10. __pycache__/config.cpython-310.pyc +0 -0
  11. __pycache__/config.cpython-311.pyc +0 -0
  12. __pycache__/config.cpython-313.pyc +0 -0
  13. __pycache__/main.cpython-310.pyc +0 -0
  14. __pycache__/main.cpython-311.pyc +0 -0
  15. __pycache__/main.cpython-313.pyc +0 -0
  16. ai_router.py +37 -0
  17. backend_for_huggingface.zip +0 -0
  18. config.py +15 -0
  19. genai_vqa_full_project.tar.gz +0 -0
  20. logs/.ipynb_checkpoints/main-checkpoint.py +78 -0
  21. logs/.ipynb_checkpoints/requests-checkpoint.log +55 -0
  22. logs/main.py +78 -0
  23. logs/requests.log +199 -0
  24. main.py +116 -0
  25. models/.ipynb_checkpoints/blip_model-checkpoint.py +28 -0
  26. models/.ipynb_checkpoints/gemini_vision-checkpoint.py +74 -0
  27. models/.ipynb_checkpoints/reasoning_model-checkpoint.py +39 -0
  28. models/__pycache__/blip_model.cpython-310.pyc +0 -0
  29. models/__pycache__/blip_model.cpython-311.pyc +0 -0
  30. models/__pycache__/blip_model.cpython-313.pyc +0 -0
  31. models/__pycache__/external_model.cpython-310.pyc +0 -0
  32. models/__pycache__/gemini_vision.cpython-310.pyc +0 -0
  33. models/__pycache__/gemini_vision.cpython-311.pyc +0 -0
  34. models/__pycache__/gemini_vision.cpython-313.pyc +0 -0
  35. models/__pycache__/reasoning_model.cpython-310.pyc +0 -0
  36. models/__pycache__/reasoning_model.cpython-311.pyc +0 -0
  37. models/__pycache__/reasoning_model.cpython-313.pyc +0 -0
  38. models/blip_model.py +30 -0
  39. models/gemini_vision.py +68 -0
  40. models/reasoning_model.py +39 -0
  41. requirements.txt +8 -0
  42. static/app.js +429 -0
  43. static/css/.ipynb_checkpoints/style-checkpoint.css +14 -0
  44. static/css/style.css +14 -0
  45. static/js/.ipynb_checkpoints/app-checkpoint.js +91 -0
  46. static/js/app.js +91 -0
  47. static/style.css +611 -0
  48. templates/.ipynb_checkpoints/index-checkpoint.html +122 -0
  49. templates/index.html +266 -0
  50. 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">&times;</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