From 9910d48bd6d55b52fb41d06cc6a5092a48a89058 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 3 May 2026 15:34:29 +0200 Subject: [PATCH] feat: Prompt-Tunen-Dialog mit LLM-Generierung (llama.cpp) --- web/index.html | 26 +++++ web/js/editor.js | 229 ++++++++++++++++++++++++++++++++++++++++++++ web/js/templates.js | 1 + 3 files changed, 256 insertions(+) diff --git a/web/index.html b/web/index.html index daf4bd7..848e20e 100644 --- a/web/index.html +++ b/web/index.html @@ -49,6 +49,32 @@
+ + +

Prompt Templates

Git Managed diff --git a/web/js/editor.js b/web/js/editor.js index 2528a02..6ac25ca 100644 --- a/web/js/editor.js +++ b/web/js/editor.js @@ -295,6 +295,235 @@ function extractJsonFromForm(formDiv) { 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 = ` + + + + Generieren`; + + const container = document.getElementById('tune-content'); + container.innerHTML = '
Lade Template...
'; + 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 = `
✗ Fehler: ${e.message}
`; + }); +} + +/** + * 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 = ` + + + + 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() { + document.getElementById('tune-modal').classList.remove('active'); + 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 diff --git a/web/js/templates.js b/web/js/templates.js index ba6c7d5..baba283 100644 --- a/web/js/templates.js +++ b/web/js/templates.js @@ -37,6 +37,7 @@ function renderTemplates(templates) { +
`).join('');