Spaces:
Running
Fix Control entry flow, hide persona editor, polish dashboard
Browse filesOperators opening the app in Reachy Mini Control landed on the persona/
API console (raw prompt + tools.txt editing) instead of the dashboard.
- index.html now redirects straight to /dashboard so the dashboard is
the greeting screen
- Move the persona/prompt/tools studio into personality.html (markup
unchanged so main.js keeps driving it), reachable from a new
"Advanced" button in the dashboard header
- Dashboard polish: de-duplicate State (top strip now shows Known
Guests instead of a second State pill), collapse the developer-only
"Last Face Event -> Model" block, wrap the header for Control's narrow
panel, warmer empty states
- Fix header face confidence rendering: it multiplied an LBPH distance
by 100 and showed nonsense like "5037%"; now shows the raw LBPH value,
consistent with the rest of the UI
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@@ -289,6 +289,7 @@
|
|
| 289 |
|
| 290 |
header {
|
| 291 |
display: flex; align-items: center; gap: 12px;
|
|
|
|
| 292 |
padding: 14px 24px; border-bottom: 1px solid var(--border);
|
| 293 |
background: var(--panel);
|
| 294 |
}
|
|
@@ -398,6 +399,16 @@
|
|
| 398 |
letter-spacing: 0.08em; color: var(--muted);
|
| 399 |
padding: 0 8px 6px; text-align: center;
|
| 400 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
.event-compact-list { list-style: none; display: flex; flex-direction: column; gap: 4px; padding: 0 8px 8px; }
|
| 402 |
.event-compact-item { padding: 4px 0; border-top: 1px solid rgba(255,255,255,0.05); }
|
| 403 |
.event-compact-key {
|
|
@@ -948,8 +959,17 @@
|
|
| 948 |
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';" />
|
| 949 |
<span class="brand-logo-fallback" style="font-size:1.4rem; display:none;">🤖</span>
|
| 950 |
<h1>MethdAI Receptionist</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 951 |
<button class="btn icon-only" id="btn-theme-toggle"
|
| 952 |
-
style="margin-left:
|
| 953 |
title="Toggle light / dark theme">
|
| 954 |
<span id="theme-toggle-icon">🌙</span>
|
| 955 |
</button>
|
|
@@ -973,7 +993,7 @@
|
|
| 973 |
<button id="volume-mute" style="background:none; border:none; cursor:pointer;
|
| 974 |
color:var(--text); font-size:0.95rem; padding:0; line-height:1;">🔊</button>
|
| 975 |
<input type="range" id="volume-slider" min="0" max="100" value="80"
|
| 976 |
-
style="width:
|
| 977 |
<span id="volume-label" style="min-width:32px; text-align:right;">—</span>
|
| 978 |
</div>
|
| 979 |
|
|
@@ -1020,12 +1040,14 @@
|
|
| 1020 |
<span class="stat-value dim" id="stat-last-visit">—</span>
|
| 1021 |
<span class="stat-sub" id="stat-last-visit-time">—</span>
|
| 1022 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1023 |
<div class="stat-card">
|
| 1024 |
-
<span class="stat-label">
|
| 1025 |
-
<span class="stat-value
|
| 1026 |
-
|
| 1027 |
-
</span>
|
| 1028 |
-
<span class="stat-sub" id="stat-current-visitor">No active visitor</span>
|
| 1029 |
</div>
|
| 1030 |
</div>
|
| 1031 |
|
|
@@ -1052,8 +1074,8 @@
|
|
| 1052 |
<div class="best-face-name unknown" id="best-face-name">—</div>
|
| 1053 |
<div class="best-face-meta" id="best-face-meta">No detection</div>
|
| 1054 |
|
| 1055 |
-
<
|
| 1056 |
-
<
|
| 1057 |
<ul class="event-compact-list">
|
| 1058 |
<li class="event-compact-item">
|
| 1059 |
<div class="event-compact-key">Status</div>
|
|
@@ -1068,7 +1090,7 @@
|
|
| 1068 |
<div class="event-compact-val" id="event-previous">—</div>
|
| 1069 |
</li>
|
| 1070 |
</ul>
|
| 1071 |
-
</
|
| 1072 |
</div>
|
| 1073 |
</div>
|
| 1074 |
</div>
|
|
@@ -1142,7 +1164,7 @@
|
|
| 1142 |
</div>
|
| 1143 |
<div class="panel-body">
|
| 1144 |
<ul class="guest-list" id="guest-list">
|
| 1145 |
-
<li class="empty">No
|
| 1146 |
</ul>
|
| 1147 |
</div>
|
| 1148 |
</div>
|
|
@@ -1155,7 +1177,7 @@
|
|
| 1155 |
</div>
|
| 1156 |
<div class="panel-body">
|
| 1157 |
<ul class="outbox-list" id="outbox-list">
|
| 1158 |
-
<li class="empty">No
|
| 1159 |
</ul>
|
| 1160 |
</div>
|
| 1161 |
</div>
|
|
@@ -1350,7 +1372,11 @@
|
|
| 1350 |
if (d.is_known) {
|
| 1351 |
dot.className = 'dot known';
|
| 1352 |
label.textContent = d.name;
|
| 1353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1354 |
overlay.textContent = `✓ Known: ${d.name}`;
|
| 1355 |
overlay.className = 'video-overlay known';
|
| 1356 |
} else {
|
|
@@ -1409,7 +1435,7 @@
|
|
| 1409 |
const r = await fetch(`${BASE}/api/calendar`);
|
| 1410 |
const appts = await r.json();
|
| 1411 |
const ul = document.getElementById('calendar-list');
|
| 1412 |
-
if (!appts.length) { ul.innerHTML =
|
| 1413 |
ul.innerHTML = appts.map(a => `
|
| 1414 |
<li class="appt-item">
|
| 1415 |
<span class="appt-time">${a.time}</span>
|
|
@@ -1429,7 +1455,9 @@
|
|
| 1429 |
const guests = await r.json();
|
| 1430 |
const ul = document.getElementById('guest-list');
|
| 1431 |
document.getElementById('guest-count').textContent = guests.length ? `${guests.length}` : '';
|
| 1432 |
-
|
|
|
|
|
|
|
| 1433 |
ul.innerHTML = guests.map(g => `
|
| 1434 |
<li class="guest-item">
|
| 1435 |
<div class="guest-avatar">
|
|
@@ -1464,7 +1492,7 @@
|
|
| 1464 |
const ul = document.getElementById('outbox-list');
|
| 1465 |
document.getElementById('outbox-count').textContent = items.length ? `${items.length}` : '';
|
| 1466 |
if (!items.length) {
|
| 1467 |
-
ul.innerHTML = '<li class="empty">No
|
| 1468 |
_lastOutboxCount = 0;
|
| 1469 |
return;
|
| 1470 |
}
|
|
@@ -1545,13 +1573,8 @@
|
|
| 1545 |
lvTimeEl.textContent = 'No visits yet';
|
| 1546 |
}
|
| 1547 |
|
| 1548 |
-
|
| 1549 |
-
|
| 1550 |
-
stateEl.textContent = state.replaceAll('_', ' ');
|
| 1551 |
-
stateEl.className = `session-state-pill ${state}`;
|
| 1552 |
-
|
| 1553 |
-
const visEl = document.getElementById('stat-current-visitor');
|
| 1554 |
-
visEl.textContent = d.current_visitor ? `Visitor: ${d.current_visitor}` : 'No active visitor';
|
| 1555 |
} catch(e) {}
|
| 1556 |
}
|
| 1557 |
|
|
|
|
| 289 |
|
| 290 |
header {
|
| 291 |
display: flex; align-items: center; gap: 12px;
|
| 292 |
+
flex-wrap: wrap; row-gap: 10px; /* wrap cleanly in Control's narrow panel */
|
| 293 |
padding: 14px 24px; border-bottom: 1px solid var(--border);
|
| 294 |
background: var(--panel);
|
| 295 |
}
|
|
|
|
| 399 |
letter-spacing: 0.08em; color: var(--muted);
|
| 400 |
padding: 0 8px 6px; text-align: center;
|
| 401 |
}
|
| 402 |
+
/* Collapsible wrapper — developer detail, closed by default so the
|
| 403 |
+
camera panel reads clean during a demo. */
|
| 404 |
+
details.best-face-events-panel > summary {
|
| 405 |
+
cursor: pointer; list-style: none; user-select: none;
|
| 406 |
+
}
|
| 407 |
+
details.best-face-events-panel > summary::-webkit-details-marker { display: none; }
|
| 408 |
+
details.best-face-events-panel > summary::after {
|
| 409 |
+
content: ' ▸'; color: var(--muted); font-size: 0.7rem;
|
| 410 |
+
}
|
| 411 |
+
details.best-face-events-panel[open] > summary::after { content: ' ▾'; }
|
| 412 |
.event-compact-list { list-style: none; display: flex; flex-direction: column; gap: 4px; padding: 0 8px 8px; }
|
| 413 |
.event-compact-item { padding: 4px 0; border-top: 1px solid rgba(255,255,255,0.05); }
|
| 414 |
.event-compact-key {
|
|
|
|
| 959 |
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';" />
|
| 960 |
<span class="brand-logo-fallback" style="font-size:1.4rem; display:none;">🤖</span>
|
| 961 |
<h1>MethdAI Receptionist</h1>
|
| 962 |
+
<!-- Advanced / Developer: opens the persona + prompt + tools studio.
|
| 963 |
+
Kept off the default flow (operators land on this dashboard, not the
|
| 964 |
+
studio); this is the deliberate escape hatch for editing the robot's
|
| 965 |
+
personality when you actually need to. -->
|
| 966 |
+
<a class="btn" id="btn-advanced" href="/static/personality.html"
|
| 967 |
+
style="margin-left:auto; text-decoration:none;"
|
| 968 |
+
title="Developer: edit the robot's persona, prompt & enabled tools">
|
| 969 |
+
🧑💻 Advanced
|
| 970 |
+
</a>
|
| 971 |
<button class="btn icon-only" id="btn-theme-toggle"
|
| 972 |
+
style="margin-left:8px"
|
| 973 |
title="Toggle light / dark theme">
|
| 974 |
<span id="theme-toggle-icon">🌙</span>
|
| 975 |
</button>
|
|
|
|
| 993 |
<button id="volume-mute" style="background:none; border:none; cursor:pointer;
|
| 994 |
color:var(--text); font-size:0.95rem; padding:0; line-height:1;">🔊</button>
|
| 995 |
<input type="range" id="volume-slider" min="0" max="100" value="80"
|
| 996 |
+
style="width:72px; accent-color:var(--accent);" />
|
| 997 |
<span id="volume-label" style="min-width:32px; text-align:right;">—</span>
|
| 998 |
</div>
|
| 999 |
|
|
|
|
| 1040 |
<span class="stat-value dim" id="stat-last-visit">—</span>
|
| 1041 |
<span class="stat-sub" id="stat-last-visit-time">—</span>
|
| 1042 |
</div>
|
| 1043 |
+
<!-- Known Guests KPI. Replaced the old "Current State" card here — that
|
| 1044 |
+
value was a duplicate of the State pill in the Active Session panel
|
| 1045 |
+
below. The authoritative live state (with the journey trail + LLM
|
| 1046 |
+
cue) lives there; this slot now surfaces a non-duplicative metric. -->
|
| 1047 |
<div class="stat-card">
|
| 1048 |
+
<span class="stat-label">Known Guests</span>
|
| 1049 |
+
<span class="stat-value" id="stat-known-guests">—</span>
|
| 1050 |
+
<span class="stat-sub">Registered faces</span>
|
|
|
|
|
|
|
| 1051 |
</div>
|
| 1052 |
</div>
|
| 1053 |
|
|
|
|
| 1074 |
<div class="best-face-name unknown" id="best-face-name">—</div>
|
| 1075 |
<div class="best-face-meta" id="best-face-meta">No detection</div>
|
| 1076 |
|
| 1077 |
+
<details class="best-face-events-panel">
|
| 1078 |
+
<summary class="event-title">🧠 Last Face Event → Model</summary>
|
| 1079 |
<ul class="event-compact-list">
|
| 1080 |
<li class="event-compact-item">
|
| 1081 |
<div class="event-compact-key">Status</div>
|
|
|
|
| 1090 |
<div class="event-compact-val" id="event-previous">—</div>
|
| 1091 |
</li>
|
| 1092 |
</ul>
|
| 1093 |
+
</details>
|
| 1094 |
</div>
|
| 1095 |
</div>
|
| 1096 |
</div>
|
|
|
|
| 1164 |
</div>
|
| 1165 |
<div class="panel-body">
|
| 1166 |
<ul class="guest-list" id="guest-list">
|
| 1167 |
+
<li class="empty">No visitors registered yet — new faces appear here after they check in.</li>
|
| 1168 |
</ul>
|
| 1169 |
</div>
|
| 1170 |
</div>
|
|
|
|
| 1177 |
</div>
|
| 1178 |
<div class="panel-body">
|
| 1179 |
<ul class="outbox-list" id="outbox-list">
|
| 1180 |
+
<li class="empty">No host notifications yet — emails to hosts will appear here.</li>
|
| 1181 |
</ul>
|
| 1182 |
</div>
|
| 1183 |
</div>
|
|
|
|
| 1372 |
if (d.is_known) {
|
| 1373 |
dot.className = 'dot known';
|
| 1374 |
label.textContent = d.name;
|
| 1375 |
+
// `confidence` is an LBPH *distance* (lower = better match), not a
|
| 1376 |
+
// 0–1 probability — multiplying by 100 produced nonsense like
|
| 1377 |
+
// "5037%" in the header. Show the raw LBPH value, consistent with
|
| 1378 |
+
// the Best-Face panel and the session cue below.
|
| 1379 |
+
conf.textContent = `LBPH ${(Number(d.confidence) || 0).toFixed(1)}`;
|
| 1380 |
overlay.textContent = `✓ Known: ${d.name}`;
|
| 1381 |
overlay.className = 'video-overlay known';
|
| 1382 |
} else {
|
|
|
|
| 1435 |
const r = await fetch(`${BASE}/api/calendar`);
|
| 1436 |
const appts = await r.json();
|
| 1437 |
const ul = document.getElementById('calendar-list');
|
| 1438 |
+
if (!appts.length) { ul.innerHTML = "<li class=\"empty\">No appointments on today's calendar.</li>"; return; }
|
| 1439 |
ul.innerHTML = appts.map(a => `
|
| 1440 |
<li class="appt-item">
|
| 1441 |
<span class="appt-time">${a.time}</span>
|
|
|
|
| 1455 |
const guests = await r.json();
|
| 1456 |
const ul = document.getElementById('guest-list');
|
| 1457 |
document.getElementById('guest-count').textContent = guests.length ? `${guests.length}` : '';
|
| 1458 |
+
const kg = document.getElementById('stat-known-guests');
|
| 1459 |
+
if (kg) kg.textContent = `${guests.length}`;
|
| 1460 |
+
if (!guests.length) { ul.innerHTML = '<li class="empty">No visitors registered yet — new faces appear here after they check in.</li>'; return; }
|
| 1461 |
ul.innerHTML = guests.map(g => `
|
| 1462 |
<li class="guest-item">
|
| 1463 |
<div class="guest-avatar">
|
|
|
|
| 1492 |
const ul = document.getElementById('outbox-list');
|
| 1493 |
document.getElementById('outbox-count').textContent = items.length ? `${items.length}` : '';
|
| 1494 |
if (!items.length) {
|
| 1495 |
+
ul.innerHTML = '<li class="empty">No host notifications yet — emails to hosts will appear here.</li>';
|
| 1496 |
_lastOutboxCount = 0;
|
| 1497 |
return;
|
| 1498 |
}
|
|
|
|
| 1573 |
lvTimeEl.textContent = 'No visits yet';
|
| 1574 |
}
|
| 1575 |
|
| 1576 |
+
// Current state / visitor intentionally NOT rendered here anymore —
|
| 1577 |
+
// they duplicated the Active Session panel. See the Known Guests card.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1578 |
} catch(e) {}
|
| 1579 |
}
|
| 1580 |
|
|
@@ -3,118 +3,28 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
-
<title>
|
| 7 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
-
<
|
| 11 |
-
<div id="loading" class="loading">
|
| 12 |
-
<div class="spinner"></div>
|
| 13 |
-
<p>Loading…</p>
|
| 14 |
-
</div>
|
| 15 |
-
<div class="container">
|
| 16 |
-
<div class="dashboard-banner" role="navigation" aria-label="Dashboard quick link">
|
| 17 |
-
<a href="/dashboard">Open Receptionist Dashboard</a>
|
| 18 |
-
</div>
|
| 19 |
-
|
| 20 |
-
<header class="hero">
|
| 21 |
-
<div class="pill">Headless control</div>
|
| 22 |
-
<h1>Reachy Mini Conversation</h1>
|
| 23 |
-
<p class="subtitle">Configure your OpenAI key and tweak personalities without the full UI.</p>
|
| 24 |
-
</header>
|
| 25 |
-
|
| 26 |
-
<div id="configured" class="panel hidden">
|
| 27 |
-
<div class="panel-heading">
|
| 28 |
-
<div>
|
| 29 |
-
<p class="eyebrow">Credentials</p>
|
| 30 |
-
<h2>API key ready</h2>
|
| 31 |
-
</div>
|
| 32 |
-
<span class="chip chip-ok">Connected</span>
|
| 33 |
-
</div>
|
| 34 |
-
<p class="muted">OpenAI API key is already configured. You can jump straight to personalities.</p>
|
| 35 |
-
<button id="change-key-btn" class="ghost">Change API key</button>
|
| 36 |
-
</div>
|
| 37 |
-
|
| 38 |
-
<div id="form-panel" class="panel hidden">
|
| 39 |
-
<div class="panel-heading">
|
| 40 |
-
<div>
|
| 41 |
-
<p class="eyebrow">Credentials</p>
|
| 42 |
-
<h2>Connect OpenAI</h2>
|
| 43 |
-
</div>
|
| 44 |
-
<span class="chip">Required</span>
|
| 45 |
-
</div>
|
| 46 |
-
<p class="muted">Paste your API key once and we will store it locally for the headless conversation loop.</p>
|
| 47 |
-
<label for="api-key">OpenAI API Key</label>
|
| 48 |
-
<input id="api-key" type="password" placeholder="sk-..." autocomplete="off" />
|
| 49 |
-
<div class="actions">
|
| 50 |
-
<button id="save-btn">Save key</button>
|
| 51 |
-
<p id="status" class="status"></p>
|
| 52 |
-
</div>
|
| 53 |
-
</div>
|
| 54 |
-
|
| 55 |
-
<div id="personality-panel" class="panel hidden">
|
| 56 |
-
<div class="panel-heading">
|
| 57 |
-
<div>
|
| 58 |
-
<p class="eyebrow">Profiles</p>
|
| 59 |
-
<h2>Personality studio</h2>
|
| 60 |
-
</div>
|
| 61 |
-
<span class="chip">Live</span>
|
| 62 |
-
</div>
|
| 63 |
-
<p class="muted">Create lean instruction sets, toggle tools, and apply a voice for your Reachy Mini.</p>
|
| 64 |
-
<div class="section">
|
| 65 |
-
<div class="section-heading">
|
| 66 |
-
<h3>Select & launch</h3>
|
| 67 |
-
<p class="muted small">Pick a profile and choose what should launch on startup.</p>
|
| 68 |
-
</div>
|
| 69 |
-
<div class="row row-top">
|
| 70 |
-
<label for="personality-select">Select</label>
|
| 71 |
-
<select id="personality-select"></select>
|
| 72 |
-
<button id="persist-personality" class="ghost">Use on startup</button>
|
| 73 |
-
<button id="apply-personality" class="ghost">Apply</button>
|
| 74 |
-
<button id="new-personality" class="ghost">New</button>
|
| 75 |
-
</div>
|
| 76 |
-
<div class="row">
|
| 77 |
-
<label>Startup personality</label>
|
| 78 |
-
<div class="startup-row">
|
| 79 |
-
<span id="startup-label" class="chip">Built-in default</span>
|
| 80 |
-
</div>
|
| 81 |
-
</div>
|
| 82 |
-
</div>
|
| 83 |
-
|
| 84 |
-
<div class="section">
|
| 85 |
-
<div class="section-heading">
|
| 86 |
-
<h3>Create / edit</h3>
|
| 87 |
-
<p class="muted small">Adjust instructions, tools, and voice, then save your profile.</p>
|
| 88 |
-
</div>
|
| 89 |
-
<div class="row">
|
| 90 |
-
<label for="personality-name">Name</label>
|
| 91 |
-
<input id="personality-name" type="text" class="input-field" placeholder="my_profile" />
|
| 92 |
-
<label for="voice-select">Voice</label>
|
| 93 |
-
<select id="voice-select"></select>
|
| 94 |
-
</div>
|
| 95 |
-
<div class="row">
|
| 96 |
-
<label for="instructions-ta">Instructions</label>
|
| 97 |
-
<textarea id="instructions-ta" rows="8" placeholder="# Write your instructions here"></textarea>
|
| 98 |
-
</div>
|
| 99 |
-
<div class="row">
|
| 100 |
-
<label for="tools-ta">tools.txt</label>
|
| 101 |
-
<textarea id="tools-ta" rows="6" placeholder="# tools enabled for this profile"></textarea>
|
| 102 |
-
</div>
|
| 103 |
-
<div class="row">
|
| 104 |
-
<label for="tools-available">Available tools</label>
|
| 105 |
-
<div id="tools-available" class="checkbox-grid"></div>
|
| 106 |
-
</div>
|
| 107 |
-
<div class="row row-save">
|
| 108 |
-
<label></label>
|
| 109 |
-
<div class="actions">
|
| 110 |
-
<button id="save-personality">Save</button>
|
| 111 |
-
</div>
|
| 112 |
-
</div>
|
| 113 |
-
</div>
|
| 114 |
-
<p id="personality-status" class="status"></p>
|
| 115 |
-
</div>
|
| 116 |
-
</div>
|
| 117 |
-
|
| 118 |
-
<script src="/static/main.js"></script>
|
| 119 |
</body>
|
| 120 |
-
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>MethdAI Receptionist</title>
|
| 7 |
+
<!-- The app landing page now goes straight to the operator dashboard.
|
| 8 |
+
The old persona/API console lived here and was the first thing an
|
| 9 |
+
operator saw in Reachy Mini Control — it exposed raw prompt/tools
|
| 10 |
+
editing that end users should never touch. That studio moved to
|
| 11 |
+
/static/personality.html and is reachable from the dashboard's
|
| 12 |
+
"Advanced" button. Redirect via meta-refresh + JS (covers Control's
|
| 13 |
+
embedded webview) with a manual link as a no-JS fallback. -->
|
| 14 |
+
<meta http-equiv="refresh" content="0; url=/dashboard" />
|
| 15 |
+
<script>
|
| 16 |
+
window.location.replace("/dashboard");
|
| 17 |
+
</script>
|
| 18 |
+
<style>
|
| 19 |
+
body {
|
| 20 |
+
margin: 0; min-height: 100vh; display: flex; align-items: center;
|
| 21 |
+
justify-content: center; background: #0e1118; color: #e2e8f0;
|
| 22 |
+
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
| 23 |
+
}
|
| 24 |
+
a { color: #818cf8; }
|
| 25 |
+
</style>
|
| 26 |
</head>
|
| 27 |
<body>
|
| 28 |
+
<p>Opening the Receptionist Dashboard… <a href="/dashboard">Continue</a></p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
</body>
|
| 30 |
+
</html>
|
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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" />
|
| 6 |
+
<title>Advanced — Personality Studio</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="ambient"></div>
|
| 11 |
+
<div id="loading" class="loading">
|
| 12 |
+
<div class="spinner"></div>
|
| 13 |
+
<p>Loading…</p>
|
| 14 |
+
</div>
|
| 15 |
+
<div class="container">
|
| 16 |
+
<div class="dashboard-banner" role="navigation" aria-label="Dashboard quick link">
|
| 17 |
+
<a href="/dashboard">← Back to Receptionist Dashboard</a>
|
| 18 |
+
</div>
|
| 19 |
+
|
| 20 |
+
<header class="hero">
|
| 21 |
+
<div class="pill">Advanced · Developer</div>
|
| 22 |
+
<h1>Personality studio</h1>
|
| 23 |
+
<p class="subtitle">Edit the robot's persona, prompt, and enabled tools. Not needed for normal operation.</p>
|
| 24 |
+
</header>
|
| 25 |
+
|
| 26 |
+
<div id="configured" class="panel hidden">
|
| 27 |
+
<div class="panel-heading">
|
| 28 |
+
<div>
|
| 29 |
+
<p class="eyebrow">Credentials</p>
|
| 30 |
+
<h2>API key ready</h2>
|
| 31 |
+
</div>
|
| 32 |
+
<span class="chip chip-ok">Connected</span>
|
| 33 |
+
</div>
|
| 34 |
+
<p class="muted">OpenAI API key is already configured. You can jump straight to personalities.</p>
|
| 35 |
+
<button id="change-key-btn" class="ghost">Change API key</button>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<div id="form-panel" class="panel hidden">
|
| 39 |
+
<div class="panel-heading">
|
| 40 |
+
<div>
|
| 41 |
+
<p class="eyebrow">Credentials</p>
|
| 42 |
+
<h2>Connect OpenAI</h2>
|
| 43 |
+
</div>
|
| 44 |
+
<span class="chip">Required</span>
|
| 45 |
+
</div>
|
| 46 |
+
<p class="muted">Paste your API key once and we will store it locally for the headless conversation loop.</p>
|
| 47 |
+
<label for="api-key">OpenAI API Key</label>
|
| 48 |
+
<input id="api-key" type="password" placeholder="sk-..." autocomplete="off" />
|
| 49 |
+
<div class="actions">
|
| 50 |
+
<button id="save-btn">Save key</button>
|
| 51 |
+
<p id="status" class="status"></p>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div id="personality-panel" class="panel hidden">
|
| 56 |
+
<div class="panel-heading">
|
| 57 |
+
<div>
|
| 58 |
+
<p class="eyebrow">Profiles</p>
|
| 59 |
+
<h2>Personality studio</h2>
|
| 60 |
+
</div>
|
| 61 |
+
<span class="chip">Live</span>
|
| 62 |
+
</div>
|
| 63 |
+
<p class="muted">Create lean instruction sets, toggle tools, and apply a voice for your Reachy Mini.</p>
|
| 64 |
+
<div class="section">
|
| 65 |
+
<div class="section-heading">
|
| 66 |
+
<h3>Select & launch</h3>
|
| 67 |
+
<p class="muted small">Pick a profile and choose what should launch on startup.</p>
|
| 68 |
+
</div>
|
| 69 |
+
<div class="row row-top">
|
| 70 |
+
<label for="personality-select">Select</label>
|
| 71 |
+
<select id="personality-select"></select>
|
| 72 |
+
<button id="persist-personality" class="ghost">Use on startup</button>
|
| 73 |
+
<button id="apply-personality" class="ghost">Apply</button>
|
| 74 |
+
<button id="new-personality" class="ghost">New</button>
|
| 75 |
+
</div>
|
| 76 |
+
<div class="row">
|
| 77 |
+
<label>Startup personality</label>
|
| 78 |
+
<div class="startup-row">
|
| 79 |
+
<span id="startup-label" class="chip">Built-in default</span>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
|
| 84 |
+
<div class="section">
|
| 85 |
+
<div class="section-heading">
|
| 86 |
+
<h3>Create / edit</h3>
|
| 87 |
+
<p class="muted small">Adjust instructions, tools, and voice, then save your profile.</p>
|
| 88 |
+
</div>
|
| 89 |
+
<div class="row">
|
| 90 |
+
<label for="personality-name">Name</label>
|
| 91 |
+
<input id="personality-name" type="text" class="input-field" placeholder="my_profile" />
|
| 92 |
+
<label for="voice-select">Voice</label>
|
| 93 |
+
<select id="voice-select"></select>
|
| 94 |
+
</div>
|
| 95 |
+
<div class="row">
|
| 96 |
+
<label for="instructions-ta">Instructions</label>
|
| 97 |
+
<textarea id="instructions-ta" rows="8" placeholder="# Write your instructions here"></textarea>
|
| 98 |
+
</div>
|
| 99 |
+
<div class="row">
|
| 100 |
+
<label for="tools-ta">tools.txt</label>
|
| 101 |
+
<textarea id="tools-ta" rows="6" placeholder="# tools enabled for this profile"></textarea>
|
| 102 |
+
</div>
|
| 103 |
+
<div class="row">
|
| 104 |
+
<label for="tools-available">Available tools</label>
|
| 105 |
+
<div id="tools-available" class="checkbox-grid"></div>
|
| 106 |
+
</div>
|
| 107 |
+
<div class="row row-save">
|
| 108 |
+
<label></label>
|
| 109 |
+
<div class="actions">
|
| 110 |
+
<button id="save-personality">Save</button>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
</div>
|
| 114 |
+
<p id="personality-status" class="status"></p>
|
| 115 |
+
</div>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<script src="/static/main.js"></script>
|
| 119 |
+
</body>
|
| 120 |
+
</html>
|