(() => {
const API_URL = 'https://fb.srv.medeba-media.de/api';
const PROVIDER_MODELS = {
gemini: [
{ value: '', label: 'Standard (gemini-2.0-flash-exp)' },
{ value: 'gemini-2.0-flash-exp', label: 'Gemini 2.0 Flash' },
{ value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash' },
{ value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro' }
],
claude: [
{ value: '', label: 'Standard (claude-3-5-haiku)' },
{ value: 'claude-3-5-haiku-20241022', label: 'Claude 3.5 Haiku (schnell)' },
{ value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet (beste Qualität)' },
{ value: 'claude-3-opus-20240229', label: 'Claude 3 Opus' }
],
openai: [
{ value: '', label: 'Standard (gpt-3.5-turbo)' },
{ value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo (günstig)' },
{ value: 'gpt-4o-mini', label: 'GPT-4o Mini' },
{ value: 'gpt-4o', label: 'GPT-4o' },
{ value: 'gpt-4-turbo', label: 'GPT-4 Turbo' }
]
};
const PROVIDER_INFO = {
gemini: {
name: 'Google Gemini',
apiKeyLink: 'https://aistudio.google.com/app/apikey',
apiKeyHelp: 'API-Schlüssel erforderlich. Erstelle ihn im Google AI Studio.'
},
claude: {
name: 'Anthropic Claude',
apiKeyLink: 'https://console.anthropic.com/settings/keys',
apiKeyHelp: 'API-Schlüssel erforderlich. Erstelle ihn in der Anthropic Console.'
},
openai: {
name: 'OpenAI',
apiKeyLink: 'https://platform.openai.com/api-keys',
apiKeyHelp: 'Für lokale OpenAI-kompatible Server (z.B. Ollama) kannst du den Schlüssel leer lassen.'
}
};
let credentials = [];
let currentSettings = null;
let hiddenSettings = { auto_purge_enabled: true, retention_days: 90 };
function apiFetch(url, options = {}) {
return fetch(url, {...options, credentials: 'include'});
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
bottom: 84px;
right: 24px;
background: ${type === 'error' ? '#e74c3c' : type === 'success' ? '#42b72a' : '#1877f2'};
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
z-index: 999999;
max-width: 350px;
animation: slideIn 0.3s ease-out;
pointer-events: none;
`;
toast.textContent = message;
if (!document.getElementById('settings-toast-styles')) {
const style = document.createElement('style');
style.id = 'settings-toast-styles';
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`;
document.head.appendChild(style);
}
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
function showError(msg) { showToast(msg, 'error'); }
function showSuccess(msg) { showToast(msg, 'success'); }
async function loadCredentials() {
const res = await apiFetch(`${API_URL}/ai-credentials`);
if (!res.ok) throw new Error('Failed to load credentials');
credentials = await res.json();
renderCredentials();
updateActiveCredentialSelect();
}
async function loadSettings() {
const res = await apiFetch(`${API_URL}/ai-settings`);
if (!res.ok) throw new Error('Failed to load settings');
currentSettings = await res.json();
document.getElementById('aiEnabled').checked = currentSettings.enabled === 1;
document.getElementById('activeCredential').value = currentSettings.active_credential_id || '';
document.getElementById('aiPromptPrefix').value = currentSettings.prompt_prefix ||
'Schreibe einen freundlichen, authentischen Kommentar auf Deutsch zu folgendem Facebook-Post. Der Kommentar soll natürlich wirken und maximal 2-3 Sätze lang sein:\n\n';
}
async function loadHiddenSettings() {
const res = await apiFetch(`${API_URL}/hidden-settings`);
if (!res.ok) throw new Error('Failed to load hidden settings');
hiddenSettings = await res.json();
applyHiddenSettingsUI();
}
function applyHiddenSettingsUI() {
const autoToggle = document.getElementById('autoPurgeHiddenToggle');
const retentionInput = document.getElementById('hiddenRetentionDays');
if (autoToggle) {
autoToggle.checked = !!hiddenSettings.auto_purge_enabled;
}
if (retentionInput) {
retentionInput.value = hiddenSettings.retention_days || 90;
retentionInput.disabled = !autoToggle?.checked;
}
}
function normalizeRetentionInput(value) {
const parsed = parseInt(value, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
return 90;
}
return Math.min(365, Math.max(1, parsed));
}
async function saveHiddenSettings(event, { silent = false } = {}) {
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
const autoToggle = document.getElementById('autoPurgeHiddenToggle');
const retentionInput = document.getElementById('hiddenRetentionDays');
const autoEnabled = autoToggle ? autoToggle.checked : true;
const retention = normalizeRetentionInput(retentionInput ? retentionInput.value : hiddenSettings.retention_days);
try {
const res = await apiFetch(`${API_URL}/hidden-settings`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
auto_purge_enabled: autoEnabled,
retention_days: retention
})
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Speichern');
}
hiddenSettings = await res.json();
applyHiddenSettingsUI();
if (!silent) {
showSuccess('✅ Einstellungen für versteckte Beiträge gespeichert');
}
return true;
} catch (err) {
if (!silent) {
showError('❌ ' + err.message);
}
return false;
}
}
async function purgeHiddenNow() {
const btn = document.getElementById('purgeHiddenNowBtn');
const originalText = btn ? btn.textContent : '';
if (btn) {
btn.disabled = true;
btn.textContent = 'Bereinige...';
}
try {
const res = await apiFetch(`${API_URL}/search-posts`, { method: 'DELETE' });
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Bereinigen');
}
showSuccess('🧹 Versteckte Beiträge wurden zurückgesetzt');
} catch (err) {
showError('❌ ' + err.message);
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = originalText || 'Jetzt bereinigen';
}
}
}
function shorten(text, maxLength = 80) {
if (typeof text !== 'string') {
return '';
}
if (text.length <= maxLength) {
return text;
}
return `${text.slice(0, maxLength - 3)}...`;
}
function escapeHtmlAttr(text) {
return escapeHtml(text || '').replace(/"/g, '"');
}
function formatTimeLabel(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
}
function formatRelativePast(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
const diffMs = Date.now() - date.getTime();
if (diffMs < 0) return 'gerade eben';
const diffMinutes = Math.round(diffMs / 60000);
if (diffMinutes <= 1) return 'gerade eben';
if (diffMinutes < 60) return `vor ${diffMinutes} Min`;
const diffHours = Math.round(diffMinutes / 60);
if (diffHours < 24) return `vor ${diffHours} Std`;
const diffDays = Math.round(diffHours / 24);
if (diffDays === 1) return 'gestern';
if (diffDays < 7) return `vor ${diffDays} Tagen`;
return date.toLocaleDateString('de-DE');
}
function formatRelativeFuture(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
const diffMs = date.getTime() - Date.now();
if (diffMs <= 0) return 'gleich';
const diffMinutes = Math.round(diffMs / 60000);
if (diffMinutes < 1) return 'gleich';
if (diffMinutes < 60) return `in ${diffMinutes} Min`;
const diffHours = Math.round(diffMinutes / 60);
if (diffHours < 24) return `in ${diffHours} Std`;
return date.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function buildCredentialBadges(credential) {
const badges = [];
if (!credential.is_active) {
badges.push({
label: 'Deaktiviert',
className: 'status-badge--muted',
title: 'Dieser Login ist derzeit deaktiviert'
});
} else if (credential.auto_disabled) {
const untilText = credential.auto_disabled_until ? formatRelativeFuture(credential.auto_disabled_until) : 'läuft';
const untilTime = credential.auto_disabled_until ? formatTimeLabel(credential.auto_disabled_until) : '';
const reason = credential.auto_disabled_reason ? credential.auto_disabled_reason.replace(/^AUTO:/, '').trim() : 'Automatisch deaktiviert';
badges.push({
label: `Cooldown ${untilText}${untilTime ? ` (${untilTime})` : ''}`.trim(),
className: 'status-badge--warning',
title: reason || 'Automatisch deaktiviert'
});
} else {
badges.push({
label: 'Aktiv',
className: 'status-badge--success',
title: 'Login ist aktiv'
});
}
if (credential.last_error_message) {
badges.push({
label: `Fehler ${formatRelativePast(credential.last_error_at)}`.trim(),
className: 'status-badge--danger',
title: credential.last_error_message
});
}
if (credential.usage_24h_count) {
const resetHint = credential.usage_24h_reset_at ? `Reset ${formatRelativeFuture(credential.usage_24h_reset_at)}` : '24h Nutzung';
badges.push({
label: `24h: ${credential.usage_24h_count}`,
className: 'status-badge--info',
title: resetHint
});
}
if (credential.last_rate_limit_remaining) {
badges.push({
label: `Limit: ${credential.last_rate_limit_remaining}`,
className: 'status-badge--neutral',
title: 'Letzter „rate limit remaining“-Wert'
});
}
return badges;
}
function buildCredentialMetaLines(credential) {
const lines = [];
if (credential.last_success_at) {
lines.push(`Zuletzt erfolgreich: ${formatRelativePast(credential.last_success_at)}`);
}
if (!credential.last_success_at && credential.last_used_at) {
lines.push(`Zuletzt genutzt: ${formatRelativePast(credential.last_used_at)}`);
}
if (credential.rate_limit_reset_at && !credential.auto_disabled) {
lines.push(`Limit-Reset ${formatRelativeFuture(credential.rate_limit_reset_at)}`);
}
if (credential.latest_event && credential.latest_event.type) {
const typeLabel = credential.latest_event.type.replace(/_/g, ' ');
const eventTime = credential.latest_event.created_at ? formatRelativePast(credential.latest_event.created_at) : '';
const message = credential.latest_event.message ? shorten(credential.latest_event.message, 90) : '';
const parts = [`Letztes Event (${typeLabel})`];
if (eventTime) parts.push(eventTime);
if (message) parts.push(`– ${message}`);
lines.push(parts.join(' '));
}
return lines;
}
function renderCredentials() {
const list = document.getElementById('credentialsList');
if (!credentials.length) {
list.innerHTML = '
Noch keine Anmeldedaten gespeichert
';
return;
}
list.innerHTML = credentials.map((c, index) => {
const providerName = escapeHtml(PROVIDER_INFO[c.provider]?.name || c.provider);
const modelLabel = c.model ? ` · ${escapeHtml(c.model)}` : '';
const endpointLabel = c.base_url ? ` · ${escapeHtml(c.base_url)}` : '';
const badges = buildCredentialBadges(c);
const badgesHtml = badges.length
? `${badges.map(badge => `${escapeHtml(badge.label)}`).join('')}
`
: '';
const metaLines = buildCredentialMetaLines(c);
const metaHtml = metaLines.length
? ``
: '';
return `
⋮⋮
`;
}).join('');
// Add drag and drop event listeners
setupDragAndDrop();
}
function updateActiveCredentialSelect() {
const select = document.getElementById('activeCredential');
select.innerHTML = '' +
credentials.map(c => ``).join('');
if (currentSettings?.active_credential_id) {
select.value = currentSettings.active_credential_id;
}
}
function updateModelOptions(provider) {
const modelInput = document.getElementById('credentialModel');
const modelList = document.getElementById('credentialModelOptions');
const apiKeyInput = document.getElementById('credentialApiKey');
const baseUrlGroup = document.getElementById('credentialBaseUrlGroup');
const baseUrlHelp = document.getElementById('credentialBaseUrlHelp');
const baseUrlInput = document.getElementById('credentialBaseUrl');
const info = PROVIDER_INFO[provider];
const models = PROVIDER_MODELS[provider] || [];
if (modelList) {
modelList.innerHTML = models.map(m => ``).join('');
}
if (modelInput) {
const firstSuggestion = models.find(m => m.value)?.value;
modelInput.placeholder = firstSuggestion
? `z.B. ${firstSuggestion}`
: 'Modell-ID (z.B. llama3.1)';
}
const help = document.getElementById('credentialApiKeyHelp');
if (help) {
if (info) {
const parts = [];
if (info.apiKeyHelp) {
parts.push(info.apiKeyHelp);
}
if (info.apiKeyLink) {
parts.push(`API-Schlüssel erstellen`);
}
help.innerHTML = parts.join(' ');
} else {
help.textContent = '';
}
}
if (apiKeyInput) {
if (provider === 'openai') {
apiKeyInput.placeholder = 'sk-... oder leer für lokale Server';
} else {
apiKeyInput.placeholder = 'API-Schlüssel';
}
}
if (baseUrlGroup && baseUrlHelp) {
if (provider === 'openai') {
baseUrlGroup.style.display = 'block';
baseUrlHelp.textContent = 'Leer lassen für die offizielle OpenAI-API. Für lokale OpenAI/Ollama-Server gib die Basis-URL an, z.B. http://localhost:11434/v1';
if (baseUrlInput) {
baseUrlInput.placeholder = 'https://api.openai.com/v1 oder http://localhost:11434/v1';
}
} else {
baseUrlGroup.style.display = 'none';
baseUrlHelp.textContent = '';
if (baseUrlInput) {
baseUrlInput.placeholder = '';
}
}
}
const modelHelp = document.getElementById('credentialModelHelp');
if (modelHelp) {
modelHelp.textContent = 'Trage die Modell-ID ein. Du kannst einen Vorschlag auswählen oder einen eigenen Wert eingeben.';
}
}
function openCredentialModal(credential = null) {
const modal = document.getElementById('credentialModal');
const form = document.getElementById('credentialForm');
const apiKeyInput = document.getElementById('credentialApiKey');
const baseUrlInput = document.getElementById('credentialBaseUrl');
if (credential) {
document.getElementById('credentialModalTitle').textContent = 'Anmeldedaten bearbeiten';
document.getElementById('credentialId').value = credential.id;
document.getElementById('credentialName').value = credential.name;
document.getElementById('credentialProvider').value = credential.provider;
updateModelOptions(credential.provider);
document.getElementById('credentialModel').value = credential.model || '';
if (baseUrlInput) {
baseUrlInput.value = credential.base_url || '';
}
if (apiKeyInput) {
apiKeyInput.value = '';
apiKeyInput.placeholder = credential.provider === 'openai'
? 'Leer lassen, um den bestehenden Schlüssel zu behalten'
: 'Leer lassen, um den bestehenden Schlüssel zu behalten';
}
} else {
document.getElementById('credentialModalTitle').textContent = 'Anmeldedaten hinzufügen';
form.reset();
updateModelOptions('gemini');
document.getElementById('credentialId').value = '';
if (apiKeyInput) {
apiKeyInput.value = '';
apiKeyInput.placeholder = 'API-Schlüssel';
}
if (baseUrlInput) {
baseUrlInput.value = '';
}
}
modal.removeAttribute('hidden');
}
function closeCredentialModal() {
document.getElementById('credentialModal').setAttribute('hidden', '');
}
async function saveCredential(e) {
e.preventDefault();
try {
const id = document.getElementById('credentialId').value;
const name = document.getElementById('credentialName').value.trim();
const provider = document.getElementById('credentialProvider').value;
const apiKey = document.getElementById('credentialApiKey').value.trim();
const model = document.getElementById('credentialModel').value.trim();
const baseUrlRaw = document.getElementById('credentialBaseUrl')?.value.trim() || '';
if (!name) {
throw new Error('Bitte einen Namen angeben');
}
const data = {
name,
provider,
model: model || null,
base_url: provider === 'openai' ? baseUrlRaw : ''
};
if (!id) {
if (!apiKey && !(provider === 'openai' && baseUrlRaw)) {
throw new Error('API-Schlüssel ist erforderlich (oder Basis-URL für lokale OpenAI-kompatible Server angeben)');
}
data.api_key = apiKey;
} else if (apiKey) {
data.api_key = apiKey;
}
const url = id ? `${API_URL}/ai-credentials/${id}` : `${API_URL}/ai-credentials`;
const method = id ? 'PUT' : 'POST';
const res = await apiFetch(url, {
method,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Speichern der Anmeldedaten');
}
await loadCredentials();
closeCredentialModal();
showSuccess('✅ Anmeldedaten erfolgreich gespeichert');
} catch (err) {
showError('❌ ' + err.message);
}
}
async function editCredential(id) {
const cred = credentials.find(c => c.id === id);
if (!cred) {
showError('Anmeldedaten nicht gefunden');
return;
}
openCredentialModal(cred);
}
async function toggleCredentialActive(id, isActive) {
try {
const res = await apiFetch(`${API_URL}/ai-credentials/${id}`, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ is_active: isActive ? 1 : 0 })
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Aktualisieren');
}
await loadCredentials();
showSuccess(`✅ Login ${isActive ? 'aktiviert' : 'deaktiviert'}`);
} catch (err) {
showError('❌ ' + err.message);
await loadCredentials(); // Reload to reset checkbox
}
}
async function deleteCredential(id) {
if (!confirm('Wirklich löschen?')) return;
const res = await apiFetch(`${API_URL}/ai-credentials/${id}`, {method: 'DELETE'});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Failed to delete');
}
await loadCredentials();
showSuccess('Anmeldedaten gelöscht');
}
// Make functions globally accessible for inline handlers
window.toggleCredentialActive = toggleCredentialActive;
window.editCredential = editCredential;
window.deleteCredential = deleteCredential;
// ============================================================================
// DRAG AND DROP
// ============================================================================
let draggedElement = null;
function setupDragAndDrop() {
const items = document.querySelectorAll('.credential-item');
items.forEach(item => {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('drop', handleDrop);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
});
}
function handleDragStart(e) {
draggedElement = this;
this.style.opacity = '0.4';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function handleDragEnter(e) {
if (this !== draggedElement) {
this.classList.add('drag-over');
}
}
function handleDragLeave(e) {
this.classList.remove('drag-over');
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation();
}
if (draggedElement !== this) {
// Get the container
const container = this.parentNode;
const allItems = [...container.querySelectorAll('.credential-item')];
// Get indices
const draggedIndex = allItems.indexOf(draggedElement);
const targetIndex = allItems.indexOf(this);
// Reorder in DOM
if (draggedIndex < targetIndex) {
this.parentNode.insertBefore(draggedElement, this.nextSibling);
} else {
this.parentNode.insertBefore(draggedElement, this);
}
// Update backend
saveCredentialOrder();
}
this.classList.remove('drag-over');
return false;
}
function handleDragEnd(e) {
this.style.opacity = '1';
// Remove all drag-over classes
document.querySelectorAll('.credential-item').forEach(item => {
item.classList.remove('drag-over');
});
}
async function saveCredentialOrder() {
try {
const items = document.querySelectorAll('.credential-item');
const order = Array.from(items).map(item => parseInt(item.dataset.credentialId));
const res = await apiFetch(`${API_URL}/ai-credentials/reorder`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ order })
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Speichern der Reihenfolge');
}
credentials = await res.json();
showSuccess('✅ Reihenfolge gespeichert');
} catch (err) {
showError('❌ ' + err.message);
await loadCredentials(); // Reload to restore original order
}
}
async function saveSettings(e, { silent = false } = {}) {
if (e && typeof e.preventDefault === 'function') {
e.preventDefault();
}
try {
const data = {
enabled: document.getElementById('aiEnabled').checked,
active_credential_id: parseInt(document.getElementById('activeCredential').value) || null,
prompt_prefix: document.getElementById('aiPromptPrefix').value
};
const res = await apiFetch(`${API_URL}/ai-settings`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Speichern der Einstellungen');
}
currentSettings = await res.json();
if (!silent) {
showSuccess('✅ Einstellungen erfolgreich gespeichert');
}
return true;
} catch (err) {
if (!silent) {
showError('❌ ' + err.message);
}
return false;
}
}
async function testComment() {
const modal = document.getElementById('testModal');
modal.removeAttribute('hidden');
document.getElementById('testResult').style.display = 'none';
document.getElementById('testError').style.display = 'none';
// Load last test data from localStorage
const lastTest = localStorage.getItem('lastTestComment');
if (lastTest) {
try {
const data = JSON.parse(lastTest);
document.getElementById('testPostText').value = data.postText || '';
document.getElementById('testProfileNumber').value = data.profileNumber || '1';
} catch (e) {
console.error('Failed to load last test comment:', e);
}
}
}
async function generateTest() {
const text = document.getElementById('testPostText').value;
const profileNumber = parseInt(document.getElementById('testProfileNumber').value);
if (!text) return;
document.getElementById('testLoading').style.display = 'block';
document.getElementById('testResult').style.display = 'none';
document.getElementById('testError').style.display = 'none';
try {
const res = await apiFetch(`${API_URL}/ai/generate-comment`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({postText: text, profileNumber})
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Failed');
}
const data = await res.json();
document.getElementById('testComment').textContent = data.comment;
document.getElementById('testResult').style.display = 'block';
// Save test data to localStorage
localStorage.setItem('lastTestComment', JSON.stringify({
postText: text,
profileNumber: profileNumber,
comment: data.comment,
timestamp: new Date().toISOString()
}));
} catch (err) {
document.getElementById('testError').textContent = err.message;
document.getElementById('testError').style.display = 'block';
} finally {
document.getElementById('testLoading').style.display = 'none';
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async function saveAllSettings(event) {
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
const saveBtn = document.getElementById('saveAllFloatingBtn');
const labelEl = saveBtn ? saveBtn.querySelector('.label') : null;
const spinnerEl = saveBtn ? saveBtn.querySelector('.spinner') : null;
const defaultLabel = saveBtn
? (saveBtn.dataset.defaultLabel || (labelEl && labelEl.textContent.trim()) || 'Einstellungen speichern')
: 'Einstellungen speichern';
if (saveBtn) {
saveBtn.dataset.defaultLabel = defaultLabel;
}
if (saveBtn) {
saveBtn.disabled = true;
saveBtn.classList.add('is-saving');
if (labelEl) {
labelEl.textContent = 'Speichern...';
} else {
saveBtn.textContent = 'Speichern...';
}
if (spinnerEl) {
spinnerEl.style.display = 'inline-block';
spinnerEl.classList.add('spin');
}
}
const results = await Promise.all([
saveSettings(null, { silent: true }),
saveHiddenSettings(null, { silent: true }),
saveAllFriends({ silent: true })
]);
const allOk = results.every(Boolean);
if (allOk) {
showSuccess('Gespeichert');
} else {
showError('❌ Nicht alle Einstellungen konnten gespeichert werden');
}
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.classList.remove('is-saving');
const label = saveBtn.querySelector('.label');
const spinner = saveBtn.querySelector('.spinner');
if (label) {
label.textContent = saveBtn.dataset.defaultLabel || 'Einstellungen speichern';
} else {
saveBtn.textContent = 'Einstellungen speichern';
}
if (spinner) {
spinner.style.display = 'none';
spinner.classList.remove('spin');
}
}
}
// ============================================================================
// PROFILE FRIENDS
// ============================================================================
let profileFriends = {};
async function loadProfileFriends() {
const list = document.getElementById('profileFriendsList');
list.innerHTML = '';
for (let i = 1; i <= 5; i++) {
const res = await apiFetch(`${API_URL}/profile-friends/${i}`);
const data = await res.json();
profileFriends[i] = data.friend_names || '';
const div = document.createElement('div');
div.className = 'form-group';
div.innerHTML = `
Kommagetrennte Liste von Freundesnamen für Profil ${i}
`;
list.appendChild(div);
document.getElementById(`friends${i}`).addEventListener('blur', async (e) => {
const newValue = e.target.value.trim();
if (newValue !== profileFriends[i]) {
await saveFriends(i, newValue);
}
});
}
}
async function saveFriends(profileNumber, friendNames, { silent = false } = {}) {
try {
const res = await apiFetch(`${API_URL}/profile-friends/${profileNumber}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ friend_names: friendNames })
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Fehler beim Speichern');
}
profileFriends[profileNumber] = friendNames;
if (!silent) {
showSuccess(`✅ Freunde für Profil ${profileNumber} gespeichert`);
}
return true;
} catch (err) {
if (!silent) {
showError('❌ ' + err.message);
}
return false;
}
}
async function saveAllFriends({ silent = false } = {}) {
let success = true;
for (let i = 1; i <= 5; i++) {
const input = document.getElementById(`friends${i}`);
if (!input) {
continue;
}
const newValue = input.value.trim();
if (newValue !== profileFriends[i]) {
const result = await saveFriends(i, newValue, { silent });
success = success && result;
}
}
return success;
}
// Event listeners
document.getElementById('addCredentialBtn').addEventListener('click', () => openCredentialModal());
document.getElementById('credentialModalClose').addEventListener('click', closeCredentialModal);
document.getElementById('credentialCancelBtn').addEventListener('click', closeCredentialModal);
document.getElementById('credentialForm').addEventListener('submit', saveCredential);
document.getElementById('credentialProvider').addEventListener('change', e => updateModelOptions(e.target.value));
document.getElementById('aiSettingsForm').addEventListener('submit', (e) => {
e.preventDefault();
saveSettings(e);
});
document.getElementById('testBtn').addEventListener('click', testComment);
document.getElementById('testModalClose').addEventListener('click', () => document.getElementById('testModal').setAttribute('hidden', ''));
document.getElementById('generateTestComment').addEventListener('click', generateTest);
document.getElementById('purgeHiddenNowBtn').addEventListener('click', purgeHiddenNow);
document.getElementById('saveAllFloatingBtn').addEventListener('click', saveAllSettings);
const autoPurgeHiddenToggle = document.getElementById('autoPurgeHiddenToggle');
if (autoPurgeHiddenToggle) {
autoPurgeHiddenToggle.addEventListener('change', () => {
const retentionInput = document.getElementById('hiddenRetentionDays');
if (retentionInput) {
retentionInput.disabled = !autoPurgeHiddenToggle.checked;
}
});
}
// Initialize
Promise.all([loadCredentials(), loadSettings(), loadHiddenSettings(), loadProfileFriends()]).catch(err => showError(err.message));
})();