Spaces:
Running
Running
| # app.py β Codle AI Platform | |
| # | |
| # Security: HF_TOKEN is loaded exclusively from the environment via utils.py. | |
| # No secrets are accepted from the UI, passed through Gradio state, | |
| # or exposed in frontend JS/HTML/console output. | |
| import sys | |
| import os | |
| import re as _re | |
| import json | |
| import logging | |
| import datetime | |
| import gradio as gr | |
| from dotenv import load_dotenv | |
| from prompts import ( | |
| build_prompt, | |
| build_translation_prompt, | |
| build_complexity_prompt, | |
| resolve_language, | |
| SUPPORTED_LANGUAGES, | |
| ) | |
| from utils import ( | |
| generate_explanation, | |
| generate_translation, | |
| generate_complexity_analysis, | |
| ) | |
| # βββ Startup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| load_dotenv() | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| ) | |
| logger = logging.getLogger(__name__) | |
| def _validate_config() -> None: | |
| if not os.getenv("HF_TOKEN", "").strip(): | |
| logger.error("HF_TOKEN is not set. Add it to .env and restart.") | |
| sys.exit("Startup aborted: API configuration missing. Set HF_TOKEN in .env.") | |
| _validate_config() | |
| # βββ Asset loaders ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _read_asset(filename: str, fallback: str = "") -> str: | |
| path = os.path.join(os.path.dirname(__file__), "assets", filename) | |
| try: | |
| with open(path, encoding="utf-8") as f: | |
| return f.read() | |
| except OSError: | |
| return fallback | |
| CSS = _read_asset("styles.css") | |
| JS_SRC = _read_asset("codle.js") | |
| # βββ Language β starter template mapping ββββββββββββββββββββββββββββββββββββββ | |
| # display_name β (codemirror_language_id, starter_code) | |
| LANG_TEMPLATES: dict[str, tuple[str, str]] = { | |
| "Python": ("python", """\ | |
| def factorial(n): | |
| # Base case | |
| if n == 0: | |
| return 1 | |
| # Recursive case | |
| return n * factorial(n - 1) | |
| """), | |
| "JavaScript": ("javascript", """\ | |
| // Recursive factorial | |
| function factorial(n) { | |
| if (n <= 1) return 1; | |
| return n * factorial(n - 1); | |
| } | |
| """), | |
| "TypeScript": ("javascript", """\ | |
| // Recursive factorial with TypeScript types | |
| function factorial(n: number): number { | |
| if (n <= 1) return 1; | |
| return n * factorial(n - 1); | |
| } | |
| """), | |
| "Java": ("java", """\ | |
| public class Main { | |
| // Recursive factorial | |
| static int factorial(int n) { | |
| if (n <= 1) return 1; | |
| return n * factorial(n - 1); | |
| } | |
| } | |
| """), | |
| "C++": ("cpp", """\ | |
| #include <iostream> | |
| // Recursive factorial | |
| int factorial(int n) { | |
| if (n <= 1) return 1; | |
| return n * factorial(n - 1); | |
| } | |
| """), | |
| "C": ("c", """\ | |
| #include <stdio.h> | |
| /* Recursive factorial */ | |
| int factorial(int n) { | |
| if (n <= 1) return 1; | |
| return n * factorial(n - 1); | |
| } | |
| """), | |
| "Go": ("go", """\ | |
| package main | |
| // factorial computes n! recursively | |
| func factorial(n int) int { | |
| if n <= 1 { | |
| return 1 | |
| } | |
| return n * factorial(n-1) | |
| } | |
| """), | |
| "C#": ("csharp", """\ | |
| public class Program { | |
| // Recursive factorial | |
| static int Factorial(int n) { | |
| if (n <= 1) return 1; | |
| return n * Factorial(n - 1); | |
| } | |
| } | |
| """), | |
| "Rust": ("rust", """\ | |
| // Recursive factorial | |
| fn factorial(n: u64) -> u64 { | |
| if n <= 1 { 1 } else { n * factorial(n - 1) } | |
| } | |
| """), | |
| } | |
| ALL_LANGUAGES = list(LANG_TEMPLATES.keys()) # keeps insertion order | |
| def _cm_lang(display: str) -> str | None: | |
| lang = LANG_TEMPLATES.get(display, ("python", ""))[0] | |
| supported = { | |
| "python", "c", "cpp", "markdown", "latex", "json", "html", "css", | |
| "javascript", "jinja2", "typescript", "yaml", "dockerfile", "shell", "r", "sql" | |
| } | |
| return lang if lang in supported else None | |
| def _template(display: str) -> str: | |
| return LANG_TEMPLATES.get(display, ("python", "# Start coding here\n"))[1] | |
| def switch_language(lang: str): | |
| """Swap both syntax-highlight mode and starter template when dropdown changes.""" | |
| return gr.update(language=_cm_lang(lang), value=_template(lang)) | |
| # βββ Language detector ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Ordered rules β more specific languages first to avoid misclassification. | |
| _LANG_PATTERNS: list[tuple[_re.Pattern, str]] = [ | |
| (_re.compile(r'\bpublic\s+class\b|\bSystem\.out\.println\b|\bimport\s+java\.', _re.M), "Java"), | |
| (_re.compile(r'\busing\s+System\b|\bConsole\.Write|\bnamespace\s+\w+\s*[{;]|\bpublic\s+static\s+void\s+Main\b', _re.M), "C#"), | |
| (_re.compile(r'\bpackage\s+main\b|\bfmt\.Print|\bfunc\s+main\s*\(\)', _re.M), "Go"), | |
| (_re.compile(r'\bfn\s+\w+\s*\(|\blet\s+mut\b|\bprintln!\s*\(|\bimpl\b', _re.M), "Rust"), | |
| (_re.compile(r':\s*(string|number|boolean|void|any)\b|interface\s+\w+\s*\{', _re.M), "TypeScript"), | |
| (_re.compile(r'\bconst\s+\w+\s*=|\bfunction\s+\w+\s*\(|\bconsole\.log\b', _re.M), "JavaScript"), | |
| (_re.compile(r'#include\s*<[a-z_]+>\s*\n.*(?:std::|cout|cin|vector\s*<)', _re.M | _re.S), "C++"), | |
| (_re.compile(r'#include\s*<(?:stdio|stdlib|string)\.h>|\bprintf\s*\(|\bscanf\s*\(', _re.M), "C"), | |
| (_re.compile(r'^\s*def\s+\w+\s*\(|^\s*class\s+\w+\s*:|^\s*import\s+\w+|^\s*from\s+\w+\s+import', _re.M), "Python"), | |
| ] | |
| def detect_language(code: str) -> str | None: | |
| """Heuristic language detection. Returns display name or None.""" | |
| if not code or not code.strip(): | |
| return None | |
| for pattern, lang in _LANG_PATTERNS: | |
| if pattern.search(code): | |
| return lang | |
| return None | |
| # βββ Controller: Code Explanation βββββββββββββββββββββββββββββββββββββββββββββ | |
| def explain_code( | |
| code: str, language: str, | |
| bug_detection: bool, complexity_analysis: bool, simple_mode: bool, | |
| ) -> str: | |
| if not code or not code.strip(): | |
| return "### β οΈ Please enter or select some code to analyze." | |
| resolved = resolve_language(language) | |
| if not resolved: | |
| return f"### β οΈ Language **{language}** is not supported. Please select a supported language." | |
| prompt = build_prompt(code, language, bug_detection, complexity_analysis, simple_mode) | |
| try: | |
| return generate_explanation(prompt) | |
| except RuntimeError as e: | |
| return f"### β {e}" | |
| except Exception: | |
| logger.exception("Unexpected error in explain_code.") | |
| return "### β An unexpected error occurred. Please try again later." | |
| def save_report(text: str): | |
| _bad = ("Results will appear here", "β οΈ", "β", "No report") | |
| if not text or any(b in text for b in _bad): | |
| return gr.update(value=None, visible=False) | |
| filename = "codle_explanation_report.md" | |
| try: | |
| with open(filename, "w", encoding="utf-8") as f: | |
| f.write(text) | |
| return gr.update(value=filename, visible=True) | |
| except Exception: | |
| logger.exception("Error saving report.") | |
| return gr.update(value=None, visible=False) | |
| def export_json(explanation: str, language: str, code: str): | |
| _bad = ("Results will appear here", "β οΈ", "β") | |
| if not explanation or any(b in explanation for b in _bad): | |
| return gr.update(value=None, visible=False) | |
| data = { | |
| "generated_at": datetime.datetime.utcnow().isoformat() + "Z", | |
| "language": language, | |
| "code_length": len(code), | |
| "explanation": explanation, | |
| } | |
| filename = "codle_report.json" | |
| try: | |
| with open(filename, "w", encoding="utf-8") as f: | |
| json.dump(data, f, indent=2, ensure_ascii=False) | |
| return gr.update(value=filename, visible=True) | |
| except Exception: | |
| logger.exception("Error saving JSON report.") | |
| return gr.update(value=None, visible=False) | |
| def clear_all(): | |
| return ( | |
| "", | |
| "### AI Explanation\n\nResults will appear here.", | |
| gr.update(value=None, visible=False), | |
| gr.update(value=None, visible=False), | |
| ) | |
| # βββ Controller: Code Translator ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def translate_code(code: str, source_lang: str, target_lang: str): | |
| if not code or not code.strip(): | |
| return "### β οΈ Please enter code to translate.", "", "", "" | |
| if source_lang == target_lang: | |
| return "### βΉοΈ Source and target languages are the same.", "", "", "" | |
| src_resolved = resolve_language(source_lang) | |
| tgt_resolved = resolve_language(target_lang) | |
| if not src_resolved: | |
| return f"### β οΈ Source language **{source_lang}** is not supported.", "", "", "" | |
| if not tgt_resolved: | |
| return f"### β οΈ Target language **{target_lang}** is not supported.", "", "", "" | |
| prompt = build_translation_prompt(code, source_lang, target_lang) | |
| try: | |
| result = generate_translation(prompt) | |
| translated = _extract_section(result, "Translated Code", next_header="Translation Explanation") | |
| explanation = _extract_section(result, "Translation Explanation", next_header="Key Differences") | |
| differences = _extract_section(result, "Key Differences", next_header="Optimization Notes") | |
| opt_notes = _extract_section(result, "Optimization Notes") | |
| return translated or result, explanation, differences, opt_notes | |
| except RuntimeError as e: | |
| return f"### β {e}", "", "", "" | |
| except Exception: | |
| logger.exception("Unexpected error in translate_code.") | |
| return "### β An unexpected error occurred.", "", "", "" | |
| def _extract_section(text: str, header: str, next_header: str = None) -> str: | |
| """Extract a markdown section by heading. Falls back to plain search.""" | |
| pattern = rf"#{1,3}\s+{_re.escape(header)}\s*\n(.*?)" | |
| end_pat = rf"#{1,3}\s+{_re.escape(next_header)}" if next_header else r"$" | |
| match = _re.search(pattern + end_pat, text, _re.DOTALL | _re.IGNORECASE) | |
| if match: | |
| return match.group(1).strip() | |
| idx = text.lower().find(header.lower()) | |
| if idx != -1: | |
| chunk = text[idx + len(header):].strip() | |
| if next_header: | |
| nidx = chunk.lower().find(next_header.lower()) | |
| if nidx != -1: | |
| chunk = chunk[:nidx].strip() | |
| return chunk[:1200] | |
| return "" | |
| # βββ Controller: Complexity Analysis ββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze_complexity(code: str, language: str): | |
| if not code or not code.strip(): | |
| return "*Enter code and click Analyze Complexity.*", _complexity_dashboard_html(None) | |
| resolved = resolve_language(language) | |
| if not resolved: | |
| return ( | |
| f"### β οΈ Language **{language}** is not supported for complexity analysis.", | |
| _complexity_dashboard_html(None), | |
| ) | |
| # Non-blocking mismatch warning | |
| detected = detect_language(code) | |
| warn = "" | |
| if detected and detected != language: | |
| warn = ( | |
| f"> β οΈ **Language mismatch:** code looks like **{detected}** " | |
| f"but **{language}** is selected. Results may be inaccurate.\n\n" | |
| ) | |
| prompt = build_complexity_prompt(code, language) | |
| try: | |
| result = generate_complexity_analysis(prompt) | |
| dashboard_html = _build_complexity_dashboard(result) | |
| return warn + result, dashboard_html | |
| except RuntimeError as e: | |
| return f"### β {e}", _complexity_dashboard_html(None) | |
| except Exception: | |
| logger.exception("Unexpected error in analyze_complexity.") | |
| return "### β An unexpected error occurred.", _complexity_dashboard_html(None) | |
| def _build_complexity_dashboard(report: str) -> str: | |
| """Parse LLM complexity report β HTML badge cards + chart.""" | |
| def extract(pattern, text, default="β"): | |
| m = _re.search(pattern, text, _re.IGNORECASE) | |
| if m: | |
| val = m.group(1).strip() | |
| # Strip markdown bold formatting and backticks | |
| val = val.strip("*").strip("`").strip() | |
| return val | |
| return default | |
| # Split report into Time and Space sections to prevent overall cross-matching | |
| time_sec = "" | |
| space_sec = "" | |
| parts = _re.split(r"##\s*Space Complexity", report, flags=_re.IGNORECASE) | |
| if len(parts) > 1: | |
| time_sec = parts[0] | |
| space_sec = parts[1] | |
| else: | |
| time_sec = report | |
| space_sec = report | |
| # Extract raw values | |
| time_overall = extract(r"Overall:\s*([^\n]+)", time_sec) | |
| time_worst = extract(r"Worst Case:\s*([^\n]+)", time_sec) | |
| time_avg = extract(r"Average Case:\s*([^\n]+)", time_sec) | |
| space_overall = extract(r"Overall:\s*([^\n]+)", space_sec) | |
| # Try multiple class match patterns | |
| complexity_cls = extract(r"Complexity Class:\s*([^\n]+)", report, "Unknown") | |
| if complexity_cls == "Unknown": | |
| complexity_cls = extract(r"##\s*Complexity Class\s*\n([^\n]+)", report, "Unknown") | |
| # Clean raw values from HTML characters (like <, >) and LaTeX delimiters | |
| def clean_val(v: str) -> str: | |
| v = v.replace("<", "").replace(">", "").replace("\\(", "").replace("\\)", "").replace("$", "") | |
| v = v.strip() | |
| if v.startswith("(") and v.endswith(")"): | |
| v = v[1:-1].strip() | |
| return v | |
| time_overall = clean_val(time_overall) | |
| time_worst = clean_val(time_worst) | |
| time_avg = clean_val(time_avg) | |
| space_overall = clean_val(space_overall) | |
| complexity_cls = clean_val(complexity_cls) | |
| # Class maps for visual styling and highlights | |
| cls_map = { | |
| "constant": "class-constant", "logarithmic": "class-log", | |
| "linearithmic": "class-linearithmic", "linear": "class-linear", | |
| "quadratic": "class-quadratic", "exponential": "class-exponential", | |
| } | |
| curve_map = { | |
| "constant": "O(1)", "logarithmic": "O(log n)", | |
| "linearithmic": "O(n log n)", "linear": "O(n)", | |
| "quadratic": "O(n\u00b2)", "exponential": "O(2^n)", | |
| } | |
| cls_key = complexity_cls.lower().strip() | |
| css_cls = next((v for k, v in cls_map.items() if k in cls_key), "") | |
| highlight = curve_map.get(next((k for k in curve_map if k in cls_key), ""), "") | |
| return _complexity_dashboard_html({ | |
| "time_overall": time_overall, | |
| "time_worst": time_worst, | |
| "time_avg": time_avg, | |
| "space": space_overall, | |
| "cls": complexity_cls, | |
| "css_cls": css_cls, | |
| "highlight": highlight, | |
| }) | |
| def _complexity_dashboard_html(data) -> str: | |
| if not data: | |
| return '<div class="cx-placeholder">Run complexity analysis to see the dashboard.</div>' | |
| hl_js = f'"{data["highlight"]}"' if data.get("highlight") else "null" | |
| return f""" | |
| <div id="complexity-dashboard"> | |
| <div class="complexity-card {data['css_cls']}"> | |
| <span class="badge-O">{data['time_overall']}</span> | |
| <span class="badge-label">Time Overall</span> | |
| </div> | |
| <div class="complexity-card {data['css_cls']}"> | |
| <span class="badge-O">{data['time_worst']}</span> | |
| <span class="badge-label">Worst Case</span> | |
| </div> | |
| <div class="complexity-card {data['css_cls']}"> | |
| <span class="badge-O">{data['time_avg']}</span> | |
| <span class="badge-label">Average Case</span> | |
| </div> | |
| <div class="complexity-card"> | |
| <span class="badge-O" style="color:#22d3ee">{data['space']}</span> | |
| <span class="badge-label">Space Overall</span> | |
| </div> | |
| <div class="complexity-card"> | |
| <span class="badge-O" style="font-size:0.88rem;color:#a78bfa">{data['cls']}</span> | |
| <span class="badge-label">Class</span> | |
| </div> | |
| </div> | |
| <div id="complexity-chart-wrap"> | |
| <canvas id="complexity-chart"></canvas> | |
| </div> | |
| <script> | |
| (function(){{ | |
| var HL={hl_js}, tries=0; | |
| function draw(){{ | |
| var cv=document.getElementById("complexity-chart"); | |
| var wr=document.getElementById("complexity-chart-wrap"); | |
| if(!cv||!wr){{if(++tries<30)setTimeout(draw,200);return;}} | |
| if(!window.Codle||!Codle.ComplexityChart){{if(++tries<30)setTimeout(draw,200);return;}} | |
| wr.classList.add("has-chart"); | |
| requestAnimationFrame(function(){{ | |
| cv.width=wr.clientWidth||600; cv.height=wr.clientHeight||240; | |
| try{{Codle.ComplexityChart.animateDraw(cv,HL);}} | |
| catch(e){{console.warn("Chart draw failed:",e);}} | |
| }}); | |
| }} | |
| draw(); | |
| }})(); | |
| </script>""" | |
| # βββ HTML helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _header_html() -> str: | |
| svg = """<svg width="30" height="30" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <rect x="1" y="1" width="30" height="30" rx="8" stroke="#818cf8" stroke-width="1.5"/> | |
| <path d="M9 12 L14 16 L9 20" stroke="#818cf8" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/> | |
| <path d="M17 20 L23 20" stroke="#818cf8" stroke-width="1.8" stroke-linecap="round"/> | |
| </svg>""" | |
| return f""" | |
| <canvas id="codle-particles"></canvas> | |
| <div id="codle-header"> | |
| <div id="codle-logo-wrap" style="background:transparent;border:none;box-shadow:none; | |
| width:34px;height:34px;animation:none;overflow:visible;display:flex;align-items:center;"> | |
| {svg} | |
| </div> | |
| <span id="codle-title" style="font-size:1.05rem;font-weight:700;letter-spacing:1.5px; | |
| color:#c7d2fe;text-transform:uppercase;">CODLE AI</span> | |
| </div>""" | |
| def _kbd_strip_html() -> str: | |
| return """<div id="codle-kbd-strip"> | |
| <span><kbd>Ctrl+Enter</kbd> Analyze</span> | |
| <span><kbd>Ctrl+K</kbd> Clear</span> | |
| <span><kbd>Ctrl+S</kbd> Save report</span> | |
| <span><kbd>Ctrl+Shift+C</kbd> Copy</span> | |
| <span><kbd>Ctrl+/</kbd> Command palette</span> | |
| </div>""" | |
| # βββ Example snippets βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| PYTHON_EX = """\ | |
| def factorial(n): | |
| if n < 0: | |
| raise ValueError("Factorial undefined for negatives") | |
| if n == 0 or n == 1: | |
| return 1 | |
| return n * factorial(n - 1)""" | |
| JS_EX = """\ | |
| async function fetchUserData(userId) { | |
| try { | |
| const response = await fetch(`https://api.example.com/users/${userId}`); | |
| if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | |
| return await response.json(); | |
| } catch (error) { | |
| console.error("Failed:", error); | |
| return null; | |
| } | |
| }""" | |
| CPP_EX = """\ | |
| #include <vector> | |
| #include <algorithm> | |
| void bubbleSort(std::vector<int>& arr) { | |
| int n = arr.size(); | |
| for (int i = 0; i < n - 1; ++i) | |
| for (int j = 0; j < n - i - 1; ++j) | |
| if (arr[j] > arr[j+1]) | |
| std::swap(arr[j], arr[j+1]); | |
| }""" | |
| JAVA_EX = """\ | |
| public class BinarySearch { | |
| static int binarySearch(int[] arr, int target) { | |
| int low = 0, high = arr.length - 1; | |
| while (low <= high) { | |
| int mid = low + (high - low) / 2; | |
| if (arr[mid] == target) return mid; | |
| else if (arr[mid] < target) low = mid + 1; | |
| else high = mid - 1; | |
| } | |
| return -1; | |
| } | |
| }""" | |
| GO_EX = """\ | |
| package main | |
| func binarySearch(arr []int, target int) int { | |
| low, high := 0, len(arr)-1 | |
| for low <= high { | |
| mid := low + (high-low)/2 | |
| if arr[mid] == target { | |
| return mid | |
| } else if arr[mid] < target { | |
| low = mid + 1 | |
| } else { | |
| high = mid - 1 | |
| } | |
| } | |
| return -1 | |
| }""" | |
| RUST_EX = """\ | |
| fn fibonacci(n: u64) -> u64 { | |
| match n { | |
| 0 => 0, | |
| 1 => 1, | |
| _ => fibonacci(n - 1) + fibonacci(n - 2), | |
| } | |
| }""" | |
| # βββ Gradio theme ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| theme = gr.themes.Soft( | |
| primary_hue="indigo", | |
| secondary_hue="slate", | |
| neutral_hue="slate", | |
| font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"], | |
| font_mono=[gr.themes.GoogleFont("Fira Code"), "monospace"], | |
| ) | |
| # βββ Gradio UI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks( | |
| title="Codle AI", | |
| theme=theme, | |
| css=CSS, | |
| head=f"<script>{JS_SRC}</script>", | |
| ) as demo: | |
| gr.HTML(_header_html()) | |
| with gr.Tabs(): | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 1 β Explain | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.TabItem("π Explain"): | |
| with gr.Row(equal_height=False): | |
| # Left β inputs | |
| with gr.Column(scale=5, min_width=300, elem_classes="glass-card"): | |
| gr.HTML('<div class="section-label">Configuration</div>') | |
| language = gr.Dropdown( | |
| choices=ALL_LANGUAGES, value="Python", | |
| label="Programming Language", | |
| info="Changing language loads a starter template.", | |
| ) | |
| code_input = gr.Code( | |
| label="Code Editor", | |
| language="python", | |
| lines=22, | |
| value=PYTHON_EX, | |
| elem_classes="code-editor-wrap", | |
| ) | |
| gr.HTML('<div class="section-label" style="margin-top:14px">Analysis Options</div>') | |
| with gr.Row(): | |
| bug_detection = gr.Checkbox( | |
| label="π Bug Detector", value=True, | |
| info="Logical errors, edge cases, security issues.", | |
| ) | |
| complexity_analysis = gr.Checkbox( | |
| label="π Complexity Analyzer", value=True, | |
| info="Time & space complexity breakdown.", | |
| ) | |
| simple_mode = gr.Checkbox( | |
| label="πΆ Explain Like I'm 10", | |
| value=False, | |
| info="Ultra-simple language for absolute beginners.", | |
| ) | |
| with gr.Row(): | |
| explain_btn = gr.Button("π Analyze Code", variant="primary", elem_classes="btn-primary") | |
| clear_btn = gr.Button("π Clear", elem_classes="btn-glass btn-clear") | |
| gr.HTML(_kbd_strip_html()) | |
| # Right β output | |
| with gr.Column(scale=7, min_width=360, elem_classes="glass-card"): | |
| gr.HTML('<div class="section-label">AI Mentorship Report</div>') | |
| output = gr.Markdown( | |
| value="### AI Explanation\n\nResults will appear here.", | |
| elem_classes="output-panel", | |
| ) | |
| with gr.Row(): | |
| copy_btn = gr.Button("π Copy", elem_id="copy_btn", elem_classes="btn-glass") | |
| download_md_btn = gr.Button("π₯ Markdown", elem_classes="btn-glass btn-download-md") | |
| download_json_btn = gr.Button("π JSON", elem_classes="btn-glass btn-download-json") | |
| with gr.Row(): | |
| file_output = gr.File(label="Markdown Report", visible=False) | |
| json_file_output = gr.File(label="JSON Report", visible=False) | |
| gr.Markdown("---\n### π‘ Code Templates") | |
| gr.Examples( | |
| examples=[ | |
| [PYTHON_EX, "Python"], | |
| [JS_EX, "JavaScript"], | |
| [CPP_EX, "C++"], | |
| [JAVA_EX, "Java"], | |
| [GO_EX, "Go"], | |
| [RUST_EX, "Rust"], | |
| ], | |
| inputs=[code_input, language], | |
| ) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 2 β Translate | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.TabItem("π Translate"): | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_classes="glass-card"): | |
| gr.HTML('<div class="section-label">Source</div>') | |
| src_lang = gr.Dropdown( | |
| choices=ALL_LANGUAGES, value="Python", | |
| label="Source Language", | |
| ) | |
| src_code = gr.Code( | |
| label="Source Code", | |
| language="python", | |
| lines=20, | |
| value=PYTHON_EX, | |
| elem_classes="code-editor-wrap", | |
| ) | |
| with gr.Column(scale=1, elem_classes="glass-card"): | |
| gr.HTML('<div class="section-label">Target</div>') | |
| tgt_lang = gr.Dropdown( | |
| choices=ALL_LANGUAGES, value="JavaScript", | |
| label="Target Language", | |
| ) | |
| translate_btn = gr.Button( | |
| "β‘ Translate Code", variant="primary", elem_classes="btn-primary", | |
| ) | |
| gr.Markdown( | |
| "_All 9 languages supported. Selects a starter template on dropdown change._", | |
| ) | |
| with gr.Tabs(): | |
| with gr.TabItem("π Converted Code"): | |
| translated_output = gr.Markdown( | |
| value="*Translation will appear here.*", | |
| elem_classes="output-panel", | |
| ) | |
| with gr.TabItem("π¬ Explanation"): | |
| translation_explanation = gr.Markdown( | |
| value="*Explanation will appear here.*", | |
| elem_classes="output-panel", | |
| ) | |
| with gr.TabItem("π Key Differences"): | |
| translation_diff = gr.Markdown( | |
| value="*Differences will appear here.*", | |
| elem_classes="output-panel", | |
| ) | |
| with gr.TabItem("β‘ Optimization Notes"): | |
| translation_opts = gr.Markdown( | |
| value="*Optimization notes will appear here.*", | |
| elem_classes="output-panel", | |
| ) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TAB 3 β Complexity | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.TabItem("π Complexity"): | |
| with gr.Row(): | |
| with gr.Column(scale=4, min_width=280, elem_classes="glass-card"): | |
| gr.HTML('<div class="section-label">Code Input</div>') | |
| cx_lang = gr.Dropdown( | |
| choices=ALL_LANGUAGES, value="Python", | |
| label="Language", | |
| ) | |
| cx_code = gr.Code( | |
| label="Code to Analyze", | |
| language="python", | |
| lines=20, | |
| value=PYTHON_EX, | |
| elem_classes="code-editor-wrap", | |
| ) | |
| cx_btn = gr.Button( | |
| "π Analyze Complexity", variant="primary", elem_classes="btn-primary", | |
| ) | |
| with gr.Column(scale=6, elem_classes="glass-card"): | |
| gr.HTML('<div class="section-label">Complexity Dashboard</div>') | |
| cx_dashboard = gr.HTML(value=_complexity_dashboard_html(None)) | |
| gr.HTML('<div class="section-label" style="margin-top:18px">Full Report</div>') | |
| cx_output = gr.Markdown( | |
| value="*Complexity analysis will appear here.*", | |
| elem_classes="output-panel", | |
| ) | |
| # βββ Event bindings βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Tab 1 β Explain | |
| language.change(switch_language, inputs=language, outputs=code_input) | |
| explain_btn.click( | |
| explain_code, | |
| inputs=[code_input, language, bug_detection, complexity_analysis, simple_mode], | |
| outputs=output, | |
| ) | |
| clear_btn.click( | |
| clear_all, | |
| outputs=[code_input, output, file_output, json_file_output], | |
| ) | |
| copy_btn.click( | |
| fn=None, inputs=[output], outputs=None, | |
| js="""(text) => { | |
| if (!text || text.includes("Results will appear here") || text.includes("Please enter")) { | |
| Codle && Codle.Toast.info("No report to copy yet."); | |
| return; | |
| } | |
| navigator.clipboard.writeText(text) | |
| .then(() => Codle && Codle.Toast.success("Report copied!")) | |
| .catch(() => Codle && Codle.Toast.error("Clipboard access denied.")); | |
| }""", | |
| ) | |
| download_md_btn.click(save_report, inputs=output, outputs=file_output) | |
| download_json_btn.click( | |
| export_json, | |
| inputs=[output, language, code_input], | |
| outputs=json_file_output, | |
| ) | |
| # Tab 2 β Translate | |
| # Source language change: swap syntax + template in source editor | |
| src_lang.change(switch_language, inputs=src_lang, outputs=src_code) | |
| translate_btn.click( | |
| translate_code, | |
| inputs=[src_code, src_lang, tgt_lang], | |
| outputs=[translated_output, translation_explanation, translation_diff, translation_opts], | |
| ) | |
| # Tab 3 β Complexity | |
| cx_lang.change(switch_language, inputs=cx_lang, outputs=cx_code) | |
| cx_btn.click( | |
| analyze_complexity, | |
| inputs=[cx_code, cx_lang], | |
| outputs=[cx_output, cx_dashboard], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.environ.get("PORT", 7860)), | |
| ) | |