Files
Will Miao 86c0239b8c fix(init): honest error state and per-page completion for initialization
Two initialization-page defects made failures look like progress:

- A cache read exception while rendering a model/recipe page forced
  is_initializing=True, showing an endless spinner; worse, the polling
  fallback then reported the idle scanner as "complete", reloading the
  page straight back into the same exception. Page handlers now pass
  init_error to the initialization template, which renders the error
  state (with retry) server-side, and initialization.js stays on it
  instead of starting WebSocket/polling.

- The WebSocket path reloaded once the page's own scanner finished,
  while the polling fallback waited for every scanner. The polling
  fallback now mirrors the per-page semantics: /api/lm/init-status
  accepts ?page=<pageType> (aggregate behavior preserved without it),
  and ModelScanner records _init_error so a failed initial scan is
  reported as "error" instead of masquerading as "complete". Polling
  stops on error instead of reloading into the failure.
2026-10-25 09:02:57 +08:00

102 lines
5.5 KiB
HTML

<!-- Initialization Component -->
<div class="initialization-container" id="initializationContainer">
<div class="initialization-content">
<div class="initialization-header">
<h2 id="initTitle">{% block init_title %}{{ t('initialization.title') }}{% endblock %}</h2>
<p class="init-subtitle" id="initSubtitle">{% block init_message %}{{ t('initialization.message') }}{% endblock %}
</p>
</div>
<div class="loading-content"{% if init_error %} hidden{% endif %}>
<div class="loading-spinner"></div>
<div class="loading-status" id="progressStatus">{{ t('initialization.status') }}</div>
<!-- Use initialization-specific classes for the progress bar -->
<div class="init-progress-container">
<div class="init-progress-bar" id="initProgressBar"></div>
</div>
<div class="progress-details">
<span id="progressPercentage">0%</span>
<span id="remainingTime">{{ t('initialization.estimatingTime') }}</span>
</div>
</div>
<!-- Server-rendered when the page itself failed to load the cache:
stays visible and initialization.js skips polling for it. -->
<div class="init-error" id="initError"{% if not init_error %} hidden{% endif %}>
<i class="fas fa-exclamation-circle"></i>
<div class="init-error-content">
<h3>{{ t('initialization.error.title') }}</h3>
<p id="initErrorText">{{ init_error or '' }}</p>
<button type="button" class="primary-btn" id="initRetryBtn">{{ t('initialization.error.retry') }}</button>
</div>
</div>
<div class="tips-container">
<div class="tips-header">
<i class="fas fa-lightbulb"></i>
<h3>{{ t('initialization.tips.title') }}</h3>
</div>
<div class="tips-content">
<div class="tip-carousel" id="tipCarousel">
<div class="tip-item active">
<div class="tip-image">
<img src="{{ rel_prefix }}loras_static/images/tips/civitai-api.png" alt="{{ t('initialization.tips.civitai.alt') }}"
onerror="this.src='/loras_static/images/no-preview.png'">
</div>
<div class="tip-text">
<h4>{{ t('initialization.tips.civitai.title') }}</h4>
<p>{{ t('initialization.tips.civitai.description') }}</p>
</div>
</div>
<div class="tip-item">
<div class="tip-image">
<img src="{{ rel_prefix }}loras_static/images/tips/civitai-download.png" alt="{{ t('initialization.tips.download.alt') }}"
onerror="this.src='/loras_static/images/no-preview.png'">
</div>
<div class="tip-text">
<h4>{{ t('initialization.tips.download.title') }}</h4>
<p>{{ t('initialization.tips.download.description') }}</p>
</div>
</div>
<div class="tip-item">
<div class="tip-image">
<img src="{{ rel_prefix }}loras_static/images/tips/recipes.png" alt="{{ t('initialization.tips.recipes.alt') }}"
onerror="this.src='/loras_static/images/no-preview.png'">
</div>
<div class="tip-text">
<h4>{{ t('initialization.tips.recipes.title') }}</h4>
<p>{{ t('initialization.tips.recipes.description') }}</p>
</div>
</div>
<div class="tip-item">
<div class="tip-image">
<img src="{{ rel_prefix }}loras_static/images/tips/filter.png" alt="{{ t('initialization.tips.filter.alt') }}"
onerror="this.src='/loras_static/images/no-preview.png'">
</div>
<div class="tip-text">
<h4>{{ t('initialization.tips.filter.title') }}</h4>
<p>{{ t('initialization.tips.filter.description') }}</p>
</div>
</div>
<div class="tip-item">
<div class="tip-image">
<img src="{{ rel_prefix }}loras_static/images/tips/search.webp" alt="{{ t('initialization.tips.search.alt') }}"
onerror="this.src='/loras_static/images/no-preview.png'">
</div>
<div class="tip-text">
<h4>{{ t('initialization.tips.search.title') }}</h4>
<p>{{ t('initialization.tips.search.description') }}</p>
</div>
</div>
</div>
<div class="tip-navigation">
<span class="tip-dot active" data-index="0"></span>
<span class="tip-dot" data-index="1"></span>
<span class="tip-dot" data-index="2"></span>
<span class="tip-dot" data-index="3"></span>
<span class="tip-dot" data-index="4"></span>
</div>
</div>
</div>
</div>
</div>