mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-10-10 03:22:12 -03:00
feat(modal): open model metadata sidecar location from the model modal
Centralized sidecar storage moves .metadata.json into a mirror tree, so the Location link in the model modal could no longer reveal a model's sidecar. The Location row now shows an inline metadata icon button (centralized mode only) that calls a new POST /api/lm/models/open-sidecar-location endpoint: the backend resolves the sidecar through py/utils/sidecar_paths, reveals the .metadata.json selected in the file manager (falling back to the sidecar directory when it has not been written yet), and returns the path for clipboard mode on headless/Docker sessions. The platform reveal logic is factored into a shared FileSystemHandler._reveal_file helper. All 9 locales translated.
This commit is contained in:
@@ -349,6 +349,34 @@
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.file-path-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.file-path-wrapper .file-path {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.location-action-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--lora-accent);
|
||||
cursor: pointer;
|
||||
padding: 2px 5px;
|
||||
border-radius: var(--border-radius-xs);
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.location-action-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(66, 153, 225, 0.1);
|
||||
}
|
||||
|
||||
.file-path[data-action="open-file-location"] {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -365,6 +365,16 @@ export async function showModelModal(model, modelType) {
|
||||
};
|
||||
const escapedFilePathAttr = escapeAttribute(modelWithFullData.file_path || '');
|
||||
const escapedFolderPath = escapeHtml((modelWithFullData.file_path || '').replace(/[^/]+$/, '') || 'N/A');
|
||||
// Centralized sidecar storage moves .metadata.json into a mirror tree, so
|
||||
// the model path link can no longer reveal it — offer a dedicated entry
|
||||
// only in that mode (alongside mode keeps sidecars next to the model).
|
||||
const sidecarLocationButton = state.global.settings.sidecar_storage_mode === 'centralized'
|
||||
? `<button class="location-action-btn" title="${translate('modals.model.actions.openSidecarLocation', {}, 'Open metadata location')}"
|
||||
data-action="open-sidecar-location"
|
||||
data-filepath="${escapedFilePathAttr}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</button>`
|
||||
: '';
|
||||
// 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. Civitai model /
|
||||
@@ -697,11 +707,14 @@ export async function showModelModal(model, modelType) {
|
||||
<div class="info-item">
|
||||
<div class="location-wrapper">
|
||||
<label>${translate('modals.model.metadata.location', {}, 'Location')}</label>
|
||||
<span class="file-path" title="${translate('modals.model.actions.openFileLocation', {}, 'Open file location')}"
|
||||
data-action="open-file-location"
|
||||
data-filepath="${escapedFilePathAttr}">
|
||||
${escapedFolderPath}
|
||||
</span>
|
||||
<div class="file-path-wrapper">
|
||||
<span class="file-path" title="${translate('modals.model.actions.openFileLocation', {}, 'Open file location')}"
|
||||
data-action="open-file-location"
|
||||
data-filepath="${escapedFilePathAttr}">
|
||||
${escapedFolderPath}
|
||||
</span>
|
||||
${sidecarLocationButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item base-size">
|
||||
@@ -1014,6 +1027,12 @@ function setupEventHandlers(filePath, modelType) {
|
||||
openFileLocation(filePath);
|
||||
}
|
||||
break;
|
||||
case 'open-sidecar-location':
|
||||
const sidecarModelPath = target.dataset.filepath || getModalFilePath();
|
||||
if (sidecarModelPath) {
|
||||
openSidecarLocation(sidecarModelPath);
|
||||
}
|
||||
break;
|
||||
case 'nav-prev':
|
||||
handleDirectionalNavigation('prev', modelType);
|
||||
break;
|
||||
@@ -1412,6 +1431,37 @@ async function openFileLocation(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call backend to open the model's .metadata.json sidecar location
|
||||
* @param {string} filePath model file path (backend derives the sidecar path)
|
||||
*/
|
||||
async function openSidecarLocation(filePath) {
|
||||
try {
|
||||
const resp = await fetch('/api/lm/models/open-sidecar-location', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ 'file_path': filePath })
|
||||
});
|
||||
if (!resp.ok) throw new Error('Failed to open sidecar location');
|
||||
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.mode === 'clipboard' && data.path) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(data.path);
|
||||
showToast('modals.model.openSidecarLocation.copied', { path: data.path }, 'success');
|
||||
} catch (clipboardErr) {
|
||||
console.warn('Clipboard API not available:', clipboardErr);
|
||||
showToast('modals.model.openSidecarLocation.clipboardFallback', { path: data.path }, 'info');
|
||||
}
|
||||
} else {
|
||||
showToast('modals.model.openSidecarLocation.success', {}, 'success');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('modals.model.openSidecarLocation.failed', {}, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSendToWorkflow(target, modelType) {
|
||||
const filePath = getModalFilePath();
|
||||
if (!filePath) {
|
||||
|
||||
Reference in New Issue
Block a user