homelab-brain/fuenfvoracht/src/templates/prompts.html
2026-02-27 06:47:20 +07:00

224 lines
9.4 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Prompts — FünfVorAcht</title>
<link rel="stylesheet" href="/static/tailwind.min.css">
<style>
body { background: #0f172a; color: #e2e8f0; }
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; }
.tg-preview { background: #212d3b; border-left: 3px solid #3b82f6; font-family: system-ui; white-space: pre-wrap; line-height: 1.6; }
textarea { background: #0f172a; border: 1px solid #334155; color: #e2e8f0; border-radius: 8px; resize: vertical; }
textarea:focus { outline: none; border-color: #3b82f6; }
input[type=text] { background: #0f172a; border: 1px solid #334155; color: #e2e8f0; border-radius: 8px; }
input[type=text]:focus { outline: none; border-color: #3b82f6; }
</style>
</head>
<body class="min-h-screen">
<nav class="bg-slate-900 border-b border-slate-700 px-6 py-4 flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="flex items-center gap-1 bg-slate-800 border border-slate-700 rounded-lg p-1">
<span class="flex items-center gap-1.5 bg-slate-700 text-white text-xs font-semibold px-3 py-1.5 rounded-md">🕗 FünfVorAcht</span>
<a href="https://redakteur.orbitalo.net" target="_blank"
class="flex items-center gap-1.5 text-slate-500 hover:text-slate-200 hover:bg-slate-700 text-xs font-medium px-3 py-1.5 rounded-md transition">📝 Redakteur</a>
</div>
</div>
<div class="flex gap-4 text-sm">
<a href="/" class="text-slate-400 hover:text-white">Studio</a>
<a href="/history" class="text-slate-400 hover:text-white">History</a>
<a href="/prompts" class="text-blue-400 font-semibold">Prompts</a>
<a href="/settings" class="text-slate-400 hover:text-white">Einstellungen</a>
<a href="/hilfe" class="text-slate-400 hover:text-white">❓ Hilfe</a>
</div>
</nav>
<div class="max-w-6xl mx-auto px-6 py-8">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-white">🧠 Prompt-Bibliothek</h1>
<button onclick="showNewPrompt()"
class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg text-sm transition">
+ Neuer Prompt
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Prompt Liste -->
<div class="space-y-4">
{% for p in prompts %}
<div class="card p-4 cursor-pointer hover:border-blue-500 transition {% if p.is_default %}border-green-600{% endif %}"
onclick="loadPrompt({{ p.id }}, {{ p.system_prompt|tojson }}, {{ p.name|tojson }}, {{ p.test_result|tojson if p.test_result else 'null' }}, {{ p.is_default }})">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="font-semibold text-white">{{ p.name }}</span>
{% if p.is_default %}
<span class="bg-green-800 text-green-300 text-xs px-2 py-0.5 rounded-full">Standard</span>
{% endif %}
</div>
{% if not p.is_default %}
<form action="/prompts/delete/{{ p.id }}" method="POST" onclick="event.stopPropagation()" onsubmit="return confirm('Prompt «{{ p.name }}» wirklich löschen?')">
<button type="submit" class="text-red-400 hover:text-red-300 text-xs px-2 py-1 rounded hover:bg-red-900/30 transition">🗑</button>
</form>
{% endif %}
</div>
<div class="text-xs text-slate-400 truncate">{{ p.system_prompt[:100] }}…</div>
{% if p.last_tested_at %}
<div class="text-xs text-slate-500 mt-1">Zuletzt getestet: {{ p.last_tested_at[:16] }}</div>
{% endif %}
</div>
{% endfor %}
</div>
<!-- Editor + Preview -->
<div class="space-y-4">
<div class="card p-4" id="editor-card">
<h2 class="text-white font-semibold mb-4" id="editor-title">Prompt auswählen</h2>
<input type="hidden" id="prompt-id">
<div class="mb-3">
<label class="text-xs text-slate-400 mb-1 block">Name</label>
<input type="text" id="prompt-name" class="w-full px-3 py-2 text-sm" placeholder="z.B. Standard">
</div>
<div class="mb-3">
<label class="text-xs text-slate-400 mb-1 block">
System-Prompt
<span class="text-slate-600 ml-2">Variablen: {source} {date} {tag}</span>
</label>
<textarea id="prompt-text" class="w-full px-3 py-2 text-sm" rows="12" placeholder="Prompt hier eingeben…"></textarea>
</div>
<div class="mb-3">
<label class="text-xs text-slate-400 mb-1 block">Test-Quelle (URL oder Text)</label>
<input type="text" id="test-source" class="w-full px-3 py-2 text-sm"
value="https://tagesschau.de" placeholder="URL oder Text zum Testen">
</div>
<div class="flex gap-2 flex-wrap">
<button onclick="testPrompt()"
class="bg-yellow-700 hover:bg-yellow-600 text-white px-4 py-2 rounded-lg text-sm transition">
🧪 Testen
</button>
<button onclick="savePrompt()"
class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded-lg text-sm transition">
💾 Speichern
</button>
<button onclick="setDefault()"
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg text-sm transition">
⭐ Als Standard
</button>
<button onclick="deleteCurrentPrompt()" id="btn-delete"
class="bg-red-800 hover:bg-red-700 text-white px-4 py-2 rounded-lg text-sm transition ml-auto"
style="display:none">
🗑 Löschen
</button>
</div>
</div>
<!-- Test-Preview -->
<div class="card p-4" id="preview-card" style="display:none">
<div class="flex items-center justify-between mb-3">
<h3 class="text-white font-semibold">📱 Telegram-Vorschau</h3>
<span class="text-xs text-slate-500" id="char-count"></span>
</div>
<div id="tg-preview" class="tg-preview rounded-lg p-4 text-sm text-slate-200"></div>
</div>
</div>
</div>
</div>
<script>
let currentPromptId = null;
let currentIsDefault = false;
function loadPrompt(id, text, name, testResult, isDefault) {
currentPromptId = id;
currentIsDefault = !!isDefault;
document.getElementById('prompt-id').value = id;
document.getElementById('prompt-name').value = name;
document.getElementById('prompt-text').value = text;
document.getElementById('editor-title').textContent = `Bearbeiten: ${name}`;
document.getElementById('btn-delete').style.display = isDefault ? 'none' : 'block';
if (testResult) {
showPreview(testResult);
}
}
function showNewPrompt() {
currentPromptId = null;
currentIsDefault = false;
document.getElementById('prompt-id').value = '';
document.getElementById('prompt-name').value = '';
document.getElementById('prompt-text').value = '';
document.getElementById('editor-title').textContent = 'Neuer Prompt';
document.getElementById('btn-delete').style.display = 'none';
}
async function deleteCurrentPrompt() {
if (!currentPromptId) return;
if (currentIsDefault) return alert('Standard-Prompt kann nicht gelöscht werden');
const name = document.getElementById('prompt-name').value;
if (!confirm(`Prompt «${name}» wirklich löschen?`)) return;
await fetch(`/prompts/delete/${currentPromptId}`, {method: 'POST'});
location.reload();
}
async function testPrompt() {
const system_prompt = document.getElementById('prompt-text').value;
const source = document.getElementById('test-source').value;
const btn = event.target;
btn.textContent = '⏳ Generiere...';
btn.disabled = true;
try {
const r = await fetch('/prompts/test', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({system_prompt, source, tag: 'Politik', prompt_id: currentPromptId})
});
const d = await r.json();
if (d.success) {
showPreview(d.result);
} else {
alert('Fehler: ' + d.error);
}
} catch(e) {
alert('Verbindungsfehler: ' + e);
} finally {
btn.textContent = '🧪 Testen';
btn.disabled = false;
}
}
function showPreview(text) {
const preview = document.getElementById('tg-preview');
const card = document.getElementById('preview-card');
// Render Telegram HTML tags visually
let html = text
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/&lt;b&gt;(.*?)&lt;\/b&gt;/gs, '<strong>$1</strong>')
.replace(/&lt;i&gt;(.*?)&lt;\/i&gt;/gs, '<em>$1</em>')
.replace(/&lt;a href="(.*?)"&gt;(.*?)&lt;\/a&gt;/gs, '<a href="$1" class="text-blue-400 underline">$2</a>');
preview.innerHTML = html;
document.getElementById('char-count').textContent = `${text.length} Zeichen ${text.length > 4096 ? '⚠️ ZU LANG' : '✓'}`;
card.style.display = 'block';
}
async function savePrompt() {
const form = new FormData();
form.append('id', document.getElementById('prompt-id').value);
form.append('name', document.getElementById('prompt-name').value);
form.append('system_prompt', document.getElementById('prompt-text').value);
const r = await fetch('/prompts/save', {method: 'POST', body: form});
location.reload();
}
async function setDefault() {
if (!currentPromptId) return alert('Zuerst Prompt auswählen');
const r = await fetch(`/prompts/default/${currentPromptId}`, {method: 'POST'});
location.reload();
}
</script>
</body>
</html>