diff --git a/locales/de.json b/locales/de.json index d73a5df8..6bf5fb63 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "Zuvor heruntergeladen, aber derzeit nicht in Ihrer Bibliothek.", "alreadyInLibrary": "Bereits in Bibliothek", "autoOrganizedPath": "[Automatisch organisiert durch Pfadvorlage]", + "fileSelection": { + "title": "Dateiformat auswählen", + "files": "Dateien", + "select": "Datei auswählen" + }, "errors": { "invalidUrl": "Ungültiges Civitai URL-Format", "noVersions": "Keine Versionen für dieses Modell verfügbar" diff --git a/locales/en.json b/locales/en.json index b9bc05ba..65f09b35 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "Previously downloaded, but it is not currently in your library.", "alreadyInLibrary": "Already in Library", "autoOrganizedPath": "[Auto-organized by path template]", + "fileSelection": { + "title": "Select File Format", + "files": "files", + "select": "Select File" + }, "errors": { "invalidUrl": "Invalid Civitai URL format", "noVersions": "No versions available for this model" diff --git a/locales/es.json b/locales/es.json index c5b7cde0..ae7871e9 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "Descargado anteriormente, pero actualmente no está en tu biblioteca.", "alreadyInLibrary": "Ya en la biblioteca", "autoOrganizedPath": "[Auto-organizado por plantilla de ruta]", + "fileSelection": { + "title": "Seleccionar formato de archivo", + "files": "archivos", + "select": "Seleccionar archivo" + }, "errors": { "invalidUrl": "Formato de URL de Civitai inválido", "noVersions": "No hay versiones disponibles para este modelo" diff --git a/locales/fr.json b/locales/fr.json index 60c129ef..de7dbab8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "Déjà téléchargé, mais il n'est actuellement pas dans votre bibliothèque.", "alreadyInLibrary": "Déjà dans la bibliothèque", "autoOrganizedPath": "[Auto-organisé par modèle de chemin]", + "fileSelection": { + "title": "Choisir le format de fichier", + "files": "fichiers", + "select": "Choisir le fichier" + }, "errors": { "invalidUrl": "Format d'URL Civitai invalide", "noVersions": "Aucune version disponible pour ce modèle" diff --git a/locales/he.json b/locales/he.json index f9088b8f..f88abfdc 100644 --- a/locales/he.json +++ b/locales/he.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "הורד בעבר, אך הוא אינו נמצא כרגע בספרייה שלך.", "alreadyInLibrary": "כבר בספרייה", "autoOrganizedPath": "[מאורגן אוטומטית לפי תבנית נתיב]", + "fileSelection": { + "title": "בחר פורמט קובץ", + "files": "קבצים", + "select": "בחר קובץ" + }, "errors": { "invalidUrl": "פורמט URL של Civitai לא חוקי", "noVersions": "אין גרסאות זמינות למודל זה" diff --git a/locales/ja.json b/locales/ja.json index 677fe9db..72ab5a40 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "以前にダウンロード済みですが、現在はライブラリにありません。", "alreadyInLibrary": "既にライブラリ内", "autoOrganizedPath": "[パステンプレートによる自動整理]", + "fileSelection": { + "title": "ファイル形式を選択", + "files": "ファイル", + "select": "ファイルを選択" + }, "errors": { "invalidUrl": "無効なCivitai URL形式", "noVersions": "このモデルの利用可能なバージョンがありません" diff --git a/locales/ko.json b/locales/ko.json index 502b668a..f265f99f 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "이전에 다운로드했지만 현재 라이브러리에 없습니다.", "alreadyInLibrary": "이미 라이브러리에 있음", "autoOrganizedPath": "[경로 템플릿으로 자동 정리됨]", + "fileSelection": { + "title": "파일 형식 선택", + "files": "개 파일", + "select": "파일 선택" + }, "errors": { "invalidUrl": "잘못된 Civitai URL 형식", "noVersions": "이 모델에 사용 가능한 버전이 없습니다" diff --git a/locales/ru.json b/locales/ru.json index f8b346d0..d666060f 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "Ранее загружено, но сейчас этого нет в вашей библиотеке.", "alreadyInLibrary": "Уже в библиотеке", "autoOrganizedPath": "[Автоматически организовано по шаблону пути]", + "fileSelection": { + "title": "Выбрать формат файла", + "files": "файлов", + "select": "Выбрать файл" + }, "errors": { "invalidUrl": "Неверный формат URL Civitai", "noVersions": "Нет доступных версий для этой модели" diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 36e6f2d1..46f67846 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "之前已下载,但当前不在你的库中。", "alreadyInLibrary": "已存在于库中", "autoOrganizedPath": "【已按路径模板自动整理】", + "fileSelection": { + "title": "选择文件格式", + "files": "个文件", + "select": "选择文件" + }, "errors": { "invalidUrl": "无效的 Civitai URL 格式", "noVersions": "此模型没有可用版本" diff --git a/locales/zh-TW.json b/locales/zh-TW.json index b2cd78ef..4ff95aba 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1031,6 +1031,11 @@ "downloadedTooltip": "先前已下載,但目前不在你的庫中。", "alreadyInLibrary": "已在庫存", "autoOrganizedPath": "[依路徑範本自動整理]", + "fileSelection": { + "title": "選擇檔案格式", + "files": "個檔案", + "select": "選擇檔案" + }, "errors": { "invalidUrl": "Civitai 網址格式無效", "noVersions": "此模型無可用版本" diff --git a/static/css/components/modal/download-modal.css b/static/css/components/modal/download-modal.css index d146b87b..33a1c2b4 100644 --- a/static/css/components/modal/download-modal.css +++ b/static/css/components/modal/download-modal.css @@ -502,4 +502,170 @@ opacity: 0.5; pointer-events: none; user-select: none; +} + +/* File Count Badge on Version Items */ +.file-select-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 10px; + background: oklch(var(--lora-accent) / 0.18); + color: var(--lora-accent); + font-size: inherit; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid oklch(var(--lora-accent) / 0.35); + user-select: none; + box-shadow: 0 1px 2px oklch(var(--lora-accent) / 0.1); +} + +.file-select-badge:hover { + background: oklch(var(--lora-accent) / 0.3); + border-color: var(--lora-accent); + transform: scale(1.05); + box-shadow: 0 2px 6px oklch(var(--lora-accent) / 0.2); +} + +.file-select-badge:active { + transform: scale(0.98); +} + +.file-select-badge i { + font-size: 0.9em; +} + +.file-select-badge .badge-arrow { + margin-left: 2px; + font-size: 0.65em; + opacity: 0.7; +} + +/* File Selection Step */ +.file-selection-header { + margin-bottom: var(--space-3); +} + +.file-selection-header h3 { + margin: 0 0 4px 0; + font-size: 1.1em; + color: var(--text-color); +} + +.file-selection-version-name { + font-size: 0.9em; + color: var(--text-color); + opacity: 0.7; +} + +.file-selection-list { + max-height: 360px; + overflow-y: auto; + margin: var(--space-2) 0; + display: flex; + flex-direction: column; + gap: 8px; +} + +.file-option { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: all 0.2s ease; + background: var(--bg-color); +} + +.file-option:hover { + border-color: var(--lora-accent); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); +} + +.file-option.selected { + border: 2px solid var(--lora-accent); + background: oklch(var(--lora-accent) / 0.05); +} + +.file-option-radio { + flex-shrink: 0; +} + +.file-option-radio input[type="radio"] { + width: 16px; + height: 16px; + accent-color: var(--lora-accent); + cursor: pointer; +} + +.file-option-info { + flex: 1; + min-width: 0; +} + +.file-option-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 4px; +} + +.file-tag { + display: inline-block; + padding: 2px 7px; + border-radius: 4px; + font-size: 0.8em; + font-weight: 500; + line-height: 1.4; +} + +.file-tag.format { + background: oklch(var(--lora-accent) / 0.1); + color: var(--lora-accent); +} + +.file-tag.fp { + background: oklch(0.6 0.15 250 / 0.1); + color: oklch(0.55 0.15 250); +} + +.file-tag.size { + background: oklch(0.55 0.1 160 / 0.1); + color: oklch(0.5 0.12 160); +} + +.file-option-name { + font-size: 0.8em; + color: var(--text-color); + opacity: 0.6; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-top: 2px; +} + +.file-option-size { + font-size: 0.9em; + color: var(--text-color); + white-space: nowrap; + font-variant-numeric: tabular-nums; +} + +/* Dark theme adjustments */ +[data-theme="dark"] .file-option { + background: var(--lora-surface); +} + +[data-theme="dark"] .file-tag.fp { + background: oklch(0.55 0.12 250 / 0.15); + color: oklch(0.7 0.12 250); +} + +[data-theme="dark"] .file-tag.size { + background: oklch(0.5 0.08 160 / 0.15); + color: oklch(0.65 0.08 160); } \ No newline at end of file diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 750c2f21..c80d0fd3 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -909,7 +909,7 @@ export class BaseModelApiClient { } } - async downloadModel(modelId, versionId, modelRoot, relativePath, useDefaultPaths = false, downloadId, source = null) { + async downloadModel(modelId, versionId, modelRoot, relativePath, useDefaultPaths = false, downloadId, source = null, fileParams = null) { try { const response = await fetch(DOWNLOAD_ENDPOINTS.download, { method: 'POST', @@ -921,7 +921,8 @@ export class BaseModelApiClient { relative_path: relativePath, use_default_paths: useDefaultPaths, download_id: downloadId, - ...(source ? { source } : {}) + ...(source ? { source } : {}), + ...(fileParams ? { file_params: fileParams } : {}) }) }); diff --git a/static/js/managers/DownloadManager.js b/static/js/managers/DownloadManager.js index 52ed7085..48dcbe3c 100644 --- a/static/js/managers/DownloadManager.js +++ b/static/js/managers/DownloadManager.js @@ -33,6 +33,8 @@ export class DownloadManager { this.handleStartDownload = this.startDownload.bind(this); this.handleBackToUrl = this.backToUrl.bind(this); this.handleBackToVersions = this.backToVersions.bind(this); + this.handleBackToVersionFromFiles = this.backToVersionFromFiles.bind(this); + this.handleConfirmFileSelection = this.confirmFileSelection.bind(this); this.handleCloseModal = this.closeModal.bind(this); this.handleToggleDefaultPath = this.toggleDefaultPath.bind(this); } @@ -80,6 +82,10 @@ export class DownloadManager { document.getElementById('backToVersionsBtn').addEventListener('click', this.handleBackToVersions); document.getElementById('closeDownloadModal').addEventListener('click', this.handleCloseModal); + // File selection step buttons + document.getElementById('backToVersionFromFilesBtn').addEventListener('click', this.handleBackToVersionFromFiles); + document.getElementById('confirmFileSelection').addEventListener('click', this.handleConfirmFileSelection); + // Default path toggle handler document.getElementById('useDefaultPath').addEventListener('change', this.handleToggleDefaultPath); } @@ -129,6 +135,7 @@ export class DownloadManager { this.modelId = null; this.modelVersionId = null; this.source = null; + this.selectedFile = null; this.selectedFolder = ''; @@ -247,9 +254,12 @@ export class DownloadManager { const firstImage = version.images?.find(img => !img.url.endsWith('.mp4')); const thumbnailUrl = firstImage ? firstImage.url : '/loras_static/images/no-preview.png'; + // Count model-type files per version + const modelFiles = (version.files || []).filter(f => f.type === 'Model'); + const primaryFile = modelFiles.find(f => f.primary) || modelFiles[0] || {}; const fileSize = version.modelSizeKB ? (version.modelSizeKB / 1024).toFixed(2) : - (version.files[0]?.sizeKB / 1024).toFixed(2); + ((primaryFile.sizeKB || 0) / 1024).toFixed(2); const existsLocally = version.existsLocally; const hasBeenDownloaded = version.hasBeenDownloaded && !existsLocally; @@ -282,6 +292,12 @@ export class DownloadManager { `; } + const fileBadge = modelFiles.length > 1 && !existsLocally + ? ` + ${modelFiles.length} ${translate('modals.download.fileSelection.files')} + ` + : ''; + return `