mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-10-05 17:35:33 -03:00
feat(update): add a Buzz price alerts panel to the notification bell
P5a of docs/plans/paid-model-price-tracking.md: one surface that answers "what got cheaper / became free", without a permanent button (the grid filter was dropped by owner decision, so the panel carries the actions itself). - price_alert_since records when an alert started, so the panel can say "dropped 3 d ago" and count what is new since the user last looked; it is set on the first sight of an already-cheap version, preserved while the alert stands, and cleared when the price rises back above the threshold - get_price_alerts() compares the threshold at read time (editing it takes effect immediately, no refresh needed) and returns both kinds in one list; model_type=None covers every type, which the shared update DB makes a single query. "became free" needs no price data, so it is reported even while price tracking is off - GET /api/lm/price-alerts, registered once in MiscRoutes rather than per model type, decorating rows best-effort with the local model name and file path from the scanner indexes (a cold cache just omits them) - a third tab in the notification bell: segments for under-threshold and became-free, the three states (tracking off / nothing matching / stale), and per-row actions (CivitAI always, Open when the model is local) - two non-permanent entry points share one helper: the controls-bar updates dropdown and the global context menu, whose label carries the unread count - unread state stays client-side (localStorage watermark); the count is fetched once on init and only when price tracking is enabled - the per-type frontend client method is removed as dead code; the per-type backend route stays for the companion extension
This commit is contained in:
@@ -4,6 +4,7 @@ import { translate } from '../../utils/i18nHelpers.js';
|
||||
import { state } from '../../state/index.js';
|
||||
import { getCompleteApiConfig, getCurrentModelType } from '../../api/apiConfig.js';
|
||||
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
|
||||
import { openPriceAlertsPanel, updateService } from '../../managers/UpdateService.js';
|
||||
import { rematchModalManager } from '../../managers/RematchModalManager.js';
|
||||
import { showRematchSummary } from '../RematchSummaryModal.js';
|
||||
|
||||
@@ -27,6 +28,7 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
const excludedModelsItem = this.menu.querySelector('[data-action="manage-excluded-models"]');
|
||||
const rematchRecipesItem = this.menu.querySelector('[data-action="rematch-recipes"]');
|
||||
const groupByModelItem = this.menu.querySelector('[data-action="toggle-group-by-model"]');
|
||||
const priceAlertsItem = this.menu.querySelector('[data-action="price-alerts"]');
|
||||
const groupByModelCheck = groupByModelItem?.querySelector('.check-indicator');
|
||||
|
||||
// Update check indicator for group-by-model
|
||||
@@ -35,6 +37,16 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
groupByModelCheck.style.display = isEnabled ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Append the unread count when it is already known (loaded once on init,
|
||||
// and only when price tracking is on), so the label stays a plain label
|
||||
// for everyone else.
|
||||
const priceAlertsLabel = priceAlertsItem?.querySelector('#globalPriceAlertsLabel');
|
||||
if (priceAlertsLabel) {
|
||||
const unread = updateService?.getUnreadPriceAlertCount?.() || 0;
|
||||
const base = translate('globalContextMenu.priceAlerts.label', {}, 'Price alerts');
|
||||
priceAlertsLabel.textContent = unread > 0 ? `${base} (${unread})` : base;
|
||||
}
|
||||
|
||||
if (isRecipesPage) {
|
||||
modelUpdateItem?.classList.add('hidden');
|
||||
licenseRefreshItem?.classList.add('hidden');
|
||||
@@ -42,6 +54,7 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
cleanupExamplesItem?.classList.add('hidden');
|
||||
excludedModelsItem?.classList.add('hidden');
|
||||
groupByModelItem?.classList.add('hidden');
|
||||
priceAlertsItem?.classList.add('hidden');
|
||||
rematchRecipesItem?.classList.remove('hidden');
|
||||
} else {
|
||||
modelUpdateItem?.classList.remove('hidden');
|
||||
@@ -50,6 +63,7 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
cleanupExamplesItem?.classList.remove('hidden');
|
||||
excludedModelsItem?.classList.remove('hidden');
|
||||
groupByModelItem?.classList.remove('hidden');
|
||||
priceAlertsItem?.classList.remove('hidden');
|
||||
rematchRecipesItem?.classList.add('hidden');
|
||||
}
|
||||
|
||||
@@ -89,6 +103,9 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
console.error('Failed to check model updates:', error);
|
||||
});
|
||||
break;
|
||||
case 'price-alerts':
|
||||
openPriceAlertsPanel();
|
||||
break;
|
||||
case 'fetch-missing-licenses':
|
||||
this.fetchMissingLicenses(menuItem).catch((error) => {
|
||||
console.error('Failed to refresh missing license metadata:', error);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getStorageItem, setStorageItem, removeStorageItem, getSessionItem, setS
|
||||
import { showToast, openCivitaiByMetadata, isTypingContext } from '../../utils/uiHelpers.js';
|
||||
import { eventManager } from '../../utils/EventManager.js';
|
||||
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
|
||||
import { openPriceAlertsPanel } from '../../managers/UpdateService.js';
|
||||
import { sidebarManager } from '../SidebarManager.js';
|
||||
import { initSortDropdown, applySortToSelect, randomizeSortValue } from './SortDropdown.js';
|
||||
|
||||
@@ -256,6 +257,17 @@ export class PageControls {
|
||||
});
|
||||
}
|
||||
|
||||
// Opens the notification bell on the price alerts tab (same action as the
|
||||
// global context menu entry).
|
||||
const priceAlertsOption = document.getElementById('priceAlertsMenuItem');
|
||||
if (priceAlertsOption) {
|
||||
priceAlertsOption.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
document.querySelector('.dropdown-group.active')?.classList.remove('active');
|
||||
openPriceAlertsPanel();
|
||||
});
|
||||
}
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.dropdown-group')) {
|
||||
|
||||
Reference in New Issue
Block a user