mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-26 13:34:08 -03:00
Add an opt-in 'centralized' sidecar storage mode alongside the default 'alongside' layout. In centralized mode, .metadata.json sidecars and preview assets live under a configurable root (sidecar_storage_path, default <settings_dir>/sidecars), mirroring the library-relative directory structure: <root>/<library>/<root_basename>/<rel_dir>/. Backend: - settings: sidecar_storage_mode / sidecar_storage_path with validation; changing either refreshes the preview allowlist - config: centralized root added to preview-serving allowlist - lifecycle: delete / move / rename / folder-rename / folder-delete and undoable-delete staging all operate on the mirror tree in centralized mode (model files themselves never move); EXDEV-tolerant cross- filesystem moves - scanners: pending-hash filesystem scan walks the mirror tree in centralized mode; preview discovery reads from the sidecar dir; .civitai.info stays co-located in both modes - migration: SidecarMigrationUseCase moves sidecars+previews between layouts both directions (keep-newer conflict resolution, preview_url rewriting, WebSocket progress), exposed as POST+GET /api/lm/sidecars/migrate with a mode guard (force=true for the settings-first flow) Frontend: - settings modal: sidecar storage section (mode select + path input with browse/validation), mode-change confirmation offering immediate migration (force=true), and a 'Migrate Sidecars Now' action - i18n keys synced to all locales ([TODO: Translate] placeholders) Docs: metadata-json-schema.md gains a storage-location section; AGENTS.md records the sidecar_paths helper convention.
203 lines
10 KiB
HTML
203 lines
10 KiB
HTML
<!-- Delete Confirmation Modal -->
|
|
<div id="deleteModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2>{{ t('modals.deleteModel.title') }}</h2>
|
|
<p class="delete-message">{{ t('modals.deleteModel.message') }}</p>
|
|
<div class="delete-model-info"></div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" onclick="closeDeleteModal()">{{ t('common.actions.cancel') }}</button>
|
|
<button class="delete-btn" onclick="confirmDelete()">{{ t('common.actions.delete') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Exclude Confirmation Modal -->
|
|
<div id="excludeModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2>{{ t('modals.excludeModel.title') }}</h2>
|
|
<p class="delete-message">{{ t('modals.excludeModel.message') }}</p>
|
|
<div class="exclude-model-info"></div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" onclick="closeExcludeModal()">{{ t('common.actions.cancel') }}</button>
|
|
<button class="exclude-btn" onclick="confirmExclude()">{{ t('modals.exclude.confirm') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipes Duplicate Delete Confirmation Modal -->
|
|
<div id="duplicateDeleteModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2>{{ t('modals.deleteDuplicateRecipes.title') }}</h2>
|
|
<p class="delete-message">{{ t('modals.deleteDuplicateRecipes.message') }}</p>
|
|
<div class="delete-model-info">
|
|
<p><span id="duplicateDeleteCount">0</span> {{ t('modals.deleteDuplicateRecipes.countMessage') }}</p>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" onclick="modalManager.closeModal('duplicateDeleteModal')">{{ t('common.actions.cancel') }}</button>
|
|
<button class="delete-btn" onclick="recipeManager.confirmDeleteDuplicates()">{{ t('common.actions.delete') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Models Duplicate Delete Confirmation Modal -->
|
|
<div id="modelDuplicateDeleteModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2>{{ t('modals.deleteDuplicateModels.title') }}</h2>
|
|
<p class="delete-message">{{ t('modals.deleteDuplicateModels.message') }}</p>
|
|
<div class="delete-model-info">
|
|
<p><span id="modelDuplicateDeleteCount">0</span> {{ t('modals.deleteDuplicateModels.countMessage') }}</p>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" onclick="modalManager.closeModal('modelDuplicateDeleteModal')">{{ t('common.actions.cancel') }}</button>
|
|
<button class="delete-btn" onclick="modelDuplicatesManager.confirmDeleteDuplicates()">{{ t('common.actions.delete') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Delete Confirmation Modal -->
|
|
<div id="bulkDeleteModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2>{{ t('modals.bulkDelete.title') }}</h2>
|
|
<p class="delete-message">{{ t('modals.bulkDelete.message') }}</p>
|
|
<div class="delete-model-info">
|
|
<p><span id="bulkDeleteCount">0</span> {{ t('modals.bulkDelete.countMessage') }}</p>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" onclick="modalManager.closeModal('bulkDeleteModal')">{{ t('common.actions.cancel') }}</button>
|
|
<button class="delete-btn" onclick="bulkManager.confirmBulkDelete()">{{ t('modals.bulkDelete.action') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Check Updates Confirmation Modal -->
|
|
<div id="checkUpdatesConfirmModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2 data-role="title"></h2>
|
|
<p class="confirmation-message" data-role="message"></p>
|
|
<p class="confirmation-tip" data-role="tip"></p>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" data-action="cancel-check-updates">{{ t('common.actions.cancel') }}</button>
|
|
<button class="primary-btn" data-action="confirm-check-updates">{{ t('modals.checkUpdates.action') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filename Template Apply/Revert Confirmation Modal
|
|
Self-managed by SettingsManager (NOT registered with ModalManager): it
|
|
stacks above the settings modal, like the directory picker. -->
|
|
<div id="filenameTemplateConfirmModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2 data-role="title"></h2>
|
|
<p class="delete-message" data-role="message"></p>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" data-action="cancel-filename-template">{{ t('common.actions.cancel') }}</button>
|
|
<button class="primary-btn" data-action="confirm-filename-template"></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidecar Migration Confirmation Modal
|
|
Self-managed by SettingsManager (NOT registered with ModalManager): it
|
|
stacks above the settings modal, like the directory picker. -->
|
|
<div id="sidecarMigrationConfirmModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2 data-role="title"></h2>
|
|
<p class="delete-message" data-role="message"></p>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" data-action="cancel-sidecar-migration">{{ t('common.actions.cancel') }}</button>
|
|
<button class="primary-btn" data-action="confirm-sidecar-migration"></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar Folder Delete Confirmation Modal -->
|
|
<!-- Shared by two states: 'confirm' (model-free folder) and 'blocked' (the
|
|
subtree still holds models, so a cascade delete is refused). -->
|
|
<div id="deleteFolderModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2 data-role="title">{{ t('sidebar.deleteFolderModal.title') }}</h2>
|
|
<p class="delete-message" data-role="message">{{ t('sidebar.deleteFolderModal.message') }}</p>
|
|
<div class="delete-model-info" data-role="info"></div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" data-action="cancel-delete-folder">{{ t('common.actions.cancel') }}</button>
|
|
<button class="delete-btn" data-action="confirm-delete-folder">{{ t('sidebar.deleteFolderModal.confirm') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Download Missing LoRAs Confirmation Modal -->
|
|
<div id="bulkDownloadMissingLorasModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>{{ t('modals.bulkDownloadMissingLoras.title') }}</h2>
|
|
<span class="close" onclick="modalManager.closeModal('bulkDownloadMissingLorasModal')">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="confirmation-message" id="bulkDownloadMissingLorasMessage"></p>
|
|
<div class="bulk-download-loras-preview" id="bulkDownloadMissingLorasPreview">
|
|
<p class="preview-title">{{ t('modals.bulkDownloadMissingLoras.previewTitle') }}</p>
|
|
<ul class="bulk-download-loras-list" id="bulkDownloadMissingLorasList"></ul>
|
|
</div>
|
|
<p class="confirmation-note">
|
|
<i class="fas fa-info-circle"></i>
|
|
{{ t('modals.bulkDownloadMissingLoras.note') }}
|
|
</p>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="secondary-btn" onclick="modalManager.closeModal('bulkDownloadMissingLorasModal')">{{ t('common.actions.cancel') }}</button>
|
|
<button class="primary-btn" id="bulkDownloadMissingLorasConfirmBtn" onclick="bulkMissingLoraDownloadManager.confirmDownload()">
|
|
<i class="fas fa-download"></i>
|
|
{{ t('modals.bulkDownloadMissingLoras.downloadButton') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolve Filename Conflicts Confirmation Modal -->
|
|
<div id="resolveFilenameConflictsModal" class="modal delete-modal">
|
|
<div class="modal-content delete-modal-content">
|
|
<h2>{{ t('conflictConfirm.title') }}</h2>
|
|
<p class="confirmation-message">{{ t('conflictConfirm.message') }}</p>
|
|
<p class="resolve-conflicts-detail" id="resolveConflictsDetail"></p>
|
|
<div class="resolve-conflicts-impact" id="resolveConflictsImpact"></div>
|
|
<div class="modal-actions">
|
|
<button class="cancel-btn" onclick="modalManager.closeModal('resolveFilenameConflictsModal')">{{ t('common.actions.cancel') }}</button>
|
|
<button class="primary-btn" id="resolveConflictsConfirmBtn" onclick="doctorManager.confirmResolveConflicts()">
|
|
<i class="fas fa-check"></i>
|
|
{{ t('conflictConfirm.confirm') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recipe Rematch Options Modal -->
|
|
<div id="rematchOptionsModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>{{ t('modals.rematchOptions.title') }}</h2>
|
|
<span class="close" onclick="rematchModalManager.cancelOptions()">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="confirmation-message" id="rematchOptionsMessage"></p>
|
|
<label class="rematch-option-card" for="rematchOptionsRelaxed">
|
|
<input type="checkbox" id="rematchOptionsRelaxed">
|
|
<span class="rematch-option-checkmark" aria-hidden="true"></span>
|
|
<span class="rematch-option-text">
|
|
<span class="rematch-option-title">{{ t('modals.rematchOptions.relaxedLabel') }}</span>
|
|
<span class="rematch-option-caveat">
|
|
<i class="fas fa-info-circle" aria-hidden="true"></i>
|
|
{{ t('modals.rematchOptions.relaxedDescription') }}
|
|
</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="secondary-btn" onclick="rematchModalManager.cancelOptions()">{{ t('common.actions.cancel') }}</button>
|
|
<button class="primary-btn" id="rematchOptionsConfirmBtn" onclick="rematchModalManager.confirmOptions()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
{{ t('modals.rematchOptions.confirmButton') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|