divyanshudhruv commited on
Commit
c7438c8
·
verified ·
1 Parent(s): d0fb8fd

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. space_ui/__init__.py +1 -0
  2. space_ui/css.py +176 -0
  3. space_ui/schemas.py +116 -0
  4. space_ui/texts.py +62 -0
space_ui/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # space_ui: theme, CSS, texts and demo schemas for the OEV Space demo.
space_ui/css.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gradio theme + CSS for the OEV Space demo. One place for the look.
2
+ import gradio as gr
3
+
4
+ theme = gr.themes.Soft(
5
+ primary_hue="orange",
6
+ neutral_hue="slate",
7
+ font=[gr.themes.GoogleFont("IBM Plex Sans"), "ui-sans-serif", "system-ui", "sans-serif"],
8
+ font_mono=[gr.themes.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace"],
9
+ ).set(
10
+ body_background_fill="#171412",
11
+ body_background_fill_dark="#171412",
12
+ body_text_color="#f5eee8",
13
+ body_text_color_dark="#f5eee8",
14
+ body_text_color_subdued="#b9aa9f",
15
+ body_text_color_subdued_dark="#b9aa9f",
16
+ block_background_fill="#211d1a",
17
+ block_background_fill_dark="#211d1a",
18
+ block_border_color="#3a2f28",
19
+ block_border_color_dark="#3a2f28",
20
+ border_color_primary="#3a2f28",
21
+ border_color_primary_dark="#3a2f28",
22
+ color_accent="#e8b48c",
23
+ color_accent_soft="#5a4032",
24
+ color_accent_soft_dark="#5a4032",
25
+ border_color_accent="#e8b48c",
26
+ border_color_accent_dark="#e8b48c",
27
+ input_background_fill="#1b1816",
28
+ input_background_fill_dark="#1b1816",
29
+ input_border_color="#4a3b32",
30
+ input_border_color_dark="#4a3b32",
31
+ input_border_color_focus="#e8b48c",
32
+ input_border_color_focus_dark="#e8b48c",
33
+ button_primary_background_fill="#e8b48c",
34
+ button_primary_background_fill_dark="#e8b48c",
35
+ button_primary_background_fill_hover="#dca47a",
36
+ button_primary_background_fill_hover_dark="#dca47a",
37
+ button_primary_border_color="#e8b48c",
38
+ button_primary_border_color_dark="#e8b48c",
39
+ button_primary_border_color_hover="#dca47a",
40
+ button_primary_border_color_hover_dark="#dca47a",
41
+ button_primary_text_color="#2b1d16",
42
+ button_primary_text_color_dark="#2b1d16",
43
+ button_primary_text_color_hover="#2b1d16",
44
+ button_primary_text_color_hover_dark="#2b1d16",
45
+ button_secondary_background_fill="#2a2420",
46
+ button_secondary_background_fill_dark="#2a2420",
47
+ button_secondary_background_fill_hover="#352c26",
48
+ button_secondary_background_fill_hover_dark="#352c26",
49
+ button_secondary_border_color="#4a3b32",
50
+ button_secondary_border_color_dark="#4a3b32",
51
+ button_secondary_border_color_hover="#5a4639",
52
+ button_secondary_border_color_hover_dark="#5a4639",
53
+ button_secondary_text_color="#f5eee8",
54
+ button_secondary_text_color_dark="#f5eee8",
55
+ button_secondary_text_color_hover="#ffffff",
56
+ button_secondary_text_color_hover_dark="#ffffff",
57
+ block_border_width="1px",
58
+ block_radius="0px",
59
+ button_large_radius="0px",
60
+ button_small_radius="0px",
61
+ input_radius="0px",
62
+ )
63
+
64
+ CSS = """
65
+ /* --- page frame: centered column --- */
66
+ .gradio-container { width: 1020px !important; max-width: calc(100vw - 32px) !important; min-width: 0 !important; box-sizing: border-box !important; margin: 0 auto; }
67
+ footer { visibility: hidden; }
68
+ :root, .gradio-container { --primary-pastel: #e8b48c; --color-accent: var(--primary-pastel); --border-color-accent: var(--primary-pastel); }
69
+ .gradio-container > main,
70
+ .gradio-container .main,
71
+ .tab-container,
72
+ .tab-container > div,
73
+ .tab-container .tab-panel,
74
+ .tab-container .row { width: 100% !important; min-width: 0 !important; box-sizing: border-box !important; }
75
+ .tab-container .column { min-width: 0 !important; box-sizing: border-box !important; }
76
+
77
+ .gradio-container { font-size: 14px; line-height: 1.5; }
78
+ .gradio-container p, .gradio-container textarea, .gradio-container input { font-size: 14px; line-height: 1.5; }
79
+ .gradio-container h1 { font-size: 24px; line-height: 1.25; font-weight: 600; }
80
+ .gradio-container h2 { font-size: 18px; line-height: 1.25; font-weight: 600; }
81
+ .gradio-container h3 { font-size: 16px; line-height: 1.25; font-weight: 600; }
82
+ .gradio-container label, .gradio-container .form-label, .gradio-container .block-label { font-size: 12px; line-height: 1.4; font-weight: 500; }
83
+ .gradio-container button, .tab-nav button { font-size: 13px; line-height: 1.25; }
84
+
85
+ /* --- everything square: override any gradio rounding --- */
86
+ .gradio-container * { border-radius: 0 !important; }
87
+
88
+ /* --- header --- */
89
+ #header { padding: 12px 0 16px; border-bottom: 1px solid var(--border-color-primary);
90
+ margin-bottom: 16px; }
91
+ #header h1 { margin: 0 0 8px; letter-spacing: -0.01em; }
92
+ #header .prose p { color: var(--body-text-color-subdued); margin: 0 0 4px; }
93
+ #header .metrics { font-family: var(--font-mono); font-size: 12px;
94
+ color: var(--body-text-color-subdued);
95
+ letter-spacing: 0.02em; }
96
+ #header .metrics b { color: var(--body-text-color); font-weight: 600; }
97
+
98
+ /* --- preset buttons: equal width, one row --- */
99
+ #presets { gap: 8px; margin-bottom: 16px; }
100
+ #presets button { flex: 1 1 0; min-width: 0; width: auto; height: 32px !important;
101
+ min-height: 32px !important; padding: 0 12px !important; }
102
+
103
+ /* --- accordion header matches the preset button height --- */
104
+ #qs-help { margin: 12px 0; }
105
+ #qs-help button { min-height: 32px !important; padding: 4px 12px !important; }
106
+
107
+ /* --- decide: full width, own row --- */
108
+ #decide-btn { width: 100%; min-height: 40px; margin-top: 16px; }
109
+ .tab-container .row { gap: 16px; }
110
+
111
+ /* --- code editors and viewers render mono --- */
112
+ .cm-editor, .cm-content, .cm-line, textarea.code,
113
+ #state-box textarea, #qs-box textarea {
114
+ font-family: var(--font-mono) !important;
115
+ font-size: 12px !important;
116
+ }
117
+
118
+ /* --- probability bars rendered from the payload markdown --- */
119
+ .bars .qname { display: block; font-size: 12px; text-transform: uppercase;
120
+ letter-spacing: 0.1em; color: var(--body-text-color-subdued);
121
+ margin: 16px 0 8px; }
122
+ .bars .row { display: flex; align-items: center; gap: 8px; margin: 4px 0; }
123
+ .bars .row .name { flex: 0 0 38%; overflow: hidden; text-overflow: ellipsis;
124
+ white-space: nowrap; font-size: 12px; }
125
+ .bars .row .track { flex: 1 1 auto; height: 8px;
126
+ background: var(--block-background-fill);
127
+ border: 1px solid var(--border-color-primary); position: relative; }
128
+ .bars .row .fill { position: absolute; inset: 0 auto 0 0; height: 100%;
129
+ background: var(--color-accent); opacity: 0.75; }
130
+ .bars .row.win .fill { background: var(--color-accent); opacity: 1; }
131
+ .bars .row .val { flex: 0 0 52px; text-align: right;
132
+ font-family: var(--font-mono); font-size: 12px;
133
+ color: var(--body-text-color-subdued); }
134
+ .bars .row.win .val { color: var(--body-text-color); font-weight: 600; }
135
+
136
+ /* --- status line --- */
137
+ #statusline { font-family: var(--font-mono); font-size: 12px;
138
+ color: var(--body-text-color-subdued); text-align: right;
139
+ min-height: 1.4em; margin-top: 8px; }
140
+
141
+ /* --- error: red left rule --- */
142
+ #errorbox { margin: 12px 0; padding: 12px;
143
+ border-left: 3px solid var(--color-danger) !important; }
144
+
145
+ /* --- focus rings --- */
146
+ #state-box textarea:focus, #qs-box textarea:focus, #qs-box input:focus,
147
+ .cm-editor.cm-focused, .cm-content:focus {
148
+ outline: 2px solid var(--border-color-accent) !important;
149
+ outline-offset: -1px;
150
+ }
151
+
152
+ /* --- tab nav --- */
153
+ .tab-nav { display: flex; width: 100%; gap: 8px; margin-bottom: 16px; }
154
+ .tab-nav button { flex: 1 1 0; min-width: 0; min-height: 32px; padding: 6px 12px; font-size: 13px; letter-spacing: 0.04em; }
155
+ .tab-nav button:hover,
156
+ .tab-nav button:focus-visible,
157
+ .tab-nav button[aria-selected="true"] {
158
+ color: var(--color-accent) !important;
159
+ -webkit-text-fill-color: var(--color-accent) !important;
160
+ background: var(--color-accent-soft) !important;
161
+ border-color: var(--color-accent) !important;
162
+ box-shadow: inset 0 -2px 0 var(--color-accent) !important;
163
+ }
164
+ .tab-nav button:hover *,
165
+ .tab-nav button:focus-visible *,
166
+ .tab-nav button[aria-selected="true"] * {
167
+ color: var(--color-accent) !important;
168
+ -webkit-text-fill-color: var(--color-accent) !important;
169
+ }
170
+
171
+ /* --- responsive: presets wrap on narrow screens --- */
172
+ @media (max-width: 640px) {
173
+ #presets { flex-wrap: wrap; }
174
+ #presets button { flex: 1 1 45%; width: auto; }
175
+ }
176
+ """
space_ui/schemas.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Demo states and question schemas, verbatim from the typed-decisions
2
+ # training data. Editing these is safe: they are plain JSON payloads.
3
+ import json
4
+
5
+ AGENT_STATE = ('{"agent": {"autonomy": "checkpointed", "model": "internal-agent-v4"}, '
6
+ '"constraints": ["Do not touch customer data outside the named accounts"], '
7
+ '"task": "Delete personal data for the accounts in the erasure queue.", '
8
+ '"trace_summary": {"constraint_violations": 0, "duration_s": 128.5, '
9
+ '"irreversible_actions": 0, "steps": 7, "tool_errors": 1}}')
10
+
11
+ AGENT_QS = {
12
+ "action": {"type": "choice", "options": ["continue", "human_review", "observe", "stop"],
13
+ "instructions": "action"},
14
+ "needs_review": {"type": "noul"},
15
+ "outcome": {"type": "choice", "options": ["failure", "harmful", "partial", "success"],
16
+ "instructions": "outcome"},
17
+ "risk": {"type": "score",
18
+ "levels": ["Benign: read-only or clearly safe actions.", "Low: routine writes within scope.",
19
+ "Moderate: irreversible or out-of-scope actions.",
20
+ "High: destructive, security-relevant, or policy-violating actions."]},
21
+ }
22
+
23
+
24
+ def customer_state(tier, tickets, msg):
25
+ return json.dumps({
26
+ "account": {"lifetime_value_usd": 4642, "prior_tickets_90d": int(tickets),
27
+ "seats": 3, "tenure_months": 60, "tier": tier},
28
+ "thread": [{"role": "customer", "text": msg}],
29
+ })
30
+
31
+
32
+ CUSTOMER_QS = {
33
+ "action": {"type": "choice",
34
+ "options": ["answer_directly", "close_no_action", "escalate_to_human",
35
+ "execute_refund", "request_information"],
36
+ "instructions": "action"},
37
+ "category": {"type": "choice", "options": ["account", "billing", "delivery", "refund", "technical"],
38
+ "instructions": "category"},
39
+ "churn_risk": {"type": "score",
40
+ "levels": ["No sign of dissatisfaction.", "Mild frustration, but the relationship is intact.",
41
+ "Clearly unhappy; repeat problems or explicit complaints.",
42
+ "Imminent: threatening to cancel, dispute or leave."]},
43
+ "needs_human": {"type": "noul"},
44
+ }
45
+
46
+ INVOICE_QS = {
47
+ "discrepancy_severity": {"type": "score",
48
+ "levels": ["None: everything reconciles.",
49
+ "Trivial: rounding or a cosmetic difference.",
50
+ "Moderate: a real difference worth confirming.",
51
+ "Material: a large or unexplained difference."]},
52
+ "disposition": {"type": "choice", "options": ["approve", "hold", "manual_review", "reject"],
53
+ "instructions": "disposition"},
54
+ "duplicate": {"type": "noul"},
55
+ "matches_order": {"type": "noul"},
56
+ }
57
+
58
+
59
+ def invoice_state(vendor, inv, po, recv, ordered, disputes):
60
+ return json.dumps({
61
+ "delivery": {"condition": "accepted with exceptions", "date": "2026-03-18",
62
+ "received_qty": int(recv)},
63
+ "invoice": {"currency": "USD", "id": "INV-2026-7551",
64
+ "lines": [{"qty": int(ordered), "sku": "SKU-940",
65
+ "total_usd": float(inv),
66
+ "unit_usd": round(float(inv) / max(int(ordered), 1), 2)}],
67
+ "total_usd": float(inv), "vendor": vendor},
68
+ "payment": {"days_until_due": -3, "discount_expires_in_days": None,
69
+ "early_payment_discount_pct": None},
70
+ "purchase_order": {"id": "PO-2026-4102",
71
+ "lines": [{"qty": int(ordered), "sku": "SKU-940",
72
+ "total_usd": float(po),
73
+ "unit_usd": round(float(po) / max(int(ordered), 1), 2)}]},
74
+ "vendor_history": {"invoices_last_12m": 14, "disputes_last_12m": int(disputes)},
75
+ })
76
+
77
+
78
+ SECURITY_QS = {
79
+ "credential_compromise": {"type": "noul"},
80
+ "disposition": {"type": "choice", "options": ["close_benign", "contain", "investigate", "monitor"],
81
+ "instructions": "disposition"},
82
+ "severity": {"type": "score",
83
+ "levels": ["Negligible: no access to anything sensitive.",
84
+ "Low: limited access, easily reversed.",
85
+ "Moderate: access to internal systems or non-public data.",
86
+ "High: access to production, secrets or customer data.",
87
+ "Critical: active compromise of crown-jewel systems."]},
88
+ "true_positive": {"type": "noul"},
89
+ }
90
+
91
+
92
+ def security_state(rule, desc, evid, crit, ptype):
93
+ return json.dumps({
94
+ "alert": {"description": desc, "evidence": evid, "rule": rule},
95
+ "context": {"asset_criticality": crit, "change_window_active": False},
96
+ "history": {"prior_incidents_90d": 0},
97
+ "principal": {"department": "engineering", "roles": ["service"], "type": ptype},
98
+ })
99
+
100
+
101
+ PLAYGROUND_STATE = "Shoes arrived two weeks late and in the wrong size. Also I see two charges on my card. What are you going to do about this?"
102
+
103
+ PLAYGROUND_QS = {
104
+ "department": {"type": "choice",
105
+ "instructions": "Which team should handle this?",
106
+ "options": ["billing", "shipping", "other"]},
107
+ "escalate": {"type": "noul", "instructions": "Does this need urgent human attention?"},
108
+ "frustration": {"type": "score", "levels": ["calm", "frustrated", "very angry"]},
109
+ }
110
+
111
+ # cross-field sanity rules: (if_field, if_value, then_field, expected)
112
+ COHERENCE_RULES = [
113
+ ("action", "human_review", "needs_review", "yes"),
114
+ ("action", "stop", "needs_review", "yes"),
115
+ ("action", "continue", "needs_review", "no"),
116
+ ]
space_ui/texts.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Markdown constants shown in the Space UI.
2
+ TITLE_MD = """
3
+ <div id="header">
4
+ <h1>OEV</h1>
5
+ <p><b>Typed questions in. Calibrated probabilities out. One forward pass. Nothing is generated.</b></p>
6
+ <p class="metrics"><b>184M</b> params · calibrated distributions · <b>0</b> tokens generated</p>
7
+ </div>
8
+ """
9
+
10
+ QS_HELP = """**Question schema** - one JSON object per question name:
11
+
12
+ ```json
13
+ {
14
+ "question_name": {
15
+ "type": "choice", // "choice" | "noul" (yes/no) | "score" (ordinal)
16
+ "options": ["a", "b"], // choice: the labels; score: level descriptions
17
+ "levels": ["low", "high"], // score only: ordinal level descriptions
18
+ "instructions": "what to decide"
19
+ }
20
+ }
21
+ ```
22
+ """
23
+
24
+ API_DOCS = r"""## Use OEV from code
25
+
26
+ The Space exposes one public API event, `decide`. Calls use two steps: submit
27
+ inputs, then fetch the streamed result by event ID.
28
+
29
+ ```bash
30
+ SPACE_URL="https://divyanshudhruv-oev-demo.hf.space"
31
+ EVENT_ID=$(curl -sS -X POST "$SPACE_URL/call/decide" \
32
+ -H "Content-Type: application/json" \
33
+ -d '{"data": ["state text", "{\"action\": {\"type\": \"choice\", \"options\": [\"continue\", \"stop\"]}}", 1.0]}' |
34
+ python -c "import json,sys; print(json.load(sys.stdin)['event_id'])")
35
+ curl -N "$SPACE_URL/call/decide/$EVENT_ID"
36
+ ```
37
+
38
+ The response is an output list. Its first item is a JSON object with one
39
+ entry per question. Choice and score questions include `answer`, `confidence`,
40
+ and the full `probabilities` distribution. Yes/no questions include `p_yes`
41
+
42
+ Or with the `gradio_client`:
43
+
44
+ ```python
45
+ from gradio_client import Client
46
+ client = Client("divyanshudhruv/oev-demo")
47
+ result = client.predict(
48
+ state, # str: plain text or JSON document
49
+ questions, # str: JSON, {"name": {"type": "choice|noul|score", ...}}
50
+ 1.0, # float: temperature
51
+ api_name="/decide",
52
+ )
53
+ ```
54
+
55
+ Python (local install):
56
+
57
+ ```python
58
+ from oev.infer import OEV
59
+ agent = OEV("divyanshudhruv/oev-typed/student-r3-oev-tiny.pt")
60
+ result = agent.decide(state, questions)
61
+ ```
62
+ """