diff --git a/docs/i18n-translation-guidelines.md b/docs/i18n-translation-guidelines.md index aced6bad..8ac194a0 100644 --- a/docs/i18n-translation-guidelines.md +++ b/docs/i18n-translation-guidelines.md @@ -120,6 +120,14 @@ Locales: `en`, `zh-CN`, `zh-TW`, `ja`, `ko`, `fr`, `de`, `es`, `ru`, `he` (RTL). > style. All 9 locales are translated (terminology in §2, "Download routing feature"), > so the "no remaining placeholders" claim holds again. +> **Status (2026-10, Civitai ids in model modal):** the model modal's hash footnote now +> shows the Civitai model id and version id (right-aligned, with copy buttons), adding +> 4 keys (`modals.model.metadata.civitaiModelId` / `.civitaiVersionId`, +> `modals.model.actions.copyCivitaiId` / `.civitaiIdCopied`). The same pass removed the +> search-options "hash" toggle (`header.search.filters.hash`) because hash/id search is +> now always on. All 9 locales are translated (terminology in §2, "Civitai ids feature"), +> so the "no remaining placeholders" claim holds again. + --- ## 1. Hard rules (do not violate) @@ -586,6 +594,24 @@ directly (model-type names, R3). The tooltip quotes the `modals.download.useDefa label verbatim with each locale's UI-label quoting style (zh-CN “ ”, zh-TW/ja 「 」, ko `' '`, fr « … », de „ … “, es/ru/he «…»). +### Civitai ids feature (model/version id in the model modal) + +The model modal's hash footnote shows the Civitai **model id** and **version id** with +copy buttons (`modals.model.metadata.civitaiModelId` / `.civitaiVersionId` labels, +`modals.model.actions.copyCivitaiId` tooltip, `.civitaiIdCopied` toast). **"ID" stays +Latin in every locale** (same precedent as `recipes.*.copyId`), and `Civitai` is the +brand (R3) — it is never translated or transliterated; the casing mirrors `en.json` +verbatim (R9). The copy/copied strings reuse each locale's existing clipboard patterns +(`modals.model.actions.copyHash` / `openFileLocation.copied`). + +| Term | Rendering | +|---|---| +| Model ID (label) | zh-CN 模型 ID · zh-TW 模型 ID · ja モデル ID · ko 모델 ID · fr ID du modèle · de Modell-ID · es ID del modelo · ru ID модели · he מזהה מודל | +| Version ID (label) | zh-CN 版本 ID · zh-TW 版本 ID · ja バージョン ID · ko 버전 ID · fr ID de version · de Versions-ID · es ID de versión · ru ID версии · he מזהה גרסה | + +Hebrew uses its established מזהה ("identifier") noun instead of Latin `ID` in these +labels, matching `recipes.*.copyId` (העתק מזהה מתכון). + --- ## 3. Cross-cutting confusion hot-spots (must-fix list) diff --git a/locales/de.json b/locales/de.json index 99b92321..27d813f7 100644 --- a/locales/de.json +++ b/locales/de.json @@ -251,7 +251,6 @@ "modelname": "Modellname", "tags": "Tags", "creator": "Ersteller", - "hash": "Hash", "title": "Rezept-Titel", "loraName": "LoRA-Dateiname", "loraModel": "LoRA-Modellname", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "An ComfyUI senden", "sendToWorkflowText": "An ComfyUI senden", "copyHash": "Hash kopieren", + "copyCivitaiId": "Civitai-ID kopieren", + "civitaiIdCopied": "Civitai-ID in die Zwischenablage kopiert", "deleteModelWithShortcut": "Modell löschen (Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "Basismodell", "size": "Größe", "hashes": "Hashes", + "civitaiModelId": "Modell-ID", + "civitaiVersionId": "Versions-ID", "unknown": "Unbekannt", "usageTips": "Nutzungstipps", "additionalNotes": "Zusätzliche Notizen", diff --git a/locales/en.json b/locales/en.json index 9b83bf13..19eb7b77 100644 --- a/locales/en.json +++ b/locales/en.json @@ -251,7 +251,6 @@ "modelname": "Model Name", "tags": "Tags", "creator": "Creator", - "hash": "Hash", "title": "Recipe Title", "loraName": "LoRA Filename", "loraModel": "LoRA Model Name", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "Send to ComfyUI", "sendToWorkflowText": "Send to ComfyUI", "copyHash": "Copy hash", + "copyCivitaiId": "Copy Civitai ID", + "civitaiIdCopied": "Civitai ID copied to clipboard", "deleteModelWithShortcut": "Delete model (Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "Base Model", "size": "Size", "hashes": "Hashes", + "civitaiModelId": "Model ID", + "civitaiVersionId": "Version ID", "unknown": "Unknown", "usageTips": "Usage Tips", "additionalNotes": "Additional Notes", diff --git a/locales/es.json b/locales/es.json index bfc1e6de..65f05b1f 100644 --- a/locales/es.json +++ b/locales/es.json @@ -251,7 +251,6 @@ "modelname": "Nombre del modelo", "tags": "Etiquetas", "creator": "Creador", - "hash": "Hash", "title": "Título de la receta", "loraName": "Nombre de archivo LoRA", "loraModel": "Nombre del modelo LoRA", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "Enviar a ComfyUI", "sendToWorkflowText": "Enviar a ComfyUI", "copyHash": "Copiar hash", + "copyCivitaiId": "Copiar ID de Civitai", + "civitaiIdCopied": "ID de Civitai copiado al portapapeles", "deleteModelWithShortcut": "Eliminar modelo (Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "Modelo base", "size": "Tamaño", "hashes": "Hashes", + "civitaiModelId": "ID del modelo", + "civitaiVersionId": "ID de versión", "unknown": "Desconocido", "usageTips": "Consejos de uso", "additionalNotes": "Notas adicionales", diff --git a/locales/fr.json b/locales/fr.json index 4dbfb441..0174265a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -251,7 +251,6 @@ "modelname": "Nom du modèle", "tags": "Tags", "creator": "Créateur", - "hash": "Hash", "title": "Titre de la recipe", "loraName": "Nom de fichier LoRA", "loraModel": "Nom du modèle LoRA", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "Envoyer vers ComfyUI", "sendToWorkflowText": "Envoyer vers ComfyUI", "copyHash": "Copier le hash", + "copyCivitaiId": "Copier l’ID Civitai", + "civitaiIdCopied": "ID Civitai copié dans le presse-papiers", "deleteModelWithShortcut": "Supprimer le modèle (Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "Modèle de base", "size": "Taille", "hashes": "Hashes", + "civitaiModelId": "ID du modèle", + "civitaiVersionId": "ID de version", "unknown": "Inconnu", "usageTips": "Conseils d'utilisation", "additionalNotes": "Notes supplémentaires", diff --git a/locales/he.json b/locales/he.json index 7ba591c0..d70d9980 100644 --- a/locales/he.json +++ b/locales/he.json @@ -251,7 +251,6 @@ "modelname": "שם מודל", "tags": "תגיות", "creator": "יוצר", - "hash": "hash", "title": "כותרת מתכון", "loraName": "שם קובץ LoRA", "loraModel": "שם מודל LoRA", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "שלח ל-ComfyUI", "sendToWorkflowText": "שלח ל-ComfyUI", "copyHash": "העתק hash", + "copyCivitaiId": "העתק מזהה Civitai", + "civitaiIdCopied": "מזהה Civitai הועתק ללוח העריכה", "deleteModelWithShortcut": "מחק מודל (Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "מודל בסיס", "size": "גודל", "hashes": "hashes", + "civitaiModelId": "מזהה מודל", + "civitaiVersionId": "מזהה גרסה", "unknown": "לא ידוע", "usageTips": "טיפים לשימוש", "additionalNotes": "הערות נוספות", diff --git a/locales/ja.json b/locales/ja.json index 521ae066..91a8b874 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -251,7 +251,6 @@ "modelname": "モデル名", "tags": "タグ", "creator": "作成者", - "hash": "ハッシュ", "title": "レシピタイトル", "loraName": "LoRAファイル名", "loraModel": "LoRAモデル名", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "ComfyUI に送信", "sendToWorkflowText": "ComfyUI に送信", "copyHash": "ハッシュをコピー", + "copyCivitaiId": "Civitai IDをコピー", + "civitaiIdCopied": "Civitai IDをクリップボードにコピーしました", "deleteModelWithShortcut": "モデルを削除(Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "ベースモデル", "size": "サイズ", "hashes": "ハッシュ", + "civitaiModelId": "モデル ID", + "civitaiVersionId": "バージョン ID", "unknown": "不明", "usageTips": "使用のヒント", "additionalNotes": "追加メモ", diff --git a/locales/ko.json b/locales/ko.json index fb197ae7..3c034f75 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -251,7 +251,6 @@ "modelname": "모델명", "tags": "태그", "creator": "제작자", - "hash": "해시", "title": "레시피 제목", "loraName": "LoRA 파일명", "loraModel": "LoRA 모델명", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "ComfyUI로 보내기", "sendToWorkflowText": "ComfyUI로 보내기", "copyHash": "해시 복사", + "copyCivitaiId": "Civitai ID 복사", + "civitaiIdCopied": "Civitai ID가 클립보드에 복사되었습니다", "deleteModelWithShortcut": "모델 삭제(Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "베이스 모델", "size": "크기", "hashes": "해시", + "civitaiModelId": "모델 ID", + "civitaiVersionId": "버전 ID", "unknown": "알 수 없음", "usageTips": "사용 팁", "additionalNotes": "추가 메모", diff --git a/locales/ru.json b/locales/ru.json index 353085ae..1a1a975c 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -251,7 +251,6 @@ "modelname": "Название модели", "tags": "Теги", "creator": "Автор", - "hash": "Хэш", "title": "Название рецепта", "loraName": "Имя файла LoRA", "loraModel": "Название модели LoRA", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "Отправить в ComfyUI", "sendToWorkflowText": "Отправить в ComfyUI", "copyHash": "Копировать хеш", + "copyCivitaiId": "Копировать ID Civitai", + "civitaiIdCopied": "ID Civitai скопирован в буфер обмена", "deleteModelWithShortcut": "Удалить модель (Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "Базовая модель", "size": "Размер", "hashes": "Хэши", + "civitaiModelId": "ID модели", + "civitaiVersionId": "ID версии", "unknown": "Неизвестно", "usageTips": "Советы по использованию", "additionalNotes": "Дополнительные заметки", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 01d30be8..db9c25ba 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -251,7 +251,6 @@ "modelname": "模型名称", "tags": "标签", "creator": "创作者", - "hash": "哈希", "title": "配方标题", "loraName": "LoRA 文件名", "loraModel": "LoRA 模型名称", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "发送到 ComfyUI", "sendToWorkflowText": "发送到 ComfyUI", "copyHash": "复制哈希值", + "copyCivitaiId": "复制 Civitai ID", + "civitaiIdCopied": "Civitai ID 已复制到剪贴板", "deleteModelWithShortcut": "删除模型(Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "基础模型", "size": "大小", "hashes": "哈希值", + "civitaiModelId": "模型 ID", + "civitaiVersionId": "版本 ID", "unknown": "未知", "usageTips": "使用提示", "additionalNotes": "附加备注", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 4454e8db..f9689df6 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -251,7 +251,6 @@ "modelname": "模型名稱", "tags": "標籤", "creator": "創作者", - "hash": "雜湊", "title": "配方標題", "loraName": "LoRA 檔案名稱", "loraModel": "LoRA 模型名稱", @@ -1837,6 +1836,8 @@ "sendToWorkflow": "傳送到 ComfyUI", "sendToWorkflowText": "傳送到 ComfyUI", "copyHash": "複製雜湊值", + "copyCivitaiId": "複製 Civitai ID", + "civitaiIdCopied": "Civitai ID 已複製到剪貼簿", "deleteModelWithShortcut": "刪除模型(Del)" }, "openFileLocation": { @@ -1855,6 +1856,8 @@ "baseModel": "基礎模型", "size": "大小", "hashes": "雜湊值", + "civitaiModelId": "模型 ID", + "civitaiVersionId": "版本 ID", "unknown": "未知", "usageTips": "使用提示", "additionalNotes": "附加備註", diff --git a/py/routes/handlers/model_handlers.py b/py/routes/handlers/model_handlers.py index a2287ef7..d2fe867b 100644 --- a/py/routes/handlers/model_handlers.py +++ b/py/routes/handlers/model_handlers.py @@ -387,7 +387,6 @@ class ModelListingHandler: == "true", "tags": request.query.get("search_tags", "false").lower() == "true", "creator": request.query.get("search_creator", "false").lower() == "true", - "hash": request.query.get("search_hash", "false").lower() == "true", "recursive": request.query.get("recursive", "true").lower() == "true", } diff --git a/py/services/model_query.py b/py/services/model_query.py index 29d5c544..a15ea05e 100644 --- a/py/services/model_query.py +++ b/py/services/model_query.py @@ -432,7 +432,6 @@ class SearchStrategy: "tags": False, "recursive": True, "creator": False, - "hash": False, } def __init__( @@ -495,13 +494,14 @@ class SearchStrategy: results.append(item) continue - # Hash search is always exact (never fuzzy): match the full - # sha256, its autov2 prefix (first 10 chars), or the autov3 hash. - if options.get("hash", False): - hash_query = search_lower.strip() - if hash_query and self._matches_hash(item, hash_query): - results.append(item) - continue + # Hash/id search is always exact (never fuzzy) and always on: it + # matches the full sha256, its autov2 prefix (first 10 chars), + # the autov3 hash, or the Civitai model/version ids. Exact-match + # semantics mean it adds no noise to ordinary keyword searches. + hash_query = search_lower.strip() + if hash_query and self._matches_hash(item, hash_query): + results.append(item) + continue return results @@ -515,6 +515,18 @@ class SearchStrategy: autov3 = item.get("autov3") if isinstance(autov3, str) and autov3 and hash_query == autov3.lower(): return True + civitai = item.get("civitai") + if isinstance(civitai, dict): + # A model card corresponds to one Civitai version: `modelId` is + # the model id (may match several cards when the library holds + # multiple versions), `id` is the version id (unique per card). + for key in ("modelId", "model_id", "id"): + value = civitai.get(key) + if value is None: + continue + value_str = str(value).strip() + if value_str and value_str != "0" and hash_query == value_str: + return True return False def _matches( diff --git a/static/css/components/lora-modal/lora-modal.css b/static/css/components/lora-modal/lora-modal.css index d5fcca7e..4604fea2 100644 --- a/static/css/components/lora-modal/lora-modal.css +++ b/static/css/components/lora-modal/lora-modal.css @@ -292,6 +292,15 @@ font-size: 0.8em; } +/* Civitai model/version ids — pushed to the right end of the footnote line */ +.hash-footnote .civitai-id-group { + display: inline-flex; + align-items: baseline; + flex-wrap: wrap; + gap: 4px 8px; + margin-left: auto; +} + .hash-footnote .hash-copy-btn { display: inline-flex; align-items: center; diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 28aa6867..5030869d 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -1566,9 +1566,6 @@ export class BaseModelApiClient { if (pageState.searchOptions.creator !== undefined) { params.append('search_creator', pageState.searchOptions.creator.toString()); } - if (pageState.searchOptions.hash !== undefined) { - params.append('search_hash', pageState.searchOptions.hash.toString()); - } } } diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js index 73a84515..e66c480d 100644 --- a/static/js/components/shared/ModelModal.js +++ b/static/js/components/shared/ModelModal.js @@ -355,7 +355,9 @@ export async function showModelModal(model, modelType) { const escapedFolderPath = escapeHtml((modelWithFullData.file_path || '').replace(/[^/]+$/, '') || 'N/A'); // De-emphasized hash display: a borderless full-width footnote line below // the info grid — sha256 middle-truncated (first 10 + last 6), autov3 in - // full (12 chars); the full value is copied via data-hash. + // full (12 chars); the full value is copied via data-hash. Civitai model / + // version ids join the same line on the right when the model comes from + // the Civitai ecosystem (a model card corresponds to one Civitai version). const modelSha256 = modelWithFullData.sha256 || ''; const modelAutov3 = modelWithFullData.autov3 || ''; const truncatedSha256 = modelSha256.length > 16 @@ -383,8 +385,47 @@ export async function showModelModal(model, modelType) { `); } - const hashesMarkup = modelSha256 && hashEntries.length ? ` -