Sync AI limit status across tabs
This commit is contained in:
@@ -1017,11 +1017,14 @@ const aiCredentialCache = {
|
|||||||
pending: null
|
pending: null
|
||||||
};
|
};
|
||||||
const AI_SETTINGS_CACHE_TTL = 30 * 1000;
|
const AI_SETTINGS_CACHE_TTL = 30 * 1000;
|
||||||
|
const AI_SETTINGS_SYNC_KEY = 'fb_tracker_ai_settings_sync';
|
||||||
const aiSettingsCache = {
|
const aiSettingsCache = {
|
||||||
data: null,
|
data: null,
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
pending: null
|
pending: null
|
||||||
};
|
};
|
||||||
|
const aiAvailabilitySubscribers = new Set();
|
||||||
|
let aiSettingsBroadcastChannel = null;
|
||||||
const MODERATION_SETTINGS_CACHE_TTL = 5 * 60 * 1000;
|
const MODERATION_SETTINGS_CACHE_TTL = 5 * 60 * 1000;
|
||||||
const moderationSettingsCache = {
|
const moderationSettingsCache = {
|
||||||
data: null,
|
data: null,
|
||||||
@@ -1141,6 +1144,97 @@ function backendFetch(url, options = {}) {
|
|||||||
return fetch(url, config);
|
return fetch(url, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyAIAvailabilitySubscribers() {
|
||||||
|
aiAvailabilitySubscribers.forEach((subscriber) => {
|
||||||
|
try {
|
||||||
|
subscriber(aiSettingsCache.data || null);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to notify AI availability subscriber:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAISettingsBroadcastChannel() {
|
||||||
|
if (aiSettingsBroadcastChannel || typeof BroadcastChannel !== 'function') {
|
||||||
|
return aiSettingsBroadcastChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
aiSettingsBroadcastChannel = new BroadcastChannel('fb-tracker-ai-settings');
|
||||||
|
aiSettingsBroadcastChannel.addEventListener('message', (event) => {
|
||||||
|
const data = event && event.data ? event.data : null;
|
||||||
|
if (!data || !data.settings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.timestamp && data.timestamp <= aiSettingsCache.timestamp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyAISettingsSnapshot(data.settings, { timestamp: data.timestamp || Date.now(), broadcast: false });
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to initialize AI settings BroadcastChannel:', error);
|
||||||
|
aiSettingsBroadcastChannel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aiSettingsBroadcastChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAISettingsSnapshot(settings, options = {}) {
|
||||||
|
if (!settings || typeof settings !== 'object') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { timestamp = Date.now(), broadcast = false } = options;
|
||||||
|
aiSettingsCache.data = settings;
|
||||||
|
aiSettingsCache.timestamp = timestamp;
|
||||||
|
notifyAIAvailabilitySubscribers();
|
||||||
|
|
||||||
|
if (!broadcast) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
timestamp,
|
||||||
|
settings
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem(AI_SETTINGS_SYNC_KEY, payload);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to sync AI settings via localStorage:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ensureAISettingsBroadcastChannel()?.postMessage({ timestamp, settings });
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to sync AI settings via BroadcastChannel:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('storage', (event) => {
|
||||||
|
if (!event || event.key !== AI_SETTINGS_SYNC_KEY || !event.newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(event.newValue);
|
||||||
|
if (!payload || !payload.settings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.timestamp && payload.timestamp <= aiSettingsCache.timestamp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyAISettingsSnapshot(payload.settings, {
|
||||||
|
timestamp: payload.timestamp || Date.now(),
|
||||||
|
broadcast: false
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to process synced AI settings:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ensureAISettingsBroadcastChannel();
|
||||||
|
|
||||||
async function fetchActiveAICredentials(forceRefresh = false) {
|
async function fetchActiveAICredentials(forceRefresh = false) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (!forceRefresh && aiCredentialCache.data && (now - aiCredentialCache.timestamp < AI_CREDENTIAL_CACHE_TTL)) {
|
if (!forceRefresh && aiCredentialCache.data && (now - aiCredentialCache.timestamp < AI_CREDENTIAL_CACHE_TTL)) {
|
||||||
@@ -1205,8 +1299,7 @@ async function fetchAISettings(forceRefresh = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const settings = await response.json();
|
const settings = await response.json();
|
||||||
aiSettingsCache.data = settings;
|
applyAISettingsSnapshot(settings, { timestamp: Date.now(), broadcast: forceRefresh });
|
||||||
aiSettingsCache.timestamp = Date.now();
|
|
||||||
return settings;
|
return settings;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -1226,6 +1319,26 @@ function getAIAutoCommentRateLimitStatusFromSettings(settings, profileNumber) {
|
|||||||
return statuses.find((entry) => parseInt(entry?.profile_number, 10) === normalizedProfile) || null;
|
return statuses.find((entry) => parseInt(entry?.profile_number, 10) === normalizedProfile) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upsertAIAutoCommentRateLimitStatus(settings, status) {
|
||||||
|
if (!status || typeof status !== 'object') {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = settings && typeof settings === 'object'
|
||||||
|
? settings
|
||||||
|
: { enabled: 1, rate_limit_statuses: [] };
|
||||||
|
const currentStatuses = Array.isArray(existing.rate_limit_statuses) ? existing.rate_limit_statuses : [];
|
||||||
|
const normalizedProfile = parseInt(status.profile_number, 10);
|
||||||
|
const nextStatuses = currentStatuses.filter((entry) => parseInt(entry?.profile_number, 10) !== normalizedProfile);
|
||||||
|
nextStatuses.push(status);
|
||||||
|
nextStatuses.sort((left, right) => (parseInt(left?.profile_number, 10) || 0) - (parseInt(right?.profile_number, 10) || 0));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...existing,
|
||||||
|
rate_limit_statuses: nextStatuses
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function formatAIAutoCommentRateLimitReason(status) {
|
function formatAIAutoCommentRateLimitReason(status) {
|
||||||
if (!status || !status.blocked_reason) {
|
if (!status || !status.blocked_reason) {
|
||||||
return 'Aktion aktuell gesperrt';
|
return 'Aktion aktuell gesperrt';
|
||||||
@@ -6807,6 +6920,27 @@ async function addAICommentButton(container, postElement) {
|
|||||||
return await rateLimitRefreshPromise;
|
return await rateLimitRefreshPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSharedAISettingsUpdate = async (settings) => {
|
||||||
|
if (!wrapper.isConnected) {
|
||||||
|
aiAvailabilitySubscribers.delete(handleSharedAISettingsUpdate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const profileNumber = await fetchBackendProfileNumber();
|
||||||
|
if (!profileNumber) {
|
||||||
|
applyAvailabilityState(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const status = getAIAutoCommentRateLimitStatusFromSettings(settings, profileNumber);
|
||||||
|
applyAvailabilityState(status);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to apply shared AI settings update:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
aiAvailabilitySubscribers.add(handleSharedAISettingsUpdate);
|
||||||
|
|
||||||
const updateNoteIndicator = () => {
|
const updateNoteIndicator = () => {
|
||||||
const note = getAdditionalNote();
|
const note = getAdditionalNote();
|
||||||
const hasNote = note.trim().length > 0;
|
const hasNote = note.trim().length > 0;
|
||||||
@@ -7577,9 +7711,11 @@ async function addAICommentButton(container, postElement) {
|
|||||||
});
|
});
|
||||||
mergeTraceInfo(aiResult);
|
mergeTraceInfo(aiResult);
|
||||||
if (aiResult.autoCommentRateLimitStatus) {
|
if (aiResult.autoCommentRateLimitStatus) {
|
||||||
|
const nextSettings = upsertAIAutoCommentRateLimitStatus(aiSettingsCache.data, aiResult.autoCommentRateLimitStatus);
|
||||||
|
applyAISettingsSnapshot(nextSettings, { timestamp: Date.now(), broadcast: true });
|
||||||
applyAvailabilityState(aiResult.autoCommentRateLimitStatus);
|
applyAvailabilityState(aiResult.autoCommentRateLimitStatus);
|
||||||
} else {
|
} else {
|
||||||
void refreshAvailabilityState(true);
|
void refreshAvailabilityState(true, profileNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
throwIfCancelled();
|
throwIfCancelled();
|
||||||
@@ -7694,7 +7830,11 @@ async function addAICommentButton(container, postElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error && error.status === 429) {
|
if (error && error.status === 429) {
|
||||||
await refreshAvailabilityState(true);
|
const syncedStatus = await refreshAvailabilityState(true);
|
||||||
|
if (syncedStatus) {
|
||||||
|
const nextSettings = upsertAIAutoCommentRateLimitStatus(aiSettingsCache.data, syncedStatus);
|
||||||
|
applyAISettingsSnapshot(nextSettings, { timestamp: Date.now(), broadcast: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = aiContext.cancelled || isCancellationError(error);
|
const cancelled = aiContext.cancelled || isCancellationError(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user