Spaces:
Running on Zero
Running on Zero
| # 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. | |
| 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 | |
| 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, | |
| ) |