546 lines
22 KiB
JavaScript
546 lines
22 KiB
JavaScript
/**
|
|
* JSON-Editor für die Template-Bearbeitung.
|
|
*
|
|
* Generiert ein Formular aus JSON-Daten und extrahiert
|
|
* die Werte zurück in ein JSON-Objekt.
|
|
*/
|
|
|
|
let currentIndent = 2;
|
|
|
|
/**
|
|
* Öffne Edit-Modal für eine Template-Datei
|
|
* @param {string} path - Pfad zur Template-Datei
|
|
*/
|
|
function editModalContent(path) {
|
|
window.currentEditTemplate = path;
|
|
const title = path.split('/').pop();
|
|
document.getElementById('edit-title').textContent = `Template bearbeiten: ${title}`;
|
|
window._wasViewModalOpen = document.getElementById('modal').classList.contains('active');
|
|
|
|
// Inhalt laden und editierbare Formulare abhängig vom Dateityp erstellen
|
|
fetch(path)
|
|
.then(r => r.text())
|
|
.then(content => {
|
|
const editContainer = document.getElementById('edit-content-content');
|
|
editContainer.innerHTML = '';
|
|
|
|
if (path.endsWith('.json')) {
|
|
try {
|
|
const jsonData = JSON.parse(content);
|
|
// Original-Indent erkennen
|
|
const indentMatch = content.match(/^\s{2,8}/m);
|
|
currentIndent = indentMatch ? indentMatch[0].length : 2;
|
|
createJsonEditUI(editContainer, jsonData, path);
|
|
} catch (e) {
|
|
// Falls JSON ungültig, als Text bearbeiten
|
|
createTextEditUI(editContainer, content);
|
|
}
|
|
} else {
|
|
// Markdown als einfaches Textfeld
|
|
createTextEditUI(editContainer, content);
|
|
}
|
|
|
|
document.getElementById('edit-modal').classList.add('active');
|
|
})
|
|
.catch(e => showToast(`✗ Fehler beim Laden: ${e.message}`));
|
|
}
|
|
|
|
/**
|
|
* Erstelle ein Textarea für die Bearbeitung
|
|
* @param {HTMLElement} container - Ziel-Container
|
|
* @param {string} content - Dateiinhalt
|
|
*/
|
|
function createTextEditUI(container, content) {
|
|
const textarea = document.createElement('textarea');
|
|
textarea.id = 'edit-textarea';
|
|
textarea.value = content;
|
|
textarea.style.cssText = 'width: 100%; min-height: 300px; background: var(--bg-input); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 10px; font-family: var(--mono); font-size: 13px;';
|
|
textarea.spellcheck = false;
|
|
container.appendChild(textarea);
|
|
}
|
|
|
|
/**
|
|
* Erstelle ein Formular aus JSON-Daten
|
|
* @param {HTMLElement} container - Ziel-Container
|
|
* @param {Object} jsonData - Zu bearbeitende JSON-Daten
|
|
* @param {string} editPathHint - Pfad-Hinweis
|
|
*/
|
|
function createJsonEditUI(container, jsonData, editPathHint = '') {
|
|
editContainerRef = container;
|
|
container.innerHTML = '';
|
|
|
|
const formDiv = document.createElement('div');
|
|
formDiv.style.cssText = 'display: flex; flex-direction: column; gap: 16px; padding: 8px; background: var(--bg-card); border-radius: 4px; margin: 0; min-height: 300px; overflow-y: auto;';
|
|
|
|
// Rekursive Funktion zum Erstellen von Eingabefeldern für alle Properties
|
|
function buildJsonForm(data, prefix = '', level = 0, targetElement = null) {
|
|
if (typeof data !== 'object' || data === null) return;
|
|
|
|
const container = targetElement || document.getElementById('edit-content-content');
|
|
if (!container) return;
|
|
|
|
Object.keys(data).forEach(key => {
|
|
if (key.includes('__proto__') || key.includes('constructor') || key.includes('prototype')) {
|
|
return;
|
|
}
|
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
const value = data[key];
|
|
const isObject = typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
const isArray = Array.isArray(value);
|
|
|
|
// Für Objekte: Rekursiv alle inneren Properties anzeigen
|
|
if (isObject) {
|
|
const fieldContainer = document.createElement('div');
|
|
fieldContainer.style.cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #4CAF50;';
|
|
|
|
const label = document.createElement('label');
|
|
label.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
|
label.textContent = fullKey + ' (Objekt)';
|
|
label.style.cssText = 'font-weight: 600; color: #4CAF50; margin-bottom: 8px; display: block; font-size: 14px;';
|
|
fieldContainer.appendChild(label);
|
|
|
|
// Rekursiv innere Properties in den Container einfügen
|
|
const innerContainer = document.createElement('div');
|
|
innerContainer.style.cssText = 'padding-left: 12px; margin-top: 4px;';
|
|
|
|
buildJsonForm(value, fullKey, level + 1, innerContainer);
|
|
|
|
fieldContainer.appendChild(innerContainer);
|
|
|
|
container.appendChild(fieldContainer);
|
|
return;
|
|
}
|
|
|
|
// Für Arrays: Rekursiv jedes Element als eigenes Feld anzeigen
|
|
if (isArray) {
|
|
const arrayContainer = document.createElement('div');
|
|
arrayContainer.style.cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #2196F3;';
|
|
|
|
const arrayLabel = document.createElement('label');
|
|
arrayLabel.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
|
arrayLabel.textContent = fullKey + ' (Array) - ' + value.length + ' Elemente';
|
|
arrayLabel.style.cssText = 'font-weight: 600; color: #2196F3; margin-bottom: 8px; display: block; font-size: 14px;';
|
|
arrayContainer.appendChild(arrayLabel);
|
|
|
|
// Für jedes Array-Element ein separates Eingabefeld
|
|
const arrayItemsContainer = document.createElement('div');
|
|
arrayItemsContainer.style.cssText = 'margin-top: 4px; padding-left: 12px;';
|
|
|
|
value.forEach((item, index) => {
|
|
const itemKey = `${fullKey}[${index}]`;
|
|
const itemContainer = document.createElement('div');
|
|
itemContainer.style.cssText = 'background: #2a2a2a; padding: 8px; margin: 4px 0; border-radius: 3px; border-left: 2px solid #FF9800;';
|
|
|
|
const itemLabel = document.createElement('label');
|
|
itemLabel.htmlFor = `edit-${itemKey.replace(/[.\s]/g, '-')}`;
|
|
itemLabel.textContent = `Element [${index}]`;
|
|
itemLabel.style.cssText = 'font-weight: 500; color: #FF9800; margin-bottom: 4px; display: block; font-size: 13px;';
|
|
itemContainer.appendChild(itemLabel);
|
|
|
|
// Prüfen, ob das Array-Element selbst ein Objekt ist
|
|
if (typeof item === 'object' && item !== null) {
|
|
const innerObjContainer = document.createElement('div');
|
|
innerObjContainer.style.cssText = 'padding-left: 12px; margin-top: 4px;';
|
|
|
|
buildJsonForm(item, itemKey, level + 1, innerObjContainer);
|
|
|
|
itemContainer.appendChild(innerObjContainer);
|
|
} else {
|
|
const itemFieldContainer = document.createElement('div');
|
|
itemFieldContainer.style.cssText = 'background: var(--bg-input); padding: 8px; border-radius: 3px; margin-bottom: 4px;';
|
|
|
|
const input = document.createElement('input');
|
|
const type = typeof item === 'boolean' ? 'checkbox' : typeof item === 'number' ? 'number' : 'text';
|
|
input.type = type;
|
|
input.value = item !== null && item !== undefined ? item : '';
|
|
input.dataset.key = itemKey;
|
|
input.dataset.type = typeof item;
|
|
input.dataset.arrayIndex = index;
|
|
|
|
if (type === 'checkbox') input.checked = item;
|
|
else input.value = String(item !== null && item !== undefined ? item : '');
|
|
|
|
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;';
|
|
itemFieldContainer.appendChild(input);
|
|
itemContainer.appendChild(itemFieldContainer);
|
|
}
|
|
|
|
arrayItemsContainer.appendChild(itemContainer);
|
|
});
|
|
arrayContainer.appendChild(arrayItemsContainer);
|
|
container.appendChild(arrayContainer);
|
|
return;
|
|
}
|
|
|
|
// Für primitive Werte: Standard-Eingabefeld erstellen
|
|
const fieldContainer = document.createElement('div');
|
|
fieldContainer.style.cssText = 'background: var(--bg-input); padding: 8px; border-radius: 4px; border: 1px solid transparent;';
|
|
|
|
const label = document.createElement('label');
|
|
label.htmlFor = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
|
label.textContent = fullKey;
|
|
label.style.cssText = 'font-weight: 600; color: var(--text-primary); display: block; margin-bottom: 4px; font-size: 14px; margin-top: 0;';
|
|
fieldContainer.appendChild(label);
|
|
|
|
// Das 'template'-Feld als Textarea rendern
|
|
const isTemplateField = (fullKey === 'template');
|
|
const type = typeof value === 'boolean' ? 'checkbox' : typeof value === 'number' ? 'number' : 'text';
|
|
|
|
if (isTemplateField) {
|
|
const textarea = document.createElement('textarea');
|
|
textarea.id = `edit-${fullKey.replace(/[.\s]/g, '-')}`;
|
|
textarea.value = value !== null && value !== undefined ? value : '';
|
|
textarea.dataset.key = fullKey;
|
|
textarea.dataset.type = typeof value;
|
|
textarea.style.cssText = 'width: 100%; min-height: 200px; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px; resize: vertical;';
|
|
textarea.spellcheck = false;
|
|
fieldContainer.appendChild(textarea);
|
|
} else {
|
|
const input = document.createElement('input');
|
|
input.type = type;
|
|
input.value = value !== null && value !== undefined ? value : '';
|
|
input.dataset.key = fullKey;
|
|
input.dataset.type = typeof value;
|
|
|
|
if (type === 'checkbox') input.checked = value;
|
|
else input.value = String(value !== null && value !== undefined ? value : '');
|
|
|
|
input.style.cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;';
|
|
fieldContainer.appendChild(input);
|
|
}
|
|
container.appendChild(fieldContainer);
|
|
});
|
|
}
|
|
|
|
buildJsonForm(jsonData, '', 0, formDiv);
|
|
container.appendChild(formDiv);
|
|
}
|
|
|
|
/**
|
|
* Extrahiere den Wert aus einem Input-Element
|
|
* @param {HTMLElement} input - Input-Element
|
|
* @returns {*} Extrahierter Wert
|
|
*/
|
|
function extractInputValue(input) {
|
|
if (input.type === 'checkbox') {
|
|
return input.checked;
|
|
}
|
|
|
|
const value = input.value;
|
|
|
|
try {
|
|
// Nur wenn der Input-Typ "text" ist und Wert JSON-ähnlich formatiert
|
|
if (input.type === 'text' && value.length > 0) {
|
|
if ((value.startsWith('{') && value.endsWith('}')) ||
|
|
(value.startsWith('[') && value.endsWith(']'))) {
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch (parseErr) {
|
|
// Kein gültiges JSON, behalte String-Wert
|
|
}
|
|
}
|
|
}
|
|
if (input.dataset.type === 'number') {
|
|
return Number(value);
|
|
}
|
|
return value;
|
|
} catch (e) {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extrahiere JSON aus dem Formular-Container
|
|
* @param {HTMLElement} formDiv - Formular-Container
|
|
* @returns {Object} Extrahiertes JSON-Objekt
|
|
*/
|
|
function extractJsonFromForm(formDiv) {
|
|
// Map: full key -> { value, type }
|
|
const inputEntries = [];
|
|
const inputs = formDiv.querySelectorAll('[data-key]');
|
|
inputs.forEach(input => {
|
|
const key = input.dataset.key || '';
|
|
inputEntries.push({ key, value: extractInputValue(input) });
|
|
});
|
|
|
|
// Sort by key to ensure stable parent-first construction
|
|
inputEntries.sort((a,b) => a.key.localeCompare(b.key));
|
|
|
|
const result = Object.create(null);
|
|
inputEntries.forEach(({ key, value }) => {
|
|
const parts = key.split('.');
|
|
let cur = result;
|
|
for (let i = 0; i < parts.length; i++) {
|
|
const part = parts[i];
|
|
const isLast = i === parts.length - 1;
|
|
const lastBracket = part.lastIndexOf('[');
|
|
const lastCloseBracket = part.lastIndexOf(']');
|
|
let m = null;
|
|
if (lastBracket !== -1 && lastCloseBracket !== -1 && lastCloseBracket > lastBracket) {
|
|
const beforeBracket = part.substring(0, lastBracket);
|
|
const between = part.substring(lastBracket + 1, lastCloseBracket);
|
|
const afterBracket = part.substring(lastCloseBracket + 1);
|
|
if (afterBracket === '' && /^\d+$/.test(between)) {
|
|
m = [part, beforeBracket, between];
|
|
}
|
|
}
|
|
if (m && isLast) {
|
|
const base = m[1];
|
|
const idx = Number(m[2]);
|
|
if (!cur[base]) cur[base] = Object.create(null);
|
|
cur[base][idx] = value;
|
|
} else if (m && !isLast) {
|
|
const base = m[1];
|
|
const idx = Number(m[2]);
|
|
if (!cur[base]) cur[base] = [];
|
|
if (!cur[base][idx]) cur[base][idx] = Object.create(null);
|
|
cur = cur[base][idx];
|
|
} else if (isLast) {
|
|
cur[part] = value;
|
|
} else {
|
|
if (!cur[part]) cur[part] = Object.create(null);
|
|
cur = cur[part];
|
|
}
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* LLM-API-Konfiguration (llama.cpp / Kilo-Instanz)
|
|
*/
|
|
const LLM_BASE = 'http://localhost:8001/v1';
|
|
const LLM_MODEL = 'Qwen3.6-35B-A3B-UD-Q3_K_S.gguf';
|
|
|
|
/**
|
|
* Zustand für den Tunen-Dialog
|
|
*/
|
|
window._tuneState = { path: '', rawPath: '', template: '' };
|
|
|
|
/**
|
|
* Öffne Tune-Modal für eine Template-Datei
|
|
* @param {string} path - Pfad zur Template-Datei
|
|
*/
|
|
function tuneModalContent(path) {
|
|
window._tuneState.path = path;
|
|
window._tuneState.rawPath = '';
|
|
window._tuneState.template = '';
|
|
window._tuneState.generated = '';
|
|
|
|
const title = path.split('/').pop();
|
|
document.getElementById('tune-title').textContent = `Prompt tunen: ${title}`;
|
|
|
|
// Generate-Button aktivieren, Apply-Button verstecken
|
|
document.getElementById('tune-generate-btn').style.display = '';
|
|
document.getElementById('tune-apply-btn').style.display = 'none';
|
|
document.getElementById('tune-generate-btn').disabled = false;
|
|
document.getElementById('tune-generate-btn').innerHTML = `
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 2v4m0 12v4m8-8h-4M6 12H2m14.5-7.5L16 5.5m-8 8L5.5 16m13-10.5L16 8m-8 8L5.5 16"/>
|
|
</svg>
|
|
Generieren`;
|
|
|
|
const container = document.getElementById('tune-content');
|
|
container.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-muted)">Lade Template...</div>';
|
|
document.getElementById('tune-modal').classList.add('active');
|
|
|
|
fetch(path)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
window._tuneState.template = data.template || '';
|
|
renderTuneUI(window._tuneState);
|
|
})
|
|
.catch(e => {
|
|
container.innerHTML = `<div style="color:var(--text-error);text-align:center;padding:20px">✗ Fehler: ${e.message}</div>`;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Render die Tune-UI mit Original-Template und Preprompt
|
|
* @param {Object} state - Tune-Zustand
|
|
*/
|
|
function renderTuneUI(state) {
|
|
const container = document.getElementById('tune-content');
|
|
container.innerHTML = '';
|
|
|
|
// Original-Template
|
|
const originalPanel = document.createElement('div');
|
|
originalPanel.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
|
|
const origLabel = document.createElement('label');
|
|
origLabel.style.cssText = 'font-weight:600;font-size:13px;color:var(--text-secondary);';
|
|
origLabel.textContent = 'Original-Template';
|
|
originalPanel.appendChild(origLabel);
|
|
|
|
const origTextarea = document.createElement('textarea');
|
|
origTextarea.id = 'tune-original';
|
|
origTextarea.value = state.template;
|
|
origTextarea.readOnly = true;
|
|
origTextarea.style.cssText = 'width:100%;min-height:200px;padding:10px;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:4px;font-family:var(--mono);font-size:13px;resize:vertical;';
|
|
originalPanel.appendChild(origTextarea);
|
|
container.appendChild(originalPanel);
|
|
|
|
// Preprompt-Feld
|
|
const prepromptPanel = document.createElement('div');
|
|
prepromptPanel.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
|
|
const prepromptLabel = document.createElement('label');
|
|
prepromptLabel.style.cssText = 'font-weight:600;font-size:13px;color:var(--text-secondary);';
|
|
prepromptLabel.textContent = 'Preprompt (deine Wünsche zur Verbesserung)';
|
|
prepromptPanel.appendChild(prepromptLabel);
|
|
|
|
const prepromptTextarea = document.createElement('textarea');
|
|
prepromptTextarea.id = 'tune-preprompt';
|
|
prepromptTextarea.style.cssText = 'width:100%;min-height:80px;padding:10px;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:4px;font-family:var(--mono);font-size:13px;resize:vertical;';
|
|
prepromptTextarea.placeholder = 'z.B. Mach das Template kürzer, füge Security-Checks hinzu, verwende einen professionelleren Ton...';
|
|
prepromptPanel.appendChild(prepromptTextarea);
|
|
container.appendChild(prepromptPanel);
|
|
|
|
// Ergebnis-Panel (initial versteckt)
|
|
const resultPanel = document.createElement('div');
|
|
resultPanel.id = 'tune-result-panel';
|
|
resultPanel.style.cssText = 'display:none;flex-direction:column;gap:6px;';
|
|
const resultLabel = document.createElement('label');
|
|
resultLabel.style.cssText = 'font-weight:600;font-size:13px;color:var(--text-secondary);';
|
|
resultLabel.textContent = 'Generiertes Template';
|
|
resultPanel.appendChild(resultLabel);
|
|
|
|
const resultTextarea = document.createElement('textarea');
|
|
resultTextarea.id = 'tune-result';
|
|
resultTextarea.readOnly = true;
|
|
resultTextarea.style.cssText = 'width:100%;min-height:200px;padding:10px;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:4px;font-family:var(--mono);font-size:13px;resize:vertical;';
|
|
resultPanel.appendChild(resultTextarea);
|
|
container.appendChild(resultPanel);
|
|
}
|
|
|
|
/**
|
|
* Sende Preprompt + Template an LLM und zeige Ergebnis
|
|
*/
|
|
async function generateTunedTemplate() {
|
|
const preprompt = document.getElementById('tune-preprompt').value.trim();
|
|
const original = document.getElementById('tune-original').value;
|
|
|
|
if (!preprompt) {
|
|
showToast('✗ Bitte Preprompt eingeben');
|
|
return;
|
|
}
|
|
|
|
const btn = document.getElementById('tune-generate-btn');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '⏳ Generiere...';
|
|
|
|
const resultPanel = document.getElementById('tune-result-panel');
|
|
const resultTextarea = document.getElementById('tune-result');
|
|
resultPanel.style.display = 'flex';
|
|
resultTextarea.value = 'Warte auf Generierung...';
|
|
|
|
const systemPrompt = 'Du bist ein Experte für Prompt-Engineering. Du erhältst ein vorhandenes Template und Verbesserungswünsche. Gib NUR das verbesserte Template zurück. Keine Erklärungen, keine Markdown-Codeblöcke, keine Zitatzeichen. Nur den reinen Template-Text.';
|
|
|
|
const userPrompt = `Verbessere dieses Template basierend auf den folgenden Anweisungen:
|
|
|
|
=== Vorhandenes Template ===
|
|
${original}
|
|
|
|
=== Verbesserungswünsche ===
|
|
${preprompt}
|
|
|
|
Gib das verbesserte Template zurück.`;
|
|
|
|
try {
|
|
const response = await fetch(`${LLM_BASE}/chat/completions`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
model: LLM_MODEL,
|
|
messages: [
|
|
{ role: 'system', content: systemPrompt },
|
|
{ role: 'user', content: userPrompt }
|
|
],
|
|
stream: false,
|
|
temperature: 0.7,
|
|
max_tokens: 2048
|
|
})
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const data = await response.json();
|
|
let generated = data.choices?.[0]?.message?.content || '';
|
|
|
|
// Bereinige Markdown-Codeblöcke falls vorhanden
|
|
generated = generated.replace(/^```(?:markdown|text)?\s*\n?/i, '').replace(/\n```$/, '');
|
|
generated = generated.trim();
|
|
|
|
window._tuneState.generated = generated;
|
|
resultTextarea.value = generated || '(Leer - nothing generated)';
|
|
|
|
// Ergebnis-Panel hervorheben und einblenden
|
|
resultPanel.style.border = '1px solid var(--accent)';
|
|
resultPanel.style.transition = 'border-color 0.3s';
|
|
document.getElementById('tune-apply-btn').style.display = '';
|
|
resultPanel.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
showToast('✓ Template generiert');
|
|
} catch (e) {
|
|
resultTextarea.value = `Fehler: ${e.message}\n\nStelle sicher, dass der llama.cpp Server auf ${LLM_BASE} läuft.`;
|
|
showToast(`✗ ${e.message}`);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = `
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 2v4m0 12v4m8-8h-4M6 12H2m14.5-7.5L16 5.5m-8 8L5.5 16m13-10.5L16 8m-8 8L5.5 16"/>
|
|
</svg>
|
|
Generieren`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generiertes Template übernehmen (ersetzt template-Feld im JSON)
|
|
*/
|
|
async function applyTunedContent() {
|
|
const state = window._tuneState;
|
|
const generated = document.getElementById('tune-result').value;
|
|
|
|
if (!generated || generated.startsWith('Fehler:') || generated.startsWith('Leer')) {
|
|
showToast('✗ Kein gültiges Ergebnis zum Übernehmen');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(state.path);
|
|
if (!response.ok) throw new Error('Konnte Template nicht laden');
|
|
|
|
const data = await response.json();
|
|
data.template = generated;
|
|
|
|
const saveResponse = await fetch(state.path, {
|
|
method: 'PUT',
|
|
headers: {'Content-Type': 'text/plain'},
|
|
body: JSON.stringify(data, null, 2)
|
|
});
|
|
|
|
if (saveResponse.ok) {
|
|
showToast('✓ Template übernommen und gespeichert');
|
|
closeTuneModal();
|
|
} else {
|
|
throw new Error(`HTTP ${saveResponse.status}`);
|
|
}
|
|
} catch (e) {
|
|
showToast(`✗ Fehler beim Speichern: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schließe Tune-Modal
|
|
*/
|
|
function closeTuneModal() {
|
|
window._lastFocusedElement = document.activeElement;
|
|
var modal = document.getElementById('tune-modal');
|
|
modal.classList.remove('active');
|
|
_setAriaHidden('tune-modal', true);
|
|
if (window._lastFocusedElement && window._lastFocusedElement.focus) {
|
|
window._lastFocusedElement.focus();
|
|
}
|
|
window._tuneState = { path: '', rawPath: '', template: '' };
|
|
}
|
|
|
|
// Export for api.js (global scope, loaded before api.js)
|
|
// editModalContent, createTextEditUI, createJsonEditUI, extractInputValue, extractJsonFromForm
|
|
// sind als globale Funktionen verfügbar
|