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
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user