Add show theme feature for themed episodes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+26
-1
@@ -5314,6 +5314,7 @@ TIME: {time_ctx} {season_ctx}
|
||||
{fluency_hint}
|
||||
{f'SOME DETAILS ABOUT THEM: {seed_text}' if seed_text else ''}
|
||||
{f'CALLER ENERGY: {style_hint}' if style_hint else ''}
|
||||
{f"SHOW THEME: Tonight's show theme is '{session.show_theme}'. This caller might have a story or angle related to this theme — or they might not. Not every caller has to be about the theme, but if their reason for calling can naturally connect to it, lean into that connection. The theme should feel like a through-line, not a mandate." if session.show_theme else ''}
|
||||
|
||||
Respond with a JSON object containing these fields:
|
||||
|
||||
@@ -6014,6 +6015,10 @@ def get_caller_prompt(caller: dict, show_history: str = "",
|
||||
parts.append(research_context)
|
||||
world_context = "\n".join(parts) + "\n"
|
||||
|
||||
theme_context = ""
|
||||
if session.show_theme:
|
||||
theme_context = f"\nSHOW THEME: Tonight's show theme is \"{session.show_theme}\". You're aware of the theme — the host mentioned it at the top of the show. If your story or situation connects to it, you might bring it up naturally. But don't force it. Not every caller has to be about the theme. If the host steers you toward the theme, go with it.\n"
|
||||
|
||||
now = datetime.now(_MST)
|
||||
date_str = now.strftime("%A, %B %d")
|
||||
|
||||
@@ -6060,7 +6065,7 @@ You are {caller['name']}. You are the CALLER. You are NOT Luke. Luke is the HOST
|
||||
|
||||
YOUR BACKGROUND:
|
||||
{caller['vibe']}
|
||||
{relationship_context}{history}{world_context}{emotional_read}
|
||||
{relationship_context}{history}{world_context}{theme_context}{emotional_read}
|
||||
You're a real person calling a late-night radio show. You called because you've got something specific and you want to talk about it.
|
||||
|
||||
{pacing_block}
|
||||
@@ -6215,6 +6220,7 @@ class Session:
|
||||
self.caller_queue: list[str] = [] # Sorted presentation order of caller keys
|
||||
self.relationship_context: dict[str, str] = {} # caller_key → relationship prompt injection
|
||||
self.intern_monitoring: bool = True # Devon monitors conversations by default
|
||||
self.show_theme: str = "" # Current show theme (e.g. "St. Patrick's Day")
|
||||
|
||||
def start_call(self, caller_key: str):
|
||||
self.current_caller_key = caller_key
|
||||
@@ -8759,6 +8765,25 @@ async def update_settings(data: dict):
|
||||
return llm_service.get_settings()
|
||||
|
||||
|
||||
# --- Show Theme ---
|
||||
|
||||
@app.get("/api/show-theme")
|
||||
async def get_show_theme():
|
||||
return {"theme": session.show_theme}
|
||||
|
||||
|
||||
@app.post("/api/show-theme")
|
||||
async def set_show_theme(data: dict):
|
||||
theme = data.get("theme", "").strip()[:100]
|
||||
old_theme = session.show_theme
|
||||
session.show_theme = theme
|
||||
if theme:
|
||||
print(f"[Theme] Show theme set: {theme}")
|
||||
elif old_theme:
|
||||
print(f"[Theme] Show theme cleared (was: {old_theme})")
|
||||
return {"theme": session.show_theme}
|
||||
|
||||
|
||||
# --- Cost Tracking Endpoints ---
|
||||
|
||||
@app.get("/api/costs")
|
||||
|
||||
@@ -113,6 +113,69 @@ header button:hover {
|
||||
border-color: rgba(232, 121, 29, 0.3);
|
||||
}
|
||||
|
||||
.theme-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.theme-label {
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-input {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.85rem;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.theme-input:focus {
|
||||
outline: none;
|
||||
border-color: #f5a623;
|
||||
}
|
||||
|
||||
.theme-input.active {
|
||||
border-color: #f5a623;
|
||||
background: rgba(245, 166, 35, 0.1);
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.theme-btn.set {
|
||||
background: #f5a623;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.theme-btn.set:hover {
|
||||
background: #e6991a;
|
||||
}
|
||||
|
||||
.theme-btn.clear {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #aaa;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.theme-btn.clear:hover {
|
||||
background: rgba(255, 80, 80, 0.3);
|
||||
color: #ff5050;
|
||||
}
|
||||
|
||||
.on-air-btn {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
<button id="export-session-btn">Export</button>
|
||||
<button id="settings-btn">Settings</button>
|
||||
</div>
|
||||
<div class="theme-bar">
|
||||
<label for="show-theme-input" class="theme-label">Theme:</label>
|
||||
<input type="text" id="show-theme-input" class="theme-input" placeholder="e.g. St. Patrick's Day" maxlength="100">
|
||||
<button id="set-theme-btn" class="theme-btn set" title="Set show theme">Set</button>
|
||||
<button id="clear-theme-btn" class="theme-btn clear hidden" title="Clear theme">✕</button>
|
||||
</div>
|
||||
<div id="show-clock" class="show-clock">
|
||||
<span class="clock-time" id="clock-time"></span>
|
||||
<span id="show-timers" class="show-timers hidden">
|
||||
|
||||
@@ -130,6 +130,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadSettings();
|
||||
initEventListeners();
|
||||
initClock();
|
||||
loadShowTheme();
|
||||
loadVoicemails();
|
||||
setInterval(loadVoicemails, 30000);
|
||||
loadEmails();
|
||||
@@ -345,6 +346,13 @@ function initEventListeners() {
|
||||
document.getElementById('devon-play-btn')?.addEventListener('click', playDevonSuggestion);
|
||||
document.getElementById('devon-dismiss-btn')?.addEventListener('click', dismissDevonSuggestion);
|
||||
|
||||
// Show Theme
|
||||
document.getElementById('set-theme-btn')?.addEventListener('click', setShowTheme);
|
||||
document.getElementById('clear-theme-btn')?.addEventListener('click', clearShowTheme);
|
||||
document.getElementById('show-theme-input')?.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') setShowTheme();
|
||||
});
|
||||
|
||||
// Settings
|
||||
document.getElementById('settings-btn')?.addEventListener('click', async () => {
|
||||
document.getElementById('settings-modal')?.classList.remove('hidden');
|
||||
@@ -692,6 +700,7 @@ async function newSession() {
|
||||
|
||||
// Reload callers to get new session ID
|
||||
await loadCallers();
|
||||
await loadShowTheme();
|
||||
|
||||
log('New session started - all callers have fresh backgrounds');
|
||||
}
|
||||
@@ -1159,6 +1168,69 @@ async function playSFX(soundFile) {
|
||||
}
|
||||
|
||||
|
||||
// --- Show Theme ---
|
||||
async function loadShowTheme() {
|
||||
try {
|
||||
const res = await fetch('/api/show-theme');
|
||||
const data = await res.json();
|
||||
const input = document.getElementById('show-theme-input');
|
||||
const setBtn = document.getElementById('set-theme-btn');
|
||||
const clearBtn = document.getElementById('clear-theme-btn');
|
||||
if (data.theme) {
|
||||
input.value = data.theme;
|
||||
input.classList.add('active');
|
||||
setBtn.classList.add('hidden');
|
||||
clearBtn.classList.remove('hidden');
|
||||
} else {
|
||||
input.value = '';
|
||||
input.classList.remove('active');
|
||||
setBtn.classList.remove('hidden');
|
||||
clearBtn.classList.add('hidden');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load show theme:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function setShowTheme() {
|
||||
const input = document.getElementById('show-theme-input');
|
||||
const theme = input.value.trim();
|
||||
if (!theme) return;
|
||||
try {
|
||||
const res = await fetch('/api/show-theme', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.theme) {
|
||||
input.classList.add('active');
|
||||
document.getElementById('set-theme-btn').classList.add('hidden');
|
||||
document.getElementById('clear-theme-btn').classList.remove('hidden');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to set show theme:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function clearShowTheme() {
|
||||
try {
|
||||
await fetch('/api/show-theme', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme: '' })
|
||||
});
|
||||
const input = document.getElementById('show-theme-input');
|
||||
input.value = '';
|
||||
input.classList.remove('active');
|
||||
document.getElementById('set-theme-btn').classList.remove('hidden');
|
||||
document.getElementById('clear-theme-btn').classList.add('hidden');
|
||||
} catch (e) {
|
||||
console.error('Failed to clear show theme:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Settings ---
|
||||
async function loadSettings() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user