willystumblr commited on
Commit
ad21498
·
verified ·
1 Parent(s): 947959e

Sync from GitHub

Browse files
Files changed (5) hide show
  1. app.py +25 -0
  2. assets/css/style.css +1046 -0
  3. assets/js/demo.js +539 -0
  4. index.html +374 -0
  5. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PICon Demo — HuggingFace Space entry point.
3
+
4
+ Serves the static demo page (index.html + assets) via FastAPI on port 7860.
5
+ All API calls from the browser go directly to the Railway backend.
6
+ """
7
+
8
+ from fastapi import FastAPI
9
+ from fastapi.responses import FileResponse
10
+ from fastapi.staticfiles import StaticFiles
11
+ import uvicorn
12
+
13
+ app = FastAPI()
14
+
15
+
16
+ @app.get("/")
17
+ async def root():
18
+ return FileResponse("index.html")
19
+
20
+
21
+ app.mount("/assets", StaticFiles(directory="assets"), name="assets")
22
+
23
+
24
+ if __name__ == "__main__":
25
+ uvicorn.run(app, host="0.0.0.0", port=7860)
assets/css/style.css ADDED
@@ -0,0 +1,1046 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===== Reset & Base ===== */
2
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3
+
4
+ :root {
5
+ --sidebar-w: 240px;
6
+ --color-bg: #ffffff;
7
+ --color-bg-alt: #f8f9fa;
8
+ --color-text: #1a1a2e;
9
+ --color-text-muted: #6b7280;
10
+ --color-primary: #4f46e5;
11
+ --color-primary-light: #e0e7ff;
12
+ --color-accent: #10b981;
13
+ --color-border: #e5e7eb;
14
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
15
+ --font-mono: 'JetBrains Mono', monospace;
16
+ --radius: 8px;
17
+ --shadow: 0 1px 3px rgba(0,0,0,0.08);
18
+ }
19
+
20
+ html { font-size: 16px; scroll-behavior: smooth; }
21
+ body {
22
+ font-family: var(--font-sans);
23
+ color: var(--color-text);
24
+ background: var(--color-bg);
25
+ line-height: 1.65;
26
+ display: flex;
27
+ min-height: 100vh;
28
+ }
29
+
30
+ a { color: var(--color-primary); text-decoration: none; }
31
+ a:hover { text-decoration: underline; }
32
+
33
+ /* ===== Sidebar ===== */
34
+ .sidebar {
35
+ position: fixed;
36
+ top: 0; left: 0;
37
+ width: var(--sidebar-w);
38
+ height: 100vh;
39
+ background: #fafafa;
40
+ border-right: 1px solid var(--color-border);
41
+ display: flex;
42
+ flex-direction: column;
43
+ z-index: 100;
44
+ overflow-y: auto;
45
+ }
46
+
47
+ .sidebar-header {
48
+ padding: 1.5rem 1.25rem 1rem;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: space-between;
52
+ }
53
+
54
+ .sidebar-logo {
55
+ font-size: 1.25rem;
56
+ font-weight: 700;
57
+ color: var(--color-primary);
58
+ letter-spacing: -0.02em;
59
+ }
60
+ .sidebar-logo:hover { text-decoration: none; }
61
+
62
+ .sidebar-toggle { display: none; background: none; border: none; cursor: pointer; }
63
+ .sidebar-toggle span {
64
+ display: block; width: 20px; height: 2px;
65
+ background: var(--color-text); margin: 4px 0;
66
+ transition: 0.2s;
67
+ }
68
+
69
+ .sidebar-nav {
70
+ list-style: none;
71
+ padding: 0 0.75rem;
72
+ flex: 1;
73
+ }
74
+
75
+ .nav-item { margin-bottom: 2px; }
76
+
77
+ .nav-link {
78
+ display: block;
79
+ padding: 0.5rem 0.75rem;
80
+ border-radius: 6px;
81
+ font-size: 0.875rem;
82
+ font-weight: 500;
83
+ color: var(--color-text);
84
+ transition: background 0.15s;
85
+ }
86
+ .nav-link:hover { background: var(--color-primary-light); text-decoration: none; }
87
+ .nav-item.active > .nav-link {
88
+ background: var(--color-primary-light);
89
+ color: var(--color-primary);
90
+ }
91
+
92
+ .nav-children {
93
+ list-style: none;
94
+ padding-left: 1rem;
95
+ }
96
+ .nav-child a {
97
+ display: block;
98
+ padding: 0.3rem 0.75rem;
99
+ font-size: 0.8125rem;
100
+ color: var(--color-text-muted);
101
+ border-radius: 4px;
102
+ }
103
+ .nav-child a:hover { color: var(--color-primary); text-decoration: none; }
104
+ .nav-child.active a { color: var(--color-primary); font-weight: 500; }
105
+
106
+ .sidebar-footer {
107
+ padding: 1rem 1.25rem;
108
+ border-top: 1px solid var(--color-border);
109
+ display: flex;
110
+ gap: 1rem;
111
+ }
112
+ .sidebar-link {
113
+ font-size: 0.8125rem;
114
+ color: var(--color-text-muted);
115
+ }
116
+
117
+ /* ===== Page Wrap ===== */
118
+ .page-wrap {
119
+ margin-left: var(--sidebar-w);
120
+ flex: 1;
121
+ display: flex;
122
+ flex-direction: column;
123
+ min-height: 100vh;
124
+ }
125
+
126
+ /* ===== Main Content ===== */
127
+ .main-content {
128
+ flex: 1;
129
+ }
130
+
131
+ .container {
132
+ max-width: 75%;
133
+ margin: 0 auto;
134
+ padding: 3rem 2rem;
135
+ }
136
+
137
+ .container-wide {
138
+ max-width: 85%;
139
+ margin: 0 auto;
140
+ padding: 3rem 2rem;
141
+ }
142
+
143
+ /* ===== Typography ===== */
144
+ h1, h2, h3, h4 { line-height: 1.3; letter-spacing: -0.01em; }
145
+ h1 { font-size: 2rem; font-weight: 700; margin-bottom: 0.75rem; }
146
+ h2 { font-size: 1.5rem; font-weight: 600; margin: 2rem 0 0.75rem; }
147
+ h3 { font-size: 1.125rem; font-weight: 600; margin: 1.5rem 0 0.5rem; }
148
+ p { margin-bottom: 1rem; }
149
+
150
+ .subtitle {
151
+ font-size: 1.125rem;
152
+ color: var(--color-text-muted);
153
+ margin-bottom: 2rem;
154
+ }
155
+
156
+ /* ===== Hero (Home) ===== */
157
+ .hero {
158
+ text-align: center;
159
+ padding: 5rem 2rem 3rem;
160
+ background: linear-gradient(135deg, #f0f0ff 0%, #e8faf0 100%);
161
+ border-bottom: 1px solid var(--color-border);
162
+ }
163
+
164
+ .hero h1 {
165
+ font-size: 2.5rem;
166
+ margin-bottom: 0.5rem;
167
+ }
168
+
169
+ .hero .tagline {
170
+ font-size: 1.125rem;
171
+ color: var(--color-text-muted);
172
+ max-width: 800px;
173
+ margin: 0 auto 2.5rem;
174
+ line-height: 1.7;
175
+ }
176
+
177
+ .cta-group {
178
+ display: flex;
179
+ gap: 1rem;
180
+ justify-content: center;
181
+ flex-wrap: wrap;
182
+ }
183
+
184
+ .btn {
185
+ display: inline-flex;
186
+ align-items: center;
187
+ gap: 0.5rem;
188
+ padding: 0.75rem 1.75rem;
189
+ border-radius: var(--radius);
190
+ font-size: 0.9375rem;
191
+ font-weight: 600;
192
+ transition: all 0.15s;
193
+ border: none;
194
+ cursor: pointer;
195
+ }
196
+ .btn:hover { text-decoration: none; transform: translateY(-1px); }
197
+
198
+ .btn-primary {
199
+ background: var(--color-primary);
200
+ color: #fff;
201
+ }
202
+ .btn-primary:hover { background: #4338ca; }
203
+
204
+ .btn-accent {
205
+ background: var(--color-accent);
206
+ color: #fff;
207
+ }
208
+ .btn-accent:hover { background: #059669; }
209
+
210
+ .btn-outline {
211
+ background: transparent;
212
+ color: var(--color-primary);
213
+ border: 2px solid var(--color-primary);
214
+ }
215
+ .btn-outline:hover { background: var(--color-primary-light); }
216
+
217
+ /* ===== Home Sections ===== */
218
+ .home-section {
219
+ padding: 3rem 2rem;
220
+ max-width: 75%;
221
+ margin: 0 auto;
222
+ }
223
+
224
+ .feature-grid {
225
+ display: grid;
226
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
227
+ gap: 1.5rem;
228
+ margin-top: 1.5rem;
229
+ }
230
+
231
+ .feature-card {
232
+ padding: 1.5rem;
233
+ background: var(--color-bg);
234
+ border: 1px solid var(--color-border);
235
+ border-radius: var(--radius);
236
+ }
237
+
238
+ .feature-card h3 {
239
+ margin: 0 0 0.5rem;
240
+ font-size: 1rem;
241
+ }
242
+
243
+ .feature-card p {
244
+ font-size: 0.875rem;
245
+ color: var(--color-text-muted);
246
+ margin: 0;
247
+ }
248
+
249
+ /* ===== Research ===== */
250
+ .paper-header {
251
+ border-bottom: 1px solid var(--color-border);
252
+ padding-bottom: 1.5rem;
253
+ margin-bottom: 2rem;
254
+ }
255
+
256
+ .paper-header h1 { font-size: 1.75rem; }
257
+
258
+ .paper-meta {
259
+ display: flex;
260
+ gap: 1.5rem;
261
+ flex-wrap: wrap;
262
+ margin-top: 0.75rem;
263
+ font-size: 0.875rem;
264
+ color: var(--color-text-muted);
265
+ }
266
+
267
+ .abstract-box {
268
+ background: var(--color-bg-alt);
269
+ border-left: 4px solid var(--color-primary);
270
+ padding: 1.5rem;
271
+ border-radius: 0 var(--radius) var(--radius) 0;
272
+ margin: 1.5rem 0;
273
+ }
274
+
275
+ .abstract-box p {
276
+ margin: 0;
277
+ font-size: 0.9375rem;
278
+ line-height: 1.75;
279
+ }
280
+
281
+ .dimensions-grid {
282
+ display: grid;
283
+ grid-template-columns: repeat(3, 1fr);
284
+ gap: 1rem;
285
+ margin: 1.5rem 0;
286
+ }
287
+
288
+ .dimension-card {
289
+ padding: 1.25rem;
290
+ border: 1px solid var(--color-border);
291
+ border-radius: var(--radius);
292
+ text-align: center;
293
+ }
294
+
295
+ .dimension-card .icon { font-size: 1.5rem; margin-bottom: 0.5rem; }
296
+ .dimension-card h4 { font-size: 0.875rem; margin-bottom: 0.25rem; }
297
+ .dimension-card p { font-size: 0.8125rem; color: var(--color-text-muted); margin: 0; }
298
+
299
+ /* ===== Method page ===== */
300
+ .phase-steps {
301
+ list-style: none;
302
+ counter-reset: step;
303
+ padding: 0;
304
+ margin: 1.5rem 0;
305
+ }
306
+
307
+ .phase-steps li {
308
+ counter-increment: step;
309
+ display: flex;
310
+ gap: 1rem;
311
+ align-items: flex-start;
312
+ padding: 1rem 0;
313
+ border-bottom: 1px solid var(--color-border);
314
+ }
315
+
316
+ .phase-steps li:last-child { border-bottom: none; }
317
+
318
+ .phase-steps li::before {
319
+ content: counter(step);
320
+ flex-shrink: 0;
321
+ width: 2rem;
322
+ height: 2rem;
323
+ display: flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ background: var(--color-primary);
327
+ color: #fff;
328
+ border-radius: 50%;
329
+ font-size: 0.875rem;
330
+ font-weight: 700;
331
+ }
332
+
333
+ .step-label {
334
+ font-weight: 600;
335
+ font-size: 0.9375rem;
336
+ margin-bottom: 0.25rem;
337
+ }
338
+
339
+ .step-desc {
340
+ font-size: 0.875rem;
341
+ color: var(--color-text-muted);
342
+ line-height: 1.6;
343
+ }
344
+
345
+ .callout {
346
+ background: var(--color-primary-light);
347
+ border-left: 4px solid var(--color-primary);
348
+ padding: 1rem 1.25rem;
349
+ border-radius: 0 var(--radius) var(--radius) 0;
350
+ margin: 1.5rem 0;
351
+ font-size: 0.9375rem;
352
+ }
353
+
354
+ .config-card {
355
+ background: var(--color-bg-alt);
356
+ border: 1px solid var(--color-border);
357
+ border-radius: var(--radius);
358
+ padding: 1.25rem 1.5rem;
359
+ margin: 1.5rem 0;
360
+ }
361
+
362
+ .config-card h4 {
363
+ margin: 0 0 0.75rem;
364
+ font-size: 0.9375rem;
365
+ }
366
+
367
+ .config-grid {
368
+ display: grid;
369
+ grid-template-columns: 1fr 1fr;
370
+ gap: 0.5rem;
371
+ }
372
+
373
+ .config-item {
374
+ display: flex;
375
+ justify-content: space-between;
376
+ padding: 0.375rem 0;
377
+ font-size: 0.875rem;
378
+ }
379
+
380
+ .config-role { color: var(--color-text-muted); }
381
+ .config-value { font-weight: 600; font-family: var(--font-mono); font-size: 0.8125rem; }
382
+
383
+ .config-note {
384
+ margin: 0.75rem 0 0;
385
+ font-size: 0.8125rem;
386
+ color: var(--color-text-muted);
387
+ }
388
+
389
+ /* ===== Formula toggles ===== */
390
+ details.formula-toggle {
391
+ margin: 1rem 0;
392
+ border: 1px solid var(--color-border);
393
+ border-radius: var(--radius);
394
+ overflow: hidden;
395
+ }
396
+
397
+ details.formula-toggle summary {
398
+ padding: 0.625rem 1rem;
399
+ background: var(--color-bg-alt);
400
+ cursor: pointer;
401
+ font-size: 0.875rem;
402
+ font-weight: 500;
403
+ color: var(--color-primary);
404
+ list-style: none;
405
+ display: flex;
406
+ align-items: center;
407
+ gap: 0.5rem;
408
+ }
409
+
410
+ details.formula-toggle summary::-webkit-details-marker { display: none; }
411
+
412
+ details.formula-toggle summary::before {
413
+ content: '\25B6';
414
+ font-size: 0.625rem;
415
+ transition: transform 0.2s;
416
+ }
417
+
418
+ details.formula-toggle[open] summary::before {
419
+ transform: rotate(90deg);
420
+ }
421
+
422
+ details.formula-toggle .formula-content {
423
+ padding: 1.25rem 1.5rem;
424
+ font-size: 0.9375rem;
425
+ line-height: 1.75;
426
+ border-top: 1px solid var(--color-border);
427
+ }
428
+
429
+ details.formula-toggle .formula-content p {
430
+ margin-bottom: 0.75rem;
431
+ }
432
+
433
+ details.formula-toggle .formula-content p:last-child {
434
+ margin-bottom: 0;
435
+ }
436
+
437
+ .page-nav {
438
+ display: flex;
439
+ gap: 1rem;
440
+ margin-top: 2.5rem;
441
+ padding-top: 1.5rem;
442
+ border-top: 1px solid var(--color-border);
443
+ }
444
+
445
+ /* ===== Method / Results figures ===== */
446
+ .figure-row {
447
+ display: flex;
448
+ gap: 1.5rem;
449
+ align-items: flex-start;
450
+ margin: 1.5rem 0;
451
+ }
452
+ .figure-row figure {
453
+ flex: 1;
454
+ min-width: 0;
455
+ margin: 0;
456
+ }
457
+
458
+ figure {
459
+ margin: 1.5rem 0;
460
+ }
461
+ figure img {
462
+ max-width: 100%;
463
+ border-radius: var(--radius);
464
+ border: 1px solid var(--color-border);
465
+ }
466
+ figcaption {
467
+ margin-top: 0.5rem;
468
+ font-size: 0.8125rem;
469
+ color: var(--color-text-muted);
470
+ text-align: center;
471
+ }
472
+
473
+ table {
474
+ width: 100%;
475
+ border-collapse: collapse;
476
+ font-size: 0.875rem;
477
+ margin: 1.5rem 0;
478
+ }
479
+ th, td {
480
+ padding: 0.625rem 0.75rem;
481
+ border: 1px solid var(--color-border);
482
+ text-align: left;
483
+ }
484
+ th { background: var(--color-bg-alt); font-weight: 600; }
485
+
486
+ /* ===== Demo ===== */
487
+ .demo-tabs {
488
+ display: flex;
489
+ border-bottom: 2px solid var(--color-border);
490
+ margin-bottom: 1.5rem;
491
+ }
492
+
493
+ .demo-tab {
494
+ padding: 0.75rem 1.5rem;
495
+ font-size: 0.9375rem;
496
+ font-weight: 500;
497
+ background: none;
498
+ border: none;
499
+ cursor: pointer;
500
+ color: var(--color-text-muted);
501
+ border-bottom: 2px solid transparent;
502
+ margin-bottom: -2px;
503
+ transition: 0.15s;
504
+ }
505
+ .demo-tab:hover { color: var(--color-text); }
506
+ .demo-tab.active {
507
+ color: var(--color-primary);
508
+ border-bottom-color: var(--color-primary);
509
+ }
510
+
511
+ .demo-panel { display: none; }
512
+ .demo-panel.active { display: block; }
513
+
514
+ .demo-description {
515
+ margin-bottom: 1.5rem;
516
+ padding: 1rem 1.25rem;
517
+ background: var(--color-bg-alt);
518
+ border-radius: var(--radius);
519
+ font-size: 0.9375rem;
520
+ }
521
+
522
+ /* Chat UI */
523
+ .chat-container {
524
+ border: 1px solid var(--color-border);
525
+ border-radius: var(--radius);
526
+ overflow: hidden;
527
+ background: #fff;
528
+ }
529
+
530
+ .chat-header {
531
+ padding: 0.75rem 1rem;
532
+ background: var(--color-bg-alt);
533
+ border-bottom: 1px solid var(--color-border);
534
+ display: flex;
535
+ justify-content: space-between;
536
+ align-items: center;
537
+ }
538
+
539
+ .chat-header h4 { margin: 0; font-size: 0.875rem; }
540
+
541
+ .chat-progress {
542
+ font-size: 0.75rem;
543
+ color: var(--color-text-muted);
544
+ }
545
+
546
+ .chat-messages {
547
+ height: 400px;
548
+ overflow-y: auto;
549
+ padding: 1rem;
550
+ display: flex;
551
+ flex-direction: column;
552
+ gap: 0.75rem;
553
+ }
554
+
555
+ .chat-msg {
556
+ max-width: 80%;
557
+ padding: 0.625rem 0.875rem;
558
+ border-radius: 12px;
559
+ font-size: 0.875rem;
560
+ line-height: 1.5;
561
+ word-wrap: break-word;
562
+ }
563
+
564
+ .chat-msg.system {
565
+ align-self: flex-start;
566
+ background: var(--color-bg-alt);
567
+ border: 1px solid var(--color-border);
568
+ }
569
+
570
+ .chat-msg.user {
571
+ align-self: flex-end;
572
+ background: var(--color-primary);
573
+ color: #fff;
574
+ }
575
+
576
+ .chat-msg.info {
577
+ align-self: center;
578
+ background: var(--color-primary-light);
579
+ color: var(--color-primary);
580
+ font-size: 0.8125rem;
581
+ text-align: center;
582
+ }
583
+
584
+ .chat-typing {
585
+ align-self: flex-start;
586
+ display: flex;
587
+ gap: 4px;
588
+ padding: 0.625rem 0.875rem;
589
+ }
590
+ .chat-typing span {
591
+ width: 6px; height: 6px;
592
+ background: var(--color-text-muted);
593
+ border-radius: 50%;
594
+ animation: bounce 1.2s infinite;
595
+ }
596
+ .chat-typing span:nth-child(2) { animation-delay: 0.15s; }
597
+ .chat-typing span:nth-child(3) { animation-delay: 0.3s; }
598
+
599
+ @keyframes bounce {
600
+ 0%, 60%, 100% { transform: translateY(0); }
601
+ 30% { transform: translateY(-6px); }
602
+ }
603
+
604
+ /* Agent terminal log viewer */
605
+ .agent-terminal {
606
+ border: 1px solid #2d2d2d;
607
+ border-radius: var(--radius);
608
+ overflow: hidden;
609
+ background: #1e1e1e;
610
+ }
611
+
612
+ .terminal-header {
613
+ padding: 0.5rem 1rem;
614
+ background: #2d2d2d;
615
+ display: flex;
616
+ justify-content: space-between;
617
+ align-items: center;
618
+ }
619
+
620
+ .terminal-title {
621
+ font-family: var(--font-mono);
622
+ font-size: 0.8125rem;
623
+ color: #a0a0a0;
624
+ }
625
+
626
+ .terminal-header .chat-progress {
627
+ color: #7ec699;
628
+ }
629
+
630
+ .agent-terminal .terminal-body {
631
+ height: 420px;
632
+ overflow-y: auto;
633
+ padding: 1rem;
634
+ margin: 0;
635
+ font-family: var(--font-mono);
636
+ font-size: 0.78rem;
637
+ line-height: 1.6;
638
+ color: #d4d4d4;
639
+ background: #1e1e1e;
640
+ white-space: pre-wrap;
641
+ word-break: break-word;
642
+ }
643
+
644
+ .chat-input {
645
+ display: flex;
646
+ border-top: 1px solid var(--color-border);
647
+ }
648
+
649
+ .chat-input input {
650
+ flex: 1;
651
+ padding: 0.75rem 1rem;
652
+ border: none;
653
+ font-size: 0.875rem;
654
+ font-family: var(--font-sans);
655
+ outline: none;
656
+ }
657
+
658
+ .chat-input button {
659
+ padding: 0.75rem 1.25rem;
660
+ background: var(--color-primary);
661
+ color: #fff;
662
+ border: none;
663
+ font-weight: 600;
664
+ font-size: 0.875rem;
665
+ cursor: pointer;
666
+ transition: background 0.15s;
667
+ }
668
+ .chat-input button:hover { background: #4338ca; }
669
+ .chat-input button:disabled { opacity: 0.5; cursor: not-allowed; }
670
+
671
+ /* Agent submode selector */
672
+ .agent-submode-selector {
673
+ display: grid;
674
+ grid-template-columns: 1fr 1fr;
675
+ gap: 1rem;
676
+ margin-bottom: 1.5rem;
677
+ }
678
+
679
+ .agent-submode-card {
680
+ display: flex;
681
+ flex-direction: column;
682
+ align-items: flex-start;
683
+ gap: 0.375rem;
684
+ padding: 1.25rem;
685
+ border: 2px solid var(--color-border);
686
+ border-radius: var(--radius);
687
+ background: #fff;
688
+ cursor: pointer;
689
+ text-align: left;
690
+ transition: border-color 0.15s, box-shadow 0.15s;
691
+ }
692
+
693
+ .agent-submode-card:hover {
694
+ border-color: var(--color-primary);
695
+ }
696
+
697
+ .agent-submode-card.active {
698
+ border-color: var(--color-primary);
699
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
700
+ }
701
+
702
+ .submode-icon {
703
+ font-size: 1.5rem;
704
+ }
705
+
706
+ .submode-title {
707
+ font-weight: 600;
708
+ font-size: 0.9375rem;
709
+ }
710
+
711
+ .submode-desc {
712
+ font-size: 0.8125rem;
713
+ color: var(--color-text-muted);
714
+ line-height: 1.45;
715
+ }
716
+
717
+ /* Agent test form */
718
+ .agent-form {
719
+ display: flex;
720
+ flex-direction: column;
721
+ gap: 1rem;
722
+ margin-bottom: 1.5rem;
723
+ }
724
+
725
+ .form-group label {
726
+ display: block;
727
+ font-size: 0.8125rem;
728
+ font-weight: 500;
729
+ margin-bottom: 0.25rem;
730
+ }
731
+
732
+ .form-group input,
733
+ .form-group textarea,
734
+ .form-group select {
735
+ width: 100%;
736
+ padding: 0.5rem 0.75rem;
737
+ border: 1px solid var(--color-border);
738
+ border-radius: 6px;
739
+ font-size: 0.875rem;
740
+ font-family: var(--font-sans);
741
+ }
742
+
743
+ .form-group textarea {
744
+ font-family: var(--font-mono);
745
+ font-size: 0.8125rem;
746
+ min-height: 120px;
747
+ resize: vertical;
748
+ }
749
+
750
+ .form-row {
751
+ display: flex;
752
+ gap: 1rem;
753
+ }
754
+
755
+ .label-hint {
756
+ font-weight: 400;
757
+ color: var(--color-text-muted);
758
+ }
759
+
760
+ /* Results card */
761
+ .results-card {
762
+ padding: 1.5rem;
763
+ border: 1px solid var(--color-border);
764
+ border-radius: var(--radius);
765
+ background: #fff;
766
+ margin-top: 1.5rem;
767
+ }
768
+ .results-card h3 { margin: 0 0 1rem; font-size: 1.125rem; }
769
+ .results-note {
770
+ margin: 1rem 0 0;
771
+ font-size: 0.8125rem;
772
+ color: var(--color-text-muted);
773
+ }
774
+
775
+ .score-grid {
776
+ display: grid;
777
+ grid-template-columns: repeat(3, 1fr);
778
+ gap: 1rem;
779
+ }
780
+ .score-cell {
781
+ text-align: center;
782
+ padding: 1rem;
783
+ border: 1px solid var(--color-border);
784
+ border-radius: var(--radius);
785
+ background: var(--color-bg-alt);
786
+ }
787
+ .score-cell .score-label {
788
+ font-size: 0.75rem;
789
+ font-weight: 600;
790
+ color: var(--color-text-muted);
791
+ text-transform: uppercase;
792
+ letter-spacing: 0.04em;
793
+ }
794
+ .score-cell .score-value {
795
+ font-size: 1.75rem;
796
+ font-weight: 700;
797
+ color: var(--color-primary);
798
+ margin: 0.25rem 0;
799
+ }
800
+ .score-cell .score-sub {
801
+ font-size: 0.75rem;
802
+ color: var(--color-text-muted);
803
+ }
804
+
805
+ /* Leaderboard */
806
+ .leaderboard-controls {
807
+ display: flex;
808
+ gap: 1rem;
809
+ margin-bottom: 1rem;
810
+ }
811
+ .leaderboard-filter label {
812
+ font-size: 0.75rem;
813
+ font-weight: 600;
814
+ color: var(--color-text-muted);
815
+ display: block;
816
+ margin-bottom: 0.25rem;
817
+ }
818
+ .leaderboard-filter select {
819
+ padding: 0.375rem 0.625rem;
820
+ border: 1px solid var(--color-border);
821
+ border-radius: 6px;
822
+ font-size: 0.8125rem;
823
+ font-family: var(--font-sans);
824
+ background: #fff;
825
+ }
826
+
827
+ .leaderboard-table-wrap {
828
+ overflow-x: auto;
829
+ border: 1px solid var(--color-border);
830
+ border-radius: var(--radius);
831
+ }
832
+
833
+ .leaderboard-table {
834
+ width: 100%;
835
+ border-collapse: collapse;
836
+ font-size: 0.875rem;
837
+ }
838
+ .leaderboard-table th,
839
+ .leaderboard-table td {
840
+ padding: 0.625rem 0.75rem;
841
+ border-bottom: 1px solid var(--color-border);
842
+ text-align: left;
843
+ }
844
+ .leaderboard-table th {
845
+ background: var(--color-bg-alt);
846
+ font-weight: 600;
847
+ font-size: 0.8125rem;
848
+ position: sticky;
849
+ top: 0;
850
+ }
851
+ .leaderboard-table th.sortable {
852
+ cursor: pointer;
853
+ user-select: none;
854
+ }
855
+ .leaderboard-table th.sortable:hover {
856
+ color: var(--color-primary);
857
+ }
858
+ .leaderboard-table th.active {
859
+ color: var(--color-primary);
860
+ }
861
+ .leaderboard-table th.active::after {
862
+ content: ' \25BC';
863
+ font-size: 0.625rem;
864
+ }
865
+ .leaderboard-table tbody tr:hover {
866
+ background: var(--color-primary-light);
867
+ }
868
+ .leaderboard-table .lb-rank {
869
+ width: 2.5rem;
870
+ text-align: center;
871
+ font-weight: 600;
872
+ color: var(--color-text-muted);
873
+ }
874
+ .leaderboard-table .lb-score {
875
+ text-align: right;
876
+ font-family: var(--font-mono);
877
+ font-size: 0.8125rem;
878
+ width: 5rem;
879
+ }
880
+ .leaderboard-table .lb-type {
881
+ width: 7rem;
882
+ }
883
+ .lb-badge {
884
+ display: inline-block;
885
+ font-size: 0.6875rem;
886
+ font-weight: 600;
887
+ padding: 0.125rem 0.5rem;
888
+ border-radius: 10px;
889
+ text-transform: uppercase;
890
+ letter-spacing: 0.03em;
891
+ }
892
+ .lb-badge.baseline { background: #dbeafe; color: #1e40af; }
893
+ .lb-badge.prompting { background: #dcfce7; color: #166534; }
894
+ .lb-badge.finetuned { background: #fef3c7; color: #92400e; }
895
+ .lb-badge.rag { background: #f3e8ff; color: #6b21a8; }
896
+ .lb-badge.commercial { background: #fce7f3; color: #9d174d; }
897
+ .lb-badge.community { background: #e0e7ff; color: #3730a3; }
898
+
899
+ .lb-rank-1 { color: #d97706; font-weight: 700; }
900
+ .lb-rank-2 { color: #6b7280; font-weight: 700; }
901
+ .lb-rank-3 { color: #92400e; font-weight: 700; }
902
+
903
+ .lb-human-row {
904
+ background: #fffbeb;
905
+ }
906
+
907
+ .leaderboard-footnote {
908
+ margin-top: 0.75rem;
909
+ font-size: 0.75rem;
910
+ color: var(--color-text-muted);
911
+ line-height: 1.6;
912
+ }
913
+
914
+ /* Python API section */
915
+ .python-api-section {
916
+ padding-bottom: 2rem;
917
+ }
918
+ .python-api-section h2 {
919
+ margin-bottom: 0.5rem;
920
+ }
921
+
922
+ .code-block {
923
+ margin: 1rem 0;
924
+ border: 1px solid var(--color-border);
925
+ border-radius: var(--radius);
926
+ overflow: hidden;
927
+ }
928
+ .code-block .code-header {
929
+ padding: 0.5rem 0.75rem;
930
+ background: var(--color-bg-alt);
931
+ border-bottom: 1px solid var(--color-border);
932
+ font-size: 0.75rem;
933
+ font-weight: 600;
934
+ color: var(--color-text-muted);
935
+ }
936
+ .code-block pre {
937
+ margin: 0;
938
+ border-radius: 0;
939
+ background: #1e1e2e;
940
+ color: #cdd6f4;
941
+ padding: 1rem 1.25rem;
942
+ font-size: 0.8125rem;
943
+ line-height: 1.6;
944
+ }
945
+ .code-block pre code {
946
+ background: none;
947
+ padding: 0;
948
+ color: inherit;
949
+ font-size: inherit;
950
+ }
951
+
952
+ /* ===== Contact / Cite ===== */
953
+ .bibtex-block {
954
+ background: #1e1e2e;
955
+ color: #cdd6f4;
956
+ padding: 1.25rem;
957
+ border-radius: var(--radius);
958
+ font-family: var(--font-mono);
959
+ font-size: 0.8125rem;
960
+ line-height: 1.6;
961
+ overflow-x: auto;
962
+ position: relative;
963
+ }
964
+
965
+ .bibtex-block pre {
966
+ background: transparent;
967
+ color: inherit;
968
+ padding: 0;
969
+ margin: 0;
970
+ }
971
+
972
+ .copy-btn {
973
+ position: absolute;
974
+ top: 0.5rem;
975
+ right: 0.5rem;
976
+ padding: 0.25rem 0.5rem;
977
+ background: rgba(255,255,255,0.1);
978
+ border: 1px solid rgba(255,255,255,0.15);
979
+ border-radius: 4px;
980
+ color: #cdd6f4;
981
+ font-size: 0.75rem;
982
+ cursor: pointer;
983
+ }
984
+ .copy-btn:hover { background: rgba(255,255,255,0.2); }
985
+
986
+ .contact-grid {
987
+ display: grid;
988
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
989
+ gap: 1.5rem;
990
+ margin: 1.5rem 0;
991
+ }
992
+
993
+ .contact-card {
994
+ padding: 1.25rem;
995
+ border: 1px solid var(--color-border);
996
+ border-radius: var(--radius);
997
+ }
998
+ .contact-card h4 { margin: 0 0 0.5rem; font-size: 0.9375rem; }
999
+ .contact-card p { margin: 0; font-size: 0.875rem; color: var(--color-text-muted); }
1000
+
1001
+ /* ===== Footer ===== */
1002
+ .site-footer {
1003
+ border-top: 1px solid var(--color-border);
1004
+ padding: 1.5rem 2rem;
1005
+ text-align: center;
1006
+ font-size: 0.8125rem;
1007
+ color: var(--color-text-muted);
1008
+ }
1009
+
1010
+ /* ===== Code ===== */
1011
+ code {
1012
+ font-family: var(--font-mono);
1013
+ font-size: 0.85em;
1014
+ background: var(--color-bg-alt);
1015
+ padding: 0.15em 0.35em;
1016
+ border-radius: 4px;
1017
+ }
1018
+
1019
+ pre {
1020
+ background: var(--color-bg-alt);
1021
+ padding: 1rem;
1022
+ border-radius: var(--radius);
1023
+ overflow-x: auto;
1024
+ font-size: 0.8125rem;
1025
+ }
1026
+
1027
+ /* ===== Responsive ===== */
1028
+ @media (max-width: 768px) {
1029
+ .sidebar {
1030
+ transform: translateX(-100%);
1031
+ transition: transform 0.25s;
1032
+ }
1033
+ .sidebar.open { transform: translateX(0); }
1034
+ .sidebar-toggle { display: block; }
1035
+
1036
+ .page-wrap { margin-left: 0; }
1037
+
1038
+ .hero h1 { font-size: 1.75rem; }
1039
+ .dimensions-grid { grid-template-columns: 1fr; }
1040
+ .figure-row { flex-direction: column; }
1041
+ .config-grid { grid-template-columns: 1fr; }
1042
+ .chat-messages { height: 300px; }
1043
+ .score-grid { grid-template-columns: 1fr; }
1044
+ .form-row { flex-direction: column; }
1045
+ .leaderboard-controls { flex-direction: column; }
1046
+ }
assets/js/demo.js ADDED
@@ -0,0 +1,539 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * PICon Demo — Experience Mode, Agent Test Mode & Leaderboard
3
+ */
4
+ (function () {
5
+ 'use strict';
6
+
7
+ // --- Config ---
8
+ const API_BASE = (document.querySelector('meta[name="demo-api-url"]') || {}).content
9
+ || window.PICON_API_URL
10
+ || '';
11
+
12
+ // ===== Leaderboard Data (paper baselines) =====
13
+ // IC = Internal Consistency, EC = External Consistency, RC = Retest Consistency
14
+ // Area = normalized triangle area on IC-EC-RC radar chart
15
+ var BASELINES = [
16
+ { name: 'Human', type: 'baseline', arch: 'Baseline', turns: 50, ic: 0.90, ec: 0.66, rc: 0.94 },
17
+ { name: 'Human Simulacra', type: 'baseline', arch: 'RAG', turns: 50, ic: 0.79, ec: 0.63, rc: 0.87 },
18
+ { name: 'Li et al. (2025)',type: 'baseline', arch: 'Prompting', turns: 50, ic: 0.73, ec: 0.59, rc: 0.98 },
19
+ { name: 'DeepPersona', type: 'baseline', arch: 'Prompting', turns: 50, ic: 0.72, ec: 0.54, rc: 0.92 },
20
+ { name: 'Character.ai', type: 'baseline', arch: 'Commercial', turns: 50, ic: 0.71, ec: 0.71, rc: 0.46 },
21
+ { name: 'Twin 2K 500', type: 'baseline', arch: 'Prompting', turns: 50, ic: 0.53, ec: 0.26, rc: 0.95 },
22
+ { name: 'Consistent LLM', type: 'baseline', arch: 'Fine-tuned', turns: 50, ic: 0.31, ec: 0.30, rc: 0.14 },
23
+ { name: 'OpenCharacter', type: 'baseline', arch: 'Fine-tuned', turns: 50, ic: 0.16, ec: 0.15, rc: 0.14 },
24
+ ];
25
+
26
+ // Compute normalized triangle area: (IC*EC + EC*RC + RC*IC) / 3
27
+ function computeArea(d) {
28
+ return (d.ic * d.ec + d.ec * d.rc + d.rc * d.ic) / 3;
29
+ }
30
+
31
+ BASELINES.forEach(function (d) { d.area = computeArea(d); });
32
+
33
+ // Community submissions (loaded from API; localStorage as fallback)
34
+ var communityEntries = [];
35
+ try {
36
+ communityEntries = JSON.parse(localStorage.getItem('picon_community') || '[]');
37
+ } catch (e) { /* ignore */ }
38
+
39
+ function fetchCommunityEntries() {
40
+ if (!API_BASE) return;
41
+ fetch(API_BASE + '/api/leaderboard')
42
+ .then(function (res) { return res.json(); })
43
+ .then(function (data) {
44
+ if (data.entries && data.entries.length > 0) {
45
+ communityEntries = data.entries.map(function (e) {
46
+ e.area = computeArea(e);
47
+ return e;
48
+ });
49
+ renderLeaderboard();
50
+ }
51
+ })
52
+ .catch(function () { /* keep localStorage entries */ });
53
+ }
54
+
55
+ // ===== Tab switching =====
56
+ document.querySelectorAll('.demo-tab').forEach(function (tab) {
57
+ tab.addEventListener('click', function () {
58
+ document.querySelectorAll('.demo-tab').forEach(function (t) { t.classList.remove('active'); });
59
+ document.querySelectorAll('.demo-panel').forEach(function (p) { p.classList.remove('active'); });
60
+ tab.classList.add('active');
61
+ document.getElementById('panel-' + tab.dataset.tab).classList.add('active');
62
+ if (tab.dataset.tab === 'leaderboard') fetchCommunityEntries();
63
+ });
64
+ });
65
+
66
+ // ===== Helpers =====
67
+
68
+ function linkify(text) {
69
+ return text.replace(/(https?:\/\/[^\s)<>]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
70
+ }
71
+
72
+ function addMessage(container, type, text) {
73
+ var el = document.createElement('div');
74
+ el.className = 'chat-msg ' + type;
75
+ el.innerHTML = linkify(text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));
76
+ container.appendChild(el);
77
+ container.scrollTop = container.scrollHeight;
78
+ return el;
79
+ }
80
+
81
+ function showTyping(container) {
82
+ var el = document.createElement('div');
83
+ el.className = 'chat-typing';
84
+ el.innerHTML = '<span></span><span></span><span></span>';
85
+ container.appendChild(el);
86
+ container.scrollTop = container.scrollHeight;
87
+ return el;
88
+ }
89
+
90
+ function setProgress(el, progress) {
91
+ if (!progress) return;
92
+ var phase = {
93
+ predefined: 'Part 1: Getting to Know You',
94
+ main: 'Part 2: Interrogation',
95
+ repeat: 'Part 3: Retest',
96
+ complete: 'Complete'
97
+ }[progress.phase] || progress.phase;
98
+ el.textContent = phase + ' — Q' + progress.current + '/' + progress.total;
99
+ }
100
+
101
+ function fmtScore(v) {
102
+ if (v == null) return '—';
103
+ return v.toFixed(2);
104
+ }
105
+
106
+ function renderScoreGrid(gridEl, scores) {
107
+ gridEl.innerHTML = '';
108
+ var dims = [
109
+ { key: 'ic', label: 'Internal Consistency', sub: 'Non-contradiction × Cooperativeness' },
110
+ { key: 'ec', label: 'External Consistency', sub: 'Non-refutation × Coverage' },
111
+ { key: 'rc', label: 'Retest Consistency', sub: 'Inter-session stability' },
112
+ ];
113
+ dims.forEach(function (d) {
114
+ var cell = document.createElement('div');
115
+ cell.className = 'score-cell';
116
+ cell.innerHTML =
117
+ '<div class="score-label">' + d.label + '</div>' +
118
+ '<div class="score-value">' + fmtScore(scores[d.key]) + '</div>' +
119
+ '<div class="score-sub">' + d.sub + '</div>';
120
+ gridEl.appendChild(cell);
121
+ });
122
+ }
123
+
124
+ // ===== Experience Mode =====
125
+
126
+ var expStart = document.getElementById('exp-start');
127
+ var expChat = document.getElementById('exp-chat');
128
+ var expMessages = document.getElementById('exp-messages');
129
+ var expInput = document.getElementById('exp-input');
130
+ var expSend = document.getElementById('exp-send');
131
+ var expProgress = document.getElementById('exp-progress');
132
+ var expSessionId = null;
133
+ var expLoading = false;
134
+
135
+ document.getElementById('exp-start-btn').addEventListener('click', async function () {
136
+ var name = document.getElementById('exp-name').value.trim();
137
+ var turns = parseInt(document.getElementById('exp-turns').value);
138
+ if (!name) return;
139
+
140
+ if (!API_BASE) {
141
+ alert('Demo backend is not configured. Please set demo_api_url in _config.yml.');
142
+ return;
143
+ }
144
+
145
+ expStart.style.display = 'none';
146
+ expChat.style.display = 'block';
147
+ addMessage(expMessages, 'info', 'Starting interview for ' + name + '...');
148
+
149
+ try {
150
+ var res = await fetch(API_BASE + '/api/start', {
151
+ method: 'POST',
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: JSON.stringify({ name: name, num_turns: turns })
154
+ });
155
+
156
+ if (!res.ok) throw new Error('Failed to start: ' + res.status);
157
+ var data = await res.json();
158
+
159
+ expSessionId = data.session_id;
160
+ setProgress(expProgress, data.progress);
161
+ addMessage(expMessages, 'system', data.first_question);
162
+ expSend.disabled = false;
163
+ expInput.focus();
164
+ } catch (err) {
165
+ addMessage(expMessages, 'info', 'Error: ' + err.message);
166
+ }
167
+ });
168
+
169
+ async function sendExperienceResponse() {
170
+ var text = expInput.value.trim();
171
+ if (!text || !expSessionId || expLoading) return;
172
+
173
+ expInput.value = '';
174
+ expLoading = true;
175
+ expSend.disabled = true;
176
+ addMessage(expMessages, 'user', text);
177
+ var typing = showTyping(expMessages);
178
+
179
+ try {
180
+ var res = await fetch(API_BASE + '/api/respond', {
181
+ method: 'POST',
182
+ headers: { 'Content-Type': 'application/json' },
183
+ body: JSON.stringify({
184
+ session_id: expSessionId,
185
+ response: text,
186
+ })
187
+ });
188
+
189
+ typing.remove();
190
+ if (!res.ok) throw new Error('Error: ' + res.status);
191
+ var data = await res.json();
192
+
193
+ setProgress(expProgress, data.progress);
194
+
195
+ if (data.is_complete) {
196
+ addMessage(expMessages, 'info', 'Interview complete! Calculating your consistency scores...');
197
+ expSend.disabled = true;
198
+ expInput.disabled = true;
199
+
200
+ try {
201
+ var rRes = await fetch(API_BASE + '/api/results/' + expSessionId);
202
+ if (rRes.ok) {
203
+ var results = await rRes.json();
204
+ var expResultsEl = document.getElementById('exp-results');
205
+ expResultsEl.style.display = 'block';
206
+ renderScoreGrid(
207
+ document.getElementById('exp-score-grid'),
208
+ results.results.eval_scores || {}
209
+ );
210
+ }
211
+ } catch (e) {
212
+ addMessage(expMessages, 'info', 'Results saved. Thank you for participating!');
213
+ }
214
+ return;
215
+ }
216
+
217
+ if (data.next_question) {
218
+ addMessage(expMessages, 'system', data.next_question);
219
+ }
220
+ } catch (err) {
221
+ typing.remove();
222
+ addMessage(expMessages, 'info', 'Error: ' + err.message);
223
+ } finally {
224
+ expLoading = false;
225
+ if (!expInput.disabled) {
226
+ expSend.disabled = false;
227
+ expInput.focus();
228
+ }
229
+ }
230
+ }
231
+
232
+ expSend.addEventListener('click', sendExperienceResponse);
233
+ expInput.addEventListener('keydown', function (e) {
234
+ if (e.key === 'Enter') sendExperienceResponse();
235
+ });
236
+
237
+ // ===== Agent Test Mode =====
238
+
239
+ var agentTerminal = document.getElementById('agent-terminal-body');
240
+ var agentProgress = document.getElementById('agent-progress');
241
+ var agentSessionId = null;
242
+ var agentLogIndex = 0; // track how many log lines we've fetched
243
+ var activeSubmode = 'external';
244
+
245
+ // Submode switching
246
+ document.querySelectorAll('.agent-submode-card').forEach(function (card) {
247
+ card.addEventListener('click', function () {
248
+ document.querySelectorAll('.agent-submode-card').forEach(function (c) { c.classList.remove('active'); });
249
+ card.classList.add('active');
250
+ activeSubmode = card.dataset.submode;
251
+ document.getElementById('agent-form-external').style.display = activeSubmode === 'external' ? 'flex' : 'none';
252
+ document.getElementById('agent-form-quick').style.display = activeSubmode === 'quick' ? 'flex' : 'none';
253
+ });
254
+ });
255
+
256
+ function startAgentEvaluation(payload, displayLabel) {
257
+ if (!API_BASE) {
258
+ alert('Demo backend is not configured. Please set demo_api_url in _config.yml.');
259
+ return;
260
+ }
261
+
262
+ document.getElementById('agent-submode-selector').style.display = 'none';
263
+ document.getElementById('agent-form-external').style.display = 'none';
264
+ document.getElementById('agent-form-quick').style.display = 'none';
265
+ document.getElementById('agent-log').style.display = 'block';
266
+ agentTerminal.textContent = '';
267
+ agentLogIndex = 0;
268
+
269
+ appendTerminal('$ picon.evaluate(' + displayLabel + ')\n');
270
+ agentProgress.textContent = 'Starting evaluation...';
271
+
272
+ (async function () {
273
+ try {
274
+ var res = await fetch(API_BASE + '/api/agent/start', {
275
+ method: 'POST',
276
+ headers: { 'Content-Type': 'application/json' },
277
+ body: JSON.stringify(payload)
278
+ });
279
+
280
+ if (!res.ok) throw new Error('Failed to start: ' + res.status);
281
+ var data = await res.json();
282
+ agentSessionId = data.session_id;
283
+ appendTerminal('Evaluation started. This may take several minutes...\n\n');
284
+ pollAgentProgress(data.session_id);
285
+ } catch (err) {
286
+ appendTerminal('ERROR: ' + err.message + '\n');
287
+ agentProgress.textContent = 'Error';
288
+ }
289
+ })();
290
+ }
291
+
292
+ // External Agent start
293
+ document.getElementById('agent-start-btn-external').addEventListener('click', function () {
294
+ var name = document.getElementById('ext-agent-name').value.trim();
295
+ var endpoint = document.getElementById('ext-agent-endpoint').value.trim();
296
+ var turns = document.getElementById('ext-agent-turns').value;
297
+ var sessions = document.getElementById('ext-agent-sessions').value;
298
+
299
+ if (!name) { alert('Please provide an agent name.'); return; }
300
+ if (!endpoint) { alert('Please provide your agent\'s API endpoint.'); return; }
301
+
302
+ startAgentEvaluation({
303
+ mode: 'external',
304
+ name: name,
305
+ api_base: endpoint,
306
+ num_turns: parseInt(turns),
307
+ num_sessions: parseInt(sessions),
308
+ }, name + ', endpoint=' + endpoint + ', turns=' + turns);
309
+ });
310
+
311
+ // Quick Agent start
312
+ document.getElementById('agent-start-btn-quick').addEventListener('click', function () {
313
+ var name = document.getElementById('quick-agent-name').value.trim();
314
+ var model = document.getElementById('quick-agent-model').value.trim();
315
+ var apiKey = document.getElementById('quick-agent-api-key').value.trim();
316
+ var persona = document.getElementById('quick-agent-persona').value.trim();
317
+ var turns = document.getElementById('quick-agent-turns').value;
318
+ var sessions = document.getElementById('quick-agent-sessions').value;
319
+
320
+ if (!name) { alert('Please provide an agent name.'); return; }
321
+ if (!model) { alert('Please provide a model name (e.g. gpt-4o, gemini/gemini-2.5-flash).'); return; }
322
+ if (!apiKey) { alert('Please provide an API key. This covers your agent\'s LLM inference cost only — PICon evaluation cost is on us.'); return; }
323
+ if (!persona) { alert('Please provide a persona / system prompt.'); return; }
324
+
325
+ startAgentEvaluation({
326
+ mode: 'quick',
327
+ name: name,
328
+ model: model,
329
+ api_key: apiKey,
330
+ persona: persona,
331
+ num_turns: parseInt(turns),
332
+ num_sessions: parseInt(sessions),
333
+ }, name + ', model=' + model + ', turns=' + turns);
334
+ });
335
+
336
+ function appendTerminal(text) {
337
+ agentTerminal.textContent += text;
338
+ agentTerminal.scrollTop = agentTerminal.scrollHeight;
339
+ }
340
+
341
+ async function fetchAgentLogs(sessionId) {
342
+ try {
343
+ var res = await fetch(API_BASE + '/api/agent/logs/' + sessionId + '?since=' + agentLogIndex);
344
+ if (!res.ok) return;
345
+ var data = await res.json();
346
+ if (data.lines && data.lines.length > 0) {
347
+ appendTerminal(data.lines.join('\n') + '\n');
348
+ agentLogIndex = data.total;
349
+ }
350
+ } catch (e) { /* ignore */ }
351
+ }
352
+
353
+ async function pollAgentProgress(sessionId) {
354
+ var interval = setInterval(async function () {
355
+ try {
356
+ // Fetch logs and status in parallel
357
+ await fetchAgentLogs(sessionId);
358
+
359
+ var res = await fetch(API_BASE + '/api/agent/status/' + sessionId);
360
+ if (!res.ok) { clearInterval(interval); return; }
361
+ var data = await res.json();
362
+
363
+ agentProgress.textContent =
364
+ 'Session ' + data.current_session + '/' + data.total_sessions + ' — Running...';
365
+
366
+ if (data.is_complete) {
367
+ clearInterval(interval);
368
+
369
+ // Fetch final logs
370
+ await fetchAgentLogs(sessionId);
371
+
372
+ if (data.error) {
373
+ agentProgress.textContent = 'Error';
374
+ appendTerminal('\nERROR: ' + data.error + '\n');
375
+ return;
376
+ }
377
+
378
+ agentProgress.textContent = 'Complete';
379
+ appendTerminal('\n--- Evaluation complete ---\n');
380
+
381
+ // Fetch results
382
+ var rRes = await fetch(API_BASE + '/api/agent/results/' + sessionId);
383
+ if (rRes.ok) {
384
+ var results = await rRes.json();
385
+ document.getElementById('agent-log').style.display = 'none';
386
+ document.getElementById('agent-results').style.display = 'block';
387
+ renderScoreGrid(
388
+ document.getElementById('agent-score-grid'),
389
+ results.scores || {}
390
+ );
391
+
392
+ // Add to community leaderboard
393
+ var entry = {
394
+ name: results.name || 'Agent',
395
+ type: 'community',
396
+ arch: 'Community',
397
+ turns: parseInt(document.getElementById(activeSubmode === 'external' ? 'ext-agent-turns' : 'quick-agent-turns').value),
398
+ ic: results.scores.ic || 0,
399
+ ec: results.scores.ec || 0,
400
+ rc: results.scores.rc || 0,
401
+ };
402
+ entry.area = computeArea(entry);
403
+ communityEntries.push(entry);
404
+ localStorage.setItem('picon_community', JSON.stringify(communityEntries));
405
+ renderLeaderboard();
406
+ }
407
+ }
408
+ } catch (err) {
409
+ clearInterval(interval);
410
+ appendTerminal('\nERROR: ' + err.message + '\n');
411
+ }
412
+ }, 3000);
413
+ }
414
+
415
+ function showAgentForm() {
416
+ document.getElementById('agent-submode-selector').style.display = '';
417
+ document.getElementById('agent-form-external').style.display = activeSubmode === 'external' ? 'flex' : 'none';
418
+ document.getElementById('agent-form-quick').style.display = activeSubmode === 'quick' ? 'flex' : 'none';
419
+ }
420
+
421
+ // Cancel button
422
+ var cancelBtn = document.getElementById('agent-cancel-btn');
423
+ if (cancelBtn) {
424
+ cancelBtn.addEventListener('click', async function () {
425
+ if (agentSessionId && API_BASE) {
426
+ try { await fetch(API_BASE + '/api/agent/cancel/' + agentSessionId, { method: 'DELETE' }); }
427
+ catch (e) { /* ignore */ }
428
+ }
429
+ document.getElementById('agent-log').style.display = 'none';
430
+ showAgentForm();
431
+ agentTerminal.textContent = '';
432
+ agentLogIndex = 0;
433
+ });
434
+ }
435
+
436
+ // Retry button
437
+ var retryBtn = document.getElementById('agent-retry-btn');
438
+ if (retryBtn) {
439
+ retryBtn.addEventListener('click', function () {
440
+ document.getElementById('agent-results').style.display = 'none';
441
+ showAgentForm();
442
+ agentTerminal.textContent = '';
443
+ agentLogIndex = 0;
444
+ });
445
+ }
446
+
447
+ // ===== Leaderboard =====
448
+
449
+ var currentSort = 'area';
450
+ var currentFilter = 'all';
451
+ var currentTurnsFilter = 'all';
452
+
453
+ function getAllEntries() {
454
+ return BASELINES.concat(communityEntries);
455
+ }
456
+
457
+ function renderLeaderboard() {
458
+ var entries = getAllEntries();
459
+
460
+ // Filter by type
461
+ if (currentFilter !== 'all') {
462
+ entries = entries.filter(function (d) { return d.type === currentFilter; });
463
+ }
464
+
465
+ // Filter by turns
466
+ if (currentTurnsFilter !== 'all') {
467
+ var turnsVal = parseInt(currentTurnsFilter);
468
+ entries = entries.filter(function (d) { return d.turns === turnsVal; });
469
+ }
470
+
471
+ // Sort descending
472
+ entries.sort(function (a, b) { return (b[currentSort] || 0) - (a[currentSort] || 0); });
473
+
474
+ var tbody = document.getElementById('leaderboard-body');
475
+ tbody.innerHTML = '';
476
+
477
+ entries.forEach(function (d, i) {
478
+ var rank = i + 1;
479
+ var tr = document.createElement('tr');
480
+ if (d.name === 'Human') tr.className = 'lb-human-row';
481
+
482
+ var archClass = d.arch.toLowerCase().replace(/[^a-z]/g, '');
483
+ if (archClass === 'finetuned') archClass = 'finetuned';
484
+ else if (archClass === 'ragbased' || archClass === 'rag') archClass = 'rag';
485
+
486
+ var badgeClass = d.type === 'community' ? 'community' : archClass;
487
+
488
+ var rankClass = '';
489
+ if (rank === 1) rankClass = 'lb-rank-1';
490
+ else if (rank === 2) rankClass = 'lb-rank-2';
491
+ else if (rank === 3) rankClass = 'lb-rank-3';
492
+
493
+ tr.innerHTML =
494
+ '<td class="lb-rank ' + rankClass + '">' + rank + '</td>' +
495
+ '<td class="lb-name">' + d.name + '</td>' +
496
+ '<td class="lb-type"><span class="lb-badge ' + badgeClass + '">' + d.arch + '</span></td>' +
497
+ '<td class="lb-turns">' + (d.turns || '—') + '</td>' +
498
+ '<td class="lb-score">' + fmtScore(d.ic) + '</td>' +
499
+ '<td class="lb-score">' + fmtScore(d.ec) + '</td>' +
500
+ '<td class="lb-score">' + fmtScore(d.rc) + '</td>' +
501
+ '<td class="lb-score"><strong>' + fmtScore(d.area) + '</strong></td>';
502
+
503
+ tbody.appendChild(tr);
504
+ });
505
+
506
+ // Update active header
507
+ document.querySelectorAll('.leaderboard-table th.sortable').forEach(function (th) {
508
+ th.classList.toggle('active', th.dataset.col === currentSort);
509
+ });
510
+ }
511
+
512
+ // Sort handlers
513
+ document.querySelectorAll('.leaderboard-table th.sortable').forEach(function (th) {
514
+ th.addEventListener('click', function () {
515
+ currentSort = th.dataset.col;
516
+ document.getElementById('lb-sort').value = currentSort;
517
+ renderLeaderboard();
518
+ });
519
+ });
520
+
521
+ document.getElementById('lb-sort').addEventListener('change', function () {
522
+ currentSort = this.value;
523
+ renderLeaderboard();
524
+ });
525
+
526
+ document.getElementById('lb-type-filter').addEventListener('change', function () {
527
+ currentFilter = this.value;
528
+ renderLeaderboard();
529
+ });
530
+
531
+ document.getElementById('lb-turns-filter').addEventListener('change', function () {
532
+ currentTurnsFilter = this.value;
533
+ renderLeaderboard();
534
+ });
535
+
536
+ // Initial render
537
+ renderLeaderboard();
538
+
539
+ })();
index.html ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Demo | PICon</title>
7
+ <meta name="description" content="Try PICon — experience the interrogation yourself or test your own persona agent">
8
+ <meta name="demo-api-url" content="https://picongithubio-production.up.railway.app">
9
+ <link rel="stylesheet" href="/assets/css/style.css">
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
13
+ <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
14
+ <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js"
15
+ onload="renderMathInElement(document.body, {delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}]});"></script>
16
+ </head>
17
+ <body>
18
+
19
+ <nav class="sidebar" id="sidebar">
20
+ <div class="sidebar-header">
21
+ <a href="https://kaist-edlab.github.io/picon/" class="sidebar-logo">PICon</a>
22
+ <button class="sidebar-toggle" id="sidebar-toggle" aria-label="Toggle navigation">
23
+ <span></span><span></span><span></span>
24
+ </button>
25
+ </div>
26
+ <ul class="sidebar-nav" id="sidebar-nav">
27
+ <li class="nav-item">
28
+ <a href="https://kaist-edlab.github.io/picon/" class="nav-link">Home</a>
29
+ </li>
30
+ <li class="nav-item">
31
+ <a href="https://kaist-edlab.github.io/picon/research/" class="nav-link">Research</a>
32
+ </li>
33
+ <li class="nav-item active">
34
+ <a href="#" class="nav-link">Demo</a>
35
+ </li>
36
+ <li class="nav-item">
37
+ <a href="https://kaist-edlab.github.io/picon/contact/" class="nav-link">Contact</a>
38
+ </li>
39
+ </ul>
40
+ <div class="sidebar-footer">
41
+ <a href="https://github.com/willystumblr/picon" class="sidebar-link" target="_blank">GitHub</a>
42
+ <a href="https://arxiv.org/abs/2603.25620" class="sidebar-link" target="_blank">arXiv</a>
43
+ </div>
44
+ </nav>
45
+
46
+ <script>
47
+ const toggle = document.getElementById('sidebar-toggle');
48
+ const sidebar = document.getElementById('sidebar');
49
+ toggle.addEventListener('click', () => {
50
+ sidebar.classList.toggle('open');
51
+ });
52
+ </script>
53
+
54
+ <div class="page-wrap">
55
+ <main class="main-content">
56
+
57
+ <div class="container-wide">
58
+ <h1>Demo</h1>
59
+ <p class="subtitle">Experience the PICon interrogation, test your own persona agent, or browse the leaderboard</p>
60
+
61
+ <!-- Mode Tabs -->
62
+ <div class="demo-tabs">
63
+ <button class="demo-tab active" data-tab="experience">Experience Mode</button>
64
+ <button class="demo-tab" data-tab="agent-test">Agent Test</button>
65
+ <button class="demo-tab" data-tab="leaderboard">Leaderboard</button>
66
+ </div>
67
+
68
+ <!-- ===== Experience Mode ===== -->
69
+ <div class="demo-panel active" id="panel-experience">
70
+ <div class="demo-description">
71
+ <strong>Experience the interrogation yourself.</strong>
72
+ You play as a persona being interrogated by PICon's multi-turn questioning system.
73
+ Answer as yourself &mdash; PICon will probe your responses with logically chained follow-ups
74
+ and verify factual claims in real time. At the end, you'll see your consistency scores across
75
+ all three dimensions.
76
+ </div>
77
+
78
+ <!-- Name entry -->
79
+ <div id="exp-start" class="agent-form">
80
+ <div class="form-row">
81
+ <div class="form-group" style="flex:2;">
82
+ <label for="exp-name">Your name</label>
83
+ <input type="text" id="exp-name" placeholder="Enter your name to begin">
84
+ </div>
85
+ <div class="form-group" style="flex:1;">
86
+ <label for="exp-turns">Turns</label>
87
+ <select id="exp-turns">
88
+ <option value="30">30 turns (quick)</option>
89
+ <option value="50" selected>50 turns (standard)</option>
90
+ <option value="75">75 turns (thorough)</option>
91
+ </select>
92
+ </div>
93
+ </div>
94
+ <button class="btn btn-primary" id="exp-start-btn">Start Interview</button>
95
+ </div>
96
+
97
+ <!-- Chat -->
98
+ <div id="exp-chat" style="display:none;">
99
+ <div class="chat-container">
100
+ <div class="chat-header">
101
+ <h4>PICon Interview</h4>
102
+ <span class="chat-progress" id="exp-progress"></span>
103
+ </div>
104
+ <div class="chat-messages" id="exp-messages"></div>
105
+ <div class="chat-input">
106
+ <input type="text" id="exp-input" placeholder="Type your response..." autocomplete="off">
107
+ <button id="exp-send" disabled>Send</button>
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ <!-- Results card (shown after completion) -->
113
+ <div id="exp-results" style="display:none;">
114
+ <div class="results-card">
115
+ <h3>Your Consistency Report</h3>
116
+ <div class="score-grid" id="exp-score-grid"></div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- ===== Agent Test Mode ===== -->
122
+ <div class="demo-panel" id="panel-agent-test">
123
+ <div class="demo-description">
124
+ <strong>Test your own persona agent.</strong>
125
+ PICon will run the full interrogation pipeline and return a detailed consistency report.
126
+ Results are automatically added to the leaderboard.
127
+ </div>
128
+
129
+ <!-- Submode selector -->
130
+ <div class="agent-submode-selector" id="agent-submode-selector">
131
+ <button class="agent-submode-card active" data-submode="external">
132
+ <span class="submode-icon">🔗</span>
133
+ <span class="submode-title">Connect External Agent</span>
134
+ <span class="submode-desc">Already have an agent hosted elsewhere? Just provide its endpoint URL — no API key or model config needed.</span>
135
+ </button>
136
+ <button class="agent-submode-card" data-submode="quick">
137
+ <span class="submode-icon">⚡</span>
138
+ <span class="submode-title">Quick Agent Setup</span>
139
+ <span class="submode-desc">Pick an LLM, write a persona prompt, and PICon will build and evaluate the agent for you.</span>
140
+ </button>
141
+ </div>
142
+
143
+ <!-- External Agent form -->
144
+ <div class="agent-form" id="agent-form-external">
145
+ <div class="form-group">
146
+ <label for="ext-agent-name">Agent Name <span class="label-hint">(required)</span></label>
147
+ <input type="text" id="ext-agent-name" placeholder="e.g. MyPersonaBot v2">
148
+ </div>
149
+ <div class="form-group">
150
+ <label for="ext-agent-endpoint">Agent API Endpoint <span class="label-hint">(required — OpenAI chat-completions compatible)</span></label>
151
+ <input type="url" id="ext-agent-endpoint" placeholder="e.g. https://your-server.com/v1">
152
+ </div>
153
+ <div class="form-row">
154
+ <div class="form-group" style="flex:1;">
155
+ <label for="ext-agent-turns">Interrogation Turns</label>
156
+ <select id="ext-agent-turns">
157
+ <option value="30">30 turns (quick)</option>
158
+ <option value="50" selected>50 turns (standard)</option>
159
+ <option value="75">75 turns (thorough)</option>
160
+ </select>
161
+ </div>
162
+ <div class="form-group" style="flex:1;">
163
+ <label for="ext-agent-sessions">Sessions</label>
164
+ <select id="ext-agent-sessions">
165
+ <option value="1" selected>1 session</option>
166
+ <option value="2">2 sessions</option>
167
+ <option value="3">3 sessions</option>
168
+ </select>
169
+ </div>
170
+ </div>
171
+ <button class="btn btn-accent" id="agent-start-btn-external">Run PICon Evaluation</button>
172
+ </div>
173
+
174
+ <!-- Quick Agent form -->
175
+ <div class="agent-form" id="agent-form-quick" style="display:none;">
176
+ <div class="form-row">
177
+ <div class="form-group" style="flex:1;">
178
+ <label for="quick-agent-name">Agent Name <span class="label-hint">(required)</span></label>
179
+ <input type="text" id="quick-agent-name" placeholder="e.g. MyPersonaBot v2">
180
+ </div>
181
+ <div class="form-group" style="flex:1;">
182
+ <label for="quick-agent-model">Model <span class="label-hint">(required)</span></label>
183
+ <input type="text" id="quick-agent-model" placeholder="e.g. gpt-4o, gemini/gemini-2.5-flash">
184
+ </div>
185
+ </div>
186
+ <div class="form-group">
187
+ <label for="quick-agent-api-key">API Key <span class="label-hint">(required — covers your agent's LLM inference cost only)</span></label>
188
+ <input type="password" id="quick-agent-api-key" placeholder="Your API key (e.g. OpenAI, Gemini)">
189
+ </div>
190
+ <div class="form-group">
191
+ <label for="quick-agent-persona">Persona / System Prompt <span class="label-hint">(required)</span></label>
192
+ <textarea id="quick-agent-persona" rows="5" placeholder="Paste the system prompt that defines your agent's persona. This is sent as the system message and also used by PICon for evaluation."></textarea>
193
+ </div>
194
+ <div class="form-row">
195
+ <div class="form-group" style="flex:1;">
196
+ <label for="quick-agent-turns">Interrogation Turns</label>
197
+ <select id="quick-agent-turns">
198
+ <option value="30">30 turns (quick)</option>
199
+ <option value="50" selected>50 turns (standard)</option>
200
+ <option value="75">75 turns (thorough)</option>
201
+ </select>
202
+ </div>
203
+ <div class="form-group" style="flex:1;">
204
+ <label for="quick-agent-sessions">Sessions</label>
205
+ <select id="quick-agent-sessions">
206
+ <option value="1" selected>1 session</option>
207
+ <option value="2">2 sessions</option>
208
+ <option value="3">3 sessions</option>
209
+ </select>
210
+ </div>
211
+ </div>
212
+ <button class="btn btn-accent" id="agent-start-btn-quick">Run PICon Evaluation</button>
213
+ </div>
214
+
215
+ <!-- Agent test live log -->
216
+ <div id="agent-log" style="display:none;">
217
+ <div class="agent-terminal">
218
+ <div class="terminal-header">
219
+ <span class="terminal-title">PICon Evaluation</span>
220
+ <span class="chat-progress" id="agent-progress">Initializing...</span>
221
+ </div>
222
+ <pre class="terminal-body" id="agent-terminal-body"></pre>
223
+ </div>
224
+ <button class="btn btn-secondary" id="agent-cancel-btn" style="margin-top:0.75rem;">Cancel</button>
225
+ </div>
226
+
227
+ <!-- Agent results -->
228
+ <div id="agent-results" style="display:none;">
229
+ <div class="results-card">
230
+ <h3>Evaluation Report</h3>
231
+ <div class="score-grid" id="agent-score-grid"></div>
232
+ <p class="results-note">Your agent has been added to the leaderboard.</p>
233
+ </div>
234
+ <button class="btn btn-primary" id="agent-retry-btn" style="margin-top:0.75rem;">Test Another Agent</button>
235
+ </div>
236
+ </div>
237
+
238
+ <!-- ===== Leaderboard ===== -->
239
+ <div class="demo-panel" id="panel-leaderboard">
240
+ <div class="demo-description">
241
+ <strong>PICon Consistency Leaderboard.</strong>
242
+ Baseline scores from the paper's evaluation targets, plus community-submitted agents.
243
+ All baselines are evaluated under the same interrogation protocol (50 turns, 2 sessions).
244
+ </div>
245
+
246
+ <div class="leaderboard-controls">
247
+ <div class="leaderboard-filter">
248
+ <label>Sort by</label>
249
+ <select id="lb-sort">
250
+ <option value="area" selected>Overall (Area)</option>
251
+ <option value="ic">Internal Consistency</option>
252
+ <option value="ec">External Consistency</option>
253
+ <option value="rc">Retest Consistency</option>
254
+ </select>
255
+ </div>
256
+ <div class="leaderboard-filter">
257
+ <label>Type</label>
258
+ <select id="lb-type-filter">
259
+ <option value="all" selected>All</option>
260
+ <option value="baseline">Baseline (Paper)</option>
261
+ <option value="community">Community</option>
262
+ </select>
263
+ </div>
264
+ <div class="leaderboard-filter">
265
+ <label>Turns</label>
266
+ <select id="lb-turns-filter">
267
+ <option value="all" selected>All</option>
268
+ <option value="75">75</option>
269
+ <option value="50">50</option>
270
+ <option value="30">30</option>
271
+ </select>
272
+ </div>
273
+ </div>
274
+
275
+ <div class="leaderboard-table-wrap">
276
+ <table class="leaderboard-table" id="leaderboard-table">
277
+ <thead>
278
+ <tr>
279
+ <th class="lb-rank">#</th>
280
+ <th class="lb-name">Agent</th>
281
+ <th class="lb-type">Type</th>
282
+ <th class="lb-turns">Turns</th>
283
+ <th class="lb-score sortable" data-col="ic">IC</th>
284
+ <th class="lb-score sortable" data-col="ec">EC</th>
285
+ <th class="lb-score sortable" data-col="rc">RC</th>
286
+ <th class="lb-score sortable active" data-col="area">Area</th>
287
+ </tr>
288
+ </thead>
289
+ <tbody id="leaderboard-body"></tbody>
290
+ </table>
291
+ </div>
292
+
293
+ <p class="leaderboard-footnote">
294
+ <strong>IC</strong> = Internal Consistency (harmonic mean of non-contradiction &amp; cooperativeness).
295
+ <strong>EC</strong> = External Consistency (harmonic mean of non-refutation &amp; coverage).
296
+ <strong>RC</strong> = Retest Consistency (intra-session stability).
297
+ <strong>Area</strong> = normalized triangle area on the IC&ndash;EC&ndash;RC radar chart.
298
+ </p>
299
+ </div>
300
+ </div>
301
+
302
+ <!-- ===== Use via Python ===== -->
303
+ <div class="container" style="margin-top: 3rem;">
304
+ <div class="python-api-section">
305
+ <h2>Use via Python</h2>
306
+ <p>
307
+ Install the <code>picon</code> package to run evaluations programmatically &mdash;
308
+ no web UI needed.
309
+ </p>
310
+
311
+ <div class="code-block">
312
+ <div class="code-header">Installation</div>
313
+ <pre><code>pip install picon</code></pre>
314
+ </div>
315
+
316
+ <div class="code-block">
317
+ <div class="code-header">Quick Start</div>
318
+ <pre><code>import picon
319
+
320
+ result = picon.run(
321
+ persona="You are a 35-year-old software engineer named John...",
322
+ name="John",
323
+ model="gemini/gemini-2.5-flash",
324
+ num_turns=20,
325
+ num_sessions=2,
326
+ do_eval=True,
327
+ )
328
+
329
+ print(result.eval_scores)
330
+ # {
331
+ # "internal_harmonic_mean": 0.85,
332
+ # "internal_responsiveness": 0.90,
333
+ # "internal_consistency": 0.81,
334
+ # "external_wilson": 0.72,
335
+ # "inter_session_stability": 0.88,
336
+ # "intra_session_stability": 0.91,
337
+ # }
338
+ result.save("results/john.json")</code></pre>
339
+ </div>
340
+
341
+ <div class="code-block">
342
+ <div class="code-header">External Agent (Blackbox API)</div>
343
+ <pre><code># Only the endpoint URL is needed — persona is baked into the agent
344
+ result = picon.run(
345
+ api_base="http://your-server.com/v1", # OpenAI-compatible endpoint
346
+ num_turns=30,
347
+ )</code></pre>
348
+ </div>
349
+
350
+ <div class="code-block">
351
+ <div class="code-header">Evaluate Existing Results</div>
352
+ <pre><code>scores = picon.evaluate("results/john.json")
353
+ print(scores)</code></pre>
354
+ </div>
355
+
356
+ <p style="margin-top:1rem; font-size:0.875rem; color:var(--color-text-muted);">
357
+ See the <a href="https://github.com/willystumblr/picon">GitHub repository</a>
358
+ for full documentation, CLI usage, and advanced configuration.
359
+ </p>
360
+ </div>
361
+ </div>
362
+
363
+ </main>
364
+
365
+ <footer class="site-footer">
366
+ <div class="container">
367
+ <p>&copy; 2026 PICon Authors. Built with Jekyll &amp; GitHub Pages.</p>
368
+ </div>
369
+ </footer>
370
+ </div>
371
+
372
+ <script src="/assets/js/demo.js"></script>
373
+ </body>
374
+ </html>
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastapi
2
+ uvicorn[standard]