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

+10
View File
@@ -941,6 +941,16 @@
gap: var(--badge-spacing); gap: var(--badge-spacing);
} }
.shared-content .msg {
max-width: unset;
align-self: stretch;
}
.shared-content .msg.assistant {
background: unset;
border: unset;
}
.shared-loading { .shared-loading {
text-align: center; text-align: center;
padding: 3em 1em; padding: 3em 1em;
+86 -13
View File
@@ -15,11 +15,7 @@
cursor: pointer; cursor: pointer;
} }
.avatar-section { /* ── Avatar Preview (in Formularen) ── */
display: flex;
align-items: center;
gap: var(--box-spacing);
}
.avatar-preview { .avatar-preview {
width: 8em; width: 8em;
@@ -32,37 +28,114 @@
overflow: hidden; overflow: hidden;
flex-shrink: 0; flex-shrink: 0;
border-radius: var(--border-radius); border-radius: var(--border-radius);
cursor: pointer;
transition: border-color var(--transition-duration);
}
.avatar-preview:hover {
border-color: var(--primary);
} }
.avatar-preview img { .avatar-preview img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
transition: scale var(--transition-duration);
} }
.avatar-scale-row { /* ── Avatar Editor Modal ── */
.av-overlay {
position: fixed;
inset: 0;
background: rgb(from var(--f1-bg) r g b / var(--semi-transparent));
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.av-modal {
background: var(--f2-bg);
border: var(--border-width) solid var(--f2-border);
border-radius: var(--border-radius);
width: 90vw;
max-width: 420px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.av-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--box-padding);
border-bottom: var(--border-width) solid var(--f1-border);
background: var(--f1-bg);
}
.av-body {
padding: var(--box-padding);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--box-spacing);
}
.av-preview-large {
width: 16em;
height: 16em;
background: var(--f3-bg);
border: var(--border-width) solid var(--f2-border);
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.av-preview-large img {
width: 100%;
height: 100%;
object-fit: cover;
}
.av-controls {
width: 100%;
display: flex;
flex-direction: column;
gap: var(--badge-spacing);
}
.av-scale-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--badge-spacing); gap: var(--badge-spacing);
flex: 1;
padding: var(--box-padding);
} }
.avatar-scale-row label {
.av-scale-row label {
margin: 0; margin: 0;
white-space: nowrap;
} }
.avatar-scale-row input[type="range"] {
.av-scale-row input[type="range"] {
flex: 1; flex: 1;
accent-color: var(--primary); accent-color: var(--primary);
} }
.avatar-scale-row span {
.av-scale-row .av-scale-value {
font-size: 0.85em; font-size: 0.85em;
min-width: 2.5em; min-width: 2.5em;
text-align: right; text-align: right;
opacity: 0.7; opacity: 0.7;
} }
.avatar-ai-section { .av-actions {
display: flex;
gap: var(--badge-spacing);
}
.av-ai-section {
width: 100%; width: 100%;
} }
+3 -34
View File
@@ -100,23 +100,7 @@
</header> </header>
<div class="page-content"> <div class="page-content">
<div class="form"> <div class="form">
<div class="avatar-section"> <div class="avatar-preview" id="sessionImagePreview">?</div>
<div class="avatar-preview" id="sessionImagePreview">?</div>
<input type="file" id="sessionImageInput" accept="image/*" hidden>
<button id="sessionImageBtn">Bild ändern</button>
<button id="sessionImageClearBtn">Entfernen</button>
</div>
<div class="avatar-scale-row" id="sessionScaleRow" hidden>
<label>Zoom</label>
<input type="range" id="sessionAvatarScale" min="0.5" max="3" step="0.1" value="1">
<span id="sessionScaleValue">1×</span>
</div>
<div class="avatar-ai-section" id="sessionAiSection" hidden>
<div class="input-with-btn">
<input type="text" id="sessionAiPrompt" placeholder="Bildbeschreibung eingeben...">
<button type="button" id="sessionAiGenerateBtn" class="icon-btn" title="Bild generieren"><i class="fa fa-magic"></i></button>
</div>
</div>
<label>Name</label> <label>Name</label>
<input type="text" id="sessionName" placeholder="Name der Session"> <input type="text" id="sessionName" placeholder="Name der Session">
<label>Agent</label> <label>Agent</label>
@@ -169,23 +153,7 @@
</header> </header>
<div class="page-content"> <div class="page-content">
<div class="form"> <div class="form">
<div class="avatar-section"> <div class="avatar-preview" id="avatarPreview">?</div>
<div class="avatar-preview" id="avatarPreview">?</div>
<input type="file" id="avatarInput" accept="image/*" hidden>
<button id="avatarBtn">Avatar ändern</button>
<button id="avatarClearBtn">Entfernen</button>
</div>
<div class="avatar-scale-row" id="agentScaleRow" hidden>
<label>Zoom</label>
<input type="range" id="agentAvatarScale" min="0.5" max="3" step="0.1" value="1">
<span id="agentScaleValue">1×</span>
</div>
<div class="avatar-ai-section" id="agentAiSection" hidden>
<div class="input-with-btn">
<input type="text" id="agentAiPrompt" placeholder="Bildbeschreibung eingeben...">
<button type="button" id="agentAiGenerateBtn" class="icon-btn" title="Bild generieren"><i class="fa fa-magic"></i></button>
</div>
</div>
<label>Name</label> <label>Name</label>
<input type="text" id="agentName" placeholder="Name des Agents"> <input type="text" id="agentName" placeholder="Name des Agents">
<label>Model</label> <label>Model</label>
@@ -538,6 +506,7 @@
<script src="js/history.js"></script> <script src="js/history.js"></script>
<script src="js/router.js"></script> <script src="js/router.js"></script>
<script src="js/commands-ui.js"></script> <script src="js/commands-ui.js"></script>
<script src="js/avatar-editor.js"></script>
<script src="js/pages/dashboard.js"></script> <script src="js/pages/dashboard.js"></script>
<script src="js/pages/cronjob-page.js"></script> <script src="js/pages/cronjob-page.js"></script>
<script src="js/pages/agent-page.js"></script> <script src="js/pages/agent-page.js"></script>
+261
View File
@@ -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 = '<img src="' + imageData + '" alt="" style="scale: ' + scale + '">';
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 =
'<div class="av-modal">' +
'<div class="av-header">' +
'<h3>Avatar bearbeiten</h3>' +
'<button class="icon-btn av-close"><i class="fa fa-close"></i></button>' +
'</div>' +
'<div class="av-body">' +
'<div class="av-preview-large">' +
(imageData ? '<img src="' + imageData + '" alt="" style="scale: ' + scale + '">' : '?') +
'</div>' +
'<div class="av-controls">' +
'<div class="av-scale-row"' + (imageData ? '' : ' hidden') + '>' +
'<label>Zoom</label>' +
'<input type="range" class="av-scale" min="0.5" max="3" step="0.1" value="' + scale + '">' +
'<span class="av-scale-value">' + scale + '×</span>' +
'</div>' +
'<div class="av-actions">' +
'<button class="av-upload-btn"><i class="fa fa-upload"></i> Hochladen</button>' +
'<input type="file" class="av-file-input" accept="image/*" hidden>' +
'<button class="av-clear-btn danger-btn"' + (imageData ? '' : ' hidden') + '><i class="fa fa-trash"></i> Entfernen</button>' +
'</div>' +
'<div class="av-ai-section"' + (kreaAvailable ? '' : ' hidden') + '>' +
'<div class="input-with-btn">' +
'<input type="text" class="av-ai-prompt" placeholder="Bildbeschreibung eingeben...">' +
'<button type="button" class="icon-btn av-ai-btn" title="Bild generieren"><i class="fa fa-magic"></i></button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
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 = '<i class="fa fa-spinner fa-spin"></i>';
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 = '<i class="fa fa-magic"></i>';
}
}
// 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 = '<img src="' + imageData + '" alt="" style="scale: ' + scale + '">';
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 };
})();
+12 -117
View File
@@ -1,7 +1,6 @@
// ── Agent Page ── // ── Agent Page ──
let _agentAvatarData = null; let _agentAvatarEditor = null;
let _agentPasteHandler = null;
registerPage('agent', { registerPage('agent', {
async init(params) { async init(params) {
@@ -26,9 +25,6 @@ registerPage('agent', {
const useMemoryEl = document.getElementById('agentUseMemory'); const useMemoryEl = document.getElementById('agentUseMemory');
const webSearchVersionEl = document.getElementById('agentWebSearchVersion'); const webSearchVersionEl = document.getElementById('agentWebSearchVersion');
const avatarPreview = document.getElementById('avatarPreview'); 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 saveBtn = document.getElementById('agentSaveBtn');
const cancelBtn = document.getElementById('agentCancelBtn'); const cancelBtn = document.getElementById('agentCancelBtn');
const deleteBtn = document.getElementById('agentDeleteBtn'); const deleteBtn = document.getElementById('agentDeleteBtn');
@@ -38,18 +34,12 @@ registerPage('agent', {
const skillList = document.getElementById('agentSkillList'); const skillList = document.getElementById('agentSkillList');
const more = document.querySelector('.more'); const more = document.querySelector('.more');
const scaleRow = document.getElementById('agentScaleRow'); // Avatar Editor initialisieren
const scaleInput = document.getElementById('agentAvatarScale'); _agentAvatarEditor = AvatarEditor.init({
const scaleValue = document.getElementById('agentScaleValue'); previewEl: avatarPreview,
initialImage: (agent && agent.avatar) || null,
_agentAvatarData = null; initialScale: (agent && agent.avatarScale) || 1
});
function updatePreviewScale() {
var img = avatarPreview.querySelector('img');
if (img) img.style.scale = scaleInput.value;
scaleValue.textContent = scaleInput.value + '×';
}
scaleInput.addEventListener('input', updatePreviewScale);
if (agent) { if (agent) {
title.textContent = agent.name; title.textContent = agent.name;
@@ -63,19 +53,8 @@ registerPage('agent', {
useUserInfoEl.checked = agent.useUserInfo !== false; useUserInfoEl.checked = agent.useUserInfo !== false;
useMemoryEl.checked = agent.useMemory !== false; useMemoryEl.checked = agent.useMemory !== false;
webSearchVersionEl.value = agent.webSearchVersion || ''; webSearchVersionEl.value = agent.webSearchVersion || '';
_agentAvatarData = agent.avatar || null;
deleteBtn.hidden = false; deleteBtn.hidden = false;
if (agent.avatarScale && agent.avatarScale !== 1) {
scaleInput.value = agent.avatarScale;
scaleValue.textContent = agent.avatarScale + '×';
}
if (agent.avatar) {
avatarPreview.innerHTML = `<img src="${agent.avatar}" alt="Avatar" style="scale: ${agent.avatarScale || 1}">`;
scaleRow.hidden = false;
}
// Custom Commands laden // Custom Commands laden
loadCommandsIntoUI(commandsList, agent.customCommands); loadCommandsIntoUI(commandsList, agent.customCommands);
@@ -99,89 +78,6 @@ registerPage('agent', {
// Befehl hinzufügen // Befehl hinzufügen
addCommandBtn.addEventListener('click', () => createCommandRow(commandsList)); 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 = '<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 = () => {
_agentAvatarData = reader.result;
avatarPreview.innerHTML = `<img src="${reader.result}" alt="Avatar">`;
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(); }
});
// 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 = `<img src="${reader.result}" alt="Avatar">`;
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 = `<img src="${reader.result}" alt="Avatar">`;
scaleRow.hidden = false;
updatePreviewScale();
};
reader.readAsDataURL(file);
return;
}
}
});
more.addEventListener('click', () => { more.addEventListener('click', () => {
const content = more.querySelector('[hidden]'); const content = more.querySelector('[hidden]');
while (content.firstChild) while (content.firstChild)
@@ -202,8 +98,8 @@ registerPage('agent', {
useUserInfo: useUserInfoEl.checked, useUserInfo: useUserInfoEl.checked,
useMemory: useMemoryEl.checked, useMemory: useMemoryEl.checked,
webSearchVersion: webSearchVersionEl.value, webSearchVersion: webSearchVersionEl.value,
avatar: _agentAvatarData, avatar: _agentAvatarEditor.getImage(),
avatarScale: parseFloat(scaleInput.value) || 1, avatarScale: _agentAvatarEditor.getScale(),
customCommands: getCommandsFromUI(commandsList) customCommands: getCommandsFromUI(commandsList)
}; };
@@ -234,11 +130,10 @@ registerPage('agent', {
}); });
}, },
destroy() { destroy() {
if (_agentPasteHandler) { if (_agentAvatarEditor) {
document.removeEventListener('paste', _agentPasteHandler); _agentAvatarEditor.destroy();
_agentPasteHandler = null; _agentAvatarEditor = null;
} }
_agentAvatarData = null;
} }
}); });
+12 -117
View File
@@ -1,7 +1,6 @@
// ── Session Edit Page ── // ── Session Edit Page ──
let _sessionImageData = null; let _sessionAvatarEditor = null;
let _sessionPasteHandler = null;
let _sessionContextMde = null; let _sessionContextMde = null;
registerPage('session', { registerPage('session', {
@@ -22,9 +21,6 @@ registerPage('session', {
const workspaceEl = document.getElementById('sessionWorkspace'); const workspaceEl = document.getElementById('sessionWorkspace');
const agentSelect = document.getElementById('agentSelect'); const agentSelect = document.getElementById('agentSelect');
const imagePreview = document.getElementById('sessionImagePreview'); 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 saveBtn = document.getElementById('sessionSaveBtn');
const cancelBtn = document.getElementById('sessionCancelBtn'); const cancelBtn = document.getElementById('sessionCancelBtn');
const deleteBtn = document.getElementById('sessionDeleteBtn'); const deleteBtn = document.getElementById('sessionDeleteBtn');
@@ -32,37 +28,20 @@ registerPage('session', {
const addCommandBtn = document.getElementById('addCommandBtn'); const addCommandBtn = document.getElementById('addCommandBtn');
const more = document.querySelector('.more'); const more = document.querySelector('.more');
const scaleRow = document.getElementById('sessionScaleRow'); // Avatar Editor initialisieren
const scaleInput = document.getElementById('sessionAvatarScale'); _sessionAvatarEditor = AvatarEditor.init({
const scaleValue = document.getElementById('sessionScaleValue'); previewEl: imagePreview,
initialImage: (session && session.image) || null,
_sessionImageData = null; initialScale: (session && session.avatarScale) || 1
});
function updatePreviewScale() {
var img = imagePreview.querySelector('img');
if (img) img.style.scale = scaleInput.value;
scaleValue.textContent = scaleInput.value + '×';
}
scaleInput.addEventListener('input', updatePreviewScale);
if (session) { if (session) {
titleEl.textContent = session.name || 'Unbenannt'; titleEl.textContent = session.name || 'Unbenannt';
nameEl.value = session.name || ''; nameEl.value = session.name || '';
descEl.value = session.description || ''; descEl.value = session.description || '';
workspaceEl.value = session.workspace || ''; workspaceEl.value = session.workspace || '';
_sessionImageData = session.image || null;
deleteBtn.hidden = false; 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 // Custom Commands laden
loadCommandsIntoUI(commandsList, session.customCommands); loadCommandsIntoUI(commandsList, session.customCommands);
} else { } else {
@@ -104,89 +83,6 @@ registerPage('session', {
} }
if (session && session.agentId) agentSelect.value = session.agentId; 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', () => { more.addEventListener('click', () => {
const content = more.querySelector('[hidden]'); const content = more.querySelector('[hidden]');
while (content.firstChild) while (content.firstChild)
@@ -232,12 +128,12 @@ registerPage('session', {
const data = { const data = {
name: nameEl.value.trim() || 'Unbenannt', name: nameEl.value.trim() || 'Unbenannt',
description: descEl.value.trim(), description: descEl.value.trim(),
image: _sessionImageData, image: _sessionAvatarEditor.getImage(),
agentId: agentSelect.value || null, agentId: agentSelect.value || null,
context: _sessionContextMde.value().trim() || null, context: _sessionContextMde.value().trim() || null,
workspace: workspaceEl.value.trim() || null, workspace: workspaceEl.value.trim() || null,
customCommands: commands, customCommands: commands,
avatarScale: parseFloat(scaleInput.value) || 1 avatarScale: _sessionAvatarEditor.getScale()
}; };
if (isNew) { if (isNew) {
@@ -274,11 +170,10 @@ registerPage('session', {
_sessionContextMde.toTextArea(); _sessionContextMde.toTextArea();
_sessionContextMde = null; _sessionContextMde = null;
} }
if (_sessionPasteHandler) { if (_sessionAvatarEditor) {
document.removeEventListener('paste', _sessionPasteHandler); _sessionAvatarEditor.destroy();
_sessionPasteHandler = null; _sessionAvatarEditor = null;
} }
_sessionImageData = null;
} }
}); });