-
-
?
-
-
-
-
-
-
-
- 1×
-
-
+
?
@@ -538,6 +506,7 @@
+
diff --git a/wwwroot/js/avatar-editor.js b/wwwroot/js/avatar-editor.js
new file mode 100644
index 0000000..2c6cacd
--- /dev/null
+++ b/wwwroot/js/avatar-editor.js
@@ -0,0 +1,261 @@
+// ── Avatar Editor Modal ──
+// Wiederverwendbar für Session + Agent Avatar-Bearbeitung.
+//
+// Usage:
+// const editor = AvatarEditor.init({
+// previewEl: document.getElementById('myPreview'),
+// initialImage: 'data:image/...' || null,
+// initialScale: 1.5,
+// onChange(imageData, scale) { ... }
+// });
+// // Cleanup:
+// editor.destroy();
+// // Get current values:
+// editor.getImage(); // data-url or null
+// editor.getScale(); // number
+
+var AvatarEditor = (function () {
+
+ function init(opts) {
+ var previewEl = opts.previewEl;
+ var imageData = opts.initialImage || null;
+ var scale = opts.initialScale || 1;
+ var onChange = opts.onChange || function () {};
+ var overlay = null;
+ var pasteHandler = null;
+ var kreaAvailable = false;
+
+ // Krea-Status prüfen
+ apiKreaStatus().then(function (s) { kreaAvailable = s.configured; }).catch(function () {});
+
+ // Preview rendern
+ function renderPreview() {
+ if (imageData) {
+ previewEl.innerHTML = '

';
+ previewEl.classList.add('has-image');
+ } else {
+ previewEl.innerHTML = '?';
+ previewEl.classList.remove('has-image');
+ }
+ }
+
+ renderPreview();
+
+ // Click → Modal öffnen
+ previewEl.style.cursor = 'pointer';
+ previewEl.addEventListener('click', openModal);
+
+ // Paste Handler (global, immer aktiv)
+ document.addEventListener('paste', pasteHandler = function (e) {
+ if (overlay) return; // Modal offen → Paste dort behandeln
+ var items = e.clipboardData && e.clipboardData.items;
+ if (!items) return;
+ for (var i = 0; i < items.length; i++) {
+ if (items[i].type.indexOf('image/') === 0) {
+ e.preventDefault();
+ readFile(items[i].getAsFile());
+ return;
+ }
+ }
+ });
+
+ function readFile(file) {
+ if (!file) return;
+ var reader = new FileReader();
+ reader.onload = function () {
+ imageData = reader.result;
+ renderPreview();
+ onChange(imageData, scale);
+ if (overlay) refreshModal();
+ };
+ reader.readAsDataURL(file);
+ }
+
+ function setImage(data) {
+ imageData = data;
+ scale = 1;
+ renderPreview();
+ onChange(imageData, scale);
+ }
+
+ function openModal() {
+ if (overlay) return;
+
+ overlay = document.createElement('div');
+ overlay.className = 'av-overlay';
+ overlay.innerHTML =
+ '
' +
+ '' +
+ '
' +
+ '
' +
+ (imageData ? '

' : '?') +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '' + scale + '×' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+
+ document.body.appendChild(overlay);
+
+ // Refs
+ var modal = overlay.querySelector('.av-modal');
+ var closeBtn = overlay.querySelector('.av-close');
+ var previewLarge = overlay.querySelector('.av-preview-large');
+ var scaleRow = overlay.querySelector('.av-scale-row');
+ var scaleInput = overlay.querySelector('.av-scale');
+ var scaleValueEl = overlay.querySelector('.av-scale-value');
+ var uploadBtn = overlay.querySelector('.av-upload-btn');
+ var fileInput = overlay.querySelector('.av-file-input');
+ var clearBtn = overlay.querySelector('.av-clear-btn');
+ var aiPrompt = overlay.querySelector('.av-ai-prompt');
+ var aiBtn = overlay.querySelector('.av-ai-btn');
+
+ // Close
+ closeBtn.addEventListener('click', closeModal);
+ overlay.addEventListener('click', function (e) {
+ if (e.target === overlay) closeModal();
+ });
+
+ // Scale
+ scaleInput.addEventListener('input', function () {
+ scale = parseFloat(scaleInput.value);
+ scaleValueEl.textContent = scale + '×';
+ var img = previewLarge.querySelector('img');
+ if (img) img.style.scale = scale;
+ renderPreview();
+ onChange(imageData, scale);
+ });
+
+ // Upload
+ uploadBtn.addEventListener('click', function () { fileInput.click(); });
+ fileInput.addEventListener('change', function () {
+ if (fileInput.files[0]) readFile(fileInput.files[0]);
+ });
+
+ // Clear
+ clearBtn.addEventListener('click', function () {
+ imageData = null;
+ scale = 1;
+ scaleInput.value = 1;
+ scaleValueEl.textContent = '1×';
+ renderPreview();
+ onChange(imageData, scale);
+ refreshModal();
+ });
+
+ // AI Generate
+ aiBtn.addEventListener('click', generateAi);
+ aiPrompt.addEventListener('keydown', function (e) {
+ if (e.key === 'Enter') { e.preventDefault(); generateAi(); }
+ });
+
+ async function generateAi() {
+ var prompt = aiPrompt.value.trim();
+ if (!prompt) return;
+ aiBtn.disabled = true;
+ aiBtn.innerHTML = '
';
+ try {
+ var result = await apiKreaGenerate(prompt);
+ if (result.files && result.files.length > 0) {
+ var imgResp = await fetch('/api/images/file/' + result.files[0]);
+ var blob = await imgResp.blob();
+ var reader = new FileReader();
+ reader.onload = function () {
+ imageData = reader.result;
+ scale = 1;
+ scaleInput.value = 1;
+ scaleValueEl.textContent = '1×';
+ renderPreview();
+ onChange(imageData, scale);
+ refreshModal();
+ };
+ reader.readAsDataURL(blob);
+ }
+ } catch (e) {
+ showToast('Fehler: ' + e.message, 'error');
+ } finally {
+ aiBtn.disabled = false;
+ aiBtn.innerHTML = '
';
+ }
+ }
+
+ // Paste im Modal
+ modal.addEventListener('paste', function (e) {
+ var items = e.clipboardData && e.clipboardData.items;
+ if (!items) return;
+ for (var i = 0; i < items.length; i++) {
+ if (items[i].type.indexOf('image/') === 0) {
+ e.preventDefault();
+ readFile(items[i].getAsFile());
+ return;
+ }
+ }
+ });
+
+ // Escape
+ overlay._keyHandler = function (e) {
+ if (e.key === 'Escape') closeModal();
+ };
+ document.addEventListener('keydown', overlay._keyHandler);
+ }
+
+ function refreshModal() {
+ if (!overlay) return;
+ var previewLarge = overlay.querySelector('.av-preview-large');
+ var scaleRow = overlay.querySelector('.av-scale-row');
+ var clearBtn = overlay.querySelector('.av-clear-btn');
+ if (imageData) {
+ previewLarge.innerHTML = '

';
+ scaleRow.hidden = false;
+ clearBtn.hidden = false;
+ } else {
+ previewLarge.innerHTML = '?';
+ scaleRow.hidden = true;
+ clearBtn.hidden = true;
+ }
+ }
+
+ function closeModal() {
+ if (!overlay) return;
+ document.removeEventListener('keydown', overlay._keyHandler);
+ overlay.remove();
+ overlay = null;
+ }
+
+ // Public API
+ return {
+ getImage: function () { return imageData; },
+ getScale: function () { return scale; },
+ destroy: function () {
+ closeModal();
+ if (pasteHandler) {
+ document.removeEventListener('paste', pasteHandler);
+ pasteHandler = null;
+ }
+ previewEl.removeEventListener('click', openModal);
+ previewEl.style.cursor = '';
+ }
+ };
+ }
+
+ return { init: init };
+})();
diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js
index 311fbcd..52070ad 100644
--- a/wwwroot/js/pages/agent-page.js
+++ b/wwwroot/js/pages/agent-page.js
@@ -1,7 +1,6 @@
// ── Agent Page ──
-let _agentAvatarData = null;
-let _agentPasteHandler = null;
+let _agentAvatarEditor = null;
registerPage('agent', {
async init(params) {
@@ -26,9 +25,6 @@ registerPage('agent', {
const useMemoryEl = document.getElementById('agentUseMemory');
const webSearchVersionEl = document.getElementById('agentWebSearchVersion');
const avatarPreview = document.getElementById('avatarPreview');
- const avatarInput = document.getElementById('avatarInput');
- const avatarBtn = document.getElementById('avatarBtn');
- const avatarClearBtn = document.getElementById('avatarClearBtn');
const saveBtn = document.getElementById('agentSaveBtn');
const cancelBtn = document.getElementById('agentCancelBtn');
const deleteBtn = document.getElementById('agentDeleteBtn');
@@ -38,18 +34,12 @@ 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);
+ // Avatar Editor initialisieren
+ _agentAvatarEditor = AvatarEditor.init({
+ previewEl: avatarPreview,
+ initialImage: (agent && agent.avatar) || null,
+ initialScale: (agent && agent.avatarScale) || 1
+ });
if (agent) {
title.textContent = agent.name;
@@ -63,19 +53,8 @@ registerPage('agent', {
useUserInfoEl.checked = agent.useUserInfo !== false;
useMemoryEl.checked = agent.useMemory !== false;
webSearchVersionEl.value = agent.webSearchVersion || '';
- _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 = `

`;
- scaleRow.hidden = false;
- }
-
// Custom Commands laden
loadCommandsIntoUI(commandsList, agent.customCommands);
@@ -99,89 +78,6 @@ 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 = `

`;
- 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', () => {
- const file = avatarInput.files[0];
- if (!file) return;
- const reader = new FileReader();
- reader.onload = () => {
- _agentAvatarData = reader.result;
- avatarPreview.innerHTML = `

`;
- scaleRow.hidden = false;
- updatePreviewScale();
- };
- reader.readAsDataURL(file);
- });
-
- avatarClearBtn.addEventListener('click', () => {
- _agentAvatarData = null;
- avatarPreview.innerHTML = '?';
- avatarInput.value = '';
- scaleRow.hidden = true;
- scaleInput.value = 1;
- scaleValue.textContent = '1×';
- });
-
- // Avatar Paste (Ctrl+V)
- document.addEventListener('paste', _agentPasteHandler = (e) => {
- const items = e.clipboardData?.items;
- if (!items) return;
- for (const item of items) {
- if (item.type.startsWith('image/')) {
- e.preventDefault();
- const file = item.getAsFile();
- if (!file) return;
- const reader = new FileReader();
- reader.onload = () => {
- _agentAvatarData = reader.result;
- avatarPreview.innerHTML = `

`;
- scaleRow.hidden = false;
- updatePreviewScale();
- };
- reader.readAsDataURL(file);
- return;
- }
- }
- });
-
more.addEventListener('click', () => {
const content = more.querySelector('[hidden]');
while (content.firstChild)
@@ -202,8 +98,8 @@ registerPage('agent', {
useUserInfo: useUserInfoEl.checked,
useMemory: useMemoryEl.checked,
webSearchVersion: webSearchVersionEl.value,
- avatar: _agentAvatarData,
- avatarScale: parseFloat(scaleInput.value) || 1,
+ avatar: _agentAvatarEditor.getImage(),
+ avatarScale: _agentAvatarEditor.getScale(),
customCommands: getCommandsFromUI(commandsList)
};
@@ -234,11 +130,10 @@ registerPage('agent', {
});
},
destroy() {
- if (_agentPasteHandler) {
- document.removeEventListener('paste', _agentPasteHandler);
- _agentPasteHandler = null;
+ if (_agentAvatarEditor) {
+ _agentAvatarEditor.destroy();
+ _agentAvatarEditor = null;
}
- _agentAvatarData = null;
}
});
diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js
index 2e7eb1c..b0929de 100644
--- a/wwwroot/js/pages/session-page.js
+++ b/wwwroot/js/pages/session-page.js
@@ -1,7 +1,6 @@
// ── Session Edit Page ──
-let _sessionImageData = null;
-let _sessionPasteHandler = null;
+let _sessionAvatarEditor = null;
let _sessionContextMde = null;
registerPage('session', {
@@ -22,9 +21,6 @@ registerPage('session', {
const workspaceEl = document.getElementById('sessionWorkspace');
const agentSelect = document.getElementById('agentSelect');
const imagePreview = document.getElementById('sessionImagePreview');
- const imageInput = document.getElementById('sessionImageInput');
- const imageBtn = document.getElementById('sessionImageBtn');
- const imageClearBtn = document.getElementById('sessionImageClearBtn');
const saveBtn = document.getElementById('sessionSaveBtn');
const cancelBtn = document.getElementById('sessionCancelBtn');
const deleteBtn = document.getElementById('sessionDeleteBtn');
@@ -32,37 +28,20 @@ registerPage('session', {
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);
+ // Avatar Editor initialisieren
+ _sessionAvatarEditor = AvatarEditor.init({
+ previewEl: imagePreview,
+ initialImage: (session && session.image) || null,
+ initialScale: (session && session.avatarScale) || 1
+ });
if (session) {
titleEl.textContent = session.name || 'Unbenannt';
nameEl.value = session.name || '';
descEl.value = session.description || '';
workspaceEl.value = session.workspace || '';
- _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 = `

`;
- scaleRow.hidden = false;
- }
-
// Custom Commands laden
loadCommandsIntoUI(commandsList, session.customCommands);
} else {
@@ -104,89 +83,6 @@ 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', () => {
- const file = imageInput.files[0];
- if (!file) return;
- const reader = new FileReader();
- reader.onload = () => {
- _sessionImageData = reader.result;
- imagePreview.innerHTML = `

`;
- scaleRow.hidden = false;
- updatePreviewScale();
- };
- reader.readAsDataURL(file);
- });
-
- imageClearBtn.addEventListener('click', () => {
- _sessionImageData = null;
- imagePreview.innerHTML = '?';
- imageInput.value = '';
- scaleRow.hidden = true;
- scaleInput.value = 1;
- scaleValue.textContent = '1×';
- });
-
- // Bild Paste (Ctrl+V)
- document.addEventListener('paste', _sessionPasteHandler = (e) => {
- const items = e.clipboardData?.items;
- if (!items) return;
- for (const item of items) {
- if (item.type.startsWith('image/')) {
- e.preventDefault();
- const file = item.getAsFile();
- if (!file) return;
- const reader = new FileReader();
- reader.onload = () => {
- _sessionImageData = reader.result;
- imagePreview.innerHTML = `

`;
- scaleRow.hidden = false;
- updatePreviewScale();
- };
- reader.readAsDataURL(file);
- return;
- }
- }
- });
-
more.addEventListener('click', () => {
const content = more.querySelector('[hidden]');
while (content.firstChild)
@@ -232,12 +128,12 @@ registerPage('session', {
const data = {
name: nameEl.value.trim() || 'Unbenannt',
description: descEl.value.trim(),
- image: _sessionImageData,
+ image: _sessionAvatarEditor.getImage(),
agentId: agentSelect.value || null,
context: _sessionContextMde.value().trim() || null,
workspace: workspaceEl.value.trim() || null,
customCommands: commands,
- avatarScale: parseFloat(scaleInput.value) || 1
+ avatarScale: _sessionAvatarEditor.getScale()
};
if (isNew) {
@@ -274,11 +170,10 @@ registerPage('session', {
_sessionContextMde.toTextArea();
_sessionContextMde = null;
}
- if (_sessionPasteHandler) {
- document.removeEventListener('paste', _sessionPasteHandler);
- _sessionPasteHandler = null;
+ if (_sessionAvatarEditor) {
+ _sessionAvatarEditor.destroy();
+ _sessionAvatarEditor = null;
}
- _sessionImageData = null;
}
});