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:
1 parent
057d37d76f
commit
34cecd03a1
6 files changed
+384
-281
No files matched your search
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user