banao-tech's picture
Upload 2 files
c6b57d1 verified
Raw
History Blame Contribute Delete
109 kB
import os
import io
import time
import json
import base64
import random
import logging
import traceback
import requests
from datetime import datetime
from pathlib import Path
from dotenv import load_dotenv
import gradio as gr
from google import genai
from google.genai import types
from google.genai.errors import APIError
from PIL import Image
# Load dotenv if running locally
load_dotenv()
# Self Vision API imports
from self_vision_api import run_self_vision, validate_request
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
)
def parse_input_json(roadmap: dict, profile: dict) -> dict:
"""Parse roadmap + user_profile JSONs into run_generation() inputs."""
icp_raw = roadmap.get("icp_type", "high")
icp = "low_wage" if icp_raw == "low" else "high_wage"
# Derive gender from user_name
name = profile.get("user_name", "")
female_names = ["Priya", "Priyanka", "Ananya", "Pooja", "Shreya",
"Divya", "Anjali", "Neha", "Riya", "Fatima", "Ayesha",
"Lakshmi", "Meera", "Nandini", "Tanvi", "Deepika"]
gender = "female" if any(n in name for n in female_names) else "male"
vision = roadmap.get("vision_profile", {})
user_goal = (
f"{roadmap.get('target_role', '')} — "
f"{vision.get('top_motivation', '')} "
f"{vision.get('vision_12mo', '')}"
)
milestones = []
for m in roadmap.get("milestones", []):
milestones.append({
"milestone_id": m["milestone_id"],
"identity_statement": m.get("identity_statement", ""),
"market_value_display": m.get("market_value_display", ""),
})
return {
"user_id": roadmap.get("user_id", ""),
"user_name": profile.get("user_name", ""),
"user_goal": user_goal,
"domain": "Software Development",
"icp": icp,
"gender": gender,
"milestones": milestones,
"ai_roadmap_id": roadmap.get("ai_roadmap_id", ""),
"ai_session_id": roadmap.get("ai_session_id", ""),
}
IMAGE_MODEL = "gemini-3.1-flash-image"
# Shared client — initialised once, reused for all generation calls
_api_key = os.environ.get("GEMINI_API_KEY", "")
_genai_client = genai.Client(api_key=_api_key) if _api_key else None
# ---------------------------------------------------------------------------
# Load user personas from JSON
# ---------------------------------------------------------------------------
PERSONAS_PATH = Path(__file__).parent / "user_personas.json"
PERSONAS = []
PERSONA_MAP = {} # "PERS_01 — B.Tech CS Student → Software Engineering" -> persona dict
if PERSONAS_PATH.exists():
with open(PERSONAS_PATH, "r", encoding="utf-8") as f:
PERSONAS = json.load(f)
for p in PERSONAS:
career_path = p.get('Associated Career Path', '')
label = f"{p['Persona ID']}{p['Current Role']}{career_path}"
PERSONA_MAP[label] = p
PERSONA_CHOICES = ["-- Select an Example Persona --"] + list(PERSONA_MAP.keys())
# ---------------------------------------------------------------------------
# Professional Domain to Career Path mapping
# ---------------------------------------------------------------------------
PROF_DOMAIN_TO_CAREER_PATH = {
"Software Development": "Software Engineering",
"UI/UX Design": "Software Engineering",
"Finance": "Data Science",
"Human Resources": "Data Analytics",
"Manufacturing": "Data Analytics",
"Artificial Intelligence": "Data Science",
"Robotics": "ML/AI Engineering",
"Data Science": "Data Science",
"Consulting": "Product Management",
"Cybersecurity": "DevOps/Cloud",
"Cloud Computing": "DevOps/Cloud",
"E-commerce": "Mobile Development",
"Telecommunications": "Mobile Development",
"Education Technology": "Product Management",
"Operations / BPO": "BPO / Team Lead Path"
}
# ---------------------------------------------------------------------------
# Core Data Models: Domains & Market Value Parameters
# ---------------------------------------------------------------------------
CAREER_MILESTONES_PATH = Path(__file__).parent / "career_milestones.json"
CAREER_MILESTONES = {}
if CAREER_MILESTONES_PATH.exists():
with open(CAREER_MILESTONES_PATH, "r", encoding="utf-8") as f:
CAREER_MILESTONES = json.load(f)
# ---------------------------------------------------------------------------
# Career path to domain mapping (matches DOMAIN_PROPS keys)
# ---------------------------------------------------------------------------
CAREER_PATH_TO_DOMAIN = {
"Software Engineering": "Software Engineering",
"Data Analytics": "Data Analytics",
"Data Science": "Data Science",
"Machine Learning & AI Engineering": "ML/AI Engineering",
"DevOps & Cloud Engineering": "DevOps/Cloud",
"Mobile Development": "Mobile Development",
"Product Management": "Product Management",
"BPO / Team Lead Path": "BPO / Team Lead Path",
}
# ---------------------------------------------------------------------------
# ICP scene contexts (same as lambda_handler.py)
# ---------------------------------------------------------------------------
# Per-milestone visual metadata: attire, lighting mood, and expression tone.
# These complement the scene description and drive visual differentiation.
MILESTONE_VISUAL_META = {
"L1": {
"attire": "smart casual — clean chinos, a well-fitted solid-colour shirt, new white sneakers",
"lighting": "warm golden-hour morning light streaming through large east-facing office windows, soft lens flare",
"expression": "open-eyed curiosity mixed with quiet excitement — a small natural smile, fully present",
},
"L2": {
"attire": "business-casual — pressed dark trousers, a collared half-sleeve shirt in a muted tone, smart leather shoes",
"lighting": "soft diffused afternoon light from a north-facing window, no harsh shadows, slight warm colour cast",
"expression": "calm, engaged confidence — slightly raised eyebrow while making a point, commanding but approachable",
},
"L3": {
"attire": "elevated business-casual — tailored trousers, a structured blazer over a crisp shirt, premium watch visible",
"lighting": "cool neutral office lighting supplemented by natural overcast daylight through glass walls, clean shadows",
"expression": "focused authority — leaning slightly forward, measured gaze, the expression of someone who has already solved this problem",
},
"L4": {
"attire": "premium business formal — a sharply tailored dark suit, subtle tie, polished Oxford shoes, confident posture",
"lighting": "dramatic directional sunlight from large floor-to-ceiling windows hitting at 45 degrees, deep bokeh background",
"expression": "calm gravitas — poised, still, the expression of someone in complete control of the room",
},
"P1": {
"attire": "professional formal — neat tucked-in formal shirt, pressed trousers or salwar, polished formal shoes",
"lighting": "bright even fluorescent office lighting with a warm overhead fill, cheerful and energetic atmosphere",
"expression": "proud and determined — upright posture, bright eyes, the unmistakable energy of someone on their first day at a real job",
},
"P2": {
"attire": "professional formal — slightly more confident styling than P1, a well-fitted formal shirt, ID badge on lanyard",
"lighting": "warm soft overhead office light, clean and professional, slightly warmer tone than P1",
"expression": "patient and confident — a helpful half-smile while demonstrating something, the ease of genuine expertise",
},
"P3": {
"attire": "professional formal — structured formal shirt or kurta, team-lead badge prominently visible, neat and authoritative",
"lighting": "warm professional lighting with a slight directional key light from the side, giving a natural sense of presence",
"expression": "calm authority — standing tall, one hand near the laptop or whiteboard, the stance of someone who runs the meeting",
},
}
ICP_SCENE_CONTEXTS = {
"high_wage": {
"L1": {
"scene": "seated at a clean standing desk in a bright modern tech startup office in Bangalore, fresh employee ID card clipped to a smart casual shirt, open floor plan with friendly colleagues working in background, a strategy diagram on a nearby whiteboard",
"identity": "You've landed your first tech job — your engineering journey begins here",
"market": "6-12 LPA", # Naukri 2026: Freshers 6-12 across SWE/DS/AI/Cloud
},
"L2": {
"scene": "standing at a whiteboard in a modern product-team war room, drawing a clean diagram outlining a solution, three colleagues seated around the table with laptops open and nodding, post-it notes and sprint cards covering the wall behind",
"identity": "You think and ship like an engineer — your team trusts your technical judgment",
"market": "12-22 LPA", # Naukri 2026: upper-fresher to early mid-level
},
"L3": {
"scene": "at a glass-walled breakout room in a modern tech campus, seated across from a small group of 3 senior peers in a focused technical review and strategy discussion, laptop open, coffee cups on the table, city skyline visible through the glass",
"identity": "You own features end-to-end — peers come to you when things get hard",
"market": "22-40 LPA", # Naukri 2026: 5+ yrs avg 18-35 across roles
},
"L4": {
"scene": "standing at the front of a large sunlit boardroom in a premium Indian tech company, presenting to 10 senior leaders and managers, a large wall-mounted screen behind showing a high-level strategic roadmap and OKR dashboard with green metrics, one hand gesturing toward the screen",
"identity": "You set the technical direction — leaders look to you to define what gets built next",
"market": "40-75 LPA", # Naukri 2026: Sr 40 LPA-1.5 Cr (conservative band)
},
},
"low_wage": {
"P1": {
"scene": "seated upright at a clean modern workstation in a well-lit BPO or digital services office, a desktop computer showing a data entry or CRM screen, a shiny new employee ID card on the desk, colleagues at nearby workstations",
"identity": "You have your first digital job — you show up, you deliver, you grow",
"market": "12,000-18,000 per month",
},
"P2": {
"scene": "leaning toward a colleague at an adjacent workstation, patiently showing them something on the screen, two monitors visible, the surrounding office is busy and modern",
"identity": "You are the reliable one — your team comes to you when they're stuck",
"market": "18,000-28,000 per month",
},
"P3": {
"scene": "standing at the head of a small conference table presenting from a laptop to a team of 5 direct reports, a whiteboard behind with a weekly targets chart, everyone looking at the presenter with attention and respect, team lead badge visible",
"identity": "You lead the team — your name is attached to the team's results",
"market": "28,000-45,000 per month",
},
},
}
DOMAIN_PROPS = {
"Software Engineering": "laptop screen shows a code editor with Python or Java code",
"Data Analytics": "laptop screen shows data dashboards with charts and graphs",
"Data Science": "laptop screen shows a Jupyter notebook with graphs and ML outputs",
"ML/AI Engineering": "laptop screen shows neural network diagrams and model metrics",
"DevOps/Cloud": "multiple monitors show server dashboards and terminal windows",
"Product Management": "product roadmap visible on screen with sticky notes on wall",
"Mobile Development": "phone connected to a laptop showing a mobile app interface being debugged",
"BPO / Team Lead Path": "workstation with CRM software or communication dashboards open on screen",
"Default": "laptop open with professional work visible on screen",
}
DOMAIN_IDENTITIES = {
"Software Engineering": {
"L1": "You've landed your first tech job — your engineering journey begins here",
"L2": "You think and ship like an engineer — your team trusts your technical judgment",
"L3": "You own features end-to-end — peers come to you when things get hard",
"L4": "You set the technical direction — leaders look to you to define what gets built next"
},
"Data Analytics": {
"L1": "You've landed your first data role — your analytical journey begins here",
"L2": "You uncover actionable insights — your team relies on your data storytelling",
"L3": "You own critical metrics end-to-end — peers come to you for data truth",
"L4": "You define the data strategy — leaders look to you to drive business decisions"
},
"Data Science": {
"L1": "You've landed your first DS role — your modeling journey begins here",
"L2": "You build and deploy models — your team trusts your predictive accuracy",
"L3": "You own complex ML pipelines end-to-end — peers come to you for model architecture",
"L4": "You set the algorithmic vision — leaders look to you for AI-driven transformation"
},
"ML/AI Engineering": {
"L1": "You've landed your first AI role — your journey into intelligent systems begins here",
"L2": "You engineer robust ML systems — your team trusts your technical execution",
"L3": "You own scalable AI solutions end-to-end — peers look to you to solve hard architectural problems",
"L4": "You define the AI architecture — leaders rely on you to build the intelligent future"
},
"DevOps/Cloud": {
"L1": "You've landed your first Cloud role — your infrastructure journey begins here",
"L2": "You automate and scale — your team trusts your operational excellence",
"L3": "You own infrastructure end-to-end — peers come to you when systems break",
"L4": "You set the cloud architecture — leaders look to you for scalable, resilient platforms"
},
"Product Management": {
"L1": "You've landed your first PM role — your product journey begins here",
"L2": "You ship user value — your team trusts your prioritization and execution",
"L3": "You own product areas end-to-end — peers rely on you for cross-functional alignment",
"L4": "You set the product vision — leaders look to you to define the roadmap and market strategy"
},
"Mobile Development": {
"L1": "You've landed your first mobile role — your app development journey begins here",
"L2": "You build seamless mobile experiences — your team trusts your front-end expertise",
"L3": "You own complex app features end-to-end — peers come to you for architecture advice",
"L4": "You set the mobile strategy — leaders look to you for the future of the platform"
},
"BPO / Team Lead Path": {
"P1": "You have your first digital job — you show up, you deliver, you grow",
"P2": "You are the reliable one — your team comes to you when they're stuck",
"P3": "You lead the team — your name is attached to the team's results"
},
"Default": {
"L1": "You've landed your first professional role — your journey begins here",
"L2": "You execute with confidence — your team trusts your professional judgment",
"L3": "You manage cross-functional projects — peers rely on you for problem resolution",
"L4": "You set the operational standard — leaders look to you for efficiency and growth"
}
}
DOMAIN_CHOICES = list(PROF_DOMAIN_TO_CAREER_PATH.keys())
ICP_CHOICES = ["high_wage", "low_wage"]
# ---------------------------------------------------------------------------
# Prompt builder (same as lambda_handler.py)
# ---------------------------------------------------------------------------
def build_prompt(
milestone_id,
icp,
domain,
user_goal,
has_reference_photo,
gender="male",
milestone_label=None,
identity_statement=None,
market_value_display=None,
skills_context=None,
):
icp_scenes = ICP_SCENE_CONTEXTS.get(icp, ICP_SCENE_CONTEXTS["high_wage"])
# Resolve correct milestone key mapping if mismatch occurs (e.g. low-wage roadmap using "L" keys)
resolved_mid = milestone_id
if milestone_id not in icp_scenes:
import re
digits = re.findall(r'\d+', milestone_id)
if digits:
idx = int(digits[0]) - 1
keys = list(icp_scenes.keys())
if 0 <= idx < len(keys):
resolved_mid = keys[idx]
else:
resolved_mid = keys[-1]
else:
resolved_mid = list(icp_scenes.keys())[-1]
ctx = icp_scenes.get(resolved_mid, list(icp_scenes.values())[-1])
visual = MILESTONE_VISUAL_META.get(resolved_mid, {})
career_path = PROF_DOMAIN_TO_CAREER_PATH.get(domain, "Default")
# Case-insensitive domain prop selection
domain_key = next((k for k in DOMAIN_PROPS if k.lower() == career_path.lower()), "Default")
domain_prop = DOMAIN_PROPS[domain_key]
# scene always comes from hardcoded ctx — identity_statement is career copy, not a scene
scene = ctx.get("scene", "")
if skills_context:
scene += f" In the background on a screen or whiteboard, you can clearly see text, diagrams, or dashboards displaying topics about: {skills_context}."
fallback_identity = DOMAIN_IDENTITIES.get(domain_key, DOMAIN_IDENTITIES["Default"]).get(resolved_mid, ctx.get("identity", resolved_mid))
label = milestone_label or fallback_identity
identity = identity_statement or fallback_identity
market = market_value_display or ctx.get("market", "")
attire = visual.get("attire", "smart professional attire appropriate to the career stage")
if gender == "female":
# Replace male-oriented clothing terms with professional female equivalents
attire = attire.replace("clean chinos, a well-fitted solid-colour shirt", "clean trousers or a sleek skirt, a well-fitted solid-colour blouse or shirt")
attire = attire.replace("collared half-sleeve shirt", "collared blouse or half-sleeve shirt")
attire = attire.replace("crisp shirt", "crisp blouse or shirt")
attire = attire.replace("subtle tie, polished Oxford shoes", "polished formal shoes")
attire = attire.replace("dark suit", "dark trouser suit or elegant skirt suit")
attire = attire.replace("tucked-in formal shirt", "tucked-in formal blouse or elegant salwar kameez")
attire = attire.replace("formal shirt, ID badge", "formal shirt or elegant top, ID badge")
attire = attire.replace("formal shirt or kurta", "formal shirt or elegant kurta")
lighting = visual.get("lighting", "soft natural office lighting")
expression = visual.get("expression", "confident and focused professional expression")
if has_reference_photo:
subject_line = (
f"The subject is a real young Indian {gender}, the exact person from the reference photo provided. "
"You MUST preserve: their face structure, skin tone, eye shape, hair style, and all distinctive facial features. "
"The face must be immediately recognisable as the same person. Do not idealise, smooth, or alter their appearance."
)
else:
if gender == "female":
subject_line = (
"The subject is a real young Indian woman, mid-to-late 20s. "
"Natural skin tone, authentic features — not a model or stock photo face."
)
else:
subject_line = (
"The subject is a real young Indian man, mid-to-late 20s. "
"Natural skin tone, authentic features — not a model or stock photo face."
)
return f"""A high-end cinematic editorial portrait photograph. A candid, authentic moment captured in natural lighting.
SUBJECT
{subject_line}
Attire: {attire}.
Expression: {expression}.
SETTING
{scene}.
{domain_prop}. The setting should feel premium, contemporary, and distinctly Indian (e.g. situated in modern tech hubs like Bangalore, Hyderabad, or Mumbai).
COMPOSITION & DEPTH
Vertical 9:16 portrait orientation, optimized for phone screens.
The subject is positioned naturally in the frame using the rule of thirds, occupying the upper two-thirds of the image.
Cinematic depth of field with a clean subject separation and beautiful, soft background bokeh (f/1.8 lens emulation).
No empty space or awkward cropping.
LIGHTING & ATMOSPHERE
{lighting}.
Cinematic soft wrap lighting on the face with gentle gradient transitions, avoiding harsh direct light or flat studio lighting.
Subtle volumetric atmosphere with soft shadows that add dimension and volume to the features.
PHOTOGRAPHY STYLE & TECHNICAL SPECS
Shot on a professional camera system like a Sony A7R V or Canon EOS R5 with an 85mm f/1.4 prime lens.
Highly detailed and crisp subject sharpness focusing on the eyes.
Natural skin texture showing realistic pores, skin grain, and individual hair strands without any artificial smoothing or airbrushing.
A professional color grade with rich midtones, warm highlights, and subtle shadow roll-off.
CAREER CONTEXT (to guide mood, environment details, and visual energy)
Milestone stage: {label}
Professional identity: {identity}
Current market standing: {market}
Ultimate career goal: {user_goal}
STRICT NEGATIVE PROMPT RULES (DO NOT INCLUDE ANY OF THESE VISUALLY)
- No text, watermarks, signatures, logos, or overlaid text of any kind
- No generic, cheesy stock photo poses (e.g. forced smiles, arms crossed, pointing at screens)
- No CGI, 3D renders, digital paintings, illustrations, or synthetic artificial textures
- No airbrushed, plastic-like skin or doll-like facial features
- No double chin, distorted hands, or anatomy issues
- No oversaturated colors, flat lighting, or HDR effects
- Do not place any legible or stylized text on screens in the image"""
# ---------------------------------------------------------------------------
# Image generation
# ---------------------------------------------------------------------------
MAX_RETRIES = 2 # number of retry attempts on transient failures
def _extract_image(response):
"""Pull the first IMAGE part out of a Gemini response and return a PIL Image."""
if not response.parts:
return None
for part in response.parts:
if part.inline_data is not None:
raw = part.inline_data.data
if isinstance(raw, str):
raw = base64.b64decode(raw)
return Image.open(io.BytesIO(raw))
return None
def generate_single_image(prompt, reference_photo, api_key):
"""Generate a single milestone image. Returns PIL Image or None."""
# Prefer the module-level shared client; fall back to per-call if key differs
client = _genai_client if (_api_key and api_key == _api_key) else genai.Client(api_key=api_key)
gen_config = types.GenerateContentConfig(response_modalities=["IMAGE"])
# --- Attempt 1: with face reference ---
if reference_photo is not None:
for attempt in range(1, MAX_RETRIES + 1):
try:
print(f" [ref] attempt {attempt}/{MAX_RETRIES}")
response = client.models.generate_content(
model=IMAGE_MODEL,
contents=[prompt, reference_photo],
config=gen_config,
)
img = _extract_image(response)
if img:
return img
print(f" [ref] attempt {attempt}: no image in response")
except Exception as exc:
print(f" [ref] attempt {attempt} failed: {exc}")
print(" [ref] all attempts failed — falling back to scene-only")
# --- Attempt 2: scene-only (no face reference) ---
for attempt in range(1, MAX_RETRIES + 1):
try:
print(f" [scene] attempt {attempt}/{MAX_RETRIES}")
response = client.models.generate_content(
model=IMAGE_MODEL,
contents=[prompt],
config=gen_config,
)
img = _extract_image(response)
if img:
return img
print(f" [scene] attempt {attempt}: no image in response")
except Exception as exc:
print(f" [scene] attempt {attempt} failed: {exc}")
return None
# ---------------------------------------------------------------------------
# Persona auto-fill logic
# ---------------------------------------------------------------------------
def on_gender_change(current_text, target_gender):
if not current_text:
return current_text
import re
text = current_text
if target_gender == "male":
# female -> male
text = re.sub(r'\bShe\b', 'He', text)
text = re.sub(r'\bshe\b', 'he', text)
text = re.sub(r'\bHerself\b', 'Himself', text)
text = re.sub(r'\bherself\b', 'himself', text)
# known objective cases
text = re.sub(r'\bmakes her\b', 'makes him', text, flags=re.IGNORECASE)
text = re.sub(r'\bpositions her\b', 'positions him', text, flags=re.IGNORECASE)
text = re.sub(r'\bHer\b', 'His', text)
text = re.sub(r'\bher\b', 'his', text)
elif target_gender == "female":
# male -> female
text = re.sub(r'\bHe\b', 'She', text)
text = re.sub(r'\bhe\b', 'she', text)
text = re.sub(r'\bHimself\b', 'Herself', text)
text = re.sub(r'\bhimself\b', 'herself', text)
text = re.sub(r'\bHis\b', 'Her', text)
text = re.sub(r'\bhis\b', 'her', text)
text = re.sub(r'\bHim\b', 'Her', text)
text = re.sub(r'\bhim\b', 'her', text)
return text
def on_persona_change(persona_label):
"""When a persona is selected, auto-fill the form fields."""
if persona_label == "-- Select an Example Persona --" or persona_label not in PERSONA_MAP:
return (
"", # user_goal
"Software Development", # domain
"high_wage", # icp
"", # gender
"" # milestone text
)
p = PERSONA_MAP[persona_label]
# Derive user_goal from persona
user_goal = p.get("Target Career Ambition", "")
reason = p.get("Reason for Pursuing This Career Path", "")
if reason:
user_goal = f"{user_goal}{reason}"
# Derive domain from professional domain
domain = p.get("Professional Domain", "Software Development")
# Derive ICP from Market Value Anchor
market_anchor = p.get("Market Value Anchor", "")
if market_anchor.startswith("P"):
icp = "low_wage"
else:
icp = "high_wage"
# Derive gender from Persona ID since names are now generic
persona_id = p.get("Persona ID", "")
female_persona_ids = ["PERS_02", "PERS_04", "PERS_06", "PERS_07", "PERS_09", "PERS_11", "PERS_13", "PERS_15", "PERS_17", "PERS_19", "PERS_21"]
if persona_id in female_persona_ids:
gender = "female"
elif persona_id:
gender = "male"
else:
name = p.get("Name", "")
female_indicators = [
"Fatima", "Pooja", "Lakshmi", "Ayesha", "Shreya", "Divya",
"Anjali", "Tanya", "Nandini", "Sunita", "Priya", "Ananya",
"Meera", "Kavitha", "Deepika", "Tanvi", "Riya", "Neha"
]
gender = "female" if any(ind in name for ind in female_indicators) else "male"
milestone_md = CAREER_MILESTONES.get(persona_id, "")
return (user_goal, domain, icp, gender, milestone_md)
# ---------------------------------------------------------------------------
# Milestone defaults per ICP
# ---------------------------------------------------------------------------
def calculate_market_value(domain, icp, milestone):
career_path = PROF_DOMAIN_TO_CAREER_PATH.get(domain, "Default")
MARKET_RATES = {
"high_wage": {
"Software Engineering": {"L1": "6-12 LPA", "L2": "12-24 LPA", "L3": "24-45 LPA", "L4": "45-80 LPA"},
"Data Analytics": {"L1": "5-8 LPA", "L2": "8-14 LPA", "L3": "14-22 LPA", "L4": "22-35 LPA"},
"Data Science": {"L1": "8-14 LPA", "L2": "14-25 LPA", "L3": "25-45 LPA", "L4": "45-75 LPA"},
"Machine Learning & AI Engineering": {"L1": "12-18 LPA", "L2": "18-35 LPA", "L3": "35-65 LPA", "L4": "65-120 LPA"},
"DevOps/Cloud": {"L1": "8-14 LPA", "L2": "14-26 LPA", "L3": "26-45 LPA", "L4": "45-80 LPA"},
"DevOps & Cloud Engineering": {"L1": "8-14 LPA", "L2": "14-26 LPA", "L3": "26-45 LPA", "L4": "45-80 LPA"},
"Product Management": {"L1": "10-16 LPA", "L2": "16-28 LPA", "L3": "28-55 LPA", "L4": "55-100 LPA"},
"Mobile Development": {"L1": "6-10 LPA", "L2": "10-18 LPA", "L3": "18-32 LPA", "L4": "32-55 LPA"},
"BPO / Team Lead Path": {"L1": "4-6 LPA", "L2": "6-10 LPA", "L3": "10-15 LPA", "L4": "15-25 LPA"},
"Default": {"L1": "6-12 LPA", "L2": "12-22 LPA", "L3": "22-40 LPA", "L4": "40-75 LPA"}
},
"low_wage": {
"BPO / Team Lead Path": {"P1": "15k-25k / mo", "P2": "25k-40k / mo", "P3": "40k-70k / mo", "P4": "70k-1.2L / mo"},
"Data Analytics": {"P1": "15k-25k / mo", "P2": "25k-40k / mo", "P3": "40k-65k / mo", "P4": "65k-90k / mo"},
"Software Engineering": {"P1": "20k-30k / mo", "P2": "30k-50k / mo", "P3": "50k-80k / mo", "P4": "80k-1.2L / mo"},
"Default": {"P1": "15k-22k / mo", "P2": "22k-35k / mo", "P3": "35k-50k / mo", "P4": "50k-80k / mo"}
}
}
path_rates = MARKET_RATES.get(icp, MARKET_RATES["high_wage"])
rates = path_rates.get(career_path, path_rates["Default"])
return rates.get(milestone, rates.get("L1" if icp == "high_wage" else "P1", "N/A"))
def get_milestone_defaults(icp_value, domain_value, persona_label=None):
"""Return milestone defaults based on ICP and domain selection."""
custom_milestones = None
if persona_label and persona_label in PERSONA_MAP:
p = PERSONA_MAP[persona_label]
if "Custom Milestones" in p:
custom_milestones = p["Custom Milestones"]
results = []
for i in range(4):
if custom_milestones is not None:
if i < len(custom_milestones):
m = custom_milestones[i]
results.extend([
gr.update(visible=True),
m.get("milestone_id", ""),
m.get("identity_statement", ""),
m.get("market_value_display", ""),
])
else:
results.extend([
gr.update(visible=False),
"",
"",
"",
])
else:
career_path = PROF_DOMAIN_TO_CAREER_PATH.get(domain_value, "Default")
icp_scenes = ICP_SCENE_CONTEXTS.get(icp_value, ICP_SCENE_CONTEXTS["high_wage"])
milestone_ids = list(icp_scenes.keys())
# Grab identity statements from the selected domain (Career Path map) or Default
identities = DOMAIN_IDENTITIES.get(career_path, DOMAIN_IDENTITIES["Default"])
if i < len(milestone_ids):
mid = milestone_ids[i]
ctx = icp_scenes[mid]
identity = DOMAIN_IDENTITIES.get(career_path, DOMAIN_IDENTITIES["Default"]).get(mid, ctx.get("identity", ""))
dynamic_market = calculate_market_value(domain_value, icp_value, mid)
results.extend([
gr.update(visible=True), # group visibility
mid, # milestone_id
identity, # description
dynamic_market, # dynamic market value
])
else:
results.extend([
gr.update(visible=False), # group visibility
"", # milestone_id
"", # description
"", # market value
])
return results
def get_milestones_from_roadmap_or_defaults(roadmap_dict, icp_value, domain_value):
"""
Extract milestones from roadmap_dict if available.
Otherwise, fall back to default milestones for the given icp and domain.
"""
results = []
milestones = roadmap_dict.get("milestones", []) if roadmap_dict else []
for i in range(4):
if i < len(milestones):
m = milestones[i]
mid = m.get("milestone_id") or m.get("label") or f"M{i+1}"
desc = m.get("identity_statement") or m.get("identity_label") or ""
val = m.get("market_value_display") or ""
results.extend([
gr.update(visible=True),
mid,
desc,
val,
])
else:
if roadmap_dict:
# Hide unused milestone slots
results.extend([
gr.update(visible=False),
"",
"",
"",
])
else:
# No roadmap at all: use standard defaults
career_path = PROF_DOMAIN_TO_CAREER_PATH.get(domain_value, "Default")
icp_scenes = ICP_SCENE_CONTEXTS.get(icp_value, ICP_SCENE_CONTEXTS["high_wage"])
milestone_ids = list(icp_scenes.keys())
if i < len(milestone_ids):
mid = milestone_ids[i]
ctx = icp_scenes[mid]
identity = DOMAIN_IDENTITIES.get(career_path, DOMAIN_IDENTITIES["Default"]).get(mid, ctx.get("identity", ""))
dynamic_market = calculate_market_value(domain_value, icp_value, mid)
results.extend([
gr.update(visible=True),
mid,
identity,
dynamic_market,
])
else:
results.extend([
gr.update(visible=False),
"",
"",
"",
])
return results
def load_data_from_url(request: gr.Request):
"""
Load data from the URL query parameters.
e.g. ?user_id=test_user_fresh_001 or ?roadmap_id=ai_roadmap_...
"""
user_id = ""
roadmap_id_val = ""
if request is not None and request.query_params:
user_id = request.query_params.get("user_id", "")
roadmap_id_val = request.query_params.get("roadmap_id", "")
# If roadmap_id provided but no user_id, resolve it
if roadmap_id_val and not user_id:
try:
from pinecone_client import PineconeClient
pc = PineconeClient()
resolve_result = pc.resolve_user_from_roadmap(roadmap_id_val)
if resolve_result["status"] == "success":
user_id = resolve_result["user_id"]
except Exception:
pass
if not user_id:
milestone_defaults = get_milestones_from_roadmap_or_defaults(None, "high_wage", "Software Development")
return (
roadmap_id_val, # roadmap_id_input
"", # user_id_input
"", # raw_transcription_input
"", # user_name
"", # user_goal
"Software Development", # domain
"high_wage", # icp
"male", # gender_input
"📋 Awaiting user configuration...", # status_box
*milestone_defaults,
"*Select a persona or enter a User ID to see detailed career trajectory.*"
)
# Fetch onboarding context and roadmap context
from pinecone_client import PineconeClient, KEY_ONBOARDING, KEY_ROADMAP, KEY_SELF_VISION
pc = PineconeClient()
fetch_res = pc.fetch_conversation(user_id, KEY_ONBOARDING)
onboarding_text = fetch_res.get("text", "")
fetch_roadmap_res = pc.fetch_conversation(user_id, KEY_ROADMAP)
roadmap_text = fetch_roadmap_res.get("text", "")
fetch_sv_res = pc.fetch_conversation(user_id, KEY_SELF_VISION)
self_vision_text = fetch_sv_res.get("text", "")
user_name_val = ""
user_goal_val = ""
gender_val = "male"
domain_val = "Software Development"
icp_val = "high_wage"
roadmap_dict = None
status_msg = f"📥 Automatically loaded User ID '{user_id}' from URL query parameters."
if onboarding_text:
status_msg += "\n📥 Found prior onboarding conversation in Pinecone."
try:
profile = json.loads(onboarding_text)
status_msg += " Parsed profile data."
user_name_val = profile.get("user_name", "")
# Extract primary goal
objectives = profile.get("learning_objectives", {})
user_goal_val = objectives.get("primary_goal", "")
motivation = objectives.get("key_motivation", "")
if motivation and user_goal_val:
user_goal_val = f"{user_goal_val}{motivation}"
elif motivation:
user_goal_val = motivation
# Extract gender from user_name
female_indicators = ["Priya", "Priyanka", "Ananya", "Pooja", "Shreya", "Divya", "Anjali", "Neha", "Riya", "Fatima", "Ayesha", "Lakshmi", "Meera", "Nandini", "Tanvi", "Deepika", "Priyambada"]
if any(ind in user_name_val for ind in female_indicators):
gender_val = "female"
# Extract domain if present
dev_areas = profile.get("development_areas", {})
focused = dev_areas.get("focused_topics", [])
if focused:
topic = focused[0].lower()
if "data science" in topic:
domain_val = "Data Science"
elif "data analytics" in topic or "analytics" in topic:
domain_val = "Data Science"
elif "machine learning" in topic or "ml" in topic or "ai" in topic:
domain_val = "Artificial Intelligence"
elif "devops" in topic or "cloud" in topic or "infrastructure" in topic:
domain_val = "Cloud Computing"
elif "mobile" in topic or "app" in topic or "android" in topic or "ios" in topic:
domain_val = "E-commerce"
elif "product" in topic:
domain_val = "Education Technology"
elif "bpo" in topic or "operations" in topic:
domain_val = "Operations / BPO"
elif "software" in topic or "web" in topic or "full stack" in topic:
domain_val = "Software Development"
except Exception:
pass
else:
status_msg += "\n⚠️ No prior onboarding conversation found in Pinecone for this User ID."
if roadmap_text:
status_msg += "\n📥 Found career roadmap in Pinecone. Parsed roadmap data."
try:
roadmap_dict = json.loads(roadmap_text)
target_role = roadmap_dict.get("target_role", "")
vision = roadmap_dict.get("vision_profile", {})
top_motivation = vision.get("top_motivation", "")
vision_12mo = vision.get("vision_12mo", "")
roadmap_goal = f"{target_role}"
if top_motivation or vision_12mo:
details_parts = []
if top_motivation:
details_parts.append(top_motivation)
if vision_12mo:
details_parts.append(vision_12mo)
roadmap_goal += " — " + " ".join(details_parts)
if roadmap_goal.strip():
user_goal_val = roadmap_goal
# Extract ICP from roadmap
icp_raw = roadmap_dict.get("icp_type", "high")
icp_val = "low_wage" if icp_raw == "low" else "high_wage"
# Try to map target role to domain
if target_role:
role_lower = target_role.lower()
if "data science" in role_lower or "analytics" in role_lower or "analyst" in role_lower:
domain_val = "Data Science"
elif "machine learning" in role_lower or "ml" in role_lower or "ai" in role_lower or "artificial intelligence" in role_lower:
domain_val = "Artificial Intelligence"
elif "devops" in role_lower or "cloud" in role_lower or "infrastructure" in role_lower:
domain_val = "Cloud Computing"
elif "mobile" in role_lower or "app" in role_lower or "android" in role_lower or "ios" in role_lower:
domain_val = "E-commerce"
elif "product" in role_lower:
domain_val = "Education Technology"
elif "bpo" in role_lower or "operations" in role_lower:
domain_val = "Operations / BPO"
elif "software" in role_lower or "web" in role_lower or "full stack" in role_lower or "developer" in role_lower or "engineer" in role_lower:
domain_val = "Software Development"
except Exception as e:
status_msg += f" (Error parsing roadmap: {e})"
else:
status_msg += "\n⚠️ No career roadmap found in Pinecone for this User ID."
if domain_val not in DOMAIN_CHOICES:
domain_val = "Software Development"
milestone_updates = get_milestones_from_roadmap_or_defaults(roadmap_dict, icp_val, domain_val)
milestone_details_val = "*No detailed milestones found in roadmap.*"
if roadmap_dict and roadmap_dict.get("milestones"):
combined_md = []
milestones_list = roadmap_dict.get("milestones", [])[:4]
for i, m in enumerate(milestones_list):
m_ui = {
"milestone_id": m.get("milestone_id", ""),
"identity_statement": m.get("identity_statement", ""),
"market_value_display": m.get("market_value_display", "")
}
detail = build_rich_milestone_detail(
milestone_ui=m_ui,
roadmap_dict=roadmap_dict,
onboarding_profile=profile if 'profile' in locals() else None,
milestone_index=i,
total_milestones=len(milestones_list)
)
combined_md.append(detail)
milestone_details_val = "\n\n---\n\n".join(combined_md)
else:
milestone_details_val = "*Select a persona or enter a User ID to see detailed career trajectory.*"
return (
roadmap_id_val if roadmap_id_val else "",
user_id,
self_vision_text,
user_name_val,
user_goal_val,
domain_val,
icp_val,
gender_val,
status_msg,
*milestone_updates,
milestone_details_val
)
def on_user_id_change(user_id):
"""
Called when the User ID input is submitted or loses focus (blur).
Fetches the onboarding context and roadmap context from Pinecone and auto-populates the form.
"""
user_id = (user_id or "").strip()
if not user_id:
milestone_defaults = get_milestones_from_roadmap_or_defaults(None, "high_wage", "Software Development")
return (
"", # raw_transcription_input
"", # user_name
"", # user_goal
"Software Development", # domain
"high_wage", # icp
"male", # gender_input
"📋 Awaiting user configuration...", # status_box
*milestone_defaults,
"*Select a persona or enter a User ID to see detailed career trajectory.*"
)
# Fetch onboarding context and roadmap context
from pinecone_client import PineconeClient, KEY_ONBOARDING, KEY_ROADMAP, KEY_SELF_VISION
pc = PineconeClient()
fetch_res = pc.fetch_conversation(user_id, KEY_ONBOARDING)
onboarding_text = fetch_res.get("text", "")
fetch_roadmap_res = pc.fetch_conversation(user_id, KEY_ROADMAP)
roadmap_text = fetch_roadmap_res.get("text", "")
fetch_sv_res = pc.fetch_conversation(user_id, KEY_SELF_VISION)
self_vision_text = fetch_sv_res.get("text", "")
user_name_val = ""
user_goal_val = ""
gender_val = "male"
domain_val = "Software Development"
icp_val = "high_wage"
roadmap_dict = None
status_msg = f"📥 Automatically loaded onboarding context and roadmap for User ID '{user_id}' from Pinecone."
if onboarding_text:
try:
profile = json.loads(onboarding_text)
user_name_val = profile.get("user_name", "")
# Extract primary goal
objectives = profile.get("learning_objectives", {})
user_goal_val = objectives.get("primary_goal", "")
motivation = objectives.get("key_motivation", "")
if motivation and user_goal_val:
user_goal_val = f"{user_goal_val}{motivation}"
elif motivation:
user_goal_val = motivation
# Extract gender from user_name
female_indicators = ["Priya", "Priyanka", "Ananya", "Pooja", "Shreya", "Divya", "Anjali", "Neha", "Riya", "Fatima", "Ayesha", "Lakshmi", "Meera", "Nandini", "Tanvi", "Deepika", "Priyambada"]
if any(ind in user_name_val for ind in female_indicators):
gender_val = "female"
# Extract domain if present
dev_areas = profile.get("development_areas", {})
focused = dev_areas.get("focused_topics", [])
if focused:
topic = focused[0].lower()
if "data science" in topic:
domain_val = "Data Science"
elif "data analytics" in topic or "analytics" in topic:
domain_val = "Data Science"
elif "machine learning" in topic or "ml" in topic or "ai" in topic:
domain_val = "Artificial Intelligence"
elif "devops" in topic or "cloud" in topic or "infrastructure" in topic:
domain_val = "Cloud Computing"
elif "mobile" in topic or "app" in topic or "android" in topic or "ios" in topic:
domain_val = "E-commerce"
elif "product" in topic:
domain_val = "Education Technology"
elif "bpo" in topic or "operations" in topic:
domain_val = "Operations / BPO"
elif "software" in topic or "web" in topic or "full stack" in topic:
domain_val = "Software Development"
except Exception:
pass
else:
status_msg += "\n⚠️ No prior onboarding conversation found in Pinecone for this User ID."
if roadmap_text:
try:
roadmap_dict = json.loads(roadmap_text)
target_role = roadmap_dict.get("target_role", "")
vision = roadmap_dict.get("vision_profile", {})
top_motivation = vision.get("top_motivation", "")
vision_12mo = vision.get("vision_12mo", "")
roadmap_goal = f"{target_role}"
if top_motivation or vision_12mo:
details_parts = []
if top_motivation:
details_parts.append(top_motivation)
if vision_12mo:
details_parts.append(vision_12mo)
roadmap_goal += " — " + " ".join(details_parts)
if roadmap_goal.strip():
user_goal_val = roadmap_goal
# Extract ICP from roadmap
icp_raw = roadmap_dict.get("icp_type", "high")
icp_val = "low_wage" if icp_raw == "low" else "high_wage"
# Try to map target role to domain
if target_role:
role_lower = target_role.lower()
if "data science" in role_lower or "analytics" in role_lower or "analyst" in role_lower:
domain_val = "Data Science"
elif "machine learning" in role_lower or "ml" in role_lower or "ai" in role_lower or "artificial intelligence" in role_lower:
domain_val = "Artificial Intelligence"
elif "devops" in role_lower or "cloud" in role_lower or "infrastructure" in role_lower:
domain_val = "Cloud Computing"
elif "mobile" in role_lower or "app" in role_lower or "android" in role_lower or "ios" in role_lower:
domain_val = "E-commerce"
elif "product" in role_lower:
domain_val = "Education Technology"
elif "bpo" in role_lower or "operations" in role_lower:
domain_val = "Operations / BPO"
elif "software" in role_lower or "web" in role_lower or "full stack" in role_lower or "developer" in role_lower or "engineer" in role_lower:
domain_val = "Software Development"
except Exception as e:
status_msg += f" (Error parsing roadmap: {e})"
else:
status_msg += "\n⚠️ No career roadmap found in Pinecone for this User ID."
if domain_val not in DOMAIN_CHOICES:
domain_val = "Software Development"
milestone_updates = get_milestones_from_roadmap_or_defaults(roadmap_dict, icp_val, domain_val)
milestone_details_val = "*No detailed milestones found in roadmap.*"
if roadmap_dict and roadmap_dict.get("milestones"):
combined_md = []
milestones_list = roadmap_dict.get("milestones", [])[:4]
for i, m in enumerate(milestones_list):
m_ui = {
"milestone_id": m.get("milestone_id", ""),
"identity_statement": m.get("identity_statement", ""),
"market_value_display": m.get("market_value_display", "")
}
detail = build_rich_milestone_detail(
milestone_ui=m_ui,
roadmap_dict=roadmap_dict,
onboarding_profile=profile if 'profile' in locals() else None,
milestone_index=i,
total_milestones=len(milestones_list)
)
combined_md.append(detail)
milestone_details_val = "\n\n---\n\n".join(combined_md)
else:
milestone_details_val = "*Select a persona or enter a User ID to see detailed career trajectory.*"
return (
self_vision_text,
user_name_val,
user_goal_val,
domain_val,
icp_val,
gender_val,
status_msg,
*milestone_updates,
milestone_details_val
)
def on_roadmap_id_change(roadmap_id):
"""
Called when the Roadmap ID input is submitted or loses focus.
Resolves the user_id from the roadmap_id via Pinecone reverse index,
then triggers the full data load flow.
"""
roadmap_id = (roadmap_id or "").strip()
if not roadmap_id:
milestone_defaults = get_milestones_from_roadmap_or_defaults(None, "high_wage", "Software Development")
return (
"", # user_id_input
"", # raw_transcription_input
"", # user_name
"", # user_goal
"Software Development", # domain
"high_wage", # icp
"male", # gender_input
"📋 Awaiting user configuration...", # status_box
*milestone_defaults,
"*Select a persona or enter a User ID to see detailed career trajectory.*"
)
from pinecone_client import PineconeClient
pc = PineconeClient()
# Resolve roadmap_id → user_id
resolve_result = pc.resolve_user_from_roadmap(roadmap_id)
user_id = resolve_result.get("user_id", "")
if not user_id:
milestone_defaults = get_milestones_from_roadmap_or_defaults(None, "high_wage", "Software Development")
return (
"", # user_id_input
"", # raw_transcription_input
"", # user_name
"", # user_goal
"Software Development", # domain
"high_wage", # icp
"male", # gender_input
f"⚠️ No user found for Roadmap ID '{roadmap_id}'. Try entering the User ID directly.", # status_box
*milestone_defaults,
"*Select a persona or enter a User ID to see detailed career trajectory.*"
)
# We have the user_id — now do the full data load via on_user_id_change
result = on_user_id_change(user_id)
# Prepend the resolved user_id to the result
return (user_id, *result)
def on_dev_upsert(key_selection, user_id, content):
"""
Manually upsert a roadmap, onboarding, or self-vision conversation to Pinecone.
"""
user_id = (user_id or "").strip()
content = (content or "").strip()
if not user_id:
return "❌ Error: Target User ID is required."
if not content:
return "❌ Error: Content is empty."
from pinecone_client import PineconeClient, KEY_ONBOARDING, KEY_ROADMAP, KEY_SELF_VISION
# Map selection to key
key = KEY_ROADMAP
if "onboarding" in key_selection.lower():
key = KEY_ONBOARDING
elif "self-vision" in key_selection.lower():
key = KEY_SELF_VISION
# Try to validate JSON if it's roadmap or onboarding
if key in (KEY_ROADMAP, KEY_ONBOARDING):
try:
import json
json.loads(content)
except Exception as e:
return f"❌ Error: Content is not valid JSON ({e}). Please check format."
try:
pc = PineconeClient()
res = pc.upsert_conversation(user_id, key, content)
if res.get("status") == "success":
msg = f"✅ Success: Stored '{key}' under User ID '{user_id}'."
if key == KEY_ROADMAP:
msg += " (Reverse mapping registered.)"
return f"{msg}\nElapsed time: {res.get('elapsed_ms', 0):.1f}ms"
else:
return "❌ Error: Failed to upsert to Pinecone."
except Exception as e:
return f"❌ Error: {e}"
# ---------------------------------------------------------------------------
# Rich milestone detail builder (onboarding + roadmap)
# ---------------------------------------------------------------------------
def build_rich_milestone_detail(
milestone_ui,
roadmap_dict=None,
onboarding_profile=None,
milestone_index=0,
total_milestones=1,
):
"""
Build rich milestone detail text by combining onboarding profile
and roadmap milestone data.
Args:
milestone_ui: dict with milestone_id, identity_statement, market_value_display
(from the Gradio UI inputs)
roadmap_dict: parsed roadmap JSON (full dict), or None
onboarding_profile: parsed onboarding/user_profile JSON, or None
milestone_index: 0-based index of this milestone
total_milestones: total number of milestones being generated
Returns:
Markdown string with enriched milestone detail.
"""
mid = milestone_ui.get("milestone_id", "")
mdesc = milestone_ui.get("identity_statement", "")
mval = milestone_ui.get("market_value_display", "")
# ── Find matching roadmap milestone (by milestone_id) ──
roadmap_milestone = None
if roadmap_dict:
for rm in roadmap_dict.get("milestones", []):
if rm.get("milestone_id") == mid:
roadmap_milestone = rm
break
# ── Header with label ──
identity_label = ""
if roadmap_milestone:
identity_label = roadmap_milestone.get("identity_label", "")
header = f"#### 🏁 {mid}"
if identity_label:
header += f" — {identity_label}"
sections = [header, ""]
# ── Identity Statement ──
if mdesc:
sections.append(f"**🎯 Your Identity at This Stage**")
sections.append(mdesc)
sections.append("")
# ── Market Value ──
if mval:
sections.append(f"**💰 Market Value:** {mval}")
sections.append("")
# ── Where You Are Now (from roadmap vision_profile + onboarding) ──
where_now_parts = []
if roadmap_dict:
vision = roadmap_dict.get("vision_profile", {})
current_state = vision.get("current_state", "")
main_blocker = vision.get("main_blocker", "")
if current_state and milestone_index == 0:
where_now_parts.append(current_state)
if main_blocker and milestone_index == 0:
where_now_parts.append(f"**Key challenge to overcome:** {main_blocker}")
if onboarding_profile:
bg = onboarding_profile.get("background_skills", {})
exp_level = bg.get("experience_level", "")
tech_skills = bg.get("technical_skills", [])
soft_skills = bg.get("soft_skills", [])
if exp_level and milestone_index == 0:
where_now_parts.append(f"**Experience level:** {exp_level}")
if tech_skills and milestone_index == 0:
where_now_parts.append(f"**Existing technical skills:** {', '.join(tech_skills)}")
if soft_skills and milestone_index == 0:
where_now_parts.append(f"**Soft skills:** {', '.join(soft_skills)}")
if where_now_parts:
sections.append("**📍 Where You Are Now**")
sections.extend(where_now_parts)
sections.append("")
# ── What You'll Master (from roadmap modules + skills) ──
if roadmap_milestone:
modules = roadmap_milestone.get("modules", [])
if modules:
sections.append("**📚 What You'll Master**")
total_hours = 0
for mod in modules:
mod_title = mod.get("title", "Module")
mod_desc = mod.get("description", "")
skills = mod.get("skills", [])
# Module header
mod_line = f"- **{mod_title}**"
if mod_desc:
mod_line += f" — {mod_desc}"
sections.append(mod_line)
# Skills under module
for skill in skills:
s_title = skill.get("title", "")
s_hours = skill.get("estimated_hours", 0)
s_diff = skill.get("difficulty", 0)
total_hours += s_hours
skill_line = f" - {s_title}"
skill_meta = []
if s_hours:
skill_meta.append(f"{s_hours} hrs")
if s_diff:
# Convert 0-1 difficulty to a label
if s_diff <= 0.25:
skill_meta.append("beginner")
elif s_diff <= 0.5:
skill_meta.append("intermediate")
elif s_diff <= 0.75:
skill_meta.append("advanced")
else:
skill_meta.append("expert")
if skill_meta:
skill_line += f" *({', '.join(skill_meta)})*"
sections.append(skill_line)
sections.append("")
# Effort summary
if total_hours > 0:
sections.append(f"**⏱️ Estimated Effort:** ~{total_hours} hours across {len(modules)} module{'s' if len(modules) != 1 else ''}")
sections.append("")
# Checkpoint
checkpoint = roadmap_milestone.get("checkpoint_rule", {})
cp_type = checkpoint.get("checkpoint_type", "")
cp_mastery = checkpoint.get("required_mastery", 0)
if cp_type:
mastery_pct = int(cp_mastery * 100) if cp_mastery else 0
cp_display = cp_type.replace("_", " ").title()
sections.append(f"**🎓 Checkpoint:** {cp_display} | Required mastery: {mastery_pct}%")
sections.append("")
# ── Personalized For You (from onboarding) ──
personalized_parts = []
if onboarding_profile:
# Learning style
ls = onboarding_profile.get("learning_style", {})
pref_style = ls.get("preferred_style", "")
pace = ls.get("pace", "")
if pref_style and pref_style != "not specified":
personalized_parts.append(f"**Learning style:** {pref_style}")
if pace and pace != "not specified":
personalized_parts.append(f"**Pace:** {pace}")
# Time commitment
pi = onboarding_profile.get("personalized_insights", {})
time_commit = pi.get("time_commitment", "")
goal_timeline = pi.get("goal_timeline", "")
familiar_tools = pi.get("familiar_tools", [])
if time_commit:
personalized_parts.append(f"**Your time:** {time_commit}")
if goal_timeline:
personalized_parts.append(f"**Target timeline:** {goal_timeline}")
if familiar_tools:
personalized_parts.append(f"**Tools you know:** {', '.join(familiar_tools)}")
# Challenges
barriers = onboarding_profile.get("learning_barriers", {})
challenges = barriers.get("challenges", [])
if challenges:
personalized_parts.append(f"**Watch out for:** {', '.join(challenges)}")
# Focused topics
dev_areas = onboarding_profile.get("development_areas", {})
topics = dev_areas.get("focused_topics", [])
if topics:
personalized_parts.append(f"**Your interests:** {', '.join(topics)}")
# Primary goal + motivation
objectives = onboarding_profile.get("learning_objectives", {})
primary_goal = objectives.get("primary_goal", "")
motivation = objectives.get("key_motivation", "")
if primary_goal:
goal_line = f"**Your goal:** {primary_goal}"
if motivation:
goal_line += f" — {motivation}"
personalized_parts.append(goal_line)
if personalized_parts:
sections.append("**🔗 Personalized For You**")
sections.extend(personalized_parts)
sections.append("")
# ── Progress indicator ──
sections.append(f"*Milestone {milestone_index + 1} of {total_milestones}*")
return "\n".join(sections)
# ---------------------------------------------------------------------------
# Main generation function (unified: images + Pinecone persistence)
# ---------------------------------------------------------------------------
def run_generation(
user_id_input, raw_transcription_input,
user_goal, domain, icp, gender_input, reference_photo,
m1_visible, m1_id, m1_desc, m1_val,
m2_visible, m2_id, m2_desc, m2_val,
m3_visible, m3_id, m3_desc, m3_val,
m4_visible, m4_id, m4_desc, m4_val,
):
"""Generate milestone images + run Pinecone persistence pipeline."""
api_key = os.environ.get("GEMINI_API_KEY", "")
empty_details = ["", "", "", ""]
pinecone_empty = "" # empty pinecone status
sv_empty = {
"status": "Awaiting generation...",
"message": "Click 'Generate Self-Vision' to run the pipeline."
}
if not api_key:
yield (None, None, None, None,
*empty_details,
gr.update(interactive=True),
"❌ No API key provided. Set the GEMINI_API_KEY environment variable.",
None, pinecone_empty, sv_empty)
return
# ── Pinecone: resolve user_id ──
uid = (user_id_input or "").strip()
raw_transcript = (raw_transcription_input or "").strip()
use_pinecone = bool(uid)
# ── Pinecone STEP: Fetch onboarding context ──
pinecone_status_lines = []
onboarding_context = ""
self_vision_result = None
if use_pinecone:
pinecone_status_lines.append(f"🔑 User ID: {uid}")
try:
from pinecone_client import PineconeClient, KEY_ONBOARDING, KEY_ROADMAP
pc = PineconeClient()
# Fetch onboarding context
fetch_result = pc.fetch_conversation(uid, KEY_ONBOARDING)
onboarding_context = fetch_result["text"]
fetch_status = fetch_result["status"]
pinecone_status_lines.append(f"📥 Onboarding fetch: {fetch_status}")
if onboarding_context:
from context_processor import count_words
pinecone_status_lines.append(f" Context: {count_words(onboarding_context)} words")
# Fetch roadmap context
fetch_roadmap = pc.fetch_conversation(uid, KEY_ROADMAP)
roadmap_context = fetch_roadmap["text"]
roadmap_status = fetch_roadmap["status"]
pinecone_status_lines.append(f"🗺️ Roadmap fetch: {roadmap_status}")
if roadmap_context:
from context_processor import count_words
pinecone_status_lines.append(f" Roadmap: {count_words(roadmap_context)} words")
try:
roadmap_data = json.loads(roadmap_context)
roadmap_id = roadmap_data.get("roadmap_id", "")
if roadmap_id:
pinecone_status_lines.append(f" Roadmap ID: {roadmap_id}")
except Exception:
roadmap_data = None
else:
pinecone_status_lines.append(" ⚠️ No roadmap found for this User ID")
except Exception as e:
pinecone_status_lines.append(f"⚠️ Fetch error: {e}")
# Resolve gender
gender = "male"
gl = str(gender_input).lower()
if "female" in gl or "woman" in gl or "girl" in gl:
gender = "female"
elif "male" in gl or "man" in gl or "boy" in gl:
gender = "male"
else:
gender = random.choice(["male", "female"])
milestones = []
for mid, mdesc, mval, mvis in [
(m1_id, m1_desc, m1_val, m1_visible),
(m2_id, m2_desc, m2_val, m2_visible),
(m3_id, m3_desc, m3_val, m3_visible),
(m4_id, m4_desc, m4_val, m4_visible),
]:
if mid and mid.strip():
milestones.append({
"milestone_id": mid.strip(),
"identity_statement": mdesc,
"market_value_display": mval,
})
if not milestones:
yield (None, None, None, None,
*empty_details,
gr.update(interactive=True),
"❌ No milestones configured.",
None, "\n".join(pinecone_status_lines), sv_empty)
return
ref_photo = None
if reference_photo is not None:
if isinstance(reference_photo, Image.Image):
ref_photo = reference_photo
else:
try:
ref_photo = Image.open(reference_photo)
except Exception:
ref_photo = None
if ref_photo and ref_photo.mode != "RGB":
ref_photo = ref_photo.convert("RGB")
images = [None, None, None, None]
details = ["", "", "", ""]
transcriptions = ["", "", "", ""]
total_start = time.time()
# ── Parse onboarding + roadmap for rich milestone details ──
parsed_onboarding = None
parsed_roadmap = None
if use_pinecone:
# Parse onboarding profile
if onboarding_context:
try:
parsed_onboarding = json.loads(onboarding_context)
except Exception:
parsed_onboarding = None
# roadmap_data was already parsed above (L1137); make it available
# If not fetched from Pinecone, try to see if we have it
try:
parsed_roadmap = roadmap_data # set at L1137 inside the Pinecone block
except NameError:
parsed_roadmap = None
# ── Override prompt variables from Pinecone roadmap/onboarding if available ──
if use_pinecone:
if parsed_roadmap:
# Override ICP
icp_raw = parsed_roadmap.get("icp_type", "high")
icp = "low_wage" if icp_raw == "low" else "high_wage"
# Override milestones list directly from the roadmap JSON
roadmap_milestones = parsed_roadmap.get("milestones", [])
if roadmap_milestones:
milestones = []
for rm in roadmap_milestones[:4]: # limit to at most 4
rmid = rm.get("milestone_id") or rm.get("label") or ""
rmdesc = rm.get("identity_statement") or rm.get("identity_label") or ""
rmval = rm.get("market_value_display") or ""
rmlabel = rm.get("identity_label") or ""
if rmid:
milestones.append({
"milestone_id": rmid.strip(),
"identity_statement": rmdesc,
"market_value_display": rmval,
"identity_label": rmlabel,
})
# Override domain
target_role = parsed_roadmap.get("target_role", "")
if target_role:
role_lower = target_role.lower()
if "data science" in role_lower or "analytics" in role_lower or "analyst" in role_lower:
domain = "Data Science"
elif "machine learning" in role_lower or "ml" in role_lower or "ai" in role_lower or "artificial intelligence" in role_lower:
domain = "Artificial Intelligence"
elif "devops" in role_lower or "cloud" in role_lower or "infrastructure" in role_lower:
domain = "Cloud Computing"
elif "mobile" in role_lower or "app" in role_lower or "android" in role_lower or "ios" in role_lower:
domain = "E-commerce"
elif "product" in role_lower:
domain = "Education Technology"
elif "bpo" in role_lower or "operations" in role_lower:
domain = "Operations / BPO"
elif "software" in role_lower or "web" in role_lower or "full stack" in role_lower or "developer" in role_lower or "engineer" in role_lower:
domain = "Software Development"
# Override user_goal
vision = parsed_roadmap.get("vision_profile", {})
top_motivation = vision.get("top_motivation", "")
vision_12mo = vision.get("vision_12mo", "")
roadmap_goal = f"{target_role}"
if top_motivation or vision_12mo:
details_parts = []
if top_motivation:
details_parts.append(top_motivation)
if vision_12mo:
details_parts.append(vision_12mo)
roadmap_goal += " — " + " ".join(details_parts)
if roadmap_goal.strip():
user_goal = roadmap_goal
if parsed_onboarding:
# Override Gender
user_name_val = parsed_onboarding.get("user_name", "")
female_indicators = ["Priya", "Priyanka", "Ananya", "Pooja", "Shreya", "Divya", "Anjali", "Neha", "Riya", "Fatima", "Ayesha", "Lakshmi", "Meera", "Nandini", "Tanvi", "Deepika", "Priyambada"]
if any(ind in user_name_val for ind in female_indicators):
gender = "female"
else:
gender = "male"
# Pre-build detail text for all milestones using both sources
for i, milestone in enumerate(milestones):
details[i] = build_rich_milestone_detail(
milestone_ui=milestone,
roadmap_dict=parsed_roadmap,
onboarding_profile=parsed_onboarding,
milestone_index=i,
total_milestones=len(milestones),
)
for i, milestone in enumerate(milestones):
mid = milestone["milestone_id"]
status_msg = f"⏳ Generating milestone {mid} ({i+1}/{len(milestones)})..."
yield (*images,
*details,
gr.update(interactive=False, value="⏳ Generating..."),
status_msg,
None, "\n".join(pinecone_status_lines), sv_empty)
# Extract skills/modules context from roadmap milestone to customize the image setting
skills_list = []
if parsed_roadmap:
for rm in parsed_roadmap.get("milestones", []):
if rm.get("milestone_id") == mid:
for mod in rm.get("modules", []):
mod_title = mod.get("title", "")
if mod_title:
skills_list.append(mod_title)
for skill in mod.get("skills", []):
s_title = skill.get("title", "")
if s_title:
skills_list.append(s_title)
break
skills_context = ", ".join(skills_list[:4]) if skills_list else ""
prompt_with_ref = build_prompt(
milestone_id=mid,
icp=icp,
domain=domain,
user_goal=user_goal,
has_reference_photo=True,
gender=gender,
milestone_label=milestone.get("identity_label"),
identity_statement=milestone.get("identity_statement"),
market_value_display=milestone.get("market_value_display"),
skills_context=skills_context,
)
transcriptions[i] = prompt_with_ref
prompt_scene_only = build_prompt(
milestone_id=mid,
icp=icp,
domain=domain,
user_goal=user_goal,
has_reference_photo=False,
gender=gender,
milestone_label=milestone.get("identity_label"),
identity_statement=milestone.get("identity_statement"),
market_value_display=milestone.get("market_value_display"),
skills_context=skills_context,
)
try:
result_img = generate_single_image(
prompt_with_ref if ref_photo else prompt_scene_only,
ref_photo,
api_key,
)
images[i] = result_img
except Exception as e:
print(f"Error generating {mid}: {e}")
traceback.print_exc()
elapsed = time.time() - total_start
successful = sum(1 for img in images if img is not None)
total_attempted = len(milestones)
# ── Pinecone STEP: Run Self Vision LLM + persist ──
if use_pinecone and raw_transcript:
pinecone_status_lines.append("")
pinecone_status_lines.append("🧠 Running Self Vision LLM...")
try:
sv_result = run_self_vision(
user_id=uid,
raw_transcription=raw_transcript,
)
if "self_vision_result" in sv_result:
self_vision_result = sv_result["self_vision_result"]
m = sv_result.get("metadata", {})
pinecone_status_lines.append(f"✅ LLM generation complete")
pinecone_status_lines.append(f"📤 Upsert: {m.get('upsert_status', '?')}")
pinecone_status_lines.append(f"📊 Context words: {m.get('context_word_count', '?')}")
pinecone_status_lines.append(f"📥 Onboarding used: {m.get('fetch_status', '?')}")
pinecone_status_lines.append(f"🗺️ Roadmap used: {m.get('fetch_roadmap_status', '?')}")
pinecone_status_lines.append(f"✂️ Truncation: {m.get('truncation_applied', False)}")
pinecone_status_lines.append(f"⏱️ Pipeline: {m.get('execution_time_ms', '?')}ms")
else:
pinecone_status_lines.append(f"⚠️ LLM error: {sv_result.get('error', 'unknown')}")
except Exception as e:
pinecone_status_lines.append(f"⚠️ Pipeline error: {e}")
elif use_pinecone and not raw_transcript:
# No transcript provided — just store the prompts as raw transcript
combined_prompts = "\n\n---\n\n".join(t for t in transcriptions if t)
try:
from pinecone_client import PineconeClient, KEY_SELF_VISION
pc = PineconeClient()
upsert_result = pc.upsert_conversation(uid, KEY_SELF_VISION, combined_prompts)
pinecone_status_lines.append(f"📤 Stored prompts as transcript: {upsert_result['status']}")
self_vision_result = {
"status": "Stored prompts only",
"message": "Prompts were stored to Pinecone as raw transcript. LLM synthesis was skipped because no raw transcription was provided."
}
except Exception as e:
pinecone_status_lines.append(f"⚠️ Upsert error: {e}")
self_vision_result = {
"status": "Upsert error",
"error": str(e)
}
else:
self_vision_result = {
"status": "Inactive",
"message": "Pinecone persistence and LLM synthesis were not run because no User ID was provided."
}
summary = (
f"✅ Done! {successful}/{total_attempted} images generated in {elapsed:.1f}s\n"
f"Face reference: {'used' if ref_photo else 'not provided'}\n"
f"Gender: {gender} | Domain: {domain} | ICP: {icp}"
)
if use_pinecone:
summary += f"\nPinecone: namespace={uid}"
# Build output JSON
output_json = {
"service": "self-vision",
"version": "v1.0",
"input_source": "roadmap",
"user_id": uid,
"generated_at": datetime.utcnow().isoformat(),
"domain": domain,
"icp": icp,
"gender": gender,
"face_reference_used": ref_photo is not None,
"milestones": [
{
"milestone_id": milestones[i]["milestone_id"],
"identity_statement": milestones[i].get("identity_statement", ""),
"market_value_display": milestones[i].get("market_value_display", ""),
"raw_prompt": transcriptions[i],
"success": images[i] is not None,
"image_base64": (
__import__('base64').b64encode(
(lambda img: (img.save(buf := __import__('io').BytesIO(), format='JPEG'), buf)[1])(images[i]).getvalue()
).decode() if images[i] is not None else None
),
}
for i in range(len(milestones))
],
"summary": {
"total_milestones": len(milestones),
"successful": sum(1 for img in images if img is not None),
"face_reference_used": ref_photo is not None,
}
}
# Save JSON to file so user can download it
output_json_path = "/tmp/self_vision_output.json"
os.makedirs(os.path.dirname(output_json_path), exist_ok=True)
with open(output_json_path, "w") as f:
json.dump(output_json, f, indent=2)
yield (*images,
*details,
gr.update(interactive=True, value="🚀 Generate Self-Vision"),
summary,
output_json_path,
"\n".join(pinecone_status_lines),
self_vision_result)
# ---------------------------------------------------------------------------
# Gradio UI
# ---------------------------------------------------------------------------
CUSTOM_CSS = """
/* ── Global dark overrides ── */
.gradio-container {
max-width: 1400px !important;
margin: 0 auto !important;
background: #0a0a1a !important;
}
.dark, body, .main, .app {
background: #0a0a1a !important;
}
/* ── Typography ── */
h1, h2, h3, h4, h5, .markdown h1, .markdown h2, .markdown h3, .markdown h4 {
color: #e2e8f0 !important;
}
p, span, label, .label-wrap span, .markdown p {
color: #cbd5e1 !important;
}
/* ── Section headers ── */
.section-header {
background: linear-gradient(135deg, #7c3aed 0%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
}
/* ── Input fields & dropdowns ── */
input, textarea, select,
.wrap input, .wrap textarea,
input[type="text"], input[type="password"],
.border-none input, .border-none textarea {
background: #131328 !important;
border: 1px solid #2d2d5e !important;
color: #e2e8f0 !important;
border-radius: 10px !important;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input:focus, textarea:focus {
border-color: #7c3aed !important;
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2) !important;
outline: none !important;
}
/* ── Dropdown menus ── */
.dropdown-container, .options, ul[role="listbox"] {
background: #131328 !important;
border: 1px solid #2d2d5e !important;
border-radius: 10px !important;
}
ul[role="listbox"] li {
color: #cbd5e1 !important;
padding: 10px 14px !important;
transition: background 0.15s ease;
}
ul[role="listbox"] li:hover, ul[role="listbox"] li[aria-selected="true"] {
background: rgba(124, 58, 237, 0.2) !important;
color: #f1f5f9 !important;
}
/* ── Groups / Accordion panels ── */
.form, .block, .panel, fieldset, .group {
background: transparent !important;
border: none !important;
}
.gr-group {
background: #0f0f25 !important;
border: 1px solid #1e1e48 !important;
border-radius: 14px !important;
padding: 16px !important;
margin-bottom: 8px !important;
}
/* ── Milestone output cards ── */
.milestone-card {
background: linear-gradient(145deg, #0f0f25 0%, #141432 100%) !important;
border: 1px solid #2d2d5e !important;
border-radius: 16px !important;
padding: 14px !important;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.milestone-card:hover {
border-color: #7c3aed !important;
box-shadow: 0 0 20px rgba(124, 58, 237, 0.15) !important;
}
/* ── Milestone detail text panels ── */
.milestone-detail {
background: linear-gradient(145deg, #0d0d22 0%, #12122e 100%) !important;
border: 1px solid #1e1e48 !important;
border-left: 3px solid #7c3aed !important;
border-radius: 12px !important;
padding: 18px 16px !important;
display: flex;
align-items: flex-start;
}
.milestone-detail h4, .milestone-detail .markdown h4 {
color: #a78bfa !important;
font-size: 1.05em !important;
margin: 0 0 10px 0 !important;
}
.milestone-detail p, .milestone-detail .markdown p {
color: #94a3b8 !important;
line-height: 1.65 !important;
margin: 6px 0 !important;
font-size: 0.92em !important;
}
.milestone-detail strong, .milestone-detail .markdown strong {
color: #c4b5fd !important;
}
/* ── Generate button ── */
.gen-btn {
background: linear-gradient(135deg, #7c3aed 0%, #6366f1 50%, #818cf8 100%) !important;
border: none !important;
font-size: 1.15em !important;
font-weight: 700 !important;
padding: 14px 28px !important;
border-radius: 14px !important;
color: #ffffff !important;
letter-spacing: 0.3px;
box-shadow: 0 4px 20px rgba(124, 58, 237, 0.35) !important;
transition: transform 0.15s ease, box-shadow 0.2s ease !important;
}
.gen-btn:hover {
transform: translateY(-1px) !important;
box-shadow: 0 6px 28px rgba(124, 58, 237, 0.5) !important;
}
.gen-btn:active {
transform: translateY(0px) !important;
}
/* ── Status box ── */
.status-box textarea {
font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace !important;
font-size: 0.88em !important;
background: #0a0a1a !important;
color: #a78bfa !important;
border: 1px solid #1e1e48 !important;
border-radius: 10px !important;
line-height: 1.6 !important;
}
/* ── Image upload area ── */
.image-container, .upload-container {
background: #0f0f25 !important;
border: 2px dashed #2d2d5e !important;
border-radius: 14px !important;
transition: border-color 0.2s ease;
}
.image-container:hover, .upload-container:hover {
border-color: #7c3aed !important;
}
/* ── Checkbox hide ── */
.checkbox-container { display: none; }
/* ── Image output styling ── */
.milestone-card img {
border-radius: 12px !important;
}
/* ── Labels ── */
.label-wrap {
color: #a78bfa !important;
}
.label-wrap span {
color: #a78bfa !important;
font-weight: 600 !important;
font-size: 0.9em !important;
}
/* ── Separator lines ── */
hr {
border-color: #1e1e48 !important;
opacity: 0.5;
}
/* ── Title area ── */
.title-area {
text-align: center;
padding: 8px 0 20px 0;
}
.title-area h1 {
background: linear-gradient(135deg, #a78bfa 0%, #818cf8 40%, #c084fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: 2em !important;
}
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #0a0a1a; }
::-webkit-scrollbar-thumb { background: #2d2d5e; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #7c3aed; }
"""
with gr.Blocks(
title="Vidya Self-Vision — POC",
theme=gr.themes.Base(
primary_hue=gr.themes.colors.violet,
secondary_hue=gr.themes.colors.indigo,
neutral_hue=gr.themes.colors.slate,
font=gr.themes.GoogleFont("Inter"),
font_mono=gr.themes.GoogleFont("JetBrains Mono"),
).set(
body_background_fill="#0a0a1a",
body_background_fill_dark="#0a0a1a",
background_fill_primary="#0f0f25",
background_fill_primary_dark="#0f0f25",
background_fill_secondary="#131328",
background_fill_secondary_dark="#131328",
block_background_fill="#0f0f25",
block_background_fill_dark="#0f0f25",
block_border_color="#1e1e48",
block_border_color_dark="#1e1e48",
block_label_text_color="#a78bfa",
block_label_text_color_dark="#a78bfa",
block_label_background_fill="#0a0a1a",
block_label_background_fill_dark="#0a0a1a",
block_title_text_color="#e2e8f0",
block_title_text_color_dark="#e2e8f0",
body_text_color="#cbd5e1",
body_text_color_dark="#cbd5e1",
body_text_color_subdued="#64748b",
body_text_color_subdued_dark="#64748b",
input_background_fill="#131328",
input_background_fill_dark="#131328",
input_border_color="#2d2d5e",
input_border_color_dark="#2d2d5e",
input_border_color_focus="#7c3aed",
input_border_color_focus_dark="#7c3aed",
input_placeholder_color="#64748b",
input_placeholder_color_dark="#64748b",
panel_background_fill="#0d0d22",
panel_background_fill_dark="#0d0d22",
panel_border_color="#1e1e48",
panel_border_color_dark="#1e1e48",
button_primary_background_fill="linear-gradient(135deg, #7c3aed 0%, #6366f1 100%)",
button_primary_background_fill_dark="linear-gradient(135deg, #7c3aed 0%, #6366f1 100%)",
button_primary_text_color="#ffffff",
button_primary_text_color_dark="#ffffff",
button_primary_border_color="transparent",
button_primary_border_color_dark="transparent",
button_secondary_background_fill="#1e1e48",
button_secondary_background_fill_dark="#1e1e48",
button_secondary_text_color="#cbd5e1",
button_secondary_text_color_dark="#cbd5e1",
border_color_primary="#2d2d5e",
border_color_primary_dark="#2d2d5e",
border_color_accent="#7c3aed",
border_color_accent_dark="#7c3aed",
color_accent="#7c3aed",
color_accent_soft="rgba(124, 58, 237, 0.15)",
color_accent_soft_dark="rgba(124, 58, 237, 0.15)",
link_text_color="#a78bfa",
link_text_color_dark="#a78bfa",
loader_color="#7c3aed",
loader_color_dark="#7c3aed",
shadow_drop="0 4px 12px rgba(0, 0, 0, 0.4)",
shadow_drop_lg="0 8px 24px rgba(0, 0, 0, 0.5)",
),
css=CUSTOM_CSS,
) as demo:
gr.Markdown(
"""
<div class="title-area">
# 🔮 Vidya Self-Vision — POC
Milestone Image Generation · Cross-POC Conversation Persistence
</div>
"""
)
with gr.Row():
# ── LEFT COLUMN: Controls ──
with gr.Column(scale=1):
gr.Markdown("### 🔗 Cross-POC Persistence")
roadmap_id_input = gr.Textbox(
label="🗺️ Roadmap ID",
placeholder="e.g. ai_roadmap_20260611162618_d40b3260",
lines=1,
info="Enter a Roadmap ID to auto-resolve the User ID",
)
user_id_input = gr.Textbox(
label="🔑 User ID (UUID from Onboarding)",
placeholder="e.g. 550e8400-e29b-41d4-a716-446655440000",
lines=1,
)
with gr.Accordion("📝 Raw Transcription (optional)", open=False):
raw_transcription_input = gr.Textbox(
label="Self-Vision session transcript",
placeholder="Paste the raw self-vision transcript here for Pinecone storage + LLM analysis…",
lines=6,
)
gr.Markdown("---")
gr.Markdown("### 👤 Persona & Configuration")
persona_selector = gr.Dropdown(
choices=PERSONA_CHOICES,
value="-- Select an Example Persona --",
label="🎯 Select Example Learner Persona (Auto-fills inputs)",
interactive=True,
)
with gr.Accordion("Detailed Career Milestones", open=False):
milestone_details_md = gr.Markdown(value="*Select a persona to see detailed career trajectory.*")
user_name = gr.Textbox(
label="✏️ Your Name",
placeholder="Enter your name here",
lines=1,
)
user_goal = gr.Textbox(
label="🎯 Target Career Ambition",
placeholder="e.g., get an engineering role at a product company like Swiggy",
lines=2,
)
domain = gr.Dropdown(
choices=DOMAIN_CHOICES,
value="Software Development",
label="💼 Professional Domain",
interactive=True,
)
icp = gr.Dropdown(
choices=ICP_CHOICES,
value="high_wage",
label="📊 ICP (Income Customer Profile)",
interactive=True,
)
gender_input = gr.Dropdown(
choices=["male", "female"],
value="male",
label="👤 Gender",
interactive=True,
)
reference_photo = gr.Image(
label="📸 Reference Photo (optional — enables face personalization)",
type="pil",
height=200,
)
gr.Markdown("---")
gr.Markdown("### 🏁 Milestone Configuration")
# Milestone 1
with gr.Group(visible=True) as m1_group:
gr.Markdown("**Milestone 1**")
m1_visible = gr.Checkbox(value=True, visible=False)
m1_id = gr.Textbox(label="ID", value="L1", lines=1)
m1_desc = gr.Textbox(
label="Identity Statement",
value="You've landed your first tech job — your engineering journey begins here",
lines=2,
)
m1_val = gr.Textbox(label="Market Value", value="6-12 LPA", lines=1)
# Milestone 2
with gr.Group(visible=True) as m2_group:
gr.Markdown("**Milestone 2**")
m2_visible = gr.Checkbox(value=True, visible=False)
m2_id = gr.Textbox(label="ID", value="L2", lines=1)
m2_desc = gr.Textbox(
label="Identity Statement",
value="You think and ship like an engineer — your team trusts your technical judgment",
lines=2,
)
m2_val = gr.Textbox(label="Market Value", value="12-22 LPA", lines=1)
# Milestone 3
with gr.Group(visible=True) as m3_group:
gr.Markdown("**Milestone 3**")
m3_visible = gr.Checkbox(value=True, visible=False)
m3_id = gr.Textbox(label="ID", value="L3", lines=1)
m3_desc = gr.Textbox(
label="Identity Statement",
value="You own features end-to-end — peers come to you when things get hard",
lines=2,
)
m3_val = gr.Textbox(label="Market Value", value="22-40 LPA", lines=1)
# Milestone 4
with gr.Group(visible=True) as m4_group:
gr.Markdown("**Milestone 4**")
m4_visible = gr.Checkbox(value=True, visible=False)
m4_id = gr.Textbox(label="ID", value="L4", lines=1)
m4_desc = gr.Textbox(
label="Identity Statement",
value="You set the technical direction — leaders look to you to define what gets built next",
lines=2,
)
m4_val = gr.Textbox(label="Market Value", value="40-75 LPA", lines=1)
gr.Markdown("---")
# --- Developer tools accordion ---
with gr.Accordion("🛠️ Pinecone Developer Utilities", open=False):
gr.Markdown(
"Manually upload/upsert context data (Onboarding profile or Roadmap JSON) to Pinecone."
)
dev_key = gr.Dropdown(
choices=[
"Roadmap (roadmap_output)",
"Onboarding (onboarding_conversation)",
"Self-Vision (self_vision_conversation)"
],
value="Roadmap (roadmap_output)",
label="Context Type",
)
dev_user_id = gr.Textbox(
label="Target User ID",
placeholder="Enter User ID",
lines=1,
)
dev_content = gr.Textbox(
label="JSON or Text Content",
placeholder="Paste the raw JSON or text content here...",
lines=8,
)
dev_upsert_btn = gr.Button("Upsert to Pinecone", variant="secondary")
dev_status = gr.Textbox(
label="Upsert Status",
interactive=False,
lines=2,
)
gr.Markdown("---")
gen_btn = gr.Button(
"🚀 Generate Self-Vision",
variant="primary",
elem_classes=["gen-btn"],
)
status_box = gr.Textbox(
label="📋 Status",
lines=4,
interactive=False,
elem_classes=["status-box"],
)
output_json_file = gr.File(
label="📥 Download Output JSON",
visible=True
)
# ── RIGHT COLUMN: Output images & Pinecone status ──
with gr.Column(scale=2):
gr.Markdown("### 🖼️ Milestone Visualizations (9:16 portrait)")
with gr.Row():
with gr.Column(scale=2, elem_classes=["milestone-card"]):
m1_image = gr.Image(label="Milestone 1", type="pil", height=400)
with gr.Column(scale=1, elem_classes=["milestone-detail"]):
m1_detail = gr.Markdown(value="")
with gr.Column(scale=2, elem_classes=["milestone-card"]):
m2_image = gr.Image(label="Milestone 2", type="pil", height=400)
with gr.Column(scale=1, elem_classes=["milestone-detail"]):
m2_detail = gr.Markdown(value="")
with gr.Row():
with gr.Column(scale=2, elem_classes=["milestone-card"]):
m3_image = gr.Image(label="Milestone 3", type="pil", height=400)
with gr.Column(scale=1, elem_classes=["milestone-detail"]):
m3_detail = gr.Markdown(value="")
with gr.Column(scale=2, elem_classes=["milestone-card"]):
m4_image = gr.Image(label="Milestone 4", type="pil", height=400)
with gr.Column(scale=1, elem_classes=["milestone-detail"]):
m4_detail = gr.Markdown(value="")
gr.Markdown("### 🧠 Pinecone Integration & Self-Vision Synthesis")
with gr.Row():
pinecone_status = gr.Textbox(
label="📡 Pinecone & LLM Pipeline Status",
lines=6,
interactive=False,
elem_classes=["status-box"],
)
with gr.Row():
self_vision_result_box = gr.JSON(
label="🔮 Self Vision LLM Result",
value={
"status": "Awaiting generation...",
"message": "Enter your User ID and Raw Transcription on the left column, then click 'Generate Self-Vision' to run the pipeline."
}
)
# ── Image Gen Event handlers ──
# Persona selector auto-fills form fields
persona_selector.change(
fn=on_persona_change,
inputs=[persona_selector],
outputs=[user_goal, domain, icp, gender_input, milestone_details_md],
).then(
fn=get_milestone_defaults,
inputs=[icp, domain, persona_selector],
outputs=[
m1_group, m1_id, m1_desc, m1_val,
m2_group, m2_id, m2_desc, m2_val,
m3_group, m3_id, m3_desc, m3_val,
m4_group, m4_id, m4_desc, m4_val,
],
)
# Gender selector dynamically updates pronouns in the user goal
gender_input.change(
fn=on_gender_change,
inputs=[user_goal, gender_input],
outputs=[user_goal],
)
# Auto-fetch from Pinecone when user_id is changed/submitted/unfocused
user_id_input.blur(
fn=on_user_id_change,
inputs=[user_id_input],
outputs=[
raw_transcription_input,
user_name,
user_goal,
domain,
icp,
gender_input,
status_box,
m1_group, m1_id, m1_desc, m1_val,
m2_group, m2_id, m2_desc, m2_val,
m3_group, m3_id, m3_desc, m3_val,
m4_group, m4_id, m4_desc, m4_val,
milestone_details_md,
],
)
user_id_input.submit(
fn=on_user_id_change,
inputs=[user_id_input],
outputs=[
raw_transcription_input,
user_name,
user_goal,
domain,
icp,
gender_input,
status_box,
m1_group, m1_id, m1_desc, m1_val,
m2_group, m2_id, m2_desc, m2_val,
m3_group, m3_id, m3_desc, m3_val,
m4_group, m4_id, m4_desc, m4_val,
milestone_details_md,
],
)
# Auto-resolve User ID when Roadmap ID is entered
roadmap_id_outputs = [
user_id_input,
raw_transcription_input,
user_name,
user_goal,
domain,
icp,
gender_input,
status_box,
m1_group, m1_id, m1_desc, m1_val,
m2_group, m2_id, m2_desc, m2_val,
m3_group, m3_id, m3_desc, m3_val,
m4_group, m4_id, m4_desc, m4_val,
milestone_details_md,
]
roadmap_id_input.blur(
fn=on_roadmap_id_change,
inputs=[roadmap_id_input],
outputs=roadmap_id_outputs,
)
roadmap_id_input.submit(
fn=on_roadmap_id_change,
inputs=[roadmap_id_input],
outputs=roadmap_id_outputs,
)
# ICP or Domain change updates milestone defaults
gr.on(
triggers=[icp.change, domain.change],
fn=get_milestone_defaults,
inputs=[icp, domain, persona_selector],
outputs=[
m1_group, m1_id, m1_desc, m1_val,
m2_group, m2_id, m2_desc, m2_val,
m3_group, m3_id, m3_desc, m3_val,
m4_group, m4_id, m4_desc, m4_val,
],
)
# Generate button
gen_btn.click(
fn=run_generation,
inputs=[
user_id_input, raw_transcription_input,
user_goal, domain, icp, gender_input, reference_photo,
m1_visible, m1_id, m1_desc, m1_val,
m2_visible, m2_id, m2_desc, m2_val,
m3_visible, m3_id, m3_desc, m3_val,
m4_visible, m4_id, m4_desc, m4_val,
],
outputs=[
m1_image, m2_image, m3_image, m4_image,
m1_detail, m2_detail, m3_detail, m4_detail,
gen_btn, status_box,
output_json_file,
pinecone_status,
self_vision_result_box,
],
)
# Developer utilities callbacks
dev_upsert_btn.click(
fn=on_dev_upsert,
inputs=[dev_key, dev_user_id, dev_content],
outputs=[dev_status],
)
user_id_input.change(
fn=lambda uid: uid,
inputs=[user_id_input],
outputs=[dev_user_id],
)
demo.load(
fn=load_data_from_url,
inputs=None,
outputs=[
roadmap_id_input,
user_id_input,
raw_transcription_input,
user_name,
user_goal,
domain,
icp,
gender_input,
status_box,
m1_group, m1_id, m1_desc, m1_val,
m2_group, m2_id, m2_desc, m2_val,
m3_group, m3_id, m3_desc, m3_val,
m4_group, m4_id, m4_desc, m4_val,
milestone_details_md,
]
)
# ─────────────────────────────────────────────────────────────────────
# Startup — HF Spaces compatible + local FastAPI REST endpoint
# ─────────────────────────────────────────────────────────────────────
# HF Spaces (sdk: gradio) discovers the `demo` variable at module level
# and calls demo.launch() automatically. The REST API endpoint is only
# available when running locally via `python app.py`.
# ─────────────────────────────────────────────────────────────────────
demo.queue()
if __name__ == "__main__":
import os as _os
# Detect Hugging Face Spaces environment
if _os.getenv("SPACE_ID"):
# Running on HF Spaces — use Gradio's built-in launcher
demo.launch(server_name="0.0.0.0", server_port=7860)
else:
# Running locally — mount Gradio inside FastAPI for REST endpoint
import uvicorn
import fastapi
_app = fastapi.FastAPI()
@_app.post("/api/self_vision")
async def api_self_vision(request: fastapi.Request):
"""POST /api/self_vision — programmatic Self Vision pipeline."""
try:
body = await request.json()
except Exception:
return fastapi.responses.JSONResponse(
status_code=400,
content={"error": "invalid JSON body"},
)
err = validate_request(body)
if err:
return fastapi.responses.JSONResponse(
status_code=err["status_code"],
content={"error": err["error"]},
)
result = run_self_vision(
user_id=body["user_id"].strip(),
raw_transcription=body["raw_transcription"].strip(),
lesson_context=body.get("lesson_context", ""),
session_metadata=body.get("session_metadata"),
)
if "error" in result and "self_vision_result" not in result:
status_code = result.get("status_code", 500)
return fastapi.responses.JSONResponse(
status_code=status_code,
content={"error": result["error"]},
)
return fastapi.responses.JSONResponse(
status_code=200,
content=result,
)
@_app.post("/api/upsert_context")
async def api_upsert_context(request: fastapi.Request):
"""POST /api/upsert_context — programmatic upsert of context to Pinecone."""
try:
body = await request.json()
except Exception:
return fastapi.responses.JSONResponse(
status_code=400,
content={"error": "invalid JSON body"},
)
user_id = body.get("user_id", "").strip()
key = body.get("key", "").strip()
text = body.get("text", "").strip()
if not user_id or not key or not text:
return fastapi.responses.JSONResponse(
status_code=400,
content={"error": "user_id, key, and text are required fields"},
)
from pinecone_client import KEY_ONBOARDING, KEY_ROADMAP, KEY_SELF_VISION
key_map = {
"onboarding": KEY_ONBOARDING,
"roadmap": KEY_ROADMAP,
"self_vision": KEY_SELF_VISION,
KEY_ONBOARDING: KEY_ONBOARDING,
KEY_ROADMAP: KEY_ROADMAP,
KEY_SELF_VISION: KEY_SELF_VISION
}
resolved_key = key_map.get(key.lower())
if not resolved_key:
return fastapi.responses.JSONResponse(
status_code=400,
content={"error": f"invalid key '{key}'. Must be 'onboarding', 'roadmap', or 'self_vision'"},
)
# Try to validate JSON if it's roadmap or onboarding
if resolved_key in (KEY_ROADMAP, KEY_ONBOARDING):
try:
json.loads(text)
except Exception as e:
return fastapi.responses.JSONResponse(
status_code=400,
content={"error": f"text is not valid JSON ({e})"},
)
try:
from pinecone_client import PineconeClient
pc = PineconeClient()
res = pc.upsert_conversation(user_id, resolved_key, text)
if res.get("status") == "success":
return fastapi.responses.JSONResponse(
status_code=200,
content={
"status": "success",
"message": f"Successfully upserted context for key '{resolved_key}' under user '{user_id}'",
"elapsed_ms": res.get("elapsed_ms")
},
)
else:
return fastapi.responses.JSONResponse(
status_code=500,
content={"error": "Failed to upsert context to Pinecone"},
)
except Exception as e:
return fastapi.responses.JSONResponse(
status_code=500,
content={"error": str(e)},
)
_app = gr.mount_gradio_app(_app, demo, path="/")
uvicorn.run(_app, host="0.0.0.0", port=7860)