🎯 KI-Modellverwaltung - Dynamisches Laden verfügbarer Modelle via opencode models - 29 Modelle verfügbar (opencode, anthropic, ollama) - Gruppierung nach Anbieter in UI - Cache-Mechanismus (1h TTL) für Performance - API-Endpoint /api/models für Modellabfrage 🧠 Memory-System komplett überarbeitet - JSON-basierte strukturierte Erinnerungen statt Markdown-Chaos - Separate Memory-Typen: tasks.json, notes.json, research.json - Automatische Memory-Zusammenfassung im Systemprompt - Limitierung auf letzte 100 Einträge pro Typ - Vollständige Task-Ergebnisse statt abgeschnittener Texte 📁 Agenten-Ordnerstruktur - work/ Verzeichnis für Agent-Dateien - memory/ Verzeichnis für strukturierte Erinnerungen - Agenten arbeiten nur in eigenem work-Verzeichnis - Absolute Pfade werden übergeben - Dateien-UI zeigt Agent-Work-Folders 💬 Chat-System überarbeitet - Echte Agent-Ausführung statt Mock-Responses - Server-Sent Events für Live-Streaming - Session-basierte Chat-History - Loading-Spinner und Status-Anzeigen - Automatisches Speichern in Session 🎭 Personality Integration - personality.md wird jetzt geladen - Persönlichkeit vor Systemprompt eingefügt - Gilt für alle: Chat, Tasks, Orchestrator, Email-Poller ✨ Weitere Verbesserungen - Alle Agenten nutzen execute_agent_task() zentral - Memory-Speicherung nach jedem Task - Work-Files in Datei-Verwaltung sichtbar - System-Dateien ausgeblendet - API-Route für Agent-Work-Dateien
314 lines
12 KiB
HTML
314 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Agenten{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1>Agenten-Verwaltung</h1>
|
|
<p>System-Prompts, Erinnerungen und Persönlichkeit bearbeiten</p>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-header bg-dark">
|
|
<h5 class="mb-0">Agenten</h5>
|
|
</div>
|
|
<div class="list-group list-group-flush">
|
|
{% for agent in agents_list %}
|
|
<a href="{{ url_for('agents', edit=agent.name) }}"
|
|
class="list-group-item list-group-item-action {% if edit_agent == agent.name %}active{% endif %}">
|
|
<strong>{{ agent.name.replace('_', ' ').title() }}</strong>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-9">
|
|
{% if edit_agent %}
|
|
<div class="card">
|
|
<div class="card-header bg-warning">
|
|
<h5 class="mb-0">Bearbeiten: {{ edit_agent.replace('_', ' ').title() }}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" action="{{ url_for('agents', edit=edit_agent) }}">
|
|
<input type="hidden" name="agent_name" value="{{ edit_agent }}">
|
|
|
|
<ul class="nav nav-tabs mb-3" id="agentTab" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="prompt-tab" data-bs-toggle="tab" data-bs-target="#prompt" type="button">
|
|
System-Prompt
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="reminders-tab" data-bs-toggle="tab" data-bs-target="#reminders" type="button">
|
|
Erinnerungen
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="personality-tab" data-bs-toggle="tab" data-bs-target="#personality" type="button">
|
|
Persönlichkeit
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="model-tab" data-bs-toggle="tab" data-bs-target="#model" type="button">
|
|
Modell
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link text-danger" id="danger-tab" data-bs-toggle="tab" data-bs-target="#danger" type="button">
|
|
⚠️ Löschen
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" id="agentTabContent">
|
|
<div class="tab-pane fade show active" id="prompt">
|
|
<div class="mb-3">
|
|
<label for="prompt_content" class="form-label">System-Prompt</label>
|
|
<textarea class="form-control font-monospace" id="prompt_content" name="prompt_content"
|
|
rows="18" style="font-size:.82rem;line-height:1.5;">{{ edit_prompt }}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="reminders">
|
|
<div class="mb-3">
|
|
<label for="reminders_content" class="form-label">Erinnerungen</label>
|
|
<div class="form-text mb-2">
|
|
Hier kannst du manuell Notizen hinzufügen. Agenten können diese Datei auch selbst beschreiben.
|
|
</div>
|
|
<textarea class="form-control font-monospace" id="reminders_content" name="reminders_content"
|
|
rows="18" style="font-size:.82rem;line-height:1.5;">{{ edit_reminders }}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="personality">
|
|
<div class="mb-3">
|
|
<label for="personality_content" class="form-label">Persönlichkeit (optional)</label>
|
|
<div class="form-text mb-2">
|
|
Definiere die Persönlichkeitszüge des Agenten.
|
|
</div>
|
|
<textarea class="form-control font-monospace" id="personality_content" name="personality_content"
|
|
rows="18" style="font-size:.82rem;line-height:1.5;">{{ edit_personality }}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="model">
|
|
<div class="mb-3">
|
|
<label for="model_select" class="form-label">KI-Modell</label>
|
|
<div class="form-text mb-2">
|
|
Wähle das KI-Modell, das dieser Agent verwendet. <span class="badge bg-secondary">{{ available_models.count }} Modelle</span>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" onclick="refreshModels()">
|
|
<span id="refresh-icon">🔄</span> Aktualisieren
|
|
</button>
|
|
</div>
|
|
<select class="form-select" id="model_select" name="model_select" onchange="saveModel('{{ edit_agent }}')">
|
|
{% if available_models.grouped %}
|
|
{% for provider, models in available_models.grouped.items() %}
|
|
<optgroup label="{{ provider }}">
|
|
{% for model in models %}
|
|
<option value="{{ model }}" {% if edit_model == model %}selected{% endif %}>{{ model }}</option>
|
|
{% endfor %}
|
|
</optgroup>
|
|
{% endfor %}
|
|
{% else %}
|
|
{% for model in available_models.models %}
|
|
<option value="{{ model }}" {% if edit_model == model %}selected{% endif %}>{{ model }}</option>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</select>
|
|
<div id="modelStatus" class="form-text mt-2"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="danger">
|
|
<div class="alert alert-danger">
|
|
<h5 class="alert-heading">⚠️ Danger Zone</h5>
|
|
<p class="mb-2">Hier kannst du diesen Agenten dauerhaft löschen. <strong>Diese Aktion kann nicht rückgängig gemacht werden!</strong></p>
|
|
<p class="mb-3"><small>Alle Dateien im Ordner <code>agents/{{ edit_agent }}/</code> werden gelöscht.</small></p>
|
|
<button type="button" class="btn btn-danger" onclick="deleteAgent('{{ edit_agent }}')">
|
|
Agent löschen
|
|
</button>
|
|
<div id="deleteStatus" class="form-text mt-2"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">Speichern</button>
|
|
<a href="{{ url_for('agents') }}" class="btn btn-secondary">Abbrechen</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="card-header bg-dark d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Alle Agenten</h5>
|
|
<span class="badge bg-secondary">{{ agents_list|length }}</span>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if agents_list %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Agent</th>
|
|
<th>Prompt-Vorschau</th>
|
|
<th style="width:120px;">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for agent in agents_list %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ agent.name.replace('_', ' ').title() }}</strong>
|
|
</td>
|
|
<td>
|
|
<small style="color:var(--text-muted);">
|
|
{{ agent.prompt[:160] }}{% if agent.prompt|length > 160 %}…{% endif %}
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<a href="{{ url_for('agents', edit=agent.name) }}" class="btn btn-sm btn-outline-primary">Bearbeiten</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-warning m-3">
|
|
Keine Agenten gefunden. Stelle sicher, dass <code>agents/</code> Unterverzeichnisse mit <code>systemprompt.md</code> enthält.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function saveModel(agentName) {
|
|
const select = document.getElementById('model_select');
|
|
const model = select.value;
|
|
const status = document.getElementById('modelStatus');
|
|
|
|
fetch('/api/agent/' + agentName + '/model', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ model: model })
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
status.textContent = '✓ ' + data.message;
|
|
status.className = 'form-text mt-2 text-success';
|
|
} else {
|
|
status.textContent = 'Fehler: ' + data.error;
|
|
status.className = 'form-text mt-2 text-danger';
|
|
}
|
|
})
|
|
.catch(err => {
|
|
status.textContent = 'Fehler: ' + err.message;
|
|
status.className = 'form-text mt-2 text-danger';
|
|
});
|
|
}
|
|
|
|
function refreshModels() {
|
|
const icon = document.getElementById('refresh-icon');
|
|
const status = document.getElementById('modelStatus');
|
|
const select = document.getElementById('model_select');
|
|
const currentModel = select.value;
|
|
|
|
icon.textContent = '⏳';
|
|
status.textContent = 'Lade Modelle...';
|
|
status.className = 'form-text mt-2 text-info';
|
|
|
|
fetch('/api/models?refresh=true')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
// Dropdown neu aufbauen
|
|
select.innerHTML = '';
|
|
|
|
if (data.grouped) {
|
|
Object.keys(data.grouped).forEach(provider => {
|
|
const optgroup = document.createElement('optgroup');
|
|
optgroup.label = provider;
|
|
|
|
data.grouped[provider].forEach(model => {
|
|
const option = document.createElement('option');
|
|
option.value = model;
|
|
option.textContent = model;
|
|
if (model === currentModel) {
|
|
option.selected = true;
|
|
}
|
|
optgroup.appendChild(option);
|
|
});
|
|
|
|
select.appendChild(optgroup);
|
|
});
|
|
} else {
|
|
data.models.forEach(model => {
|
|
const option = document.createElement('option');
|
|
option.value = model;
|
|
option.textContent = model;
|
|
if (model === currentModel) {
|
|
option.selected = true;
|
|
}
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
icon.textContent = '🔄';
|
|
status.textContent = '✓ ' + data.count + ' Modelle geladen';
|
|
status.className = 'form-text mt-2 text-success';
|
|
|
|
setTimeout(() => {
|
|
status.textContent = '';
|
|
}, 3000);
|
|
})
|
|
.catch(err => {
|
|
icon.textContent = '🔄';
|
|
status.textContent = 'Fehler beim Laden: ' + err.message;
|
|
status.className = 'form-text mt-2 text-danger';
|
|
});
|
|
}
|
|
|
|
function deleteAgent(agentName) {
|
|
if (!confirm('Willst du den Agenten "' + agentName + '" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden!')) {
|
|
return;
|
|
}
|
|
if (!confirm('Sind Sie ABSOLUT sicher? Alle Dateien werden gelöscht!')) {
|
|
return;
|
|
}
|
|
|
|
const status = document.getElementById('deleteStatus');
|
|
status.textContent = 'Lösche...';
|
|
status.className = 'form-text mt-2 text-warning';
|
|
|
|
fetch('/api/agent/' + agentName + '/delete', {
|
|
method: 'DELETE'
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
status.textContent = '✓ ' + data.message;
|
|
status.className = 'form-text mt-2 text-success';
|
|
setTimeout(() => {
|
|
window.location.href = '/agents';
|
|
}, 1500);
|
|
} else {
|
|
status.textContent = 'Fehler: ' + data.error;
|
|
status.className = 'form-text mt-2 text-danger';
|
|
}
|
|
})
|
|
.catch(err => {
|
|
status.textContent = 'Fehler: ' + err.message;
|
|
status.className = 'form-text mt-2 text-danger';
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|