import gradio as gr import pandas as pd import os from datetime import date from huggingface_hub import HfApi CSV_PATH = "observations.csv" REPO_ID = "Flame-Forged/coherence-gap-explorer" HF_TOKEN = os.environ.get("HF_TOKEN") custom_css = """ body, .gradio-container { background-color: #0d0d1a !important; color: #e0e0f0 !important; font-family: 'Georgia', serif !important; } .gradio-container h1 { font-size: 2.2em !important; font-weight: 900 !important; background: linear-gradient(90deg, #a855f7, #f59e0b) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; padding-bottom: 6px !important; } .gradio-container p, .gradio-container label { color: #c4b5fd !important; } .tab-nav { background: #1a1a2e !important; border-bottom: 2px solid #7c3aed !important; } .tab-nav button { color: #a0aec0 !important; font-weight: 600 !important; font-size: 1em !important; border-radius: 6px 6px 0 0 !important; padding: 10px 24px !important; } .tab-nav button.selected { background: #7c3aed !important; color: #ffffff !important; border-bottom: none !important; } input[type="text"], textarea, select, .gr-box { background-color: #1a1a2e !important; color: #e0e0f0 !important; border: 1px solid #4c1d95 !important; border-radius: 6px !important; } input[type="text"]:focus, textarea:focus { border-color: #a855f7 !important; outline: none !important; box-shadow: 0 0 0 2px rgba(168, 85, 247, 0.3) !important; } button.primary { background: linear-gradient(90deg, #7c3aed, #a855f7) !important; color: white !important; border: none !important; font-weight: 700 !important; font-size: 1em !important; padding: 10px 28px !important; border-radius: 8px !important; cursor: pointer !important; transition: opacity 0.2s !important; } button.primary:hover { opacity: 0.85 !important; } table { background-color: #12122a !important; border-collapse: collapse !important; width: 100% !important; } th { background-color: #4c1d95 !important; color: #f59e0b !important; font-weight: 700 !important; text-transform: uppercase !important; font-size: 0.78em !important; letter-spacing: 0.08em !important; padding: 10px 14px !important; border-bottom: 2px solid #7c3aed !important; } td { background-color: #0d0d1a !important; color: #e0e0f0 !important; padding: 6px 14px !important; border-bottom: 1px solid #1e1e3a !important; font-size: 0.9em !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; vertical-align: middle !important; } tr:hover td { background-color: #1a1a2e !important; } .result-count p { color: #f59e0b !important; font-style: italic !important; font-size: 0.9em !important; } .table-wrap { max-height: 500px !important; overflow-y: auto !important; } ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: #0d0d1a; } ::-webkit-scrollbar-thumb { background: #7c3aed; border-radius: 3px; } """ def load_data(): return pd.read_csv(CSV_PATH, on_bad_lines='skip', engine='python') def get_choices(df, column): if column not in df.columns: return ["All"] vals = sorted(df[column].dropna().unique().tolist()) return ["All"] + [str(v) for v in vals] def filter_data(platform, behavior_category, confidence, search_term): df = load_data() if platform != "All": df = df[df["platform"].astype(str) == platform] if behavior_category != "All": df = df[df["behavior_category"].astype(str) == behavior_category] if confidence != "All": df = df[df["interpretive_confidence"].astype(str) == confidence] if search_term.strip(): mask = df.apply( lambda row: row.astype(str).str.contains( search_term.strip(), case=False, na=False ).any(), axis=1 ) df = df[mask] return df def update(platform, behavior_category, confidence, search_term): df = filter_data(platform, behavior_category, confidence, search_term) count = f"*Showing {len(df)} of {len(load_data())} observations*" return df, count def build_stats(): df = load_data() total = len(df) def make_bar(count, total, width=20): filled = int(round(count / total * width)) if total > 0 else 0 return "█" * filled + "░" * (width - filled) def section(title, series): lines = [f"### {title}\n"] for val, count in series.sort_values(ascending=False).items(): bar = make_bar(count, total) pct = round(count / total * 100) lines.append(f"`{bar}` **{val}** — {count} ({pct}%)") return "\n\n".join(lines) platforms = section("By Platform", df["platform"].value_counts()) categories = section("By Behavior Category", df["behavior_category"].value_counts()) confidence = section("By Interpretive Confidence", df["interpretive_confidence"].value_counts()) repro = section("By Reproducibility Status", df["reproducibility_status"].value_counts()) evidence_pct = round(df["raw_prompt_available"].astype(str).str.lower().eq("yes").mean() * 100) response_pct = round(df["raw_response_available"].astype(str).str.lower().eq("yes").mean() * 100) return f"""## Dataset Overview | Metric | Value | |---|---| | Total observations | **{total}** | | Platforms covered | **{df['platform'].nunique()}** | | Behavior categories | **{df['behavior_category'].nunique()}** | | Raw prompt available | **{evidence_pct}%** of observations | | Raw response available | **{response_pct}%** of observations | --- {platforms} --- {categories} --- {confidence} --- {repro} """ def submit_observation( f_date, platform, model_version, memory_enabled, window_type, prompt_class, behavior_category, description, evidence, screenshot_ids, raw_prompt, raw_response, confidence, alternative_explanations, reproducibility_status, coder_notes ): new_row = { "date": f_date, "platform": platform, "model_version": model_version, "memory_enabled": memory_enabled, "window_type": window_type, "prompt_class": prompt_class, "behavior_category": behavior_category, "description": description, "evidence": evidence, "screenshot_ids": screenshot_ids, "raw_prompt_available": raw_prompt, "raw_response_available": raw_response, "interpretive_confidence": confidence, "alternative_explanations": alternative_explanations, "reproducibility_status": reproducibility_status, "coder_notes": coder_notes } df = load_data() new_df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True) new_df.to_csv(CSV_PATH, index=False) if HF_TOKEN: api = HfApi(token=HF_TOKEN) api.upload_file( path_or_fileobj=CSV_PATH, path_in_repo="observations.csv", repo_id=REPO_ID, repo_type="space" ) return "✅ Observation submitted and saved permanently!" else: return "⚠️ Saved locally but HF_TOKEN not set — won't persist after restart." initial_df = load_data() with gr.Blocks(title="Coherence Gap Explorer") as demo: gr.Markdown("# Coherence Gap Explorer") gr.Markdown( "Interactive browser for the observational dataset from the Coherence Gap paper." ) with gr.Tab("Browse & Filter"): with gr.Row(): platform_dd = gr.Dropdown( choices=get_choices(initial_df, "platform"), value="All", label="Platform" ) category_dd = gr.Dropdown( choices=get_choices(initial_df, "behavior_category"), value="All", label="Behavior Category" ) confidence_dd = gr.Dropdown( choices=get_choices(initial_df, "interpretive_confidence"), value="All", label="Interpretive Confidence" ) search_box = gr.Textbox( label="Search", placeholder="Search across all columns...", lines=1 ) result_count = gr.Markdown( f"*Showing {len(initial_df)} of {len(initial_df)} observations*", elem_classes=["result-count"] ) table = gr.Dataframe( value=initial_df, label="Observations", interactive=False, wrap=False, elem_classes=["table-wrap"] ) inputs = [platform_dd, category_dd, confidence_dd, search_box] for component in inputs: component.change(fn=update, inputs=inputs, outputs=[table, result_count]) with gr.Tab("Stats"): refresh_btn = gr.Button("Refresh Stats", variant="primary") stats_display = gr.Markdown(value=build_stats()) refresh_btn.click(fn=build_stats, inputs=[], outputs=stats_display) with gr.Tab("Submit Observation"): gr.Markdown("### Add a New Observation") with gr.Row(): f_date = gr.Textbox(label="Date", value=str(date.today()), placeholder="YYYY-MM-DD") f_platform = gr.Textbox(label="Platform") f_model_version = gr.Textbox(label="Model Version") with gr.Row(): f_memory = gr.Dropdown(choices=["Yes", "No", "Unknown"], label="Memory Enabled") f_window = gr.Textbox(label="Window Type") f_prompt_class = gr.Textbox(label="Prompt Class") f_category = gr.Textbox(label="Behavior Category") with gr.Row(): f_raw_prompt = gr.Dropdown(choices=["Yes", "No", "Unknown"], label="Raw Prompt Available") f_raw_response = gr.Dropdown(choices=["Yes", "No", "Unknown"], label="Raw Response Available") f_confidence = gr.Dropdown(choices=["Low", "Medium", "High"], label="Interpretive Confidence") f_repro = gr.Textbox(label="Reproducibility Status") f_description = gr.Textbox(label="Description", lines=3) f_evidence = gr.Textbox(label="Evidence", lines=3) f_alternative = gr.Textbox(label="Alternative Explanations", lines=2) f_screenshots = gr.Textbox(label="Screenshot IDs") f_notes = gr.Textbox(label="Coder Notes", lines=2) submit_btn = gr.Button("Submit Observation", variant="primary") status = gr.Markdown("") submit_btn.click( fn=submit_observation, inputs=[ f_date, f_platform, f_model_version, f_memory, f_window, f_prompt_class, f_category, f_description, f_evidence, f_screenshots, f_raw_prompt, f_raw_response, f_confidence, f_alternative, f_repro, f_notes ], outputs=status ) demo.launch(css=custom_css)