Profil-Seite: Anzeigename, Passwort ändern, Persona (ersetzt USER.md)

- Neuer Menüpunkt 'Mein Profil' statt 'Logout'
- Profil-Page: Anzeigename, Persona (Freitext), Passwort ändern, Logout
- ProfileEndpoint: GET/PUT /api/profile, PUT /api/profile/password
- User-Persona im System-Prompt statt USER.md (Fallback: Anzeigename/Username)
- SystemPromptBuilder: dataDir-Parameter entfernt, userPersona-Parameter hinzugefügt
- DB-Migration: persona-Spalte in users-Tabelle
This commit is contained in:
Meleki committed 2026-03-02 15:02:39 +01:00
1 parent f49c526384
commit 7149904ae4
12 files changed
+341 -26

No files matched your search

+24
View File
@@ -195,6 +195,30 @@ label.skill-item {
margin-left: auto;
}
/* ── Profile ── */
.profile-message {
padding: var(--badge-spacing) var(--box-spacing);
border-radius: var(--border-radius);
margin-top: var(--badge-spacing);
}
.profile-message-success {
background: rgba(0, 180, 0, 0.15);
color: #0a0;
}
.profile-message-error {
background: rgba(220, 0, 0, 0.15);
color: var(--dangerous);
}
.profile-divider {
border: none;
border-top: var(--border-width) solid var(--f2-border);
margin: var(--box-spacing) 0;
}
.danger-btn {
color: var(--dangerous);
border-color: var(--dangerous);
+40 -1
View File
@@ -50,7 +50,7 @@
<h1 class="dashboard-title">// gott</h1>
<div class="dashboard-links">
<a id="dashboardSettingsLink" class="dashboard-link">Einstellungen</a>
<a id="dashboardLogoutLink" class="dashboard-link">Logout</a>
<a id="dashboardProfileLink" class="dashboard-link">Mein Profil</a>
</div>
</header>
<div class="page-content">
@@ -288,6 +288,44 @@
</div>
</template>
<template id="tpl-profile">
<div class="page form-page">
<header>
<button class="back-btn icon-btn" data-page="dashboard"><i class="fa fa-arrow-left"></i></button>
<h1>Mein Profil</h1>
</header>
<div class="page-content">
<div class="form">
<label>Benutzername</label>
<input type="text" id="profileUsername" disabled>
<label>Anzeigename</label>
<input type="text" id="profileDisplayName" placeholder="Anzeigename">
<label>Persona</label>
<span class="setting-hint">Wird im System-Prompt verwendet, wenn der Agent "Kennt sein Gegenüber" aktiviert hat. Ersetzt USER.md.</span>
<textarea id="profilePersona" rows="8" placeholder="Beschreibe dich selbst – Name, Rolle, Präferenzen, Kommunikationsstil..."></textarea>
<div class="form-actions">
<button id="profileSaveBtn">Speichern</button>
</div>
<div class="profile-message" id="profileMessage" hidden></div>
<hr class="profile-divider">
<h2>Passwort ändern</h2>
<label>Aktuelles Passwort</label>
<input type="password" id="profileCurrentPassword" placeholder="Aktuelles Passwort" autocomplete="current-password">
<label>Neues Passwort</label>
<input type="password" id="profileNewPassword" placeholder="Neues Passwort" autocomplete="new-password">
<label>Neues Passwort bestätigen</label>
<input type="password" id="profileConfirmPassword" placeholder="Passwort wiederholen" autocomplete="new-password">
<div class="form-actions">
<button id="profilePasswordBtn">Passwort ändern</button>
</div>
<div class="profile-message" id="profilePasswordMessage" hidden></div>
<hr class="profile-divider">
<button id="profileLogoutBtn" class="danger-btn">Abmelden</button>
</div>
</div>
</div>
</template>
<script src="js/lib/marked.min.js"></script>
<script src="js/lib/codemirror.min.js"></script>
<script src="js/lib/cm-mode-xml.min.js"></script>
@@ -315,6 +353,7 @@
<script src="js/pages/chat-page.js"></script>
<script src="js/pages/login-page.js"></script>
<script src="js/pages/settings-page.js"></script>
<script src="js/pages/profile-page.js"></script>
<script src="js/app.js"></script>
</body>
</html>
+34
View File
@@ -221,6 +221,40 @@ async function apiRemoveAgentSkill(agentId, slug) {
return fetch(`/api/agents/${agentId}/skills/${slug}`, { method: 'DELETE' });
}
// ── Profile API ──
async function apiGetProfile() {
const resp = await fetch('/api/profile');
if (!resp.ok) return null;
return resp.json();
}
async function apiUpdateProfile(data) {
const resp = await fetch('/api/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Fehler beim Speichern');
}
return resp.json();
}
async function apiChangePassword(currentPassword, newPassword) {
const resp = await fetch('/api/profile/password', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ currentPassword, newPassword })
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Fehler beim Ändern');
}
return resp.json();
}
async function apiBackupDb() {
const resp = await fetch('/api/backup', { method: 'POST' });
if (!resp.ok) throw new Error('Backup fehlgeschlagen');
+3 -4
View File
@@ -8,11 +8,10 @@ registerPage('dashboard', {
navigateTo('settings');
});
// Logout-Link
document.getElementById('dashboardLogoutLink').addEventListener('click', async (e) => {
// Profil-Link
document.getElementById('dashboardProfileLink').addEventListener('click', (e) => {
e.preventDefault();
await apiLogout();
navigateTo('login');
navigateTo('profile');
});
// Globaler Click-Handler zum Schließen aller Card-Menüs
+112
View File
@@ -0,0 +1,112 @@
// ── Profile Page ──
registerPage('profile', {
async init() {
var profile = await apiGetProfile();
if (!profile) return;
var usernameInput = document.getElementById('profileUsername');
var displayNameInput = document.getElementById('profileDisplayName');
var personaInput = document.getElementById('profilePersona');
var saveBtn = document.getElementById('profileSaveBtn');
var messageDiv = document.getElementById('profileMessage');
var currentPwInput = document.getElementById('profileCurrentPassword');
var newPwInput = document.getElementById('profileNewPassword');
var confirmPwInput = document.getElementById('profileConfirmPassword');
var passwordBtn = document.getElementById('profilePasswordBtn');
var passwordMessageDiv = document.getElementById('profilePasswordMessage');
var logoutBtn = document.getElementById('profileLogoutBtn');
// Felder befüllen
usernameInput.value = profile.username || '';
displayNameInput.value = profile.displayName || '';
personaInput.value = profile.persona || '';
// Profil speichern
saveBtn.addEventListener('click', async function() {
saveBtn.disabled = true;
saveBtn.textContent = 'Speichern...';
messageDiv.hidden = true;
try {
var updated = await apiUpdateProfile({
displayName: displayNameInput.value.trim(),
persona: personaInput.value.trim()
});
// currentUser aktualisieren
if (currentUser) {
currentUser.displayName = updated.displayName;
}
messageDiv.textContent = 'Profil gespeichert';
messageDiv.className = 'profile-message profile-message-success';
messageDiv.hidden = false;
setTimeout(function() { messageDiv.hidden = true; }, 3000);
} catch (err) {
messageDiv.textContent = err.message;
messageDiv.className = 'profile-message profile-message-error';
messageDiv.hidden = false;
} finally {
saveBtn.disabled = false;
saveBtn.textContent = 'Speichern';
}
});
// Passwort ändern
passwordBtn.addEventListener('click', async function() {
passwordMessageDiv.hidden = true;
var currentPw = currentPwInput.value;
var newPw = newPwInput.value;
var confirmPw = confirmPwInput.value;
if (!currentPw || !newPw) {
passwordMessageDiv.textContent = 'Alle Felder ausfüllen';
passwordMessageDiv.className = 'profile-message profile-message-error';
passwordMessageDiv.hidden = false;
return;
}
if (newPw !== confirmPw) {
passwordMessageDiv.textContent = 'Neue Passwörter stimmen nicht überein';
passwordMessageDiv.className = 'profile-message profile-message-error';
passwordMessageDiv.hidden = false;
return;
}
passwordBtn.disabled = true;
passwordBtn.textContent = 'Ändern...';
try {
await apiChangePassword(currentPw, newPw);
currentPwInput.value = '';
newPwInput.value = '';
confirmPwInput.value = '';
passwordMessageDiv.textContent = 'Passwort geändert';
passwordMessageDiv.className = 'profile-message profile-message-success';
passwordMessageDiv.hidden = false;
setTimeout(function() { passwordMessageDiv.hidden = true; }, 3000);
} catch (err) {
passwordMessageDiv.textContent = err.message;
passwordMessageDiv.className = 'profile-message profile-message-error';
passwordMessageDiv.hidden = false;
} finally {
passwordBtn.disabled = false;
passwordBtn.textContent = 'Passwort ändern';
}
});
// Logout
logoutBtn.addEventListener('click', async function() {
await apiLogout();
currentUser = null;
navigateTo('login');
});
},
destroy() {}
});