Fix caller name mismatch after mid-session theme change
Setting a show theme after the caller lineup was loaded caused every caller button to keep its pre-theme label while the LLM dialog used the post-theme bg names — host says "Hi Phil", caller replies "it's Cody, actually". Three underlying bugs, all fixed here: - _regenerate_backgrounds_for_keys ignored its keys parameter and replaced the entire caller_backgrounds dict, clobbering used slots along with unused ones. Now only the listed slots are touched. - set_show_theme's used_keys detection compared record.caller_name (the slim bg name) against CALLER_BASES[k]["name"] (the randomized fallback name) — two different namespaces that never match, so every slot was flagged unused. Now it matches against session.caller_backgrounds[k]["name"]. - set_show_theme fired the regeneration as a detached asyncio task, so the POST returned before bg was consistent. Even if the frontend did reload /api/callers, it would race the regen. Now awaited. Frontend setShowTheme/clearShowTheme now call loadCallers() after the theme POST resolves so the button list actually refreshes, with a "Regenerating..." button state during the ~30-60s wait. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-8
@@ -240,13 +240,18 @@ _randomize_callers() # Initial assignment
|
|||||||
|
|
||||||
|
|
||||||
async def _regenerate_backgrounds_for_keys(keys: list[str]):
|
async def _regenerate_backgrounds_for_keys(keys: list[str]):
|
||||||
"""Regenerate backgrounds for unused callers (e.g. after theme change).
|
"""Regenerate backgrounds for the given slot keys only (e.g. unused callers
|
||||||
Re-runs the batch pregeneration to pick up the new theme."""
|
after a theme change). Runs a fresh batch and copies only the requested
|
||||||
|
slots into session.caller_backgrounds — used slots are preserved so
|
||||||
|
in-progress/completed calls keep their identities."""
|
||||||
if not keys:
|
if not keys:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
session.caller_backgrounds = await session._build_backgrounds()
|
fresh = await session._build_backgrounds()
|
||||||
print(f"[Background] Regenerated backgrounds after theme change (touched {len(keys)} unused slots)")
|
for k in keys:
|
||||||
|
if k in fresh:
|
||||||
|
session.caller_backgrounds[k] = fresh[k]
|
||||||
|
print(f"[Background] Regenerated backgrounds for {len(keys)} slots: {', '.join(keys)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Background] Regen failed: {e}")
|
print(f"[Background] Regen failed: {e}")
|
||||||
|
|
||||||
@@ -3689,20 +3694,26 @@ async def set_show_theme(data: dict):
|
|||||||
elif old_theme:
|
elif old_theme:
|
||||||
print(f"[Theme] Show theme cleared (was: {old_theme})")
|
print(f"[Theme] Show theme cleared (was: {old_theme})")
|
||||||
|
|
||||||
# Regenerate backgrounds for unused callers so theme gets baked in
|
# Regenerate backgrounds for unused callers so theme gets baked in.
|
||||||
|
# Awaited (not fire-and-forget) so the response only returns once the
|
||||||
|
# caller list is consistent — the frontend reloads /api/callers after
|
||||||
|
# this resolves and must see the new names, not the stale ones.
|
||||||
if theme and theme != old_theme:
|
if theme and theme != old_theme:
|
||||||
used_keys = set()
|
used_keys = set()
|
||||||
if session.current_caller_key:
|
if session.current_caller_key:
|
||||||
used_keys.add(session.current_caller_key)
|
used_keys.add(session.current_caller_key)
|
||||||
|
# Match call_history entries against the slim background names,
|
||||||
|
# not CALLER_BASES randomized fallbacks — those live in a different
|
||||||
|
# namespace and would never match, flagging every slot as unused.
|
||||||
for record in session.call_history:
|
for record in session.call_history:
|
||||||
for key, base in CALLER_BASES.items():
|
for key, bg in session.caller_backgrounds.items():
|
||||||
if base.get("name") == record.caller_name:
|
if isinstance(bg, dict) and bg.get("name") == record.caller_name:
|
||||||
used_keys.add(key)
|
used_keys.add(key)
|
||||||
break
|
break
|
||||||
unused_keys = [k for k in CALLER_BASES if k not in used_keys]
|
unused_keys = [k for k in CALLER_BASES if k not in used_keys]
|
||||||
if unused_keys:
|
if unused_keys:
|
||||||
asyncio.create_task(_regenerate_backgrounds_for_keys(unused_keys))
|
|
||||||
print(f"[Theme] Regenerating backgrounds for {len(unused_keys)} unused callers")
|
print(f"[Theme] Regenerating backgrounds for {len(unused_keys)} unused callers")
|
||||||
|
await _regenerate_backgrounds_for_keys(unused_keys)
|
||||||
|
|
||||||
return {"theme": session.show_theme}
|
return {"theme": session.show_theme}
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -1356,8 +1356,14 @@ async function loadShowTheme() {
|
|||||||
|
|
||||||
async function setShowTheme() {
|
async function setShowTheme() {
|
||||||
const input = document.getElementById('show-theme-input');
|
const input = document.getElementById('show-theme-input');
|
||||||
|
const setBtn = document.getElementById('set-theme-btn');
|
||||||
const theme = input.value.trim();
|
const theme = input.value.trim();
|
||||||
if (!theme) return;
|
if (!theme) return;
|
||||||
|
const originalText = setBtn?.textContent;
|
||||||
|
if (setBtn) {
|
||||||
|
setBtn.disabled = true;
|
||||||
|
setBtn.textContent = 'Regenerating...';
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/show-theme', {
|
const res = await fetch('/api/show-theme', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1367,11 +1373,19 @@ async function setShowTheme() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.theme) {
|
if (data.theme) {
|
||||||
input.classList.add('active');
|
input.classList.add('active');
|
||||||
document.getElementById('set-theme-btn').classList.add('hidden');
|
setBtn?.classList.add('hidden');
|
||||||
document.getElementById('clear-theme-btn').classList.remove('hidden');
|
document.getElementById('clear-theme-btn').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
// Backend regenerated backgrounds for unused callers — refresh the
|
||||||
|
// button list so we show the new names, not the pre-theme cache.
|
||||||
|
await loadCallers();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to set show theme:', e);
|
console.error('Failed to set show theme:', e);
|
||||||
|
} finally {
|
||||||
|
if (setBtn) {
|
||||||
|
setBtn.disabled = false;
|
||||||
|
setBtn.textContent = originalText || 'Set';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1387,6 +1401,7 @@ async function clearShowTheme() {
|
|||||||
input.classList.remove('active');
|
input.classList.remove('active');
|
||||||
document.getElementById('set-theme-btn').classList.remove('hidden');
|
document.getElementById('set-theme-btn').classList.remove('hidden');
|
||||||
document.getElementById('clear-theme-btn').classList.add('hidden');
|
document.getElementById('clear-theme-btn').classList.add('hidden');
|
||||||
|
await loadCallers();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to clear show theme:', e);
|
console.error('Failed to clear show theme:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user