Spaces:
Running on Zero
Running on Zero
Download app.py from AaronTekle/Agentic-DataEngineering: direct link, hf CLI and curl.
- Browser
- Download file 44.7 kB
-
https://huggingface.co/spaces/AaronTekle/Agentic-DataEngineering/resolve/main/app.py
- Command line
-
hf download hf://spaces/AaronTekle/Agentic-DataEngineering/app.py
-
curl -L -o app.py https://huggingface.co/spaces/AaronTekle/Agentic-DataEngineering/resolve/main/app.py
44.7 kB
| from __future__ import annotations | |
| import spaces | |
| import html, json, os, threading, uuid | |
| from pathlib import Path | |
| from typing import Any | |
| import gradio as gr | |
| import pandas as pd | |
| from huggingface_hub import HfApi, hf_hub_download | |
| from agent import DataEngineeringAgent | |
| from config import APP_TITLE, DEFAULT_MAX_TOKENS, HF_MODEL_ID, HF_PROVIDER, HF_TOKEN, MAX_PROFILE_ROWS, MAX_UPLOAD_MB, SUPPORTED_DATA_EXTENSIONS | |
| from data_engine import DataContext | |
| SESSIONS: dict[str, DataContext] = {} | |
| LOCK = threading.Lock() | |
| # Public Hugging Face example dataset used to populate the workspace on startup | |
| EXAMPLE_DATASET_REPO = "Saturia/e-commerce" | |
| EXAMPLE_DATASET_SOURCE = f"hf://datasets/{EXAMPLE_DATASET_REPO}" | |
| def _sid(sid): return sid or uuid.uuid4().hex | |
| def _get(sid): | |
| if not sid: return None | |
| with LOCK: return SESSIONS.get(sid) | |
| def _set(sid, ctx): | |
| with LOCK: SESSIONS[sid] = ctx | |
| def _status(ctx, message="Ready"): | |
| details = "No dataset loaded" if ctx is None else f"{ctx.profile()['rows']:,} rows 路 {ctx.profile()['columns']:,} columns 路 {html.escape(ctx.source_name)}" | |
| state_class = "ready" if ctx is None else "active" | |
| return f''' | |
| <div class="status-card {state_class}"> | |
| <div class="status-row"> | |
| <span class="status-dot"></span> | |
| <span class="status-kicker">DATASET STATUS</span> | |
| </div> | |
| <strong>{html.escape(message)}</strong> | |
| <span class="status-details">{details}</span> | |
| </div> | |
| ''' | |
| def _schema_table(ctx): | |
| return pd.DataFrame([ | |
| { | |
| "column": c["name"], | |
| "logical_type": c["logical_type"], | |
| "pandas_dtype": c["pandas_dtype"], | |
| "null_pct": c["null_pct"], | |
| "unique_count": c["unique_count"], | |
| "examples": json.dumps(c["examples"]), | |
| } | |
| for c in ctx.profile()["column_schema"] | |
| ]) | |
| def load_dataset(uploaded: Any, sid: str | None): | |
| sid = _sid(sid) | |
| if uploaded is None: | |
| return sid, _status(_get(sid), "No file selected"), pd.DataFrame(), pd.DataFrame(), "{}", {} | |
| path = Path(uploaded.name if hasattr(uploaded, "name") else str(uploaded)) | |
| if path.suffix.lower() not in SUPPORTED_DATA_EXTENSIONS: | |
| raise gr.Error("Unsupported file type.") | |
| size = path.stat().st_size / 1024 / 1024 | |
| if size > MAX_UPLOAD_MB: | |
| raise gr.Error(f"{path.name} is {size:.1f} MB. Limit is {MAX_UPLOAD_MB} MB.") | |
| try: | |
| ctx = DataContext.from_path(path, max_rows=MAX_PROFILE_ROWS) | |
| _set(sid, ctx) | |
| except Exception as exc: | |
| raise gr.Error(f"Could not load {path.name}: {exc}") from exc | |
| inferred = { | |
| "columns": { | |
| c["name"]: {"type": c["logical_type"], "nullable": c["nullable"]} | |
| for c in ctx.profile()["column_schema"] | |
| } | |
| } | |
| diag = { | |
| "source": ctx.source_name, | |
| "rows_loaded": ctx.profile()["rows"], | |
| "columns": ctx.profile()["columns"], | |
| "memory_mb": ctx.profile()["memory_mb"], | |
| "duplicate_rows": ctx.profile()["duplicate_rows"], | |
| "profiling_row_limit": MAX_PROFILE_ROWS, | |
| } | |
| return sid, _status(ctx, "Dataset loaded"), ctx.dataframe.head(20), _schema_table(ctx), json.dumps(inferred, indent=2), diag | |
| def _dataset_outputs(ctx: DataContext, sid: str, message: str, extra_diagnostics: dict[str, Any] | None = None): | |
| inferred = { | |
| "columns": { | |
| c["name"]: {"type": c["logical_type"], "nullable": c["nullable"]} | |
| for c in ctx.profile()["column_schema"] | |
| } | |
| } | |
| diag = { | |
| "source": ctx.source_name, | |
| "rows_loaded": ctx.profile()["rows"], | |
| "columns": ctx.profile()["columns"], | |
| "memory_mb": ctx.profile()["memory_mb"], | |
| "duplicate_rows": ctx.profile()["duplicate_rows"], | |
| "profiling_row_limit": MAX_PROFILE_ROWS, | |
| } | |
| if extra_diagnostics: | |
| diag.update(extra_diagnostics) | |
| return ( | |
| sid, | |
| _status(ctx, message), | |
| ctx.dataframe.head(20), | |
| _schema_table(ctx), | |
| json.dumps(inferred, indent=2), | |
| diag, | |
| ) | |
| def load_example_dataset(sid: str | None): | |
| """Load the public Hugging Face example dataset into the current workspace.""" | |
| sid = _sid(sid) | |
| try: | |
| # Resolve the public dataset's CSV file dynamically so the app is not coupled to a repository-specific filename | |
| repo_files = HfApi().list_repo_files( | |
| repo_id=EXAMPLE_DATASET_REPO, | |
| repo_type="dataset", | |
| ) | |
| csv_files = sorted( | |
| (name for name in repo_files if name.lower().endswith(".csv")), | |
| key=lambda name: (name.count("/"), len(name), name), | |
| ) | |
| if not csv_files: | |
| raise RuntimeError("No CSV file was found in the example dataset repository.") | |
| example_file = csv_files[0] | |
| cached_path = hf_hub_download( | |
| repo_id=EXAMPLE_DATASET_REPO, | |
| filename=example_file, | |
| repo_type="dataset", | |
| ) | |
| ctx = DataContext.from_path(cached_path, max_rows=MAX_PROFILE_ROWS) | |
| ctx.source_name = f"{EXAMPLE_DATASET_SOURCE}/{example_file}" | |
| _set(sid, ctx) | |
| return _dataset_outputs( | |
| ctx, | |
| sid, | |
| "HF example dataset loaded", | |
| { | |
| "dataset_repo": EXAMPLE_DATASET_REPO, | |
| "dataset_file": example_file, | |
| "dataset_origin": "Hugging Face Datasets", | |
| "preloaded_example": True, | |
| }, | |
| ) | |
| except Exception as exc: | |
| # Keep the Space usable even if the Hub is temporarily unavailable | |
| return ( | |
| sid, | |
| _status(_get(sid), "HF example dataset unavailable"), | |
| pd.DataFrame(), | |
| pd.DataFrame(), | |
| "{}", | |
| { | |
| "dataset_repo": EXAMPLE_DATASET_REPO, | |
| "preloaded_example": False, | |
| "error": str(exc), | |
| }, | |
| ) | |
| def run_quality(sid): | |
| ctx = _get(sid) | |
| if ctx is None: | |
| raise gr.Error("Load a dataset first.") | |
| q = ctx.quality_report() | |
| return ctx.quality_markdown(), pd.DataFrame(q["issues"]), { | |
| "issue_count": q["issue_count"], | |
| "high": q["high"], | |
| "medium": q["medium"], | |
| "low": q["low"], | |
| } | |
| def validate_schema(schema_json, sid): | |
| ctx = _get(sid) | |
| if ctx is None: | |
| raise gr.Error("Load a dataset first.") | |
| r = ctx.validate_schema(schema_json) | |
| summary = "### Schema validation\n**PASS**\n\n" + r.get("message", "") if r.get("valid") else "### Schema validation\n**FAIL**\n\n" + "\n".join(f"- {x}" for x in r.get("errors", [])) | |
| if r.get("warnings"): | |
| summary += "\n\n**Warnings**\n" + "\n".join(f"- {x}" for x in r["warnings"]) | |
| return summary, r | |
| def run_agent(task, schema, dialect, target, temperature, max_tokens, sid): | |
| ctx = _get(sid) | |
| if ctx is None: | |
| raise gr.Error("Load a dataset first.") | |
| if not (task or "").strip(): | |
| raise gr.Error("Enter an engineering task.") | |
| agent = DataEngineeringAgent(ctx, schema, dialect, target, temperature, int(max_tokens)) | |
| answer, sql, py, trace = agent.run(task.strip()) | |
| diag = { | |
| "model": HF_MODEL_ID, | |
| "provider": HF_PROVIDER or "HF automatic routing", | |
| "hf_token_configured": bool(HF_TOKEN), | |
| "tool_calls": len(trace), | |
| "tools_used": [x["tool"] for x in trace], | |
| } | |
| return answer, sql, py, trace, diag | |
| def clear_session(sid): | |
| if sid: | |
| with LOCK: | |
| SESSIONS.pop(sid, None) | |
| return "", _status(None, "Session cleared"), pd.DataFrame(), pd.DataFrame(), "{}", "", pd.DataFrame(), "", "", "", [], {} | |
| CSS = r''' | |
| :root { | |
| --bg-0: #05050a; | |
| --bg-1: #080a12; | |
| --bg-2: #0d101b; | |
| --panel: rgba(12, 15, 27, .78); | |
| --panel-strong: rgba(15, 19, 34, .94); | |
| --panel-soft: rgba(255, 255, 255, .035); | |
| --stroke: rgba(255, 255, 255, .09); | |
| --stroke-strong: rgba(255, 255, 255, .16); | |
| --text: #f7f9ff; | |
| --muted: #98a2b6; | |
| --muted-2: #6d778c; | |
| --cyan: #31d7ff; | |
| --cyan-2: #0ea5e9; | |
| --violet: #8b5cf6; | |
| --violet-2: #6d5dfc; | |
| --green: #43e5a0; | |
| --amber: #ffbd59; | |
| --danger: #ff5d78; | |
| --shadow: 0 30px 90px rgba(0, 0, 0, .46); | |
| } | |
| * { box-sizing: border-box; } | |
| .gradio-container, | |
| .gradio-container button, | |
| .gradio-container input, | |
| .gradio-container textarea, | |
| .gradio-container select { | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important; | |
| } | |
| html { scroll-behavior: smooth; } | |
| html, body { background: var(--bg-0) !important; } | |
| body { color: var(--text) !important; } | |
| .gradio-container { | |
| max-width: 1580px !important; | |
| margin: 0 auto !important; | |
| min-height: 100vh; | |
| color: var(--text) !important; | |
| background: | |
| radial-gradient(circle at 14% -4%, rgba(49, 215, 255, .16), transparent 32%), | |
| radial-gradient(circle at 83% 6%, rgba(139, 92, 246, .18), transparent 30%), | |
| radial-gradient(circle at 50% 105%, rgba(67, 229, 160, .07), transparent 35%), | |
| linear-gradient(145deg, #05050a 0%, #080b13 46%, #090c16 100%) !important; | |
| position: relative; | |
| } | |
| .gradio-container::before { | |
| content: ""; | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| opacity: .18; | |
| background-image: | |
| linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px); | |
| background-size: 44px 44px; | |
| mask-image: linear-gradient(to bottom, black, transparent 85%); | |
| } | |
| .main-shell { padding: 28px 26px 44px; position: relative; z-index: 1; } | |
| .hero { | |
| position: relative; | |
| overflow: hidden; | |
| border: 1px solid rgba(255,255,255,.11); | |
| background: | |
| linear-gradient(130deg, rgba(17,21,38,.96), rgba(8,10,18,.93) 62%, rgba(10,12,22,.96)); | |
| border-radius: 28px; | |
| padding: 40px 42px 38px; | |
| box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.045); | |
| margin-bottom: 18px; | |
| } | |
| .hero::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0 0 auto 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--cyan), var(--violet), transparent); | |
| box-shadow: 0 0 34px rgba(49, 215, 255, .45); | |
| } | |
| .hero::after { | |
| content: ""; | |
| position: absolute; | |
| width: 580px; | |
| height: 580px; | |
| right: -225px; | |
| top: -285px; | |
| border-radius: 50%; | |
| background: | |
| radial-gradient(circle at 48% 48%, rgba(49,215,255,.09), transparent 34%), | |
| conic-gradient(from 0deg, rgba(49,215,255,.18), rgba(139,92,246,.18), rgba(67,229,160,.08), rgba(49,215,255,.18)); | |
| filter: blur(2px); | |
| opacity: .8; | |
| } | |
| .hero-grid { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) auto; | |
| gap: 26px; | |
| align-items: end; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .eyebrow { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| color: #bdefff; | |
| font-size: 11px; | |
| font-weight: 900; | |
| letter-spacing: .19em; | |
| text-transform: uppercase; | |
| } | |
| .eyebrow-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--green); | |
| box-shadow: 0 0 16px rgba(67,229,160,.75); | |
| } | |
| .hero h1 { | |
| margin: 12px 0 12px; | |
| max-width: 980px; | |
| color: #ffffff; | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| font-size: clamp(42px, 5vw, 68px); | |
| line-height: 1.02; | |
| letter-spacing: -.035em; | |
| font-weight: 950; | |
| font-style: normal; | |
| text-rendering: geometricPrecision; | |
| } | |
| .gradient-word { | |
| color: #ffffff; | |
| background: none; | |
| -webkit-background-clip: initial; | |
| background-clip: initial; | |
| filter: none; | |
| font-style: normal; | |
| font-weight: 950; | |
| } | |
| .hero p { | |
| max-width: 930px; | |
| color: #aab4c9; | |
| font-size: 15px; | |
| line-height: 1.72; | |
| margin: 0; | |
| } | |
| .hero-orbit { | |
| width: 170px; | |
| height: 170px; | |
| border: 1px solid rgba(255,255,255,.08); | |
| border-radius: 28px; | |
| background: linear-gradient(145deg, rgba(49,215,255,.06), rgba(139,92,246,.05)); | |
| position: relative; | |
| box-shadow: inset 0 0 45px rgba(49,215,255,.035); | |
| } | |
| .hero-orbit::before, | |
| .hero-orbit::after { | |
| content: ""; | |
| position: absolute; | |
| border-radius: 50%; | |
| border: 1px solid rgba(255,255,255,.10); | |
| inset: 24px; | |
| } | |
| .hero-orbit::after { inset: 54px; border-color: rgba(49,215,255,.35); box-shadow: 0 0 26px rgba(49,215,255,.12); } | |
| .hero-orbit-core { | |
| position: absolute; | |
| width: 16px; | |
| height: 16px; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| border-radius: 5px; | |
| background: linear-gradient(135deg, var(--cyan), var(--violet)); | |
| box-shadow: 0 0 28px rgba(49,215,255,.6); | |
| } | |
| .badges { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 22px; } | |
| .badge { | |
| border: 1px solid rgba(255,255,255,.105); | |
| background: rgba(255,255,255,.035); | |
| padding: 8px 11px; | |
| border-radius: 999px; | |
| color: #aeb9ce; | |
| font-size: 11px; | |
| backdrop-filter: blur(12px); | |
| } | |
| .badge strong { color: #eefbff; margin-right: 4px; } | |
| .badge-accent { border-color: rgba(49,215,255,.22); background: rgba(49,215,255,.055); color: #c6f3ff; } | |
| .workflow-strip { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 10px; | |
| margin: 0 0 18px; | |
| } | |
| .workflow-step { | |
| position: relative; | |
| min-height: 76px; | |
| border: 1px solid var(--stroke); | |
| border-radius: 17px; | |
| padding: 14px 15px; | |
| background: linear-gradient(145deg, rgba(255,255,255,.038), rgba(255,255,255,.018)); | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,.025); | |
| } | |
| .workflow-step span { display: block; color: #68758d; font-size: 10px; font-weight: 900; letter-spacing: .12em; } | |
| .workflow-step strong { display: block; color: #f1f5ff; font-size: 13px; margin-top: 5px; } | |
| .workflow-step small { display: block; color: #7f8ba0; font-size: 11px; margin-top: 3px; } | |
| .workflow-step:nth-child(1) { border-top-color: rgba(49,215,255,.55); } | |
| .workflow-step:nth-child(2) { border-top-color: rgba(67,229,160,.55); } | |
| .workflow-step:nth-child(3) { border-top-color: rgba(255,189,89,.55); } | |
| .workflow-step:nth-child(4) { border-top-color: rgba(139,92,246,.62); } | |
| .app-panel { | |
| background: rgba(10, 13, 24, .78) !important; | |
| border: 1px solid var(--stroke) !important; | |
| border-radius: 24px !important; | |
| box-shadow: 0 24px 65px rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.028); | |
| /* Gradio dropdown menus are position:fixed and use viewport coordinates. | |
| Keep backdrop-filter off this ancestor so menus stay correctly anchored. | |
| overflow:hidden restores the rounded panel/tab clipping without reintroducing | |
| the dropdown positioning bug. */ | |
| overflow: hidden; | |
| backdrop-filter: none; | |
| -webkit-backdrop-filter: none; | |
| padding-top: 0 !important; | |
| } | |
| .side-column { gap: 18px !important; } | |
| .sidebar-card { | |
| background: linear-gradient(145deg, rgba(17,21,37,.88), rgba(10,13,23,.88)); | |
| border: 1px solid var(--stroke); | |
| border-radius: 18px; | |
| padding: 22px; | |
| margin-bottom: 0; | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,.025), 0 16px 45px rgba(0,0,0,.20); | |
| } | |
| .sidebar-card + .sidebar-card { margin-top: 20px; } | |
| .sidebar-card h3 { margin: 0 0 18px; color: #fff; font-size: 13px; letter-spacing: -.01em; } | |
| .sidebar-card p, .sidebar-card li { color: var(--muted); font-size: 12px; line-height: 1.82; } | |
| .sidebar-card ol { margin: 16px 0 0; padding-left: 20px; } | |
| .sidebar-card li + li { margin-top: 13px; } | |
| .sidebar-card code { color: #bff4ff; background: rgba(49,215,255,.07); border: 1px solid rgba(49,215,255,.12); padding: 2px 5px; border-radius: 6px; } | |
| .runtime-line { display: flex; align-items: center; justify-content: space-between; gap: 22px; padding: 18px 0; border-bottom: 1px solid rgba(255,255,255,.06); } | |
| .runtime-line:last-child { border-bottom: 0; padding-bottom: 2px; } | |
| .runtime-line span { color: #718099; font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; min-width: 88px; } | |
| .runtime-line strong { color: #eaf0fb; font-size: 11px; text-align: right; max-width: 66%; overflow-wrap: anywhere; line-height: 1.55; } | |
| .status-card { | |
| border: 1px solid rgba(49,215,255,.16); | |
| background: linear-gradient(145deg, rgba(49,215,255,.065), rgba(139,92,246,.035)); | |
| border-radius: 18px; | |
| padding: 16px; | |
| color: #acb8cc; | |
| min-height: 98px; | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,.028), 0 16px 40px rgba(0,0,0,.18); | |
| } | |
| .status-row { display: flex; align-items: center; gap: 7px; margin-bottom: 9px; } | |
| .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #667085; box-shadow: 0 0 0 4px rgba(102,112,133,.08); } | |
| .status-card.active .status-dot { background: var(--green); box-shadow: 0 0 0 4px rgba(67,229,160,.08), 0 0 18px rgba(67,229,160,.55); } | |
| .status-kicker { color: #718099; font-size: 9px; font-weight: 900; letter-spacing: .13em; } | |
| .status-card strong { display: block; color: #fff; font-size: 15px; margin-bottom: 5px; } | |
| .status-details { display: block; color: #8e9aaf; font-size: 11px; line-height: 1.45; } | |
| .section-head { | |
| margin-bottom: 16px; | |
| padding: 2px 2px 0; | |
| } | |
| .section-head .section-kicker { color: var(--cyan); font-size: 10px; font-weight: 900; letter-spacing: .13em; text-transform: uppercase; } | |
| .section-head h2 { margin: 6px 0 5px; font-size: 21px; color: #fff; letter-spacing: -.025em; } | |
| .section-head p { margin: 0; color: #7f8aa0; font-size: 12px; line-height: 1.55; } | |
| #main-tabs [role="tablist"] { | |
| padding: 20px 22px 21px !important; | |
| gap: 14px !important; | |
| min-height: 88px !important; | |
| align-items: center !important; | |
| justify-content: flex-start !important; | |
| border-top: 1px solid rgba(255,255,255,.045) !important; | |
| border-bottom: 2px solid rgba(255,255,255,.10) !important; | |
| background: linear-gradient(180deg, rgba(255,255,255,.028), rgba(255,255,255,.012)) !important; | |
| } | |
| #main-tabs button[role="tab"] { | |
| margin: 0 !important; | |
| padding: 16px 24px !important; | |
| min-height: 54px !important; | |
| min-width: 172px !important; | |
| display: inline-flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| text-align: center !important; | |
| border: 1px solid rgba(255,255,255,.09) !important; | |
| border-radius: 14px !important; | |
| color: #a7b0c2 !important; | |
| background: rgba(255,255,255,.018) !important; | |
| font-family: "Segoe UI Variable", "Segoe UI", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Arial, sans-serif !important; | |
| font-size: 14px !important; | |
| font-weight: 700 !important; | |
| line-height: 1.2 !important; | |
| letter-spacing: -.015em !important; | |
| font-style: normal !important; | |
| transition: | |
| background .18s ease, | |
| border-color .18s ease, | |
| color .18s ease, | |
| box-shadow .18s ease, | |
| transform .18s ease !important; | |
| } | |
| /* Disable Gradio's built-in active underline so it cannot extend past the tab. */ | |
| #main-tabs button[role="tab"]::before, | |
| #main-tabs button[role="tab"]::after { | |
| display: none !important; | |
| content: none !important; | |
| width: 0 !important; | |
| height: 0 !important; | |
| border: 0 !important; | |
| box-shadow: none !important; | |
| } | |
| #main-tabs button[role="tab"]:hover { | |
| color: #f4f7ff !important; | |
| background: rgba(255,255,255,.045) !important; | |
| border-color: rgba(255,255,255,.12) !important; | |
| transform: translateY(-1px); | |
| } | |
| #main-tabs button[role="tab"][aria-selected="true"] { | |
| color: #ffffff !important; | |
| background: linear-gradient(135deg, rgba(49,215,255,.13), rgba(139,92,246,.11)) !important; | |
| border-color: rgba(49,215,255,.30) !important; | |
| box-shadow: | |
| inset 0 0 0 1px rgba(255,255,255,.025), | |
| 0 8px 22px rgba(0,0,0,.20) !important; | |
| } | |
| .tab-body { padding: 34px 28px 32px !important; } | |
| #load-button, #agent-button, #quality-button, #schema-button { | |
| font-weight: 900 !important; | |
| color: #041017 !important; | |
| background: linear-gradient(135deg, #70e6ff 0%, #31d7ff 42%, #8b7cff 100%) !important; | |
| border: 0 !important; | |
| border-radius: 12px !important; | |
| box-shadow: 0 10px 28px rgba(49,215,255,.15), inset 0 1px 0 rgba(255,255,255,.35) !important; | |
| transition: transform .16s ease, filter .16s ease, box-shadow .16s ease !important; | |
| } | |
| #load-button:hover, #agent-button:hover, #quality-button:hover, #schema-button:hover { | |
| transform: translateY(-1px); | |
| filter: brightness(1.07); | |
| box-shadow: 0 14px 34px rgba(49,215,255,.21), 0 0 28px rgba(139,92,246,.08) !important; | |
| } | |
| #agent-button { min-height: 50px !important; font-size: 14px !important; } | |
| button.secondary, button:not(.primary) { border-radius: 11px !important; } | |
| /* Data Health output heading normalization */ | |
| #quality-summary { | |
| margin-top: 20px !important; | |
| padding: 0 !important; | |
| overflow: visible !important; | |
| } | |
| #quality-summary h3 { | |
| margin: 0 0 14px !important; | |
| padding: 0 !important; | |
| color: #ffffff !important; | |
| font-family: "Segoe UI Variable", "Segoe UI", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Arial, sans-serif !important; | |
| font-size: 18px !important; | |
| font-weight: 750 !important; | |
| line-height: 1.3 !important; | |
| letter-spacing: -.02em !important; | |
| overflow: visible !important; | |
| } | |
| #quality-summary ul { | |
| margin: 0 !important; | |
| padding-left: 20px !important; | |
| } | |
| #quality-summary li { | |
| margin: 7px 0 !important; | |
| color: #c4ccda !important; | |
| line-height: 1.55 !important; | |
| } | |
| #quality-issues { | |
| margin-top: 20px !important; | |
| overflow: visible !important; | |
| } | |
| #quality-issues [data-testid="block-info"] { | |
| position: relative !important; | |
| inset: auto !important; | |
| transform: none !important; | |
| display: block !important; | |
| width: 100% !important; | |
| margin: 0 0 10px !important; | |
| padding: 0 2px !important; | |
| color: #ffffff !important; | |
| font-family: "Segoe UI Variable", "Segoe UI", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Arial, sans-serif !important; | |
| font-size: 13px !important; | |
| font-weight: 700 !important; | |
| line-height: 1.35 !important; | |
| letter-spacing: -.01em !important; | |
| overflow: visible !important; | |
| clip: auto !important; | |
| white-space: normal !important; | |
| } | |
| #quality-issues [data-testid="block-info"] span { | |
| display: inline-block !important; | |
| line-height: 1.35 !important; | |
| overflow: visible !important; | |
| } | |
| /* Schema validation output heading normalization */ | |
| #schema-summary { | |
| margin-top: 22px !important; | |
| padding: 0 !important; | |
| overflow: visible !important; | |
| } | |
| #schema-summary h3 { | |
| margin: 0 0 14px !important; | |
| padding: 0 !important; | |
| color: #ffffff !important; | |
| font-family: "Segoe UI Variable", "Segoe UI", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Arial, sans-serif !important; | |
| font-size: 18px !important; | |
| font-weight: 750 !important; | |
| line-height: 1.3 !important; | |
| letter-spacing: -.02em !important; | |
| overflow: visible !important; | |
| } | |
| #schema-summary p { | |
| margin: 8px 0 !important; | |
| color: #c4ccda !important; | |
| line-height: 1.6 !important; | |
| } | |
| #schema-summary strong { | |
| color: #ffffff !important; | |
| font-weight: 800 !important; | |
| } | |
| #schema-summary ul { | |
| margin: 10px 0 0 !important; | |
| padding-left: 20px !important; | |
| } | |
| #schema-summary li { | |
| margin: 7px 0 !important; | |
| color: #c4ccda !important; | |
| line-height: 1.55 !important; | |
| } | |
| /* Gradio form surfaces */ | |
| .gradio-container textarea, | |
| .gradio-container input, | |
| .gradio-container select { | |
| color: #eef3ff !important; | |
| } | |
| .gradio-container .wrap, | |
| .gradio-container .form, | |
| .gradio-container [data-testid="block-info"] { border-color: rgba(255,255,255,.075) !important; } | |
| .gradio-container .block { | |
| border-radius: 15px !important; | |
| } | |
| .gradio-container label span { color: #9ca8bc !important; } | |
| /* File upload dropzone */ | |
| .gradio-container [data-testid="file-upload"] { | |
| border: 1px dashed rgba(49,215,255,.23) !important; | |
| background: linear-gradient(145deg, rgba(49,215,255,.035), rgba(139,92,246,.025)) !important; | |
| border-radius: 16px !important; | |
| } | |
| /* Dataset dataframe header fix only */ | |
| #dataset-preview, | |
| #dataset-schema { | |
| margin-top: 18px !important; | |
| overflow: visible !important; | |
| } | |
| #dataset-preview [data-testid="block-info"], | |
| #dataset-schema [data-testid="block-info"] { | |
| position: relative !important; | |
| inset: auto !important; | |
| transform: none !important; | |
| display: block !important; | |
| width: 100% !important; | |
| margin: 0 0 9px !important; | |
| padding: 0 2px !important; | |
| line-height: 1.35 !important; | |
| overflow: visible !important; | |
| clip: auto !important; | |
| white-space: normal !important; | |
| } | |
| #dataset-preview [data-testid="block-info"] span, | |
| #dataset-schema [data-testid="block-info"] span { | |
| display: inline-block !important; | |
| line-height: 1.35 !important; | |
| overflow: visible !important; | |
| } | |
| /* Tables */ | |
| .gradio-container table { border-collapse: separate !important; border-spacing: 0 !important; } | |
| .gradio-container th { | |
| background: #111626 !important; | |
| color: #c9d3e6 !important; | |
| border-color: rgba(255,255,255,.07) !important; | |
| font-size: 11px !important; | |
| } | |
| .gradio-container td { | |
| background: rgba(9,12,22,.84) !important; | |
| color: #abb7cb !important; | |
| border-color: rgba(255,255,255,.055) !important; | |
| font-size: 11px !important; | |
| } | |
| /* Nested result tabs */ | |
| .result-tabs [role="tablist"] { gap: 6px !important; margin-top: 8px !important; } | |
| .result-tabs button[role="tab"] { | |
| border-radius: 9px !important; | |
| font-weight: 800 !important; | |
| } | |
| .agent-answer { | |
| border: 1px solid rgba(139,92,246,.14) !important; | |
| background: linear-gradient(145deg, rgba(139,92,246,.035), rgba(49,215,255,.025)) !important; | |
| border-radius: 16px !important; | |
| padding: 6px 16px 12px !important; | |
| margin-top: 4px !important; | |
| } | |
| .warning-note { | |
| margin-top: 15px; | |
| padding: 12px 14px; | |
| border: 1px solid rgba(255,189,89,.18); | |
| border-left: 3px solid var(--amber); | |
| border-radius: 12px; | |
| background: rgba(255,189,89,.045); | |
| color: #b9b3a5; | |
| font-size: 11px; | |
| line-height: 1.55; | |
| } | |
| .footer-note { | |
| color: #5f6b80; | |
| font-size: 10px; | |
| text-align: center; | |
| margin-top: 18px; | |
| letter-spacing: .05em; | |
| } | |
| /* ========================================================= | |
| VISUAL-ONLY POLISH | |
| No textual content or application logic is changed. | |
| ========================================================= */ | |
| /* Ambient hero motion */ | |
| .hero::after { | |
| animation: heroGlowDrift 14s ease-in-out infinite alternate; | |
| transform-origin: center; | |
| } | |
| @keyframes heroGlowDrift { | |
| 0% { transform: translate3d(0, 0, 0) scale(1); opacity: .68; } | |
| 50% { transform: translate3d(-20px, 14px, 0) scale(1.035); opacity: .88; } | |
| 100% { transform: translate3d(12px, -8px, 0) scale(.985); opacity: .72; } | |
| } | |
| /* Animated orbital graphic */ | |
| .hero-orbit { | |
| overflow: hidden; | |
| transition: transform .35s ease, border-color .35s ease, box-shadow .35s ease; | |
| } | |
| .hero-orbit:hover { | |
| transform: translateY(-3px) scale(1.015); | |
| border-color: rgba(49,215,255,.22); | |
| box-shadow: | |
| inset 0 0 55px rgba(49,215,255,.055), | |
| 0 18px 50px rgba(0,0,0,.24); | |
| } | |
| .hero-orbit::before { | |
| animation: orbitSpin 13s linear infinite; | |
| } | |
| .hero-orbit::after { | |
| animation: orbitSpinReverse 8s linear infinite; | |
| } | |
| .hero-orbit-core { | |
| animation: corePulse 2.8s ease-in-out infinite; | |
| } | |
| @keyframes orbitSpin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| @keyframes orbitSpinReverse { | |
| from { transform: rotate(360deg); } | |
| to { transform: rotate(0deg); } | |
| } | |
| @keyframes corePulse { | |
| 0%,100% { | |
| transform: translate(-50%, -50%) scale(1); | |
| box-shadow: 0 0 20px rgba(49,215,255,.50); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(1.14); | |
| box-shadow: | |
| 0 0 28px rgba(49,215,255,.76), | |
| 0 0 42px rgba(139,92,246,.20); | |
| } | |
| } | |
| /* Badge interaction */ | |
| .badge { | |
| position: relative; | |
| overflow: hidden; | |
| transition: | |
| transform .18s ease, | |
| border-color .18s ease, | |
| background .18s ease, | |
| color .18s ease; | |
| } | |
| .badge:hover { | |
| transform: translateY(-2px); | |
| border-color: rgba(49,215,255,.24); | |
| background: rgba(49,215,255,.055); | |
| color: #dff8ff; | |
| } | |
| /* Workflow cards gain depth and a subtle pipeline connector */ | |
| .workflow-strip { | |
| position: relative; | |
| } | |
| .workflow-step { | |
| overflow: hidden; | |
| transition: | |
| transform .20s ease, | |
| border-color .20s ease, | |
| background .20s ease, | |
| box-shadow .20s ease; | |
| } | |
| .workflow-step::after { | |
| content: ""; | |
| position: absolute; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, rgba(49,215,255,.32), transparent); | |
| opacity: 0; | |
| transform: scaleX(.35); | |
| transition: opacity .20s ease, transform .20s ease; | |
| } | |
| .workflow-step:hover { | |
| transform: translateY(-3px); | |
| background: linear-gradient(145deg, rgba(255,255,255,.052), rgba(255,255,255,.022)); | |
| border-color: rgba(255,255,255,.15); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255,255,255,.04), | |
| 0 12px 30px rgba(0,0,0,.20); | |
| } | |
| .workflow-step:hover::after { | |
| opacity: 1; | |
| transform: scaleX(1); | |
| } | |
| /* Main application panel gets a faint inner glow */ | |
| .app-panel { | |
| position: relative; | |
| } | |
| .app-panel::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| pointer-events: none; | |
| border-radius: inherit; | |
| /* Keep the glass effect on a non-ancestor layer so fixed dropdowns stay viewport-anchored. */ | |
| backdrop-filter: blur(18px); | |
| -webkit-backdrop-filter: blur(18px); | |
| box-shadow: | |
| inset 0 0 60px rgba(49,215,255,.018), | |
| inset 0 0 90px rgba(139,92,246,.012); | |
| z-index: 0; | |
| } | |
| #main-tabs { | |
| position: relative; | |
| z-index: 1; | |
| } | |
| /* Active tab gets a restrained glow */ | |
| #main-tabs button[role="tab"][aria-selected="true"] { | |
| box-shadow: | |
| inset 0 0 0 1px rgba(255,255,255,.025), | |
| 0 8px 22px rgba(0,0,0,.20), | |
| 0 0 18px rgba(49,215,255,.055) !important; | |
| } | |
| /* Sidebar cards feel less static */ | |
| .sidebar-card { | |
| position: relative; | |
| overflow: hidden; | |
| transition: | |
| transform .20s ease, | |
| border-color .20s ease, | |
| box-shadow .20s ease; | |
| } | |
| .sidebar-card::before { | |
| content: ""; | |
| position: absolute; | |
| width: 140px; | |
| height: 140px; | |
| right: -85px; | |
| top: -85px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle, rgba(49,215,255,.075), transparent 68%); | |
| pointer-events: none; | |
| opacity: .7; | |
| } | |
| .sidebar-card:hover { | |
| transform: translateY(-2px); | |
| border-color: rgba(49,215,255,.15); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255,255,255,.035), | |
| 0 18px 45px rgba(0,0,0,.24); | |
| } | |
| /* Workspace status pulse */ | |
| .status-card .status-dot { | |
| animation: statusPulse 3s ease-in-out infinite; | |
| } | |
| .status-card.active .status-dot { | |
| animation: statusActivePulse 2.2s ease-in-out infinite; | |
| } | |
| @keyframes statusPulse { | |
| 0%,100% { opacity: .65; transform: scale(1); } | |
| 50% { opacity: 1; transform: scale(1.12); } | |
| } | |
| @keyframes statusActivePulse { | |
| 0%,100% { | |
| transform: scale(1); | |
| box-shadow: 0 0 0 4px rgba(67,229,160,.08), 0 0 12px rgba(67,229,160,.40); | |
| } | |
| 50% { | |
| transform: scale(1.13); | |
| box-shadow: 0 0 0 6px rgba(67,229,160,.06), 0 0 22px rgba(67,229,160,.62); | |
| } | |
| } | |
| /* Primary button sheen */ | |
| #load-button, | |
| #agent-button, | |
| #quality-button, | |
| #schema-button { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| #load-button::after, | |
| #agent-button::after, | |
| #quality-button::after, | |
| #schema-button::after { | |
| content: ""; | |
| position: absolute; | |
| top: -45%; | |
| left: -38%; | |
| width: 28%; | |
| height: 190%; | |
| transform: rotate(18deg); | |
| background: linear-gradient( | |
| 90deg, | |
| transparent, | |
| rgba(255,255,255,.42), | |
| transparent | |
| ); | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| #load-button:hover::after, | |
| #agent-button:hover::after, | |
| #quality-button:hover::after, | |
| #schema-button:hover::after { | |
| opacity: .75; | |
| animation: buttonSheen .75s ease forwards; | |
| } | |
| @keyframes buttonSheen { | |
| from { left: -38%; } | |
| to { left: 112%; } | |
| } | |
| /* Upload zone becomes more tactile */ | |
| .gradio-container [data-testid="file-upload"] { | |
| transition: | |
| border-color .22s ease, | |
| background .22s ease, | |
| box-shadow .22s ease, | |
| transform .22s ease; | |
| } | |
| .gradio-container [data-testid="file-upload"]:hover { | |
| border-color: rgba(49,215,255,.42) !important; | |
| background: | |
| linear-gradient(145deg, rgba(49,215,255,.055), rgba(139,92,246,.035)) !important; | |
| box-shadow: | |
| inset 0 0 34px rgba(49,215,255,.025), | |
| 0 10px 26px rgba(0,0,0,.14); | |
| } | |
| /* Data tables get a subtle row hover */ | |
| .gradio-container tbody tr { | |
| transition: background .16s ease; | |
| } | |
| .gradio-container tbody tr:hover td { | |
| background: rgba(49,215,255,.035) !important; | |
| color: #d7e2f5 !important; | |
| } | |
| /* Accordions feel interactive without changing layout */ | |
| .gradio-container details { | |
| transition: | |
| border-color .20s ease, | |
| box-shadow .20s ease, | |
| background .20s ease; | |
| } | |
| .gradio-container details:hover { | |
| border-color: rgba(49,215,255,.13) !important; | |
| box-shadow: 0 10px 28px rgba(0,0,0,.12); | |
| } | |
| /* Respect reduced-motion preferences */ | |
| @media (prefers-reduced-motion: reduce) { | |
| .hero::after, | |
| .hero-orbit::before, | |
| .hero-orbit::after, | |
| .hero-orbit-core, | |
| .status-card .status-dot, | |
| .status-card.active .status-dot { | |
| animation: none !important; | |
| } | |
| #load-button:hover::after, | |
| #agent-button:hover::after, | |
| #quality-button:hover::after, | |
| #schema-button:hover::after { | |
| animation: none !important; | |
| } | |
| .workflow-step, | |
| .sidebar-card, | |
| .badge, | |
| .hero-orbit { | |
| transition: none !important; | |
| } | |
| } | |
| @media (max-width: 980px) { | |
| .hero-grid { grid-template-columns: 1fr; } | |
| .hero-orbit { display: none; } | |
| .workflow-strip { grid-template-columns: repeat(2, minmax(0,1fr)); } | |
| } | |
| @media (max-width: 800px) { | |
| .main-shell { padding: 14px 10px 28px; } | |
| .hero { padding: 28px 21px 26px; border-radius: 22px; } | |
| .hero h1 { font-size: 42px; } | |
| .workflow-strip { grid-template-columns: 1fr 1fr; } | |
| .tab-body { padding: 20px 15px 22px !important; } | |
| #main-tabs [role="tablist"] { padding: 14px !important; min-height: 74px !important; gap: 10px !important; overflow-x: auto; flex-wrap: nowrap !important; } | |
| #main-tabs button[role="tab"] { min-width: 160px !important; padding: 14px 20px !important; } | |
| } | |
| @media (max-width: 560px) { | |
| .hero h1 { font-size: 36px; } | |
| .workflow-strip { grid-template-columns: 1fr; } | |
| } | |
| ''' | |
| HEAD = ''' | |
| <meta name="theme-color" content="#070913"> | |
| <meta name="description" content="Modern agentic data engineering workspace for profiling, schema validation, quality auditing, SQL, and PySpark generation."> | |
| ''' | |
| def build_app(): | |
| runtime = f"HF Inference 路 {HF_MODEL_ID}" if HF_TOKEN else "Qwen/Qwen3-Coder-30B-A3B-Instruct" | |
| provider = HF_PROVIDER or "HF automatic routing" | |
| hero = f''' | |
| <div class="hero"> | |
| <div class="hero-grid"> | |
| <div> | |
| <div class="eyebrow"><span class="eyebrow-dot"></span> Agentic Data Engineering - SQL & PySpark</div> | |
| <h1>Agentic Data Engineer</h1> | |
| <p>Use this Data Engineering AI agent to audit datasets, find data quality issues, check data constraints, and generate SQL and PySpark pipelines to fix them.</p> | |
| <div class="badges"> | |
| <span class="badge badge-accent"><strong>Agent</strong> HF tool calling</span> | |
| <span class="badge"><strong>Model</strong> {html.escape(HF_MODEL_ID)}</span> | |
| <span class="badge"><strong>SQL</strong> DuckDB + SQLGlot</span> | |
| <span class="badge"><strong>Output</strong> PySpark + SQL</span> | |
| </div> | |
| </div> | |
| <div class="hero-orbit" aria-hidden="true"><div class="hero-orbit-core"></div></div> | |
| </div> | |
| </div> | |
| ''' | |
| workflow = ''' | |
| <div class="workflow-strip"> | |
| <div class="workflow-step"><span>01 路 INGEST</span><strong>load dataset</strong><small>CSV 路 Parquet 路 JSON 路 Excel</small></div> | |
| <div class="workflow-step"><span>02 路 ANALYZE</span><strong>data profiling + audit</strong><small>schema 路 nulls 路 duplicates 路 quality</small></div> | |
| <div class="workflow-step"><span>03 路 VALIDATE</span><strong>data validation</strong><small>Expected schema + warnings</small></div> | |
| <div class="workflow-step"><span>04 路 BUILD</span><strong>generate pipeline</strong><small>SQL 路 PySpark 路 tool trace</small></div> | |
| </div> | |
| ''' | |
| with gr.Blocks(title=APP_TITLE) as demo: | |
| sid = gr.State("") | |
| with gr.Column(elem_classes=["main-shell"]): | |
| gr.HTML(hero) | |
| gr.HTML(workflow) | |
| with gr.Row(equal_height=False): | |
| with gr.Column(scale=9, min_width=590, elem_classes=["app-panel"]): | |
| with gr.Tabs(selected="load", elem_id="main-tabs"): | |
| with gr.Tab("01. Dataset", id="load"): | |
| with gr.Column(elem_classes=["tab-body"]): | |
| gr.HTML('''<div class="section-head"><div class="section-kicker">Dataset workspace</div><h2>Load Your Data In</h2><p>Upload a supported dataset file and generate a fast structural profile before running the agent</p></div>''') | |
| upload = gr.File( | |
| label="Dataset file", | |
| file_types=[".csv", ".parquet", ".json", ".jsonl", ".xlsx", ".xls"], | |
| ) | |
| with gr.Row(): | |
| load_btn = gr.Button("Load + Profile Dataset", variant="primary", elem_id="load-button") | |
| example_btn = gr.Button("Load HF Example Dataset") | |
| clear_btn = gr.Button("Clear Workspace") | |
| preview = gr.Dataframe(label="Data preview", interactive=False, wrap=True, elem_id="dataset-preview") | |
| schema_table = gr.Dataframe(label="Inferred schema", interactive=False, wrap=True, elem_id="dataset-schema") | |
| gr.HTML("<div class='warning-note'><strong>Public Space:</strong> do not upload confidential, regulated, proprietary, production customer, or PII.</div>") | |
| with gr.Tab("02. Data Quality (Audits)", id="quality"): | |
| with gr.Column(elem_classes=["tab-body"]): | |
| gr.HTML('''<div class="section-head"><div class="section-kicker">Data Auditing</div><h2>Data Quality (audits)</h2><p>Run data quality checks first, then compare the dataset against an editable expected schema</p></div>''') | |
| with gr.Row(): | |
| quality_btn = gr.Button("Run Quality Audit", variant="primary", elem_id="quality-button") | |
| schema_btn = gr.Button("Validate Schema", variant="primary", elem_id="schema-button") | |
| quality_summary = gr.Markdown("", elem_id="quality-summary") | |
| quality_table = gr.Dataframe(label="Detected issues", interactive=False, wrap=True, elem_id="quality-issues") | |
| expected_schema = gr.Code(label="Expected schema JSON", language="json", value="{}", lines=16) | |
| schema_summary = gr.Markdown("", elem_id="schema-summary") | |
| schema_result = gr.JSON(label="Schema validation details") | |
| with gr.Tab("03. Data Engineering Agent", id="agent"): | |
| with gr.Column(elem_classes=["tab-body"]): | |
| gr.HTML('''<div class="section-head"><div class="section-kicker">Data Engineering Agent</div><h2>Turn Findings Into Pipeline Code</h2><p>Describe the engineering outcome. the agent can inspect the dataset, call data quality tools, validate SQL, and generate relevant code</p></div>''') | |
| task = gr.Textbox( | |
| label="Engineering task", | |
| placeholder="Example: Audit this dataset, identify the highest-risk quality issues, then generate an idempotent SQL cleanup pipeline and a PySpark equivalent.", | |
| lines=5, | |
| max_lines=10, | |
| ) | |
| with gr.Row(): | |
| dialect = gr.Dropdown( | |
| choices=["duckdb", "spark", "postgres", "mysql", "snowflake", "bigquery", "tsql"], | |
| value="duckdb", | |
| label="SQL dialect", | |
| ) | |
| target = gr.Dropdown( | |
| choices=["SQL + PySpark", "SQL only", "PySpark only"], | |
| value="SQL + PySpark", | |
| label="Target output", | |
| ) | |
| agent_btn = gr.Button("Run Data Engineering Agent", variant="primary", elem_id="agent-button") | |
| answer = gr.Markdown("") | |
| with gr.Tabs(elem_classes=["result-tabs"]): | |
| with gr.Tab("SQL Pipeline"): | |
| sql_out = gr.Code(label="Generated SQL", language="sql", lines=22) | |
| with gr.Tab("PySpark Pipeline"): | |
| py_out = gr.Code(label="Generated PySpark", language="python", lines=22) | |
| with gr.Tab("Tool Trace"): | |
| trace = gr.JSON(label="Agent tool calls and observations") | |
| with gr.Column(scale=4, min_width=330, elem_classes=["side-column"]): | |
| status = gr.HTML(_status(None)) | |
| gr.HTML(f''' | |
| <div class="sidebar-card"> | |
| <h3>Runtime</h3> | |
| <div class="runtime-line"><span>Base Model path</span><strong>{html.escape(runtime)}</strong></div> | |
| <div class="runtime-line"><span>Provider</span><strong>{html.escape(provider)}</strong></div> | |
| <div class="runtime-line"><span>Execution</span><strong>DuckDB sandbox</strong></div> | |
| </div> | |
| <div class="sidebar-card"> | |
| <h3>Agent Capabilities</h3> | |
| <ol> | |
| <li>Inspect shape, types, nulls, cardinality, and examples</li> | |
| <li>Run data-quality checks</li> | |
| <li>Validate the expected schema contract</li> | |
| <li>Validate and execute safe read-only SQL against <code>dataset</code></li> | |
| <li>Generate SQL and PySpark remediation pipelines</li> | |
| </ol> | |
| </div> | |
| <div class="sidebar-card"> | |
| <h3>Execution Model</h3> | |
| <p>DuckDB runs lightweight validation inside the Space. PySpark is generated as deployable target code, so the UI stays fast without carrying a Spark JVM runtime.</p> | |
| </div> | |
| ''') | |
| with gr.Accordion("Agent controls", open=True): | |
| temperature = gr.Slider(0.0, .8, value=.15, step=.05, label="Temperature") | |
| max_tokens = gr.Slider(600, 3200, value=DEFAULT_MAX_TOKENS, step=100, label="Maximum model tokens") | |
| with gr.Accordion("Diagnostics", open=False): | |
| diagnostics = gr.JSON(value={}, label="Runtime diagnostics") | |
| gr.HTML("<div class='footer-note'>QWEN3-CODER 路 HUGGING FACE INFERENCE 路 DUCKDB 路 SQLGLOT 路 GRADIO</div>") | |
| dataset_outputs = [sid, status, preview, schema_table, expected_schema, diagnostics] | |
| # Preload the HF example when a new browser session opens. | |
| demo.load( | |
| load_example_dataset, | |
| inputs=[sid], | |
| outputs=dataset_outputs, | |
| ) | |
| # Users can still replace the example with their own upload at any time | |
| load_btn.click(load_dataset, [upload, sid], dataset_outputs) | |
| example_btn.click(load_example_dataset, [sid], dataset_outputs) | |
| quality_btn.click(run_quality, [sid], [quality_summary, quality_table, diagnostics]) | |
| schema_btn.click(validate_schema, [expected_schema, sid], [schema_summary, schema_result]) | |
| agent_btn.click(run_agent, [task, expected_schema, dialect, target, temperature, max_tokens, sid], [answer, sql_out, py_out, trace, diagnostics]) | |
| task.submit(run_agent, [task, expected_schema, dialect, target, temperature, max_tokens, sid], [answer, sql_out, py_out, trace, diagnostics]) | |
| clear_btn.click(clear_session, [sid], [sid, status, preview, schema_table, expected_schema, quality_summary, quality_table, schema_summary, sql_out, py_out, trace, diagnostics]) | |
| return demo | |
| if __name__ == "__main__": | |
| app = build_app() | |
| app.queue(default_concurrency_limit=2).launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.getenv("PORT", "7860")), | |
| show_error=True, | |
| ssr_mode=False, | |
| theme=gr.themes.Base(), | |
| css=CSS, | |
| head=HEAD, | |
| ) |