diff --git a/Endpoints/SettingsEndpoint.cs b/Endpoints/SettingsEndpoint.cs new file mode 100644 index 0000000..c1d6a0f --- /dev/null +++ b/Endpoints/SettingsEndpoint.cs @@ -0,0 +1,35 @@ +using System.Text.Json; +using Gott.Extensions; +using Gott.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Gott.Endpoints; + +public static class SettingsEndpoint +{ + public static void MapSettingsEndpoints(this WebApplication app) + { + app.MapGet("/api/settings", HandleGet); + app.MapPut("/api/settings", HandleUpdate); + } + + private static async System.Threading.Tasks.Task HandleGet(HttpContext ctx, DatabaseService db) + { + var userId = ctx.GetUserId(); + var json = await db.GetUserSettings(userId); + ctx.Response.ContentType = "application/json"; + await ctx.Response.WriteAsync(json ?? "{}"); + } + + private static async System.Threading.Tasks.Task HandleUpdate(HttpContext ctx, DatabaseService db) + { + var userId = ctx.GetUserId(); + var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); + var json = body.ToString(); + await db.UpdateUserSettings(userId, json); + ctx.Response.ContentType = "application/json"; + await ctx.Response.WriteAsync(json); + } +} diff --git a/Models/UserRecord.cs b/Models/UserRecord.cs index 39a56c3..205fdd8 100644 --- a/Models/UserRecord.cs +++ b/Models/UserRecord.cs @@ -11,6 +11,7 @@ public class UserRecord public string UpdatedAt { get; set; } public bool IsAdmin { get; set; } public string Persona { get; set; } + public string Settings { get; set; } public string GetEffectivePersona() { diff --git a/Program.cs b/Program.cs index 8d5ff89..5440c14 100644 --- a/Program.cs +++ b/Program.cs @@ -54,6 +54,7 @@ app.MapInfoEndpoint(); app.MapBackupEndpoint(); app.MapSkillEndpoints(); app.MapProfileEndpoints(); +app.MapSettingsEndpoints(); app.MapTelegramEndpoints(); app.Run("http://0.0.0.0:19000"); diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index b615ce2..52a245a 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -162,6 +162,7 @@ public class DatabaseService TryAddColumn(conn, "agents", "user_id", "TEXT"); TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "users", "persona", "TEXT"); + TryAddColumn(conn, "users", "settings", "TEXT"); conn.Execute(@" CREATE TABLE IF NOT EXISTS agent_skills ( @@ -792,6 +793,22 @@ public class DatabaseService new { userId, displayName, persona, now = DateTime.UtcNow.ToString("o") }); } + public async Task GetUserSettings(string userId) + { + using var conn = CreateConnection(); + return await conn.QuerySingleOrDefaultAsync( + "SELECT settings FROM users WHERE user_id = @userId", + new { userId }); + } + + public async Task UpdateUserSettings(string userId, string settings) + { + using var conn = CreateConnection(); + await conn.ExecuteAsync( + "UPDATE users SET settings = @settings, updated_at = @now WHERE user_id = @userId", + new { userId, settings, now = DateTime.UtcNow.ToString("o") }); + } + // ── Telegram ── public async Task CreateTelegramLinkCode(string userId, string sessionId, string agentId) diff --git a/wwwroot/css/main.css b/wwwroot/css/main.css index c7dada8..1508286 100644 --- a/wwwroot/css/main.css +++ b/wwwroot/css/main.css @@ -79,7 +79,6 @@ header { display: flex; flex-direction: column; overflow: hidden; - transition: var(--transition-duration); } .page-content { @@ -96,4 +95,5 @@ header, width: var(--page-width); max-width: 100%; align-self: center; + transition: width var(--transition-duration); } diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index b06757e..e467b17 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -306,6 +306,21 @@ async function apiTelegramDeleteWebhook() { return resp.json(); } +async function apiGetSettings() { + const resp = await fetch('/api/settings'); + if (!resp.ok) return {}; + return resp.json(); +} + +async function apiSaveSettings(settings) { + const resp = await fetch('/api/settings', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(settings) + }); + return resp.json(); +} + async function apiBackupDb() { const resp = await fetch('/api/backup', { method: 'POST', credentials: 'include' }); if (!resp.ok) throw new Error('Backup fehlgeschlagen'); diff --git a/wwwroot/js/app.js b/wwwroot/js/app.js index b801c31..de9d383 100644 --- a/wwwroot/js/app.js +++ b/wwwroot/js/app.js @@ -1,5 +1,28 @@ -// ── Global User State ── +// ── Global State ── var currentUser = null; +var currentSettings = null; + +const SETTINGS_DEFAULTS = { + pageWidth: 900, + toolCallDisplay: 'modifications', + chainMetaDisplay: 'sometimes', + showDeletedMessages: false +}; + +function loadSettings() { + return Object.assign({}, SETTINGS_DEFAULTS, currentSettings); +} + +async function saveSettings(settings) { + currentSettings = settings; + applySettings(); + try { await apiSaveSettings(settings); } catch (_) {} +} + +function applySettings() { + var s = loadSettings(); + document.documentElement.style.setProperty('--page-width', s.pageWidth + 'px'); +} // ── App Init ── @@ -12,6 +35,8 @@ document.addEventListener('DOMContentLoaded', async () => { } currentUser = user; + currentSettings = await apiGetSettings(); + applySettings(); loadClaudeStatus(); const { page, id } = parseHash(); diff --git a/wwwroot/js/pages/settings-page.js b/wwwroot/js/pages/settings-page.js index 252fa63..36702a8 100644 --- a/wwwroot/js/pages/settings-page.js +++ b/wwwroot/js/pages/settings-page.js @@ -1,38 +1,5 @@ // ── Settings Page ── -const SETTINGS_KEY = 'settings'; - -const SETTINGS_DEFAULTS = { - pageWidth: 900, - toolCallDisplay: 'modifications', // 'all' | 'modifications' | 'hidden' - chainMetaDisplay: 'sometimes', // 'always' | 'sometimes' | 'never' - showDeletedMessages: false -}; - -function loadSettings() { - try { - const raw = localStorage.getItem(SETTINGS_KEY); - if (raw) return Object.assign({}, SETTINGS_DEFAULTS, JSON.parse(raw)); - } catch (_) {} - return Object.assign({}, SETTINGS_DEFAULTS); -} - -function saveSettings(settings) { - localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); -} - -function resetSettings() { - localStorage.removeItem(SETTINGS_KEY); -} - -function applySettings() { - const s = loadSettings(); - document.documentElement.style.setProperty('--page-width', s.pageWidth + 'px'); -} - -// Apply on load -applySettings(); - registerPage('settings', { init() { const slider = document.getElementById('settingPageWidth'); @@ -62,26 +29,25 @@ registerPage('settings', { }); slider.addEventListener('change', () => { - const s = loadSettings(); + var s = loadSettings(); s.pageWidth = parseInt(slider.value, 10); saveSettings(s); - applySettings(); }); toolCallSelect.addEventListener('change', () => { - const s = loadSettings(); + var s = loadSettings(); s.toolCallDisplay = toolCallSelect.value; saveSettings(s); }); chainMetaSelect.addEventListener('change', () => { - const s = loadSettings(); + var s = loadSettings(); s.chainMetaDisplay = chainMetaSelect.value; saveSettings(s); }); showDeletedToggle.addEventListener('change', () => { - const s = loadSettings(); + var s = loadSettings(); s.showDeletedMessages = showDeletedToggle.checked; saveSettings(s); }); @@ -100,10 +66,9 @@ registerPage('settings', { } }); - resetBtn.addEventListener('click', () => { + resetBtn.addEventListener('click', async () => { if (!confirm('Alle Einstellungen auf Standard zurücksetzen?')) return; - resetSettings(); - applySettings(); + await saveSettings(Object.assign({}, SETTINGS_DEFAULTS)); // Update UI slider.value = SETTINGS_DEFAULTS.pageWidth; display.textContent = SETTINGS_DEFAULTS.pageWidth + 'px';