mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
feat(metadata): implement metadata archive management and update settings for metadata providers
This commit is contained in:
@@ -16,7 +16,9 @@
|
|||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"version": "Version"
|
"version": "Version",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"select": "Language",
|
"select": "Language",
|
||||||
@@ -178,7 +180,8 @@
|
|||||||
"folderSettings": "Folder Settings",
|
"folderSettings": "Folder Settings",
|
||||||
"downloadPathTemplates": "Download Path Templates",
|
"downloadPathTemplates": "Download Path Templates",
|
||||||
"exampleImages": "Example Images",
|
"exampleImages": "Example Images",
|
||||||
"misc": "Misc."
|
"misc": "Misc.",
|
||||||
|
"metadataArchive": "Metadata Archive Database"
|
||||||
},
|
},
|
||||||
"contentFiltering": {
|
"contentFiltering": {
|
||||||
"blurNsfwContent": "Blur NSFW Content",
|
"blurNsfwContent": "Blur NSFW Content",
|
||||||
@@ -273,6 +276,31 @@
|
|||||||
"misc": {
|
"misc": {
|
||||||
"includeTriggerWords": "Include Trigger Words in LoRA Syntax",
|
"includeTriggerWords": "Include Trigger Words in LoRA Syntax",
|
||||||
"includeTriggerWordsHelp": "Include trained trigger words when copying LoRA syntax to clipboard"
|
"includeTriggerWordsHelp": "Include trained trigger words when copying LoRA syntax to clipboard"
|
||||||
|
},
|
||||||
|
"metadataArchive": {
|
||||||
|
"enableArchiveDb": "Enable Metadata Archive Database",
|
||||||
|
"enableArchiveDbHelp": "Use local database for faster metadata retrieval and access to deleted models. Recommended for better performance.",
|
||||||
|
"providerPriority": "Metadata Provider Priority",
|
||||||
|
"providerPriorityHelp": "Choose which metadata source to try first when loading model information",
|
||||||
|
"priorityArchiveDb": "Archive Database (Recommended)",
|
||||||
|
"priorityCivitaiApi": "Civitai API",
|
||||||
|
"status": "Status",
|
||||||
|
"statusAvailable": "Available",
|
||||||
|
"statusUnavailable": "Not Available",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"currentPriority": "Current Priority",
|
||||||
|
"management": "Database Management",
|
||||||
|
"managementHelp": "Download or remove the metadata archive database",
|
||||||
|
"downloadButton": "Download Database",
|
||||||
|
"downloadingButton": "Downloading...",
|
||||||
|
"downloadedButton": "Downloaded",
|
||||||
|
"removeButton": "Remove Database",
|
||||||
|
"removingButton": "Removing...",
|
||||||
|
"downloadSuccess": "Metadata archive database downloaded successfully",
|
||||||
|
"downloadError": "Failed to download metadata archive database",
|
||||||
|
"removeSuccess": "Metadata archive database removed successfully",
|
||||||
|
"removeError": "Failed to remove metadata archive database",
|
||||||
|
"removeConfirm": "Are you sure you want to remove the metadata archive database? This will delete the local database file and you'll need to download it again to use this feature."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"loras": {
|
"loras": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from aiohttp import web
|
|||||||
from .base_model_routes import BaseModelRoutes
|
from .base_model_routes import BaseModelRoutes
|
||||||
from ..services.checkpoint_service import CheckpointService
|
from ..services.checkpoint_service import CheckpointService
|
||||||
from ..services.service_registry import ServiceRegistry
|
from ..services.service_registry import ServiceRegistry
|
||||||
|
from ..services.model_metadata_provider import ModelMetadataProviderManager
|
||||||
from ..config import config
|
from ..config import config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -15,14 +16,14 @@ class CheckpointRoutes(BaseModelRoutes):
|
|||||||
"""Initialize Checkpoint routes with Checkpoint service"""
|
"""Initialize Checkpoint routes with Checkpoint service"""
|
||||||
# Service will be initialized later via setup_routes
|
# Service will be initialized later via setup_routes
|
||||||
self.service = None
|
self.service = None
|
||||||
self.civitai_client = None
|
self.metadata_provider = None
|
||||||
self.template_name = "checkpoints.html"
|
self.template_name = "checkpoints.html"
|
||||||
|
|
||||||
async def initialize_services(self):
|
async def initialize_services(self):
|
||||||
"""Initialize services from ServiceRegistry"""
|
"""Initialize services from ServiceRegistry"""
|
||||||
checkpoint_scanner = await ServiceRegistry.get_checkpoint_scanner()
|
checkpoint_scanner = await ServiceRegistry.get_checkpoint_scanner()
|
||||||
self.service = CheckpointService(checkpoint_scanner)
|
self.service = CheckpointService(checkpoint_scanner)
|
||||||
self.civitai_client = await ServiceRegistry.get_civitai_client()
|
self.metadata_provider = await ModelMetadataProviderManager.get_instance()
|
||||||
|
|
||||||
# Initialize parent with the service
|
# Initialize parent with the service
|
||||||
super().__init__(self.service)
|
super().__init__(self.service)
|
||||||
@@ -66,7 +67,7 @@ class CheckpointRoutes(BaseModelRoutes):
|
|||||||
"""Get available versions for a Civitai checkpoint model with local availability info"""
|
"""Get available versions for a Civitai checkpoint model with local availability info"""
|
||||||
try:
|
try:
|
||||||
model_id = request.match_info['model_id']
|
model_id = request.match_info['model_id']
|
||||||
response = await self.civitai_client.get_model_versions(model_id)
|
response = await self.metadata_provider.get_model_versions(model_id)
|
||||||
if not response or not response.get('modelVersions'):
|
if not response or not response.get('modelVersions'):
|
||||||
return web.Response(status=404, text="Model not found")
|
return web.Response(status=404, text="Model not found")
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from aiohttp import web
|
|||||||
from .base_model_routes import BaseModelRoutes
|
from .base_model_routes import BaseModelRoutes
|
||||||
from ..services.embedding_service import EmbeddingService
|
from ..services.embedding_service import EmbeddingService
|
||||||
from ..services.service_registry import ServiceRegistry
|
from ..services.service_registry import ServiceRegistry
|
||||||
|
from ..services.model_metadata_provider import ModelMetadataProviderManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -14,14 +15,14 @@ class EmbeddingRoutes(BaseModelRoutes):
|
|||||||
"""Initialize Embedding routes with Embedding service"""
|
"""Initialize Embedding routes with Embedding service"""
|
||||||
# Service will be initialized later via setup_routes
|
# Service will be initialized later via setup_routes
|
||||||
self.service = None
|
self.service = None
|
||||||
self.civitai_client = None
|
self.metadata_provider = None
|
||||||
self.template_name = "embeddings.html"
|
self.template_name = "embeddings.html"
|
||||||
|
|
||||||
async def initialize_services(self):
|
async def initialize_services(self):
|
||||||
"""Initialize services from ServiceRegistry"""
|
"""Initialize services from ServiceRegistry"""
|
||||||
embedding_scanner = await ServiceRegistry.get_embedding_scanner()
|
embedding_scanner = await ServiceRegistry.get_embedding_scanner()
|
||||||
self.service = EmbeddingService(embedding_scanner)
|
self.service = EmbeddingService(embedding_scanner)
|
||||||
self.civitai_client = await ServiceRegistry.get_civitai_client()
|
self.metadata_provider = await ModelMetadataProviderManager.get_instance()
|
||||||
|
|
||||||
# Initialize parent with the service
|
# Initialize parent with the service
|
||||||
super().__init__(self.service)
|
super().__init__(self.service)
|
||||||
@@ -61,7 +62,7 @@ class EmbeddingRoutes(BaseModelRoutes):
|
|||||||
"""Get available versions for a Civitai embedding model with local availability info"""
|
"""Get available versions for a Civitai embedding model with local availability info"""
|
||||||
try:
|
try:
|
||||||
model_id = request.match_info['model_id']
|
model_id = request.match_info['model_id']
|
||||||
response = await self.civitai_client.get_model_versions(model_id)
|
response = await self.metadata_provider.get_model_versions(model_id)
|
||||||
if not response or not response.get('modelVersions'):
|
if not response or not response.get('modelVersions'):
|
||||||
return web.Response(status=404, text="Model not found")
|
return web.Response(status=404, text="Model not found")
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from server import PromptServer # type: ignore
|
|||||||
from .base_model_routes import BaseModelRoutes
|
from .base_model_routes import BaseModelRoutes
|
||||||
from ..services.lora_service import LoraService
|
from ..services.lora_service import LoraService
|
||||||
from ..services.service_registry import ServiceRegistry
|
from ..services.service_registry import ServiceRegistry
|
||||||
|
from ..services.model_metadata_provider import ModelMetadataProviderManager
|
||||||
from ..utils.routes_common import ModelRouteUtils
|
from ..utils.routes_common import ModelRouteUtils
|
||||||
from ..utils.utils import get_lora_info
|
from ..utils.utils import get_lora_info
|
||||||
|
|
||||||
@@ -19,14 +20,14 @@ class LoraRoutes(BaseModelRoutes):
|
|||||||
"""Initialize LoRA routes with LoRA service"""
|
"""Initialize LoRA routes with LoRA service"""
|
||||||
# Service will be initialized later via setup_routes
|
# Service will be initialized later via setup_routes
|
||||||
self.service = None
|
self.service = None
|
||||||
self.civitai_client = None
|
self.metadata_provider = None
|
||||||
self.template_name = "loras.html"
|
self.template_name = "loras.html"
|
||||||
|
|
||||||
async def initialize_services(self):
|
async def initialize_services(self):
|
||||||
"""Initialize services from ServiceRegistry"""
|
"""Initialize services from ServiceRegistry"""
|
||||||
lora_scanner = await ServiceRegistry.get_lora_scanner()
|
lora_scanner = await ServiceRegistry.get_lora_scanner()
|
||||||
self.service = LoraService(lora_scanner)
|
self.service = LoraService(lora_scanner)
|
||||||
self.civitai_client = await ServiceRegistry.get_civitai_client()
|
self.metadata_provider = await ModelMetadataProviderManager.get_instance()
|
||||||
|
|
||||||
# Initialize parent with the service
|
# Initialize parent with the service
|
||||||
super().__init__(self.service)
|
super().__init__(self.service)
|
||||||
@@ -217,7 +218,7 @@ class LoraRoutes(BaseModelRoutes):
|
|||||||
"""Get available versions for a Civitai LoRA model with local availability info"""
|
"""Get available versions for a Civitai LoRA model with local availability info"""
|
||||||
try:
|
try:
|
||||||
model_id = request.match_info['model_id']
|
model_id = request.match_info['model_id']
|
||||||
response = await self.civitai_client.get_model_versions(model_id)
|
response = await self.metadata_provider.get_model_versions(model_id)
|
||||||
if not response or not response.get('modelVersions'):
|
if not response or not response.get('modelVersions'):
|
||||||
return web.Response(status=404, text="Model not found")
|
return web.Response(status=404, text="Model not found")
|
||||||
|
|
||||||
@@ -261,8 +262,8 @@ class LoraRoutes(BaseModelRoutes):
|
|||||||
try:
|
try:
|
||||||
model_version_id = request.match_info.get('modelVersionId')
|
model_version_id = request.match_info.get('modelVersionId')
|
||||||
|
|
||||||
# Get model details from Civitai API
|
# Get model details from metadata provider
|
||||||
model, error_msg = await self.civitai_client.get_model_version_info(model_version_id)
|
model, error_msg = await self.metadata_provider.get_model_version_info(model_version_id)
|
||||||
|
|
||||||
if not model:
|
if not model:
|
||||||
# Log warning for failed model retrieval
|
# Log warning for failed model retrieval
|
||||||
@@ -288,7 +289,7 @@ class LoraRoutes(BaseModelRoutes):
|
|||||||
"""Get CivitAI model details by hash"""
|
"""Get CivitAI model details by hash"""
|
||||||
try:
|
try:
|
||||||
hash = request.match_info.get('hash')
|
hash = request.match_info.get('hash')
|
||||||
model = await self.civitai_client.get_model_by_hash(hash)
|
model = await self.metadata_provider.get_model_by_hash(hash)
|
||||||
return web.json_response(model)
|
return web.json_response(model)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error fetching model details by hash: {e}")
|
logger.error(f"Error fetching model details by hash: {e}")
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ from ..utils.lora_metadata import extract_trained_words
|
|||||||
from ..config import config
|
from ..config import config
|
||||||
from ..utils.constants import SUPPORTED_MEDIA_EXTENSIONS, NODE_TYPES, DEFAULT_NODE_COLOR
|
from ..utils.constants import SUPPORTED_MEDIA_EXTENSIONS, NODE_TYPES, DEFAULT_NODE_COLOR
|
||||||
from ..services.service_registry import ServiceRegistry
|
from ..services.service_registry import ServiceRegistry
|
||||||
|
from ..services.metadata_service import get_metadata_archive_manager, update_metadata_provider_priority
|
||||||
|
from ..services.websocket_manager import ws_manager
|
||||||
import re
|
import re
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -112,6 +114,11 @@ class MiscRoutes:
|
|||||||
|
|
||||||
# Add new route for checking if a model exists in the library
|
# Add new route for checking if a model exists in the library
|
||||||
app.router.add_get('/api/check-model-exists', MiscRoutes.check_model_exists)
|
app.router.add_get('/api/check-model-exists', MiscRoutes.check_model_exists)
|
||||||
|
|
||||||
|
# Add routes for metadata archive database management
|
||||||
|
app.router.add_post('/api/download-metadata-archive', MiscRoutes.download_metadata_archive)
|
||||||
|
app.router.add_post('/api/remove-metadata-archive', MiscRoutes.remove_metadata_archive)
|
||||||
|
app.router.add_get('/api/metadata-archive-status', MiscRoutes.get_metadata_archive_status)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def clear_cache(request):
|
async def clear_cache(request):
|
||||||
@@ -697,3 +704,108 @@ class MiscRoutes:
|
|||||||
'success': False,
|
'success': False,
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}, status=500)
|
}, status=500)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def download_metadata_archive(request):
|
||||||
|
"""Download and extract the metadata archive database"""
|
||||||
|
try:
|
||||||
|
archive_manager = await get_metadata_archive_manager()
|
||||||
|
|
||||||
|
# Progress callback to send updates via WebSocket
|
||||||
|
def progress_callback(stage, message):
|
||||||
|
asyncio.create_task(ws_manager.broadcast({
|
||||||
|
'stage': stage,
|
||||||
|
'message': message,
|
||||||
|
'type': 'metadata_archive_download'
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Download and extract in background
|
||||||
|
success = await archive_manager.download_and_extract_database(progress_callback)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Update settings to enable metadata archive
|
||||||
|
settings.set('enable_metadata_archive_db', True)
|
||||||
|
|
||||||
|
# Update provider priority
|
||||||
|
await update_metadata_provider_priority()
|
||||||
|
|
||||||
|
return web.json_response({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Metadata archive database downloaded and extracted successfully'
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Failed to download and extract metadata archive database'
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading metadata archive: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def remove_metadata_archive(request):
|
||||||
|
"""Remove the metadata archive database"""
|
||||||
|
try:
|
||||||
|
archive_manager = await get_metadata_archive_manager()
|
||||||
|
|
||||||
|
success = await archive_manager.remove_database()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Update settings to disable metadata archive
|
||||||
|
settings.set('enable_metadata_archive_db', False)
|
||||||
|
|
||||||
|
# Update provider priority
|
||||||
|
await update_metadata_provider_priority()
|
||||||
|
|
||||||
|
return web.json_response({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Metadata archive database removed successfully'
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Failed to remove metadata archive database'
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error removing metadata archive: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_metadata_archive_status(request):
|
||||||
|
"""Get the status of metadata archive database"""
|
||||||
|
try:
|
||||||
|
archive_manager = await get_metadata_archive_manager()
|
||||||
|
|
||||||
|
is_available = archive_manager.is_database_available()
|
||||||
|
is_enabled = settings.get('enable_metadata_archive_db', False)
|
||||||
|
priority = settings.get('metadata_provider_priority', 'archive_db')
|
||||||
|
|
||||||
|
db_size = 0
|
||||||
|
if is_available:
|
||||||
|
db_path = archive_manager.get_database_path()
|
||||||
|
if db_path and os.path.exists(db_path):
|
||||||
|
db_size = os.path.getsize(db_path)
|
||||||
|
|
||||||
|
return web.json_response({
|
||||||
|
'success': True,
|
||||||
|
'isAvailable': is_available,
|
||||||
|
'isEnabled': is_enabled,
|
||||||
|
'priority': priority,
|
||||||
|
'databaseSize': db_size,
|
||||||
|
'databasePath': archive_manager.get_database_path() if is_available else None
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting metadata archive status: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}, status=500)
|
||||||
|
|||||||
150
py/services/metadata_archive_manager.py
Normal file
150
py/services/metadata_archive_manager.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import zipfile
|
||||||
|
import aiohttp
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class MetadataArchiveManager:
|
||||||
|
"""Manages downloading and extracting Civitai metadata archive database"""
|
||||||
|
|
||||||
|
DOWNLOAD_URLS = [
|
||||||
|
"https://github.com/willmiao/civitai-metadata-archive-db/releases/download/db-2025-08-08/civitai.zip",
|
||||||
|
"https://huggingface.co/datasets/willmiao/civitai-metadata-archive-db/blob/main/civitai.zip"
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, base_path: str):
|
||||||
|
"""Initialize with base path where files will be stored"""
|
||||||
|
self.base_path = Path(base_path)
|
||||||
|
self.civitai_folder = self.base_path / "civitai"
|
||||||
|
self.archive_path = self.base_path / "civitai.zip"
|
||||||
|
self.db_path = self.civitai_folder / "civitai.sqlite"
|
||||||
|
|
||||||
|
def is_database_available(self) -> bool:
|
||||||
|
"""Check if the SQLite database is available and valid"""
|
||||||
|
return self.db_path.exists() and self.db_path.stat().st_size > 0
|
||||||
|
|
||||||
|
def get_database_path(self) -> Optional[str]:
|
||||||
|
"""Get the path to the SQLite database if available"""
|
||||||
|
if self.is_database_available():
|
||||||
|
return str(self.db_path)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def download_and_extract_database(self, progress_callback=None) -> bool:
|
||||||
|
"""Download and extract the metadata archive database
|
||||||
|
|
||||||
|
Args:
|
||||||
|
progress_callback: Optional callback function to report progress
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create directories if they don't exist
|
||||||
|
self.base_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.civitai_folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Download the archive
|
||||||
|
if not await self._download_archive(progress_callback):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Extract the archive
|
||||||
|
if not await self._extract_archive(progress_callback):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Clean up the archive file
|
||||||
|
if self.archive_path.exists():
|
||||||
|
self.archive_path.unlink()
|
||||||
|
|
||||||
|
logger.info(f"Successfully downloaded and extracted metadata database to {self.db_path}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading and extracting metadata database: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _download_archive(self, progress_callback=None) -> bool:
|
||||||
|
"""Download the zip archive from one of the available URLs"""
|
||||||
|
for url in self.DOWNLOAD_URLS:
|
||||||
|
try:
|
||||||
|
logger.info(f"Attempting to download from {url}")
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback("download", f"Downloading from {url}")
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
total_size = int(response.headers.get('content-length', 0))
|
||||||
|
downloaded = 0
|
||||||
|
|
||||||
|
with open(self.archive_path, 'wb') as f:
|
||||||
|
async for chunk in response.content.iter_chunked(8192):
|
||||||
|
f.write(chunk)
|
||||||
|
downloaded += len(chunk)
|
||||||
|
|
||||||
|
if progress_callback and total_size > 0:
|
||||||
|
percentage = (downloaded / total_size) * 100
|
||||||
|
progress_callback("download", f"Downloaded {percentage:.1f}%")
|
||||||
|
|
||||||
|
logger.info(f"Successfully downloaded archive from {url}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"Failed to download from {url}: HTTP {response.status}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error downloading from {url}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.error("Failed to download archive from any URL")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _extract_archive(self, progress_callback=None) -> bool:
|
||||||
|
"""Extract the zip archive to the civitai folder"""
|
||||||
|
try:
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback("extract", "Extracting archive...")
|
||||||
|
|
||||||
|
# Run extraction in thread pool to avoid blocking
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
await loop.run_in_executor(None, self._extract_zip_sync)
|
||||||
|
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback("extract", "Extraction completed")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting archive: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _extract_zip_sync(self):
|
||||||
|
"""Synchronous zip extraction (runs in thread pool)"""
|
||||||
|
with zipfile.ZipFile(self.archive_path, 'r') as archive:
|
||||||
|
archive.extractall(path=self.base_path)
|
||||||
|
|
||||||
|
async def remove_database(self) -> bool:
|
||||||
|
"""Remove the metadata database and folder"""
|
||||||
|
try:
|
||||||
|
if self.civitai_folder.exists():
|
||||||
|
# Remove all files in the civitai folder
|
||||||
|
for file_path in self.civitai_folder.iterdir():
|
||||||
|
if file_path.is_file():
|
||||||
|
file_path.unlink()
|
||||||
|
|
||||||
|
# Remove the folder itself
|
||||||
|
self.civitai_folder.rmdir()
|
||||||
|
|
||||||
|
# Also remove the archive file if it exists
|
||||||
|
if self.archive_path.exists():
|
||||||
|
self.archive_path.unlink()
|
||||||
|
|
||||||
|
logger.info("Successfully removed metadata database")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error removing metadata database: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
@@ -1,28 +1,97 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from .model_metadata_provider import ModelMetadataProviderManager, SQLiteModelMetadataProvider
|
from .model_metadata_provider import (
|
||||||
|
ModelMetadataProviderManager,
|
||||||
|
SQLiteModelMetadataProvider,
|
||||||
|
CivitaiModelMetadataProvider,
|
||||||
|
FallbackMetadataProvider
|
||||||
|
)
|
||||||
|
from .settings_manager import settings
|
||||||
|
from .metadata_archive_manager import MetadataArchiveManager
|
||||||
|
from .service_registry import ServiceRegistry
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def initialize_metadata_providers():
|
async def initialize_metadata_providers():
|
||||||
"""Initialize and configure all metadata providers"""
|
"""Initialize and configure all metadata providers based on settings"""
|
||||||
provider_manager = await ModelMetadataProviderManager.get_instance()
|
provider_manager = await ModelMetadataProviderManager.get_instance()
|
||||||
|
|
||||||
# Use hardcoded SQLite DB path if not set in settings
|
# Get settings
|
||||||
db_path = os.path.join(
|
enable_archive_db = settings.get('enable_metadata_archive_db', False)
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
priority = settings.get('metadata_provider_priority', 'archive_db')
|
||||||
'civitai', 'civitai.sqlite'
|
|
||||||
)
|
providers = []
|
||||||
if db_path and os.path.exists(db_path):
|
|
||||||
try:
|
# Initialize archive database provider if enabled
|
||||||
sqlite_provider = SQLiteModelMetadataProvider(db_path)
|
if enable_archive_db:
|
||||||
provider_manager.register_provider('sqlite', sqlite_provider)
|
# Initialize archive manager
|
||||||
logger.info(f"SQLite metadata provider registered with database: {db_path}")
|
base_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
except Exception as e:
|
archive_manager = MetadataArchiveManager(base_path)
|
||||||
logger.error(f"Failed to initialize SQLite metadata provider: {e}")
|
|
||||||
|
db_path = archive_manager.get_database_path()
|
||||||
|
if db_path:
|
||||||
|
try:
|
||||||
|
sqlite_provider = SQLiteModelMetadataProvider(db_path)
|
||||||
|
provider_manager.register_provider('sqlite', sqlite_provider)
|
||||||
|
providers.append(('sqlite', sqlite_provider))
|
||||||
|
logger.info(f"SQLite metadata provider registered with database: {db_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to initialize SQLite metadata provider: {e}")
|
||||||
|
else:
|
||||||
|
logger.warning("Metadata archive database is enabled but not available")
|
||||||
|
|
||||||
|
# Initialize Civitai API provider
|
||||||
|
try:
|
||||||
|
civitai_client = await ServiceRegistry.get_civitai_client()
|
||||||
|
civitai_provider = CivitaiModelMetadataProvider(civitai_client)
|
||||||
|
provider_manager.register_provider('civitai_api', civitai_provider)
|
||||||
|
providers.append(('civitai_api', civitai_provider))
|
||||||
|
logger.info("Civitai API metadata provider registered")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to initialize Civitai API metadata provider: {e}")
|
||||||
|
|
||||||
|
# Set up fallback provider based on priority
|
||||||
|
if len(providers) > 1:
|
||||||
|
# Order providers based on priority setting
|
||||||
|
if priority == 'archive_db':
|
||||||
|
# Archive DB first, then Civitai API
|
||||||
|
ordered_providers = [p[1] for p in providers if p[0] == 'sqlite'] + [p[1] for p in providers if p[0] == 'civitai_api']
|
||||||
|
else:
|
||||||
|
# Civitai API first, then Archive DB
|
||||||
|
ordered_providers = [p[1] for p in providers if p[0] == 'civitai_api'] + [p[1] for p in providers if p[0] == 'sqlite']
|
||||||
|
|
||||||
|
if ordered_providers:
|
||||||
|
fallback_provider = FallbackMetadataProvider(ordered_providers)
|
||||||
|
provider_manager.register_provider('fallback', fallback_provider, is_default=True)
|
||||||
|
logger.info(f"Fallback metadata provider registered with priority: {priority}")
|
||||||
|
elif len(providers) == 1:
|
||||||
|
# Only one provider available, set it as default
|
||||||
|
provider_name, provider = providers[0]
|
||||||
|
provider_manager.register_provider(provider_name, provider, is_default=True)
|
||||||
|
logger.info(f"Single metadata provider registered as default: {provider_name}")
|
||||||
|
else:
|
||||||
|
logger.warning("No metadata providers available")
|
||||||
|
|
||||||
return provider_manager
|
return provider_manager
|
||||||
|
|
||||||
|
async def update_metadata_provider_priority():
|
||||||
|
"""Update metadata provider priority based on current settings"""
|
||||||
|
provider_manager = await ModelMetadataProviderManager.get_instance()
|
||||||
|
|
||||||
|
# Get current settings
|
||||||
|
enable_archive_db = settings.get('enable_metadata_archive_db', False)
|
||||||
|
priority = settings.get('metadata_provider_priority', 'archive_db')
|
||||||
|
|
||||||
|
# Rebuild providers with new priority
|
||||||
|
await initialize_metadata_providers()
|
||||||
|
|
||||||
|
logger.info(f"Updated metadata provider priority to: {priority}")
|
||||||
|
|
||||||
|
async def get_metadata_archive_manager():
|
||||||
|
"""Get metadata archive manager instance"""
|
||||||
|
base_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
return MetadataArchiveManager(base_path)
|
||||||
|
|
||||||
async def get_metadata_provider(provider_name: str = None):
|
async def get_metadata_provider(provider_name: str = None):
|
||||||
"""Get a specific metadata provider or default provider"""
|
"""Get a specific metadata provider or default provider"""
|
||||||
provider_manager = await ModelMetadataProviderManager.get_instance()
|
provider_manager = await ModelMetadataProviderManager.get_instance()
|
||||||
|
|||||||
@@ -297,7 +297,8 @@ class FallbackMetadataProvider(ModelMetadataProvider):
|
|||||||
result = await provider.get_model_versions(model_id)
|
result = await provider.get_model_versions(model_id)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.debug(f"Provider failed for get_model_versions: {e}")
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -307,27 +308,30 @@ class FallbackMetadataProvider(ModelMetadataProvider):
|
|||||||
result = await provider.get_model_version(model_id, version_id)
|
result = await provider.get_model_version(model_id, version_id)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.debug(f"Provider failed for get_model_version: {e}")
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_model_version_info(self, version_id: str) -> Tuple[Optional[Dict], Optional[str]]:
|
async def get_model_version_info(self, version_id: str) -> Tuple[Optional[Dict], Optional[str]]:
|
||||||
for provider in self.providers:
|
for provider in self.providers:
|
||||||
try:
|
try:
|
||||||
result, err = await provider.get_model_version_info(version_id)
|
result, error = await provider.get_model_version_info(version_id)
|
||||||
if result:
|
if result:
|
||||||
return result, err
|
return result, error
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.debug(f"Provider failed for get_model_version_info: {e}")
|
||||||
continue
|
continue
|
||||||
return None, "Not found in any provider"
|
return None, "No provider could retrieve the data"
|
||||||
|
|
||||||
async def get_model_metadata(self, model_id: str) -> Tuple[Optional[Dict], int]:
|
async def get_model_metadata(self, model_id: str) -> Tuple[Optional[Dict], int]:
|
||||||
for provider in self.providers:
|
for provider in self.providers:
|
||||||
try:
|
try:
|
||||||
result, code = await provider.get_model_metadata(model_id)
|
result, status = await provider.get_model_metadata(model_id)
|
||||||
if result:
|
if result:
|
||||||
return result, code
|
return result, status
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.debug(f"Provider failed for get_model_metadata: {e}")
|
||||||
continue
|
continue
|
||||||
return None, 404
|
return None, 404
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ class SettingsManager:
|
|||||||
return {
|
return {
|
||||||
"civitai_api_key": "",
|
"civitai_api_key": "",
|
||||||
"show_only_sfw": False,
|
"show_only_sfw": False,
|
||||||
"language": "en" # 添加默认语言设置
|
"language": "en", # 添加默认语言设置
|
||||||
|
"enable_metadata_archive_db": False, # Enable metadata archive database
|
||||||
|
"metadata_provider_priority": "archive_db" # Default priority: 'archive_db' or 'civitai_api'
|
||||||
}
|
}
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
def get(self, key: str, default: Any = None) -> Any:
|
||||||
|
|||||||
@@ -260,6 +260,9 @@ export class SettingsManager {
|
|||||||
includeTriggerWordsCheckbox.checked = state.global.settings.includeTriggerWords || false;
|
includeTriggerWordsCheckbox.checked = state.global.settings.includeTriggerWords || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load metadata archive settings
|
||||||
|
await this.loadMetadataArchiveSettings();
|
||||||
|
|
||||||
// Load base model path mappings
|
// Load base model path mappings
|
||||||
this.loadBaseModelMappings();
|
this.loadBaseModelMappings();
|
||||||
|
|
||||||
@@ -838,6 +841,11 @@ export class SettingsManager {
|
|||||||
state: value ? 'toast.settings.compactModeEnabled' : 'toast.settings.compactModeDisabled'
|
state: value ? 'toast.settings.compactModeEnabled' : 'toast.settings.compactModeDisabled'
|
||||||
}, 'success');
|
}, 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for metadata archive settings
|
||||||
|
if (settingKey === 'enable_metadata_archive_db' || settingKey === 'metadata_provider_priority') {
|
||||||
|
await this.updateMetadataArchiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
|
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
|
||||||
@@ -910,11 +918,192 @@ export class SettingsManager {
|
|||||||
|
|
||||||
showToast('toast.settings.displayDensitySet', { density: densityName }, 'success');
|
showToast('toast.settings.displayDensitySet', { density: densityName }, 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for metadata archive settings
|
||||||
|
if (settingKey === 'metadata_provider_priority') {
|
||||||
|
await this.updateMetadataArchiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
|
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadMetadataArchiveSettings() {
|
||||||
|
try {
|
||||||
|
// Load current settings from state
|
||||||
|
const enableMetadataArchiveCheckbox = document.getElementById('enableMetadataArchive');
|
||||||
|
if (enableMetadataArchiveCheckbox) {
|
||||||
|
enableMetadataArchiveCheckbox.checked = state.global.settings.enable_metadata_archive_db || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataProviderPrioritySelect = document.getElementById('metadataProviderPriority');
|
||||||
|
if (metadataProviderPrioritySelect) {
|
||||||
|
metadataProviderPrioritySelect.value = state.global.settings.metadata_provider_priority || 'archive_db';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load status
|
||||||
|
await this.updateMetadataArchiveStatus();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading metadata archive settings:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateMetadataArchiveStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/metadata-archive-status');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const statusContainer = document.getElementById('metadataArchiveStatus');
|
||||||
|
if (statusContainer && data.success) {
|
||||||
|
const status = data;
|
||||||
|
const sizeText = status.databaseSize > 0 ? ` (${this.formatFileSize(status.databaseSize)})` : '';
|
||||||
|
|
||||||
|
statusContainer.innerHTML = `
|
||||||
|
<div class="status-info">
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>${translate('settings.metadataArchive.status')}:</strong>
|
||||||
|
<span class="status-badge ${status.isAvailable ? 'status-available' : 'status-unavailable'}">
|
||||||
|
${status.isAvailable ? translate('settings.metadataArchive.statusAvailable') : translate('settings.metadataArchive.statusUnavailable')}
|
||||||
|
</span>
|
||||||
|
${sizeText}
|
||||||
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>${translate('settings.metadataArchive.enabled')}:</strong>
|
||||||
|
<span class="status-badge ${status.isEnabled ? 'status-enabled' : 'status-disabled'}">
|
||||||
|
${status.isEnabled ? translate('common.enabled') : translate('common.disabled')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>${translate('settings.metadataArchive.currentPriority')}:</strong>
|
||||||
|
${status.priority === 'archive_db' ? translate('settings.metadataArchive.priorityArchiveDb') : translate('settings.metadataArchive.priorityCivitaiApi')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
const downloadBtn = document.getElementById('downloadMetadataArchiveBtn');
|
||||||
|
const removeBtn = document.getElementById('removeMetadataArchiveBtn');
|
||||||
|
|
||||||
|
if (downloadBtn) {
|
||||||
|
downloadBtn.disabled = status.isAvailable;
|
||||||
|
downloadBtn.textContent = status.isAvailable ?
|
||||||
|
translate('settings.metadataArchive.downloadedButton') :
|
||||||
|
translate('settings.metadataArchive.downloadButton');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeBtn) {
|
||||||
|
removeBtn.disabled = !status.isAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating metadata archive status:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadMetadataArchive() {
|
||||||
|
try {
|
||||||
|
const downloadBtn = document.getElementById('downloadMetadataArchiveBtn');
|
||||||
|
if (downloadBtn) {
|
||||||
|
downloadBtn.disabled = true;
|
||||||
|
downloadBtn.textContent = translate('settings.metadataArchive.downloadingButton');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/download-metadata-archive', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
showNotification(translate('settings.metadataArchive.downloadSuccess'), 'success');
|
||||||
|
|
||||||
|
// Update settings in state
|
||||||
|
state.global.settings.enable_metadata_archive_db = true;
|
||||||
|
setStorageItem('settings', state.global.settings);
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
const enableCheckbox = document.getElementById('enableMetadataArchive');
|
||||||
|
if (enableCheckbox) {
|
||||||
|
enableCheckbox.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateMetadataArchiveStatus();
|
||||||
|
} else {
|
||||||
|
showNotification(translate('settings.metadataArchive.downloadError') + ': ' + data.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading metadata archive:', error);
|
||||||
|
showNotification(translate('settings.metadataArchive.downloadError') + ': ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
const downloadBtn = document.getElementById('downloadMetadataArchiveBtn');
|
||||||
|
if (downloadBtn) {
|
||||||
|
downloadBtn.disabled = false;
|
||||||
|
downloadBtn.textContent = translate('settings.metadataArchive.downloadButton');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeMetadataArchive() {
|
||||||
|
if (!confirm(translate('settings.metadataArchive.removeConfirm'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const removeBtn = document.getElementById('removeMetadataArchiveBtn');
|
||||||
|
if (removeBtn) {
|
||||||
|
removeBtn.disabled = true;
|
||||||
|
removeBtn.textContent = translate('settings.metadataArchive.removingButton');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/remove-metadata-archive', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
showNotification(translate('settings.metadataArchive.removeSuccess'), 'success');
|
||||||
|
|
||||||
|
// Update settings in state
|
||||||
|
state.global.settings.enable_metadata_archive_db = false;
|
||||||
|
setStorageItem('settings', state.global.settings);
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
const enableCheckbox = document.getElementById('enableMetadataArchive');
|
||||||
|
if (enableCheckbox) {
|
||||||
|
enableCheckbox.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateMetadataArchiveStatus();
|
||||||
|
} else {
|
||||||
|
showNotification(translate('settings.metadataArchive.removeError') + ': ' + data.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing metadata archive:', error);
|
||||||
|
showNotification(translate('settings.metadataArchive.removeError') + ': ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
const removeBtn = document.getElementById('removeMetadataArchiveBtn');
|
||||||
|
if (removeBtn) {
|
||||||
|
removeBtn.disabled = false;
|
||||||
|
removeBtn.textContent = translate('settings.metadataArchive.removeButton');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async saveInputSetting(elementId, settingKey) {
|
async saveInputSetting(elementId, settingKey) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
|
|||||||
@@ -419,6 +419,70 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Metadata Archive Section -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>{{ t('settings.sections.metadataArchive') }}</h3>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="enableMetadataArchive">{{ t('settings.metadataArchive.enableArchiveDb') }}:</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="enableMetadataArchive" onchange="settingsManager.saveToggleSetting('enableMetadataArchive', 'enable_metadata_archive_db')">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
{{ t('settings.metadataArchive.enableArchiveDbHelp') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="metadataProviderPriority">{{ t('settings.metadataArchive.providerPriority') }}:</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control select-control">
|
||||||
|
<select id="metadataProviderPriority" onchange="settingsManager.saveSelectSetting('metadataProviderPriority', 'metadata_provider_priority')">
|
||||||
|
<option value="archive_db">{{ t('settings.metadataArchive.priorityArchiveDb') }}</option>
|
||||||
|
<option value="civitai_api">{{ t('settings.metadataArchive.priorityCivitaiApi') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
{{ t('settings.metadataArchive.providerPriorityHelp') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="metadata-archive-status" id="metadataArchiveStatus">
|
||||||
|
<!-- Status will be populated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label>{{ t('settings.metadataArchive.management') }}:</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<button type="button" id="downloadMetadataArchiveBtn" class="btn btn-primary" onclick="settingsManager.downloadMetadataArchive()">
|
||||||
|
{{ t('settings.metadataArchive.downloadButton') }}
|
||||||
|
</button>
|
||||||
|
<button type="button" id="removeMetadataArchiveBtn" class="btn btn-danger" onclick="settingsManager.removeMetadataArchive()" style="margin-left: 10px;">
|
||||||
|
{{ t('settings.metadataArchive.removeButton') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
{{ t('settings.metadataArchive.managementHelp') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user