Devon own stem/track/channel, per-category LLM routing, settings UI cleanup
Audio: - Devon gets own stem, Reaper track (Input 17), and configurable channel - play_caller_audio accepts stem_name + channel_override params - Reaper script checks 4 voice tracks (Host, Devon, Live Caller, AI Caller) - postprod.py includes devon stem in gap detection Cost optimization: - Per-category model routing: Sonnet for caller dialog, Gemini Flash for everything else - Estimated 65% cost reduction ($4.32 → ~$1.50/show) - Category models configurable from settings UI Frontend: - Settings panel: clean routing grid for output channels, model routing grid for LLM categories - Devon channel added to audio routing - Share icon SVG fill fix (currentColor) - Website homepage iterations Publishing: - Revert Castopod API workaround (API re-enabled) - Fix container media path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+48
-7
@@ -802,22 +802,63 @@ section h2 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.channel-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
.routing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.channel-row label {
|
||||
.routing-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
font-size: 0.85rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.routing-item label {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.routing-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted, #9a8b78);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.channel-input {
|
||||
width: 50px !important;
|
||||
width: 40px !important;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.model-routing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.model-routing-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.model-routing-item label {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.model-routing-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted, #9a8b78);
|
||||
}
|
||||
|
||||
.model-select {
|
||||
font-size: 0.8rem !important;
|
||||
padding: 4px 6px !important;
|
||||
}
|
||||
|
||||
.modal-content label {
|
||||
|
||||
+61
-35
@@ -195,7 +195,7 @@
|
||||
|
||||
<!-- Audio Devices -->
|
||||
<div class="settings-group">
|
||||
<h3>Audio Routing</h3>
|
||||
<h3>Audio Devices</h3>
|
||||
<div class="device-row">
|
||||
<label>
|
||||
Input Device
|
||||
@@ -203,7 +203,7 @@
|
||||
</label>
|
||||
<label>
|
||||
Ch
|
||||
<input type="number" id="input-channel" value="1" min="1" max="16" class="channel-input">
|
||||
<input type="number" id="input-channel" value="1" min="1" max="32" class="channel-input">
|
||||
</label>
|
||||
</div>
|
||||
<div class="device-row">
|
||||
@@ -212,44 +212,70 @@
|
||||
<select id="output-device"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="channel-row">
|
||||
<label>Caller Ch <input type="number" id="caller-channel" value="3" min="1" max="16" class="channel-input"></label>
|
||||
<label>Live Ch <input type="number" id="live-caller-channel" value="9" min="1" max="16" class="channel-input"></label>
|
||||
<label>Music Ch <input type="number" id="music-channel" value="5" min="1" max="16" class="channel-input"></label>
|
||||
<label>SFX Ch <input type="number" id="sfx-channel" value="7" min="1" max="16" class="channel-input"></label>
|
||||
<label>Ad Ch <input type="number" id="ad-channel" value="11" min="1" max="16" class="channel-input"></label>
|
||||
<label>Ident Ch <input type="number" id="ident-channel" value="15" min="1" max="16" class="channel-input"></label>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h3>Output Routing</h3>
|
||||
<div class="routing-grid">
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">AI Caller</span>
|
||||
<input type="number" id="caller-channel" value="3" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">Devon</span>
|
||||
<input type="number" id="devon-channel" value="17" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">Live Caller</span>
|
||||
<input type="number" id="live-caller-channel" value="9" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">Music</span>
|
||||
<input type="number" id="music-channel" value="5" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">SFX</span>
|
||||
<input type="number" id="sfx-channel" value="7" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">Ads</span>
|
||||
<input type="number" id="ad-channel" value="11" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
<div class="routing-item">
|
||||
<span class="routing-label">Idents</span>
|
||||
<input type="number" id="ident-channel" value="15" min="1" max="32" class="channel-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LLM Settings -->
|
||||
<div class="settings-group">
|
||||
<h3>LLM Provider</h3>
|
||||
<label>
|
||||
Provider
|
||||
<select id="provider">
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="ollama">Ollama</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div id="openrouter-settings">
|
||||
<label>
|
||||
Model
|
||||
<select id="openrouter-model"></select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="ollama-settings" class="hidden">
|
||||
<label>
|
||||
Model
|
||||
<select id="ollama-model"></select>
|
||||
</label>
|
||||
<label>
|
||||
Host
|
||||
<input type="text" id="ollama-host" value="http://localhost:11434">
|
||||
</label>
|
||||
<button type="button" id="refresh-ollama" class="refresh-btn">Refresh Models</button>
|
||||
<h3>LLM Model Routing</h3>
|
||||
<div class="model-routing-grid">
|
||||
<div class="model-routing-item">
|
||||
<span class="model-routing-label">Caller Dialog</span>
|
||||
<select id="model-caller_dialog" class="model-select"></select>
|
||||
</div>
|
||||
<div class="model-routing-item">
|
||||
<span class="model-routing-label">Devon Monitor</span>
|
||||
<select id="model-devon_monitor" class="model-select"></select>
|
||||
</div>
|
||||
<div class="model-routing-item">
|
||||
<span class="model-routing-label">Devon Ask</span>
|
||||
<select id="model-devon_ask" class="model-select"></select>
|
||||
</div>
|
||||
<div class="model-routing-item">
|
||||
<span class="model-routing-label">Backgrounds</span>
|
||||
<select id="model-background_gen" class="model-select"></select>
|
||||
</div>
|
||||
<div class="model-routing-item">
|
||||
<span class="model-routing-label">Call Summary</span>
|
||||
<select id="model-call_summary" class="model-select"></select>
|
||||
</div>
|
||||
<div class="model-routing-item">
|
||||
<span class="model-routing-label">News</span>
|
||||
<select id="model-news_summary" class="model-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+35
-9
@@ -493,6 +493,7 @@ async function loadAudioDevices() {
|
||||
// Channel settings
|
||||
const inputCh = document.getElementById('input-channel');
|
||||
const callerCh = document.getElementById('caller-channel');
|
||||
const devonCh = document.getElementById('devon-channel');
|
||||
const liveCallerCh = document.getElementById('live-caller-channel');
|
||||
const musicCh = document.getElementById('music-channel');
|
||||
const sfxCh = document.getElementById('sfx-channel');
|
||||
@@ -501,6 +502,7 @@ async function loadAudioDevices() {
|
||||
|
||||
if (inputCh) inputCh.value = settings.input_channel || 1;
|
||||
if (callerCh) callerCh.value = settings.caller_channel || 1;
|
||||
if (devonCh) devonCh.value = settings.devon_channel || 17;
|
||||
if (liveCallerCh) liveCallerCh.value = settings.live_caller_channel || 9;
|
||||
if (musicCh) musicCh.value = settings.music_channel || 2;
|
||||
if (sfxCh) sfxCh.value = settings.sfx_channel || 3;
|
||||
@@ -526,6 +528,7 @@ async function saveAudioDevices() {
|
||||
const outputDevice = document.getElementById('output-device')?.value;
|
||||
const inputChannel = document.getElementById('input-channel')?.value;
|
||||
const callerChannel = document.getElementById('caller-channel')?.value;
|
||||
const devonChannel = document.getElementById('devon-channel')?.value;
|
||||
const liveCallerChannel = document.getElementById('live-caller-channel')?.value;
|
||||
const musicChannel = document.getElementById('music-channel')?.value;
|
||||
const sfxChannel = document.getElementById('sfx-channel')?.value;
|
||||
@@ -541,6 +544,7 @@ async function saveAudioDevices() {
|
||||
input_channel: inputChannel ? parseInt(inputChannel) : 1,
|
||||
output_device: outputDevice ? parseInt(outputDevice) : null,
|
||||
caller_channel: callerChannel ? parseInt(callerChannel) : 1,
|
||||
devon_channel: devonChannel ? parseInt(devonChannel) : 17,
|
||||
live_caller_channel: liveCallerChannel ? parseInt(liveCallerChannel) : 9,
|
||||
music_channel: musicChannel ? parseInt(musicChannel) : 2,
|
||||
sfx_channel: sfxChannel ? parseInt(sfxChannel) : 3,
|
||||
@@ -1208,6 +1212,23 @@ async function loadSettings() {
|
||||
const ttsProvider = document.getElementById('tts-provider');
|
||||
if (ttsProvider) ttsProvider.value = data.tts_provider || 'elevenlabs';
|
||||
|
||||
// Category model routing
|
||||
const models = data.available_openrouter_models || [];
|
||||
const categoryModels = data.category_models || {};
|
||||
const categories = ['caller_dialog', 'devon_monitor', 'devon_ask', 'background_gen', 'call_summary', 'news_summary'];
|
||||
for (const cat of categories) {
|
||||
const sel = document.getElementById(`model-${cat}`);
|
||||
if (!sel) continue;
|
||||
sel.innerHTML = '';
|
||||
for (const m of models) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m;
|
||||
opt.textContent = m.split('/').pop();
|
||||
if (m === (categoryModels[cat] || data.openrouter_model)) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
|
||||
updateProviderUI();
|
||||
console.log('Settings loaded:', data.provider, 'TTS:', data.tts_provider);
|
||||
} catch (err) {
|
||||
@@ -1217,9 +1238,7 @@ async function loadSettings() {
|
||||
|
||||
|
||||
function updateProviderUI() {
|
||||
const isOpenRouter = document.getElementById('provider')?.value === 'openrouter';
|
||||
document.getElementById('openrouter-settings')?.classList.toggle('hidden', !isOpenRouter);
|
||||
document.getElementById('ollama-settings')?.classList.toggle('hidden', isOpenRouter);
|
||||
// Kept for compatibility — provider toggle removed from UI
|
||||
}
|
||||
|
||||
|
||||
@@ -1227,16 +1246,23 @@ async function saveSettings() {
|
||||
// Save audio devices
|
||||
await saveAudioDevices();
|
||||
|
||||
// Save LLM and TTS settings
|
||||
// Collect category model routing
|
||||
const categoryModels = {};
|
||||
const categories = ['caller_dialog', 'devon_monitor', 'devon_ask', 'background_gen', 'call_summary', 'news_summary'];
|
||||
for (const cat of categories) {
|
||||
const sel = document.getElementById(`model-${cat}`);
|
||||
if (sel) categoryModels[cat] = sel.value;
|
||||
}
|
||||
|
||||
// Save LLM, TTS, and model routing settings
|
||||
await fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
provider: document.getElementById('provider')?.value,
|
||||
openrouter_model: document.getElementById('openrouter-model')?.value,
|
||||
ollama_model: document.getElementById('ollama-model')?.value,
|
||||
ollama_host: document.getElementById('ollama-host')?.value,
|
||||
tts_provider: document.getElementById('tts-provider')?.value
|
||||
provider: 'openrouter',
|
||||
openrouter_model: categoryModels.caller_dialog || document.getElementById('model-caller_dialog')?.value,
|
||||
tts_provider: document.getElementById('tts-provider')?.value,
|
||||
category_models: categoryModels
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user