feat(webchat): add personal assistant mode controls in settings

This commit is contained in:
William Valentin
2026-02-18 12:04:37 -08:00
parent a8bb9f23ac
commit 43b9324c14
5 changed files with 257 additions and 2 deletions
+114
View File
@@ -19,6 +19,7 @@ function escapeHtml(text) {
let _client = null;
let _el = null;
let _settingsCache = null;
function describePushStatus(status) {
if (!status.supported) {
@@ -114,6 +115,18 @@ async function loadSettings() {
const confirmPatterns = hooks.confirm ?? [];
const logPatterns = hooks.log ?? [];
const silentPatterns = hooks.silent ?? [];
const automation = config?.automation ?? {};
const memory = config?.memory ?? {};
const tts = config?.tts ?? {};
_settingsCache = config ?? {};
const deliveryMode = automation.delivery_mode ?? 'shared_session';
const dailyBriefingEnabled = Boolean(automation.daily_briefing?.enabled);
const dailyMemoryEnabled = Boolean(memory.daily_log?.enabled);
const proactiveExtractEnabled = Boolean(memory.proactive_extract?.enabled);
const proactiveMinToolCalls = Number(memory.proactive_extract?.min_tool_calls ?? 1);
const ttsEnabled = Boolean(tts.enabled);
const ttsChannelText = Array.isArray(tts.enabled_channels) ? tts.enabled_channels.join(', ') : '';
// Build config view (redacted JSON)
const configJson = JSON.stringify(config, null, 2);
@@ -127,6 +140,44 @@ async function loadSettings() {
_el.innerHTML = `
<h1 class="page-title">Settings</h1>
<h2 class="section-title">Personal Assistant Mode</h2>
<div class="settings-section">
<div class="assistant-mode-grid">
<label class="assistant-toggle">
<input id="assist-delivery-announce" type="checkbox" ${deliveryMode === 'announce' ? 'checked' : ''} />
<span>Automation announce delivery mode</span>
</label>
<label class="assistant-toggle">
<input id="assist-daily-briefing" type="checkbox" ${dailyBriefingEnabled ? 'checked' : ''} />
<span>Daily briefing enabled</span>
</label>
<label class="assistant-toggle">
<input id="assist-memory-daily" type="checkbox" ${dailyMemoryEnabled ? 'checked' : ''} />
<span>Daily memory logging</span>
</label>
<label class="assistant-toggle">
<input id="assist-memory-proactive" type="checkbox" ${proactiveExtractEnabled ? 'checked' : ''} />
<span>Proactive memory extraction</span>
</label>
<label class="assistant-field">
<span>Proactive extract tool-call threshold</span>
<input id="assist-memory-min-tools" type="number" min="0" max="50" value="${Number.isFinite(proactiveMinToolCalls) ? proactiveMinToolCalls : 1}" />
</label>
<label class="assistant-toggle">
<input id="assist-tts-enabled" type="checkbox" ${ttsEnabled ? 'checked' : ''} />
<span>TTS voice replies enabled</span>
</label>
<label class="assistant-field">
<span>TTS channels (comma-separated, blank = all)</span>
<input id="assist-tts-channels" type="text" value="${escapeHtml(ttsChannelText)}" placeholder="telegram,discord,whatsapp" />
</label>
</div>
<div class="assistant-actions">
<button id="assistant-mode-save" class="btn btn-primary">Save Assistant Mode</button>
<span id="assistant-mode-status" class="text-sm text-muted"></span>
</div>
</div>
<h2 class="section-title">WebChat Push Notifications</h2>
<div class="settings-section">
${isPushSupported() ? '' : '<div class="text-sm text-muted">This browser does not support PushManager APIs.</div>'}
@@ -218,11 +269,73 @@ async function loadSettings() {
// Bind save hooks
_el.querySelector('#hooks-save').addEventListener('click', saveHooks);
_el.querySelector('#assistant-mode-save').addEventListener('click', saveAssistantMode);
_el.querySelector('#push-enable').addEventListener('click', onEnablePush);
_el.querySelector('#push-disable').addEventListener('click', onDisablePush);
await renderPushStatus();
}
async function saveAssistantMode() {
const status = _el.querySelector('#assistant-mode-status');
status.textContent = 'Saving...';
status.className = 'text-sm text-muted';
const useAnnounce = Boolean(_el.querySelector('#assist-delivery-announce')?.checked);
const dailyBriefing = Boolean(_el.querySelector('#assist-daily-briefing')?.checked);
const memoryDaily = Boolean(_el.querySelector('#assist-memory-daily')?.checked);
const memoryProactive = Boolean(_el.querySelector('#assist-memory-proactive')?.checked);
const ttsEnabled = Boolean(_el.querySelector('#assist-tts-enabled')?.checked);
const minToolsRaw = Number.parseInt(_el.querySelector('#assist-memory-min-tools')?.value ?? '1', 10);
const minTools = Number.isFinite(minToolsRaw) ? Math.min(50, Math.max(0, minToolsRaw)) : 1;
const ttsChannelsRaw = _el.querySelector('#assist-tts-channels')?.value ?? '';
const ttsChannels = ttsChannelsRaw
.split(',')
.map((value) => value.trim())
.filter(Boolean);
const patches = {
'automation.delivery_mode': useAnnounce ? 'announce' : 'shared_session',
'automation.daily_briefing.enabled': dailyBriefing,
'memory.daily_log.enabled': memoryDaily,
'memory.proactive_extract.enabled': memoryProactive,
'memory.proactive_extract.min_tool_calls': minTools,
'tts.enabled': ttsEnabled,
'tts.enabled_channels': ttsChannels,
};
try {
const result = await _client.call('config.patch', { patches });
const applied = result.applied ?? [];
const rejected = result.rejected ?? [];
const persisted = result.persisted === true;
const persistError = result.persistError;
if (rejected.length > 0) {
status.textContent = `Partially saved. Rejected: ${rejected.join(', ')}`;
status.className = 'text-sm text-error';
} else if (persistError) {
status.textContent = `Save failed: ${persistError}`;
status.className = 'text-sm text-error';
} else if (!persisted) {
status.textContent = `Saved in runtime only (${applied.length} updated)`;
status.className = 'text-sm text-muted';
} else {
status.textContent = `Saved (${applied.length} updated)`;
status.className = 'text-sm text-success';
if (_settingsCache && _settingsCache.automation) {
_settingsCache.automation.delivery_mode = useAnnounce ? 'announce' : 'shared_session';
}
}
} catch (err) {
status.textContent = `Error: ${err.message}`;
status.className = 'text-sm text-error';
}
setTimeout(() => {
if (status) {status.textContent = '';}
}, 5000);
}
async function saveHooks() {
const status = _el.querySelector('#hooks-status');
status.textContent = 'Saving...';
@@ -280,5 +393,6 @@ export const SettingsPage = {
teardown() {
_client = null;
_el = null;
_settingsCache = null;
},
};