Size: 21.2 KB Modified: 5/07/2026 7:08 PM
@page "/settings"
@model SettingsModel
@{
    ViewData["Title"] = "Settings - KaiChat";
}

<div class="page-header">
    <a href="/" class="back-link">&larr; 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>
}
Offline