Added Mobile Usability Module
Browse files- .dockerignore +11 -0
- Dockerfile +1 -9
- app/main.py +3 -1
- app/mobile_usability/models.py +24 -0
- app/mobile_usability/prompts.py +128 -0
- app/mobile_usability/routes.py +29 -0
- app/mobile_usability/service.py +98 -0
- app/page_speed/config.py +2 -3
- app/rag/db.py +2 -1
- app/rag/embeddings.py +25 -8
- app/rag/prompt_library.py +22 -0
- app/rag/routes.py +2 -2
- app/rag/utils.py +4 -1
- requirements.txt +0 -3
.dockerignore
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv
|
| 2 |
+
__pycache__
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
.env
|
| 8 |
+
.git
|
| 9 |
+
.gitignore
|
| 10 |
+
Dockerfile
|
| 11 |
+
.dockerignore
|
Dockerfile
CHANGED
|
@@ -4,14 +4,6 @@ FROM python:3.11-slim
|
|
| 4 |
# Prevent Python from buffering stdout/stderr (so logs appear immediately)
|
| 5 |
ENV PYTHONUNBUFFERED=1
|
| 6 |
|
| 7 |
-
# Install system dependencies needed for certain Python packages (e.g., faiss, PyTorch CPU wheels)
|
| 8 |
-
RUN apt-get update && \
|
| 9 |
-
apt-get install -y --no-install-recommends \
|
| 10 |
-
build-essential \
|
| 11 |
-
git \
|
| 12 |
-
libgomp1 \
|
| 13 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
-
|
| 15 |
# Set the working directory inside the container
|
| 16 |
WORKDIR /app
|
| 17 |
|
|
@@ -29,4 +21,4 @@ COPY . .
|
|
| 29 |
EXPOSE 8080
|
| 30 |
|
| 31 |
# Run the app on port 8080 to match Cloud Run's requirement
|
| 32 |
-
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
|
|
|
| 4 |
# Prevent Python from buffering stdout/stderr (so logs appear immediately)
|
| 5 |
ENV PYTHONUNBUFFERED=1
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# Set the working directory inside the container
|
| 8 |
WORKDIR /app
|
| 9 |
|
|
|
|
| 21 |
EXPOSE 8080
|
| 22 |
|
| 23 |
# Run the app on port 8080 to match Cloud Run's requirement
|
| 24 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
app/main.py
CHANGED
|
@@ -18,7 +18,7 @@ from app.page_speed import routes as page_speed_routes
|
|
| 18 |
from app.content_relevence import routes as content_relevance_routes
|
| 19 |
from app.keywords.routes import router as keywords_router
|
| 20 |
from app.uiux import routes as uiux_routes
|
| 21 |
-
|
| 22 |
# app/suppress_warnings.py
|
| 23 |
|
| 24 |
import warnings
|
|
@@ -93,6 +93,8 @@ app.include_router(keywords_router)
|
|
| 93 |
# Mount UI/UX router
|
| 94 |
app.include_router(uiux_routes.router)
|
| 95 |
|
|
|
|
|
|
|
| 96 |
# Add CORS middleware
|
| 97 |
app.add_middleware(
|
| 98 |
CORSMiddleware,
|
|
|
|
| 18 |
from app.content_relevence import routes as content_relevance_routes
|
| 19 |
from app.keywords.routes import router as keywords_router
|
| 20 |
from app.uiux import routes as uiux_routes
|
| 21 |
+
from app.mobile_usability import routes as mobile_usability
|
| 22 |
# app/suppress_warnings.py
|
| 23 |
|
| 24 |
import warnings
|
|
|
|
| 93 |
# Mount UI/UX router
|
| 94 |
app.include_router(uiux_routes.router)
|
| 95 |
|
| 96 |
+
app.include_router(mobile_usability.router)
|
| 97 |
+
|
| 98 |
# Add CORS middleware
|
| 99 |
app.add_middleware(
|
| 100 |
CORSMiddleware,
|
app/mobile_usability/models.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/mobile_usability/models.py
|
| 2 |
+
from pydantic import BaseModel, Field
|
| 3 |
+
from typing import Any, Dict, List
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class MobileUsabilityRequest(BaseModel):
|
| 7 |
+
"""
|
| 8 |
+
Payload for incoming Mobile Usability data.
|
| 9 |
+
"""
|
| 10 |
+
mobile_data: Dict[str, Any]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class PrioritySuggestions(BaseModel):
|
| 14 |
+
"""Categorized suggestions by effort level for mobile usability."""
|
| 15 |
+
high: List[str] = Field(..., description="High-effort suggestion strings.")
|
| 16 |
+
medium: List[str] = Field(..., description="Medium-effort suggestion strings.")
|
| 17 |
+
low: List[str] = Field(..., description="Low-effort suggestion strings.")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Recommendation(BaseModel):
|
| 21 |
+
"""Wrapper for prioritized suggestions returned by the LLM parser."""
|
| 22 |
+
priority_suggestions: PrioritySuggestions = Field(
|
| 23 |
+
..., description="All suggestions categorized by effort level."
|
| 24 |
+
)
|
app/mobile_usability/prompts.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/mobile_usability/prompts.py
|
| 2 |
+
|
| 3 |
+
class MobilePrompts:
|
| 4 |
+
"""
|
| 5 |
+
Prompt templates for mobile usability analysis (improved).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
# This system prompt is used to generate the structured priority suggestions JSON
|
| 9 |
+
SYSTEM_PROMPT = """
|
| 10 |
+
You are an **Expert Mobile Usability Analyst** and an authoritative accessibility specialist.
|
| 11 |
+
|
| 12 |
+
Task:
|
| 13 |
+
Analyze the provided mobile usability audit (multi-line report) and extract a short,
|
| 14 |
+
prioritized list of remediation suggestions. **Return only a JSON object** with a single
|
| 15 |
+
top-level key `priority_suggestions` whose value is an object containing exactly three lists:
|
| 16 |
+
- "high"
|
| 17 |
+
- "medium"
|
| 18 |
+
- "low"
|
| 19 |
+
|
| 20 |
+
Requirements for each list item (each item is a single string):
|
| 21 |
+
- Begin with a concise category tag in square brackets (e.g. [Viewport], [Tap Targets], [Font], [Layout], [Accessibility]).
|
| 22 |
+
- Include a one-sentence remediation recommendation (plain English).
|
| 23 |
+
- End with the parenthetical suffix `(Effort Level: high|medium|low; Est: Xm)` where `Xm` is an estimated time in minutes (approx).
|
| 24 |
+
- Optionally include a short rationale phrase before the parenthetical suffix.
|
| 25 |
+
|
| 26 |
+
Example list item:
|
| 27 |
+
"[Font] Fix CSS rule that sets font-size: 0px for social links; set readable font-size and ARIA labels (Effort Level: high; Est: 30m)"
|
| 28 |
+
|
| 29 |
+
Formatting rules:
|
| 30 |
+
- Return **only** the JSON object (no commentary, no surrounding text).
|
| 31 |
+
- Each list may contain zero or more items, but critical items must appear in "high".
|
| 32 |
+
- Ensure items are specific enough for a developer to action (mention affected selector(s) when possible).
|
| 33 |
+
|
| 34 |
+
{format_instructions}
|
| 35 |
+
|
| 36 |
+
Use the following to guide prioritization:
|
| 37 |
+
- **High** = content invisible (0px), severe accessibility issues, or site-breaking layout on mobile.
|
| 38 |
+
- **Medium** = important usability issues (many small tap targets, repeated spacing problems).
|
| 39 |
+
- **Low** = cosmetic or easily reversible issues (minor font-size tweaks, single button padding).
|
| 40 |
+
|
| 41 |
+
Mobile Usability Report (multi-line):
|
| 42 |
+
{report}
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
# This prompt is used to generate the human-readable multi-line audit report (the main report)
|
| 46 |
+
Report_PROMPT = """
|
| 47 |
+
You are an **Expert Mobile Usability Consultant** and must produce a clear, technical, and actionable mobile usability audit.
|
| 48 |
+
|
| 49 |
+
Output rules (must be followed exactly):
|
| 50 |
+
- Return a **multi-line string** (NOT JSON).
|
| 51 |
+
- The report **must begin** with a line containing only three dashes `---` and **end** with a line containing only three dashes `---`.
|
| 52 |
+
- Do **not** include any other text before the starting `---` or after the ending `---`.
|
| 53 |
+
- Do not provide any meta commentary, step-by-step generation notes, or extraneous text β only the report content.
|
| 54 |
+
|
| 55 |
+
Structure the report with the following sections and content (in this exact order):
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
**Overall Summary**
|
| 60 |
+
- Usability Score: (0β100) β echo the numeric score.
|
| 61 |
+
- Grade: A/B/C/D/F β map score to grade using: 90β100=A, 80β89=B, 70β79=C, 60β69=D, <60=F.
|
| 62 |
+
- Top Strengths: list the top 3 strengths (1 line each).
|
| 63 |
+
- Top Issues: list the top 3 issues (1 line each), and **tag** each issue with its severity (Critical / High / Medium / Low).
|
| 64 |
+
|
| 65 |
+
Include an immediate "Quick wins (actionable now)" sub-list containing 1β3 items that a developer can fix in β€30 minutes.
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
**Metric Breakdown**
|
| 70 |
+
For each available metric (missingViewport, horizontalScroll, smallTapTargets, fontSizeReadability, and any others present in the JSON), include a subsection with these fields:
|
| 71 |
+
|
| 72 |
+
- Metric Name:
|
| 73 |
+
- Value: (echo input value; for objects, provide a short summary and include counts: e.g., "13 flagged elements")
|
| 74 |
+
- Status: good / needs improvement / critical (pick one)
|
| 75 |
+
- Why It Matters: 1β2 concise sentences
|
| 76 |
+
- Concrete Recommendation: 1β2 sentences explaining the fix
|
| 77 |
+
- Affected Selectors: list up to 10 CSS selectors from the input (if any). If selectors are not available, say "Selectors not provided."
|
| 78 |
+
- Sample Fix (when applicable): show a minimal copy-pastable snippet (CSS or HTML) that addresses the issue β keep snippets β€8 lines.
|
| 79 |
+
- Acceptance Criteria: one or two measurable pass/fail checks (e.g., "smallTapTargets score >= 90", "no elements with computed font-size: 0px", "no horizontal scroll in Chrome device emulation at viewport widths 360β412px").
|
| 80 |
+
- Verification Steps: concise commands or DevTools steps to confirm the fix (e.g., Lighthouse CLI: `lighthouse --only-categories=best-practices,accessibility --throttling-method=devtools https://example.com` or DevTools computed style checks).
|
| 81 |
+
|
| 82 |
+
Keep each metric subsection compact and developer-focused.
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
**Action Plan (Priority + Effort + ETA)**
|
| 87 |
+
List the top 5 issues in prioritized order. For each item include:
|
| 88 |
+
- Issue Title (Severity)
|
| 89 |
+
- Root cause (one line)
|
| 90 |
+
- Fix (one-line summary)
|
| 91 |
+
- Code example (if applicable) β β€6 lines
|
| 92 |
+
- Effort Level: low / medium / high
|
| 93 |
+
- Estimated Time: Xm (minutes)
|
| 94 |
+
- Acceptance Criteria: (clear measurable outcome)
|
| 95 |
+
- Verification: (one short command or DevTools action)
|
| 96 |
+
|
| 97 |
+
Ensure at least one item in **High** priority if any content is invisible (0px) or there is a major accessibility problem.
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
|
| 101 |
+
**Selector-level Appendix**
|
| 102 |
+
- Provide a small table/listing of flagged selectors and a one-line note each (e.g., `#e-n-tab-title-31374311 β increase min-height to 48px`).
|
| 103 |
+
- If the input includes many selectors, list the first 20 and summarize the rest as "N more selectors".
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
**Monitoring & Regression Tests**
|
| 108 |
+
- Frequency recommendation based on severity (weekly vs monthly).
|
| 109 |
+
- Automated checks to add to CI (1β3 bullet suggestions, include sample Lighthouse CLI command).
|
| 110 |
+
- Acceptance gates to include in PRs (e.g., "no elements with computed font-size: 0px", "smallTapTargets score >= 90").
|
| 111 |
+
|
| 112 |
+
---
|
| 113 |
+
|
| 114 |
+
**Scoring & Grade Explanation**
|
| 115 |
+
- Explain briefly how the grade was determined (one short paragraph).
|
| 116 |
+
- Provide numeric thresholds used to mark items "critical", "needs improvement", or "good".
|
| 117 |
+
|
| 118 |
+
---
|
| 119 |
+
|
| 120 |
+
Additional guidance:
|
| 121 |
+
- Use explicit, actionable language aimed at a frontend developer.
|
| 122 |
+
- When recommending CSS fixes, prefer modern and resilient approaches (use `min-height`, `padding`, relative font units, and accessible labels).
|
| 123 |
+
- For accessibility issues (e.g., invisible content), always mark severity as Critical/High and include an accessibility rationale.
|
| 124 |
+
- Keep the report concise overall (try to keep it under ~700β900 words for normal-sized inputs), but do include code snippets and selectors as required.
|
| 125 |
+
|
| 126 |
+
Mobile usability data (JSON):
|
| 127 |
+
{mobile_data}
|
| 128 |
+
"""
|
app/mobile_usability/routes.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/mobile_usability/routes.py
|
| 2 |
+
from fastapi import APIRouter, HTTPException
|
| 3 |
+
from app.mobile_usability.service import MobileUsabilityService
|
| 4 |
+
from app.mobile_usability.models import MobileUsabilityRequest
|
| 5 |
+
|
| 6 |
+
router = APIRouter(prefix="/mobile_usability", tags=["MobileUsability"])
|
| 7 |
+
|
| 8 |
+
service = MobileUsabilityService()
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@router.post("/generate-full-report")
|
| 12 |
+
def generate_full_mobile_analysis(request: MobileUsabilityRequest):
|
| 13 |
+
"""
|
| 14 |
+
Generate full Mobile Usability analysis using Gemini: report + prioritized suggestions.
|
| 15 |
+
"""
|
| 16 |
+
try:
|
| 17 |
+
# 1) Generate report (string) via LLM
|
| 18 |
+
report = service.generate_mobile_report(request.mobile_data)
|
| 19 |
+
|
| 20 |
+
# 2) Generate prioritized suggestions via LLM (Pydantic parser)
|
| 21 |
+
priority_suggestions = service.generate_mobile_priority(report)
|
| 22 |
+
|
| 23 |
+
return {
|
| 24 |
+
"success": True,
|
| 25 |
+
"report": report,
|
| 26 |
+
"priority_suggestions": priority_suggestions
|
| 27 |
+
}
|
| 28 |
+
except Exception as e:
|
| 29 |
+
raise HTTPException(status_code=500, detail=str(e))
|
app/mobile_usability/service.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/mobile_usability/service.py
|
| 2 |
+
import os
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Dict, Any
|
| 5 |
+
|
| 6 |
+
# Optional project settings import (falls back to env var)
|
| 7 |
+
try:
|
| 8 |
+
from app.page_speed.config import settings # if present in your project
|
| 9 |
+
GEMINI_KEY = getattr(settings, "gemini_api_key", None)
|
| 10 |
+
except Exception:
|
| 11 |
+
GEMINI_KEY = None
|
| 12 |
+
|
| 13 |
+
from app.mobile_usability.models import Recommendation, PrioritySuggestions
|
| 14 |
+
from app.mobile_usability.prompts import MobilePrompts
|
| 15 |
+
|
| 16 |
+
# LangChain / Gemini wrapper imports (same style as your SEO module)
|
| 17 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 18 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 19 |
+
from langchain_core.output_parsers import PydanticOutputParser
|
| 20 |
+
|
| 21 |
+
glogger = logging.getLogger(__name__)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class MobileUsabilityService:
|
| 25 |
+
"""
|
| 26 |
+
LLM-only service for generating mobile usability report and prioritized suggestions.
|
| 27 |
+
This class requires a Gemini API key to be available (env var or settings).
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self):
|
| 31 |
+
# require Gemini key
|
| 32 |
+
key = GEMINI_KEY or os.getenv("GEMINI_API_KEY")
|
| 33 |
+
if not key:
|
| 34 |
+
msg = "Gemini API key not configured. Set settings.gemini_api_key or GEMINI_API_KEY env var."
|
| 35 |
+
glogger.error(msg)
|
| 36 |
+
raise RuntimeError(msg)
|
| 37 |
+
|
| 38 |
+
self.gemini_api_key = key
|
| 39 |
+
|
| 40 |
+
# initialize LLM wrapper (Gemini)
|
| 41 |
+
self.llm = ChatGoogleGenerativeAI(
|
| 42 |
+
model="gemini-2.5-flash",
|
| 43 |
+
temperature=0,
|
| 44 |
+
max_tokens=None,
|
| 45 |
+
timeout=None,
|
| 46 |
+
max_retries=3,
|
| 47 |
+
api_key=self.gemini_api_key
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
# Prompt template for generating the multi-line mobile usability report
|
| 51 |
+
self.report_prompt = ChatPromptTemplate.from_messages([
|
| 52 |
+
("system", MobilePrompts.Report_PROMPT),
|
| 53 |
+
("human", "Please generate a comprehensive mobile usability audit based on this data:\n\n{mobile_data}")
|
| 54 |
+
])
|
| 55 |
+
|
| 56 |
+
# Parser + priority prompt for structured priority suggestions
|
| 57 |
+
self.parser = PydanticOutputParser(pydantic_object=Recommendation)
|
| 58 |
+
self.priority_chain = (
|
| 59 |
+
ChatPromptTemplate.from_messages([
|
| 60 |
+
("system", MobilePrompts.SYSTEM_PROMPT),
|
| 61 |
+
("human", "{report}")
|
| 62 |
+
]).partial(format_instructions=self.parser.get_format_instructions())
|
| 63 |
+
| self.llm
|
| 64 |
+
| self.parser
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def generate_mobile_report(self, mobile_data: Dict[str, Any]) -> str:
|
| 68 |
+
"""
|
| 69 |
+
Generate a mobile usability audit report using Gemini LLM (raw multi-line string).
|
| 70 |
+
Raises on failure.
|
| 71 |
+
"""
|
| 72 |
+
glogger.info("Invoking Gemini to generate mobile usability report.")
|
| 73 |
+
prompt_input = {"mobile_data": mobile_data}
|
| 74 |
+
try:
|
| 75 |
+
# Use the prompt template piped into the LLM (same pattern as your SEO module)
|
| 76 |
+
prompt_with_llm = self.report_prompt | self.llm
|
| 77 |
+
response = prompt_with_llm.invoke(prompt_input)
|
| 78 |
+
if not response:
|
| 79 |
+
raise RuntimeError("Empty response from Gemini for mobile usability report.")
|
| 80 |
+
# response is typically an object with .content in your setup; keep same handling
|
| 81 |
+
return getattr(response, "content", str(response)).strip()
|
| 82 |
+
except Exception as e:
|
| 83 |
+
glogger.error("Error generating mobile report via Gemini: %s", e, exc_info=True)
|
| 84 |
+
raise
|
| 85 |
+
|
| 86 |
+
def generate_mobile_priority(self, report: str) -> PrioritySuggestions:
|
| 87 |
+
"""
|
| 88 |
+
Generate prioritized mobile-usability suggestions using LLM + PydanticOutputParser.
|
| 89 |
+
Returns PrioritySuggestions Pydantic model instance.
|
| 90 |
+
"""
|
| 91 |
+
glogger.info("Invoking Gemini to generate priority suggestions.")
|
| 92 |
+
try:
|
| 93 |
+
rec: Recommendation = self.priority_chain.invoke({"report": report})
|
| 94 |
+
# `rec` is a Pydantic model (Recommendation); return the nested PrioritySuggestions
|
| 95 |
+
return rec.priority_suggestions
|
| 96 |
+
except Exception as e:
|
| 97 |
+
glogger.error("Error generating priorities via Gemini: %s", e, exc_info=True)
|
| 98 |
+
raise
|
app/page_speed/config.py
CHANGED
|
@@ -11,7 +11,6 @@ class Settings(BaseSettings):
|
|
| 11 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
pagespeed_api_key: str
|
| 13 |
gemini_api_key: str
|
| 14 |
-
google_api_key1: str
|
| 15 |
|
| 16 |
|
| 17 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -39,13 +38,13 @@ class Settings(BaseSettings):
|
|
| 39 |
pw = quote_plus(self.mongo_password)
|
| 40 |
return (
|
| 41 |
f"mongodb+srv://{self.mongo_user}:{pw}@{self.mongo_host}/"
|
| 42 |
-
f"{self.mongo_db}?retryWrites=true&w=majority"
|
| 43 |
)
|
| 44 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 45 |
# FastAPI Server Configuration
|
| 46 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 47 |
host: str = "0.0.0.0"
|
| 48 |
-
port: int =
|
| 49 |
debug: bool = False
|
| 50 |
|
| 51 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 11 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
pagespeed_api_key: str
|
| 13 |
gemini_api_key: str
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 38 |
pw = quote_plus(self.mongo_password)
|
| 39 |
return (
|
| 40 |
f"mongodb+srv://{self.mongo_user}:{pw}@{self.mongo_host}/"
|
| 41 |
+
f"{self.mongo_db}?retryWrites=true&w=majority&ssl=true"
|
| 42 |
)
|
| 43 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 44 |
# FastAPI Server Configuration
|
| 45 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 46 |
host: str = "0.0.0.0"
|
| 47 |
+
port: int = os.getenv("port")
|
| 48 |
debug: bool = False
|
| 49 |
|
| 50 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
app/rag/db.py
CHANGED
|
@@ -7,7 +7,8 @@ from app.page_speed.config import settings
|
|
| 7 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 8 |
|
| 9 |
# Connect to MongoDB using the URI from settings
|
| 10 |
-
mongo_client = MongoClient(
|
|
|
|
| 11 |
|
| 12 |
# Use the renamed settings attributes
|
| 13 |
mongo_db = mongo_client[settings.mongo_db]
|
|
|
|
| 7 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 8 |
|
| 9 |
# Connect to MongoDB using the URI from settings
|
| 10 |
+
mongo_client = MongoClient("localhost", 27017) # Use default MongoDB port
|
| 11 |
+
# mongo_client = MongoClient(settings.mongo_uri)
|
| 12 |
|
| 13 |
# Use the renamed settings attributes
|
| 14 |
mongo_db = mongo_client[settings.mongo_db]
|
app/rag/embeddings.py
CHANGED
|
@@ -31,15 +31,32 @@ text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=100
|
|
| 31 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
|
| 33 |
|
| 34 |
-
HF_TOKEN = os.getenv("HUGGINGFACEHUB_API_TOKEN")
|
| 35 |
|
| 36 |
-
from huggingface_hub import login
|
| 37 |
|
| 38 |
-
login(HF_TOKEN)
|
| 39 |
|
| 40 |
-
model_name = "BAAI/bge-small-en-v1.5"
|
| 41 |
-
model_kwargs = {"device": "cpu"}
|
| 42 |
-
encode_kwargs = {"normalize_embeddings": True}
|
| 43 |
-
embeddings = HuggingFaceEmbeddings(
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
)
|
|
|
|
| 31 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
|
| 33 |
|
| 34 |
+
# HF_TOKEN = os.getenv("HUGGINGFACEHUB_API_TOKEN")
|
| 35 |
|
| 36 |
+
# from huggingface_hub import login
|
| 37 |
|
| 38 |
+
# login(HF_TOKEN)
|
| 39 |
|
| 40 |
+
# model_name = "BAAI/bge-small-en-v1.5"
|
| 41 |
+
# model_kwargs = {"device": "cpu"}
|
| 42 |
+
# encode_kwargs = {"normalize_embeddings": True}
|
| 43 |
+
# embeddings = HuggingFaceEmbeddings(
|
| 44 |
+
# model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
|
| 45 |
+
# )
|
| 46 |
+
|
| 47 |
+
from langchain_google_genai import GoogleGenerativeAIEmbeddings
|
| 48 |
+
from dotenv import load_dotenv
|
| 49 |
+
|
| 50 |
+
load_dotenv()
|
| 51 |
+
|
| 52 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
+
# 2. Embeddings Model (Google Gemini)
|
| 54 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 55 |
+
GOOGLE_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 56 |
+
if not GOOGLE_API_KEY:
|
| 57 |
+
raise ValueError("GOOGLE_API_KEY is not set in environment variables")
|
| 58 |
+
|
| 59 |
+
embeddings = GoogleGenerativeAIEmbeddings(
|
| 60 |
+
model="models/gemini-embedding-001",
|
| 61 |
+
google_api_key=GOOGLE_API_KEY
|
| 62 |
)
|
app/rag/prompt_library.py
CHANGED
|
@@ -115,4 +115,26 @@ Your response:
|
|
| 115 |
uiux_prompt = ChatPromptTemplate.from_messages([
|
| 116 |
("system", uiux_prompt_template),
|
| 117 |
("human", "{question}"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
])
|
|
|
|
| 115 |
uiux_prompt = ChatPromptTemplate.from_messages([
|
| 116 |
("system", uiux_prompt_template),
|
| 117 |
("human", "{question}"),
|
| 118 |
+
])
|
| 119 |
+
|
| 120 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 121 |
+
# 6. Prompt Template for Mobile Usability RAG Chatbot
|
| 122 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 123 |
+
|
| 124 |
+
mobile_usability_prompt_template = """
|
| 125 |
+
You are a Mobile Usability Assistant specialized in analyzing mobile user experience data.
|
| 126 |
+
Use the provided context (mobile usability metrics and user feedback) to answer the user's question.
|
| 127 |
+
If the context lacks sufficient information, respond with "I don't know." Avoid fabricating details.
|
| 128 |
+
|
| 129 |
+
Retrieved context:
|
| 130 |
+
{context}
|
| 131 |
+
|
| 132 |
+
User's question:
|
| 133 |
+
{question}
|
| 134 |
+
|
| 135 |
+
Your response:
|
| 136 |
+
"""
|
| 137 |
+
mobile_usability_prompt = ChatPromptTemplate.from_messages([
|
| 138 |
+
("system", mobile_usability_prompt_template),
|
| 139 |
+
("human", "{question}"),
|
| 140 |
])
|
app/rag/routes.py
CHANGED
|
@@ -20,7 +20,7 @@ router = APIRouter(prefix="/rag", tags=["rag"])
|
|
| 20 |
@router.post("/initialization/{onboarding_id}/{doc_type}", response_model=SetupResponse)
|
| 21 |
async def setup_rag_session(
|
| 22 |
onboarding_id: str = Path(..., description="Unique onboarding identifier"),
|
| 23 |
-
doc_type: str = Path(..., description="Type of document (e.g., page_speed, seo, content_relevance or
|
| 24 |
body: SetupRequest = ...
|
| 25 |
):
|
| 26 |
"""
|
|
@@ -86,7 +86,7 @@ async def chat_with_user(
|
|
| 86 |
onboarding_id: str = Path(...),
|
| 87 |
doc_type: str = Path(...),
|
| 88 |
chat_id: str = Path(...),
|
| 89 |
-
prompt_type: str = Query(..., description="Prompt type, e.g., page_speed, content_relevance or
|
| 90 |
body: ChatRequest = ...
|
| 91 |
):
|
| 92 |
"""
|
|
|
|
| 20 |
@router.post("/initialization/{onboarding_id}/{doc_type}", response_model=SetupResponse)
|
| 21 |
async def setup_rag_session(
|
| 22 |
onboarding_id: str = Path(..., description="Unique onboarding identifier"),
|
| 23 |
+
doc_type: str = Path(..., description="Type of document (e.g., page_speed, seo, content_relevance, uiux or mobile_usability)"),
|
| 24 |
body: SetupRequest = ...
|
| 25 |
):
|
| 26 |
"""
|
|
|
|
| 86 |
onboarding_id: str = Path(...),
|
| 87 |
doc_type: str = Path(...),
|
| 88 |
chat_id: str = Path(...),
|
| 89 |
+
prompt_type: str = Query(..., description="Prompt type, e.g., page_speed, content_relevance, seo, uiux or mobile_usability"),
|
| 90 |
body: ChatRequest = ...
|
| 91 |
):
|
| 92 |
"""
|
app/rag/utils.py
CHANGED
|
@@ -16,7 +16,8 @@ from .prompt_library import (
|
|
| 16 |
page_speed_prompt,
|
| 17 |
seo_prompt,
|
| 18 |
content_relevance_prompt,
|
| 19 |
-
uiux_prompt
|
|
|
|
| 20 |
)
|
| 21 |
|
| 22 |
# 1. Path with doc_type
|
|
@@ -97,6 +98,8 @@ def build_rag_chain(
|
|
| 97 |
user_prompt = content_relevance_prompt
|
| 98 |
elif prompt_type == "uiux":
|
| 99 |
user_prompt = uiux_prompt
|
|
|
|
|
|
|
| 100 |
else:
|
| 101 |
user_prompt = default_user_prompt
|
| 102 |
|
|
|
|
| 16 |
page_speed_prompt,
|
| 17 |
seo_prompt,
|
| 18 |
content_relevance_prompt,
|
| 19 |
+
uiux_prompt,
|
| 20 |
+
mobile_usability_prompt
|
| 21 |
)
|
| 22 |
|
| 23 |
# 1. Path with doc_type
|
|
|
|
| 98 |
user_prompt = content_relevance_prompt
|
| 99 |
elif prompt_type == "uiux":
|
| 100 |
user_prompt = uiux_prompt
|
| 101 |
+
elif prompt_type == "mobile_usability":
|
| 102 |
+
user_prompt = mobile_usability_prompt
|
| 103 |
else:
|
| 104 |
user_prompt = default_user_prompt
|
| 105 |
|
requirements.txt
CHANGED
|
@@ -10,7 +10,4 @@ langchain_community
|
|
| 10 |
faiss-cpu
|
| 11 |
pymongo
|
| 12 |
langchain-mongodb
|
| 13 |
-
huggingface_hub
|
| 14 |
-
sentence_transformers
|
| 15 |
langchain_google_genai
|
| 16 |
-
langchain_huggingface
|
|
|
|
| 10 |
faiss-cpu
|
| 11 |
pymongo
|
| 12 |
langchain-mongodb
|
|
|
|
|
|
|
| 13 |
langchain_google_genai
|
|
|