← Back to Files
Settings.cshtml
@page "/settings"
@model SettingsModel
@{
ViewData["Title"] = "Settings - KaiChat";
}
<div class="page-header">
<a href="/" class="back-link">← Back to Chat</a>
<h1>Settings</h1>
</div>
<div class="split-page settings-container">
<nav class="split-nav" aria-label="Settings categories">
<button type="button" class="active" data-settings-tab="model">Model</button>
<button type="button" data-settings-tab="notifications">Notifications</button>
<button type="button" data-settings-tab="nightscout">Nightscout</button>
<button type="button" data-settings-tab="autonomy">Autonomy</button>
<button type="button" data-settings-tab="paths">Paths</button>
</nav>
<div class="split-content">
@if (Model.Saved)
{
<div class="alert alert-success">Settings saved. Most changes take effect immediately.</div>
}
<form method="post" class="settings-form">
<section class="settings-panel active" data-settings-panel="model">
<div class="settings-panel-header">
<h2>Model</h2>
<button type="submit" class="btn btn-primary">Save Settings</button>
</div>
<div class="form-group">
<label for="apiEndpoint">API Endpoint</label>
<input type="url" id="apiEndpoint" name="ApiEndpoint" value="@Model.ApiEndpoint" class="form-input" />
<span class="form-hint">OpenAI-compatible API endpoint.</span>
</div>
<div class="form-group">
<label for="apiKey">API Key</label>
<input type="password" id="apiKey" name="ApiKey" value="@Model.ApiKey" class="form-input" />
</div>
<div class="form-group">
<label for="model">Model</label>
<input type="text" id="model" name="Model" value="@Model.Model" class="form-input" />
</div>
<div class="settings-field-grid">
<div class="form-group">
<label for="modelInputTokens">Model Input Tokens</label>
<input type="number" id="modelInputTokens" name="ModelInputTokens" value="@Model.ModelInputTokens" min="1" class="form-input" />
</div>
<div class="form-group">
<label for="modelOutputTokens">Model Output Tokens</label>
<input type="number" id="modelOutputTokens" name="ModelOutputTokens" value="@Model.ModelOutputTokens" min="1" class="form-input" />
</div>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="testMode" name="TestMode" value="true" @(Model.TestMode ? "checked" : "") />
<input type="hidden" name="TestMode" value="false" />
<span>Test Mode</span>
</label>
</div>
</section>
<section class="settings-panel" data-settings-panel="notifications">
<div class="settings-panel-header">
<h2>Notifications</h2>
</div>
<div class="form-group">
<label>Browser Notifications</label>
<div class="settings-notification-row">
<button type="button" class="btn" id="settingsNotificationBtn" onclick="enableKaiNotifications()">Check Notifications</button>
<span class="settings-notification-status" id="settingsNotificationStatus">Checking...</span>
</div>
</div>
</section>
<section class="settings-panel" data-settings-panel="nightscout">
<div class="settings-panel-header">
<h2>Nightscout</h2>
<button type="submit" class="btn btn-primary">Save Settings</button>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="nightscoutEnabled" name="NightscoutEnabled" value="true" @(Model.NightscoutEnabled ? "checked" : "") />
<input type="hidden" name="NightscoutEnabled" value="false" />
<span>Enable Nightscout tools</span>
</label>
</div>
<div class="form-group">
<label for="nightscoutBaseUrl">Nightscout URL</label>
<input type="url" id="nightscoutBaseUrl" name="NightscoutBaseUrl" value="@Model.NightscoutBaseUrl" class="form-input" placeholder="https://your-nightscout.example.com" />
</div>
<div class="form-group">
<label for="nightscoutApiSecret">API-SECRET Header</label>
<input type="password" id="nightscoutApiSecret" name="NightscoutApiSecret" value="@Model.NightscoutApiSecret" class="form-input" autocomplete="off" />
</div>
<div class="settings-field-grid">
<div class="form-group">
<label for="nightscoutDisplayUnits">Display Units</label>
<input type="text" id="nightscoutDisplayUnits" name="NightscoutDisplayUnits" value="@Model.NightscoutDisplayUnits" class="form-input" />
</div>
<div class="form-group">
<label class="checkbox-label settings-inline-check">
<input type="checkbox" id="nightscoutAttachLocalReadings" name="NightscoutAttachLocalReadings" value="true" @(Model.NightscoutAttachLocalReadings ? "checked" : "") />
<input type="hidden" name="NightscoutAttachLocalReadings" value="false" />
<span>Attach local CGM readings to every message</span>
</label>
</div>
</div>
<div class="settings-field-grid">
<div class="form-group">
<label for="nightscoutBridgeHost">Local Bridge Host</label>
<input type="text" id="nightscoutBridgeHost" name="NightscoutBridgeHost" value="@Model.NightscoutBridgeHost" class="form-input" />
</div>
<div class="form-group">
<label for="nightscoutBridgePort">Local Bridge Port</label>
<input type="number" id="nightscoutBridgePort" name="NightscoutBridgePort" value="@Model.NightscoutBridgePort" min="1" max="65535" class="form-input" />
</div>
<div class="form-group">
<label for="nightscoutBridgeReadingCount">Local Reading Count</label>
<input type="number" id="nightscoutBridgeReadingCount" name="NightscoutBridgeReadingCount" value="@Model.NightscoutBridgeReadingCount" min="1" max="50" class="form-input" />
</div>
<div class="form-group">
<label for="nightscoutBridgeTimeoutMs">Local Bridge Timeout Ms</label>
<input type="number" id="nightscoutBridgeTimeoutMs" name="NightscoutBridgeTimeoutMs" value="@Model.NightscoutBridgeTimeoutMs" min="500" max="15000" class="form-input" />
</div>
</div>
</section>
</form>
<section class="settings-panel" data-settings-panel="autonomy">
<div class="settings-panel-header">
<div>
<h2>Autonomy</h2>
<span class="settings-panel-subtitle" id="settingsAutonomyState">Loading...</span>
</div>
<button type="button" class="btn btn-primary" id="settingsAutonomySaveBtn" onclick="saveSettingsAutonomy()">Save Autonomy</button>
</div>
<div class="autonomy-stats settings-autonomy-stats">
<div>
<span>Next</span>
<strong id="settingsAutonomyNext">-</strong>
</div>
<div>
<span>Last</span>
<strong id="settingsAutonomyLast">-</strong>
</div>
<div>
<span>Today</span>
<strong id="settingsAutonomyCostToday">$0.000000</strong>
</div>
<div>
<span>Total</span>
<strong id="settingsAutonomyCostTotal">$0.000000</strong>
</div>
</div>
<div class="settings-field-grid">
<label class="timer-check">
<input id="settingsAutonomyEnabled" type="checkbox">
<span>Run scheduled steps</span>
</label>
<label class="timer-check">
<input id="settingsAutonomyProductionMode" type="checkbox">
<span>Production mode</span>
</label>
<label class="timer-check">
<input id="settingsAutonomyAllowWrites" type="checkbox">
<span>Allow continuity file writes</span>
</label>
<label class="timer-check">
<input id="settingsAutonomyAutoGuidanceReflection" type="checkbox">
<span>Auto reflection</span>
</label>
</div>
<div class="settings-field-grid">
<div class="form-group">
<label for="settingsAutonomyInterval">Interval minutes</label>
<input id="settingsAutonomyInterval" class="form-input" type="number" min="1" max="1440" value="15">
</div>
<div class="form-group">
<label for="settingsAutonomyMaxOutput">Max output tokens</label>
<input id="settingsAutonomyMaxOutput" class="form-input" type="number" min="200" max="6000" value="900">
</div>
<div class="form-group">
<label for="settingsAutonomyBudget">Daily estimate cap</label>
<input id="settingsAutonomyBudget" class="form-input" type="number" min="0" max="50" step="0.01" value="0">
</div>
<div class="form-group">
<label for="settingsAutonomyGuidanceReflectionInterval">Reflection steps</label>
<input id="settingsAutonomyGuidanceReflectionInterval" class="form-input" type="number" min="1" max="200" value="12">
</div>
</div>
<div class="settings-section">
<h2>Optional Message Pacing</h2>
<div class="settings-field-grid">
<label class="timer-check">
<input id="settingsAutonomyEnableMaxMessages" type="checkbox">
<span>Use daily message max</span>
</label>
<div class="form-group">
<label for="settingsAutonomyMaxMessages">Max messages per day</label>
<input id="settingsAutonomyMaxMessages" class="form-input" type="number" min="1" max="200" value="12">
</div>
<label class="timer-check">
<input id="settingsAutonomyEnableCooldown" type="checkbox">
<span>Use message cooldown</span>
</label>
<div class="form-group">
<label for="settingsAutonomyCooldown">Cooldown minutes</label>
<input id="settingsAutonomyCooldown" class="form-input" type="number" min="1" max="1440" value="60">
</div>
<label class="timer-check">
<input id="settingsAutonomyEnableQuietHours" type="checkbox">
<span>Use quiet hours</span>
</label>
<div class="settings-field-grid settings-time-grid">
<div class="form-group">
<label for="settingsAutonomyQuietStart">Quiet hours start</label>
<input id="settingsAutonomyQuietStart" class="form-input" type="time" value="22:00">
</div>
<div class="form-group">
<label for="settingsAutonomyQuietEnd">Quiet hours end</label>
<input id="settingsAutonomyQuietEnd" class="form-input" type="time" value="08:00">
</div>
</div>
</div>
</div>
<div id="settingsAutonomyMessage" class="timer-message"></div>
</section>
<section class="settings-panel" data-settings-panel="paths">
<div class="settings-panel-header">
<h2>Paths</h2>
</div>
<div class="settings-info">
<p><strong>Base Folder:</strong> @Model.BaseFolder</p>
<p><strong>System Prompt Files:</strong></p>
<ul>
@foreach (var f in Model.SystemPromptFiles)
{
<li>@f</li>
}
</ul>
</div>
</section>
</div>
</div>
@section Scripts {
<script>
let settingsAutonomyStatus = null;
function activateSettingsPanel(name) {
const hasTab = Array.from(document.querySelectorAll('[data-settings-tab]'))
.some(function (button) { return button.dataset.settingsTab === name; });
if (!hasTab) {
name = 'model';
}
document.querySelectorAll('[data-settings-tab]').forEach(function (button) {
button.classList.toggle('active', button.dataset.settingsTab === name);
});
document.querySelectorAll('[data-settings-panel]').forEach(function (panel) {
panel.classList.toggle('active', panel.dataset.settingsPanel === name);
});
if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
}
function settingsFormatTime(value) {
if (!value) return '-';
const date = new Date(value);
return Number.isNaN(date.getTime()) ? '-' : date.toLocaleString();
}
function settingsFormatUsd(value) {
const num = Number(value || 0);
return '$' + num.toFixed(6);
}
function setSettingsAutonomyMessage(message, isError) {
const el = document.getElementById('settingsAutonomyMessage');
if (!el) return;
el.textContent = message || '';
el.classList.toggle('error', !!isError);
}
function getSettingsAutonomyPayload() {
return {
enabled: document.getElementById('settingsAutonomyEnabled').checked,
productionMode: document.getElementById('settingsAutonomyProductionMode').checked,
intervalMinutes: parseInt(document.getElementById('settingsAutonomyInterval').value || '15', 10),
maxOutputTokens: parseInt(document.getElementById('settingsAutonomyMaxOutput').value || '900', 10),
dailyBudgetUsd: parseFloat(document.getElementById('settingsAutonomyBudget').value || '0'),
allowSandboxFileWrites: document.getElementById('settingsAutonomyAllowWrites').checked,
autoGuidanceReflection: document.getElementById('settingsAutonomyAutoGuidanceReflection').checked,
guidanceReflectionStepInterval: parseInt(document.getElementById('settingsAutonomyGuidanceReflectionInterval').value || '12', 10),
enableMaxVisibleMessagesPerDay: document.getElementById('settingsAutonomyEnableMaxMessages').checked,
maxVisibleMessagesPerDay: parseInt(document.getElementById('settingsAutonomyMaxMessages').value || '12', 10),
enableVisibleMessageCooldown: document.getElementById('settingsAutonomyEnableCooldown').checked,
visibleMessageCooldownMinutes: parseInt(document.getElementById('settingsAutonomyCooldown').value || '60', 10),
enableQuietHours: document.getElementById('settingsAutonomyEnableQuietHours').checked,
quietHoursStart: document.getElementById('settingsAutonomyQuietStart').value || '22:00',
quietHoursEnd: document.getElementById('settingsAutonomyQuietEnd').value || '08:00'
};
}
function renderSettingsAutonomy(status) {
settingsAutonomyStatus = status || {};
const settings = settingsAutonomyStatus.settings || {};
document.getElementById('settingsAutonomyEnabled').checked = !!settings.enabled;
document.getElementById('settingsAutonomyProductionMode').checked = !!settings.productionMode;
document.getElementById('settingsAutonomyInterval').value = settings.intervalMinutes || 15;
document.getElementById('settingsAutonomyMaxOutput').value = settings.maxOutputTokens || 900;
document.getElementById('settingsAutonomyBudget').value = settings.dailyBudgetUsd ?? 0;
document.getElementById('settingsAutonomyAllowWrites').checked = settings.allowSandboxFileWrites !== false;
document.getElementById('settingsAutonomyAutoGuidanceReflection').checked = settings.autoGuidanceReflection !== false;
document.getElementById('settingsAutonomyGuidanceReflectionInterval').value = settings.guidanceReflectionStepInterval || 12;
document.getElementById('settingsAutonomyEnableMaxMessages').checked = !!settings.enableMaxVisibleMessagesPerDay;
document.getElementById('settingsAutonomyMaxMessages').value = settings.maxVisibleMessagesPerDay || 12;
document.getElementById('settingsAutonomyEnableCooldown').checked = !!settings.enableVisibleMessageCooldown;
document.getElementById('settingsAutonomyCooldown').value = settings.visibleMessageCooldownMinutes || 60;
document.getElementById('settingsAutonomyEnableQuietHours').checked = !!settings.enableQuietHours;
document.getElementById('settingsAutonomyQuietStart').value = settings.quietHoursStart || '22:00';
document.getElementById('settingsAutonomyQuietEnd').value = settings.quietHoursEnd || '08:00';
document.getElementById('settingsAutonomyState').textContent = settingsAutonomyStatus.isRunning
? 'Running'
: (settings.enabled ? 'Scheduled' : 'Stopped');
document.getElementById('settingsAutonomyNext').textContent = settingsFormatTime(settingsAutonomyStatus.nextDueUtc);
document.getElementById('settingsAutonomyLast').textContent = settingsFormatTime(settingsAutonomyStatus.lastStepUtc);
document.getElementById('settingsAutonomyCostToday').textContent = settingsFormatUsd(settingsAutonomyStatus.estimatedCostCacheMissTodayUsd);
document.getElementById('settingsAutonomyCostTotal').textContent = settingsFormatUsd(settingsAutonomyStatus.estimatedCostCacheMissUsd);
}
async function loadSettingsAutonomy() {
try {
const response = await fetch('/api/autonomy/status');
const data = await response.json();
if (!response.ok) throw new Error('Could not load autonomy settings.');
renderSettingsAutonomy(data);
} catch (error) {
document.getElementById('settingsAutonomyState').textContent = 'Error';
setSettingsAutonomyMessage(error.message || String(error), true);
}
}
async function saveSettingsAutonomy() {
const button = document.getElementById('settingsAutonomySaveBtn');
button.disabled = true;
setSettingsAutonomyMessage('Saving...', false);
try {
const response = await fetch('/api/autonomy/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(getSettingsAutonomyPayload())
});
const data = await response.json();
if (!response.ok) throw new Error((data && data.message) || 'Could not save autonomy settings.');
renderSettingsAutonomy(data);
setSettingsAutonomyMessage('Autonomy settings saved.', false);
} catch (error) {
setSettingsAutonomyMessage(error.message || String(error), true);
} finally {
button.disabled = false;
}
}
window.saveSettingsAutonomy = saveSettingsAutonomy;
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-settings-tab]').forEach(function (button) {
button.addEventListener('click', function () {
activateSettingsPanel(button.dataset.settingsTab);
});
});
const requested = (location.hash || '').replace('#', '');
activateSettingsPanel(requested || 'model');
loadSettingsAutonomy();
});
</script>
}