feat(transport): swap Anthropic → opencode; add persona switcher
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)
This commit is contained in:
@@ -23,10 +23,28 @@
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user