diff --git a/locales/de.json b/locales/de.json
index 9fce14bb..91cf7c74 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "Suche nach doppelten Rezepten...",
"found": "{count} Duplikat-Gruppen gefunden",
"noGroups": "Keine Duplikat-Gruppen mit dem aktuellen Abgleichskriterium gefunden",
"keepLatest": "Neueste Versionen behalten",
@@ -1422,13 +1422,14 @@
},
"proceedText": "Fahren Sie nur fort, wenn Sie sicher sind, dass Sie das wollen.",
"urlLabel": "Civitai-Modell-URL:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "Fügen Sie eine beliebige Civitai-Modell-URL ein. Unterstützte Formate:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "Hinweis: Wenn keine modelVersionId angegeben ist, wird die neueste Version verwendet."
+ "title": "Fügen Sie eine beliebige Civitai- oder CivitArchive-Modell-URL ein. Unterstützte Formate:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "Hinweis: Wenn keine modelVersionId angegeben ist, wird die neueste Version verwendet.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "Neu-Verknüpfung bestätigen"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "Fehler: {message}",
"linkHfSuccess": "Modell erfolgreich mit HuggingFace verknüpft",
"linkHfFailed": "Fehler: {message}",
+ "linkCivArchSuccess": "Modell erfolgreich über CivitArchive neu verknüpft",
"fetchMetadataFirst": "Bitte rufen Sie zuerst Metadaten von CivitAI ab",
"noCivitaiInfo": "Keine CivitAI-Informationen verfügbar",
"missingHash": "Modell-Hash nicht verfügbar"
diff --git a/locales/en.json b/locales/en.json
index 233e7463..5c1cc8c1 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -864,8 +864,8 @@
},
"navigation": {
"label": "Recipe navigation",
- "previousWithShortcut": "Previous recipe (\u2190)",
- "nextWithShortcut": "Next recipe (\u2192)"
+ "previousWithShortcut": "Previous recipe (←)",
+ "nextWithShortcut": "Next recipe (→)"
},
"workflow": {
"sendWorkflow": "Send Workflow to ComfyUI",
@@ -1422,13 +1422,14 @@
},
"proceedText": "Only proceed if you're sure this is what you want.",
"urlLabel": "Civitai Model URL:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676 or https://civitai.red/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890 or https://civitai.red/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "Paste any Civitai model URL from civitai.com or civitai.red. Supported formats:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "Note: If no modelVersionId is provided, the latest version will be used."
+ "title": "Paste any Civitai or CivitArchive model URL. Supported formats:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "Note: If no modelVersionId is provided, the latest version will be used.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "Confirm Re-link"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "Error: {message}",
"linkHfSuccess": "Model successfully linked to HuggingFace",
"linkHfFailed": "Error: {message}",
+ "linkCivArchSuccess": "Model successfully re-linked via CivitArchive",
"fetchMetadataFirst": "Please fetch metadata from CivitAI first",
"noCivitaiInfo": "No CivitAI information available",
"missingHash": "Model hash not available"
diff --git a/locales/es.json b/locales/es.json
index 5d9de398..b6c412f3 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "Buscando recetas duplicadas...",
"found": "Se encontraron {count} grupos de duplicados",
"noGroups": "No se encontraron grupos de duplicados con el criterio de coincidencia actual",
"keepLatest": "Mantener versiones más recientes",
@@ -1422,13 +1422,14 @@
},
"proceedText": "Solo procede si estás seguro de que esto es lo que quieres.",
"urlLabel": "URL del modelo de Civitai:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "Pega cualquier URL de modelo de Civitai. Formatos soportados:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "Nota: Si no se proporciona modelVersionId, se usará la versión más reciente."
+ "title": "Pega cualquier URL de modelo de Civitai o CivitArchive. Formatos soportados:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "Nota: Si no se proporciona modelVersionId, se usará la versión más reciente.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "Confirmar re-vinculación"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "Error: {message}",
"linkHfSuccess": "Modelo vinculado a HuggingFace exitosamente",
"linkHfFailed": "Error: {message}",
+ "linkCivArchSuccess": "Modelo re-vinculado exitosamente mediante CivitArchive",
"fetchMetadataFirst": "Por favor obtén metadatos de CivitAI primero",
"noCivitaiInfo": "No hay información de CivitAI disponible",
"missingHash": "Hash del modelo no disponible"
diff --git a/locales/fr.json b/locales/fr.json
index cda8458a..582bcb16 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "Recherche de recettes en doublon...",
"found": "Trouvé {count} groupes de doublons",
"noGroups": "Aucun groupe de doublons trouvé avec le critère de correspondance actuel",
"keepLatest": "Garder les dernières versions",
@@ -1422,13 +1422,14 @@
},
"proceedText": "Ne procédez que si vous êtes sûr que c'est ce que vous voulez.",
"urlLabel": "URL du modèle Civitai :",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "Collez n'importe quelle URL de modèle Civitai. Formats supportés :",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "Note : Si aucun modelVersionId n'est fourni, la dernière version sera utilisée."
+ "title": "Collez n'importe quelle URL de modèle Civitai ou CivitArchive. Formats supportés :",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "Note : Si aucun modelVersionId n'est fourni, la dernière version sera utilisée.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "Confirmer la re-liaison"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "Erreur : {message}",
"linkHfSuccess": "Modèle lié à HuggingFace avec succès",
"linkHfFailed": "Erreur : {message}",
+ "linkCivArchSuccess": "Modèle relié via CivitArchive avec succès",
"fetchMetadataFirst": "Veuillez d'abord récupérer les métadonnées depuis CivitAI",
"noCivitaiInfo": "Aucune information CivitAI disponible",
"missingHash": "Hash du modèle non disponible"
diff --git a/locales/he.json b/locales/he.json
index 347f8577..69406420 100644
--- a/locales/he.json
+++ b/locales/he.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "סורק למציאת מתכונים כפולים...",
"found": "נמצאו {count} קבוצות כפולות",
"noGroups": "לא נמצאו קבוצות כפולות לפי קריטריון ההתאמה הנוכחי",
"keepLatest": "שמור גרסאות אחרונות",
@@ -1422,13 +1422,14 @@
},
"proceedText": "המשך רק אם אתה בטוח שזה מה שאתה רוצה.",
"urlLabel": "כתובת URL של מודל ב-Civitai:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "הדבק כל כתובת URL של מודל מ-Civitai. פורמטים נתמכים:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "הערה: אם לא סופק modelVersionId, תילקח הגרסה האחרונה."
+ "title": "הדבק כל כתובת URL של מודל מ-Civitai או מ-CivitArchive. פורמטים נתמכים:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "הערה: אם לא סופק modelVersionId, תילקח הגרסה האחרונה.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "אשר קישור מחדש"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "שגיאה: {message}",
"linkHfSuccess": "המודל נקשר בהצלחה ל-HuggingFace",
"linkHfFailed": "שגיאה: {message}",
+ "linkCivArchSuccess": "המודל קושר מחדש דרך CivitArchive בהצלחה",
"fetchMetadataFirst": "אנא אחזר מטא-דאטה מ-CivitAI תחילה",
"noCivitaiInfo": "אין מידע מ-CivitAI זמין",
"missingHash": "ה-hash של המודל אינו זמין"
diff --git a/locales/ja.json b/locales/ja.json
index 4c5b31b0..874af76d 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "重複レシピをスキャンしています...",
"found": "{count} 個の重複グループが見つかりました",
"noGroups": "現在の一致基準では重複グループが見つかりませんでした",
"keepLatest": "最新バージョンを保持",
@@ -1422,13 +1422,14 @@
},
"proceedText": "これが本当に必要な場合のみ続行してください。",
"urlLabel": "CivitaiモデルURL:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "CivitaiモデルURLを貼り付けてください。対応形式:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "注:modelVersionIdが提供されていない場合、最新バージョンが使用されます。"
+ "title": "CivitaiまたはCivitArchiveのモデルURLを貼り付けてください。対応形式:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "注:modelVersionIdが提供されていない場合、最新バージョンが使用されます。",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "再リンクを確認"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "エラー:{message}",
"linkHfSuccess": "モデルを HuggingFace にリンクしました",
"linkHfFailed": "エラー:{message}",
+ "linkCivArchSuccess": "モデルがCivitArchive経由で正常に再リンクされました",
"fetchMetadataFirst": "最初にCivitAIからメタデータを取得してください",
"noCivitaiInfo": "CivitAI情報が利用できません",
"missingHash": "モデルハッシュが利用できません"
diff --git a/locales/ko.json b/locales/ko.json
index 96d70bc5..a33811dd 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "중복 레시피를 스캔하는 중...",
"found": "{count}개의 중복 그룹 발견",
"noGroups": "현재 일치 기준으로 중복 그룹을 찾을 수 없습니다",
"keepLatest": "최신 버전 유지",
@@ -1422,13 +1422,14 @@
},
"proceedText": "원하는 작업이 확실한 경우에만 진행하세요.",
"urlLabel": "Civitai 모델 URL:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "모든 Civitai 모델 URL을 붙여넣으세요. 지원되는 형식:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "참고: modelVersionId가 제공되지 않으면 최신 버전이 사용됩니다."
+ "title": "Civitai 또는 CivitArchive 모델 URL을 붙여넣으세요. 지원되는 형식:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "참고: modelVersionId가 제공되지 않으면 최신 버전이 사용됩니다.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "다시 연결 확인"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "오류: {message}",
"linkHfSuccess": "모델이 HuggingFace에 연결되었습니다",
"linkHfFailed": "오류: {message}",
+ "linkCivArchSuccess": "모델이 CivitArchive을 통해 성공적으로 다시 연결되었습니다",
"fetchMetadataFirst": "먼저 CivitAI에서 메타데이터를 가져와주세요",
"noCivitaiInfo": "사용 가능한 CivitAI 정보가 없습니다",
"missingHash": "모델 해시를 사용할 수 없습니다"
diff --git a/locales/ru.json b/locales/ru.json
index a5d2f046..e14cb4cc 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "Поиск дублирующихся рецептов...",
"found": "Найдено {count} групп дубликатов",
"noGroups": "Дубликатов с текущим критерием не найдено",
"keepLatest": "Оставить последние версии",
@@ -1422,13 +1422,14 @@
},
"proceedText": "Продолжайте только если вы уверены, что это то, что вам нужно.",
"urlLabel": "URL модели Civitai:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "Вставьте любой URL модели Civitai. Поддерживаемые форматы:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "Примечание: Если modelVersionId не указан, будет использована последняя версия."
+ "title": "Вставьте любой URL модели Civitai или CivitArchive. Поддерживаемые форматы:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "Примечание: Если modelVersionId не указан, будет использована последняя версия.",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "Подтвердить пересвязывание"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "Ошибка: {message}",
"linkHfSuccess": "Модель успешно связана с HuggingFace",
"linkHfFailed": "Ошибка: {message}",
+ "linkCivArchSuccess": "Модель успешно пересвязана через CivitArchive",
"fetchMetadataFirst": "Пожалуйста, сначала получите метаданные с CivitAI",
"noCivitaiInfo": "Информация CivitAI недоступна",
"missingHash": "Хеш модели недоступен"
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 3fa81625..7c47be49 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "正在扫描重复配方...",
"found": "发现 {count} 个重复组",
"noGroups": "按当前判重依据未找到重复组",
"keepLatest": "保留最新版本",
@@ -1422,13 +1422,14 @@
},
"proceedText": "仅在你确定需要此操作时继续。",
"urlLabel": "Civitai 模型 URL:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676 或 https://civitai.red/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890 或 https://civitai.red/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "粘贴任意来自 civitai.com 或 civitai.red 的 Civitai 模型 URL。支持格式:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "注意:如果未提供 modelVersionId,将使用最新版本。"
+ "title": "粘贴任意 Civitai 或 CivitArchive 模型 URL。支持格式:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "注意:如果未提供 modelVersionId,将使用最新版本。",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "确认重新关联"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "错误:{message}",
"linkHfSuccess": "模型已成功链接到 HuggingFace",
"linkHfFailed": "错误:{message}",
+ "linkCivArchSuccess": "模型已成功通过 CivitArchive 重新关联",
"fetchMetadataFirst": "请先从 CivitAI 获取元数据",
"noCivitaiInfo": "无 CivitAI 信息",
"missingHash": "模型哈希不可用"
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 8b5004b0..39773d12 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -963,7 +963,7 @@
}
},
"duplicates": {
- "finding": "[TODO: Translate] Scanning for duplicate recipes...",
+ "finding": "正在掃描重複配方...",
"found": "發現 {count} 組重複項",
"noGroups": "按目前判重依據未找到重複組",
"keepLatest": "保留最新版本",
@@ -1422,13 +1422,14 @@
},
"proceedText": "僅在確定需要執行時才繼續。",
"urlLabel": "Civitai 模型網址:",
- "urlPlaceholder": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
+ "urlPlaceholder": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
"helpText": {
- "title": "貼上任意 Civitai 模型網址。支援格式:",
- "format1": "https://civitai.com/models/649516",
- "format2": "https://civitai.com/models/649516?modelVersionId=726676",
- "format3": "https://civitai.com/models/649516/model-name?modelVersionId=726676",
- "note": "注意:若未提供 modelVersionId,將使用最新版本。"
+ "title": "貼上任意 Civitai 或 CivitArchive 模型網址。支援格式:",
+ "format1": "https://civitai.com/models/12345",
+ "format2": "https://civitai.com/models/12345?modelVersionId=67890",
+ "format3": "https://civitai.com/models/12345/model-name?modelVersionId=67890",
+ "note": "注意:若未提供 modelVersionId,將使用最新版本。",
+ "format4": "https://civarchive.com/models/12345 (CivitArchive)"
},
"confirmAction": "確認重新連結"
},
@@ -2225,6 +2226,7 @@
"relinkFailed": "錯誤:{message}",
"linkHfSuccess": "模型已成功連結到 HuggingFace",
"linkHfFailed": "錯誤:{message}",
+ "linkCivArchSuccess": "模型已成功透過 CivitArchive 重新連結",
"fetchMetadataFirst": "請先從 CivitAI 取得 metadata",
"noCivitaiInfo": "無 CivitAI 資訊",
"missingHash": "模型雜湊不可用"
diff --git a/py/routes/handlers/model_handlers.py b/py/routes/handlers/model_handlers.py
index a09b2ffd..8b3784fd 100644
--- a/py/routes/handlers/model_handlers.py
+++ b/py/routes/handlers/model_handlers.py
@@ -634,6 +634,16 @@ class ModelManagementHandler:
file_path = data.get("file_path")
model_id = data.get("model_id")
model_version_id = data.get("model_version_id")
+ source = data.get("source")
+
+ if source not in (None, "", "civarchive"):
+ return web.json_response(
+ {
+ "success": False,
+ "error": f"Unsupported relink source: {source}",
+ },
+ status=400,
+ )
if not file_path or model_id is None:
return web.json_response(
@@ -649,20 +659,33 @@ class ModelManagementHandler:
metadata_path
)
+ relink_kwargs = {
+ "file_path": file_path,
+ "metadata": local_metadata,
+ "model_id": int(model_id),
+ "model_version_id": int(model_version_id) if model_version_id else None,
+ }
+ if source == "civarchive":
+ relink_kwargs["provider_name"] = "civarchive_api"
+
updated_metadata = await self._metadata_sync.relink_metadata(
- file_path=file_path,
- metadata=local_metadata,
- model_id=int(model_id),
- model_version_id=int(model_version_id) if model_version_id else None,
+ **relink_kwargs
)
await self._service.scanner.update_single_model_cache(
file_path, file_path, updated_metadata
)
- message = f"Model successfully re-linked to Civitai model {model_id}" + (
- f" version {model_version_id}" if model_version_id else ""
- )
+ if source == "civarchive":
+ message = (
+ f"Model successfully re-linked to CivArchive model {model_id}"
+ + (f" version {model_version_id}" if model_version_id else "")
+ )
+ else:
+ message = (
+ f"Model successfully re-linked to Civitai model {model_id}"
+ + (f" version {model_version_id}" if model_version_id else "")
+ )
return web.json_response(
{
"success": True,
@@ -670,6 +693,8 @@ class ModelManagementHandler:
"hash": updated_metadata.get("sha256", ""),
}
)
+ except ValueError as exc:
+ return web.json_response({"success": False, "error": str(exc)}, status=400)
except Exception as exc:
if is_expected_offline_error(str(exc)):
return web.json_response(
diff --git a/py/services/metadata_sync_service.py b/py/services/metadata_sync_service.py
index 13e8c671..e441a8cc 100644
--- a/py/services/metadata_sync_service.py
+++ b/py/services/metadata_sync_service.py
@@ -419,14 +419,37 @@ class MetadataSyncService:
metadata: Dict[str, Any],
model_id: int,
model_version_id: Optional[int],
+ provider_name: Optional[str] = None,
) -> Dict[str, Any]:
- """Relink a local metadata record to a specific CivitAI model version."""
+ """Relink a local metadata record to a specific CivitAI model version.
+
+ When ``provider_name`` is given, the named provider is resolved via the
+ metadata provider selector instead of the default fallback chain. A
+ missing/disabled provider surfaces a user-friendly error instead of the
+ raw selector exception.
+ """
+
+ if provider_name:
+ try:
+ provider = await self._get_provider(provider_name)
+ except ValueError as exc:
+ logger.warning(
+ "Unable to resolve metadata provider %s: %s", provider_name, exc
+ )
+ raise ValueError(
+ "CivitArchive is not available or not enabled. "
+ "Enable the CivitArchive API in settings to relink via CivArchive."
+ ) from exc
+ else:
+ provider = await self._get_default_provider()
- provider = await self._get_default_provider()
civitai_metadata = await provider.get_model_version(model_id, model_version_id)
if not civitai_metadata:
+ provider_label = (
+ "CivitArchive" if provider_name == "civarchive_api" else "CivitAI"
+ )
raise ValueError(
- f"Model version not found on CivitAI for ID: {model_id}"
+ f"Model version not found on {provider_label} for ID: {model_id}"
+ (f" with version: {model_version_id}" if model_version_id else "")
)
diff --git a/static/js/components/ContextMenu/ModelContextMenuMixin.js b/static/js/components/ContextMenu/ModelContextMenuMixin.js
index b6771060..abd33ed9 100644
--- a/static/js/components/ContextMenu/ModelContextMenuMixin.js
+++ b/static/js/components/ContextMenu/ModelContextMenuMixin.js
@@ -6,7 +6,7 @@ import { bulkManager } from '../../managers/BulkManager.js';
import { MODEL_CONFIG } from '../../api/apiConfig.js';
import { translate } from '../../utils/i18nHelpers.js';
import { getNsfwLevelSelector } from '../shared/NsfwLevelSelector.js';
-import { extractCivitaiModelUrlParts } from '../../utils/civitaiUtils.js';
+import { classifyModelRelinkUrl } from '../../utils/civitaiUtils.js';
// Mixin with shared functionality for LoraContextMenu and CheckpointContextMenu
export const ModelContextMenuMixin = {
@@ -106,6 +106,17 @@ export const ModelContextMenuMixin = {
},
// Civitai re-linking methods
+ getModelTypePrefix() {
+ // Map the mixin model type to its API route prefix; the relink route
+ // exists for all model types via COMMON_ROUTE_DEFINITIONS.
+ const prefixMap = {
+ lora: 'loras',
+ checkpoint: 'checkpoints',
+ embedding: 'embeddings'
+ };
+ return prefixMap[this.modelType] || 'loras';
+ },
+
showRelinkCivitaiModal() {
const filePath = this.currentCard.dataset.filepath;
if (!filePath) return;
@@ -123,43 +134,55 @@ export const ModelContextMenuMixin = {
// Create new bound handler
this._boundRelinkHandler = async () => {
const url = urlInput.value.trim();
- const { modelId, modelVersionId } = this.extractModelVersionId(url);
-
- if (!modelId) {
- errorDiv.textContent = 'Invalid URL format. Must include model ID.';
+ const { source, modelId, modelVersionId } = classifyModelRelinkUrl(url);
+
+ if (!source || !modelId) {
+ errorDiv.textContent = 'Invalid URL format. Expected: https://civitai.com/models/{modelId} or https://civarchive.com/models/{modelId}';
return;
}
-
+
errorDiv.textContent = '';
modalManager.closeModal('relinkCivitaiModal');
-
+
try {
- state.loadingManager.showSimpleLoading('Re-linking to Civitai...');
-
- const endpoint = this.modelType === 'checkpoint' ?
- '/api/lm/checkpoints/relink-civitai' :
- '/api/lm/loras/relink-civitai';
-
+ const isCivArchive = source === 'civarchive';
+ state.loadingManager.showSimpleLoading(
+ isCivArchive ? 'Re-linking via CivitArchive...' : 'Re-linking to Civitai...'
+ );
+
+ const endpoint = `/api/lm/${this.getModelTypePrefix()}/relink-civitai`;
+
+ const payload = {
+ file_path: filePath,
+ model_id: modelId,
+ model_version_id: modelVersionId
+ };
+ // Omitted source keeps backend default-provider behaviour; only
+ // civarchive pins the provider explicitly.
+ if (isCivArchive) {
+ payload.source = source;
+ }
+
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
- body: JSON.stringify({
- file_path: filePath,
- model_id: modelId,
- model_version_id: modelVersionId
- })
+ body: JSON.stringify(payload)
});
-
+
if (!response.ok) {
throw new Error(`Failed to re-link model: ${response.statusText}`);
}
-
+
const data = await response.json();
-
+
if (data.success) {
- showToast('toast.contextMenu.relinkSuccess', {}, 'success');
+ showToast(
+ isCivArchive ? 'toast.contextMenu.linkCivArchSuccess' : 'toast.contextMenu.relinkSuccess',
+ {},
+ 'success'
+ );
// Reload the current view to show updated data
await this.resetAndReload();
} else {
@@ -255,10 +278,6 @@ export const ModelContextMenuMixin = {
setTimeout(() => urlInput.focus(), 50);
},
- extractModelVersionId(url) {
- return extractCivitaiModelUrlParts(url);
- },
-
parseModelId(value) {
if (value === undefined || value === null || value === '') {
return null;
diff --git a/static/js/utils/civitaiUtils.js b/static/js/utils/civitaiUtils.js
index 29550e78..a5272eeb 100644
--- a/static/js/utils/civitaiUtils.js
+++ b/static/js/utils/civitaiUtils.js
@@ -207,6 +207,47 @@ export function extractCivitaiModelUrlParts(url) {
}
}
+const CIVITARCHIVE_PAGE_HOSTS = new Set([
+ 'civitaiarchive.com',
+ 'civarchive.com',
+]);
+
+/**
+ * Classify a relink URL by its hosting source and extract ids.
+ * CivitArchive mirrors the Civitai id namespace, so both sources resolve to
+ * the same {modelId, modelVersionId} shape; only `source` differs.
+ */
+export function classifyModelRelinkUrl(url) {
+ if (!url || typeof url !== 'string') {
+ return { source: null, modelId: null, modelVersionId: null };
+ }
+
+ let parsedUrl;
+ try {
+ parsedUrl = new URL(url.trim());
+ } catch (e) {
+ return { source: null, modelId: null, modelVersionId: null };
+ }
+
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
+ return { source: null, modelId: null, modelVersionId: null };
+ }
+
+ const hostname = parsedUrl.hostname.toLowerCase().replace(/^www\./, '');
+ const pathMatch = parsedUrl.pathname.match(/\/models\/(\d+)/);
+ const modelId = pathMatch ? pathMatch[1] : null;
+ const modelVersionId = parsedUrl.searchParams.get('modelVersionId');
+
+ if (SUPPORTED_CIVITAI_PAGE_HOSTS.has(hostname)) {
+ return { source: 'civitai', modelId, modelVersionId };
+ }
+ if (CIVITARCHIVE_PAGE_HOSTS.has(hostname) && modelId) {
+ return { source: 'civarchive', modelId, modelVersionId };
+ }
+
+ return { source: null, modelId: null, modelVersionId: null };
+}
+
export function extractCivitaiImageId(url) {
if (!url) {
return null;
diff --git a/templates/components/modals/relink_civitai_modal.html b/templates/components/modals/relink_civitai_modal.html
index 933eac2b..97f57641 100644
--- a/templates/components/modals/relink_civitai_modal.html
+++ b/templates/components/modals/relink_civitai_modal.html
@@ -22,6 +22,7 @@
• {{ t('modals.relinkCivitai.helpText.format1') }}
• {{ t('modals.relinkCivitai.helpText.format2') }}
• {{ t('modals.relinkCivitai.helpText.format3') }}
+ • {{ t('modals.relinkCivitai.helpText.format4') }}
{{ t('modals.relinkCivitai.helpText.note') }}
diff --git a/tests/frontend/components/contextMenu.interactions.test.js b/tests/frontend/components/contextMenu.interactions.test.js
index 9b498c90..37c54669 100644
--- a/tests/frontend/components/contextMenu.interactions.test.js
+++ b/tests/frontend/components/contextMenu.interactions.test.js
@@ -2504,4 +2504,172 @@ describe('Interaction-level regression coverage', () => {
delete stateStub.currentPageType;
});
+
+ it('opens the relink modal from the relink-civitai menu action', async () => {
+ document.body.innerHTML = `
+