AaronTekle's picture
Update app.py
f066fe2 verified
Raw
History Blame Contribute Delete
86.9 kB
# postponed evaluation of annotations for cleaner forward-reference typing
from __future__ import annotations
# standard-library utilities for escaping, serialization, environment access, synchronization, temporary files, and identifiers
import html
import json
import os
import threading
import tempfile
import uuid
# path and typing helpers used throughout dataset and UI handling
from pathlib import Path
from typing import Any
import spaces
# libraries for the Gradio interface, model serialization, dataframe handling, and Hugging Face downloads
import gradio as gr
import joblib
import pandas as pd
from huggingface_hub import hf_hub_download
# local ML agent that coordinates tool-based analysis and code generation
from agent import MachineLearningAgent
# app configuration values that control runtime behavior, dataset limits, and model settings
from config import (
APP_TITLE,
DEFAULT_MAX_TOKENS,
EXAMPLE_DATASET_FILE,
EXAMPLE_DATASET_REPO,
EXAMPLE_DATASET_TARGET,
HF_MODEL_ID,
HF_PROVIDER,
HF_TOKEN,
MAX_PROFILE_ROWS,
MAX_TRAIN_ROWS,
MAX_UPLOAD_MB,
SUPPORTED_DATA_EXTENSIONS,
)
# supported algorithms and the reusable machine-learning context implementation
from ml_engine import (
ALL_ALGORITHMS,
CLASSIFICATION_ALGORITHMS,
REGRESSION_ALGORITHMS,
MLContext,
)
# ZeroGPU startup registration probe.
# This function is never called, but guarantees Hugging Face detects @spaces.GPU at startup.
@spaces.GPU(duration=1)
def _zerogpu_startup_probe():
return None
# ml contexts by browser session identifier
SESSIONS: dict[str, MLContext] = {}
# shared session storage from concurrent access across Gradio requests
LOCK = threading.Lock()
# canonical HF source label for the bundled example dataset
EXAMPLE_DATASET_SOURCE = f"hf://datasets/{EXAMPLE_DATASET_REPO}/{EXAMPLE_DATASET_FILE}"
# Returns the existing session identifier or creates a new unique identifier when needed
def _sid(sid: str | None) -> str:
return sid or uuid.uuid4().hex
# Retrieves the machine-learning context associated with a session in a thread-safe manner
def _get(sid: str | None) -> MLContext | None:
if not sid:
return None
with LOCK:
return SESSIONS.get(sid)
# Stores a machine-learning context for the specified session under the shared lock
def _set(sid: str, ctx: MLContext) -> None:
with LOCK:
SESSIONS[sid] = ctx
# Builds the workspace-status markup from the currently loaded dataset context
def _status(ctx: MLContext | None, message: str = "Ready") -> str:
# Handles the empty-workspace state before attempting to inspect dataset metadata
if ctx is None:
details = "No dataset loaded"
state_class = "ready"
else:
# Reads the active dataset profile to summarize its dimensions and source
profile = ctx.profile()
details = f"{profile['rows']:,} rows 路 {profile['columns']:,} columns 路 {html.escape(ctx.source_name)}"
state_class = "active"
return f'''
<div class="status-card {state_class}">
<div class="status-row">
<span class="status-dot"></span>
<span class="status-kicker">WORKSPACE STATUS</span>
</div>
<strong>{html.escape(message)}</strong>
<span class="status-details">{details}</span>
</div>
'''
# Converts the dataset schema profile into a dataframe suitable for display in Gradio
def _schema_table(ctx: MLContext) -> pd.DataFrame:
return pd.DataFrame(
[
{
"column": item["name"],
"logical_type": item["logical_type"],
"pandas_dtype": item["pandas_dtype"],
"null_pct": item["null_pct"],
"unique_count": item["unique_count"],
"examples": json.dumps(item["examples"], default=str),
}
for item in ctx.profile()["column_schema"]
]
)
# Returns the algorithm choices appropriate for the currently resolved problem type
def _algorithm_choices(problem_type: str) -> list[str]:
if problem_type == "classification":
return ["Auto"] + CLASSIFICATION_ALGORITHMS
if problem_type == "regression":
return ["Auto"] + REGRESSION_ALGORITHMS
return ALL_ALGORITHMS
# Chooses a preferred prediction target from an explicit selection or inferred candidates
def _preferred_target(ctx: MLContext, preferred: str | None = None) -> str | None:
# Normalizes dataframe column names to strings for consistent UI selection behavior
columns = [str(c) for c in ctx.dataframe.columns]
if preferred and preferred in columns:
return preferred
candidates = ctx.target_candidates()
if candidates:
return candidates[0]
return columns[-1] if columns else None
# Packages dataset-derived values into the coordinated outputs expected by the interface
def _dataset_outputs(
ctx: MLContext,
sid: str,
message: str,
preferred_target: str | None = None,
extra_diagnostics: dict[str, Any] | None = None,
):
# Profiles the loaded dataset and resolves the target and inferred supervised-learning task
profile = ctx.profile()
target = _preferred_target(ctx, preferred_target)
inferred = "Auto"
resolved = None
if target:
try:
resolved = ctx.infer_problem_type(target, "Auto")
except Exception:
resolved = None
# Collects dataset metadata and current modeling limits for the diagnostics panel
diagnostics = {
"source": ctx.source_name,
"rows_loaded": profile["rows"],
"columns": profile["columns"],
"memory_mb": profile["memory_mb"],
"duplicate_rows": profile["duplicate_rows"],
"profiling_row_limit": MAX_PROFILE_ROWS,
"training_row_limit": MAX_TRAIN_ROWS,
"suggested_target": target,
"inferred_problem_type": resolved,
}
# Merges any caller-specific diagnostics into the standard dataset diagnostics payload
if extra_diagnostics:
diagnostics.update(extra_diagnostics)
# Returns synchronized UI values for status, previews, selectors, and diagnostics
return (
sid,
_status(ctx, message),
ctx.dataframe.head(24),
_schema_table(ctx),
gr.Dropdown(choices=[str(c) for c in ctx.dataframe.columns], value=target),
gr.Dropdown(value=inferred, choices=["Auto", "classification", "regression"]),
gr.Dropdown(choices=_algorithm_choices(resolved or ""), value="Auto"),
diagnostics,
)
# Loads, validates, profiles, and registers a user-uploaded dataset for the active session
def load_dataset(uploaded: Any, sid: str | None):
# Ensures the upload workflow has a valid session identifier before processing the file
sid = _sid(sid)
# Returns empty interface state when the user has not selected a file
if uploaded is None:
return (
sid,
_status(_get(sid), "No file selected"),
pd.DataFrame(),
pd.DataFrame(),
gr.Dropdown(choices=[], value=None),
gr.Dropdown(value="Auto", choices=["Auto", "classification", "regression"]),
gr.Dropdown(choices=ALL_ALGORITHMS, value="Auto"),
{},
)
# Resolves the uploaded file path before validating its format and size
path = Path(uploaded.name if hasattr(uploaded, "name") else str(uploaded))
# Rejects files whose extension is not included in the supported dataset formats
if path.suffix.lower() not in SUPPORTED_DATA_EXTENSIONS:
raise gr.Error("Unsupported file type.")
# Calculates the upload size in megabytes and enforces the configured size limit
size_mb = path.stat().st_size / 1024 / 1024
if size_mb > MAX_UPLOAD_MB:
raise gr.Error(f"{path.name} is {size_mb:.1f} MB. Limit is {MAX_UPLOAD_MB} MB.")
# Creates the machine-learning context from the uploaded file and stores it in the session
try:
ctx = MLContext.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
# Returns the standard dataset outputs along with upload-specific diagnostics
return _dataset_outputs(
ctx,
sid,
"Dataset loaded",
extra_diagnostics={"dataset_origin": "User upload", "upload_mb": round(size_mb, 3)},
)
# Downloads and loads the configured public example dataset into the current session
def load_example_dataset(sid: str | None):
# Ensures the example-dataset workflow operates under a valid session identifier
sid = _sid(sid)
# Attempts to retrieve the example dataset from the configured Hugging Face repository
try:
cached_path = hf_hub_download(
repo_id=EXAMPLE_DATASET_REPO,
filename=EXAMPLE_DATASET_FILE,
repo_type="dataset",
)
# Profiles the downloaded example dataset, labels its source, and stores its context
ctx = MLContext.from_path(cached_path, max_rows=MAX_PROFILE_ROWS)
ctx.source_name = EXAMPLE_DATASET_SOURCE
_set(sid, ctx)
return _dataset_outputs(
ctx,
sid,
"HF example dataset loaded",
preferred_target=EXAMPLE_DATASET_TARGET,
extra_diagnostics={
"dataset_repo": EXAMPLE_DATASET_REPO,
"dataset_file": EXAMPLE_DATASET_FILE,
"dataset_origin": "Hugging Face Datasets",
"preloaded_example": True,
},
)
# Falls back to empty interface outputs while preserving details about download failures
except Exception as exc:
return (
sid,
_status(_get(sid), "HF example dataset unavailable"),
pd.DataFrame(),
pd.DataFrame(),
gr.Dropdown(choices=[], value=None),
gr.Dropdown(value="Auto", choices=["Auto", "classification", "regression"]),
gr.Dropdown(choices=ALL_ALGORITHMS, value="Auto"),
{
"dataset_repo": EXAMPLE_DATASET_REPO,
"preloaded_example": False,
"error": str(exc),
},
)
# Keeps the algorithm selector, generated pipeline, and recommendation data synchronized
def sync_model_controls(target: str, problem_type: str, algorithm: str, sid: str):
# Retrieves the active dataset context required to infer compatible modeling settings
ctx = _get(sid)
# Returns neutral controls when no dataset context or target is currently available
if ctx is None or not target:
return gr.Dropdown(choices=ALL_ALGORITHMS, value="Auto"), "", {}
# Resolves the modeling task and regenerates dependent UI values from the selected target
try:
resolved = ctx.infer_problem_type(target, problem_type)
choices = _algorithm_choices(resolved)
selected = algorithm if algorithm in choices else "Auto"
code = ctx.generate_pipeline_code(target, selected, problem_type, 0.2)
info = ctx.modeling_recommendation(target, problem_type)
return gr.Dropdown(choices=choices, value=selected), code, info
except Exception as exc:
return gr.Dropdown(choices=ALL_ALGORITHMS, value="Auto"), f"# Could not generate pipeline yet: {exc}", {"error": str(exc)}
# Generates reproducible pipeline code for the selected target and modeling configuration
def generate_pipeline_code(target: str, algorithm: str, problem_type: str, test_size: float, sid: str):
# Retrieves the active session context before attempting pipeline generation
ctx = _get(sid)
# Returns instructional placeholder text when required modeling inputs are missing
if ctx is None:
return "# Load a dataset first."
if not target:
return "# Select a target column first."
# Delegates pipeline construction to the machine-learning context with the chosen settings
try:
return ctx.generate_pipeline_code(target, algorithm, problem_type, float(test_size))
except Exception as exc:
return f"# Pipeline Generation Failed: {exc}"
# Formats a training result dictionary into a concise Markdown model-evaluation summary
def _metrics_markdown(result: dict[str, Any]) -> str:
# Extracts reported metrics and renders each metric as a Markdown bullet
metrics = result.get("metrics", {})
metric_lines = "\n".join(f"- **{name}**: `{value}`" for name, value in metrics.items()) or "- No metrics returned."
return f'''### Model Evaluation
**{result.get('algorithm', 'Model')}** 路 `{result.get('problem_type', 'unknown')}`
{metric_lines}
**Training Footprint:** {result.get('train_rows', 0):,} train rows 路 {result.get('test_rows', 0):,} test rows 路 {result.get('feature_columns', 0)} raw features 路 {result.get('fit_seconds', 0)}s fit time
'''
# Trains the selected model, prepares evaluation outputs, and serializes the fitted pipeline
def train_model(target: str, algorithm: str, problem_type: str, test_size: float, sid: str):
# Validates that a dataset context and target are available before starting training
ctx = _get(sid)
if ctx is None:
raise gr.Error("Load a dataset first.")
if not target:
raise gr.Error("Select a target column.")
# Runs model training and captures the resulting metrics, metadata, and fitted pipeline
try:
result = ctx.train_candidate(target, algorithm, problem_type, float(test_size))
# Converts feature-importance results into a dataframe for interactive display
importance = pd.DataFrame(result.get("feature_importance", []))
# Regenerates pipeline code using the algorithm and problem type actually used for training
code = ctx.generate_pipeline_code(target, result["algorithm"], result["problem_type"], float(test_size))
# Builds a compact diagnostics record describing the completed training run
diagnostics = {
"model_training": "complete",
"algorithm": result["algorithm"],
"problem_type": result["problem_type"],
"rows_used": result["rows_used"],
"fit_seconds": result["fit_seconds"],
"metrics": result["metrics"],
}
# Writes the fitted pipeline to a temporary Joblib artifact that the user can download
artifact_path = Path(tempfile.gettempdir()) / f"trained_pipeline_{uuid.uuid4().hex[:10]}.joblib"
joblib.dump(ctx.last_pipeline, artifact_path)
# Returns the formatted evaluation, raw metrics, importance data, code, artifact, and diagnostics
return _metrics_markdown(result), result["metrics"], importance, code, str(artifact_path), diagnostics
except Exception as exc:
raise gr.Error(f"Model training failed: {exc}") from exc
# Compares supported baseline algorithms for the selected supervised-learning task
def compare_models(target: str, problem_type: str, test_size: float, sid: str):
# Validates that the active session contains a dataset and selected prediction target
ctx = _get(sid)
if ctx is None:
raise gr.Error("Load a dataset first.")
if not target:
raise gr.Error("Select a target column.")
# Runs the deterministic baseline comparison and converts the results into display outputs
try:
comparison = ctx.compare_algorithms(target, problem_type, float(test_size))
frame = pd.DataFrame(comparison.get("results", []))
# Extracts the best reported baseline name to build the comparison summary text
best = comparison.get("best_algorithm")
note = (
f"### Baseline Comparison:\nBest Holdout Baseline: **{best}**\n\nUse this as a starting point, then add cross-validation and hyperparameter tuning."
if best
else "### Baseline Comparison:\nNo baseline completed successfully."
)
return note, frame, comparison
except Exception as exc:
raise gr.Error(f"Baseline Comparison Failed: {exc}") from exc
# Runs the tool-calling machine-learning agent with the current dataset and modeling controls
@spaces.GPU(duration=120)
def run_agent(
task: str,
target: str,
algorithm: str,
problem_type: str,
test_size: float,
temperature: float,
max_tokens: int,
sid: str,
):
# Retrieves and validates the active context and user inputs required by the agent
ctx = _get(sid)
if ctx is None:
raise gr.Error("Load a dataset first.")
if not target:
raise gr.Error("Select a target column in Model Lab first.")
if not (task or "").strip():
raise gr.Error("Enter a machine learning task.")
# Constructs the agent with the selected target, algorithm, task type, split, and generation settings
agent = MachineLearningAgent(
context=ctx,
target=target,
algorithm=algorithm,
problem_type=problem_type,
test_size=float(test_size),
temperature=float(temperature),
max_tokens=int(max_tokens),
)
# Executes the agent task and captures its response, generated code, and tool trace
answer, code, trace = agent.run(task.strip())
# Records model, provider, tool-use, and modeling selections for runtime diagnostics
diagnostics = {
"model": HF_MODEL_ID,
"provider": HF_PROVIDER,
"hf_token_configured": bool(HF_TOKEN),
"tool_calls": len(trace),
"tools_used": [item.get("tool") for item in trace],
"target": target,
"algorithm": algorithm,
"problem_type_setting": problem_type,
}
# Returns the agent response, generated pipeline code, execution trace, and diagnostics
return answer, code, trace, diagnostics
# Clears the active session context and resets all dataset, model, and agent interface outputs
def clear_session(sid: str):
# Removes the session context under the shared lock when a session identifier is present
if sid:
with LOCK:
SESSIONS.pop(sid, None)
# Returns the full set of cleared component values expected by the reset callback
return (
"",
_status(None, "Session cleared"),
pd.DataFrame(),
pd.DataFrame(),
gr.Dropdown(choices=[], value=None),
gr.Dropdown(value="Auto", choices=["Auto", "classification", "regression"]),
gr.Dropdown(choices=ALL_ALGORITHMS, value="Auto"),
"",
{},
pd.DataFrame(),
"",
pd.DataFrame(),
"",
None,
"",
[],
{},
)
# stylesheet
CSS = r'''
:root {
--bg-0: #04050a;
--bg-1: #070a12;
--panel: rgba(11, 15, 27, .84);
--panel-soft: rgba(255,255,255,.035);
--stroke: rgba(255,255,255,.09);
--stroke-strong: rgba(255,255,255,.15);
--text: #f7f9ff;
--muted: #98a5bc;
--cyan: #35dcff;
--blue: #4f7cff;
--violet: #9368ff;
--green: #45e6a3;
--amber: #ffc46b;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
html, body { background: var(--bg-0) !important; }
body { color: var(--text) !important; }
.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;
}
.gradio-container {
max-width: 1600px !important;
margin: 0 auto !important;
min-height: 100vh;
color: var(--text) !important;
background:
radial-gradient(circle at 13% -8%, rgba(53,220,255,.15), transparent 31%),
radial-gradient(circle at 87% 3%, rgba(147,104,255,.18), transparent 30%),
radial-gradient(circle at 52% 106%, rgba(69,230,163,.07), transparent 34%),
linear-gradient(145deg, #04050a 0%, #070a12 48%, #080b16 100%) !important;
position: relative;
}
.gradio-container::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
opacity: .16;
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: 46px 46px;
mask-image: linear-gradient(to bottom, black, transparent 88%);
}
.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(16,21,38,.97), rgba(7,10,18,.94) 62%, rgba(10,12,22,.97));
border-radius: 28px;
padding: 42px 44px 39px;
box-shadow: 0 32px 95px rgba(0,0,0,.48), 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(53,220,255,.42);
}
.hero::after {
content: "";
position: absolute;
width: 610px;
height: 610px;
right: -245px;
top: -300px;
border-radius: 50%;
background:
radial-gradient(circle at 48% 48%, rgba(53,220,255,.10), transparent 34%),
conic-gradient(from 0deg, rgba(53,220,255,.18), rgba(147,104,255,.18), rgba(69,230,163,.08), rgba(53,220,255,.18));
animation: heroDrift 14s ease-in-out infinite alternate;
}
@keyframes heroDrift {
from { transform: translate3d(0,0,0) scale(1); opacity: .7; }
to { transform: translate3d(-24px,16px,0) scale(1.04); opacity: .9; }
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 28px;
align-items: end;
position: relative;
z-index: 1;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 9px;
color: #c4f3ff;
font-size: 11px;
font-weight: 900;
letter-spacing: .17em;
text-transform: uppercase;
}
.eyebrow-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--green);
box-shadow: 0 0 18px rgba(69,230,163,.75);
}
.hero h1 {
margin: 12px 0 12px;
max-width: 990px;
color: #fff;
font-size: clamp(42px, 5vw, 68px);
line-height: 1.02;
letter-spacing: -.04em;
font-weight: 950;
}
.hero p {
max-width: 960px;
color: #aab5ca;
font-size: 15px;
line-height: 1.72;
margin: 0;
}
.hero-orbit {
width: 176px;
height: 176px;
border: 1px solid rgba(255,255,255,.09);
border-radius: 30px;
background: linear-gradient(145deg, rgba(53,220,255,.06), rgba(147,104,255,.05));
position: relative;
overflow: hidden;
box-shadow: inset 0 0 50px rgba(53,220,255,.035);
}
.hero-orbit::before,
.hero-orbit::after {
content: "";
position: absolute;
border-radius: 50%;
inset: 24px;
border: 1px solid rgba(255,255,255,.11);
animation: orbitSpin 12s linear infinite;
}
.hero-orbit::after {
inset: 56px;
border-color: rgba(53,220,255,.38);
animation-direction: reverse;
animation-duration: 7s;
box-shadow: 0 0 28px rgba(53,220,255,.13);
}
.hero-orbit-core {
position: absolute;
width: 17px;
height: 17px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 5px;
background: linear-gradient(135deg, var(--cyan), var(--violet));
box-shadow: 0 0 30px rgba(53,220,255,.62);
animation: corePulse 2.6s ease-in-out infinite;
}
@keyframes orbitSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes corePulse { 50% { transform: translate(-50%,-50%) scale(1.17); } }
.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);
transition: transform .18s ease, border-color .18s ease, background .18s ease;
}
.badge:hover { transform: translateY(-2px); border-color: rgba(53,220,255,.25); background: rgba(53,220,255,.055); }
.badge strong { color: #eefbff; margin-right: 4px; }
.badge-accent { border-color: rgba(53,220,255,.22); background: rgba(53,220,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;
overflow: hidden;
min-height: 79px;
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));
transition: transform .2s ease, border-color .2s ease, background .2s ease, box-shadow .2s ease;
}
.workflow-step:hover { transform: translateY(-3px); border-color: rgba(255,255,255,.15); box-shadow: 0 12px 30px rgba(0,0,0,.2); }
.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(53,220,255,.58); }
.workflow-step:nth-child(2) { border-top-color: rgba(69,230,163,.58); }
.workflow-step:nth-child(3) { border-top-color: rgba(255,196,107,.58); }
.workflow-step:nth-child(4) { border-top-color: rgba(147,104,255,.64); }
.app-panel {
background: rgba(9,13,24,.79) !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);
overflow: hidden;
backdrop-filter: blur(18px);
padding-top: 0 !important;
}
.side-column { gap: 18px !important; }
.sidebar-card {
background: linear-gradient(145deg, rgba(17,21,37,.9), rgba(9,13,23,.9));
border: 1px solid var(--stroke);
border-radius: 18px;
padding: 22px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.025), 0 16px 45px rgba(0,0,0,.20);
transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease;
}
.sidebar-card + .sidebar-card { margin-top: 20px; }
.sidebar-card:hover { transform: translateY(-2px); border-color: rgba(53,220,255,.15); box-shadow: 0 18px 46px rgba(0,0,0,.24); }
.sidebar-card h3 { margin: 0 0 18px; color: #fff; font-size: 13px; }
.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: #c5f5ff; background: rgba(53,220,255,.07); border: 1px solid rgba(53,220,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: 68%; overflow-wrap: anywhere; line-height: 1.55; }
.status-card {
border: 1px solid rgba(53,220,255,.16);
background: linear-gradient(145deg, rgba(53,220,255,.065), rgba(147,104,255,.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(69,230,163,.08), 0 0 18px rgba(69,230,163,.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: 18px; 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: 22px; 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;
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: 180px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: 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-size: 14px !important;
font-weight: 700 !important;
transition: background .18s ease, border-color .18s ease, color .18s ease, transform .18s ease !important;
}
#main-tabs button[role="tab"]::before,
#main-tabs button[role="tab"]::after { display: none !important; content: none !important; }
#main-tabs button[role="tab"]:hover { color: #fff !important; background: rgba(255,255,255,.045) !important; transform: translateY(-1px); }
#main-tabs button[role="tab"][aria-selected="true"] {
color: #fff !important;
background: linear-gradient(135deg, rgba(53,220,255,.13), rgba(147,104,255,.11)) !important;
border-color: rgba(53,220,255,.31) !important;
box-shadow: 0 8px 22px rgba(0,0,0,.2), 0 0 18px rgba(53,220,255,.055) !important;
}
.tab-body { padding: 34px 28px 32px !important; }
#load-button, #train-button, #compare-button, #code-button, #agent-button {
font-weight: 900 !important;
color: #041017 !important;
background: linear-gradient(135deg, #74e9ff 0%, #35dcff 42%, #927fff 100%) !important;
border: 0 !important;
border-radius: 12px !important;
box-shadow: 0 10px 28px rgba(53,220,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, #train-button:hover, #compare-button:hover, #code-button:hover, #agent-button:hover {
transform: translateY(-1px);
filter: brightness(1.07);
box-shadow: 0 14px 34px rgba(53,220,255,.21), 0 0 28px rgba(147,104,255,.08) !important;
}
#agent-button { min-height: 50px !important; font-size: 14px !important; }
button.secondary, button:not(.primary) { border-radius: 11px !important; }
.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; }
.gradio-container [data-testid="file-upload"] {
border: 1px dashed rgba(53,220,255,.24) !important;
background: linear-gradient(145deg, rgba(53,220,255,.035), rgba(147,104,255,.025)) !important;
border-radius: 16px !important;
transition: border-color .22s ease, background .22s ease, box-shadow .22s ease;
}
.gradio-container [data-testid="file-upload"]:hover {
border-color: rgba(53,220,255,.43) !important;
background: linear-gradient(145deg, rgba(53,220,255,.055), rgba(147,104,255,.035)) !important;
box-shadow: inset 0 0 34px rgba(53,220,255,.025), 0 10px 26px rgba(0,0,0,.14);
}
#dataset-preview, #dataset-schema, #feature-importance, #leaderboard {
margin-top: 18px !important;
overflow: visible !important;
}
#dataset-preview [data-testid="block-info"],
#dataset-schema [data-testid="block-info"],
#feature-importance [data-testid="block-info"],
#leaderboard [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;
}
.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; }
.gradio-container tbody tr:hover td { background: rgba(53,220,255,.035) !important; color: #d7e2f5 !important; }
.result-tabs [role="tablist"] { gap: 6px !important; margin-top: 8px !important; }
.result-tabs button[role="tab"] { border-radius: 9px !important; font-weight: 800 !important; }
.info-note {
margin-top: 15px;
padding: 12px 14px;
border: 1px solid rgba(255,196,107,.18);
border-left: 3px solid var(--amber);
border-radius: 12px;
background: rgba(255,196,107,.045);
color: #b9b3a5;
font-size: 11px;
line-height: 1.55;
}
.metric-strip {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 8px 0 20px;
}
.metric-chip {
border: 1px solid rgba(255,255,255,.08);
background: rgba(255,255,255,.025);
border-radius: 14px;
padding: 13px 14px;
}
.metric-chip span { display: block; color: #718099; font-size: 9px; font-weight: 900; letter-spacing: .09em; text-transform: uppercase; }
.metric-chip strong { display: block; color: #edf4ff; font-size: 12px; margin-top: 5px; }
.footer-note { color: #5f6b80; font-size: 10px; text-align: center; margin-top: 18px; letter-spacing: .05em; }
@media (prefers-reduced-motion: reduce) {
.hero::after, .hero-orbit::before, .hero-orbit::after, .hero-orbit-core { animation: 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)); }
.metric-strip { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 800px) {
.main-shell { padding: 14px 10px 28px; }
.hero { padding: 28px 21px 26px; border-radius: 22px; }
.hero h1 { font-size: 42px; }
.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: 170px !important; padding: 14px 20px !important; }
}
@media (max-width: 560px) {
.hero h1 { font-size: 36px; }
.workflow-strip, .metric-strip { grid-template-columns: 1fr; }
}
/* =========================================================
SPECTRAL OBSIDIAN UI
Presentation-only redesign. No ML / agent logic changed.
========================================================= */
:root {
--obsidian: #050408;
--obsidian-2: #0a0710;
--surface: rgba(18, 13, 24, .84);
--surface-strong: rgba(24, 16, 31, .95);
--surface-soft: rgba(255, 255, 255, .035);
--line: rgba(255, 255, 255, .09);
--line-bright: rgba(255, 255, 255, .16);
--rose: #ff5f87;
--rose-soft: rgba(255, 95, 135, .13);
--gold: #ffd37a;
--gold-soft: rgba(255, 211, 122, .12);
--mint: #61f3c2;
--mint-soft: rgba(97, 243, 194, .10);
--lilac: #a98bff;
--lilac-soft: rgba(169, 139, 255, .12);
--ice: #dffcff;
--text-strong: #fffafc;
--text-body: #c7becd;
--text-dim: #807487;
}
/* Full canvas */
html, body {
background: var(--obsidian) !important;
}
.gradio-container {
max-width: 1640px !important;
color: var(--text-strong) !important;
background:
radial-gradient(circle at 10% -8%, rgba(255,95,135,.20), transparent 30%),
radial-gradient(circle at 93% 2%, rgba(169,139,255,.17), transparent 29%),
radial-gradient(circle at 74% 102%, rgba(97,243,194,.08), transparent 32%),
radial-gradient(circle at 12% 92%, rgba(255,211,122,.055), transparent 28%),
linear-gradient(145deg, #050408 0%, #08060d 48%, #0a0710 100%) !important;
}
.gradio-container::before {
opacity: .22 !important;
background-image:
linear-gradient(rgba(255,255,255,.018) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.018) 1px, transparent 1px),
radial-gradient(circle, rgba(255,255,255,.04) 1px, transparent 1px) !important;
background-size: 54px 54px, 54px 54px, 18px 18px !important;
mask-image: linear-gradient(to bottom, black 0%, rgba(0,0,0,.7) 58%, transparent 95%) !important;
}
.main-shell {
padding: 34px 30px 50px !important;
}
/* Hero */
.hero {
border-radius: 34px !important;
padding: 50px 50px 46px !important;
margin-bottom: 18px !important;
border: 1px solid rgba(255,255,255,.11) !important;
background:
linear-gradient(125deg, rgba(31,17,35,.97), rgba(10,8,15,.96) 56%, rgba(15,10,23,.97)) !important;
box-shadow:
0 38px 120px rgba(0,0,0,.56),
inset 0 1px 0 rgba(255,255,255,.055),
inset 0 0 90px rgba(255,95,135,.025) !important;
}
.hero::before {
height: 2px !important;
background:
linear-gradient(90deg,
transparent 2%,
var(--rose) 21%,
var(--gold) 44%,
var(--mint) 66%,
var(--lilac) 84%,
transparent 98%) !important;
box-shadow:
0 0 24px rgba(255,95,135,.28),
0 0 44px rgba(169,139,255,.18) !important;
}
.hero::after {
width: 720px !important;
height: 720px !important;
right: -285px !important;
top: -365px !important;
background:
radial-gradient(circle at 50% 50%, rgba(255,95,135,.10), transparent 34%),
conic-gradient(
from 15deg,
rgba(255,95,135,.23),
rgba(255,211,122,.10),
rgba(97,243,194,.10),
rgba(169,139,255,.22),
rgba(255,95,135,.23)
) !important;
filter: blur(1px);
}
.hero-grid {
gap: 38px !important;
align-items: center !important;
}
.eyebrow {
color: #ffd9e4 !important;
font-size: 10px !important;
letter-spacing: .20em !important;
background: rgba(255,95,135,.07);
border: 1px solid rgba(255,95,135,.16);
border-radius: 999px;
padding: 8px 11px;
}
.eyebrow-dot {
background: var(--mint) !important;
box-shadow:
0 0 0 4px rgba(97,243,194,.08),
0 0 18px rgba(97,243,194,.72) !important;
}
.hero h1 {
margin: 18px 0 14px !important;
max-width: 1040px !important;
font-size: clamp(46px, 5.3vw, 76px) !important;
line-height: .98 !important;
letter-spacing: -.052em !important;
font-weight: 950 !important;
color: #fffafa !important;
text-shadow: 0 8px 38px rgba(0,0,0,.32);
}
.hero p {
max-width: 1020px !important;
color: #c3b8c8 !important;
font-size: 15.5px !important;
line-height: 1.78 !important;
}
/* Hero signal rail */
.hero-signal-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 9px;
max-width: 800px;
margin-top: 22px;
}
.hero-signal {
position: relative;
overflow: hidden;
border: 1px solid rgba(255,255,255,.075);
border-radius: 14px;
padding: 12px 13px;
background: rgba(255,255,255,.027);
backdrop-filter: blur(14px);
}
.hero-signal::before {
content: "";
position: absolute;
width: 58px;
height: 58px;
right: -20px;
top: -27px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,95,135,.18), transparent 70%);
}
.hero-signal:nth-child(2)::before {
background: radial-gradient(circle, rgba(97,243,194,.16), transparent 70%);
}
.hero-signal:nth-child(3)::before {
background: radial-gradient(circle, rgba(169,139,255,.18), transparent 70%);
}
.hero-signal span {
display: block;
color: #776d7d;
font-size: 8.5px;
font-weight: 900;
letter-spacing: .13em;
text-transform: uppercase;
}
.hero-signal strong {
display: block;
color: #fff9fb;
font-size: 11px;
margin-top: 5px;
}
/* Hero visual */
.hero-orbit {
width: 194px !important;
height: 194px !important;
border-radius: 38px !important;
border: 1px solid rgba(255,255,255,.10) !important;
background:
radial-gradient(circle at 50% 50%, rgba(255,95,135,.055), transparent 45%),
linear-gradient(145deg, rgba(255,95,135,.055), rgba(169,139,255,.055)) !important;
box-shadow:
inset 0 0 58px rgba(169,139,255,.035),
0 18px 60px rgba(0,0,0,.25) !important;
}
.hero-orbit::before {
inset: 27px !important;
border-color: rgba(255,211,122,.20) !important;
box-shadow: inset 0 0 18px rgba(255,211,122,.025);
}
.hero-orbit::after {
inset: 61px !important;
border-color: rgba(97,243,194,.36) !important;
box-shadow:
0 0 28px rgba(97,243,194,.10),
inset 0 0 18px rgba(97,243,194,.05) !important;
}
.hero-orbit-core {
width: 20px !important;
height: 20px !important;
border-radius: 7px !important;
background: linear-gradient(135deg, var(--rose), var(--gold) 48%, var(--mint)) !important;
box-shadow:
0 0 22px rgba(255,95,135,.42),
0 0 44px rgba(97,243,194,.20) !important;
}
/* Badges */
.badges {
gap: 8px !important;
margin-top: 18px !important;
}
.badge {
color: #bcb2c2 !important;
background: rgba(255,255,255,.028) !important;
border-color: rgba(255,255,255,.085) !important;
padding: 8px 12px !important;
}
.badge strong {
color: #fff8fa !important;
}
.badge-accent {
color: #ffdbe5 !important;
border-color: rgba(255,95,135,.22) !important;
background: rgba(255,95,135,.065) !important;
}
.badge:hover {
border-color: rgba(255,95,135,.26) !important;
background: rgba(255,95,135,.055) !important;
}
/* Workflow */
.workflow-strip {
gap: 12px !important;
margin-bottom: 20px !important;
}
.workflow-step {
min-height: 100px !important;
padding: 17px 17px 15px !important;
border-radius: 21px !important;
border: 1px solid rgba(255,255,255,.075) !important;
background:
linear-gradient(145deg, rgba(255,255,255,.037), rgba(255,255,255,.014)) !important;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.028),
0 12px 32px rgba(0,0,0,.12);
}
.workflow-step::before {
content: "";
position: absolute;
left: 16px;
right: 16px;
top: 0;
height: 2px;
border-radius: 999px;
background: var(--rose);
opacity: .8;
box-shadow: 0 0 18px rgba(255,95,135,.28);
}
.workflow-step:nth-child(2)::before {
background: var(--gold);
box-shadow: 0 0 18px rgba(255,211,122,.20);
}
.workflow-step:nth-child(3)::before {
background: var(--mint);
box-shadow: 0 0 18px rgba(97,243,194,.20);
}
.workflow-step:nth-child(4)::before {
background: var(--lilac);
box-shadow: 0 0 18px rgba(169,139,255,.25);
}
.workflow-step:hover {
transform: translateY(-5px) !important;
border-color: rgba(255,255,255,.13) !important;
background:
linear-gradient(145deg, rgba(255,255,255,.052), rgba(255,255,255,.021)) !important;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.04),
0 20px 45px rgba(0,0,0,.25) !important;
}
.workflow-step span {
color: #756b7c !important;
font-size: 9px !important;
letter-spacing: .15em !important;
}
.workflow-step strong {
color: #fff9fb !important;
font-size: 14px !important;
margin-top: 8px !important;
}
.workflow-step small {
color: #8e8495 !important;
line-height: 1.4;
margin-top: 5px !important;
}
/* Main content panel */
.app-panel {
position: relative;
border-radius: 29px !important;
border: 1px solid rgba(255,255,255,.085) !important;
background:
linear-gradient(160deg, rgba(17,12,23,.91), rgba(9,7,13,.88)) !important;
box-shadow:
0 30px 82px rgba(0,0,0,.40),
inset 0 1px 0 rgba(255,255,255,.035) !important;
backdrop-filter: blur(22px);
}
.app-panel::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
box-shadow:
inset 0 0 70px rgba(255,95,135,.012),
inset 0 0 100px rgba(169,139,255,.01);
}
/* Navigation tabs */
#main-tabs [role="tablist"] {
padding: 18px !important;
gap: 9px !important;
min-height: 80px !important;
border-bottom: 1px solid rgba(255,255,255,.075) !important;
background:
linear-gradient(180deg, rgba(255,255,255,.028), rgba(255,255,255,.008)) !important;
}
#main-tabs button[role="tab"] {
min-width: 182px !important;
min-height: 48px !important;
padding: 13px 20px !important;
color: #9e93a5 !important;
border: 1px solid rgba(255,255,255,.065) !important;
border-radius: 15px !important;
background: rgba(255,255,255,.018) !important;
font-size: 13px !important;
font-weight: 800 !important;
letter-spacing: -.012em !important;
}
#main-tabs button[role="tab"]:hover {
color: #fff9fb !important;
border-color: rgba(255,95,135,.20) !important;
background: rgba(255,95,135,.04) !important;
}
#main-tabs button[role="tab"][aria-selected="true"] {
color: #fff !important;
border-color: rgba(255,95,135,.30) !important;
background:
linear-gradient(135deg, rgba(255,95,135,.13), rgba(169,139,255,.085)) !important;
box-shadow:
inset 0 0 0 1px rgba(255,255,255,.018),
0 10px 28px rgba(0,0,0,.20),
0 0 22px rgba(255,95,135,.05) !important;
}
.tab-body {
padding: 38px 32px 36px !important;
}
/* Section headers */
.section-head {
position: relative;
margin-bottom: 24px !important;
padding: 0 0 18px 0 !important;
border-bottom: 1px solid rgba(255,255,255,.06);
}
.section-head::after {
content: "";
position: absolute;
left: 0;
bottom: -1px;
width: 84px;
height: 2px;
border-radius: 999px;
background: linear-gradient(90deg, var(--rose), var(--gold));
box-shadow: 0 0 16px rgba(255,95,135,.18);
}
.section-head .section-kicker {
display: inline-flex;
align-items: center;
gap: 7px;
width: fit-content;
color: #ffd8e3 !important;
font-size: 9px !important;
font-weight: 900 !important;
letter-spacing: .14em !important;
padding: 6px 9px;
border-radius: 999px;
border: 1px solid rgba(255,95,135,.14);
background: rgba(255,95,135,.055);
}
.section-head h2 {
margin: 11px 0 6px !important;
color: #fff9fb !important;
font-size: 25px !important;
line-height: 1.18;
letter-spacing: -.035em !important;
}
.section-head p {
max-width: 920px;
color: #93889b !important;
font-size: 12.5px !important;
line-height: 1.66 !important;
}
/* Sidebar */
.side-column {
gap: 16px !important;
}
.sidebar-card {
position: relative;
overflow: hidden;
border-radius: 22px !important;
padding: 23px !important;
border: 1px solid rgba(255,255,255,.075) !important;
background:
linear-gradient(150deg, rgba(24,16,30,.92), rgba(11,8,15,.90)) !important;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.03),
0 18px 48px rgba(0,0,0,.20) !important;
}
.sidebar-card::after {
content: "";
position: absolute;
width: 150px;
height: 150px;
right: -95px;
top: -95px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,95,135,.09), transparent 70%);
pointer-events: none;
}
.sidebar-card + .sidebar-card {
margin-top: 16px !important;
}
.sidebar-card:hover {
border-color: rgba(255,95,135,.16) !important;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.035),
0 22px 55px rgba(0,0,0,.27) !important;
}
.sidebar-card h3 {
color: #fff9fb !important;
font-size: 12px !important;
font-weight: 900 !important;
letter-spacing: .02em;
}
.sidebar-card p,
.sidebar-card li {
color: #9b90a2 !important;
}
.sidebar-card li::marker {
color: var(--rose);
font-weight: 800;
}
.sidebar-card code {
color: #ffe2ea !important;
border-color: rgba(255,95,135,.13) !important;
background: rgba(255,95,135,.06) !important;
}
.runtime-line {
padding: 17px 0 !important;
border-bottom-color: rgba(255,255,255,.055) !important;
}
.runtime-line span {
color: #756a7c !important;
}
.runtime-line strong {
color: #f8eef9 !important;
}
/* Workspace status */
.status-card {
border-radius: 22px !important;
min-height: 105px !important;
padding: 18px !important;
border: 1px solid rgba(97,243,194,.13) !important;
background:
linear-gradient(145deg, rgba(97,243,194,.055), rgba(255,95,135,.035)) !important;
box-shadow:
inset 0 1px 0 rgba(255,255,255,.025),
0 18px 44px rgba(0,0,0,.18) !important;
}
.status-card.active .status-dot {
background: var(--mint) !important;
box-shadow:
0 0 0 5px rgba(97,243,194,.07),
0 0 20px rgba(97,243,194,.56) !important;
}
.status-kicker {
color: #7c7183 !important;
}
.status-card strong {
color: #fff9fb !important;
}
.status-details {
color: #978b9e !important;
}
/* Buttons */
#load-button,
#train-button,
#compare-button,
#code-button,
#agent-button {
min-height: 47px !important;
border: 0 !important;
border-radius: 14px !important;
color: #16070d !important;
font-weight: 950 !important;
letter-spacing: -.012em !important;
background:
linear-gradient(135deg, #ff8ba8 0%, #ff648b 42%, #ffd37a 100%) !important;
box-shadow:
0 12px 30px rgba(255,95,135,.16),
inset 0 1px 0 rgba(255,255,255,.48) !important;
}
#train-button:hover,
#compare-button:hover,
#code-button:hover,
#load-button:hover,
#agent-button:hover {
transform: translateY(-2px) !important;
filter: brightness(1.06) saturate(1.04) !important;
box-shadow:
0 16px 38px rgba(255,95,135,.22),
0 0 28px rgba(255,211,122,.06) !important;
}
/* Center and evenly space the three primary ML Modeling actions only. */
.model-action-row {
width: 100% !important;
max-width: 880px !important;
margin: 24px auto 0 !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
gap: clamp(28px, 4vw, 48px) !important;
}
.model-action-row > *,
.model-action-row #train-button,
.model-action-row #compare-button,
.model-action-row #code-button {
flex: 0 1 260px !important;
width: 260px !important;
max-width: 260px !important;
min-width: 0 !important;
margin: 0 !important;
}
@media (max-width: 820px) {
.model-action-row {
gap: 16px !important;
}
.model-action-row > *,
.model-action-row #train-button,
.model-action-row #compare-button,
.model-action-row #code-button {
flex: 1 1 0 !important;
width: auto !important;
max-width: none !important;
min-width: 0 !important;
}
}
@media (max-width: 620px) {
.model-action-row {
flex-direction: column !important;
gap: 12px !important;
max-width: 420px !important;
}
.model-action-row > *,
.model-action-row #train-button,
.model-action-row #compare-button,
.model-action-row #code-button {
width: 100% !important;
max-width: 420px !important;
flex: 0 0 auto !important;
}
}
/* Secondary buttons */
.gradio-container button.secondary,
.gradio-container button:not(.primary) {
border-radius: 13px !important;
color: #c9bfce !important;
border: 1px solid rgba(255,255,255,.075) !important;
background: rgba(255,255,255,.025) !important;
}
.gradio-container button.secondary:hover,
.gradio-container button:not(.primary):hover {
color: #fff !important;
border-color: rgba(255,95,135,.17) !important;
background: rgba(255,95,135,.035) !important;
}
/* Inputs */
.gradio-container textarea,
.gradio-container input,
.gradio-container select {
color: #fff7fa !important;
}
.gradio-container .block {
border-radius: 17px !important;
}
.gradio-container [data-testid="block-info"] {
color: #a99daf !important;
}
.gradio-container [data-testid="file-upload"] {
min-height: 120px;
border: 1px dashed rgba(255,95,135,.24) !important;
border-radius: 19px !important;
background:
linear-gradient(145deg, rgba(255,95,135,.035), rgba(169,139,255,.025)) !important;
transition:
transform .22s ease,
border-color .22s ease,
box-shadow .22s ease,
background .22s ease !important;
}
.gradio-container [data-testid="file-upload"]:hover {
transform: translateY(-2px);
border-color: rgba(255,95,135,.42) !important;
background:
linear-gradient(145deg, rgba(255,95,135,.055), rgba(169,139,255,.038)) !important;
box-shadow:
inset 0 0 35px rgba(255,95,135,.02),
0 13px 34px rgba(0,0,0,.16) !important;
}
/* Metric strip */
.metric-strip {
gap: 11px !important;
margin: 12px 0 22px !important;
}
.metric-chip {
position: relative;
overflow: hidden;
min-height: 79px;
border-radius: 18px !important;
padding: 15px !important;
border: 1px solid rgba(255,255,255,.07) !important;
background:
linear-gradient(145deg, rgba(255,255,255,.032), rgba(255,255,255,.013)) !important;
}
.metric-chip::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 100%;
background: var(--rose);
opacity: .75;
}
.metric-chip:nth-child(2)::before { background: var(--gold); }
.metric-chip:nth-child(3)::before { background: var(--mint); }
.metric-chip:nth-child(4)::before { background: var(--lilac); }
.metric-chip span {
color: #766c7c !important;
font-size: 8.5px !important;
}
.metric-chip strong {
color: #fff8fa !important;
font-size: 11.5px !important;
line-height: 1.45;
}
/* Tables */
.gradio-container table {
border-collapse: separate !important;
border-spacing: 0 !important;
}
.gradio-container th {
background: #17101d !important;
color: #eadfea !important;
border-color: rgba(255,255,255,.065) !important;
font-size: 10.5px !important;
font-weight: 850 !important;
}
.gradio-container td {
background: rgba(11,8,15,.88) !important;
color: #b5aabb !important;
border-color: rgba(255,255,255,.045) !important;
font-size: 10.5px !important;
}
.gradio-container tbody tr:hover td {
background: rgba(255,95,135,.035) !important;
color: #eee4ef !important;
}
/* Dataset labels */
#dataset-preview,
#dataset-schema,
#feature-importance,
#leaderboard {
margin-top: 18px !important;
overflow: visible !important;
}
#dataset-preview [data-testid="block-info"],
#dataset-schema [data-testid="block-info"],
#feature-importance [data-testid="block-info"],
#leaderboard [data-testid="block-info"] {
display: block !important;
position: relative !important;
inset: auto !important;
transform: none !important;
margin: 0 0 9px !important;
padding: 0 2px !important;
width: 100% !important;
color: #fff8fa !important;
font-size: 12px !important;
font-weight: 800 !important;
line-height: 1.35 !important;
white-space: normal !important;
overflow: visible !important;
clip: auto !important;
}
/* Code / JSON outputs */
.gradio-container pre,
.gradio-container code {
font-feature-settings: "liga" 1, "calt" 1;
}
/* Accordions */
.gradio-container details {
border-radius: 18px !important;
border-color: rgba(255,255,255,.07) !important;
background: rgba(255,255,255,.018) !important;
}
.gradio-container details:hover {
border-color: rgba(255,95,135,.15) !important;
box-shadow: 0 12px 32px rgba(0,0,0,.14) !important;
}
/* Info note */
.info-note {
border-radius: 15px !important;
border: 1px solid rgba(255,211,122,.15) !important;
border-left: 3px solid var(--gold) !important;
background: rgba(255,211,122,.035) !important;
color: #b9adb7 !important;
padding: 14px 15px !important;
}
.info-note strong {
color: #ffe5aa;
}
.info-note code {
color: #fff0c8;
background: rgba(255,211,122,.055);
border: 1px solid rgba(255,211,122,.10);
padding: 2px 5px;
border-radius: 6px;
}
/* Footer */
.footer-note {
color: #655b6b !important;
font-size: 9px !important;
letter-spacing: .14em !important;
margin-top: 23px !important;
}
/* =========================================================
GRADIO FLOATING-UI / OUTPUT CLIPPING FIXES
- Dropdown menus use floating positioning. backdrop-filter on
an ancestor creates a new containing block and can shift the
popup by the panel's page offset.
- The original app-panel rule also used overflow:hidden, which
can clip dropdowns and first-line Markdown headings.
========================================================= */
.app-panel {
overflow: visible !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
#main-tabs,
#main-tabs [role="tabpanel"],
#main-tabs .tab-body,
.model-controls-row,
.model-control {
overflow: visible !important;
}
.model-controls-row {
position: relative !important;
z-index: 40 !important;
}
.model-control {
position: relative !important;
z-index: 41 !important;
}
.model-control:focus-within {
z-index: 1000 !important;
}
/* Keep Gradio/Floating-UI dropdown option panels above the rest of the model tab. */
.gradio-container [role="listbox"] {
z-index: 100000 !important;
}
/* Second-pass stacking/layout fixes from browser screenshots.
1) Gradio 6 renders Dropdown options as a fixed <ul class="options">.
Give that actual popup the top layer so it paints over the slider label.
2) Keep the primary 01/02/03 tab rail inside the rounded app panel. */
ul.options[role="listbox"] {
z-index: 2147483000 !important;
}
#target-column-dropdown,
#problem-type-dropdown,
#algorithm-dropdown {
position: relative !important;
z-index: 100 !important;
}
#target-column-dropdown:focus-within,
#problem-type-dropdown:focus-within,
#algorithm-dropdown:focus-within {
z-index: 2147482000 !important;
}
#holdout-test-size {
position: relative !important;
z-index: 0 !important;
}
/* Earlier styling explicitly set padding-top: 0 on .app-panel.
Restoring an inner top gutter prevents the tab buttons from protruding
above the panel's rounded top border. */
.app-panel {
padding-top: 14px !important;
}
#main-tabs {
position: relative !important;
top: 0 !important;
margin-top: 0 !important;
}
#main-tabs [role="tablist"] {
position: relative !important;
top: 0 !important;
transform: none !important;
margin-top: 0 !important;
}
/* Result Markdown: prevent the first heading (Model evaluation,
Baseline Comparison, or an agent heading such as ML assessment)
from being clipped by the component's top edge. */
.result-markdown {
overflow: visible !important;
padding-top: 10px !important;
padding-bottom: 4px !important;
min-height: 0 !important;
}
.result-markdown > div,
.result-markdown .prose,
.result-markdown [data-testid="markdown"] {
overflow: visible !important;
}
.result-markdown h1,
.result-markdown h2,
.result-markdown h3,
.result-markdown h4,
.result-markdown h5,
.result-markdown h6 {
margin-top: .35rem !important;
margin-bottom: .65rem !important;
padding-top: .08em !important;
line-height: 1.35 !important;
overflow: visible !important;
}
/* Responsive refinement */
@media (max-width: 1100px) {
.hero-signal-grid {
grid-template-columns: 1fr 1fr 1fr;
}
}
@media (max-width: 800px) {
.main-shell {
padding: 14px 10px 30px !important;
}
.hero {
padding: 31px 23px 28px !important;
border-radius: 25px !important;
}
.hero h1 {
font-size: 43px !important;
}
.hero-signal-grid {
grid-template-columns: 1fr !important;
}
.tab-body {
padding: 24px 17px 26px !important;
}
}
@media (max-width: 560px) {
.hero h1 {
font-size: 37px !important;
}
#main-tabs button[role="tab"] {
min-width: 165px !important;
}
}
/* =========================================================
PRIMARY TAB SPACING FIX
Keep 01 / 02 / 03 evenly distributed across the top rail
on desktop while preserving the existing mobile behavior.
========================================================= */
@media (min-width: 801px) {
#main-tabs [role="tablist"] {
display: grid !important;
grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
gap: 18px !important;
width: 100% !important;
align-items: center !important;
}
#main-tabs button[role="tab"] {
width: 100% !important;
min-width: 0 !important;
max-width: none !important;
margin: 0 !important;
}
}
/* =========================================================
DATASET ACTION BUTTON STANDARDIZATION
Keep all three dataset actions identical in width/height.
========================================================= */
#dataset-actions-row {
align-items: stretch !important;
}
#dataset-actions-row #load-button,
#dataset-actions-row #example-button,
#dataset-actions-row #clear-button {
flex: 1 1 0 !important;
width: 100% !important;
min-width: 0 !important;
height: 44px !important;
min-height: 44px !important;
max-height: 44px !important;
margin: 0 !important;
}
#dataset-actions-row #load-button button,
#dataset-actions-row #example-button button,
#dataset-actions-row #clear-button button {
width: 100% !important;
height: 44px !important;
min-height: 44px !important;
max-height: 44px !important;
white-space: nowrap !important;
}
/* Allow the row to adapt cleanly on smaller screens without uneven controls. */
@media (max-width: 800px) {
#dataset-actions-row {
flex-wrap: wrap !important;
}
#dataset-actions-row #load-button,
#dataset-actions-row #example-button,
#dataset-actions-row #clear-button {
flex: 1 1 100% !important;
}
}
/* =========================================================
LAPTOP RESPONSIVE LAYOUT FIX
Keeps long labels inside their controls and keeps the three
ML Modeling action buttons aligned on one row on laptops.
========================================================= */
/* Dataset action row: equal controls, but allow long button text to wrap cleanly. */
#dataset-actions-row {
display: flex !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
gap: 14px !important;
}
#dataset-actions-row > * {
flex: 1 1 0 !important;
min-width: 0 !important;
}
#dataset-actions-row #load-button,
#dataset-actions-row #example-button,
#dataset-actions-row #clear-button,
#dataset-actions-row #load-button button,
#dataset-actions-row #example-button button,
#dataset-actions-row #clear-button button {
width: 100% !important;
min-width: 0 !important;
height: auto !important;
max-height: none !important;
min-height: 56px !important;
}
#dataset-actions-row #load-button button,
#dataset-actions-row #example-button button,
#dataset-actions-row #clear-button button {
white-space: normal !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
line-height: 1.18 !important;
padding: 10px 14px !important;
text-align: center !important;
}
/* Primary tabs: give the long ML Modeling label more room and allow wrapping.
(Column ratios follow content, not position: 02 is now the short "ML Agent"
label and 03 is the long "ML Modeling (Optional Fallback Models)" label.) */
@media (min-width: 801px) {
#main-tabs [role="tablist"] {
grid-template-columns:
minmax(0, 0.88fr)
minmax(0, 0.88fr)
minmax(0, 1.24fr) !important;
gap: 14px !important;
}
#main-tabs button[role="tab"] {
min-width: 0 !important;
min-height: 50px !important;
padding: 9px 16px !important;
white-space: normal !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
line-height: 1.2 !important;
text-align: center !important;
}
}
/* ML Modeling actions: kept centered as a group on laptop/desktop (matches
the .model-action-row rule above; this block only refines sizing so the
row never falls back to a left-aligned full-width stretch on wide screens). */
.model-action-row {
width: 100% !important;
max-width: 880px !important;
margin: 24px auto 0 !important;
display: flex !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: center !important;
gap: 16px !important;
}
.model-action-row > * {
flex: 1 1 0 !important;
width: auto !important;
max-width: 280px !important;
min-width: 0 !important;
margin: 0 !important;
}
.model-action-row #train-button,
.model-action-row #compare-button,
.model-action-row #code-button,
.model-action-row #train-button button,
.model-action-row #compare-button button,
.model-action-row #code-button button {
width: 100% !important;
min-width: 0 !important;
max-width: 280px !important;
height: auto !important;
min-height: 52px !important;
margin: 0 auto !important;
}
.model-action-row #train-button button,
.model-action-row #compare-button button,
.model-action-row #code-button button {
white-space: normal !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
line-height: 1.16 !important;
padding: 11px 12px !important;
text-align: center !important;
}
/* Laptop widths: slightly tighten text and spacing without wrapping rows. */
@media (min-width: 801px) and (max-width: 1450px) {
#dataset-actions-row {
gap: 10px !important;
}
#dataset-actions-row button {
font-size: 12.5px !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
#main-tabs [role="tablist"] {
gap: 10px !important;
padding-left: 12px !important;
padding-right: 12px !important;
}
#main-tabs button[role="tab"] {
font-size: 12.5px !important;
padding-left: 9px !important;
padding-right: 9px !important;
}
.model-action-row {
gap: 12px !important;
}
.model-action-row #train-button button,
.model-action-row #compare-button button,
.model-action-row #code-button button {
font-size: 13.5px !important;
padding-left: 9px !important;
padding-right: 9px !important;
}
}
/* Tablet/mobile: stack action controls so they remain readable. */
@media (max-width: 800px) {
#dataset-actions-row,
.model-action-row {
flex-direction: column !important;
flex-wrap: nowrap !important;
gap: 12px !important;
}
#dataset-actions-row > *,
.model-action-row > * {
width: 100% !important;
max-width: none !important;
flex: 0 0 auto !important;
}
#main-tabs button[role="tab"] {
white-space: normal !important;
line-height: 1.15 !important;
}
}
'''
# Defines document-level metadata injected into the application page head
HEAD = '''
<meta name="theme-color" content="#050408">
<meta name="description" content="Agentic machine learning workspace for dataset profiling, model selection, baseline evaluation, and production-oriented pipeline generation.">
'''
# Builds and wires the complete Gradio application interface and event callbacks
def build_app():
# Derives human-readable runtime labels for the configured inference model and provider
runtime = f"HF Inference 路 {HF_MODEL_ID}" if HF_TOKEN else HF_MODEL_ID
provider = HF_PROVIDER or "auto"
# Defines the hero markup displayed at the top of the application
hero = f'''
<div class="hero">
<div class="hero-grid">
<div>
<div class="eyebrow"><span class="eyebrow-dot"></span> Agentic Machine Learning 路 (Traditional ML) Supervised Learning 路 Model Selection + Pipeline Generation</div>
<h1>Agentic Machine Learning Engineer</h1>
<p>Load a real dataset, select the prediction target variable (in the data) and ML algorithm, train traditional machine learning baselines, compare models (pick the best one), and use a Qwen3-Coder tool-calling agent to turn findings into production-ready scikit-learn pipeline code.</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>ML</strong> scikit-learn pipelines</span>
<span class="badge"><strong>Data</strong> Hugging Face + uploads</span>
</div>
<div class="hero-signal-grid">
<div class="hero-signal"><span>Data plane</span><strong>HF sample + file uploads</strong></div>
<div class="hero-signal"><span>Training plane</span><strong>Local scikit-learn</strong></div>
<div class="hero-signal"><span>Agent plane</span><strong>Qwen3-Coder tool orchestration</strong></div>
</div>
</div>
<div class="hero-orbit" aria-hidden="true"><div class="hero-orbit-core"></div></div>
</div>
</div>
'''
# Defines the four-step workflow markup shown beneath the hero area
workflow = '''
<div class="workflow-strip">
<div class="workflow-step"><span>01 路 LOAD</span><strong>dataset</strong><small>HF sample 路 CSV 路 Parquet 路 JSON 路 Excel</small></div>
<div class="workflow-step"><span>02 路 DATA PRE-PROCESSING</span><strong>target + task</strong><small>classification 路 regression 路 leakage awareness</small></div>
<div class="workflow-step"><span>03 路 TRAIN</span><strong>model lab</strong><small>baseline metrics 路 comparison 路 importance</small></div>
<div class="workflow-step"><span>04 路 AGENT</span><strong>generate pipeline</strong><small>Qwen tool loop 路 code 路 trace</small></div>
</div>
'''
# Creates the root Gradio Blocks application using the configured app title
with gr.Blocks(title=APP_TITLE) as demo:
# Stores the browser session identifier in Gradio state for reuse across callbacks
sid = gr.State("")
# Creates the main application shell that contains the header, workflow, tabs, and sidebar
with gr.Column(elem_classes=["main-shell"]):
gr.HTML(hero)
gr.HTML(workflow)
# Splits the primary workspace into a main content area and a supporting sidebar
with gr.Row(equal_height=False):
with gr.Column(scale=9, min_width=600, elem_classes=["app-panel"]):
with gr.Tabs(selected="dataset", elem_id="main-tabs"):
# Builds the dataset tab for uploads, profiling, preview, and schema inspection
with gr.Tab("01. Dataset", id="dataset"):
with gr.Column(elem_classes=["tab-body"]):
gr.HTML('''<div class="section-head"><div class="section-kicker">Dataset Upload</div><h2>Load Your Machine Learning Dataset</h2><p>Public Adult Census Income dataset is preloaded via Hugging Face. Replace the template dataset with your own dataset.</p></div>''')
# Configures the dataset upload control with the supported file extensions
upload = gr.File(
label="Dataset file",
file_types=[".csv", ".parquet", ".json", ".jsonl", ".xlsx", ".xls"],
)
# Groups the dataset load, example-load, and workspace-reset actions together.
# Equal scale/min-width values keep all three actions exactly the same width.
with gr.Row(equal_height=True, elem_id="dataset-actions-row"):
load_btn = gr.Button(
"Load + Profile Dataset",
variant="primary",
elem_id="load-button",
scale=1,
min_width=0,
)
example_btn = gr.Button(
"Load HF Dataset (Example Dataset)",
elem_id="example-button",
scale=1,
min_width=0,
)
clear_btn = gr.Button(
"Clear Workspace",
elem_id="clear-button",
scale=1,
min_width=0,
)
# Creates read-only dataframe components for the dataset preview and inferred schema
preview = gr.Dataframe(label="Dataset Preview:", interactive=False, wrap=True, elem_id="dataset-preview")
schema_table = gr.Dataframe(label="Dataset Schema:", interactive=False, wrap=True, elem_id="dataset-schema")
gr.HTML(f'''<div class="info-note"><strong>HF sample:</strong> <code>{EXAMPLE_DATASET_REPO}</code> 路 target <code>{EXAMPLE_DATASET_TARGET}</code>. Public Space: do not upload confidential, regulated, proprietary, production customer, or PII data.</div>''')
# Builds the agent tab for natural-language ML tasks, generated code, and tool-trace inspection
with gr.Tab("02. ML Agent", id="agent"):
with gr.Column(elem_classes=["tab-body"]):
gr.HTML('''<div class="section-head"><div class="section-kicker">Machine Learning Agent</div><h2>Turn Dataset Findings Into an ML Strategy and Pipeline Code (sci-kit learn)</h2><p>Agent can inspect the dataset, validate the target framing, train the selected model, compare baselines, and generate scikit-learn pipelines.</p></div>''')
# Configures the agent task prompt field with a comprehensive default machine-learning request
task = gr.Textbox(
label="Machine learning task",
value="Audit this dataset for supervised machine learning, confirm the target framing, train a baseline model, compare appropriate algorithms, explain the evaluation metrics, flag leakage or data risks, and generate a production-oriented scikit-learn pipeline.",
placeholder="Example: Predict customer churn, compare baseline classifiers, prioritize recall, and generate a deployable preprocessing + model pipeline.",
lines=6,
max_lines=12,
)
# Creates the agent execution button and the visible answer output.
# agent_code / trace still receive run_agent's return values (diagnostics
# depends on trace's length) but are not displayed anywhere in the UI.
agent_btn = gr.Button("Run Machine Learning Agent", variant="primary", elem_id="agent-button")
agent_answer = gr.Markdown("", elem_id="agent-answer", elem_classes=["result-markdown"])
agent_code = gr.State()
trace = gr.State()
# Builds the modeling tab for target selection, training, comparison, and pipeline generation
with gr.Tab("03. ML Modeling (Optional Fallback Models)", id="model-lab"):
with gr.Column(elem_classes=["tab-body"]):
gr.HTML('''<div class="section-head"><div class="section-kicker">Supervised Learning</div><h2>Choose a Target Variable (Feature), Algorithm, and Evaluation Setup</h2><p>Build a preprocessing + model pipeline, evaluate it on a holdout split, compare baselines, and evaluate generated pipelines (scikit-learn pipelines).</p></div>''')
# Groups the target, problem-type, and algorithm selectors on a single modeling row.
# The CSS hooks keep Gradio's floating dropdown menus anchored to these controls.
with gr.Row(elem_classes=["model-controls-row"]):
target = gr.Dropdown(
label="Target column",
choices=[],
value=None,
interactive=True,
elem_id="target-column-dropdown",
elem_classes=["model-control"],
)
problem_type = gr.Dropdown(
label="Problem type",
choices=["Auto", "classification", "regression"],
value="Auto",
interactive=True,
elem_id="problem-type-dropdown",
elem_classes=["model-control"],
)
algorithm = gr.Dropdown(
label="Algorithm",
choices=ALL_ALGORITHMS,
value="Auto",
interactive=True,
elem_id="algorithm-dropdown",
elem_classes=["model-control"],
)
# Configures the holdout-size control used by training, comparison, and code generation
test_size = gr.Slider(0.1, 0.4, value=0.2, step=0.05, label="Holdout test size", elem_id="holdout-test-size")
gr.HTML('''
<div class="metric-strip">
<div class="metric-chip"><span>Preprocessing</span><strong>Impute + scale + one-hot</strong></div>
<div class="metric-chip"><span>Split</span><strong>Train / holdout</strong></div>
<div class="metric-chip"><span>Classification</span><strong>Accuracy 路 F1 路 ROC-AUC</strong></div>
<div class="metric-chip"><span>Regression</span><strong>MAE 路 RMSE 路 R虏</strong></div>
</div>
''')
# Groups the primary model-training, baseline-comparison, and pipeline-generation actions
with gr.Row(elem_classes=["model-action-row"]):
train_btn = gr.Button("Train Selected Model", variant="primary", elem_id="train-button")
compare_btn = gr.Button("Compare Baselines", variant="primary", elem_id="compare-button")
code_btn = gr.Button("Generate Pipeline Code", variant="primary", elem_id="code-button")
# Creates the components that display model summaries, metrics, importance, comparisons, code, and artifacts
model_summary = gr.Markdown("", elem_id="model-summary", elem_classes=["result-markdown"])
model_metrics = gr.JSON(label="Evaluation metrics")
feature_importance = gr.Dataframe(label="Feature importance / coefficient magnitude:", interactive=False, wrap=True, elem_id="feature-importance")
compare_summary = gr.Markdown("", elem_id="compare-summary", elem_classes=["result-markdown"])
leaderboard = gr.Dataframe(label="Baseline Leaderboard:", interactive=False, wrap=True, elem_id="leaderboard")
pipeline_code = gr.Code(label="Selected Algorithm Pipeline", language="python", lines=30)
model_artifact = gr.File(label="Trained Pipeline Artifact (.joblib)", interactive=False)
# Builds the sidebar containing live workspace status, runtime information, controls, and diagnostics
with gr.Column(scale=4, min_width=335, 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</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>Training</span><strong>scikit-learn 路 local CPU</strong></div>
</div>
<div class="sidebar-card">
<h3>Agent Capabilities</h3>
<ol>
<li>Check data shapes, types, nulls, cardinality, and examples</li>
<li>Pick classification or regression algorithms from the selected target feature</li>
<li>Train and evaluate baseline models</li>
<li>Compare multiple algorithms using holdout metrics</li>
<li>Generate reusable preprocessing + model pipeline code</li>
</ol>
</div>
<div class="sidebar-card">
<h3>Execution Model</h3>
<p>Model training runs locally with scikit-learn. Qwen3-Coder is called through Hugging Face Inference Providers for tool selection and final ML synthesis.</p>
</div>
''')
# Exposes generation controls for temperature and maximum model response length
with gr.Accordion("Agent controls", open=True):
temperature = gr.Slider(0.0, 0.8, value=0.15, step=0.05, label="Temperature")
max_tokens = gr.Slider(600, 3200, value=DEFAULT_MAX_TOKENS, step=100, label="Maximum model tokens")
# Adds a collapsible diagnostics panel for structured runtime and workflow metadata
with gr.Accordion("Diagnostics", open=False):
diagnostics = gr.JSON(value={}, label="Runtime diagnostics")
gr.HTML("<div class='footer-note'>QWEN3-CODER 路 HUGGING FACE INFERENCE PROVIDERS 路 SCIKIT-LEARN 路 PANDAS 路 GRADIO</div>")
# Collects the shared outputs updated whenever a dataset is loaded or preloaded
dataset_outputs = [sid, status, preview, schema_table, target, problem_type, algorithm, diagnostics]
# Preload the public Hugging Face example dataset when a browser session opens
# Registers automatic loading of the public example dataset when a browser session starts
demo.load(load_example_dataset, inputs=[sid], outputs=dataset_outputs)
# Dataset controls.
# Connects the primary dataset-load action to the upload-processing callback
load_btn.click(load_dataset, [upload, sid], dataset_outputs)
# Connects the example-dataset button to the Hugging Face loading callback
example_btn.click(load_example_dataset, [sid], dataset_outputs)
# Keep the algorithm picker and generated pipeline aligned to the selected target
# Synchronizes model choices and generated pipeline code whenever the target selection changes
target.change(sync_model_controls, [target, problem_type, algorithm, sid], [algorithm, pipeline_code, diagnostics])
# Synchronizes model choices and generated pipeline code whenever the problem type changes
problem_type.change(sync_model_controls, [target, problem_type, algorithm, sid], [algorithm, pipeline_code, diagnostics])
# Regenerates pipeline code whenever the selected algorithm changes
algorithm.change(generate_pipeline_code, [target, algorithm, problem_type, test_size, sid], [pipeline_code])
# Regenerates pipeline code whenever the holdout split size changes
test_size.change(generate_pipeline_code, [target, algorithm, problem_type, test_size, sid], [pipeline_code])
# Connects the standalone pipeline-generation button to the code-generation callback
code_btn.click(generate_pipeline_code, [target, algorithm, problem_type, test_size, sid], [pipeline_code])
# Connects the model-training button to all evaluation, artifact, and diagnostics outputs
train_btn.click(
train_model,
[target, algorithm, problem_type, test_size, sid],
[model_summary, model_metrics, feature_importance, pipeline_code, model_artifact, diagnostics],
)
# Connects the baseline-comparison button to the summary, leaderboard, and diagnostics outputs
compare_btn.click(
compare_models,
[target, problem_type, test_size, sid],
[compare_summary, leaderboard, diagnostics],
)
# Agent actions
# Groups the component values passed into and returned from the ML agent
agent_inputs = [task, target, algorithm, problem_type, test_size, temperature, max_tokens, sid]
agent_outputs = [agent_answer, agent_code, trace, diagnostics]
# Runs the agent when the dedicated action button is clicked
agent_btn.click(run_agent, agent_inputs, agent_outputs)
# Runs the same agent workflow when the task textbox is submitted directly
task.submit(run_agent, agent_inputs, agent_outputs)
# Connects workspace clearing to every component that must be reset
clear_btn.click(
clear_session,
[sid],
[
sid,
status,
preview,
schema_table,
target,
problem_type,
algorithm,
model_summary,
model_metrics,
feature_importance,
compare_summary,
leaderboard,
pipeline_code,
model_artifact,
agent_answer,
trace,
diagnostics,
],
)
# returns the fully configured Gradio Blocks application to the caller
return demo
# launches Gradio app only when this module is executed as the main program
if __name__ == "__main__":
# builds the application before configuring its queued web-server launch
app = build_app()
# starts the queued Gradio server using environment-driven networking and the defined UI assets
app.queue(default_concurrency_limit=2).launch(
server_name="0.0.0.0",
server_port=int(os.getenv("PORT", "7860")),
show_error=True,
theme=gr.themes.Base(),
css=CSS,
head=HEAD,
)