diff --git a/locales/de.json b/locales/de.json index aea0c592..49596c76 100644 --- a/locales/de.json +++ b/locales/de.json @@ -678,7 +678,12 @@ "editBaseModel": "Basis-Modell bearbeiten", "viewOnCivitai": "Auf Civitai anzeigen", "viewOnCivitaiText": "Auf Civitai anzeigen", - "viewCreatorProfile": "Ersteller-Profil anzeigen" + "viewCreatorProfile": "Ersteller-Profil anzeigen", + "openFileLocation": "Dateispeicherort öffnen" + }, + "openFileLocation": { + "success": "Dateispeicherort erfolgreich geöffnet", + "failed": "Fehler beim Öffnen des Dateispeicherorts" }, "metadata": { "version": "Version", diff --git a/locales/en.json b/locales/en.json index 251688f6..58053fca 100644 --- a/locales/en.json +++ b/locales/en.json @@ -678,7 +678,12 @@ "editBaseModel": "Edit base model", "viewOnCivitai": "View on Civitai", "viewOnCivitaiText": "View on Civitai", - "viewCreatorProfile": "View Creator Profile" + "viewCreatorProfile": "View Creator Profile", + "openFileLocation": "Open File Location" + }, + "openFileLocation": { + "success": "File location opened successfully", + "failed": "Failed to open file location" }, "metadata": { "version": "Version", diff --git a/locales/es.json b/locales/es.json index 691e4651..9072722c 100644 --- a/locales/es.json +++ b/locales/es.json @@ -678,7 +678,12 @@ "editBaseModel": "Editar modelo base", "viewOnCivitai": "Ver en Civitai", "viewOnCivitaiText": "Ver en Civitai", - "viewCreatorProfile": "Ver perfil del creador" + "viewCreatorProfile": "Ver perfil del creador", + "openFileLocation": "Abrir ubicación del archivo" + }, + "openFileLocation": { + "success": "Ubicación del archivo abierta exitosamente", + "failed": "Error al abrir la ubicación del archivo" }, "metadata": { "version": "Versión", diff --git a/locales/fr.json b/locales/fr.json index 0424a93a..16e3e3d7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -678,7 +678,12 @@ "editBaseModel": "Modifier le modèle de base", "viewOnCivitai": "Voir sur Civitai", "viewOnCivitaiText": "Voir sur Civitai", - "viewCreatorProfile": "Voir le profil du créateur" + "viewCreatorProfile": "Voir le profil du créateur", + "openFileLocation": "Ouvrir l'emplacement du fichier" + }, + "openFileLocation": { + "success": "Emplacement du fichier ouvert avec succès", + "failed": "Échec de l'ouverture de l'emplacement du fichier" }, "metadata": { "version": "Version", diff --git a/locales/ja.json b/locales/ja.json index f4bad7a6..90ab446d 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -678,7 +678,12 @@ "editBaseModel": "ベースモデルを編集", "viewOnCivitai": "Civitaiで表示", "viewOnCivitaiText": "Civitaiで表示", - "viewCreatorProfile": "作成者プロフィールを表示" + "viewCreatorProfile": "作成者プロフィールを表示", + "openFileLocation": "ファイルの場所を開く" + }, + "openFileLocation": { + "success": "ファイルの場所を正常に開きました", + "failed": "ファイルの場所を開くのに失敗しました" }, "metadata": { "version": "バージョン", diff --git a/locales/ko.json b/locales/ko.json index 5eccaead..d180b076 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -678,7 +678,12 @@ "editBaseModel": "베이스 모델 편집", "viewOnCivitai": "Civitai에서 보기", "viewOnCivitaiText": "Civitai에서 보기", - "viewCreatorProfile": "제작자 프로필 보기" + "viewCreatorProfile": "제작자 프로필 보기", + "openFileLocation": "파일 위치 열기" + }, + "openFileLocation": { + "success": "파일 위치가 성공적으로 열렸습니다", + "failed": "파일 위치 열기에 실패했습니다" }, "metadata": { "version": "버전", diff --git a/locales/ru.json b/locales/ru.json index 212c2c6f..6850954a 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -678,7 +678,12 @@ "editBaseModel": "Редактировать базовую модель", "viewOnCivitai": "Посмотреть на Civitai", "viewOnCivitaiText": "Посмотреть на Civitai", - "viewCreatorProfile": "Посмотреть профиль создателя" + "viewCreatorProfile": "Посмотреть профиль создателя", + "openFileLocation": "Открыть расположение файла" + }, + "openFileLocation": { + "success": "Расположение файла успешно открыто", + "failed": "Не удалось открыть расположение файла" }, "metadata": { "version": "Версия", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index b419ae0d..bab32fd9 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -678,7 +678,12 @@ "editBaseModel": "编辑基础模型", "viewOnCivitai": "在 Civitai 查看", "viewOnCivitaiText": "在 Civitai 查看", - "viewCreatorProfile": "查看创作者主页" + "viewCreatorProfile": "查看创作者主页", + "openFileLocation": "打开文件位置" + }, + "openFileLocation": { + "success": "文件位置已成功打开", + "failed": "打开文件位置失败" }, "metadata": { "version": "版本", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 7265fadf..926b6888 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -678,7 +678,12 @@ "editBaseModel": "編輯基礎模型", "viewOnCivitai": "在 Civitai 查看", "viewOnCivitaiText": "在 Civitai 查看", - "viewCreatorProfile": "查看創作者個人檔案" + "viewCreatorProfile": "查看創作者個人檔案", + "openFileLocation": "開啟檔案位置" + }, + "openFileLocation": { + "success": "檔案位置已成功開啟", + "failed": "開啟檔案位置失敗" }, "metadata": { "version": "版本", diff --git a/py/routes/misc_routes.py b/py/routes/misc_routes.py index 2178d7cd..5ae2eadd 100644 --- a/py/routes/misc_routes.py +++ b/py/routes/misc_routes.py @@ -3,6 +3,7 @@ import os import sys import threading import asyncio +import subprocess from server import PromptServer # type: ignore from aiohttp import web from ..services.settings_manager import settings @@ -90,6 +91,8 @@ class MiscRoutes: app.router.add_get('/api/health-check', lambda request: web.json_response({'status': 'ok'})) + app.router.add_post('/api/open-file-location', MiscRoutes.open_file_location) + # Usage stats routes app.router.add_post('/api/update-usage-stats', MiscRoutes.update_usage_stats) app.router.add_get('/api/get-usage-stats', MiscRoutes.get_usage_stats) @@ -770,3 +773,54 @@ class MiscRoutes: 'success': False, 'error': str(e) }, status=500) + + @staticmethod + async def open_file_location(request): + """ + Open the folder containing the specified file and select the file in the file explorer. + + Expects a JSON request body with: + { + "file_path": "absolute/path/to/file" + } + """ + try: + data = await request.json() + file_path = data.get('file_path') + + if not file_path: + return web.json_response({ + 'success': False, + 'error': 'Missing file_path parameter' + }, status=400) + + file_path = os.path.abspath(file_path) + + if not os.path.isfile(file_path): + return web.json_response({ + 'success': False, + 'error': 'File does not exist' + }, status=404) + + # Open the folder and select the file + if os.name == 'nt': # Windows + # explorer /select,"C:\path\to\file" + subprocess.Popen(['explorer', '/select,', file_path]) + elif os.name == 'posix': + if sys.platform == 'darwin': # macOS + subprocess.Popen(['open', '-R', file_path]) + else: # Linux (selecting file is not standard, just open folder) + folder = os.path.dirname(file_path) + subprocess.Popen(['xdg-open', folder]) + + return web.json_response({ + 'success': True, + 'message': f'Opened folder and selected file: {file_path}' + }) + + except Exception as e: + logger.error(f"Failed to open file location: {e}", exc_info=True) + return web.json_response({ + 'success': False, + 'error': str(e) + }, status=500) diff --git a/py/utils/example_images_file_manager.py b/py/utils/example_images_file_manager.py index 828e7a96..53698be4 100644 --- a/py/utils/example_images_file_manager.py +++ b/py/utils/example_images_file_manager.py @@ -1,6 +1,5 @@ import logging import os -import re import sys import subprocess from aiohttp import web diff --git a/static/css/components/lora-modal/lora-modal.css b/static/css/components/lora-modal/lora-modal.css index 0770561d..902c1533 100644 --- a/static/css/components/lora-modal/lora-modal.css +++ b/static/css/components/lora-modal/lora-modal.css @@ -67,6 +67,14 @@ font-size: 0.9em; } +.file-path[data-action="open-file-location"] { + cursor: pointer; + text-decoration: underline; +} +.file-path[data-action="open-file-location"]:hover { + opacity: 0.8; +} + .description-text { line-height: 1.5; max-height: 100px; diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js index 1f1eb2c3..2a1f525b 100644 --- a/static/js/components/shared/ModelModal.js +++ b/static/js/components/shared/ModelModal.js @@ -166,10 +166,14 @@ export async function showModelModal(model, modelType) { -
+
- ${modelWithFullData.file_path.replace(/[^/]+$/, '') || 'N/A'} + + ${modelWithFullData.file_path.replace(/[^/]+$/, '') || 'N/A'} +
@@ -318,6 +322,12 @@ function setupEventHandlers(filePath) { window.open(`https://civitai.com/user/${username}`, '_blank'); } break; + case 'open-file-location': + const filePath = target.dataset.filepath; + if (filePath) { + openFileLocation(filePath); + } + break; } } @@ -444,6 +454,24 @@ async function saveNotes(filePath) { } } +/** + * Call backend to open file location and select the file + * @param {string} filePath + */ +async function openFileLocation(filePath) { + try { + const resp = await fetch('/api/open-file-location', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ 'file_path': filePath }) + }); + if (!resp.ok) throw new Error('Failed to open file location'); + showToast('modals.model.openFileLocation.success', {}, 'success'); + } catch (err) { + showToast('modals.model.openFileLocation.failed', {}, 'error'); + } +} + // Export the model modal API const modelModal = { show: showModelModal,