refactor: Avatar-Editor als wiederverwendbares Modal-Modul

- Neues avatar-editor.js: Upload, Paste, AI-Generate, Scale, Clear in einem Modal
- Session + Agent Pages: ~280 Zeilen doppelter Avatar-Code → je 5 Zeilen AvatarEditor.init/destroy
- Avatar-Preview klickbar → öffnet Modal mit allen Einstellungen
- chat.css: Shared-Content Message-Styling (max-width, border)
This commit is contained in:
Meleki committed 2026-03-06 11:55:00 +01:00
1 parent 057d37d76f
commit 34cecd03a1
6 files changed
+384 -281

No files matched your search

+12 -117
View File
@@ -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 = `<img src="${session.image}" alt="" style="scale: ${session.avatarScale || 1}">`;
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 = '<i class="fa fa-spinner fa-spin"></i>';
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 = `<img src="${reader.result}" alt="">`;
scaleRow.hidden = false;
updatePreviewScale();
};
reader.readAsDataURL(blob);
}
} catch (e) {
showToast('Fehler: ' + e.message, 'error');
} finally {
aiGenerateBtn.disabled = false;
aiGenerateBtn.innerHTML = '<i class="fa fa-magic"></i>';
}
});
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 = `<img src="${reader.result}" alt="">`;
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 = `<img src="${reader.result}" alt="">`;
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;
}
});