Add per-profile AI comment action limits
This commit is contained in:
@@ -11,6 +11,8 @@ const app = express();
|
|||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
const MAX_PROFILES = 5;
|
const MAX_PROFILES = 5;
|
||||||
|
const AI_AUTO_COMMENT_SOURCE = 'extension-ai-button';
|
||||||
|
const AI_AUTO_COMMENT_DAILY_LIMIT_MAX = 500;
|
||||||
const DEFAULT_PROFILE_NAMES = {
|
const DEFAULT_PROFILE_NAMES = {
|
||||||
1: 'Profil 1',
|
1: 'Profil 1',
|
||||||
2: 'Profil 2',
|
2: 'Profil 2',
|
||||||
@@ -809,6 +811,167 @@ function getProfileName(profileNumber) {
|
|||||||
return DEFAULT_PROFILE_NAMES[profileNumber] || `Profil ${profileNumber}`;
|
return DEFAULT_PROFILE_NAMES[profileNumber] || `Profil ${profileNumber}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeAIAutoCommentDailyLimit(value) {
|
||||||
|
if (value === null || typeof value === 'undefined' || value === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const parsed = parseInt(value, 10);
|
||||||
|
if (Number.isNaN(parsed) || parsed < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.min(AI_AUTO_COMMENT_DAILY_LIMIT_MAX, parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalDateKey(date = new Date()) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextLocalMidnightIso(date = new Date()) {
|
||||||
|
const nextMidnight = new Date(date);
|
||||||
|
nextMidnight.setHours(24, 0, 0, 0);
|
||||||
|
return nextMidnight.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAIAutoCommentLimitPayload(profileNumber, dailyLimit, usedToday, date = new Date()) {
|
||||||
|
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||||
|
if (!normalizedProfileNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalizedDailyLimit = normalizeAIAutoCommentDailyLimit(dailyLimit);
|
||||||
|
const normalizedUsedToday = Math.max(0, parseInt(usedToday, 10) || 0);
|
||||||
|
return {
|
||||||
|
profile_number: normalizedProfileNumber,
|
||||||
|
profile_name: getProfileName(normalizedProfileNumber),
|
||||||
|
daily_limit: normalizedDailyLimit,
|
||||||
|
used_today: normalizedUsedToday,
|
||||||
|
remaining_today: normalizedDailyLimit > 0
|
||||||
|
? Math.max(0, normalizedDailyLimit - normalizedUsedToday)
|
||||||
|
: null,
|
||||||
|
blocked: normalizedDailyLimit > 0 && normalizedUsedToday >= normalizedDailyLimit,
|
||||||
|
resets_at: getNextLocalMidnightIso(date)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function listAIAutoCommentProfileLimits() {
|
||||||
|
const todayKey = getLocalDateKey();
|
||||||
|
const limitRows = db.prepare(`
|
||||||
|
SELECT profile_number, daily_limit
|
||||||
|
FROM ai_profile_auto_comment_limits
|
||||||
|
`).all();
|
||||||
|
const usageRows = db.prepare(`
|
||||||
|
SELECT profile_number, used_count
|
||||||
|
FROM ai_profile_auto_comment_usage
|
||||||
|
WHERE usage_date = ?
|
||||||
|
`).all(todayKey);
|
||||||
|
|
||||||
|
const dailyLimitByProfile = new Map();
|
||||||
|
limitRows.forEach((row) => {
|
||||||
|
const profileNumber = sanitizeProfileNumber(row.profile_number);
|
||||||
|
if (profileNumber) {
|
||||||
|
dailyLimitByProfile.set(profileNumber, normalizeAIAutoCommentDailyLimit(row.daily_limit));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const usageByProfile = new Map();
|
||||||
|
usageRows.forEach((row) => {
|
||||||
|
const profileNumber = sanitizeProfileNumber(row.profile_number);
|
||||||
|
if (profileNumber) {
|
||||||
|
usageByProfile.set(profileNumber, Math.max(0, parseInt(row.used_count, 10) || 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from({ length: MAX_PROFILES }, (_unused, index) => {
|
||||||
|
const profileNumber = index + 1;
|
||||||
|
return buildAIAutoCommentLimitPayload(
|
||||||
|
profileNumber,
|
||||||
|
dailyLimitByProfile.get(profileNumber) || 0,
|
||||||
|
usageByProfile.get(profileNumber) || 0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAIAutoCommentProfileLimit(profileNumber) {
|
||||||
|
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||||
|
if (!normalizedProfileNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const todayKey = getLocalDateKey();
|
||||||
|
const limitRow = db.prepare(`
|
||||||
|
SELECT daily_limit
|
||||||
|
FROM ai_profile_auto_comment_limits
|
||||||
|
WHERE profile_number = ?
|
||||||
|
`).get(normalizedProfileNumber);
|
||||||
|
const usageRow = db.prepare(`
|
||||||
|
SELECT used_count
|
||||||
|
FROM ai_profile_auto_comment_usage
|
||||||
|
WHERE profile_number = ?
|
||||||
|
AND usage_date = ?
|
||||||
|
`).get(normalizedProfileNumber, todayKey);
|
||||||
|
|
||||||
|
return buildAIAutoCommentLimitPayload(
|
||||||
|
normalizedProfileNumber,
|
||||||
|
limitRow ? limitRow.daily_limit : 0,
|
||||||
|
usageRow ? usageRow.used_count : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAIAutoCommentProfileLimits(profileLimits) {
|
||||||
|
const rows = Array.isArray(profileLimits) ? profileLimits : [];
|
||||||
|
const normalizedByProfile = new Map();
|
||||||
|
|
||||||
|
rows.forEach((entry) => {
|
||||||
|
const profileNumber = sanitizeProfileNumber(entry && entry.profile_number);
|
||||||
|
if (!profileNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
normalizedByProfile.set(profileNumber, normalizeAIAutoCommentDailyLimit(entry.daily_limit));
|
||||||
|
});
|
||||||
|
|
||||||
|
const upsertLimit = db.prepare(`
|
||||||
|
INSERT INTO ai_profile_auto_comment_limits (profile_number, daily_limit, updated_at)
|
||||||
|
VALUES (@profile_number, @daily_limit, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(profile_number) DO UPDATE SET
|
||||||
|
daily_limit = excluded.daily_limit,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
`);
|
||||||
|
|
||||||
|
const persist = db.transaction(() => {
|
||||||
|
for (let profileNumber = 1; profileNumber <= MAX_PROFILES; profileNumber += 1) {
|
||||||
|
upsertLimit.run({
|
||||||
|
profile_number: profileNumber,
|
||||||
|
daily_limit: normalizedByProfile.has(profileNumber)
|
||||||
|
? normalizedByProfile.get(profileNumber)
|
||||||
|
: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
persist();
|
||||||
|
return listAIAutoCommentProfileLimits();
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementAIAutoCommentProfileUsage(profileNumber) {
|
||||||
|
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||||
|
if (!normalizedProfileNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const todayKey = getLocalDateKey();
|
||||||
|
db.prepare(`
|
||||||
|
INSERT INTO ai_profile_auto_comment_usage (profile_number, usage_date, used_count, updated_at)
|
||||||
|
VALUES (?, ?, 1, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(profile_number, usage_date) DO UPDATE SET
|
||||||
|
used_count = ai_profile_auto_comment_usage.used_count + 1,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
`).run(normalizedProfileNumber, todayKey);
|
||||||
|
|
||||||
|
return getAIAutoCommentProfileLimit(normalizedProfileNumber);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeCreatorName(value) {
|
function normalizeCreatorName(value) {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
@@ -1580,6 +1743,24 @@ db.exec(`
|
|||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS ai_profile_auto_comment_limits (
|
||||||
|
profile_number INTEGER PRIMARY KEY,
|
||||||
|
daily_limit INTEGER NOT NULL DEFAULT 0,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS ai_profile_auto_comment_usage (
|
||||||
|
profile_number INTEGER NOT NULL,
|
||||||
|
usage_date TEXT NOT NULL,
|
||||||
|
used_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (profile_number, usage_date)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS search_seen_posts (
|
CREATE TABLE IF NOT EXISTS search_seen_posts (
|
||||||
url TEXT PRIMARY KEY,
|
url TEXT PRIMARY KEY,
|
||||||
@@ -2049,6 +2230,8 @@ ensureColumn('posts', 'is_successful', 'is_successful INTEGER DEFAULT 0');
|
|||||||
ensureColumn('ai_settings', 'active_credential_id', 'active_credential_id INTEGER');
|
ensureColumn('ai_settings', 'active_credential_id', 'active_credential_id INTEGER');
|
||||||
ensureColumn('ai_settings', 'prompt_prefix', 'prompt_prefix TEXT');
|
ensureColumn('ai_settings', 'prompt_prefix', 'prompt_prefix TEXT');
|
||||||
ensureColumn('ai_settings', 'enabled', 'enabled INTEGER DEFAULT 0');
|
ensureColumn('ai_settings', 'enabled', 'enabled INTEGER DEFAULT 0');
|
||||||
|
ensureColumn('ai_profile_auto_comment_limits', 'daily_limit', 'daily_limit INTEGER NOT NULL DEFAULT 0');
|
||||||
|
ensureColumn('ai_profile_auto_comment_usage', 'used_count', 'used_count INTEGER NOT NULL DEFAULT 0');
|
||||||
ensureColumn('ai_credentials', 'is_active', 'is_active INTEGER DEFAULT 1');
|
ensureColumn('ai_credentials', 'is_active', 'is_active INTEGER DEFAULT 1');
|
||||||
ensureColumn('ai_credentials', 'priority', 'priority INTEGER DEFAULT 0');
|
ensureColumn('ai_credentials', 'priority', 'priority INTEGER DEFAULT 0');
|
||||||
ensureColumn('ai_credentials', 'base_url', 'base_url TEXT');
|
ensureColumn('ai_credentials', 'base_url', 'base_url TEXT');
|
||||||
@@ -6676,7 +6859,8 @@ app.get('/api/ai-settings', (req, res) => {
|
|||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...settings,
|
...settings,
|
||||||
active_credential: activeCredential
|
active_credential: activeCredential,
|
||||||
|
profile_limits: listAIAutoCommentProfileLimits()
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
@@ -6685,7 +6869,7 @@ app.get('/api/ai-settings', (req, res) => {
|
|||||||
|
|
||||||
app.put('/api/ai-settings', (req, res) => {
|
app.put('/api/ai-settings', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { active_credential_id, prompt_prefix, enabled } = req.body;
|
const { active_credential_id, prompt_prefix, enabled, profile_limits } = req.body;
|
||||||
|
|
||||||
const existing = db.prepare('SELECT * FROM ai_settings WHERE id = 1').get();
|
const existing = db.prepare('SELECT * FROM ai_settings WHERE id = 1').get();
|
||||||
|
|
||||||
@@ -6702,6 +6886,10 @@ app.put('/api/ai-settings', (req, res) => {
|
|||||||
`).run(active_credential_id || null, prompt_prefix, enabled ? 1 : 0);
|
`).run(active_credential_id || null, prompt_prefix, enabled ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const savedProfileLimits = Array.isArray(profile_limits)
|
||||||
|
? saveAIAutoCommentProfileLimits(profile_limits)
|
||||||
|
: listAIAutoCommentProfileLimits();
|
||||||
|
|
||||||
const updated = db.prepare('SELECT * FROM ai_settings WHERE id = 1').get();
|
const updated = db.prepare('SELECT * FROM ai_settings WHERE id = 1').get();
|
||||||
|
|
||||||
let activeCredential = null;
|
let activeCredential = null;
|
||||||
@@ -6711,7 +6899,8 @@ app.put('/api/ai-settings', (req, res) => {
|
|||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...updated,
|
...updated,
|
||||||
active_credential: activeCredential
|
active_credential: activeCredential,
|
||||||
|
profile_limits: savedProfileLimits
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
@@ -7317,6 +7506,8 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { postText, profileNumber, preferredCredentialId } = requestBody;
|
const { postText, profileNumber, preferredCredentialId } = requestBody;
|
||||||
|
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||||
|
let profileLimitInfo = null;
|
||||||
|
|
||||||
if (!postText) {
|
if (!postText) {
|
||||||
return respondWithTrackedError(400, 'postText is required');
|
return respondWithTrackedError(400, 'postText is required');
|
||||||
@@ -7348,6 +7539,29 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
|||||||
return respondWithTrackedError(400, 'No active AI credentials available');
|
return respondWithTrackedError(400, 'No active AI credentials available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (traceSource === AI_AUTO_COMMENT_SOURCE) {
|
||||||
|
if (!normalizedProfileNumber) {
|
||||||
|
return respondWithTrackedError(400, 'Für die AI-Kommentar-Aktion ist ein gültiges Profil erforderlich');
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitCheckStartedMs = timingStart();
|
||||||
|
const currentProfileLimit = getAIAutoCommentProfileLimit(normalizedProfileNumber);
|
||||||
|
if (currentProfileLimit && currentProfileLimit.blocked) {
|
||||||
|
timingEnd('profileLimitCheckMs', limitCheckStartedMs);
|
||||||
|
return respondWithTrackedError(
|
||||||
|
429,
|
||||||
|
`Tageslimit für ${currentProfileLimit.profile_name} erreicht (${currentProfileLimit.used_today}/${currentProfileLimit.daily_limit}). Die Aktion ist bis morgen gesperrt.`,
|
||||||
|
{
|
||||||
|
responseMeta: {
|
||||||
|
profileLimit: currentProfileLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
profileLimitInfo = incrementAIAutoCommentProfileUsage(normalizedProfileNumber);
|
||||||
|
timingEnd('profileLimitCheckMs', limitCheckStartedMs);
|
||||||
|
}
|
||||||
|
|
||||||
let orderedCredentials = credentials;
|
let orderedCredentials = credentials;
|
||||||
if (typeof preferredCredentialId !== 'undefined' && preferredCredentialId !== null) {
|
if (typeof preferredCredentialId !== 'undefined' && preferredCredentialId !== null) {
|
||||||
const parsedPreferredId = Number(preferredCredentialId);
|
const parsedPreferredId = Number(preferredCredentialId);
|
||||||
@@ -7362,7 +7576,6 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
|||||||
|
|
||||||
const promptBuildStartedMs = timingStart();
|
const promptBuildStartedMs = timingStart();
|
||||||
let promptPrefix = settings.prompt_prefix || '';
|
let promptPrefix = settings.prompt_prefix || '';
|
||||||
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
|
||||||
|
|
||||||
if (normalizedProfileNumber) {
|
if (normalizedProfileNumber) {
|
||||||
const friends = db.prepare('SELECT friend_names FROM profile_friends WHERE profile_number = ?').get(normalizedProfileNumber);
|
const friends = db.prepare('SELECT friend_names FROM profile_friends WHERE profile_number = ?').get(normalizedProfileNumber);
|
||||||
@@ -7433,7 +7646,8 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
|||||||
responseMeta: {
|
responseMeta: {
|
||||||
usedCredential: credential.name,
|
usedCredential: credential.name,
|
||||||
usedCredentialId: credential.id,
|
usedCredentialId: credential.id,
|
||||||
attempts: attemptDetails
|
attempts: attemptDetails,
|
||||||
|
profileLimit: profileLimitInfo
|
||||||
},
|
},
|
||||||
totalDurationMs: backendTimings.totalMs
|
totalDurationMs: backendTimings.totalMs
|
||||||
});
|
});
|
||||||
@@ -7447,6 +7661,7 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
|||||||
usedCredentialId: credential.id,
|
usedCredentialId: credential.id,
|
||||||
attempts: attemptDetails,
|
attempts: attemptDetails,
|
||||||
rateLimitInfo: rateInfo || null,
|
rateLimitInfo: rateInfo || null,
|
||||||
|
profileLimitInfo,
|
||||||
traceId,
|
traceId,
|
||||||
flowId,
|
flowId,
|
||||||
timings: {
|
timings: {
|
||||||
|
|||||||
@@ -1145,6 +1145,14 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<div class="form-actions">
|
||||||
<button type="button" class="btn btn-secondary" id="testBtn">
|
<button type="button" class="btn btn-secondary" id="testBtn">
|
||||||
🧪 Kommentar testen
|
🧪 Kommentar testen
|
||||||
|
|||||||
103
web/settings.js
103
web/settings.js
@@ -41,6 +41,7 @@ const PROVIDER_INFO = {
|
|||||||
apiKeyHelp: 'Für lokale OpenAI-kompatible Server (z.B. Ollama) kannst du den Schlüssel leer lassen.'
|
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 credentials = [];
|
||||||
let currentSettings = null;
|
let currentSettings = null;
|
||||||
@@ -173,6 +174,7 @@ async function loadSettings() {
|
|||||||
document.getElementById('activeCredential').value = currentSettings.active_credential_id || '';
|
document.getElementById('activeCredential').value = currentSettings.active_credential_id || '';
|
||||||
document.getElementById('aiPromptPrefix').value = currentSettings.prompt_prefix ||
|
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';
|
'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() {
|
async function loadHiddenSettings() {
|
||||||
@@ -425,6 +427,94 @@ function normalizeSimilarityImageThresholdInput(value) {
|
|||||||
return Math.min(64, Math.max(0, parsed));
|
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() {
|
function applySimilaritySettingsUI() {
|
||||||
const textInput = document.getElementById('similarityTextThreshold');
|
const textInput = document.getElementById('similarityTextThreshold');
|
||||||
const imageInput = document.getElementById('similarityImageThreshold');
|
const imageInput = document.getElementById('similarityImageThreshold');
|
||||||
@@ -998,7 +1088,15 @@ async function saveSettings(e, { silent = false } = {}) {
|
|||||||
const data = {
|
const data = {
|
||||||
enabled: document.getElementById('aiEnabled').checked,
|
enabled: document.getElementById('aiEnabled').checked,
|
||||||
active_credential_id: parseInt(document.getElementById('activeCredential').value) || null,
|
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`, {
|
const res = await apiFetch(`${API_URL}/ai-settings`, {
|
||||||
@@ -1013,6 +1111,7 @@ async function saveSettings(e, { silent = false } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentSettings = await res.json();
|
currentSettings = await res.json();
|
||||||
|
renderAIProfileLimitInputs();
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
showSuccess('✅ Einstellungen erfolgreich gespeichert');
|
showSuccess('✅ Einstellungen erfolgreich gespeichert');
|
||||||
}
|
}
|
||||||
@@ -1058,7 +1157,7 @@ async function generateTest() {
|
|||||||
const res = await apiFetch(`${API_URL}/ai/generate-comment`, {
|
const res = await apiFetch(`${API_URL}/ai/generate-comment`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({postText: text, profileNumber})
|
body: JSON.stringify({postText: text, profileNumber, traceSource: 'settings-test'})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user