Move caller dialog to Sonnet 4.6, add SearXNG for Devon, and fix stale model ids

Working-tree changes that had accumulated without being committed. The test
updates matter most: tests/test_caller_gen.py was left behind when caller_gen
started requiring voice and age in regulars_included, so the committed tree had
a failing suite that only passed locally.

- Caller dialog moves from Haiku 4.5 to Sonnet 4.6 (~$1/show to ~$3-4/show)
- Grok pinned to x-ai/grok-4.3; grok-4, grok-4-fast and grok-4.1-fast were
  retired from OpenRouter, and llm.py swallows the 404 and returns empty text,
  so a retired id makes callers go silent with nothing in the logs
- Devon's web_search now runs against SearXNG on the NAS
- Assorted TTS, audio, news, cost tracker and control-panel changes
- CLAUDE.md updated to match

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 04:21:55 -05:00
co-authored by Claude Opus 5
parent 2901e5f4fb
commit 44ef13336e
21 changed files with 731 additions and 186 deletions
+22 -2
View File
@@ -1434,12 +1434,32 @@ section h2 {
.vm-item {
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column;
gap: 4px;
padding: 6px 8px;
border-bottom: 1px solid rgba(232, 121, 29, 0.08);
}
.vm-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.vm-transcript {
font-size: 0.8rem;
line-height: 1.35;
color: var(--text-muted);
border-left: 2px solid rgba(232, 121, 29, 0.25);
padding-left: 8px;
}
.vm-transcript-pending {
font-style: italic;
opacity: 0.6;
}
.vm-item:last-child {
border-bottom: none;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Luke at The Roost</title>
<link rel="stylesheet" href="/css/style.css?v=2">
<link rel="stylesheet" href="/css/style.css?v=3">
</head>
<body>
<div id="app">
@@ -325,6 +325,6 @@
</div>
</div>
<script src="/js/app.js?v=28"></script>
<script src="/js/app.js?v=29"></script>
</body>
</html>
+16 -10
View File
@@ -1993,18 +1993,24 @@ function renderVoicemails(voicemails) {
const secs = v.duration % 60;
const durStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
const unlistenedCls = v.listened ? '' : ' vm-unlistened';
const transcript = v.transcript
? `<div class="vm-transcript">${escapeHtml(v.transcript)}</div>`
: `<div class="vm-transcript vm-transcript-pending">Transcribing…</div>`;
return `<div class="vm-item${unlistenedCls}" data-id="${v.id}">
<div class="vm-info">
<span class="vm-phone">${v.phone}</span>
<span class="vm-time">${timeStr}</span>
<span class="vm-dur">${durStr}</span>
</div>
<div class="vm-actions">
<button class="vm-btn listen" onclick="listenVoicemail('${v.id}')">Listen</button>
<button class="vm-btn on-air" onclick="playVoicemailOnAir('${v.id}')">On Air</button>
<button class="vm-btn save" onclick="saveVoicemail('${v.id}')">Save</button>
<button class="vm-btn delete" onclick="deleteVoicemail('${v.id}')">Del</button>
<div class="vm-row">
<div class="vm-info">
<span class="vm-phone">${v.phone}</span>
<span class="vm-time">${timeStr}</span>
<span class="vm-dur">${durStr}</span>
</div>
<div class="vm-actions">
<button class="vm-btn listen" onclick="listenVoicemail('${v.id}')">Listen</button>
<button class="vm-btn on-air" onclick="playVoicemailOnAir('${v.id}')">On Air</button>
<button class="vm-btn save" onclick="saveVoicemail('${v.id}')">Save</button>
<button class="vm-btn delete" onclick="deleteVoicemail('${v.id}')">Del</button>
</div>
</div>
${transcript}
</div>`;
}).join('');
}