feat: support gated/private Hugging Face repos via access token

Add a huggingface_api_key setting (Settings UI, HF_TOKEN /
HUGGING_FACE_HUB_TOKEN env override) and attach it as a Bearer token
to Hugging Face file listing, model card fetching and downloads, so
gated and private repositories can be downloaded once the user has
accepted the repo terms.

- fetch_json/fetch_text accept custom headers; ModelSource gains an
  auth_headers() hook so handlers stay platform-agnostic
- 401/403 from the tree API now explain how to fix (configure token /
  accept gated terms)
- aria2 pre-resolves huggingface.co redirects and strips credentials
  before handing the signed CDN URL to aria2, mirroring the CivitAI
  handling so the token never leaks to the CDN
- settings API exposes huggingface_api_key_set only; the raw key joins
  _NO_SYNC_KEYS
This commit is contained in:
Will Miao
2026-09-25 18:44:06 +08:00
parent 067e605e75
commit 8b7ba59263
23 changed files with 446 additions and 23 deletions
@@ -68,6 +68,43 @@
</div>
</div>
<div class="setting-item api-key-item">
<div class="setting-row">
<div class="setting-info">
<label>{{ t('settings.huggingfaceApiKey') }}</label>
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.huggingfaceApiKeyHelp') }}"></i>
</div>
<div class="setting-control">
<!-- Status display (shown when not editing) -->
<div id="huggingfaceApiKeyStatus" class="api-key-status">
<span id="huggingfaceApiKeyStatusText" class="api-key-status-text api-key-status--unconfigured">
<i class="fas fa-times-circle text-error"></i>
{{ t('settings.huggingfaceApiKeyNotConfigured') }}
</span>
<button type="button" class="secondary-btn" id="huggingfaceApiKeyActionBtn" onclick="settingsManager.editApiKey('huggingface_api_key', 'huggingfaceApiKey')">
{{ t('settings.huggingfaceApiKeySet') }}
</button>
</div>
<!-- Inline edit view (shown when editing) -->
<div id="huggingfaceApiKeyEdit" class="api-key-edit is-hidden">
<div class="api-key-input">
<input type="text"
id="huggingfaceApiKey"
class="api-key-masked"
placeholder="{{ t('settings.huggingfaceApiKeyPlaceholder') }}"
autocomplete="off"
data-mask="css" />
<button type="button" class="toggle-visibility">
<i class="fas fa-eye"></i>
</button>
</div>
<button type="button" class="primary-btn" onclick="settingsManager.saveApiKey('huggingface_api_key', 'huggingfaceApiKey')">{{ t('common.actions.save') }}</button>
<button type="button" class="secondary-btn" onclick="settingsManager.cancelEditApiKey(true, 'huggingfaceApiKey')">{{ t('common.actions.cancel') }}</button>
</div>
</div>
</div>
</div>
{{ sm.setting_select('civitaiHost', 'civitai_host', 'settings.civitaiHost.label', [
('civitai.com', 'settings.civitaiHost.options.com'),
('civitai.red', 'settings.civitaiHost.options.red'),