b0893a3699
Part 1 — Transport swap: - Replace anthropic.AsyncAnthropic streaming with httpx SSE client calling opencode's OpenAI-compat /v1/chat/completions on sin:4096 - Auth: basic auth opencode:$OPENCODE_PASSWORD - Env: OPENCODE_URL (default http://sin:4096), OPENCODE_PASSWORD - Sidecar binding (sin:4098) consulted per message to resolve active persona; voice read from binding → cart → env default - Helper _session_id_for_user: deterministic sha256 slug per email so sidecar binding survives WebSocket reconnects - anthropic dep retained in pyproject.toml (not removed — P4 may use it) Part 2 — Persona switcher: - PERSONAS dict: bt7274, friday, samantha (slug → voice/backend/prompt) - POST /api/persona — bind persona via sidecar, maps slug → full config - GET /api/persona/current — return current binding - GET /api/personas — list available personas - chat.html: persona <select> in topnav with server-rendered active state - chat.js: onChange → fetch /api/persona, update __personaName + status badge + system message in conversation feed TODO: add CSS polish for .topnav__persona-wrap (inherits base styles for now)
86 lines
3.4 KiB
HTML
86 lines
3.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>chat.saiden.dev</title>
|
|
<meta name="description" content="A quiet channel.">
|
|
<meta name="robots" content="noindex, nofollow">
|
|
|
|
<link rel="icon" href="https://saiden.dev/favicon.ico">
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400&family=Caveat:wght@400;500&family=Inter:wght@300;400;500;600&family=Source+Serif+Pro:ital,wght@0,400;0,600;1,400&family=JetBrains+Mono:wght@400;500&display=swap">
|
|
|
|
<link rel="stylesheet" href="/static/chat.css">
|
|
</head>
|
|
<body
|
|
data-palette="{{ ui_palette }}"
|
|
data-typography="{{ ui_typography }}"
|
|
data-density="{{ ui_density }}"
|
|
data-labels="{{ ui_labels }}">
|
|
<script>
|
|
window.__pilotName = {{ pilot_name | tojson }};
|
|
window.__personaName = {{ persona_name | tojson }};
|
|
window.__personas = {{ personas | tojson }};
|
|
window.__boundSlug = {{ bound_slug | tojson }};
|
|
</script>
|
|
<nav class="topnav" aria-label="channel controls">
|
|
<button type="button" class="topnav__link" id="recalibrate-btn" title="reset calibration">recalibrate</button>
|
|
<span class="topnav__sep">·</span>
|
|
|
|
<!-- Persona switcher -->
|
|
<span class="topnav__persona-wrap">
|
|
<label class="topnav__persona-label" for="persona-select">persona</label>
|
|
<select class="topnav__persona-select" id="persona-select" title="Switch persona">
|
|
<option value="">— default —</option>
|
|
{% for p in personas %}
|
|
<option value="{{ p.slug }}"{% if p.slug == bound_slug %} selected{% endif %}>{{ p.display }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<span class="topnav__persona-status" id="persona-status">
|
|
{% if bound_display %}{{ bound_display }}{% endif %}
|
|
</span>
|
|
</span>
|
|
<span class="topnav__sep">·</span>
|
|
|
|
<a class="topnav__link" href="/auth/logout">sign out</a>
|
|
<a class="sigil" href="https://saiden.dev/" target="_blank" rel="noopener" title="Saiden">
|
|
<img src="https://saiden.dev/logo.png" alt="Saiden">
|
|
</a>
|
|
</nav>
|
|
|
|
<main class="page">
|
|
<section class="conversation" id="conversation" aria-live="polite">
|
|
</section>
|
|
|
|
<form class="prompt" id="prompt-form" autocomplete="off">
|
|
<hr class="prompt__line">
|
|
<div class="prompt__row">
|
|
<input
|
|
class="prompt__input"
|
|
id="prompt-input"
|
|
type="text"
|
|
placeholder="{% if cart %}speak to {{ persona_name }}{% else %}…{% endif %}"
|
|
autofocus
|
|
autocomplete="off"
|
|
spellcheck="true"
|
|
aria-label="message">
|
|
<button type="button" class="prompt__mic" id="mic-button" aria-label="speak"
|
|
title="hold space to speak · click to toggle">
|
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none"
|
|
stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
|
<rect x="9" y="3" width="6" height="12" rx="3"></rect>
|
|
<path d="M5 11a7 7 0 0 0 14 0"></path>
|
|
<line x1="12" y1="18" x2="12" y2="22"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</main>
|
|
|
|
<script src="/static/chat.js"></script>
|
|
</body>
|
|
</html>
|