""" CodeMentor PRO - Main Gradio App """ import os import difflib import tempfile import gradio as gr from config import SUPPORTED_LANGUAGES, EXPLANATION_LEVELS from llm import call_groq_chat, call_groq_vision_ocr, LLMNotConfiguredError from agent import ( generate_breakdown, generate_plan, cleanup_code, suggest_optimal_solution, search_documentation, find_resources, extract_code_topic, ) from error_detector import detect_errors from sandbox import benchmark_two_versions, verify_fix_equivalence from store import get_progress_snapshot from prompts import FOLLOWUP_SYSTEM_PROMPT_TEMPLATE # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _validate_inputs(code: str) -> bool: if not code or not code.strip(): gr.Warning("Please paste a code snippet before running an analysis.") return False return True def _greeting_message(language=None, level=None): if language and level: text = ( f"Got it! I've analyzed your **{language}** code at **{level}** level. " "Ask me anything about it โ bugs, alternate approaches, or just \"explain line 5 again\"." ) else: text = ( "๐ Hi, I'm CodeMentor! Head over to the **Analyze Code** tab first and run an analysis โ " "once that's done, come back here and I'll remember the context for follow-up questions." ) return [{"role": "assistant", "content": text}] def _safe_call(label, fn, *args, **kwargs): """Runs fn and never lets an exception escape to the UI as a raw traceback.""" try: return fn(*args, **kwargs), None except LLMNotConfiguredError as e: return None, f"{label} unavailable โ {e}" except Exception as e: return None, f"{label} failed โ {e}" def _citation_cards_html(citations: list) -> str: if not citations: return "
No citations found.
" cards = [] for i, c in enumerate(citations, start=1): thumb = ( f"โ ๏ธ {err}
" elif doc_findings: blocks = [] for d in doc_findings: blocks.append(f"{d['summary']}
") blocks.append(_citation_cards_html(d.get("citations", []))) docs_html = "".join(blocks) else: docs_html = "No unfamiliar libraries detected โ nothing to look up.
" # --- Resource finder (topic-based, not just language-based) --- progress(0.9, desc="Identifying core topic...") topic, err = _safe_call("Topic extraction", extract_code_topic, code, language) topic = topic or f"{language} programming" progress(0.95, desc=f"Finding resources on {topic}...") resources, err = _safe_call("Resource search", find_resources, language, topic) if err: resources_html = f"โ ๏ธ {err}
" elif resources and resources.get("ok"): topic_header = ( f"" f"๐ Topic identified: {topic}
" ) resources_html = topic_header + _citation_cards_html(resources.get("citations", [])) else: resources_html = f"No resources found for {topic} this time โ try again in a moment.
" # --- Downloadable report --- report_path = None try: report_path = build_session_report( code, language, level, breakdown_md, analogy_md, errors_md, cleanup_md, optimal_md, docs_html, resources_html, ) except Exception as e: gr.Warning(f"Could not build downloadable report: {e}") progress(1.0, desc="Done!") return ( breakdown_md, analogy_md, errors_md, cleanup_md, optimal_md, docs_html, resources_html, report_path, gr.update(visible=False), gr.update(visible=False), ) # --------------------------------------------------------------------------- # Tab 2 callbacks โ Chat / Follow-up # --------------------------------------------------------------------------- def on_chat_send(message, chat_history, last_code, last_language, last_level): chat_history = chat_history or [] if not message or not message.strip(): return chat_history, "" if not last_code: gr.Warning("Analyze a code snippet in the 'Analyze Code' tab first so I have context for follow-ups.") chat_history = chat_history + [ {"role": "user", "content": message}, {"role": "assistant", "content": "โ ๏ธ I don't have any code to discuss yet โ please run an analysis in the **Analyze Code** tab first."}, ] return chat_history, "" system_prompt = FOLLOWUP_SYSTEM_PROMPT_TEMPLATE.format( level=last_level or "Intermediate", language=last_language or "python", code=last_code, ) api_history = chat_history try: reply = call_groq_chat(system_prompt, api_history, message) except LLMNotConfiguredError as e: reply = f"โ ๏ธ {e}" except Exception as e: reply = f"โ ๏ธ Something went wrong answering that: {e}" chat_history = chat_history + [ {"role": "user", "content": message}, {"role": "assistant", "content": reply}, ] return chat_history, "" # --------------------------------------------------------------------------- # Tab 3 callbacks โ My Progress # --------------------------------------------------------------------------- def on_refresh_progress(): try: return get_progress_snapshot() except Exception as e: gr.Warning(f"Could not refresh progress: {e}") return gr.update(), gr.update(), gr.update() # --------------------------------------------------------------------------- # UI # --------------------------------------------------------------------------- with gr.Blocks(title="CodeMentor PRO") as demo: gr.Markdown("# ๐งโ๐ป CodeMentor PRO\nYour personal, execution-verified coding coach.") state_last_code = gr.State("") _initial_table, _initial_chart, _initial_summary = get_progress_snapshot() state_last_language = gr.State(SUPPORTED_LANGUAGES[0]) state_last_level = gr.State(EXPLANATION_LEVELS[0]) with gr.Tab("Analyze Code"): with gr.Row(): with gr.Column(scale=2): code_input = gr.Textbox(label="Paste your code", lines=16, placeholder="Paste your code here, or upload a screenshot belowโฆ") with gr.Accordion("๐ธ Or upload a code screenshot", open=False): image_input = gr.Image(label="Code screenshot", type="filepath", height=240) extract_btn = gr.Button("๐ Extract Code from Image", variant="secondary") with gr.Column(scale=1): language_dd = gr.Dropdown(SUPPORTED_LANGUAGES, value=SUPPORTED_LANGUAGES[0], label="Language") level_dd = gr.Dropdown(EXPLANATION_LEVELS, value=EXPLANATION_LEVELS[0], label="Explanation level") plan_btn = gr.Button("1. Preview Plan", variant="primary") plan_output = gr.Markdown(visible=False) with gr.Row(visible=False) as plan_actions: approve_btn = gr.Button("โ Approve & Run Analysis", variant="primary") cancel_btn = gr.Button("โ Cancel") with gr.Accordion("๐ Breakdown", open=True): breakdown_out = gr.Markdown() with gr.Accordion("๐ก Analogy", open=False): analogy_out = gr.Markdown() with gr.Accordion("๐ Errors & Weakness Review", open=True): errors_out = gr.Markdown() with gr.Accordion("๐งน Cleanup Diff", open=False): cleanup_out = gr.Markdown() with gr.Accordion("โก Optimal Solution", open=False): optimal_out = gr.Markdown() with gr.Accordion("๐ Documentation Findings", open=False): docs_out = gr.HTML() with gr.Accordion("๐ Further Resources", open=False): resources_out = gr.HTML() report_file = gr.File(label="Download session report") with gr.Tab("Chat / Follow-up"): gr.Markdown("Ask follow-up questions about the code you analyzed โ I'll remember the context.") chatbot = gr.Chatbot(label="CodeMentor", height=400, value=_greeting_message()) chat_input = gr.Textbox(label="Your question", placeholder="e.g. why did you suggest that fix?") chat_send_btn = gr.Button("Send", variant="primary") with gr.Tab("My Progress"): gr.Markdown("Your language-wise revision history and skill breakdown.") refresh_btn = gr.Button("๐ Refresh") revision_table = gr.Dataframe( headers=["Language", "Submissions", "Syntax Errors", "Logical Errors", "TLE Count", "Last Seen"], value=_initial_table, interactive=False, ) skill_pie = gr.Plot(value=_initial_chart) skill_summary = gr.Markdown(value=_initial_summary) # ------------------------------ Event wiring ------------------------------ plan_btn.click( on_generate_plan, inputs=[code_input, language_dd], outputs=[plan_output, plan_actions], ) cancel_btn.click( on_cancel_plan, outputs=[plan_output, plan_actions], ) extract_btn.click( on_extract_from_image, inputs=[image_input], outputs=[code_input], ) approve_btn.click( on_approve_run, inputs=[code_input, language_dd, level_dd], outputs=[ breakdown_out, analogy_out, errors_out, cleanup_out, optimal_out, docs_out, resources_out, report_file, plan_output, plan_actions, ], ).then( lambda c, l, lv: (c, l, lv, _greeting_message(language=l, level=lv)), inputs=[code_input, language_dd, level_dd], outputs=[state_last_code, state_last_language, state_last_level, chatbot], ) chat_send_btn.click( on_chat_send, inputs=[chat_input, chatbot, state_last_code, state_last_language, state_last_level], outputs=[chatbot, chat_input], ) chat_input.submit( on_chat_send, inputs=[chat_input, chatbot, state_last_code, state_last_language, state_last_level], outputs=[chatbot, chat_input], ) refresh_btn.click(on_refresh_progress, outputs=[revision_table, skill_pie, skill_summary]) if __name__ == "__main__": demo.launch()