Files
ComfyUI-Lora-Manager/templates/components/modals/update_modal.html
T
Will Miao 7ed19c185c 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
2026-10-04 15:08:48 +08:00

132 lines
8.1 KiB
HTML

<!-- Update Modal -->
<div id="updateModal" class="modal">
<div class="modal-content update-modal">
<button class="close" onclick="modalManager.closeModal('updateModal')">&times;</button>
<div class="update-header">
<i class="fas fa-bell update-icon"></i>
<h2 data-i18n="update.notificationsTitle">{{ t('update.notificationsTitle') }}</h2>
</div>
<div class="notification-tabs" role="tablist">
<button type="button" class="notification-tab active" id="updatesTab" role="tab" aria-selected="true" aria-controls="updatesPanel" tabindex="0" data-notification-tab="updates">
<span>{{ t('update.tabs.updates') }}</span>
<span class="notification-tab-badge is-dot" id="updatesTabBadge"></span>
</button>
<button type="button" class="notification-tab" id="bannersTab" role="tab" aria-selected="false" aria-controls="bannersPanel" tabindex="-1" data-notification-tab="banners">
<span>{{ t('update.tabs.messages') }}</span>
<span class="notification-tab-badge" id="bannerTabBadge"></span>
</button>
<button type="button" class="notification-tab" id="priceAlertsTab" role="tab" aria-selected="false" aria-controls="priceAlertsPanel" tabindex="-1" data-notification-tab="priceAlerts">
<span>{{ t('update.tabs.priceAlerts') }}</span>
<span class="notification-tab-badge" id="priceAlertsTabBadge"></span>
</button>
</div>
<div class="notification-panels">
<div class="notification-panel active" id="updatesPanel" role="tabpanel" aria-labelledby="updatesTab" aria-hidden="false" tabindex="0" data-notification-panel="updates">
<div class="update-content">
<!-- Channel Selector -->
<div class="update-channels" id="updateChannels">
<div class="channels-label">{{ t('update.channel') }}</div>
<div class="channel-toggle">
<button type="button" class="channel-btn" data-channel="release" id="channelRelease">
<i class="fas fa-tag"></i> {{ t('update.channels.release') }}
</button>
<button type="button" class="channel-btn" data-channel="nightly" id="channelNightly">
<i class="fas fa-moon"></i> {{ t('update.channels.nightly') }}
</button>
</div>
</div>
<div class="update-info">
<div class="version-info">
<div class="current-version">
<span class="label">{{ t('update.currentVersion') }}:</span>
<span class="version-number">v0.0.0</span>
</div>
<div class="git-info" style="display:none;">{{ t('update.commit') }}: unknown</div>
<div class="new-version">
<span class="label">{{ t('update.newVersion') }}:</span>
<span class="version-number">v0.0.0</span>
</div>
</div>
<div class="update-actions">
<a href="https://github.com/willmiao/ComfyUI-Lora-Manager" target="_blank" class="update-link">
<i class="fas fa-external-link-alt"></i> {{ t('update.viewOnGitHub') }}
</a>
<button id="updateBtn" class="primary-btn disabled">
<i class="fas fa-download"></i>
<span id="updateBtnText" data-i18n="update.updateNow">{{ t('update.updateNow') }}</span>
</button>
</div>
</div>
<!-- Update Progress Section -->
<div class="update-progress" id="updateProgress" style="display: none;">
<div class="progress-info">
<div class="progress-text" id="updateProgressText" data-i18n="update.preparingUpdate">{{ t('update.preparingUpdate') }}</div>
<div class="update-progress-bar">
<div class="progress-fill" id="updateProgressFill"></div>
</div>
</div>
</div>
<div class="changelog-section">
<h3>{{ t('update.changelog') }}</h3>
<div class="changelog-content">
<!-- Dynamic changelog content will be inserted here -->
<div class="changelog-item">
<h4>{{ t('update.checkingUpdates') }}</h4>
<p>{{ t('update.checkingMessage') }}</p>
</div>
</div>
</div>
<div class="update-preferences">
<label class="toggle-switch">
<input type="checkbox" id="updateNotifications" checked>
<span class="toggle-slider"></span>
<span class="toggle-label">{{ t('update.showNotifications') }}</span>
</label>
</div>
</div>
</div>
<div class="notification-panel" id="bannersPanel" role="tabpanel" aria-labelledby="bannersTab" aria-hidden="true" tabindex="-1" data-notification-panel="banners">
<div class="banner-history">
<h3>{{ t('update.banners.recent') }}</h3>
<p class="banner-history-empty" id="bannerHistoryEmpty" data-i18n="update.banners.empty">{{ t('update.banners.empty') }}</p>
<ul class="banner-history-list" id="bannerHistoryList"></ul>
</div>
</div>
<div class="notification-panel" id="priceAlertsPanel" role="tabpanel" aria-labelledby="priceAlertsTab" aria-hidden="true" tabindex="-1" data-notification-panel="priceAlerts">
<div class="price-alerts">
<!-- Shown while price tracking is off: the panel still lists
versions that became free, but says why prices are missing. -->
<div class="price-alerts-disabled hidden" id="priceAlertsDisabled">
<p class="price-alerts-disabled-text">{{ t('update.priceAlerts.disabled') }}</p>
<button type="button" class="price-alerts-settings-btn" id="priceAlertsSettingsBtn">
<i class="fas fa-sliders-h"></i> {{ t('update.priceAlerts.openSettings') }}
</button>
</div>
<div class="price-alerts-toolbar">
<div class="price-alerts-segments" role="tablist" aria-label="{{ t('update.priceAlerts.segmentsLabel') }}">
<button type="button" class="price-alerts-segment active" data-price-alert-segment="below_threshold" role="tab" aria-selected="true">
{{ t('update.priceAlerts.segments.belowThreshold') }}
</button>
<button type="button" class="price-alerts-segment" data-price-alert-segment="became_free" role="tab" aria-selected="false">
{{ t('update.priceAlerts.segments.becameFree') }}
</button>
</div>
<span class="price-alerts-threshold" id="priceAlertsThreshold"></span>
</div>
<p class="price-alerts-note hidden" id="priceAlertsStale"></p>
<p class="price-alerts-empty" id="priceAlertsEmpty">{{ t('update.priceAlerts.empty') }}</p>
<ul class="price-alerts-list" id="priceAlertsList"></ul>
</div>
</div>
</div>
</div>
</div>