import os # Disable experimental Gradio Node.js SSR proxy on Hugging Face Spaces os.environ["GRADIO_SSR_MODE"] = "false" os.environ["GRADIO_SERVER_NAME"] = "0.0.0.0" os.environ["GRADIO_SERVER_PORT"] = "7860" import sys import json import gradio as gr import pandas as pd import plotly.express as px # Ensure tech_radar package is importable sys.path.insert(0, os.path.abspath(".")) from tech_radar.db.database import DatabaseManager from tech_radar.db.vector_store import SemanticVectorStore from tech_radar.agents.evaluator_agent import EvaluatorAgent from tech_radar.agents.market_analyst import MarketAnalystAgent from tech_radar.scrapers.seeder import seed_database from tech_radar.mcp.tools import ( tool_search_tech_jobs, tool_analyze_skill_gap, tool_generate_resume_patch, tool_get_market_insights, tool_generate_interview_prep_kit ) # Initialize DB & Vector Engine db, vector_store = seed_database(db_path="tech_radar.db") evaluator = EvaluatorAgent() market_analyst = MarketAnalystAgent(db) # Load 3D Cyber Web App HTML web_html_path = os.path.join("tech_radar", "ui", "web_app.html") web_html_content = "" if os.path.exists(web_html_path): with open(web_html_path, "r", encoding="utf-8") as f: web_html_content = f.read() # Gradio Callbacks def search_jobs_fn(domain, city, query, min_exp): if query: semantic_results = vector_store.search_semantic(query=query, domain=domain, city=city, top_k=30) jobs_list = [j for j, score in semantic_results] else: all_jobs = db.search_jobs(domain=domain, city=city, limit=200) jobs_list = [j for j in all_jobs if j.experience_min_years <= min_exp] data = [] for j in jobs_list: data.append({ "Job ID": j.id, "Title": j.title, "Company": j.company, "Domain": j.tech_domain, "City": j.city, "Salary (LPA)": f"โ‚น{j.salary_min_lpa}L - โ‚น{j.salary_max_lpa}L", "Exp": f"{j.experience_min_years}-{j.experience_max_years} Yrs", "Work Mode": j.work_mode, "Tech Stack": ", ".join(j.tech_stack) }) return pd.DataFrame(data) def market_analytics_fn(domain, city): insights = market_analyst.generate_market_report(city=city, domain=domain) df_frameworks = pd.DataFrame(insights.top_demanded_frameworks) if not df_frameworks.empty: fig = px.bar( df_frameworks, x="percentage", y="skill", orientation="h", title=f"Top Skill Demand % in {city} ({domain})", labels={"percentage": "Demand %", "skill": "Framework"}, color="percentage", color_continuous_scale="Purples" ) fig.update_layout(template="plotly_dark", yaxis={'categoryorder':'total ascending'}) else: fig = px.bar(title="No data available") summary_md = f""" ### ๐Ÿ“Š Hiring Summary for {city} ({domain}) - **Total Active Jobs Tracked**: `{insights.total_active_jobs}` - **Average Salary**: `{insights.avg_salary_lpa} LPA` - **Salary Range**: `{insights.salary_range}` - **Market Trend**: {insights.growth_trend} **Top Employers Hiring**: {', '.join(insights.top_employers)} """ return summary_md, fig def evaluate_ats_fn(resume_text, job_id): job = db.get_job_by_id(job_id) if not job: return "Job ID not found", "", "", "", "" report = evaluator.evaluate_skill_gap(resume_text=resume_text, candidate_skills=[], job=job) patch = evaluator.generate_resume_patch(resume_text=resume_text, job=job) prep = evaluator.generate_interview_prep(job=job) matched_str = ", ".join(report.matched_skills) or "None" missing_str = ", ".join(report.missing_skills) or "None" bullets_md = "" for b in patch.tailored_bullets: bullets_md += f"- **Original**: {b['original']}\n - **Tailored**: `{b['tailored']}`\n" prep_md = "" for q in prep.technical_questions: prep_md += f"**Q ({q.category})**: {q.question}\n- *Key Answer Points*: {', '.join(q.ideal_answer_points)}\n\n" return f"{report.match_percentage}%", matched_str, missing_str, bullets_md, prep_md def execute_mcp_tool_fn(tool_name, domain, city, query, job_id, resume_text): if tool_name == "search_tech_jobs": res = tool_search_tech_jobs(domain=domain, city=city, query=query) elif tool_name == "analyze_skill_gap": res = tool_analyze_skill_gap(resume_text=resume_text, target_job_id=job_id) elif tool_name == "generate_tailored_resume_patch": res = tool_generate_resume_patch(resume_text=resume_text, target_job_id=job_id) elif tool_name == "get_market_insights": res = tool_get_market_insights(domain=domain, city=city) elif tool_name == "generate_interview_prep_kit": res = tool_generate_interview_prep_kit(target_job_id=job_id) else: res = json.dumps({"error": "Unknown tool"}) try: return json.dumps(json.loads(res), indent=2) except: return res # Build Gradio UI theme = gr.themes.Soft( primary_hue="cyan", secondary_hue="purple", neutral_hue="slate" ) with gr.Blocks(theme=theme, title="TechRadar MCP โ€” Universal Tech Hiring Intelligence") as demo: gr.Markdown(""" # โšก TechRadar MCP โ€” Universal Tech Hiring Intelligence ### Autonomous Model Context Protocol Ecosystem across Indian Tech Hubs & Remote """) with gr.Tabs(): with gr.TabItem("๐Ÿ“ก Universal Tech Job Radar"): with gr.Row(): domain_dropdown = gr.Dropdown(["All", "Backend Engineering", "Frontend Engineering", "Full Stack Engineering", "Cloud & DevOps", "Data Engineering", "AI/ML & GenAI", "Mobile Engineering"], value="All", label="Tech Domain") city_dropdown = gr.Dropdown(["All", "Bengaluru", "Pune", "Hyderabad", "Gurgaon", "Mumbai", "Chennai", "Remote"], value="All", label="City / Region") query_input = gr.Textbox(placeholder="Search Go, React, vLLM...", label="Semantic Search") exp_slider = gr.Slider(0, 12, value=10, step=1, label="Max Experience (Yrs)") search_btn = gr.Button("๐Ÿ” Search Tech Jobs", variant="primary") job_table = gr.Dataframe(label="Matching Active Roles") search_btn.click( fn=search_jobs_fn, inputs=[domain_dropdown, city_dropdown, query_input, exp_slider], outputs=job_table ) with gr.TabItem("๐Ÿ“Š Market Analytics"): with gr.Row(): an_domain = gr.Dropdown(["All", "Backend Engineering", "Frontend Engineering", "Cloud & DevOps", "AI/ML & GenAI"], value="All", label="Domain") an_city = gr.Dropdown(["All", "Bengaluru", "Pune", "Hyderabad", "Gurgaon", "Remote"], value="All", label="City") an_btn = gr.Button("๐Ÿ“Š Generate Market Report", variant="primary") with gr.Row(): an_summary = gr.Markdown() an_chart = gr.Plot() an_btn.click( fn=market_analytics_fn, inputs=[an_domain, an_city], outputs=[an_summary, an_chart] ) with gr.TabItem("๐ŸŽฏ ATS Resume & Skill Gap Evaluator"): all_jobs = db.get_all_jobs() job_ids = [j.id for j in all_jobs] with gr.Row(): with gr.Column(): resume_input = gr.Textbox( value="Senior Backend Engineer with 4 years experience building Go microservices, REST APIs, Docker, and PostgreSQL in Bengaluru.", lines=8, label="Paste Candidate Resume Text" ) job_id_select = gr.Dropdown(job_ids, value=job_ids[0] if job_ids else "", label="Select Target Job ID") eval_btn = gr.Button("๐Ÿš€ Analyze ATS Match & Skill Gap", variant="primary") with gr.Column(): match_score_out = gr.Textbox(label="Match Percentage") matched_skills_out = gr.Textbox(label="Matched Skills") missing_skills_out = gr.Textbox(label="Missing Dealbreaker Skills") bullets_out = gr.Markdown(label="ATS Tailored Resume Bullets") prep_out = gr.Markdown(label="Technical Interview Q&A Prep") eval_btn.click( fn=evaluate_ats_fn, inputs=[resume_input, job_id_select], outputs=[match_score_out, matched_skills_out, missing_skills_out, bullets_out, prep_out] ) with gr.TabItem("๐Ÿงช FastMCP Server Tester"): with gr.Row(): with gr.Column(): tool_select = gr.Dropdown(["search_tech_jobs", "analyze_skill_gap", "generate_tailored_resume_patch", "get_market_insights", "generate_interview_prep_kit"], value="search_tech_jobs", label="Select MCP Tool") mcp_domain = gr.Textbox(value="Backend Engineering", label="domain (if search/insights)") mcp_city = gr.Textbox(value="Bengaluru", label="city (if search/insights)") mcp_query = gr.Textbox(value="Go Distributed Systems", label="query (if search)") mcp_job_id = gr.Textbox(value=job_ids[0] if job_ids else "BLR-BACKEND-101", label="target_job_id") mcp_resume = gr.Textbox(value="Go & Docker developer", label="resume_text") exec_mcp_btn = gr.Button("โšก Execute FastMCP Tool", variant="primary") with gr.Column(): mcp_output_json = gr.Code(language="json", label="JSON-RPC Tool Response") exec_mcp_btn.click( fn=execute_mcp_tool_fn, inputs=[tool_select, mcp_domain, mcp_city, mcp_query, mcp_job_id, mcp_resume], outputs=mcp_output_json ) # Launch Gradio App if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)