diff --git a/Endpoints/AgentEndpoint.cs b/Endpoints/AgentEndpoint.cs index e50551d..7c7ece3 100644 --- a/Endpoints/AgentEndpoint.cs +++ b/Endpoints/AgentEndpoint.cs @@ -64,7 +64,8 @@ public static class AgentEndpoint UseUserInfo = !body.TryGetProperty("useUserInfo", out var uu) || uu.GetBoolean(), UseMemory = !body.TryGetProperty("useMemory", out var um) || um.GetBoolean(), UseWebSearch = body.TryGetProperty("webSearchVersion", out var wsv) && !string.IsNullOrEmpty(wsv.GetString()), - WebSearchVersion = body.TryGetProperty("webSearchVersion", out var wsv2) ? wsv2.GetString() ?? "" : "" + WebSearchVersion = body.TryGetProperty("webSearchVersion", out var wsv2) ? wsv2.GetString() ?? "" : "", + AvatarScale = body.TryGetProperty("avatarScale", out var asc) ? asc.GetDouble() : 1.0 }; agent = await db.CreateAgent(agent); @@ -100,6 +101,7 @@ public static class AgentEndpoint agent.WebSearchVersion = wsv.GetString() ?? ""; agent.UseWebSearch = !string.IsNullOrEmpty(agent.WebSearchVersion); } + if (body.TryGetProperty("avatarScale", out var asc)) agent.AvatarScale = asc.GetDouble(); await db.UpdateAgent(agent); memoryService.InitializeAgentFiles(agentId, agent.Name); diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index 36e472b..46c4f8d 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -76,8 +76,9 @@ public static class SessionEndpoint } var context = body.TryGetProperty("context", out var ctxEl) ? ctxEl.GetString() : null; var customCommands = body.TryGetProperty("customCommands", out var cc) && cc.ValueKind != JsonValueKind.Null ? cc.GetRawText() : null; + var avatarScale = body.TryGetProperty("avatarScale", out var asc) ? asc.GetDouble() : 1.0; - var session = await db.CreateSession(userId, name, description, image, agentId, workspace, context, customCommands); + var session = await db.CreateSession(userId, name, description, image, agentId, workspace, context, customCommands, avatarScale); await ctx.Response.WriteAsJsonAsync(session); } diff --git a/Models/AgentRecord.cs b/Models/AgentRecord.cs index af90eec..d273183 100644 --- a/Models/AgentRecord.cs +++ b/Models/AgentRecord.cs @@ -17,6 +17,7 @@ public class AgentRecord public bool UseMemory { get; set; } = true; public bool UseWebSearch { get; set; } public string WebSearchVersion { get; set; } = ""; + public double AvatarScale { get; set; } = 1.0; public string UserId { get; set; } public bool IsDeleted { get; set; } public string CreatedAt { get; set; } diff --git a/Models/SessionRecord.cs b/Models/SessionRecord.cs index e15352c..a1eca00 100644 --- a/Models/SessionRecord.cs +++ b/Models/SessionRecord.cs @@ -16,6 +16,7 @@ public class SessionRecord public string TelegramBotToken { get; set; } public string TelegramWebhookSecret { get; set; } public string TelegramChatId { get; set; } + public double AvatarScale { get; set; } = 1.0; public string CreatedAt { get; set; } public string UpdatedAt { get; set; } } diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 8813387..dfd9ddb 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -167,6 +167,8 @@ public class DatabaseService TryAddColumn(conn, "users", "persona", "TEXT"); TryAddColumn(conn, "users", "settings", "TEXT"); TryAddColumn(conn, "users", "default_path", "TEXT"); + TryAddColumn(conn, "sessions", "avatar_scale", "REAL NOT NULL DEFAULT 1.0"); + TryAddColumn(conn, "agents", "avatar_scale", "REAL NOT NULL DEFAULT 1.0"); TryAddColumn(conn, "cronjobs", "notify_session_id", "TEXT"); conn.Execute(@" @@ -320,8 +322,8 @@ public class DatabaseService agent.CreatedAt = now; agent.UpdatedAt = now; await conn.ExecuteAsync(@" - INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, is_deleted, created_at, updated_at) - VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, 0, @CreatedAt, @UpdatedAt) + INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, avatar_scale, is_deleted, created_at, updated_at) + VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, @AvatarScale, 0, @CreatedAt, @UpdatedAt) ", agent); return agent; } @@ -349,7 +351,7 @@ public class DatabaseService model = @Model, tool_choice = @ToolChoice, custom_commands = @CustomCommands, auto_commit = @AutoCommit, use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion, - updated_at = @UpdatedAt + avatar_scale = @AvatarScale, updated_at = @UpdatedAt WHERE agent_id = @AgentId AND user_id = @UserId ", agent); } @@ -364,7 +366,7 @@ public class DatabaseService // ── Sessions ── - public async Task CreateSession(string userId, string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null) + public async Task CreateSession(string userId, string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null, double avatarScale = 1.0) { using var conn = CreateConnection(); var now = DateTime.UtcNow.ToString("o"); @@ -379,12 +381,13 @@ public class DatabaseService Workspace = workspace, Context = context, CustomCommands = customCommands, + AvatarScale = avatarScale, CreatedAt = now, UpdatedAt = now }; await conn.ExecuteAsync(@" - INSERT INTO sessions (session_id, user_id, name, description, image, agent_id, workspace, context, custom_commands, created_at, updated_at) - VALUES (@SessionId, @UserId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @CreatedAt, @UpdatedAt) + INSERT INTO sessions (session_id, user_id, name, description, image, agent_id, workspace, context, custom_commands, avatar_scale, created_at, updated_at) + VALUES (@SessionId, @UserId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @AvatarScale, @CreatedAt, @UpdatedAt) ", session); return session; } @@ -427,6 +430,7 @@ public class DatabaseService s.Workspace, s.Context, s.CustomCommands, + s.AvatarScale, s.IsDeleted, s.CreatedAt, s.UpdatedAt, @@ -476,6 +480,7 @@ public class DatabaseService if (body.TryGetProperty("workspace", out var w)) { sets.Add("workspace = @workspace"); p.Add("workspace", w.GetString()); } if (body.TryGetProperty("context", out var ctx)) { sets.Add("context = @context"); p.Add("context", ctx.GetString()); } if (body.TryGetProperty("customCommands", out var cc)) { sets.Add("custom_commands = @customCommands"); p.Add("customCommands", cc.ValueKind == System.Text.Json.JsonValueKind.Null ? null : cc.GetRawText()); } + if (body.TryGetProperty("avatarScale", out var asc)) { sets.Add("avatar_scale = @avatarScale"); p.Add("avatarScale", asc.GetDouble()); } await conn.ExecuteAsync($"UPDATE sessions SET {string.Join(", ", sets)} WHERE session_id = @sessionId", p); } diff --git a/wwwroot/css/form.css b/wwwroot/css/form.css index 3f34d1c..8e53ad7 100644 --- a/wwwroot/css/form.css +++ b/wwwroot/css/form.css @@ -22,8 +22,8 @@ } .avatar-preview { - width: 64px; - height: 64px; + width: 8em; + height: 8em; background: var(--f2-bg); border: var(--border-width) solid var(--f2-border); display: flex; @@ -38,6 +38,32 @@ width: 100%; height: 100%; object-fit: cover; + transition: scale var(--transition-duration); +} + +.avatar-scale-row { + display: flex; + align-items: center; + gap: var(--badge-spacing); + flex: 1; + padding: var(--box-padding); +} +.avatar-scale-row label { + margin: 0; +} +.avatar-scale-row input[type="range"] { + flex: 1; + accent-color: var(--primary); +} +.avatar-scale-row span { + font-size: 0.85em; + min-width: 2.5em; + text-align: right; + opacity: 0.7; +} + +.avatar-ai-section { + width: 100%; } .input-with-btn { diff --git a/wwwroot/index.html b/wwwroot/index.html index cb0652c..3c3a4e1 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -106,6 +106,17 @@ + + @@ -164,6 +175,17 @@ + + diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index fee8a41..3b94419 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -472,6 +472,27 @@ async function apiAdminImpersonate(userId) { return resp.json(); } +// ── Krea Image API ── + +async function apiKreaStatus() { + const resp = await fetch('/api/images/status'); + if (!resp.ok) return { configured: false }; + return resp.json(); +} + +async function apiKreaGenerate(prompt, model, width, height) { + const resp = await fetch('/api/images/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ prompt, model: model || 'flux', width: width || 512, height: height || 512, batchSize: 1 }) + }); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + throw new Error(err.error || 'Bildgenerierung fehlgeschlagen'); + } + return resp.json(); +} + // ── Backup API ── async function apiBackupDb() { diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js index a3afa63..311fbcd 100644 --- a/wwwroot/js/pages/agent-page.js +++ b/wwwroot/js/pages/agent-page.js @@ -38,8 +38,19 @@ registerPage('agent', { const skillList = document.getElementById('agentSkillList'); const more = document.querySelector('.more'); + const scaleRow = document.getElementById('agentScaleRow'); + const scaleInput = document.getElementById('agentAvatarScale'); + const scaleValue = document.getElementById('agentScaleValue'); + _agentAvatarData = null; + function updatePreviewScale() { + var img = avatarPreview.querySelector('img'); + if (img) img.style.scale = scaleInput.value; + scaleValue.textContent = scaleInput.value + '×'; + } + scaleInput.addEventListener('input', updatePreviewScale); + if (agent) { title.textContent = agent.name; nameEl.value = agent.name || ''; @@ -55,8 +66,14 @@ registerPage('agent', { _agentAvatarData = agent.avatar || null; deleteBtn.hidden = false; + if (agent.avatarScale && agent.avatarScale !== 1) { + scaleInput.value = agent.avatarScale; + scaleValue.textContent = agent.avatarScale + '×'; + } + if (agent.avatar) { - avatarPreview.innerHTML = `Avatar`; + avatarPreview.innerHTML = `Avatar`; + scaleRow.hidden = false; } // Custom Commands laden @@ -82,6 +99,43 @@ registerPage('agent', { // Befehl hinzufügen addCommandBtn.addEventListener('click', () => createCommandRow(commandsList)); + // AI Bild-Generierung (nur wenn Krea konfiguriert) + const aiSection = document.getElementById('agentAiSection'); + const aiPrompt = document.getElementById('agentAiPrompt'); + const aiGenerateBtn = document.getElementById('agentAiGenerateBtn'); + apiKreaStatus().then(status => { + if (status.configured) aiSection.hidden = false; + }); + aiGenerateBtn.addEventListener('click', async () => { + const prompt = aiPrompt.value.trim(); + if (!prompt) return; + aiGenerateBtn.disabled = true; + aiGenerateBtn.innerHTML = ''; + try { + const result = await apiKreaGenerate(prompt); + if (result.files && result.files.length > 0) { + const imgResp = await fetch('/api/images/file/' + result.files[0]); + const blob = await imgResp.blob(); + const reader = new FileReader(); + reader.onload = () => { + _agentAvatarData = reader.result; + avatarPreview.innerHTML = `Avatar`; + scaleRow.hidden = false; + updatePreviewScale(); + }; + reader.readAsDataURL(blob); + } + } catch (e) { + showToast('Fehler: ' + e.message, 'error'); + } finally { + aiGenerateBtn.disabled = false; + aiGenerateBtn.innerHTML = ''; + } + }); + aiPrompt.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); aiGenerateBtn.click(); } + }); + // Avatar Upload avatarBtn.addEventListener('click', () => avatarInput.click()); avatarInput.addEventListener('change', () => { @@ -91,6 +145,8 @@ registerPage('agent', { reader.onload = () => { _agentAvatarData = reader.result; avatarPreview.innerHTML = `Avatar`; + scaleRow.hidden = false; + updatePreviewScale(); }; reader.readAsDataURL(file); }); @@ -99,6 +155,9 @@ registerPage('agent', { _agentAvatarData = null; avatarPreview.innerHTML = '?'; avatarInput.value = ''; + scaleRow.hidden = true; + scaleInput.value = 1; + scaleValue.textContent = '1×'; }); // Avatar Paste (Ctrl+V) @@ -114,6 +173,8 @@ registerPage('agent', { reader.onload = () => { _agentAvatarData = reader.result; avatarPreview.innerHTML = `Avatar`; + scaleRow.hidden = false; + updatePreviewScale(); }; reader.readAsDataURL(file); return; @@ -142,6 +203,7 @@ registerPage('agent', { useMemory: useMemoryEl.checked, webSearchVersion: webSearchVersionEl.value, avatar: _agentAvatarData, + avatarScale: parseFloat(scaleInput.value) || 1, customCommands: getCommandsFromUI(commandsList) }; diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index 6c71ae9..29912db 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -51,8 +51,9 @@ registerPage('dashboard', { if (s.context) sessionBadges.push(``); if (s.telegramConfigured) sessionBadges.push(``); + const scaleStyle = s.avatarScale && s.avatarScale !== 1 ? ` style="scale: ${s.avatarScale}"` : ''; card.innerHTML = ` -
${s.image ? `` : ''}
+
${s.image ? `` : ''}
${esc(s.name || 'Unbenannt')} @@ -197,8 +198,9 @@ registerPage('dashboard', { badges.push(` ${esc(skill.name)}`); } + const agentScaleStyle = a.avatarScale && a.avatarScale !== 1 ? ` style="transform: scale(${a.avatarScale})"` : ''; card.innerHTML = ` -
${a.avatar ? `` : ''}
+
${a.avatar ? `` : ''}
${esc(a.name)} diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index 3348566..2e7eb1c 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -31,9 +31,20 @@ registerPage('session', { const commandsList = document.getElementById('customCommandsList'); const addCommandBtn = document.getElementById('addCommandBtn'); const more = document.querySelector('.more'); - + + const scaleRow = document.getElementById('sessionScaleRow'); + const scaleInput = document.getElementById('sessionAvatarScale'); + const scaleValue = document.getElementById('sessionScaleValue'); + _sessionImageData = null; + function updatePreviewScale() { + var img = imagePreview.querySelector('img'); + if (img) img.style.scale = scaleInput.value; + scaleValue.textContent = scaleInput.value + '×'; + } + scaleInput.addEventListener('input', updatePreviewScale); + if (session) { titleEl.textContent = session.name || 'Unbenannt'; nameEl.value = session.name || ''; @@ -42,8 +53,14 @@ registerPage('session', { _sessionImageData = session.image || null; deleteBtn.hidden = false; + if (session.avatarScale && session.avatarScale !== 1) { + scaleInput.value = session.avatarScale; + scaleValue.textContent = session.avatarScale + '×'; + } + if (session.image) { - imagePreview.innerHTML = ``; + imagePreview.innerHTML = ``; + scaleRow.hidden = false; } // Custom Commands laden @@ -87,6 +104,43 @@ registerPage('session', { } if (session && session.agentId) agentSelect.value = session.agentId; + // AI Bild-Generierung (nur wenn Krea konfiguriert) + const aiSection = document.getElementById('sessionAiSection'); + const aiPrompt = document.getElementById('sessionAiPrompt'); + const aiGenerateBtn = document.getElementById('sessionAiGenerateBtn'); + apiKreaStatus().then(status => { + if (status.configured) aiSection.hidden = false; + }); + aiGenerateBtn.addEventListener('click', async () => { + const prompt = aiPrompt.value.trim(); + if (!prompt) return; + aiGenerateBtn.disabled = true; + aiGenerateBtn.innerHTML = ''; + try { + const result = await apiKreaGenerate(prompt); + if (result.files && result.files.length > 0) { + const imgResp = await fetch('/api/images/file/' + result.files[0]); + const blob = await imgResp.blob(); + const reader = new FileReader(); + reader.onload = () => { + _sessionImageData = reader.result; + imagePreview.innerHTML = ``; + scaleRow.hidden = false; + updatePreviewScale(); + }; + reader.readAsDataURL(blob); + } + } catch (e) { + showToast('Fehler: ' + e.message, 'error'); + } finally { + aiGenerateBtn.disabled = false; + aiGenerateBtn.innerHTML = ''; + } + }); + aiPrompt.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); aiGenerateBtn.click(); } + }); + // Bild Upload imageBtn.addEventListener('click', () => imageInput.click()); imageInput.addEventListener('change', () => { @@ -96,6 +150,8 @@ registerPage('session', { reader.onload = () => { _sessionImageData = reader.result; imagePreview.innerHTML = ``; + scaleRow.hidden = false; + updatePreviewScale(); }; reader.readAsDataURL(file); }); @@ -104,6 +160,9 @@ registerPage('session', { _sessionImageData = null; imagePreview.innerHTML = '?'; imageInput.value = ''; + scaleRow.hidden = true; + scaleInput.value = 1; + scaleValue.textContent = '1×'; }); // Bild Paste (Ctrl+V) @@ -119,6 +178,8 @@ registerPage('session', { reader.onload = () => { _sessionImageData = reader.result; imagePreview.innerHTML = ``; + scaleRow.hidden = false; + updatePreviewScale(); }; reader.readAsDataURL(file); return; @@ -175,7 +236,8 @@ registerPage('session', { agentId: agentSelect.value || null, context: _sessionContextMde.value().trim() || null, workspace: workspaceEl.value.trim() || null, - customCommands: commands + customCommands: commands, + avatarScale: parseFloat(scaleInput.value) || 1 }; if (isNew) {