diff --git a/locales/de.json b/locales/de.json index 458fcf45..eed87750 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1292,12 +1292,15 @@ "earlyAccess": "Früher Zugriff", "earlyAccessTooltip": "Für diese Version ist derzeit Civitai Early Access erforderlich", "ignored": "Ignoriert", - "ignoredTooltip": "Für diese Version sind Update-Benachrichtigungen deaktiviert" + "ignoredTooltip": "Für diese Version sind Update-Benachrichtigungen deaktiviert", + "onSiteOnly": "Nur On-Site", + "onSiteOnlyTooltip": "Diese Version ist nur für die On-Site-Generierung auf Civitai verfügbar" }, "actions": { "download": "Herunterladen", "downloadTooltip": "Diese Version herunterladen", "downloadEarlyAccessTooltip": "Diese Early-Access-Version von Civitai herunterladen", + "downloadNotAllowedTooltip": "Diese Version ist nur für die On-Site-Generierung auf Civitai verfügbar", "delete": "Löschen", "deleteTooltip": "Diese lokale Version löschen", "ignore": "Ignorieren", diff --git a/locales/en.json b/locales/en.json index d6b2413e..59104a86 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1292,12 +1292,15 @@ "earlyAccess": "Early Access", "earlyAccessTooltip": "This version currently requires Civitai early access", "ignored": "Ignored", - "ignoredTooltip": "Update notifications are disabled for this version" + "ignoredTooltip": "Update notifications are disabled for this version", + "onSiteOnly": "On-Site Only", + "onSiteOnlyTooltip": "This version is only available for on-site generation on Civitai" }, "actions": { "download": "Download", "downloadTooltip": "Download this version", "downloadEarlyAccessTooltip": "Download this early access version from Civitai", + "downloadNotAllowedTooltip": "This version is only available for on-site generation on Civitai", "delete": "Delete", "deleteTooltip": "Delete this local version", "ignore": "Ignore", diff --git a/locales/es.json b/locales/es.json index a0076a12..992d5815 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1292,12 +1292,15 @@ "earlyAccess": "Acceso temprano", "earlyAccessTooltip": "Esta versión requiere actualmente acceso temprano de Civitai", "ignored": "Ignorada", - "ignoredTooltip": "Las notificaciones de actualización están desactivadas para esta versión" + "ignoredTooltip": "Las notificaciones de actualización están desactivadas para esta versión", + "onSiteOnly": "Solo en Sitio", + "onSiteOnlyTooltip": "Esta versión solo está disponible para generación en el sitio de Civitai" }, "actions": { "download": "Descargar", "downloadTooltip": "Descargar esta versión", "downloadEarlyAccessTooltip": "Descargar esta versión de acceso temprano desde Civitai", + "downloadNotAllowedTooltip": "Esta versión solo está disponible para generación en el sitio de Civitai", "delete": "Eliminar", "deleteTooltip": "Eliminar esta versión local", "ignore": "Ignorar", diff --git a/locales/fr.json b/locales/fr.json index 624e79e0..1b183229 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1292,12 +1292,15 @@ "earlyAccess": "Accès anticipé", "earlyAccessTooltip": "Cette version nécessite actuellement l'accès anticipé Civitai", "ignored": "Ignorée", - "ignoredTooltip": "Les notifications de mise à jour sont désactivées pour cette version" + "ignoredTooltip": "Les notifications de mise à jour sont désactivées pour cette version", + "onSiteOnly": "Uniquement sur Site", + "onSiteOnlyTooltip": "Cette version n'est disponible que pour la génération sur le site Civitai" }, "actions": { "download": "Télécharger", "downloadTooltip": "Télécharger cette version", "downloadEarlyAccessTooltip": "Télécharger cette version en accès anticipé depuis Civitai", + "downloadNotAllowedTooltip": "Cette version n'est disponible que pour la génération sur le site Civitai", "delete": "Supprimer", "deleteTooltip": "Supprimer cette version locale", "ignore": "Ignorer", diff --git a/locales/he.json b/locales/he.json index a1639536..50481c75 100644 --- a/locales/he.json +++ b/locales/he.json @@ -1292,12 +1292,15 @@ "earlyAccess": "גישה מוקדמת", "earlyAccessTooltip": "גרסה זו דורשת כרגע גישת Early Access של Civitai", "ignored": "התעלם", - "ignoredTooltip": "התראות העדכון מושבתות עבור גרסה זו" + "ignoredTooltip": "התראות העדכון מושבתות עבור גרסה זו", + "onSiteOnly": "רק באתר", + "onSiteOnlyTooltip": "גרסה זו זמינה רק ליצירה באתר Civitai" }, "actions": { "download": "הורדה", "downloadTooltip": "הורד את הגרסה הזו", "downloadEarlyAccessTooltip": "הורד את גרסת ה-Early Access הזו מ-Civitai", + "downloadNotAllowedTooltip": "גרסה זו זמינה רק ליצירה באתר Civitai", "delete": "מחיקה", "deleteTooltip": "מחק את הגרסה המקומית הזו", "ignore": "התעלם", diff --git a/locales/ja.json b/locales/ja.json index 08114f46..2c5b352b 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1292,12 +1292,15 @@ "earlyAccess": "早期アクセス", "earlyAccessTooltip": "このバージョンは現在 Civitai の早期アクセスが必要です", "ignored": "無視中", - "ignoredTooltip": "このバージョンの更新通知は無効です" + "ignoredTooltip": "このバージョンの更新通知は無効です", + "onSiteOnly": "サイト内のみ", + "onSiteOnlyTooltip": "このバージョンはCivitaiサイト内でのみ利用可能で、ダウンロードはできません" }, "actions": { "download": "ダウンロード", "downloadTooltip": "このバージョンをダウンロード", "downloadEarlyAccessTooltip": "Civitai からこの早期アクセス版をダウンロード", + "downloadNotAllowedTooltip": "このバージョンはCivitaiサイト内でのみ利用可能で、ダウンロードはできません", "delete": "削除", "deleteTooltip": "このローカルバージョンを削除", "ignore": "無視", diff --git a/locales/ko.json b/locales/ko.json index 2b61f995..8b028618 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1292,12 +1292,15 @@ "earlyAccess": "얼리 액세스", "earlyAccessTooltip": "이 버전은 현재 Civitai 얼리 액세스가 필요합니다", "ignored": "무시됨", - "ignoredTooltip": "이 버전은 업데이트 알림이 비활성화되어 있습니다" + "ignoredTooltip": "이 버전은 업데이트 알림이 비활성화되어 있습니다", + "onSiteOnly": "사이트 내 전용", + "onSiteOnlyTooltip": "이 버전은 Civitai 사이트 내에서만 사용 가능하며 다운로드할 수 없습니다" }, "actions": { "download": "다운로드", "downloadTooltip": "이 버전 다운로드", "downloadEarlyAccessTooltip": "Civitai에서 이 얼리 액세스 버전 다운로드", + "downloadNotAllowedTooltip": "이 버전은 Civitai 사이트 내에서만 사용 가능하며 다운로드할 수 없습니다", "delete": "삭제", "deleteTooltip": "이 로컬 버전 삭제", "ignore": "무시", diff --git a/locales/ru.json b/locales/ru.json index c2fcf585..59246f34 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1292,12 +1292,15 @@ "earlyAccess": "Ранний доступ", "earlyAccessTooltip": "Для этой версии сейчас требуется ранний доступ Civitai", "ignored": "Игнорируется", - "ignoredTooltip": "Уведомления об обновлениях для этой версии отключены" + "ignoredTooltip": "Уведомления об обновлениях для этой версии отключены", + "onSiteOnly": "Только на Сайте", + "onSiteOnlyTooltip": "Эта версия доступна только для генерации на сайте Civitai" }, "actions": { "download": "Скачать", "downloadTooltip": "Скачать эту версию", "downloadEarlyAccessTooltip": "Скачать эту версию раннего доступа с Civitai", + "downloadNotAllowedTooltip": "Эта версия доступна только для генерации на сайте Civitai", "delete": "Удалить", "deleteTooltip": "Удалить эту локальную версию", "ignore": "Игнорировать", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index cc8df2ba..428d2aab 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -1292,12 +1292,15 @@ "earlyAccess": "抢先体验", "earlyAccessTooltip": "此版本当前需要 Civitai 抢先体验权限", "ignored": "已忽略", - "ignoredTooltip": "此版本已关闭更新通知" + "ignoredTooltip": "此版本已关闭更新通知", + "onSiteOnly": "仅站内生成", + "onSiteOnlyTooltip": "此版本仅在 Civitai 站内可用,无法下载" }, "actions": { "download": "下载", "downloadTooltip": "下载此版本", "downloadEarlyAccessTooltip": "从 Civitai 下载此抢先体验版本", + "downloadNotAllowedTooltip": "此版本仅在 Civitai 站内可用,无法下载", "delete": "删除", "deleteTooltip": "删除此本地版本", "ignore": "忽略", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index be7cbef4..dc8746d0 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1292,12 +1292,15 @@ "earlyAccess": "搶先體驗", "earlyAccessTooltip": "此版本目前需要 Civitai 搶先體驗權限", "ignored": "已忽略", - "ignoredTooltip": "此版本已關閉更新通知" + "ignoredTooltip": "此版本已關閉更新通知", + "onSiteOnly": "僅站內生成", + "onSiteOnlyTooltip": "此版本僅在 Civitai 站內可用,無法下載" }, "actions": { "download": "下載", "downloadTooltip": "下載此版本", "downloadEarlyAccessTooltip": "從 Civitai 下載此搶先體驗版本", + "downloadNotAllowedTooltip": "此版本僅在 Civitai 站內可用,無法下載", "delete": "刪除", "deleteTooltip": "刪除此本地版本", "ignore": "忽略", diff --git a/py/routes/handlers/model_handlers.py b/py/routes/handlers/model_handlers.py index efc16001..1748fe21 100644 --- a/py/routes/handlers/model_handlers.py +++ b/py/routes/handlers/model_handlers.py @@ -2423,6 +2423,7 @@ class ModelUpdateHandler: "shouldIgnore": version.should_ignore, "earlyAccessEndsAt": version.early_access_ends_at, "isEarlyAccess": is_early_access, + "usageControl": version.usage_control, "filePath": context.get("file_path"), "fileName": context.get("file_name"), } diff --git a/py/services/model_update_service.py b/py/services/model_update_service.py index 0a15a7bd..cfcba24b 100644 --- a/py/services/model_update_service.py +++ b/py/services/model_update_service.py @@ -69,6 +69,7 @@ class ModelVersionRecord: early_access_ends_at: Optional[str] = None sort_index: int = 0 is_early_access: bool = False + usage_control: Optional[str] = None # "Download", "Generation", "InternalGeneration" @dataclass @@ -101,11 +102,14 @@ class ModelUpdateRecord: return [version.version_id for version in self.versions if version.is_in_library] - def has_update(self, hide_early_access: bool = False) -> bool: + def has_update( + self, hide_early_access: bool = False, hide_non_downloadable: bool = True + ) -> bool: """Return True when a non-ignored remote version newer than the newest local copy is available. Args: hide_early_access: If True, exclude early access versions from update check. + hide_non_downloadable: If True, exclude versions that don't allow downloads. """ if self.should_ignore_model: @@ -121,6 +125,7 @@ class ModelUpdateRecord: not version.is_in_library and not version.should_ignore and not (hide_early_access and ModelUpdateRecord._is_early_access_active(version)) + and not (hide_non_downloadable and not ModelUpdateRecord._is_downloadable(version)) for version in self.versions ) @@ -129,6 +134,8 @@ class ModelUpdateRecord: continue if hide_early_access and ModelUpdateRecord._is_early_access_active(version): continue + if hide_non_downloadable and not ModelUpdateRecord._is_downloadable(version): + continue if version.version_id > max_in_library: return True return False @@ -155,11 +162,18 @@ class ModelUpdateRecord: # Phase 1: Basic EA flag from bulk API return version.is_early_access + @staticmethod + def _is_downloadable(version: ModelVersionRecord) -> bool: + if version.usage_control is None: + return True + return version.usage_control == "Download" + def has_update_for_base( self, local_version_id: Optional[int], local_base_model: Optional[str], hide_early_access: bool = False, + hide_non_downloadable: bool = True, ) -> bool: """Return True when a newer remote version with the same base model exists. @@ -167,6 +181,7 @@ class ModelUpdateRecord: local_version_id: The current local version id. local_base_model: The base model to filter by. hide_early_access: If True, exclude early access versions from update check. + hide_non_downloadable: If True, exclude versions that don't allow downloads. """ if self.should_ignore_model: @@ -197,6 +212,8 @@ class ModelUpdateRecord: continue if hide_early_access and ModelUpdateRecord._is_early_access_active(version): continue + if hide_non_downloadable and not ModelUpdateRecord._is_downloadable(version): + continue version_base = _normalize_base_model(version.base_model) if version_base != normalized_base: continue @@ -230,6 +247,7 @@ class ModelUpdateService: preview_url TEXT, is_in_library INTEGER NOT NULL DEFAULT 0, should_ignore INTEGER NOT NULL DEFAULT 0, + usage_control TEXT, PRIMARY KEY (model_id, version_id), FOREIGN KEY(model_id) REFERENCES model_update_status(model_id) ON DELETE CASCADE ); @@ -465,6 +483,10 @@ class ModelUpdateService: "ALTER TABLE model_update_versions " "ADD COLUMN is_early_access INTEGER NOT NULL DEFAULT 0" ), + "usage_control": ( + "ALTER TABLE model_update_versions " + "ADD COLUMN usage_control TEXT" + ), } for column, statement in migrations.items(): @@ -1337,6 +1359,7 @@ class ModelUpdateService: # Check availability field from bulk API for basic EA detection availability = _normalize_string(entry.get("availability")) is_early_access = availability == "EarlyAccess" + usage_control = _normalize_string(entry.get("usageControl")) return ModelVersionRecord( version_id=version_id, @@ -1350,6 +1373,7 @@ class ModelUpdateService: early_access_ends_at=early_access_ends_at, sort_index=index, is_early_access=is_early_access, + usage_control=usage_control, ) def _extract_size_bytes(self, files) -> Optional[int]: @@ -1464,7 +1488,7 @@ class ModelUpdateService: f""" SELECT model_id, version_id, sort_index, name, base_model, released_at, size_bytes, preview_url, is_in_library, should_ignore, early_access_ends_at, - is_early_access + is_early_access, usage_control FROM model_update_versions WHERE model_id IN ({placeholders}) ORDER BY model_id ASC, sort_index ASC, version_id ASC @@ -1492,6 +1516,7 @@ class ModelUpdateService: early_access_ends_at=row["early_access_ends_at"], sort_index=_normalize_int(row["sort_index"]) or 0, is_early_access=bool(row["is_early_access"]), + usage_control=row["usage_control"], ) ) @@ -1548,8 +1573,8 @@ class ModelUpdateService: INSERT INTO model_update_versions ( version_id, model_id, sort_index, name, base_model, released_at, size_bytes, preview_url, is_in_library, should_ignore, early_access_ends_at, - is_early_access - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + is_early_access, usage_control + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( version.version_id, @@ -1564,6 +1589,7 @@ class ModelUpdateService: 1 if version.should_ignore else 0, version.early_access_ends_at, 1 if version.is_early_access else 0, + version.usage_control, ), ) conn.commit() diff --git a/static/css/components/lora-modal/versions.css b/static/css/components/lora-modal/versions.css index fc8e24f8..25dd81fe 100644 --- a/static/css/components/lora-modal/versions.css +++ b/static/css/components/lora-modal/versions.css @@ -374,6 +374,14 @@ background: color-mix(in oklch, var(--lora-surface) 35%, transparent); } +.version-action-disabled { + background: transparent; + border-color: var(--border-color); + color: var(--text-muted); + opacity: 0.6; + cursor: not-allowed; +} + .version-action:disabled { opacity: 0.6; cursor: not-allowed; diff --git a/static/js/components/shared/ModelVersionsTab.js b/static/js/components/shared/ModelVersionsTab.js index 54a2fa7e..0e88fa2b 100644 --- a/static/js/components/shared/ModelVersionsTab.js +++ b/static/js/components/shared/ModelVersionsTab.js @@ -181,6 +181,13 @@ function isEarlyAccessActive(version) { } } +function isDownloadAllowed(version) { + if (!version.usageControl) { + return true; + } + return version.usageControl === 'Download'; +} + function buildMetaMarkup(version, options = {}) { const segments = []; if (version.baseModel) { @@ -230,12 +237,17 @@ function buildBadge(label, tone, options = {}) { function buildActionButton(label, variant, action, options = {}) { const attributes = [ `class="version-action ${variant}"`, - `data-version-action="${escapeHtml(action)}"`, ]; + if (action) { + attributes.push(`data-version-action="${escapeHtml(action)}"`); + } if (options.title) { attributes.push(`title="${escapeHtml(options.title)}"`); attributes.push(`aria-label="${escapeHtml(options.title)}"`); } + if (options.disabled) { + attributes.push('disabled'); + } if (options.extraAttributes) { attributes.push(options.extraAttributes); } @@ -371,6 +383,9 @@ function resolveUpdateAvailability(record, baseModel, currentVersionId) { if (hideEarlyAccess && isEarlyAccessActive(version)) { return false; } + if (!isDownloadAllowed(version)) { + return false; + } const versionBase = normalizeBaseModelName(version.baseModel); if (versionBase !== normalizedBase) { return false; @@ -502,6 +517,17 @@ function renderRow(version, options) { })); } + if (!isDownloadAllowed(version)) { + const onSiteOnlyBadgeLabel = translate('modals.model.versions.badges.onSiteOnly', {}, 'On-Site Only'); + badges.push(buildBadge(onSiteOnlyBadgeLabel, 'info', { + title: translate( + 'modals.model.versions.badges.onSiteOnlyTooltip', + {}, + 'This version is only available for on-site generation on Civitai' + ), + })); + } + if (version.shouldIgnore) { badges.push(buildBadge(ignoredBadgeLabel, 'muted', { title: translate( @@ -524,25 +550,36 @@ function renderRow(version, options) { const actions = []; if (!version.isInLibrary) { - // Download button with optional EA bolt icon + const canDownload = isDownloadAllowed(version); const downloadIcon = isEarlyAccess ? ' ' : ''; + let downloadTitle; + if (!canDownload) { + downloadTitle = translate( + 'modals.model.versions.actions.downloadNotAllowedTooltip', + {}, + 'This version is only available for on-site generation on Civitai' + ); + } else if (isEarlyAccess) { + downloadTitle = translate( + 'modals.model.versions.actions.downloadEarlyAccessTooltip', + {}, + 'Download this early access version from Civitai' + ); + } else { + downloadTitle = translate( + 'modals.model.versions.actions.downloadTooltip', + {}, + 'Download this version' + ); + } actions.push(buildActionButton( downloadLabel, - 'version-action-primary', - 'download', + canDownload ? 'version-action-primary' : 'version-action-disabled', + canDownload ? 'download' : '', { - title: isEarlyAccess - ? translate( - 'modals.model.versions.actions.downloadEarlyAccessTooltip', - {}, - 'Download this early access version from Civitai' - ) - : translate( - 'modals.model.versions.actions.downloadTooltip', - {}, - 'Download this version' - ), + title: downloadTitle, iconMarkup: downloadIcon, + disabled: !canDownload, } )); } else if (version.filePath) {