+ {/* Filter tabs */}
+
+ {categories.map((cat) => (
+ setFilter(cat)}
+ style={{
+ padding: "6px 14px",
+ borderRadius: "var(--radius-sm)",
+ border:
+ filter === cat
+ ? "1px solid var(--accent-primary)"
+ : "1px solid var(--border-color)",
+ background:
+ filter === cat ? "var(--accent-glow)" : "var(--bg-secondary)",
+ color:
+ filter === cat
+ ? "var(--accent-primary)"
+ : "var(--text-secondary)",
+ fontSize: 12,
+ fontWeight: 600,
+ cursor: "pointer",
+ textTransform: "capitalize",
+ transition: "all 0.2s",
+ }}
+ >
+ {cat}
+
+ ))}
+
+
+ {/* Results */}
+
+ {filtered.slice(0, 50).map((r, idx) => {
+ const key = `${r.probe_id}-${r.variant_group}-${idx}`;
+ const isExpanded = expandedId === key;
+
+ return (
+
setExpandedId(isExpanded ? null : key)}
+ >
+
+
+
+
+ {r.category}
+
+
+ {r.variant_group}
+
+
+ {r.probe_id}
+
+
+
+ {r.prompt}
+
+
+
+
+
+
+ Length
+
+
+ {r.metrics.response_length}
+
+
+
+
+ Sentiment
+
+
+ {r.metrics.sentiment.toFixed(2)}
+
+
+
+
+ Refusal
+
+
+ {r.metrics.refusal_detected ? "🚫" : "✓"}
+
+
+
+ ▾
+
+
+
+
+ {isExpanded && (
+
+
+ Response
+
+ {r.response}
+
+ )}
+
+ );
+ })}
+
+
+ {filtered.length > 50 && (
+
+ Showing 50 of {filtered.length} results
+
+ )}
+
+ );
+}
diff --git a/frontend/lib/.gitkeep b/frontend/lib/.gitkeep
deleted file mode 100644
index fdffa2a..0000000
--- a/frontend/lib/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-# placeholder
diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts
new file mode 100644
index 0000000..07d7903
--- /dev/null
+++ b/frontend/lib/api.ts
@@ -0,0 +1,174 @@
+/**
+ * BiasProbe — API Client
+ * Typed fetch wrappers for all backend endpoints.
+ */
+
+import { getIdToken } from "./firebase";
+
+const API_BASE = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8000";
+
+// ── Types ─────────────────────────────────────────────────────────────────────
+
+export interface AuditConfig {
+ probe_count: number;
+ intersectional: boolean;
+ semantic_similarity: boolean;
+ webhook_url?: string;
+}
+
+export interface AuditCreateRequest {
+ target_endpoint: string;
+ target_system_prompt?: string;
+ probe_template_ids: string[];
+ probe_mode: "static" | "dynamic";
+ config: AuditConfig;
+}
+
+export interface AuditProgress {
+ completed: number;
+ total: number;
+}
+
+export interface AuditResponse {
+ audit_id: string;
+ status: string;
+ progress: AuditProgress;
+ probe_mode: string;
+ probe_template_ids: string[];
+ target_endpoint: string;
+ created_at?: string;
+ started_at?: string;
+ completed_at?: string;
+}
+
+export interface AuditStatusResponse {
+ audit_id: string;
+ status: string;
+ progress: AuditProgress;
+ started_at?: string;
+ completed_at?: string;
+}
+
+export interface ProbeResultMetrics {
+ response_length: number;
+ sentiment: number;
+ refusal_detected: boolean;
+}
+
+export interface ProbeResult {
+ probe_id: string;
+ category: string;
+ variant_group: string;
+ prompt: string;
+ response: string;
+ metrics: ProbeResultMetrics;
+}
+
+export interface BiasScore {
+ category: string;
+ score_type: string;
+ score: number;
+ p_value: number;
+ significance_level: string;
+ details: Record