657 lines
21 KiB
JavaScript
657 lines
21 KiB
JavaScript
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;
|
|
|
|
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: 24px;
|
|
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;
|
|
`;
|
|
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';
|
|
}
|
|
|
|
function renderCredentials() {
|
|
const list = document.getElementById('credentialsList');
|
|
if (!credentials.length) {
|
|
list.innerHTML = '<p class="empty-state">Noch keine Anmeldedaten gespeichert</p>';
|
|
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)}` : '';
|
|
|
|
return `
|
|
<div class="credential-item" draggable="true" data-credential-id="${c.id}" data-index="${index}">
|
|
<div class="credential-item__drag-handle" title="Ziehen zum Sortieren">⋮⋮</div>
|
|
<div class="credential-item__info">
|
|
<label class="form-label" style="display: flex; align-items: center; gap: 8px; margin: 0;">
|
|
<input type="checkbox" class="form-checkbox"
|
|
${c.is_active ? 'checked' : ''}
|
|
onchange="toggleCredentialActive(${c.id}, this.checked)">
|
|
<div>
|
|
<div class="credential-item__name">${escapeHtml(c.name)}</div>
|
|
<div class="credential-item__provider">${providerName}${modelLabel}${endpointLabel}</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<div class="credential-item__actions">
|
|
<button onclick="editCredential(${c.id})" class="btn-icon" title="Bearbeiten">✏️</button>
|
|
<button onclick="deleteCredential(${c.id})" class="btn-icon" title="Löschen">🗑️</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Add drag and drop event listeners
|
|
setupDragAndDrop();
|
|
}
|
|
|
|
function updateActiveCredentialSelect() {
|
|
const select = document.getElementById('activeCredential');
|
|
select.innerHTML = '<option value="">-- Bitte wählen --</option>' +
|
|
credentials.map(c => `<option value="${c.id}">${escapeHtml(c.name)} (${PROVIDER_INFO[c.provider]?.name})</option>`).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 => `<option value="${m.value}">${m.label}</option>`).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(`<a href="${info.apiKeyLink}" target="_blank">API-Schlüssel erstellen</a>`);
|
|
}
|
|
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 function globally accessible
|
|
window.toggleCredentialActive = toggleCredentialActive;
|
|
|
|
// ============================================================================
|
|
// 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) {
|
|
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();
|
|
showSuccess('✅ Einstellungen erfolgreich gespeichert');
|
|
} catch (err) {
|
|
showError('❌ ' + err.message);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 = `
|
|
<label for="friends${i}" class="form-label">Profil ${i}</label>
|
|
<input type="text" id="friends${i}" class="form-input"
|
|
placeholder="z.B. Anna, Max, Lisa"
|
|
value="${escapeHtml(profileFriends[i])}">
|
|
<p class="form-help">Kommagetrennte Liste von Freundesnamen für Profil ${i}</p>
|
|
`;
|
|
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) {
|
|
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;
|
|
showSuccess(`✅ Freunde für Profil ${profileNumber} gespeichert`);
|
|
} catch (err) {
|
|
showError('❌ ' + err.message);
|
|
}
|
|
}
|
|
|
|
// 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('saveAllBtn').addEventListener('click', (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);
|
|
|
|
// Initialize
|
|
Promise.all([loadCredentials(), loadSettings(), loadProfileFriends()]).catch(err => showError(err.message));
|