# Copyright (c) 2026 DeepBeepMeep - part of WanGP, WanGP Community License 2.0 (see LICENSE.txt)
import gradio as gr
import os
import json
import html
from collections import defaultdict
from shared.utils.plugins import WAN2GPPlugin
from shared.utils import files_locator as fl
from shared import model_dropdowns
from mmgp import quant_router

VARIANT_LABEL_OTHER_NON_SHARED = "Other Non Shared"
VARIANT_LABEL_OTHER_SHARED = "Other Shared"
VARIANT_LABEL_MISSING = "Missing"
GLOBAL_SHARED_MODEL_TYPE = "__wgp_global_shared__"
GLOBAL_SHARED_LABEL = "WanGP global shared assets"


class modelsManagerPlugin(WAN2GPPlugin):
    def __init__(self):
        super().__init__()
        self._node_map = {}
        self._tree_data = []
        self._model_files = {}
        self._model_primary = {}
        self._model_variants = {}
        self._variant_to_model = {}
        self._model_base_label = {}
        self._model_family_label = {}
        self._model_display_label = {}
        self._model_direct_status_map = {}
        self._model_aggregated_status_map = {}
        self._model_usage_ids = {}
        self._usage_files = {}
        self._file_usage = {}
        self._file_usage_models = {}
        self._file_info = {}
        self._basename_index = {}
        self._stats_total = 0
        self._stats_by_drive = []
        self._errors = []
        self._repo_root = os.path.abspath(os.getcwd())
        self._expanded_nodes = set()
        self._current_tree_mode = "full"
        self._current_filter_text = ""
        self._last_rendered_nodes_count = 0

    def setup_ui(self):
        self.request_global("get_model_def")
        self.request_global("get_model_name")
        self.request_global("get_model_family")
        self.request_global("get_parent_model_type")
        self.request_global("get_base_model_type")
        self.request_global("get_model_handler")
        self.request_global("get_model_recursive_prop")
        self.request_global("get_model_filename")
        self.request_global("get_lora_dir")
        self.request_global("get_lora_local_path")
        self.request_global("compact_name")
        self.request_global("create_models_hierarchy")
        self.request_global("families_infos")
        self.request_global("models_def")
        self.request_global("server_config")
        self.request_global("transformer_quantization")
        self.request_global("transformer_dtype_policy")
        self.request_global("text_encoder_quantization")
        self.request_global("displayed_model_types")
        self.request_global("transformer_types")
        self.request_global("get_transformer_dtype")
        self.request_global("query_global_shared_model_files")

        self.add_custom_js(self._get_js())

        self.add_tab(
            tab_id="model_manager",
            label="Models Manager",
            component_constructor=self.create_model_ui,
        )

    def create_model_ui(self):
        with gr.Column():
            gr.Markdown("### Models Manager")
            gr.Markdown(
                "Sizes are unique GB recoverable on delete, plus shared GB used outside the branch."
            )
            with gr.Row():
                self.filter_input = gr.Textbox(
                    label="Filter Finetunes",
                    show_label=False,
                    placeholder="Type at least 3 characters from a model name",
                    lines=1,
                    scale=5,
                    visible=False,
                    elem_id="ckpt_filter_input",
                )
                self.filter_button = gr.Button("Filter", variant="primary", scale=1, visible=False, elem_id="ckpt_filter_btn", elem_classes="btn_centered")
                self.refresh_button = gr.Button(
                    "Refresh", variant="secondary", elem_id="ckpt_refresh_btn", scale=1, elem_classes="btn_centered"
                )
            with gr.Row():
                with gr.Column(scale=3, min_width=550):
                    self.tree_html = gr.HTML()
                with gr.Column(scale=2, min_width=350, elem_classes="ckpt-right-column"):
                    self.view_html = gr.HTML()

            with gr.Column(visible=False):
                self.action_input = gr.Textbox(elem_id="model_action_input")

        self.refresh_button.click(
            fn=self._show_loading,
            inputs=[],
            outputs=[self.tree_html, self.view_html, self.refresh_button, self.filter_input, self.filter_button],
            show_progress="hidden",
        ).then(
            fn=self._build_tree,
            inputs=[],
            outputs=[self.tree_html, self.view_html, self.refresh_button, self.filter_input, self.filter_button],
            show_progress="full",
        ).then(
            fn=lambda: gr.update(value=""),
            inputs=[],
            outputs=[self.filter_input],
            show_progress="hidden",
        )

        self.filter_button.click(
            fn=self._build_filtered_tree,
            inputs=[self.filter_input],
            outputs=[self.tree_html, self.view_html, self.refresh_button, self.filter_input, self.filter_button],
            show_progress="full",
        )

        self.action_input.change(
            fn=self._handle_action,
            inputs=[self.action_input],
            outputs=[self.tree_html, self.view_html, self.refresh_button, self.filter_input, self.filter_button],
            show_progress="hidden",
        )

        self.on_tab_outputs = [self.tree_html, self.view_html, self.refresh_button, self.filter_input, self.filter_button]
        return self.tree_html

    def on_tab_select(self, state: dict):
        return self._show_loading()

    def _get_js(self):
        return """
        function modelRoot() {
            if (window.gradioApp) {
                return window.gradioApp();
            }
            const app = document.querySelector('gradio-app');
            return app ? (app.shadowRoot || app) : document;
        }

        window.updatemodelInput = function(elem_id, value) {
            const root = modelRoot();
            const input = root.querySelector(`#${elem_id} textarea, #${elem_id} input`);
            if (input) {
                input.value = value;
                input.dispatchEvent(new Event('input', { bubbles: true }));
                return true;
            }
            return false;
        };

        function ensuremodelModal() {
            let backdrop = document.getElementById('ckpt-delete-modal');
            if (backdrop) return backdrop;
            backdrop = document.createElement('div');
            backdrop.id = 'ckpt-delete-modal';
            backdrop.className = 'ckpt-modal-backdrop';
            backdrop.innerHTML = `
                <div class="ckpt-modal">
                    <div class="ckpt-modal-title">Confirm Delete</div>
                    <div class="ckpt-modal-body" id="ckpt-delete-modal-body"></div>
                    <div class="ckpt-modal-actions">
                        <button class="ckpt-modal-btn" id="ckpt-delete-cancel">Cancel</button>
                        <button class="ckpt-modal-btn danger" id="ckpt-delete-unique">Delete Unique</button>
                        <button class="ckpt-modal-btn danger" id="ckpt-delete-all">Delete Unique + Shared</button>
                    </div>
                </div>
            `;
            document.body.appendChild(backdrop);
            const cancelBtn = backdrop.querySelector('#ckpt-delete-cancel');
            const uniqueBtn = backdrop.querySelector('#ckpt-delete-unique');
            const allBtn = backdrop.querySelector('#ckpt-delete-all');
            cancelBtn.addEventListener('click', () => {
                backdrop.style.display = 'none';
                backdrop.dataset.payloadUnique = '';
                backdrop.dataset.payloadAll = '';
            });
            uniqueBtn.addEventListener('click', () => {
                const payload = backdrop.dataset.payloadUnique || '';
                if (payload) {
                    window.updatemodelInput("model_action_input", payload);
                }
                backdrop.style.display = 'none';
                backdrop.dataset.payloadUnique = '';
                backdrop.dataset.payloadAll = '';
            });
            allBtn.addEventListener('click', () => {
                const payload = backdrop.dataset.payloadAll || '';
                if (payload) {
                    window.updatemodelInput("model_action_input", payload);
                }
                backdrop.style.display = 'none';
                backdrop.dataset.payloadUnique = '';
                backdrop.dataset.payloadAll = '';
            });
            backdrop.addEventListener('click', (evt) => {
                if (evt.target === backdrop) {
                    backdrop.style.display = 'none';
                    backdrop.dataset.payloadUnique = '';
                    backdrop.dataset.payloadAll = '';
                }
            });
            return backdrop;
        }

        function showmodelModal(message, payloadUnique, payloadAll, uniqueSize, totalSize) {
            const backdrop = ensuremodelModal();
            const body = backdrop.querySelector('#ckpt-delete-modal-body');
            const uniqueBtn = backdrop.querySelector('#ckpt-delete-unique');
            const allBtn = backdrop.querySelector('#ckpt-delete-all');
            body.textContent = message;
            backdrop.dataset.payloadUnique = payloadUnique || '';
            backdrop.dataset.payloadAll = payloadAll || '';
            uniqueBtn.textContent = uniqueSize ? `Delete Unique (${uniqueSize})` : 'Delete Unique';
            allBtn.textContent = totalSize ? `Delete Unique + Shared (${totalSize})` : 'Delete Unique + Shared';
            backdrop.style.display = 'flex';
        }

        window.handlemodelAction = function(button, action) {
            if (!button) return;
            const nodeId = button.dataset.nodeId;
            const nodeLabel = button.dataset.nodeLabel || nodeId;
            const uniqueSize = button.dataset.uniqueSize || "0 B";
            const totalSize = button.dataset.totalSize || uniqueSize;
            if (!nodeId) return;
            if (action === "delete") {
                const payloadUnique = JSON.stringify({
                    action: action,
                    node_id: nodeId,
                    delete_shared: false,
                    ts: Date.now()
                });
                const payloadAll = JSON.stringify({
                    action: action,
                    node_id: nodeId,
                    delete_shared: true,
                    ts: Date.now()
                });
                showmodelModal(
                    `Choose delete scope for "${nodeLabel}": unique only ${uniqueSize}, or unique + shared ${totalSize}.`,
                    payloadUnique,
                    payloadAll,
                    uniqueSize,
                    totalSize
                );
                return;
            }
            const payload = JSON.stringify({
                action: action,
                node_id: nodeId,
                ts: Date.now()
            });
            window.updatemodelInput("model_action_input", payload);
        };

        window.handlemodelRowClick = function(evt, row) {
            if (evt) {
                evt.stopPropagation();
                evt.preventDefault();
            }
            if (!row) return;
            const nodeId = row.dataset.nodeId;
            if (!nodeId) return;
            const payload = JSON.stringify({
                action: "view",
                node_id: nodeId,
                ts: Date.now()
            });
            window.updatemodelInput("model_action_input", payload);
        };

        window.handlemodelSummaryClick = function(evt, summary) {
            if (!summary) return;
            if (evt) {
                evt.stopPropagation();
                evt.preventDefault();
            }
            const details = summary.closest('details');
            if (!details) return;
            const nodeId = details.dataset.nodeId;
            if (!nodeId) return;
            const row = summary.querySelector('.ckpt-row');
            const nodeType = row ? (row.dataset.nodeType || '') : '';
            const payload = JSON.stringify({
                action: "toggle_node",
                node_id: nodeId,
                open: !details.open,
                view: nodeType === "model",
                ts: Date.now()
            });
            window.updatemodelInput("model_action_input", payload);
        };

        function ismodelTabActive() {
            const root = modelRoot();
            const tabs = root.querySelectorAll('button[role="tab"]');
            for (const tab of tabs) {
                if (tab.getAttribute('aria-selected') === 'true') {
                    const label = (tab.textContent || '').trim();
                    if (label === 'Models Manager') return true;
                }
            }
            return false;
        }

        function triggermodelRefresh() {
            const root = modelRoot();
            const btn = root.querySelector('#ckpt_refresh_btn button') || root.querySelector('#ckpt_refresh_btn');
            if (btn) btn.click();
        }

        function triggermodelFilter() {
            const root = modelRoot();
            const btn = root.querySelector('#ckpt_filter_btn button') || root.querySelector('#ckpt_filter_btn');
            if (btn) btn.click();
        }

        function setupmodelFilterEnter() {
            const root = modelRoot();
            const input = root.querySelector('#ckpt_filter_input textarea, #ckpt_filter_input input');
            if (!input || input.dataset.ckptEnterBound === '1') return;
            input.dataset.ckptEnterBound = '1';
            input.addEventListener('keydown', (evt) => {
                if (evt.key !== 'Enter') return;
                evt.preventDefault();
                evt.stopPropagation();
                triggermodelFilter();
            });
        }

        function applymodelSticky() {
            const root = modelRoot();
            const sticky = root.querySelector('.ckpt-view-sticky');
            if (!sticky) return;
            sticky.style.position = 'sticky';
            sticky.style.top = '16px';
            sticky.style.alignSelf = 'flex-start';
            let parent = sticky.parentElement;
            let steps = 0;
            while (parent && steps < 6) {
                const style = window.getComputedStyle(parent);
                if (style.overflow && style.overflow !== 'visible') {
                    parent.style.overflow = 'visible';
                }
                parent = parent.parentElement;
                steps += 1;
            }
            if (root && root.host && root.host.style) {
                const hostStyle = window.getComputedStyle(root.host);
                if (hostStyle.overflow && hostStyle.overflow !== 'visible') {
                    root.host.style.overflow = 'visible';
                }
            }
        }

        let ckptFloating = {
            sticky: null,
            scrollEl: null,
            handler: null,
            resizeHandler: null,
            anchorTop: 0,
            topPadding: 16,
        };

        function findmodelScrollContainer(element) {
            let current = element;
            while (current) {
                const style = window.getComputedStyle(current);
                const overflowY = style.overflowY;
                if ((overflowY === 'auto' || overflowY === 'scroll') && current.scrollHeight > current.clientHeight + 1) {
                    return current;
                }
                current = current.parentElement;
            }
            const root = modelRoot();
            const host = root && root.host ? root.host : null;
            current = host;
            while (current) {
                const style = window.getComputedStyle(current);
                const overflowY = style.overflowY;
                if ((overflowY === 'auto' || overflowY === 'scroll') && current.scrollHeight > current.clientHeight + 1) {
                    return current;
                }
                current = current.parentElement;
            }
            return window;
        }

        function computemodelAnchorTop(sticky, scrollEl) {
            if (scrollEl === window) {
                return sticky.getBoundingClientRect().top + window.scrollY;
            }
            const rect = sticky.getBoundingClientRect();
            const containerRect = scrollEl.getBoundingClientRect();
            return rect.top - containerRect.top + scrollEl.scrollTop;
        }

        function clearmodelFloating(sticky) {
            if (!sticky) return;
            sticky.style.transform = '';
            sticky.style.willChange = '';
        }

        function updatemodelFloating() {
            const sticky = ckptFloating.sticky;
            if (!sticky) return;
            const scrollEl = ckptFloating.scrollEl || window;
            const scrollTop = scrollEl === window ? window.scrollY : scrollEl.scrollTop;
            let maxTranslate = Infinity;
            if (scrollEl === window) {
                const doc = document.documentElement;
                maxTranslate = Math.max(
                    0,
                    (doc ? doc.scrollHeight : 0) - sticky.offsetHeight - ckptFloating.topPadding - ckptFloating.anchorTop
                );
            } else {
                maxTranslate = Math.max(
                    0,
                    scrollEl.scrollHeight - sticky.offsetHeight - ckptFloating.topPadding - ckptFloating.anchorTop
                );
            }
            const offset = scrollTop + ckptFloating.topPadding - ckptFloating.anchorTop;
            const translate = Math.max(0, Math.min(offset, maxTranslate));
            if (translate > 0) {
                sticky.style.transform = `translateY(${translate}px)`;
                sticky.style.willChange = 'transform';
            } else {
                clearmodelFloating(sticky);
            }
        }

        function teardownmodelFloating() {
            if (ckptFloating.scrollEl && ckptFloating.handler) {
                if (ckptFloating.scrollEl === window) {
                    window.removeEventListener('scroll', ckptFloating.handler);
                } else {
                    ckptFloating.scrollEl.removeEventListener('scroll', ckptFloating.handler);
                }
            }
            if (ckptFloating.resizeHandler) {
                window.removeEventListener('resize', ckptFloating.resizeHandler);
            }
            clearmodelFloating(ckptFloating.sticky);
            if (ckptFloating.sticky) {
                ckptFloating.sticky.style.position = '';
                ckptFloating.sticky.style.top = '';
            }
            ckptFloating.sticky = null;
            ckptFloating.scrollEl = null;
            ckptFloating.handler = null;
            ckptFloating.resizeHandler = null;
            ckptFloating.anchorTop = 0;
        }

        function setupmodelFloating() {
            const root = modelRoot();
            const sticky = root.querySelector('.ckpt-view-sticky');
            if (!sticky) return;
            if (ckptFloating.sticky === sticky) {
                updatemodelFloating();
                return;
            }
            teardownmodelFloating();
            ckptFloating.sticky = sticky;
            sticky.style.position = 'relative';
            sticky.style.top = '0px';
            ckptFloating.scrollEl = findmodelScrollContainer(sticky);
            ckptFloating.anchorTop = computemodelAnchorTop(sticky, ckptFloating.scrollEl);
            ckptFloating.handler = () => updatemodelFloating();
            if (ckptFloating.scrollEl === window) {
                window.addEventListener('scroll', ckptFloating.handler, { passive: true });
            } else {
                ckptFloating.scrollEl.addEventListener('scroll', ckptFloating.handler, { passive: true });
            }
            ckptFloating.resizeHandler = () => {
                ckptFloating.anchorTop = computemodelAnchorTop(sticky, ckptFloating.scrollEl);
                updatemodelFloating();
            };
            window.addEventListener('resize', ckptFloating.resizeHandler);
            updatemodelFloating();
        }

        function observemodelView() {
            const root = modelRoot();
            const column = root.querySelector('.ckpt-right-column');
            if (!column || column.dataset.ckptObserved === '1') return;
            column.dataset.ckptObserved = '1';
            const observer = new MutationObserver(() => {
                applymodelSticky();
                setupmodelFloating();
                setupmodelFilterEnter();
            });
            observer.observe(column, { childList: true, subtree: true });
        }

        let ckptTabWasActive = false;
        let ckptRefreshBusy = false;
        let ckptRefreshStartedAt = 0;

        function waitForRefreshReady() {
            const root = modelRoot();
            const treeRoot = root.querySelector('#ckpt_tree_root');
            if (treeRoot && treeRoot.dataset.status === 'ready') {
                ckptRefreshBusy = false;
                applymodelSticky();
                setupmodelFloating();
                setupmodelFilterEnter();
                return;
            }
            if (ckptRefreshStartedAt && Date.now() - ckptRefreshStartedAt > 60000) {
                ckptRefreshBusy = false;
                applymodelSticky();
                setupmodelFloating();
                setupmodelFilterEnter();
                return;
            }
            setTimeout(waitForRefreshReady, 400);
        }

        function startmodelBuild() {
            if (ckptRefreshBusy) return;
            ckptRefreshBusy = true;
            ckptRefreshStartedAt = Date.now();
            triggermodelRefresh();
            setTimeout(waitForRefreshReady, 200);
        }

        function handleTabChange() {
            const active = ismodelTabActive();
            if (active && !ckptTabWasActive) {
                startmodelBuild();
            }
            if (active) {
                observemodelView();
                setTimeout(applymodelSticky, 200);
                setTimeout(setupmodelFloating, 260);
                setTimeout(setupmodelFilterEnter, 280);
            }
            ckptTabWasActive = active;
        }

        function initmodelBridge() {
            const root = modelRoot();
            const tabs = root.querySelectorAll('button[role="tab"]');
            if (!tabs || tabs.length === 0) {
                setTimeout(initmodelBridge, 400);
                return;
            }
            const tabObserver = new MutationObserver(handleTabChange);
            tabObserver.observe(root, { attributes: true, subtree: true, attributeFilter: ['aria-selected'] });
            handleTabChange();
        }

        initmodelBridge();
        """

    def _show_loading(self):
        tree_loading = self._build_loading_html()
        view_hidden = gr.update(value="", visible=False)
        refresh_hidden = gr.update(visible=False)
        filter_input_hidden = gr.update(visible=False)
        filter_button_hidden = gr.update(visible=False)
        return tree_loading, view_hidden, refresh_hidden, filter_input_hidden, filter_button_hidden

    def _build_tree(self):
        self._build_cache()
        self._expanded_nodes = set()
        self._current_tree_mode = "full"
        self._current_filter_text = ""
        tree_html = self._build_tree_html()
        view_html = self._build_empty_view_html()
        view_visible = gr.update(value=view_html, visible=True)
        refresh_visible = gr.update(visible=True)
        filter_input_visible = gr.update(visible=True)
        filter_button_visible = gr.update(visible=True)
        return tree_html, view_visible, refresh_visible, filter_input_visible, filter_button_visible

    def _build_filtered_tree(self, filter_text):
        text = str(filter_text or "").strip()
        if len(text) < 3:
            gr.Info("Please enter at least 3 characters to filter finetunes.")
            return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()

        self._build_cache()
        self._expanded_nodes = set()
        self._current_tree_mode = "filtered"
        self._current_filter_text = text
        tree_html = self._build_filtered_tree_html(text)
        view_html = self._build_empty_view_html()
        view_visible = gr.update(value=view_html, visible=True)
        refresh_visible = gr.update(visible=True)
        filter_input_visible = gr.update(visible=True)
        filter_button_visible = gr.update(visible=True)
        return tree_html, view_visible, refresh_visible, filter_input_visible, filter_button_visible

    def _handle_action(self, payload: str):
        if not payload:
            return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
        try:
            data = json.loads(payload)
        except json.JSONDecodeError:
            return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()

        action = data.get("action")
        node_id = data.get("node_id")
        if not action or not node_id:
            return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()

        if not self._node_map:
            self._build_cache()

        if action == "view":
            view_html = self._build_view_html(node_id)
            return gr.update(), gr.update(value=view_html, visible=True), gr.update(), gr.update(), gr.update()

        if action == "toggle_node":
            if node_id not in self._node_map:
                return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
            if data.get("open"):
                self._expanded_nodes.add(node_id)
            else:
                self._expanded_nodes.discard(node_id)
            if self._current_tree_mode == "filtered" and len(self._current_filter_text) >= 3:
                tree_html = self._build_filtered_tree_html(self._current_filter_text)
            else:
                tree_html = self._build_tree_html()
            if data.get("view"):
                view_html = self._build_view_html(node_id)
                return (
                    gr.update(value=tree_html),
                    gr.update(value=view_html, visible=True),
                    gr.update(),
                    gr.update(),
                    gr.update(),
                )
            return gr.update(value=tree_html), gr.update(), gr.update(), gr.update(), gr.update()

        if action == "delete":
            delete_shared = bool(data.get("delete_shared"))
            removed, missing, errors = self._delete_files_for_node(
                node_id, delete_shared=delete_shared
            )
            if errors:
                gr.Warning(f"Delete completed with errors: {len(errors)}")
            else:
                gr.Info(f"Deleted {len(removed)} files (missing: {len(missing)})")
            return self._build_tree()

        return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()

    def _delete_files_for_node(self, node_id, delete_shared=False):
        node = self._node_map.get(node_id)
        if node is None:
            return [], [], []
        removed = []
        missing = []
        errors = []
        if delete_shared:
            target_files = node.get("files", set())
        else:
            target_files = node.get("unique_files", set())
        for path in sorted(target_files):
            if not os.path.isfile(path):
                missing.append(path)
                continue
            try:
                os.remove(path)
                removed.append(path)
            except OSError as exc:
                errors.append((path, str(exc)))
        return removed, missing, errors

    def _build_cache(self):
        self._node_map = {}
        self._tree_data = []
        self._model_files = {}
        self._model_primary = {}
        self._model_variants = {}
        self._variant_to_model = {}
        self._model_base_label = {}
        self._model_family_label = {}
        self._model_display_label = {}
        self._model_direct_status_map = {}
        self._model_aggregated_status_map = {}
        self._model_usage_ids = {}
        self._usage_files = {}
        self._file_usage = defaultdict(set)
        self._file_usage_models = defaultdict(set)
        self._file_info = {}
        self._basename_index = {}
        self._errors = []
        self._stats_total = 0
        self._stats_by_drive = []

        model_types = self._get_model_types()
        self._model_direct_status_map, self._model_aggregated_status_map = self._compute_model_status_maps(model_types)
        for model_type in model_types:
            try:
                variant_info = self._collect_model_variants(model_type)
            except Exception as exc:
                self._errors.append(f"{model_type}: {exc}")
                variant_info = {"variants": [], "primary_paths": [], "files": set()}
            variants = variant_info.get("variants", [])
            model_files = variant_info.get("files", set())
            self._model_variants[model_type] = variants
            self._model_primary[model_type] = variant_info.get("primary_paths", [])
            self._model_files[model_type] = model_files
            for path in model_files:
                self._file_usage_models[path].add(model_type)

        self._mark_global_shared_files()
        self._rebalance_other_shared_variants(model_types)

        self._usage_files = {}
        self._file_usage = defaultdict(set)
        self._variant_to_model = {}
        self._model_usage_ids = {}
        for model_type in model_types:
            usage_ids = set()
            for variant in self._model_variants.get(model_type, []):
                variant_id = variant["id"]
                usage_ids.add(variant_id)
                self._variant_to_model[variant_id] = model_type
                files = variant.get("files", set())
                self._usage_files[variant_id] = files
                for path in files:
                    self._file_usage[path].add(variant_id)
            self._model_usage_ids[model_type] = usage_ids

        for path in self._file_usage_models.keys():
            try:
                exists = os.path.isfile(path)
                size = os.path.getsize(path) if exists else 0
            except OSError:
                exists = False
                size = 0
            self._file_info[path] = {"size": size, "exists": exists}

        self._compute_global_stats()
        self._build_tree_structure(model_types)

    def _build_dropdown_deps(self, model_types):
        if not model_types:
            return None
        fallback_type = model_types[0]
        return model_dropdowns.DropdownDeps(
            transformer_types=list(model_types),
            displayed_model_types=list(model_types),
            transformer_type=fallback_type,
            three_levels_hierarchy=True,
            families_infos=self.families_infos,
            server_config=self.server_config,
            transformer_quantization=self.transformer_quantization,
            transformer_dtype_policy=self.transformer_dtype_policy,
            text_encoder_quantization=self.text_encoder_quantization,
            get_model_def=self.get_model_def,
            get_model_recursive_prop=self.get_model_recursive_prop,
            get_model_filename=self.get_model_filename,
            get_local_model_filename=self._get_local_model_filename,
            get_lora_dir=self.get_lora_dir,
            get_lora_local_path=self.get_lora_local_path,
            get_parent_model_type=self.get_parent_model_type,
            get_base_model_type=self.get_base_model_type,
            get_model_family=self.get_model_family,
            get_model_name=self.get_model_name,
            get_transformer_dtype=self.get_transformer_dtype,
        )

    def _resolve_expected_entry_path(self, entry, model_type=None):
        if not isinstance(entry, dict):
            return None
        local_path = entry.get("path", None)
        if isinstance(local_path, str) and len(local_path) > 0:
            return self._normalize_path(local_path)
        filename = entry.get("filename", "")
        extra_paths = entry.get("extra_paths", None)
        return self._resolve_path(filename, force_folder=extra_paths, lora_dir=self._safe_get_lora_dir(model_type))

    def _collect_expected_missing_files(self, model_type):
        deps = self._build_dropdown_deps([model_type])
        if deps is None:
            return set()
        missing_paths = set()
        try:
            expected_entries = []
            expected_entries.extend(model_dropdowns.get_expected_core_file_entries_for_status(deps, model_type))
            expected_entries.extend(model_dropdowns.get_expected_secondary_file_entries_for_status(deps, model_type))
        except Exception as exc:
            self._errors.append(f"Missing file check failed for {model_type}: {exc}")
            return set()

        model_def = self.get_model_def(model_type)
        if isinstance(model_def, dict):
            try:
                for resolved_path in self._collect_handler_file_paths(model_type, model_def):
                    if resolved_path and not os.path.isfile(resolved_path):
                        missing_paths.add(resolved_path)
            except Exception as exc:
                self._errors.append(f"Missing handler file check failed for {model_type}: {exc}")

        for entry in expected_entries:
            resolved_path = self._resolve_expected_entry_path(entry, model_type=model_type)
            if resolved_path and not os.path.isfile(resolved_path):
                missing_paths.add(resolved_path)
        return missing_paths

    def _compute_model_status_maps(self, model_types):
        if not model_types:
            return {}, {}
        try:
            deps = self._build_dropdown_deps(model_types)
            if deps is None:
                return {}, {}
            direct_status_map, aggregated_parent_status_map = model_dropdowns.get_model_download_status_maps(
                deps, dropdown_types=list(model_types)
            )
            return direct_status_map, aggregated_parent_status_map
        except Exception as exc:
            self._errors.append(f"Model status map failed: {exc}")
            return {}, {}

    def _decorate_status_label(self, label, status):
        return model_dropdowns.decorate_model_dropdown_label(label, status)

    def _compute_global_stats(self):
        total = 0
        by_drive = defaultdict(int)
        for path, info in self._file_info.items():
            size = info.get("size", 0)
            if size <= 0:
                continue
            total += size
            drive = self._get_drive_label(path)
            by_drive[drive] += size
        self._stats_total = total
        self._stats_by_drive = sorted(
            by_drive.items(), key=lambda item: (-item[1], item[0])
        )

    def _get_drive_label(self, path):
        drive, _ = os.path.splitdrive(path)
        if drive:
            return drive.rstrip(":").upper()
        if path.startswith("\\\\"):
            return "UNC"
        return "ROOT"

    def _get_model_types(self):
        types = []
        if isinstance(getattr(self, "models_def", None), dict) and self.models_def:
            types = sorted(self.models_def.keys())
        elif isinstance(self.transformer_types, list) and self.transformer_types:
            types = model_dropdowns.expand_model_selection(self.transformer_types, self.displayed_model_types, self.get_parent_model_type, self.get_model_family)
        elif isinstance(self.displayed_model_types, list):
            types = list(self.displayed_model_types)
        seen = set()
        ordered = []
        for model_type in types:
            if model_type and model_type not in seen:
                ordered.append(model_type)
                seen.add(model_type)
        return ordered

    def _collect_model_variants(self, model_type):
        model_def = self.get_model_def(model_type)
        if model_def is None:
            return {"variants": [], "primary_paths": [], "files": set()}

        default_dtype_policy = self._resolve_default_dtype_policy(model_type, model_def)
        transformer_variants = self._collect_transformer_variants(
            model_type, model_def, default_dtype_policy
        )
        module_variants = self._collect_module_variants(
            model_type, model_def, default_dtype_policy
        )
        text_encoder_variants = self._collect_text_encoder_variants(
            model_type, model_def, default_dtype_policy
        )
        lora_variants = self._collect_lora_variants(model_type)
        transformer_files = set()
        module_files = set()
        text_encoder_files = set()
        lora_files = set()
        model_primary_paths = []
        for variant in transformer_variants:
            transformer_files.update(variant["files"])
            model_primary_paths.extend(variant.get("primary_paths", []))
        for variant in module_variants:
            module_files.update(variant["files"])
            model_primary_paths.extend(variant.get("primary_paths", []))
        for variant in text_encoder_variants:
            text_encoder_files.update(variant["files"])
            model_primary_paths.extend(variant.get("primary_paths", []))
        for variant in lora_variants:
            lora_files.update(variant["files"])
            model_primary_paths.extend(variant.get("primary_paths", []))
        model_primary_paths = self._dedupe_paths(model_primary_paths)

        other_files, shared_files = self._collect_other_files(
            model_type,
            model_def,
            transformer_files.union(module_files),
            text_encoder_files,
            lora_files,
        )
        variants = list(transformer_variants) + list(module_variants) + list(text_encoder_variants) + list(lora_variants)
        if other_files:
            other_id = self._make_variant_id(model_type, "other", "other")
            variants.append(
                {
                    "id": other_id,
                    "label": VARIANT_LABEL_OTHER_NON_SHARED,
                    "type": "variant",
                    "files": other_files,
                    "primary_paths": [],
                }
            )
        if shared_files:
            shared_id = self._make_variant_id(model_type, "shared", "shared")
            variants.append(
                {
                    "id": shared_id,
                    "label": VARIANT_LABEL_OTHER_SHARED,
                    "type": "variant",
                    "files": shared_files,
                    "primary_paths": [],
                }
            )

        model_files = set()
        for variant in variants:
            model_files.update(variant.get("files", set()))
        missing_files = self._collect_expected_missing_files(model_type)
        if missing_files:
            missing_id = self._make_variant_id(model_type, "missing", "missing")
            variants.append(
                {
                    "id": missing_id,
                    "label": VARIANT_LABEL_MISSING,
                    "type": "missing",
                    "files": set(),
                    "missing_files": missing_files,
                    "primary_paths": [],
                }
            )

        return {
            "variants": variants,
            "primary_paths": model_primary_paths,
            "files": model_files,
        }

    def _collect_transformer_variants(
        self, model_type, model_def, default_dtype_policy
    ):
        choice_lists, modules = self._collect_transformer_choice_lists(model_type, model_def)
        base_dtypes, token_dtypes, token_labels = self._collect_variant_candidates(
            choice_lists, default_dtype_policy
        )
        variants = []
        used_labels = set()

        base_default = self._format_dtype_label(default_dtype_policy)
        if base_dtypes:
            for dtype_policy in sorted(base_dtypes):
                label = self._format_variant_label("", dtype_policy, base_default)
                files, primary_paths = self._collect_transformer_files_for_variant(
                    model_type, model_def, modules, "", dtype_policy
                )
                if not files:
                    continue
                file_label = self._detect_quant_label_from_files(files)
                label = file_label or label
                variant_label = f"Transformer {label}"
                variant_id = self._make_variant_id(model_type, "transformer", label)
                variants.append(
                    {
                        "id": variant_id,
                        "label": variant_label,
                        "type": "variant",
                        "files": files,
                        "primary_paths": primary_paths,
                    }
                )
                used_labels.add(variant_label)

        for token, dtypes in sorted(token_dtypes.items()):
            quant_param = self._quant_param_from_token(token)
            quant_label = token_labels.get(token) or self._format_quant_label(token)
            for dtype_policy in sorted(dtypes):
                label_suffix = ""
                if len(dtypes) > 1:
                    label_suffix = f" {self._format_dtype_label(dtype_policy)}"
                label = f"{quant_label}{label_suffix}"
                files, primary_paths = self._collect_transformer_files_for_variant(
                    model_type,
                    model_def,
                    modules,
                    quant_param,
                    dtype_policy,
                    token_match=token,
                )
                if not files:
                    continue
                file_label = self._detect_quant_label_from_files(files)
                quant_label = file_label or quant_label
                if not self._files_match_token(files, token):
                    continue
                label = f"{quant_label}{label_suffix}" if quant_label else self._format_dtype_label(dtype_policy)
                variant_label = f"Transformer {label}"
                if variant_label in used_labels:
                    continue
                variant_id = self._make_variant_id(model_type, "transformer", label)
                variants.append(
                    {
                        "id": variant_id,
                        "label": variant_label,
                        "type": "variant",
                        "files": files,
                        "primary_paths": primary_paths,
                    }
                )
                used_labels.add(variant_label)

        return sorted(variants, key=lambda v: v["label"].lower())

    def _collect_module_variants(self, model_type, model_def, default_dtype_policy):
        module_entries = self._expand_modules_with_ids(model_type)
        if not module_entries:
            return []

        variants = []
        used_labels = set()
        base_default = self._format_dtype_label(default_dtype_policy)
        for idx, entry in enumerate(module_entries):
            module = entry.get("module")
            module_id = entry.get("module_id", "")
            module_name = self._get_module_display_name(model_type, module, module_id, idx)
            choice_lists = self._collect_module_choice_lists(module)
            base_dtypes, token_dtypes, token_labels = self._collect_variant_candidates(
                choice_lists, default_dtype_policy
            )

            if base_dtypes:
                for dtype_policy in sorted(base_dtypes):
                    label = self._format_variant_label("", dtype_policy, base_default)
                    files = self._collect_module_files_for_variant(
                        model_type,
                        module,
                        "",
                        dtype_policy,
                    )
                    if not files:
                        continue
                    file_label = self._detect_quant_label_from_files(files)
                    label = file_label or label
                    variant_label = f"Module {module_name} {label}".strip()
                    if variant_label in used_labels:
                        continue
                    variant_id = self._make_variant_id(model_type, "module", f"{module_name} {label}")
                    variants.append(
                        {
                            "id": variant_id,
                            "label": variant_label,
                            "type": "variant",
                            "files": files,
                            "primary_paths": [],
                        }
                    )
                    used_labels.add(variant_label)

            for token, dtypes in sorted(token_dtypes.items()):
                quant_param = self._quant_param_from_token(token)
                quant_label = token_labels.get(token) or self._format_quant_label(token)
                for dtype_policy in sorted(dtypes):
                    label_suffix = ""
                    if len(dtypes) > 1:
                        label_suffix = f" {self._format_dtype_label(dtype_policy)}"
                    files = self._collect_module_files_for_variant(
                        model_type,
                        module,
                        quant_param,
                        dtype_policy,
                        token_match=token,
                    )
                    if not files:
                        continue
                    if not self._files_match_token(files, token):
                        continue
                    file_label = self._detect_quant_label_from_files(files)
                    effective_quant_label = file_label or quant_label
                    label = f"{effective_quant_label}{label_suffix}" if effective_quant_label else self._format_dtype_label(dtype_policy)
                    variant_label = f"Module {module_name} {label}".strip()
                    if variant_label in used_labels:
                        continue
                    variant_id = self._make_variant_id(model_type, "module", f"{module_name} {label}")
                    variants.append(
                        {
                            "id": variant_id,
                            "label": variant_label,
                            "type": "variant",
                            "files": files,
                            "primary_paths": [],
                        }
                    )
                    used_labels.add(variant_label)

        return sorted(variants, key=lambda v: v["label"].lower())

    def _get_module_display_name(self, model_type, module, module_id, module_index):
        if isinstance(module_id, str) and module_id:
            return module_id
        if isinstance(module, str):
            module_type, _ = self._split_module_ref(module)
            return module_type
        return model_type

    def _collect_module_choice_lists(self, module):
        choice_lists = []
        if isinstance(module, dict):
            urls1 = module.get("URLs", [])
            urls2 = module.get("URLs2", [])
            if urls1:
                choice_lists.append(urls1)
            if urls2:
                choice_lists.append(urls2)
        elif isinstance(module, list):
            if module:
                choice_lists.append(module)
        else:
            module_type = module
            sub_prop_name = "_list"
            if isinstance(module_type, str) and "#" in module_type:
                pos = module_type.rfind("#")
                sub_prop_name = module_type[pos + 1 :]
                module_type = module_type[:pos]
            try:
                mod_urls = self.get_model_recursive_prop(
                    module_type, "modules", sub_prop_name=sub_prop_name, return_list=True
                )
            except Exception:
                mod_urls = []
            if isinstance(mod_urls, list) and mod_urls:
                if all(isinstance(item, list) for item in mod_urls):
                    for entry in mod_urls:
                        if entry:
                            choice_lists.append(entry)
                else:
                    choice_lists.append(mod_urls)
        return choice_lists

    def _collect_module_files_for_variant(
        self,
        model_type,
        module,
        quantization,
        dtype_policy,
        token_match=None,
    ):
        files = set()
        is_exotic = quantization not in ("", "int8", "fp8")
        if isinstance(module, dict):
            for key in ("URLs", "URLs2"):
                urls = module.get(key, [])
                if not urls:
                    continue
                filename = ""
                if token_match and is_exotic:
                    filename = self._select_filename_by_token(urls, token_match)
                if not filename:
                    filename = self.get_model_filename(
                        model_type=model_type,
                        quantization=quantization,
                        dtype_policy=dtype_policy,
                        URLs=urls,
                    )
                if filename:
                    self._add_file(files, filename)
        elif isinstance(module, list):
            filename = ""
            if token_match and is_exotic:
                filename = self._select_filename_by_token(module, token_match)
            if not filename:
                filename = self.get_model_filename(
                    model_type=model_type,
                    quantization=quantization,
                    dtype_policy=dtype_policy,
                    URLs=module,
                )
            if filename:
                self._add_file(files, filename)
        else:
            filename = ""
            if token_match and is_exotic:
                module_type = module
                sub_prop_name = "_list"
                if isinstance(module_type, str) and "#" in module_type:
                    pos = module_type.rfind("#")
                    sub_prop_name = module_type[pos + 1 :]
                    module_type = module_type[:pos]
                try:
                    mod_urls = self.get_model_recursive_prop(
                        module_type,
                        "modules",
                        sub_prop_name=sub_prop_name,
                        return_list=True,
                    )
                except Exception:
                    mod_urls = []
                filename = self._select_filename_by_token(mod_urls, token_match)
            if not filename:
                try:
                    filename = self.get_model_filename(
                        model_type=model_type,
                        quantization=quantization,
                        dtype_policy=dtype_policy,
                        module_type=module,
                    )
                except Exception:
                    filename = ""
            if filename:
                self._add_file(files, filename)
        return files

    def _collect_transformer_choice_lists(self, model_type, model_def):
        choice_lists = []
        modules = self._expand_modules(model_type)

        urls = self.get_model_recursive_prop(model_type, "URLs", return_list=True)
        if urls:
            choice_lists.append(urls)
        if "URLs2" in model_def:
            urls2 = self.get_model_recursive_prop(model_type, "URLs2", return_list=True)
            if urls2:
                choice_lists.append(urls2)

        has_module_source = "module_source" in model_def
        has_module_source2 = "module_source2" in model_def
        for module in modules:
            if isinstance(module, dict):
                urls1 = module.get("URLs", [])
                urls2 = module.get("URLs2", [])
                if urls1 and not has_module_source:
                    choice_lists.append(urls1)
                if urls2 and not has_module_source2:
                    choice_lists.append(urls2)
            elif isinstance(module, list):
                if has_module_source:
                    continue
                if module:
                    choice_lists.append(module)
            else:
                if has_module_source:
                    continue
                module_type = module
                sub_prop_name = "_list"
                if isinstance(module_type, str) and "#" in module_type:
                    pos = module_type.rfind("#")
                    sub_prop_name = module_type[pos + 1 :]
                    module_type = module_type[:pos]
                try:
                    mod_urls = self.get_model_recursive_prop(
                        module_type, "modules", sub_prop_name=sub_prop_name, return_list=True
                    )
                except Exception:
                    mod_urls = []
                if isinstance(mod_urls, list) and mod_urls:
                    if all(isinstance(item, list) for item in mod_urls):
                        for entry in mod_urls:
                            if entry:
                                choice_lists.append(entry)
                    else:
                        choice_lists.append(mod_urls)
        return choice_lists, modules

    def _collect_transformer_files_for_variant(
        self,
        model_type,
        model_def,
        modules,
        quantization,
        dtype_policy,
        token_match=None,
    ):
        files = set()
        primary_paths = []

        model_filename = ""
        is_exotic = quantization not in ("", "int8", "fp8")
        if token_match and is_exotic:
            urls = self.get_model_recursive_prop(model_type, "URLs", return_list=True)
            model_filename = self._select_filename_by_token(urls, token_match)
        if not model_filename:
            model_filename = self.get_model_filename(
                model_type=model_type,
                quantization=quantization,
                dtype_policy=dtype_policy,
            )
        if model_filename:
            self._add_file(files, model_filename)
            primary_path = self._resolve_path(model_filename)
            if primary_path and primary_path in files:
                primary_paths.append(primary_path)

        if "URLs2" in model_def:
            model_filename2 = ""
            if token_match and is_exotic:
                urls2 = self.get_model_recursive_prop(
                    model_type, "URLs2", return_list=True
                )
                model_filename2 = self._select_filename_by_token(urls2, token_match)
            if not model_filename2:
                model_filename2 = self.get_model_filename(
                    model_type=model_type,
                    quantization=quantization,
                    dtype_policy=dtype_policy,
                    submodel_no=2,
                )
            if model_filename2:
                self._add_file(files, model_filename2)
                primary_path2 = self._resolve_path(model_filename2)
                if primary_path2 and primary_path2 in files:
                    primary_paths.append(primary_path2)

        has_module_source = "module_source" in model_def
        has_module_source2 = "module_source2" in model_def
        for module in modules:
            if isinstance(module, dict):
                urls1 = module.get("URLs", [])
                urls2 = module.get("URLs2", [])
                if urls1 and not has_module_source:
                    filename = ""
                    if token_match and is_exotic:
                        filename = self._select_filename_by_token(urls1, token_match)
                    if not filename:
                        filename = self.get_model_filename(
                            model_type=model_type,
                            quantization=quantization,
                            dtype_policy=dtype_policy,
                            URLs=urls1,
                        )
                    if filename:
                        self._add_file(files, filename)
                if urls2 and not has_module_source2:
                    filename = ""
                    if token_match and is_exotic:
                        filename = self._select_filename_by_token(urls2, token_match)
                    if not filename:
                        filename = self.get_model_filename(
                            model_type=model_type,
                            quantization=quantization,
                            dtype_policy=dtype_policy,
                            URLs=urls2,
                        )
                    if filename:
                        self._add_file(files, filename)
            elif isinstance(module, list):
                if has_module_source:
                    continue
                filename = ""
                if token_match and is_exotic:
                    filename = self._select_filename_by_token(module, token_match)
                if not filename:
                    filename = self.get_model_filename(
                        model_type=model_type,
                        quantization=quantization,
                        dtype_policy=dtype_policy,
                        URLs=module,
                    )
                if filename:
                    self._add_file(files, filename)
            else:
                if has_module_source:
                    continue
                if token_match and is_exotic:
                    module_type = module
                    sub_prop_name = "_list"
                    if isinstance(module_type, str) and "#" in module_type:
                        pos = module_type.rfind("#")
                        sub_prop_name = module_type[pos + 1 :]
                        module_type = module_type[:pos]
                    try:
                        mod_urls = self.get_model_recursive_prop(
                            module_type,
                            "modules",
                            sub_prop_name=sub_prop_name,
                            return_list=True,
                        )
                    except Exception:
                        mod_urls = []
                    filename = self._select_filename_by_token(mod_urls, token_match)
                else:
                    filename = ""
                try:
                    if not filename:
                        filename = self.get_model_filename(
                            model_type=model_type,
                            quantization=quantization,
                            dtype_policy=dtype_policy,
                            module_type=module,
                        )
                except Exception:
                    filename = ""
                if filename:
                    self._add_file(files, filename)

        return files, primary_paths

    def _collect_text_encoder_variants(
        self, model_type, model_def, default_dtype_policy
    ):
        variants = []
        text_encoder_urls = self.get_model_recursive_prop(
            model_type, "text_encoder_URLs", return_list=True
        )
        text_encoder_folder = model_def.get("text_encoder_folder", None)
        used_paths = set()
        used_labels = set()
        base_default = self._format_dtype_label(default_dtype_policy)
        if text_encoder_urls:
            base_dtypes, token_dtypes, token_labels = self._collect_variant_candidates(
                [text_encoder_urls], default_dtype_policy
            )
            if base_dtypes:
                for dtype_policy in sorted(base_dtypes):
                    label = self._format_variant_label("", dtype_policy, base_default)
                    filename = self.get_model_filename(
                        model_type=model_type,
                        quantization="",
                        dtype_policy=dtype_policy,
                        URLs=text_encoder_urls,
                    )
                    if not filename:
                        continue
                    files = set()
                    self._add_file(
                        files, filename, force_folder=text_encoder_folder
                    )
                    if not files:
                        continue
                    resolved_path = next(iter(files))
                    if resolved_path in used_paths:
                        continue
                    used_paths.add(resolved_path)
                    variant_label = f"Text Encoder {label}"
                    if variant_label in used_labels:
                        continue
                    variant_id = self._make_variant_id(model_type, "text", label)
                    variants.append(
                        {
                            "id": variant_id,
                            "label": variant_label,
                            "type": "variant",
                            "files": files,
                            "primary_paths": [resolved_path],
                        }
                    )
                    used_labels.add(variant_label)

            for token, dtypes in sorted(token_dtypes.items()):
                quant_param = self._quant_param_from_token(token)
                quant_label = token_labels.get(token) or self._format_quant_label(token)
                for dtype_policy in sorted(dtypes):
                    label_suffix = ""
                    if len(dtypes) > 1:
                        label_suffix = f" {self._format_dtype_label(dtype_policy)}"
                    label = f"{quant_label}{label_suffix}"
                    filename = ""
                    if quant_param not in ("", "int8", "fp8"):
                        filename = self._select_filename_by_token(text_encoder_urls, token)
                    if not filename:
                        filename = self.get_model_filename(
                            model_type=model_type,
                            quantization=quant_param,
                            dtype_policy=dtype_policy,
                            URLs=text_encoder_urls,
                        )
                    if not filename:
                        continue
                    files = set()
                    self._add_file(
                        files, filename, force_folder=text_encoder_folder
                    )
                    if not files:
                        continue
                    if not self._files_match_token(files, token):
                        continue
                    file_label = self._detect_quant_label_from_files(files)
                    quant_label = file_label or quant_label
                    resolved_path = next(iter(files))
                    if resolved_path in used_paths:
                        continue
                    used_paths.add(resolved_path)
                    label = f"{quant_label}{label_suffix}" if quant_label else self._format_dtype_label(dtype_policy)
                    variant_label = f"Text Encoder {label}"
                    if variant_label in used_labels:
                        continue
                    variant_id = self._make_variant_id(model_type, "text", label)
                    variants.append(
                        {
                            "id": variant_id,
                            "label": variant_label,
                            "type": "variant",
                            "files": files,
                            "primary_paths": [resolved_path],
                        }
                    )
                    used_labels.add(variant_label)
        else:
            handler = self.get_model_handler(model_type)
            get_name = getattr(handler, "get_text_encoder_filename", None)
            if get_name is not None:
                for quant_param in self._text_encoder_quant_candidates():
                    try:
                        filename = get_name(quant_param)
                    except Exception:
                        filename = None
                    if not filename:
                        continue
                    files = set()
                    self._add_file(
                        files, filename, force_folder=text_encoder_folder
                    )
                    if not files:
                        continue
                    resolved_path = next(iter(files))
                    if resolved_path in used_paths:
                        continue
                    used_paths.add(resolved_path)
                    quant_label = self._detect_quant_label_from_files(files)
                    label = quant_label or self._format_variant_label(
                        "",
                        self._detect_dtype_policy_from_name(os.path.basename(filename))
                        or default_dtype_policy,
                        base_default,
                    )
                    variant_label = f"Text Encoder {label}"
                    if variant_label in used_labels:
                        continue
                    variant_id = self._make_variant_id(model_type, "text", label)
                    variants.append(
                        {
                            "id": variant_id,
                            "label": variant_label,
                            "type": "variant",
                            "files": files,
                            "primary_paths": [resolved_path],
                        }
                    )
                    used_labels.add(variant_label)

        return sorted(variants, key=lambda v: v["label"].lower())

    def _collect_lora_variants(self, model_type):
        lora_dir = self._safe_get_lora_dir(model_type)
        if not lora_dir:
            return []
        loras = self.get_model_recursive_prop(model_type, "loras", return_list=True)
        if loras is None:
            return []

        variants = []
        used_paths = set()
        used_labels = {}
        for entry in self._ensure_list(loras):
            if not isinstance(entry, str) or len(entry) == 0:
                continue
            source_entry = entry.split("|", 1)[0]
            basename = os.path.basename(source_entry)
            if len(basename) == 0:
                continue
            files = set()
            self._add_file(files, self.get_lora_local_path(lora_dir, entry))
            if not files:
                continue
            resolved_path = next(iter(files))
            if resolved_path in used_paths:
                continue
            used_paths.add(resolved_path)
            stem = os.path.splitext(basename)[0]
            base_label = f"Lora {stem}"
            label_count = used_labels.get(base_label, 0)
            used_labels[base_label] = label_count + 1
            variant_label = base_label if label_count == 0 else f"{base_label} #{label_count + 1}"
            variant_id = self._make_variant_id(model_type, "lora", basename)
            variants.append(
                {
                    "id": variant_id,
                    "label": variant_label,
                    "type": "variant",
                    "files": files,
                    "primary_paths": [resolved_path],
                }
            )
        return sorted(variants, key=lambda v: v["label"].lower())

    def _collect_other_files(
        self,
        model_type,
        model_def,
        transformer_files,
        text_encoder_files,
        lora_files,
    ):
        other_files = set()
        shared_files = set()
        lora_dir = self._safe_get_lora_dir(model_type)

        preload_urls = self.get_model_recursive_prop(
            model_type, "preload_URLs", return_list=True
        )
        for url in self._ensure_list(preload_urls):
            self._add_file(other_files, url, lora_dir=lora_dir)

        vae_urls = model_def.get("VAE_URLs", [])
        for url in self._ensure_list(vae_urls):
            self._add_file(shared_files, url, lora_dir=lora_dir)

        handler_files = self._collect_handler_files(model_type, model_def)
        shared_files.update(handler_files)

        other_files.difference_update(transformer_files)
        other_files.difference_update(text_encoder_files)
        other_files.difference_update(lora_files)
        shared_files.difference_update(transformer_files)
        shared_files.difference_update(text_encoder_files)
        shared_files.difference_update(lora_files)
        shared_files.difference_update(other_files)
        return other_files, shared_files

    def _resolve_default_dtype_policy(self, model_type, model_def):
        dtype = model_def.get("dtype")
        if isinstance(dtype, str) and dtype.lower() in ("fp16", "bf16"):
            return dtype.lower()
        policy = self.transformer_dtype_policy
        if isinstance(policy, str) and policy:
            return policy.lower()
        return "bf16"

    def _format_dtype_label(self, dtype_policy):
        return "FP16" if dtype_policy == "fp16" else "BF16"

    def _format_quant_label(self, token):
        if not token:
            return ""
        label = quant_router.get_quantization_label(token)
        if label:
            return label
        return str(token).replace("_", " ").upper()

    def _format_variant_label(self, token, dtype_policy, default_label):
        if token:
            return self._format_quant_label(token)
        if dtype_policy:
            return self._format_dtype_label(dtype_policy)
        return default_label

    def _detect_quant_info(self, name):
        if not name:
            return "", ""
        label = quant_router.detect_quantization_label_from_filename(name) or ""
        if label:
            return self._label_to_token(label), label
        kind = quant_router.detect_quantization_kind_for_file(name, verboseLevel=0)
        if kind and kind != "none":
            kind = str(kind).lower()
            return kind, kind.upper()
        lower = str(name).lower()
        aliases = self._get_quant_aliases()
        for token in aliases:
            if token in ("bf16", "fp16", "bfloat16", "float16"):
                continue
            if token and token in lower:
                return token, ""
        return "", ""

    def _label_to_token(self, label):
        if not label:
            return ""
        raw = str(label).strip()
        lower = raw.lower()
        if lower.startswith("gguf-"):
            return lower.split("-", 1)[1]
        if lower == "gguf":
            return "gguf"
        return lower

    def _detect_dtype_policy_from_name(self, name):
        if not name:
            return ""
        lower = str(name).lower()
        if "fp16" in lower or "float16" in lower:
            return "fp16"
        if "bf16" in lower or "bfloat16" in lower:
            return "bf16"
        return ""

    def _collect_variant_candidates(self, choice_lists, default_dtype_policy):
        base_dtypes = set()
        token_dtypes = defaultdict(set)
        token_labels = {}
        for choice_list in choice_lists:
            for entry in self._ensure_list(choice_list):
                if not entry:
                    continue
                entry_str = str(entry)
                name = self._locator_basename(entry_str)
                quant_source = self._resolve_path(entry_str) or self._locator_path(entry_str) or entry_str
                token, label = self._detect_quant_info(quant_source)
                dtype_policy = self._detect_dtype_policy_from_name(name) or default_dtype_policy
                if token:
                    token = str(token).lower()
                    token_dtypes[token].add(dtype_policy)
                    if label:
                        token_labels.setdefault(token, label)
                else:
                    base_dtypes.add(dtype_policy)
        if not base_dtypes and not token_dtypes:
            base_dtypes.add(default_dtype_policy)
        return base_dtypes, token_dtypes, token_labels

    def _quant_param_from_token(self, token):
        if not token:
            return ""
        lower = str(token).lower()
        if "int8" in lower:
            return "int8"
        if "fp8" in lower or "float8" in lower:
            return "fp8"
        return lower

    def _files_match_token(self, files, token):
        if not token:
            return True
        token = str(token).lower()
        for path in files:
            base = os.path.basename(path).lower()
            if token in base:
                return True
            label = quant_router.detect_quantization_label_from_filename(path)
            if label and self._label_to_token(label) == token:
                return True
        return False

    def _detect_quant_label_from_files(self, files):
        for path in files:
            label = quant_router.detect_quantization_label_from_filename(path)
            if label:
                return label
        return ""

    def _select_filename_by_token(self, urls, token):
        if not urls or not token:
            return ""
        token = str(token).lower()
        for entry in self._ensure_list(urls):
            if not entry:
                continue
            if isinstance(entry, list):
                for item in entry:
                    if not item:
                        continue
                    base = self._locator_basename(item).lower()
                    if token in base:
                        return item
                    label = quant_router.detect_quantization_label_from_filename(item)
                    if label and self._label_to_token(label) == token:
                        return item
                continue
            base = self._locator_basename(entry).lower()
            if token in base:
                return entry
            label = quant_router.detect_quantization_label_from_filename(entry)
            if label and self._label_to_token(label) == token:
                return entry
        return ""

    def _split_module_ref(self, module_ref, default_sub_prop="_list"):
        module_type = module_ref
        sub_prop_name = default_sub_prop
        if isinstance(module_ref, str) and "#" in module_ref:
            pos = module_ref.rfind("#")
            module_type = module_ref[:pos]
            sub_prop_name = module_ref[pos + 1 :]
        return module_type, sub_prop_name

    def _resolve_recursive_prop_owner(self, model_type, prop, sub_prop_name, stack):
        if not isinstance(model_type, str) or not model_type:
            return ""
        if model_type in stack or len(stack) > 20:
            return model_type
        model_def = self.get_model_def(model_type)
        if not isinstance(model_def, dict):
            return model_type
        prop_value = model_def.get(prop, None)
        if prop_value is None:
            return model_type
        if sub_prop_name is not None:
            if sub_prop_name == "_list":
                if not isinstance(prop_value, list) or len(prop_value) != 1:
                    return model_type
                prop_value = prop_value[0]
            else:
                if not isinstance(prop_value, dict) or sub_prop_name not in prop_value:
                    return model_type
                prop_value = prop_value[sub_prop_name]
        if isinstance(prop_value, str):
            next_model_type, next_sub_prop_name = self._split_module_ref(
                prop_value, default_sub_prop=sub_prop_name
            )
            if not next_model_type:
                return model_type
            return self._resolve_recursive_prop_owner(
                next_model_type, prop, next_sub_prop_name, stack + [model_type]
            )
        return model_type

    def _resolve_final_module_id(self, module_ref):
        if not isinstance(module_ref, str) or not module_ref:
            return ""
        module_type, sub_prop_name = self._split_module_ref(module_ref)
        resolved_id = self._resolve_recursive_prop_owner(
            module_type, "modules", sub_prop_name, []
        )
        return resolved_id or module_type

    def _expand_modules_with_ids(self, model_type):
        modules = self.get_model_recursive_prop(model_type, "modules", return_list=True)
        expanded_modules = []
        modules_count = len(modules)
        for index, module in enumerate(modules):
            if isinstance(module, str):
                module_id = self._resolve_final_module_id(module)
                if "#" in module:
                    expanded_modules.append({"module": module, "module_id": module_id})
                    continue
                try:
                    expanded = self.get_model_recursive_prop(
                        module, "modules", sub_prop_name="_list", return_list=True
                    )
                except Exception:
                    expanded = []
                if expanded:
                    expanded_modules.append({"module": expanded, "module_id": module_id})
                else:
                    expanded_modules.append({"module": module, "module_id": module_id})
            else:
                fallback_id = model_type if modules_count <= 1 else f"{model_type}#{index + 1}"
                expanded_modules.append({"module": module, "module_id": fallback_id})
        return expanded_modules

    def _expand_modules(self, model_type):
        return [entry.get("module") for entry in self._expand_modules_with_ids(model_type)]

    def _make_variant_id(self, model_type, kind, label):
        slug = "".join(ch.lower() if ch.isalnum() else "_" for ch in label).strip("_")
        return f"variant::{model_type}::{kind}::{slug}"

    def _dedupe_paths(self, paths):
        seen = set()
        ordered = []
        for path in paths:
            if not path or path in seen:
                continue
            ordered.append(path)
            seen.add(path)
        return ordered

    def _get_quant_aliases(self):
        if not hasattr(self, "_quant_alias_cache"):
            aliases = set(quant_router.get_available_qtype_aliases() or [])
            aliases.update({"int8", "int8n", "fp8"})
            self._quant_alias_cache = sorted(
                {alias for alias in aliases if alias}, key=len, reverse=True
            )
        return self._quant_alias_cache

    def _text_encoder_quant_candidates(self):
        candidates = {""}
        aliases = self._get_quant_aliases()
        for token in aliases:
            if "int8" in token:
                candidates.add("int8")
            if "fp8" in token or "float8" in token:
                candidates.add("fp8")
        return sorted(candidates)

    def _collect_text_encoder_files(self, model_type, model_def):
        files = set()
        text_encoder_urls = self.get_model_recursive_prop(
            model_type, "text_encoder_URLs", return_list=True
        )
        text_encoder_folder = model_def.get("text_encoder_folder", None)
        text_encoder_filename = None
        if text_encoder_urls:
            text_encoder_filename = self.get_model_filename(
                model_type=model_type,
                quantization=self.text_encoder_quantization,
                dtype_policy=self.transformer_dtype_policy,
                URLs=text_encoder_urls,
            )
        else:
            handler = self.get_model_handler(model_type)
            get_name = getattr(handler, "get_text_encoder_filename", None)
            if get_name is not None:
                try:
                    text_encoder_filename = get_name(self.text_encoder_quantization)
                except Exception:
                    text_encoder_filename = None
        if text_encoder_filename:
            self._add_file(files, text_encoder_filename, force_folder=text_encoder_folder)
        return files

    def _collect_handler_file_paths(self, model_type, model_def):
        handler = self.get_model_handler(model_type)
        query = getattr(handler, "query_model_files", None)
        if query is None:
            return set()
        base_model_type = self.get_base_model_type(model_type)
        try:
            download_defs = query(
                self._compute_list,
                base_model_type,
                model_def,
            )
        except Exception as exc:
            self._errors.append(f"{model_type}: {exc}")
            return set()

        return self._collect_download_def_file_paths(download_defs)

    def _collect_download_def_file_paths(self, download_defs):
        if not download_defs:
            return set()
        if isinstance(download_defs, dict):
            download_defs = [download_defs]

        files = set()
        for download_def in download_defs:
            if not isinstance(download_def, dict):
                continue
            source_folders = download_def.get("sourceFolderList", [])
            file_lists = download_def.get("fileList", [])
            target_folders = download_def.get("targetFolderList")
            if target_folders is None:
                target_folders = [None] * len(source_folders)

            for source_folder, file_list, target_folder in zip(
                source_folders, file_lists, target_folders
            ):
                if target_folder == "":
                    target_folder = None
                if source_folder is None:
                    source_folder = ""

                if not file_list:
                    continue

                for filename in file_list:
                    if not filename:
                        continue
                    rel_path = self._combine_download_path(
                        target_folder, source_folder, filename
                    )
                    files.add(self._resolve_download_relpath(rel_path))
        return {path for path in files if path}

    def _collect_global_shared_file_paths(self):
        query = getattr(self, "query_global_shared_model_files", None)
        if query is None:
            return set()
        try:
            return self._collect_download_def_file_paths(query())
        except Exception as exc:
            self._errors.append(f"Global shared files: {exc}")
            return set()

    def _mark_global_shared_files(self):
        for path in self._collect_global_shared_file_paths():
            if path in self._file_usage_models:
                self._file_usage_models[path].add(GLOBAL_SHARED_MODEL_TYPE)

    def _collect_handler_files(self, model_type, model_def):
        files = self._collect_handler_file_paths(model_type, model_def)
        return {path for path in files if os.path.isfile(path)}

    def _build_tree_structure(self, model_types):
        families = defaultdict(list)
        for model_type in model_types:
            family = self.get_model_family(model_type, for_ui=True) or "unknown"
            families[family].append(model_type)

        family_order = sorted(
            families.keys(),
            key=lambda family: self.families_infos.get(family, (999, family))[0],
        )

        for family in family_order:
            family_label = self.families_infos.get(family, (999, family))[1]
            rows = []
            for model_type in families[family]:
                model_name = self.compact_name(
                    family_label, self.get_model_name(model_type)
                )
                parent_id = self.get_parent_model_type(model_type)
                rows.append((model_name, model_type, parent_id))

            rows.sort(key=lambda row: row[0])
            parents_list, children_dict = self.create_models_hierarchy(rows)
            family_node_id = f"family::{family}"
            family_models = set(families[family])
            family_usage_ids = self._collect_usage_ids_for_models(family_models)
            family_files = self._collect_files_for_usage_ids(family_usage_ids)
            self._register_node(
                family_node_id,
                family_label,
                "family",
                family_models,
                family_files,
                path_label=family_label,
            )

            base_nodes = []
            for base_label, base_id in parents_list:
                base_status = self._model_aggregated_status_map.get(
                    base_id, model_dropdowns.MODEL_FILE_STATUS_MISSING
                )
                base_label_decorated = self._decorate_status_label(base_label, base_status)
                child_entries = children_dict.get(base_id, [])
                child_nodes = []
                base_models = set()
                base_usage_ids = set()
                base_path = f"{family_label} / {base_label}"
                for child_label, child_model in child_entries:
                    child_status = self._model_direct_status_map.get(
                        child_model, model_dropdowns.MODEL_FILE_STATUS_MISSING
                    )
                    child_label_decorated = self._decorate_status_label(child_label, child_status)
                    base_models.add(child_model)
                    model_usage_ids = self._model_usage_ids.get(child_model, set())
                    base_usage_ids.update(model_usage_ids)
                    self._model_base_label[child_model] = base_label
                    self._model_family_label[child_model] = family_label
                    model_node_id = f"model::{child_model}"
                    model_files = self._collect_files_for_usage_ids(model_usage_ids)
                    primary_paths = self._model_primary.get(child_model, [])
                    model_path = f"{base_path} / {child_label}"
                    self._model_display_label[child_model] = model_path
                    self._register_node(
                        model_node_id,
                        child_label_decorated,
                        "model",
                        {child_model},
                        model_files,
                        primary_paths=primary_paths,
                        path_label=model_path,
                    )
                    variant_children = []
                    for variant in self._ordered_variants_for_display(child_model):
                        variant_id = variant["id"]
                        variant_label = variant["label"]
                        variant_type = variant.get("type", "variant")
                        variant_path = f"{model_path} / {variant_label}"
                        self._register_node(
                            variant_id,
                            variant_label,
                            variant_type,
                            {child_model},
                            variant.get("files", set()),
                            missing_files=variant.get("missing_files", set()),
                            primary_paths=variant.get("primary_paths", []),
                            path_label=variant_path,
                        )
                        variant_children.append(
                            {"id": variant_id, "label": variant_label, "type": variant_type}
                        )
                    child_nodes.append(
                        {
                            "id": model_node_id,
                            "label": child_label_decorated,
                            "type": "model",
                            "children": variant_children,
                        }
                    )

                base_node_id = f"base::{family}::{base_id}"
                base_files = self._collect_files_for_usage_ids(base_usage_ids)
                self._register_node(
                    base_node_id,
                    base_label_decorated,
                    "base",
                    base_models,
                    base_files,
                    path_label=base_path,
                )
                base_nodes.append(
                    {
                        "id": base_node_id,
                        "label": base_label_decorated,
                        "type": "base",
                        "children": child_nodes,
                    }
                )

            self._tree_data.append(
                {
                    "id": family_node_id,
                    "label": family_label,
                    "type": "family",
                    "children": base_nodes,
                }
            )

    def _collect_usage_ids_for_models(self, model_set):
        usage_ids = set()
        for model_type in model_set:
            usage_ids.update(self._model_usage_ids.get(model_type, set()))
        return usage_ids

    def _collect_files_for_usage_ids(self, usage_ids):
        files = set()
        for usage_id in usage_ids:
            files.update(self._usage_files.get(usage_id, set()))
        return files

    def _ordered_variants_for_display(self, model_type):
        variants = list(self._model_variants.get(model_type, []))
        if not variants:
            return []
        non_missing = [variant for variant in variants if variant.get("type") != "missing"]
        missing = [variant for variant in variants if variant.get("type") == "missing"]
        return non_missing + missing

    def _register_node(
        self,
        node_id,
        label,
        node_type,
        model_set,
        files_set,
        missing_files=None,
        primary_paths=None,
        path_label=None,
    ):
        files_set = set(files_set or set())
        missing_files = set(missing_files or set())
        missing_files.difference_update(files_set)
        missing_files = {path for path in missing_files if path and not os.path.isfile(path)}
        unique_files, shared_files = self._split_files_by_shared(files_set, model_set)
        unique_size = self._sum_sizes(unique_files)
        shared_size = self._sum_sizes(shared_files)
        self._node_map[node_id] = {
            "id": node_id,
            "label": label,
            "type": node_type,
            "models": set(model_set),
            "files": set(files_set),
            "unique_files": unique_files,
            "shared_files": shared_files,
            "unique_size": unique_size,
            "shared_size": shared_size,
            "total_size": unique_size + shared_size,
            "missing_files": missing_files,
            "missing_count": len(missing_files),
            "primary_paths": primary_paths or [],
            "path_label": path_label or label,
        }

    def _sum_sizes(self, files_set):
        total = 0
        for path in files_set:
            total += self._file_info.get(path, {}).get("size", 0)
        return total

    def _split_files_by_shared(self, files_set, model_set):
        unique_files = set()
        shared_files = set()
        for path in files_set:
            used_by = self._file_usage_models.get(path, set())
            if used_by.issubset(model_set):
                unique_files.add(path)
            else:
                shared_files.add(path)
        return unique_files, shared_files

    def _rebalance_other_shared_variants(self, model_types):
        for model_type in model_types:
            variants = self._model_variants.get(model_type, [])
            if not variants:
                continue
            other_variant = None
            shared_variant = None
            for variant in variants:
                variant_id = str(variant.get("id", ""))
                parts = variant_id.split("::")
                variant_kind = parts[2] if len(parts) >= 4 else ""
                label = str(variant.get("label", "")).lower()
                if variant_kind == "other" or label in ("other", "other non shared"):
                    other_variant = variant
                elif variant_kind == "shared" or label in ("shared", "other shared"):
                    shared_variant = variant

            if not other_variant and not shared_variant:
                continue

            combined = set()
            if other_variant:
                combined.update(other_variant.get("files", set()))
            if shared_variant:
                combined.update(shared_variant.get("files", set()))

            new_variants = []
            for variant in variants:
                if variant is other_variant or variant is shared_variant:
                    continue
                new_variants.append(variant)
            if not combined:
                self._model_variants[model_type] = new_variants
                continue

            model_set = {model_type}
            shared_files = {
                path
                for path in combined
                if not self._file_usage_models.get(path, set()).issubset(model_set)
            }
            other_files = combined - shared_files

            if other_files:
                if other_variant is None:
                    other_variant = {
                        "id": self._make_variant_id(model_type, "other", "other"),
                        "label": VARIANT_LABEL_OTHER_NON_SHARED,
                        "type": "variant",
                        "files": other_files,
                        "primary_paths": [],
                    }
                else:
                    other_variant["files"] = other_files
                new_variants.append(other_variant)

            if shared_files:
                if shared_variant is None:
                    shared_variant = {
                        "id": self._make_variant_id(model_type, "shared", "shared"),
                        "label": VARIANT_LABEL_OTHER_SHARED,
                        "type": "variant",
                        "files": shared_files,
                        "primary_paths": [],
                    }
                else:
                    shared_variant["files"] = shared_files
                new_variants.append(shared_variant)

            self._model_variants[model_type] = new_variants

    def _get_model_shared_label(self, model_type):
        if model_type == GLOBAL_SHARED_MODEL_TYPE:
            return GLOBAL_SHARED_LABEL
        model_name = self.get_model_name(model_type)
        if model_name:
            return model_name
        label = self._model_display_label.get(model_type)
        if label:
            return label
        return model_type

    def _shared_model_labels(self, path, exclude_models=None):
        if exclude_models is None:
            exclude_models = set()
        labels = set()
        for model_type in self._file_usage_models.get(path, set()):
            if model_type in exclude_models:
                continue
            labels.add(self._get_model_shared_label(model_type))
        return sorted(labels, key=lambda value: value.lower())

    def _build_tree_html(self):
        self._last_rendered_nodes_count = 0
        css = """
        <style>
        .ckpt-wrap { display: flex; flex-direction: column; gap: 12px; }
        .ckpt-panel { border: 1px solid var(--border-color-primary); border-radius: 14px; background: linear-gradient(135deg, rgba(255,255,255,0.02), rgba(0,0,0,0.02)); box-shadow: var(--shadow-drop-sm, 0 2px 8px rgba(0,0,0,0.08)); }
        .ckpt-panel-inner { padding: 10px; }
        .ckpt-tree { display: flex; flex-direction: column; gap: 8px; }
        .ckpt-node { border: 1px solid var(--border-color-primary); border-radius: 10px; background: var(--background-fill-secondary); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.02); }
        .ckpt-node summary { list-style: none; cursor: pointer; padding: 8px 10px; }
        .ckpt-node summary::-webkit-details-marker { display: none; }
        .ckpt-row { display: flex; align-items: center; gap: 10px; border-radius: 8px; padding: 6px 6px; }
        .ckpt-row.clickable { cursor: pointer; transition: background 0.15s ease; }
        .ckpt-row.clickable:hover { background: rgba(127,127,127,0.08); }
        .ckpt-label { flex: 1; font-weight: 600; color: inherit; }
        .ckpt-label-missing { color: #c62828; }
        .ckpt-row[data-node-type="missing"] .ckpt-label { color: #c62828 !important; font-weight: 700; }
        .ckpt-sizes { display: flex; flex-direction: column; align-items: stretch; min-width: 128px; width: 128px; font-size: 0.85em; font-variant-numeric: tabular-nums; gap: 4px; }
        .ckpt-size-unique { color: inherit; }
        .ckpt-size-shared { color: inherit; }
        .ckpt-sizes-missing { justify-content: center; }
        .ckpt-size-missing { font-size: 0.82em; color: #c62828; text-align: center; font-weight: 600; }
        .ckpt-badge { padding: 3px 8px; border-radius: 999px; font-weight: 600; font-size: 0.82em; font-variant-numeric: tabular-nums; min-width: 84px; width: 100%; display: inline-flex; align-items: center; justify-content: center; }
        .ckpt-badge-unique { background: rgba(64, 120, 255, 0.18); color: #2f5bd4; border: 1px solid rgba(64, 120, 255, 0.4); }
        .ckpt-badge-shared { background: rgba(240, 200, 40, 0.25); color: #a66a00; border: 1px solid rgba(240, 200, 40, 0.5); }
        .ckpt-badge-zero { background: rgba(120, 120, 120, 0.12); color: #888; border: 1px solid rgba(120, 120, 120, 0.25); }
        .ckpt-badge-missing { background: rgba(220, 60, 60, 0.18); color: #b71c1c; border: 1px solid rgba(220, 60, 60, 0.45); min-width: auto; width: auto; }
        .ckpt-actions { display: flex; gap: 18px; }
        .ckpt-action-btn { padding: 3px 12px; height: 30px; border: 1px solid var(--border-color-primary); border-radius: 6px; cursor: pointer; font-size: 0.85em; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; background: transparent; color: inherit; }
        .ckpt-action-btn:hover { box-shadow: var(--shadow-drop, 0 2px 6px rgba(0,0,0,0.12)); }
        .ckpt-view-btn { background: rgba(60, 120, 255, 0.9) !important; color: #ffffff !important; border-color: rgba(60, 120, 255, 0.95) !important; }
        .ckpt-view-btn:hover { background: rgba(60, 120, 255, 1) !important; }
        .ckpt-delete { background: rgba(220, 60, 60, 0.95) !important; color: #ffffff !important; border-color: rgba(220, 60, 60, 0.95) !important; }
        .ckpt-delete:hover { background: rgba(220, 60, 60, 1) !important; }
        .ckpt-children { padding: 6px 8px 10px 18px; display: flex; flex-direction: column; gap: 6px; }
        .ckpt-leaf { border: 1px solid var(--border-color-primary); border-radius: 8px; background: var(--background-fill-primary); padding: 6px 8px; }
        .ckpt-right-column { align-self: flex-start; position: relative; overflow: visible !important; }
        .ckpt-view-sticky { position: sticky; top: 16px; align-self: flex-start; }
        .ckpt-view { border: 1px solid var(--border-color-primary); border-radius: 12px; background: linear-gradient(135deg, rgba(255,255,255,0.02), rgba(0,0,0,0.02)); padding: 12px; max-height: none; overflow: visible; }
        .ckpt-view h4 { margin: 0 0 6px 0; }
        .ckpt-view-summary { font-size: 0.85em; color: inherit; margin-bottom: 8px; display: flex; flex-wrap: wrap; gap: 12px; }
        .ckpt-loading-message { font-size: 1.05em; font-weight: 600; color: inherit; padding: 16px 8px; }
        .ckpt-muted { opacity: 0.75; }
        .ckpt-stats { display: flex; flex-direction: column; gap: 6px; }
        .ckpt-stat-main { font-weight: 600; font-size: 0.95em; }
        .ckpt-stat-assumption { font-size: 0.82em; opacity: 0.85; }
        .ckpt-drive-list { display: flex; flex-wrap: wrap; gap: 8px; }
        .ckpt-drive { padding: 4px 8px; border-radius: 999px; background: var(--background-fill-primary); border: 1px solid var(--border-color-primary); font-size: 0.82em; }
        .ckpt-file-tree { display: flex; flex-direction: column; gap: 6px; max-height: none; overflow: visible; }
        .ckpt-folder { border: 1px solid var(--border-color-primary); border-radius: 8px; background: var(--background-fill-primary); }
        .ckpt-folder.shared { border-color: rgba(240,200,40,0.45); }
        .ckpt-folder.shared > summary { background: rgba(240,200,40,0.12); }
        .ckpt-folder.unique { border-color: rgba(64, 120, 255, 0.35); }
        .ckpt-folder.unique > summary { background: rgba(64, 120, 255, 0.12); }
        .ckpt-folder summary { list-style: none; cursor: pointer; padding: 6px 8px; display: flex; align-items: center; gap: 8px; position: relative; }
        .ckpt-folder summary::-webkit-details-marker { display: none; }
        .ckpt-folder-name { flex: 1; font-weight: 600; }
        .ckpt-folder-size { font-size: 0.8em; color: inherit; opacity: 0.75; font-variant-numeric: tabular-nums; }
        .ckpt-folder-children { padding: 6px 8px 8px 18px; display: flex; flex-direction: column; gap: 4px; }
        .ckpt-file-row { display: flex; gap: 8px; align-items: center; font-size: 0.85em; padding: 2px 0; }
        .ckpt-file-row.shared { position: relative; }
        .ckpt-file-row.shared { color: #a66a00; background: rgba(240, 200, 40, 0.12); border-radius: 6px; padding: 2px 6px; }
        .ckpt-file-row.unique { background: rgba(64, 120, 255, 0.12); border-radius: 6px; padding: 2px 6px; }
        .ckpt-file-row.missing-only { color: #b71c1c; background: rgba(220, 60, 60, 0.16); border-radius: 6px; padding: 2px 6px; }
        .ckpt-file-row.missing { opacity: 0.6; }
        .ckpt-file-name { flex: 1; word-break: break-all; }
        .ckpt-file-size { min-width: 70px; text-align: right; color: inherit; opacity: 0.75; font-variant-numeric: tabular-nums; }
        .ckpt-shared-tag { font-size: 0.72em; text-transform: uppercase; letter-spacing: 0.04em; border: 1px solid rgba(240,200,40,0.5); color: #a66a00; padding: 2px 6px; border-radius: 999px; background: rgba(240,200,40,0.12); }
        .ckpt-folder-tag { margin-left: 6px; }
        .ckpt-shared-tooltip { opacity: 0; visibility: hidden; pointer-events: none; position: absolute; top: 100%; left: 0; margin-top: 6px; padding: 6px 8px; background: var(--background-fill-primary); border: 1px solid var(--border-color-primary); border-radius: 8px; box-shadow: var(--shadow-drop, 0 2px 6px rgba(0,0,0,0.12)); font-size: 0.8em; z-index: 10; min-width: 160px; transform: translateY(4px); transition: opacity 0.15s ease, transform 0.15s ease; transition-delay: 0s; }
        .ckpt-shared-tooltip-title { font-weight: 600; margin-bottom: 4px; }
        .ckpt-file-row.shared:hover .ckpt-shared-tooltip { opacity: 1; visibility: visible; transform: translateY(0); transition-delay: 2s; }
        .ckpt-folder.shared summary:hover .ckpt-shared-tooltip { opacity: 1; visibility: visible; transform: translateY(0); transition-delay: 2s; }
        .ckpt-path { font-size: 0.82em; color: inherit; opacity: 0.8; margin-bottom: 6px; word-break: break-all; }
        .ckpt-primary { font-size: 0.82em; color: inherit; opacity: 0.8; margin-bottom: 8px; word-break: break-all; }
        .ckpt-missing-summary { margin: 10px 0; }
        .ckpt-errors { padding: 8px 10px; border: 1px solid rgba(220, 80, 80, 0.4); background: var(--background-fill-primary); border-radius: 8px; font-size: 0.85em; }
        .ckpt-modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.45); display: none; align-items: center; justify-content: center; z-index: 9999; }
        .ckpt-modal { width: min(420px, 90vw); background: var(--background-fill-primary); border: 1px solid var(--border-color-primary); border-radius: 14px; padding: 18px; box-shadow: 0 12px 28px rgba(0,0,0,0.25); display: flex; flex-direction: column; gap: 12px; }
        .ckpt-modal-title { font-weight: 700; font-size: 1.05em; }
        .ckpt-modal-body { font-size: 0.95em; color: inherit; opacity: 0.8; }
        .ckpt-modal-actions { display: flex; gap: 12px; justify-content: flex-end; }
        .ckpt-modal-btn { padding: 8px 14px; border-radius: 8px; border: 1px solid var(--border-color-primary); background: var(--button-secondary-background-fill, #f0f0f0); cursor: pointer; font-weight: 600; }
        .ckpt-modal-btn.danger { background: rgba(220,80,80,0.15); border-color: rgba(220,80,80,0.5); color: #c62828; }
        </style>
        """

        tree_parts = [
            css,
            "<div id='ckpt_tree_root' data-status='ready' class='ckpt-wrap'>",
        ]
        if self._errors:
            errors_html = "<br>".join(html.escape(err) for err in self._errors[:10])
            tree_parts.append(f"<div class='ckpt-errors'>{errors_html}</div>")

        tree_parts.append("<div class='ckpt-panel'><div class='ckpt-panel-inner'>")
        tree_parts.append(self._build_stats_html())
        rendered_nodes_html = []
        for family in self._tree_data:
            rendered_nodes_html.append(self._render_node(family, is_family=True))
        tree_parts.append("<div class='ckpt-tree'>")
        tree_parts.extend(rendered_nodes_html)
        tree_parts.append("</div></div></div></div>")
        return "".join(tree_parts)

    def _get_tree_css_block(self):
        tree_html = self._build_tree_html()
        start = tree_html.find("<style>")
        if start < 0:
            return ""
        end = tree_html.find("</style>", start)
        if end < 0:
            return ""
        return tree_html[start : end + len("</style>")]

    def _build_filtered_tree_html(self, filter_text):
        query = str(filter_text).strip()
        self._current_tree_mode = "filtered"
        self._current_filter_text = query
        query_lower = query.casefold()
        matches = []
        for node_id, info in self._node_map.items():
            if info.get("type") != "model":
                continue
            model_types = list(info.get("models", set()))
            if len(model_types) != 1:
                continue
            model_type = model_types[0]
            model_name = self.get_model_name(model_type)
            if not isinstance(model_name, str):
                continue
            if query_lower not in model_name.casefold():
                continue
            status = self._model_direct_status_map.get(
                model_type, model_dropdowns.MODEL_FILE_STATUS_MISSING
            )
            display_label = self._decorate_status_label(model_name, status)
            matches.append((model_name, display_label, node_id, model_type))

        matches.sort(key=lambda item: item[0].casefold())

        css = self._get_tree_css_block()
        self._last_rendered_nodes_count = 0
        title = (
            f"Filtered Finetunes for '{html.escape(query)}' "
            f"<span class='ckpt-muted'>({len(matches)} match{'es' if len(matches) != 1 else ''})</span>"
        )
        transformer_quantization = self.server_config.get("transformer_quantization", self.transformer_quantization)
        text_encoder_quantization = self.text_encoder_quantization
        cfg_line = (
            "Missing files assume config: "
            f"transformer qtype={html.escape(str(transformer_quantization))}, "
            f"text-encoder qtype={html.escape(str(text_encoder_quantization))}"
        )
        parts = [
            css,
            "<div id='ckpt_tree_root' data-status='ready' class='ckpt-wrap'>",
            "<div class='ckpt-panel'><div class='ckpt-panel-inner'>",
            f"<div class='ckpt-loading-message'>{title}</div>",
            f"<div class='ckpt-stat-assumption'>{cfg_line}</div>",
            "<div class='ckpt-tree'>",
        ]
        if not matches:
            parts.append(
                "<div class='ckpt-leaf'><div class='ckpt-row'>"
                "<div class='ckpt-label'>No finetune matches your filter.</div>"
                "</div></div>"
            )
        for _, display_label, node_id, model_type in matches:
            variant_children = [
                {
                    "id": variant["id"],
                    "label": variant["label"],
                    "type": variant.get("type", "variant"),
                    "children": [],
                }
                for variant in self._ordered_variants_for_display(model_type)
            ]
            model_node = {
                "id": node_id,
                "label": display_label,
                "type": "model",
                "children": variant_children,
            }
            parts.append(self._render_node(model_node))
        parts.extend(["</div></div></div></div>"])
        return "".join(parts)

    def _build_stats_html(self):
        total_label = self._format_gb(self._stats_total)
        transformer_quantization = self.server_config.get("transformer_quantization", self.transformer_quantization)
        text_encoder_quantization = self.text_encoder_quantization
        cfg_line = (
            "Missing files assume config: "
            f"transformer qtype={html.escape(str(transformer_quantization))}, "
            f"text-encoder qtype={html.escape(str(text_encoder_quantization))}"
        )
        drive_html = ""
        if len(self._stats_by_drive) > 1:
            drive_items = []
            for drive, size in self._stats_by_drive:
                drive_items.append(
                    f"<span class='ckpt-drive'>{html.escape(drive)}: {self._format_gb(size)}</span>"
                )
            drive_html = f"<div class='ckpt-drive-list'>{''.join(drive_items)}</div>"
        return (
            "<div class='ckpt-stats'>"
            f"<div class='ckpt-stat-main'>Total Used: {total_label}</div>"
            f"<div class='ckpt-stat-assumption'>{cfg_line}</div>"
            f"{drive_html}</div>"
        )

    def _render_node(self, node, is_family=False):
        self._last_rendered_nodes_count += 1
        node_id = node["id"]
        info = self._node_map.get(node_id, {})
        label = node.get("label", info.get("label", node_id))
        unique_size_value = info.get("unique_size", 0)
        shared_size_value = info.get("shared_size", 0)
        unique = self._format_gb(unique_size_value)
        shared = self._format_gb(shared_size_value)
        unique_label = self._format_size(unique_size_value)
        shared_label = self._format_size(shared_size_value)
        total_label = self._format_size(unique_size_value + shared_size_value)
        missing_count = int(info.get("missing_count", 0) or 0)
        node_type = info.get("type", node.get("type", ""))

        children = node.get("children", [])
        click_view = node_type in ("variant", "missing")
        row_html = self._render_row(
            label,
            node_id,
            unique,
            shared,
            unique_label,
            shared_label,
            total_label,
            node_type,
            missing_count=missing_count,
            click_view=click_view,
        )
        if children:
            node_id_attr = html.escape(node_id, quote=True)
            is_open = node_id in self._expanded_nodes
            open_attr = " open" if is_open else ""
            child_html = ""
            if is_open:
                child_html = "".join(self._render_node(child) for child in children)
            return (
                f"<details class='ckpt-node' data-node-id=\"{node_id_attr}\"{open_attr}>"
                f"<summary onclick='handlemodelSummaryClick(event, this);'>{row_html}</summary>"
                f"<div class='ckpt-children'>{child_html}</div></details>"
            )
        return f"<div class='ckpt-leaf'>{row_html}</div>"

    def _render_row(
        self,
        label,
        node_id,
        unique,
        shared,
        unique_label,
        shared_label,
        total_label,
        node_type,
        missing_count=0,
        click_view=False,
    ):
        label_display = html.escape(label)
        for status, prefix in model_dropdowns.MODEL_STATUS_PREFIXES.items():
            if label.startswith(prefix + " "):
                status_name, status_title = {
                    model_dropdowns.MODEL_FILE_STATUS_MISSING: ("missing", "Not installed"),
                    model_dropdowns.MODEL_FILE_STATUS_PARTIAL: ("partial", "Partially available"),
                    model_dropdowns.MODEL_FILE_STATUS_EXPECTED: ("available", "Available"),
                }[status]
                label_display = f"<span class='wangp-model-status' data-wangp-availability='{status_name}' role='img' aria-label='{status_title}' title='{status_title}'></span>{html.escape(label[len(prefix) + 1:])}"
                break
        label_attr = html.escape(label, quote=True)
        node_id_attr = html.escape(node_id, quote=True)
        type_attr = html.escape(node_type, quote=True)
        row_class = "ckpt-row clickable" if click_view else "ckpt-row"
        row_onclick = (
            "onclick='handlemodelRowClick(event, this);'" if click_view else ""
        )
        label_class = "ckpt-label ckpt-label-missing" if node_type == "missing" else "ckpt-label"
        if node_type == "missing":
            file_count_label = f"{missing_count} file" if missing_count == 1 else f"{missing_count} files"
            sizes_html = (
                "<div class='ckpt-sizes ckpt-sizes-missing'>"
                f"<div class='ckpt-size-missing'>{html.escape(file_count_label)}</div>"
                "</div>"
            )
        else:
            unique_badge_class = "ckpt-badge ckpt-badge-unique"
            shared_badge_class = "ckpt-badge ckpt-badge-shared"

            if unique == "0.00 GB":
                unique_badge_class += " ckpt-badge-zero"
            if shared == "0.00 GB":
                shared_badge_class += " ckpt-badge-zero"

            unique_badge = f"<span class='{unique_badge_class}'>{unique}</span>"
            shared_badge = f"<span class='{shared_badge_class}'>{shared}</span>"
            sizes_html = (
                "<div class='ckpt-sizes'>"
                f"<div class='ckpt-size-unique' title='Unique size'>{unique_badge}</div>"
                f"<div class='ckpt-size-shared' title='Shared size'>{shared_badge}</div>"
                "</div>"
            )
        show_view_button = node_type not in ("variant", "missing")
        show_delete_button = node_type != "missing"
        action_buttons = []
        if show_view_button:
            action_buttons.append(
                f"<button class='ckpt-action-btn ckpt-view-btn' data-node-id=\"{node_id_attr}\" data-node-label=\"{label_attr}\" data-node-type=\"{type_attr}\" data-unique-size=\"{unique_label}\" data-shared-size=\"{shared_label}\" data-total-size=\"{total_label}\" onclick='event.preventDefault(); event.stopPropagation(); handlemodelAction(this, \"view\");'>View</button>"
            )
        if show_delete_button:
            action_buttons.append(
                f"<button class='ckpt-action-btn ckpt-delete' data-node-id=\"{node_id_attr}\" data-node-label=\"{label_attr}\" data-node-type=\"{type_attr}\" data-unique-size=\"{unique_label}\" data-shared-size=\"{shared_label}\" data-total-size=\"{total_label}\" onclick='event.preventDefault(); event.stopPropagation(); handlemodelAction(this, \"delete\");'>Delete</button>"
            )
        actions_html = f"<div class='ckpt-actions'>{''.join(action_buttons)}</div>"
        return (
            f"<div class='{row_class}' data-node-id=\"{node_id_attr}\" data-node-label=\"{label_attr}\" data-node-type=\"{type_attr}\" {row_onclick}>"
            f"<div class='{label_class}'>{label_display}</div>"
            f"{sizes_html}"
            f"{actions_html}"
            "</div>"
        )

    def _build_empty_view_html(self):
        return (
            "<div class='ckpt-view-sticky'><div class='ckpt-view'>"
            "<h4>Files</h4>"
            "<div class='ckpt-view-summary'>Select a branch to view its files.</div>"
            "</div></div>"
        )

    def _build_loading_html(self):
        return (
            "<div id='ckpt_tree_root' data-status='loading' class='ckpt-wrap'>"
            "<div class='ckpt-panel'><div class='ckpt-panel-inner'>"
            "<div class='ckpt-loading-message'>Please wait while the models hierarchy is being built...</div>"
            "</div></div></div>"
        )

    def _build_view_html(self, node_id):
        node = self._node_map.get(node_id)
        if node is None:
            return self._build_empty_view_html()
        if node.get("type") == "missing":
            return self._build_missing_view_html(node)

        files = sorted(node["files"])
        model_set = set(node.get("models", set()))
        shared_set = {
            path
            for path in files
            if not self._file_usage_models.get(path, set()).issubset(node["models"])
        }

        shared_info = {}
        for path in shared_set:
            shared_labels = self._shared_model_labels(path, exclude_models=model_set)
            if not shared_labels:
                shared_labels = ["Shared with another model"]
            shared_info[path] = shared_labels
        file_tree = self._build_file_tree(files, shared_set, shared_info)
        file_tree_html = self._render_file_tree(file_tree)

        unique = self._format_gb(node["unique_size"])
        shared = self._format_gb(node["shared_size"])
        label_text = node["label"]
        path_label = node.get("path_label") or label_text
        header = html.escape(path_label)
        unique_badge = f"<span class='ckpt-badge ckpt-badge-unique'>{unique}</span>"
        shared_badge = f"<span class='ckpt-badge ckpt-badge-shared'>{shared}</span>"
        shared_count = len(shared_set)
        unique_count = max(0, len(files) - shared_count)
        summary = (
            f"<span class='ckpt-muted'>{unique_count} files</span> | unique {unique_badge} "
            f"<span class='ckpt-muted'>{shared_count} files</span> | shared {shared_badge}"
        )
        missing_files = self._collect_missing_files_for_node(node)
        primary_paths = node.get("primary_paths", [])
        primary_html = ""
        if primary_paths:
            label = "model path" if len(primary_paths) == 1 else "model paths"
            lines = "<br>".join(html.escape(path) for path in primary_paths)
            primary_html = f"<div class='ckpt-primary'><strong>{label}:</strong><br>{lines}</div>"
        missing_html = self._render_missing_files_block(missing_files) if node.get("type") == "model" and missing_files else ""
        return (
            "<div class='ckpt-view-sticky'><div class='ckpt-view'>"
            f"<h4><strong>{header}</strong></h4>"
            f"{primary_html}"
            f"<div class='ckpt-view-summary'>{summary}</div>"
            f"{file_tree_html}{missing_html}</div></div>"
        )

    def _build_missing_view_html(self, node):
        missing_files = self._collect_missing_files_for_node(node)
        label_text = node["label"]
        path_label = node.get("path_label") or label_text
        header = html.escape(path_label)
        file_tree_html = self._render_missing_files_block(missing_files, show_title=True)
        return (
            "<div class='ckpt-view-sticky'><div class='ckpt-view'>"
            f"<h4><strong>{header}</strong></h4>"
            f"{file_tree_html}</div></div>"
        )

    def _collect_missing_files_for_node(self, node):
        missing_files = set(node.get("missing_files", set()))
        if node.get("type") == "model":
            model_types = list(node.get("models", set()))
            if len(model_types) == 1:
                model_type = model_types[0]
                for variant in self._ordered_variants_for_display(model_type):
                    missing_files.update(variant.get("missing_files", set()))
        return sorted(missing_files, key=lambda path: self._display_path(path).lower())

    def _render_missing_files_block(self, missing_files, show_title=True):
        if not missing_files:
            return "<div class='ckpt-file-tree'><div class='ckpt-muted'>No missing files.</div></div>"
        file_rows = []
        for path in missing_files:
            display_path = html.escape(self._display_path(path))
            full_path = html.escape(path)
            file_rows.append(
                f"<div class='ckpt-file-row missing-only' title='{full_path}'>"
                f"<span class='ckpt-file-name'>{display_path}</span>"
                "</div>"
            )
        title_html = (
            f"<div class='ckpt-view-summary ckpt-missing-summary'><span class='ckpt-muted'>{len(missing_files)} files</span> | missing</div>"
            if show_title
            else ""
        )
        return f"{title_html}<div class='ckpt-file-tree'>{''.join(file_rows)}</div>"

    def _build_file_tree(self, files, shared_set, shared_info=None):
        if shared_info is None:
            shared_info = {}
        root = {
            "children": {},
            "files": [],
            "size": 0,
            "all_shared": False,
            "all_unique": False,
            "has_files": False,
            "shared_with": set(),
            "shared_count": 0,
            "is_root": True,
        }
        for path in files:
            info = self._file_info.get(path, {})
            display = self._display_path(path)
            parts = self._split_display_path(display)
            name = parts[-1] if parts else display
            shared_with = shared_info.get(path, [])
            entry = {
                "path": path,
                "display": display,
                "name": name,
                "size": info.get("size", 0),
                "exists": info.get("exists", False),
                "shared": path in shared_set,
                "shared_with": shared_with,
            }

            node = root
            for part in parts[:-1]:
                node = node["children"].setdefault(
                    part,
                    {
                        "children": {},
                        "files": [],
                        "size": 0,
                        "all_shared": False,
                        "all_unique": False,
                        "has_files": False,
                        "shared_with": set(),
                        "shared_count": 0,
                        "is_root": False,
                    },
                )
            node["files"].append(entry)

        self._compute_folder_sizes(root)
        return root

    def _compute_folder_sizes(self, node):
        total = sum(entry["size"] for entry in node["files"])
        has_files = len(node["files"]) > 0
        all_shared = all(entry["shared"] for entry in node["files"]) if node["files"] else True
        all_unique = all(not entry["shared"] for entry in node["files"]) if node["files"] else True
        shared_with = set()
        shared_count = 0
        for entry in node["files"]:
            if entry.get("shared"):
                shared_with.update(entry.get("shared_with", []))
        for child in node["children"].values():
            (
                child_total,
                child_has_files,
                child_all_shared,
                child_shared_with,
                child_shared_count,
                child_all_unique,
            ) = self._compute_folder_sizes(child)
            total += child_total
            has_files = has_files or child_has_files
            all_shared = all_shared and child_all_shared
            all_unique = all_unique and child_all_unique
            shared_with.update(child_shared_with)
            shared_count += child_shared_count
        node["size"] = total
        node["has_files"] = has_files
        node["all_shared"] = has_files and all_shared
        node["all_unique"] = has_files and all_unique
        node["shared_with"] = shared_with
        if node["all_shared"] and not node.get("is_root"):
            shared_count += 1
        node["shared_count"] = shared_count
        return total, has_files, node["all_shared"], shared_with, shared_count, node["all_unique"]

    def _render_file_tree(self, node):
        open_shared_path = node.get("shared_count", 0) == 1
        parts = []
        for name in sorted(node["children"].keys(), key=lambda n: n.lower()):
            parts.append(
                self._render_folder(
                    name,
                    node["children"][name],
                    level=0,
                    open_shared_path=open_shared_path,
                )
            )

        for entry in sorted(node["files"], key=lambda e: e["name"].lower()):
            parts.append(self._render_file_entry(entry))

        return "<div class='ckpt-file-tree'>" + "".join(parts) + "</div>"

    def _render_folder(self, name, node, level, open_shared_path=False):
        name, node = self._collapse_single_child_folder(name, node)
        folder_name = html.escape(name)
        size_label = self._format_size(node.get("size", 0))
        shared_tag = ""
        shared_tooltip = ""
        folder_class = "ckpt-folder"
        if node.get("all_shared"):
            shared_tag = "<span class='ckpt-shared-tag ckpt-folder-tag'>Shared</span>"
            folder_class += " shared"
            shared_with = sorted(node.get("shared_with", []), key=lambda value: value.lower())
            if shared_with:
                lines = "".join(
                    f"<div>{html.escape(label)}</div>" for label in shared_with
                )
                shared_tooltip = (
                    "<div class='ckpt-shared-tooltip'>"
                    "<div class='ckpt-shared-tooltip-title'>Shared with</div>"
                    f"{lines}</div>"
                )
        elif node.get("all_unique"):
            folder_class += " unique"
        open_attr = ""
        if level < 1 or (open_shared_path and node.get("shared_count", 0) == 1):
            open_attr = " open"
        children_html = []
        for child_name in sorted(node["children"].keys(), key=lambda n: n.lower()):
            children_html.append(
                self._render_folder(
                    child_name,
                    node["children"][child_name],
                    level + 1,
                    open_shared_path=open_shared_path,
                )
            )
        for entry in sorted(node["files"], key=lambda e: e["name"].lower()):
            children_html.append(self._render_file_entry(entry))
        return (
            f"<details class='{folder_class}'{open_attr}>"
            "<summary>"
            f"<span class='ckpt-folder-name'>{folder_name}</span>"
            f"{shared_tag}"
            f"<span class='ckpt-folder-size'>{size_label}</span>"
            f"{shared_tooltip}"
            "</summary>"
            "<div class='ckpt-folder-children'>"
            + "".join(children_html)
            + "</div></details>"
        )

    def _collapse_single_child_folder(self, name, node):
        parts = [name]
        current = node
        while True:
            if current.get("files"):
                break
            children = current.get("children", {})
            if len(children) != 1:
                break
            child_name, child_node = next(iter(children.items()))
            parts.append(child_name)
            current = child_node
        return os.sep.join(parts), current

    def _render_file_entry(self, entry):
        classes = ["ckpt-file-row"]
        if entry.get("shared"):
            classes.append("shared")
        else:
            classes.append("unique")
        if not entry.get("exists"):
            classes.append("missing")
        class_attr = " ".join(classes)
        name = html.escape(entry.get("name", ""))
        size_label = self._format_size(entry.get("size", 0))
        display = html.escape(entry.get("display", ""))
        shared_tag = "<span class='ckpt-shared-tag'>Shared</span>" if entry.get("shared") else ""
        shared_tooltip = ""
        if entry.get("shared"):
            shared_with = entry.get("shared_with", [])
            if shared_with:
                lines = "".join(
                    f"<div>{html.escape(label)}</div>" for label in shared_with
                )
                shared_tooltip = (
                    "<div class='ckpt-shared-tooltip'>"
                    "<div class='ckpt-shared-tooltip-title'>Shared with</div>"
                    f"{lines}</div>"
                )
        return (
            f"<div class='{class_attr}' title='{display}'>"
            f"<span class='ckpt-file-name'>{name}</span>"
            f"{shared_tag}"
            f"<span class='ckpt-file-size'>{size_label}</span>"
            f"{shared_tooltip}"
            "</div>"
        )

    def _split_display_path(self, display_path):
        drive, rest = os.path.splitdrive(display_path)
        if drive:
            drive = drive.rstrip("\\/")
        rest = rest.replace("\\", "/")
        parts = [part for part in rest.split("/") if part]
        if drive:
            return [drive] + parts
        if display_path.startswith("\\\\"):
            return ["UNC"] + parts
        return parts

    def _add_file(self, files_set, value, force_folder=None, lora_dir=None):
        path = self._resolve_path(value, force_folder=force_folder, lora_dir=lora_dir)
        if path and os.path.isfile(path):
            files_set.add(path)

    def _download_root_name(self):
        root = fl.get_download_location()
        if not root or os.path.isabs(root):
            return ""
        norm = os.path.normpath(root)
        if not norm or norm == ".":
            return ""
        return norm.split(os.sep)[0]

    def _resolve_repo_relative_path(self, rel_path):
        if not rel_path:
            return None
        rel_path = str(rel_path)
        if rel_path.startswith("http"):
            return None
        root_name = self._download_root_name()
        if not root_name:
            return None
        rel_norm = os.path.normpath(rel_path)
        parts = rel_norm.split(os.sep)
        if parts and parts[0].lower() == root_name.lower():
            return self._normalize_path(os.path.join(self._repo_root, rel_norm))
        return None

    def _get_local_model_filename(self, model_filename, use_locator=True, extra_paths=None, lora_dir=None):
        try:
            return fl.get_local_model_filename(model_filename, use_locator=use_locator, extra_paths=extra_paths, lora_dir=lora_dir)
        except Exception:
            return None

    def _locator_path(self, value, lora_dir=None):
        if not value:
            return ""
        if not isinstance(value, str):
            value = str(value)
        try:
            return fl.extract_alternate_path(value, lora_dir=lora_dir)
        except Exception:
            source_value = value.split("|", 1)[0]
            return os.path.basename(source_value) if source_value.startswith("http") else source_value

    def _locator_basename(self, value, lora_dir=None):
        path = self._locator_path(value, lora_dir=lora_dir)
        return os.path.basename(path) if path else ""

    def _download_location(self, value, force_folder=None, lora_dir=None):
        try:
            return fl.get_download_location(value, force_path=force_folder, lora_dir=lora_dir)
        except Exception:
            source_value = str(value).split("|", 1)[0]
            filename = os.path.basename(source_value) if source_value.startswith("http") else source_value
            return fl.get_download_location(filename, force_path=force_folder)

    def _resolve_path(self, value, force_folder=None, lora_dir=None):
        if not value:
            return None
        if not isinstance(value, str):
            value = str(value)
        if os.path.isabs(value):
            abs_path = self._normalize_path(value)
            if os.path.isfile(abs_path):
                return abs_path
            basename = os.path.basename(abs_path)
            return abs_path
        repo_rel = self._resolve_repo_relative_path(value)
        if repo_rel:
            return repo_rel

        local_path = self._get_local_model_filename(value, extra_paths=force_folder, lora_dir=lora_dir)
        if local_path:
            return self._normalize_path(local_path)

        expected = self._download_location(value, force_folder=force_folder, lora_dir=lora_dir)
        return self._normalize_path(expected)

    def _resolve_download_relpath(self, rel_path):
        if rel_path and os.path.isabs(rel_path):
            return self._normalize_path(rel_path)
        repo_rel = self._resolve_repo_relative_path(rel_path)
        if repo_rel:
            return repo_rel
        local = fl.locate_file(rel_path, error_if_none=False)
        if local:
            return self._normalize_path(local)
        return self._normalize_path(fl.get_download_location(rel_path))

    def _resolve_folder_path(self, source_folder, target_folder):
        parts = []
        if target_folder:
            parts.append(target_folder)
        if source_folder:
            parts.append(source_folder)
        rel_path = os.path.join(*parts) if parts else ""
        if rel_path and os.path.isabs(rel_path):
            return self._normalize_path(rel_path)
        repo_rel = self._resolve_repo_relative_path(rel_path)
        if repo_rel:
            return repo_rel
        if not rel_path:
            return self._normalize_path(fl.get_download_location())
        folder = fl.locate_folder(rel_path, error_if_none=False)
        if folder:
            return self._normalize_path(folder)
        return self._normalize_path(fl.get_download_location(rel_path))

    def _list_folder_files(self, folder_path):
        files = set()
        if not folder_path or not os.path.isdir(folder_path):
            return files
        for root, _, filenames in os.walk(folder_path):
            for name in filenames:
                files.add(self._normalize_path(os.path.join(root, name)))
        return files

    def _combine_download_path(self, target_folder, source_folder, filename):
        parts = []
        if target_folder:
            parts.append(target_folder)
        if source_folder:
            parts.append(source_folder)
        parts.append(filename)
        return os.path.join(*parts)

    def _compute_list(self, filename):
        if not filename:
            return []
        return [os.path.basename(str(filename))]

    def _ensure_list(self, value):
        if value is None:
            return []
        if isinstance(value, list):
            return value
        return [value]

    def _safe_get_lora_dir(self, model_type):
        try:
            return self.get_lora_dir(model_type)
        except Exception:
            return None

    def _normalize_path(self, path):
        return os.path.normcase(os.path.abspath(path))

    def _format_gb(self, size_bytes):
        size_gb = size_bytes / (1024 ** 3)
        return f"{size_gb:.2f} GB"

    def _format_size(self, size_bytes):
        if size_bytes >= 1024 ** 3:
            return f"{size_bytes / (1024 ** 3):.2f} GB"
        if size_bytes >= 1024 ** 2:
            return f"{size_bytes / (1024 ** 2):.1f} MB"
        if size_bytes >= 1024:
            return f"{size_bytes / 1024:.1f} KB"
        return f"{size_bytes} B"

    def _display_path(self, path):
        try:
            rel = os.path.relpath(path, self._repo_root)
            if not rel.startswith(".."):
                return rel
        except Exception:
            pass
        return path

    def _get_model_roots(self):
        roots = []
        for root in getattr(fl, "_models_paths", []) or []:
            if not root:
                continue
            if os.path.isabs(root):
                abs_root = root
            else:
                abs_root = os.path.join(self._repo_root, root)
            abs_root = self._normalize_path(abs_root)
            if abs_root not in roots:
                roots.append(abs_root)
        return roots

    def _build_basename_index(self, basenames, min_size=1):
        basenames = {name.lower() for name in basenames if name}
        if not basenames:
            return {}
        found = {}
        remaining = set(basenames)
        for root in self._get_model_roots():
            if not remaining:
                break
            if not os.path.isdir(root):
                continue
            for name in list(remaining):
                candidate = os.path.join(root, name)
                if os.path.isfile(candidate):
                    try:
                        if os.path.getsize(candidate) < min_size:
                            continue
                    except OSError:
                        continue
                    found[name] = self._normalize_path(candidate)
                    remaining.discard(name)
            if not remaining:
                break
            if os.path.normcase(root) == os.path.normcase(self._repo_root):
                continue
            for dirpath, _, filenames in os.walk(root):
                for filename in filenames:
                    key = filename.lower()
                    if key in remaining:
                        full_path = os.path.join(dirpath, filename)
                        try:
                            if os.path.getsize(full_path) < min_size:
                                continue
                        except OSError:
                            continue
                        found[key] = self._normalize_path(full_path)
                        remaining.discard(key)
                        if not remaining:
                            break
                if not remaining:
                    break
        return found

    def _find_in_model_roots(self, basename, min_size=1):
        if not basename:
            return None
        key = basename.lower()
        if key in self._basename_index:
            return self._basename_index.get(key)
        found = self._build_basename_index({basename}, min_size=min_size)
        if found:
            self._basename_index.update(found)
            return self._basename_index.get(key)
        self._basename_index[key] = None
        return None

    def _repair_missing_paths(self):
        missing = []
        for path in self._file_usage_models.keys():
            try:
                size = os.path.getsize(path) if os.path.isfile(path) else -1
            except OSError:
                size = -1
            if size <= 0:
                missing.append(path)
        if not missing:
            return
        basenames = {os.path.basename(path) for path in missing}
        found = self._build_basename_index(basenames, min_size=1)
        if not found:
            return
        for old_path in list(missing):
            key = os.path.basename(old_path).lower()
            new_path = found.get(key)
            if not new_path or new_path == old_path:
                continue
            self._swap_path(old_path, new_path)

    def _swap_path(self, old_path, new_path):
        usage_ids = self._file_usage.pop(old_path, set())
        if usage_ids:
            self._file_usage[new_path].update(usage_ids)
            for usage_id in usage_ids:
                files = self._usage_files.get(usage_id)
                if files is not None:
                    files.discard(old_path)
                    files.add(new_path)
                model_type = self._variant_to_model.get(usage_id)
                if model_type:
                    for variant in self._model_variants.get(model_type, []):
                        if variant.get("id") != usage_id:
                            continue
                        files_set = variant.get("files")
                        if isinstance(files_set, set):
                            files_set.discard(old_path)
                            files_set.add(new_path)
                        primary_paths = variant.get("primary_paths", [])
                        variant["primary_paths"] = [
                            new_path if path == old_path else path for path in primary_paths
                        ]
                        break

        model_types = self._file_usage_models.pop(old_path, set())
        if model_types:
            self._file_usage_models[new_path].update(model_types)
            for model_type in model_types:
                files = self._model_files.get(model_type)
                if files is not None:
                    files.discard(old_path)
                    files.add(new_path)
                primary_paths = self._model_primary.get(model_type)
                if primary_paths:
                    self._model_primary[model_type] = [
                        new_path if path == old_path else path for path in primary_paths
                    ]
