Add per-profile AI comment action limits

This commit is contained in:
2026-04-07 15:34:18 +02:00
parent 81dfb06f24
commit 66221f27c7
3 changed files with 329 additions and 7 deletions

View File

@@ -1145,6 +1145,14 @@
</p>
</div>
<div class="form-group">
<label class="form-label">Tageslimit pro Profil für den AI-Kommentar-Button</label>
<p class="form-help">
Gilt nur für die Aktion <code>AI - generiere automatisch einen passenden Kommentar</code> im Tracker. <code>0</code> bedeutet kein Limit.
</p>
<div id="aiProfileCommentLimits"></div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" id="testBtn">
🧪 Kommentar testen

View File

@@ -41,6 +41,7 @@ const PROVIDER_INFO = {
apiKeyHelp: 'Für lokale OpenAI-kompatible Server (z.B. Ollama) kannst du den Schlüssel leer lassen.'
}
};
const AI_AUTO_COMMENT_DAILY_LIMIT_MAX = 500;
let credentials = [];
let currentSettings = null;
@@ -173,6 +174,7 @@ async function loadSettings() {
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';
renderAIProfileLimitInputs();
}
async function loadHiddenSettings() {
@@ -425,6 +427,94 @@ function normalizeSimilarityImageThresholdInput(value) {
return Math.min(64, Math.max(0, parsed));
}
function normalizeAIProfileDailyLimitInput(value) {
const parsed = parseInt(value, 10);
if (Number.isNaN(parsed) || parsed < 0) {
return 0;
}
return Math.min(AI_AUTO_COMMENT_DAILY_LIMIT_MAX, parsed);
}
function getDefaultAIProfileLimitEntry(profileNumber) {
return {
profile_number: profileNumber,
profile_name: `Profil ${profileNumber}`,
daily_limit: 0,
used_today: 0,
remaining_today: null
};
}
function getAIProfileLimitEntries() {
const rows = Array.isArray(currentSettings?.profile_limits)
? currentSettings.profile_limits
: [];
const byProfile = new Map();
rows.forEach((row) => {
const profileNumber = parseInt(row?.profile_number, 10);
if (!Number.isNaN(profileNumber) && profileNumber >= 1 && profileNumber <= 5) {
byProfile.set(profileNumber, {
...getDefaultAIProfileLimitEntry(profileNumber),
...row,
daily_limit: normalizeAIProfileDailyLimitInput(row?.daily_limit),
used_today: Math.max(0, parseInt(row?.used_today, 10) || 0)
});
}
});
return Array.from({ length: 5 }, (_unused, index) => {
const profileNumber = index + 1;
return byProfile.get(profileNumber) || getDefaultAIProfileLimitEntry(profileNumber);
});
}
function renderAIProfileLimitInputs() {
const container = document.getElementById('aiProfileCommentLimits');
if (!container) {
return;
}
const entries = getAIProfileLimitEntries();
container.innerHTML = '';
entries.forEach((entry) => {
const wrapper = document.createElement('div');
wrapper.className = 'form-group';
wrapper.style.marginBottom = '12px';
const label = document.createElement('label');
label.className = 'form-label';
label.setAttribute('for', `aiProfileLimit${entry.profile_number}`);
label.textContent = entry.profile_name || `Profil ${entry.profile_number}`;
const input = document.createElement('input');
input.type = 'number';
input.min = '0';
input.max = String(AI_AUTO_COMMENT_DAILY_LIMIT_MAX);
input.step = '1';
input.id = `aiProfileLimit${entry.profile_number}`;
input.className = 'form-input';
input.value = String(normalizeAIProfileDailyLimitInput(entry.daily_limit));
input.placeholder = '0 = kein Limit';
input.addEventListener('blur', () => {
input.value = String(normalizeAIProfileDailyLimitInput(input.value));
});
const help = document.createElement('p');
help.className = 'form-help';
const dailyLimit = normalizeAIProfileDailyLimitInput(entry.daily_limit);
const usedToday = Math.max(0, parseInt(entry.used_today, 10) || 0);
help.textContent = dailyLimit > 0
? `Heute verwendet: ${usedToday} / ${dailyLimit}`
: `Heute verwendet: ${usedToday} · aktuell kein Limit aktiv`;
wrapper.appendChild(label);
wrapper.appendChild(input);
wrapper.appendChild(help);
container.appendChild(wrapper);
});
}
function applySimilaritySettingsUI() {
const textInput = document.getElementById('similarityTextThreshold');
const imageInput = document.getElementById('similarityImageThreshold');
@@ -998,7 +1088,15 @@ async function saveSettings(e, { silent = false } = {}) {
const data = {
enabled: document.getElementById('aiEnabled').checked,
active_credential_id: parseInt(document.getElementById('activeCredential').value) || null,
prompt_prefix: document.getElementById('aiPromptPrefix').value
prompt_prefix: document.getElementById('aiPromptPrefix').value,
profile_limits: Array.from({ length: 5 }, (_unused, index) => {
const profileNumber = index + 1;
const input = document.getElementById(`aiProfileLimit${profileNumber}`);
return {
profile_number: profileNumber,
daily_limit: normalizeAIProfileDailyLimitInput(input ? input.value : 0)
};
})
};
const res = await apiFetch(`${API_URL}/ai-settings`, {
@@ -1013,6 +1111,7 @@ async function saveSettings(e, { silent = false } = {}) {
}
currentSettings = await res.json();
renderAIProfileLimitInputs();
if (!silent) {
showSuccess('✅ Einstellungen erfolgreich gespeichert');
}
@@ -1058,7 +1157,7 @@ async function generateTest() {
const res = await apiFetch(`${API_URL}/ai/generate-comment`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({postText: text, profileNumber})
body: JSON.stringify({postText: text, profileNumber, traceSource: 'settings-test'})
});
if (!res.ok) {