<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{{ url_for('static', filename='font-awesome.css') }}">
    <title>UVR 人声分离</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            background: #f5f5f5; color: #333; height: 100vh; overflow: hidden; display: flex;
        }
        .left-panel {
            width: 480px; flex-shrink: 0; display: flex; flex-direction: column;
            border-right: 1px solid #e8e8e8; background: #fff; height: 100vh; overflow-y: auto;
        }
        .left-panel::-webkit-scrollbar { width: 4px; }
        .left-panel::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; }
        .input-section { padding: 20px 24px 28px; }

        .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
        .header h1 { font-size: 18px; font-weight: 700; color: #1a1a1a; letter-spacing: -0.01em; }
        .settings-icon {
            width: 32px; height: 32px; border: none; background: transparent;
            color: #999; font-size: 15px; cursor: pointer; border-radius: 8px;
            display: flex; align-items: center; justify-content: center; transition: all 0.15s;
        }
        .settings-icon:hover { background: #f5f5f5; color: #333; }
        .subtitle { color: #aaa; font-size: 12px; margin-bottom: 16px; }

        .section-card {
            background: #fafafa; border: 1px solid #f0f0f0; border-radius: 10px;
            padding: 14px 16px; margin-bottom: 12px; flex: 1; min-width: 0;
        }
        .card-pair { display: flex; gap: 8px; margin-bottom: 8px; }
        .card-pair .section-card { margin-bottom: 0; }
        .section-title {
            font-size: 11px; font-weight: 600; color: #999; text-transform: uppercase;
            letter-spacing: 0.06em; margin-bottom: 12px; display: flex; align-items: center; gap: 6px;
        }
        .section-title i { font-size: 11px; }
        .section-title .save-btn {
            margin-left: auto; padding: 3px 10px; border: 1px solid #e0e0e0;
            border-radius: 5px; background: #fff; color: #666; font-size: 11px;
            cursor: pointer; transition: all 0.15s;
        }
        .section-title .save-btn:hover { background: #f0f0f0; color: #333; }

        .input-group { margin-bottom: 10px; position: relative; }
        .input-group:last-child { margin-bottom: 0; }
        .input-group label { display: block; font-size: 12px; font-weight: 500; margin-bottom: 4px; color: #555; }
        .input-group .input-wrap { position: relative; }
        .input-group .input-wrap i {
            position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
            color: #c0c0c0; font-size: 12px; pointer-events: none;
        }
        .input-group input, .input-group select {
            width: 100%; padding: 7px 10px 7px 32px;
            border: 1px solid #e0e0e0; border-radius: 6px;
            background: #fff; color: #333; font-size: 13px;
            outline: none; transition: all 0.15s ease;
        }
        .input-group input::placeholder { color: #c0c0c0; }
        .input-group input:focus, .input-group select:focus {
            border-color: #0078d4; box-shadow: 0 0 0 2px rgba(0,120,212,0.06);
        }
        .input-group.no-icon input, .input-group.no-icon select { padding-left: 10px; }
        .path-row { display: flex; gap: 6px; align-items: flex-end; }
        .path-row select { width: 90px; flex-shrink: 0; padding: 7px 6px; font-size: 12px; }
        .path-row input { flex: 1; padding-left: 10px !important; }

        /* 上传区 */
        .upload-area {
            border: 2px dashed #e0e0e0; border-radius: 10px;
            padding: 24px 16px; text-align: center; cursor: pointer;
            transition: all 0.2s; background: #fff;
        }
        .upload-area:hover, .upload-area.dragover {
            border-color: #0078d4; background: #f0f7ff;
        }
        .upload-area i { font-size: 24px; color: #ccc; margin-bottom: 6px; display: block; }
        .upload-area .label { font-size: 13px; color: #999; }
        .upload-area .filename { font-size: 13px; color: #0078d4; font-weight: 600; margin-top: 4px; }
        .upload-area input[type="file"] { display: none; }

        /* 参数 */
        .slider-group { margin-bottom: 10px; }
        .slider-group:last-child { margin-bottom: 0; }
        .slider-label {
            display: flex; justify-content: space-between; align-items: center;
            font-size: 12px; font-weight: 500; margin-bottom: 4px; color: #555;
        }
        .slider-value { font-weight: 600; color: #0078d4; font-size: 12px; }
        input[type="range"] {
            width: 50%; height: 4px; -webkit-appearance: none; appearance: none;
            background: #e0e0e0; border-radius: 2px; outline: none;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none; width: 14px; height: 14px;
            background: #0078d4; border-radius: 50%; cursor: pointer;
        }
        .param-desc { font-size: 11px; color: #aaa; margin-top: 2px; line-height: 1.4; }

        /* 复选框 */
        .checkbox-group {
            display: flex; align-items: center; gap: 10px;
            margin-bottom: 10px; font-size: 12px; color: #555; cursor: pointer;
            padding: 8px 10px; background: #fff; border: 1px solid #e9ecef;
            border-radius: 6px; transition: all 0.15s ease;
        }
        .checkbox-group:hover { background: #f8f9fa; border-color: #dee2e6; }
        .checkbox-group:last-child { margin-bottom: 0; }
        .checkbox-group input[type="checkbox"] {
            width: 18px; height: 18px; accent-color: #0078d4; cursor: pointer;
        }
        .checkbox-group label { cursor: pointer; flex: 1; }

        /* 架构切换 */
        .arch-tabs {
            display: flex; gap: 4px; margin-bottom: 12px;
        }
        .arch-tab {
            flex: 1; padding: 8px 0; text-align: center;
            border: 1px solid #e0e0e0; border-radius: 6px;
            font-size: 12px; font-weight: 600; color: #999;
            cursor: pointer; transition: all 0.15s;
            background: #fff;
        }
        .arch-tab.active {
            border-color: #0078d4; color: #0078d4;
            background: #f0f7ff;
        }
        .arch-tab:hover:not(.active) { background: #fafafa; color: #666; }

        /* 架构参数区域 */
        .arch-params {
            margin-top: 12px;
            padding-top: 12px;
            border-top: 1px solid #e9ecef;
        }

        .arch-params-label {
            font-size: 11px;
            font-weight: 600;
            color: #0078d4;
            text-transform: uppercase;
            letter-spacing: 0.05em;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .arch-params-label i { font-size: 10px; }

        .arch-params .input-group {
            margin-bottom: 10px;
        }

        .arch-params .input-group:last-child {
            margin-bottom: 0;
        }

        .arch-params .param-desc {
            font-size: 11px;
            color: #999;
            margin-top: 4px;
            margin-bottom: 10px;
            line-height: 1.4;
        }

        /* 按钮 */
        .btn {
            padding: 7px 14px; border: none; border-radius: 6px; font-size: 12px;
            font-weight: 600; cursor: pointer; transition: all 0.12s ease;
            display: inline-flex; align-items: center; gap: 5px;
        }
        .btn-primary { background: #0078d4; color: #fff; }
        .btn-primary:hover:not(:disabled) { background: #006cbd; }
        .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
        .btn-outline { background: #fff; color: #666; border: 1px solid #e0e0e0; }
        .btn-outline:hover { background: #f5f5f5; color: #333; }
        .btn-sm { padding: 5px 10px; font-size: 11px; }
        .btn-block { width: 100%; justify-content: center; }

        .action-btn {
            width: 100%; padding: 10px 18px;
            background: linear-gradient(135deg, #0078d4, #005fa3);
            color: #fff; border: none; border-radius: 8px;
            font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.15s ease;
            display: flex; align-items: center; justify-content: center; gap: 8px;
            margin-top: 14px; box-shadow: 0 2px 8px rgba(0,120,212,0.2);
        }
        .action-btn:hover:not(:disabled) {
            background: linear-gradient(135deg, #006cbd, #004e8a);
            box-shadow: 0 4px 12px rgba(0,120,212,0.3); transform: translateY(-1px);
        }
        .action-btn:active:not(:disabled) { transform: translateY(0); }
        .action-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }

        /* 右侧面板 */
        .right-panel {
            flex: 1; background: #fff;
            display: flex; flex-direction: column;
            height: 100vh;
        }
        .right-header {
            padding: 20px 24px 16px;
            display: flex; align-items: center; justify-content: space-between;
            border-bottom: 1px solid #e8e8e8;
        }
        .right-header h2 { font-size: 15px; font-weight: 600; color: #333; }
        .right-body {
            flex: 1; overflow-y: auto; padding: 16px 24px 24px; min-height: 0;
        }

        /* 日志区域 */
        .log-area {
            flex: 1; flex-shrink: 0; display: flex; flex-direction: column;
            background: #1e1e1e; color: #d4d4d4; border-top: 1px solid #3e3e3e;
            min-height: 0;
        }
        .log-header {
            display: flex; align-items: center; justify-content: space-between;
            padding: 6px 10px; background: #2d2d2d; border-bottom: 1px solid #3e3e3e;
            font-size: 11px; color: #888; flex-shrink: 0;
        }
        .log-header i { margin-right: 4px; }
        .log-clear-btn {
            background: none; border: 1px solid #555; color: #999;
            border-radius: 4px; padding: 2px 8px; font-size: 10px;
            cursor: pointer; transition: all 0.15s;
        }
        .log-clear-btn:hover { background: #3e3e3e; color: #ccc; border-color: #777; }
        .log-content {
            flex: 1; overflow-y: auto; padding: 6px 10px;
            font-size: 11px; font-family: Consolas, "Courier New", monospace; line-height: 1.6;
        }
        .log-content::-webkit-scrollbar { width: 4px; }
        .log-content::-webkit-scrollbar-thumb { background: #555; border-radius: 2px; }

        /* 进度条 */
        .progress-bar {
            width: 100%; height: 8px; background: #f0f0f0; border-radius: 4px;
            overflow: hidden; margin: 12px 0;
        }
        .progress-fill {
            height: 100%; background: linear-gradient(90deg, #0078d4, #0a7a1a);
            border-radius: 4px; transition: width 0.3s;
        }
        .status-text { font-size: 12px; color: #999; margin: 8px 0; }

        /* 空状态 */
        .empty-state {
            display: flex; flex-direction: column; align-items: center;
            justify-content: center; height: 100%; color: #ccc;
        }
        .empty-state i { font-size: 48px; margin-bottom: 16px; }
        .empty-state p { font-size: 14px; }

        /* 音轨行 */
        .stem-row {
            display: flex; align-items: center; gap: 12px;
            padding: 12px 14px; border: 1px solid #f0f0f0;
            border-radius: 8px; margin-bottom: 8px; background: #fafafa;
        }
        .stem-icon {
            width: 36px; height: 36px; border-radius: 8px;
            display: flex; align-items: center; justify-content: center;
            font-size: 16px; color: #fff;
        }
        .stem-icon.vocals { background: linear-gradient(135deg, #0078d4, #005fa3); }
        .stem-icon.instrumental { background: linear-gradient(135deg, #0a7a1a, #065f12); }
        .stem-icon.other { background: linear-gradient(135deg, #f57c00, #e65100); }
        .stem-info { width: 180px; flex-shrink: 0; }
        .stem-name { font-size: 14px; font-weight: 600; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .stem-filename { font-size: 11px; color: #999; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .stem-actions { display: flex; gap: 6px; }
        .stem-btn {
            width: 32px; height: 32px; border: 1px solid #e0e0e0; border-radius: 6px;
            background: #fff; color: #666; cursor: pointer; display: flex;
            align-items: center; justify-content: center; font-size: 13px;
            transition: all 0.15s;
        }
        .stem-btn:hover { background: #f0f0f0; color: #333; border-color: #ccc; }
        .stem-btn.play-btn:hover { color: #0078d4; border-color: #0078d4; }

        /* 音频播放器 */
        .stem-player {
            display: none; align-items: center; gap: 8px;
            flex: 1; min-width: 0;
        }
        .stem-player.active { display: flex; }
        .player-btn {
            width: 26px; height: 26px; border-radius: 50%; border: none;
            background: #0078d4; color: #fff; cursor: pointer; display: flex;
            align-items: center; justify-content: center; font-size: 10px;
            flex-shrink: 0; transition: all 0.15s;
        }
        .player-btn:hover { background: #006cbd; }
        .player-btn.playing { background: #e53935; }
        .player-btn.playing:hover { background: #c62828; }
        .player-progress {
            width: 200px; flex-shrink: 0; height: 4px;
            -webkit-appearance: none; appearance: none;
            background: #e0e0e0; border-radius: 2px; outline: none; cursor: pointer;
        }
        .player-progress::-webkit-slider-thumb {
            -webkit-appearance: none; width: 12px; height: 12px;
            background: #0078d4; border-radius: 50%; cursor: pointer;
        }
        .player-time {
            font-size: 11px; color: #999; flex-shrink: 0;
            font-variant-numeric: tabular-nums; min-width: 70px; text-align: right;
        }

        /* 顶部操作栏 */
        .top-actions {
            display: flex; gap: 8px; margin-bottom: 16px;
        }
        .top-btn {
            padding: 7px 14px; border: 1px solid #e0e0e0; border-radius: 6px;
            background: #fff; color: #666; font-size: 12px; font-weight: 600;
            cursor: pointer; display: flex; align-items: center; gap: 5px;
            transition: all 0.15s;
        }
        .top-btn:hover { background: #f0f0f0; color: #333; border-color: #ccc; }
        .top-btn.primary { background: #0078d4; color: #fff; border-color: #0078d4; }
        .top-btn.primary:hover { background: #006cbd; }

        /* Toast */
        .toast-container { position: fixed; top: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
        .toast {
            padding: 10px 16px; border-radius: 8px; font-size: 13px; font-weight: 500;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15); animation: slideIn 0.3s ease;
            max-width: 360px;
        }
        .toast.success { background: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; }
        .toast.error { background: #fce4ec; color: #c62828; border: 1px solid #f8bbd0; }
        .toast.info { background: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb; }
        @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

        @media (max-width: 768px) {
            body { flex-direction: column; height: auto; overflow: auto; }
            .left-panel { width: 100% !important; height: auto; border-right: none; border-bottom: 1px solid #e8e8e8; overflow-y: visible; }
            .right-panel { height: auto; min-height: 50vh; }
            .input-section { padding: 14px 16px 20px; }
        }

        /* 设置弹窗 */
        .modal-overlay {
            display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.4); z-index: 100;
            justify-content: center; align-items: flex-start; padding-top: 80px;
        }
        .modal-overlay.show { display: flex; }
        .settings-modal {
            background: #fff; border-radius: 12px; width: 440px;
            box-shadow: 0 12px 40px rgba(0,0,0,0.2); overflow: hidden;
        }
        .modal-header {
            padding: 16px 20px; border-bottom: 1px solid #eee;
            display: flex; align-items: center; justify-content: space-between;
        }
        .modal-header h3 { font-size: 15px; font-weight: 600; color: #333; }
        .modal-close {
            width: 28px; height: 28px; border: none; background: #f5f5f5;
            border-radius: 6px; cursor: pointer; font-size: 14px; color: #999;
            display: flex; align-items: center; justify-content: center;
        }
        .modal-close:hover { background: #eee; color: #333; }
        .modal-body { padding: 20px; }
        .modal-footer {
            padding: 12px 20px; border-top: 1px solid #eee;
            display: flex; justify-content: flex-end; gap: 8px;
        }
        .modal-save-btn {
            padding: 7px 20px; font-size: 12px; border: none; border-radius: 6px;
            background: #0078d4; color: #fff; cursor: pointer; font-weight: 600;
            transition: all 0.15s;
        }
        .modal-save-btn:hover { background: #006cbd; }
    </style>
</head>
<body>
    <div class="toast-container" id="toastContainer"></div>

    <!-- 左侧：设置面板 -->
    <div class="left-panel">
        <div class="input-section">
            <div class="header">
                <h1><i class="fas fa-microphone" style="color:#0078d4;font-size:16px;margin-right:6px;"></i>UVR 人声分离</h1>
                <button class="settings-icon" onclick="openSettings()"><i class="fas fa-wrench"></i></button>
            </div>
            <p class="subtitle">基于 Ultimate Vocal Remover 的音频源分离</p>

            <!-- 上传音频 -->
            <div class="section-card">
                <div class="section-title"><i class="fas fa-file"></i> 上传音频</div>
                <div class="upload-area" id="uploadArea" onclick="document.getElementById('audioInput').click()">
                    <i class="fas fa-folder-open"></i>
                    <div class="label" id="uploadLabel">点击或拖拽音频文件到此处</div>
                    <input type="file" id="audioInput" accept=".wav,.flac,.ogg,.mp3,.aac,.m4a,.wma,.aiff,.opus">
                </div>
            </div>

            <!-- 模型 & 参数（合并卡片） -->
            <div class="section-card">
                <div class="section-title"><i class="fas fa-wrench"></i> 模型 & 参数
                    <button class="save-btn" onclick="saveConfig()"><i class="fas fa-check"></i> 保存</button>
                </div>

                <div class="input-group">
                    <label>选择架构</label>
                    <select id="modelArchSelect" onchange="switchArch(this)" style="width:100%;padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;transition:all 0.15s ease;">
                        <option value="MDX-Net">MDX-Net</option>
                        <option value="VR Arc">VR Arc</option>
                        <option value="Demucs">Demucs</option>
                    </select>
                </div>

                <div class="input-group">
                    <label>选择模型</label>
                    <select id="modelSelect" onchange="onModelSelect(this)" style="width:100%;padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;transition:all 0.15s ease;">
                        <option value="">-- 请先扫描模型目录 --</option>
                    </select>
                </div>

                <div id="selectedModelInfo" style="display: none; margin-top: 10px; padding: 10px; background: #f0f7ff; border: 1px solid #b3d4fc; border-radius: 6px; font-size: 12px;">
                    <div style="display:flex;align-items:center;gap:6px;">
                        <i class="fas fa-check-circle" style="color:#2e7d32;font-size:14px;"></i>
                        <span id="selectedModelName" style="font-weight:600;color:#1a1a1a;"></span>
                    </div>
                    <div id="selectedModelPath" style="color:#666;font-size:11px;margin-top:4px;padding-left:20px;word-break:break-all;"></div>
                </div>

                <!-- 架构参数区域 -->
                <div id="archParamsContainer">
                    <!-- Demucs 参数 -->
                    <div id="demucsParams" class="arch-params">
                        <div class="arch-params-label"><i class="fas fa-cog"></i> Demucs 参数</div>
                        <div class="input-group">
                            <label>Choose Stem（选择音轨）</label>
                            <select id="demucsStemSelect" onchange="updateStemOnlyState()" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                                <option value="All Stems">All Stems（所有音轨）</option>
                                <option value="Vocals">Vocals（人声）</option>
                                <option value="Bass">Bass（贝斯）</option>
                                <option value="Drums">Drums（鼓点）</option>
                                <option value="Other">Other（其他）</option>
                            </select>
                            <div class="param-desc">全部分轨 - 提取所有可用分分轨</div>
                        </div>
                        <div class="input-group">
                            <label>Segment（分割参数大小）</label>
                            <select id="demucsSegment" onchange="toggleCustom(this,'demucsSegmentCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                                <option value="Default">Default</option>
                                <option value="1">1</option><option value="2">2</option><option value="3">3</option>
                                <option value="4">4</option><option value="5">5</option><option value="6">6</option>
                                <option value="7">7</option><option value="8">8</option><option value="10">10</option>
                                <option value="12">12</option><option value="15">15</option><option value="20">20</option>
                                <option value="30">30</option><option value="40">40</option><option value="50">50</option>
                                <option value="__custom__">自定义...</option>
                            </select>
                            <input type="text" id="demucsSegmentCustom" placeholder="输入自定义值" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                            <div class="param-desc">较小的尺寸占用资源较少。较大的尺寸效果可能更好。</div>
                        </div>
                    </div>

                    <!-- MDX-Net 参数 -->
                    <div id="mdxParams" class="arch-params" style="display: none;">
                        <div class="arch-params-label"><i class="fas fa-cog"></i> MDX-Net 参数</div>
                        <div class="input-group">
                            <label>Segment（分割参数大小）</label>
                            <select id="mdxSegmentSize" onchange="toggleCustom(this,'mdxSegmentSizeCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                                <option value="Default">Default</option>
                                <option value="128">128</option><option value="256">256</option>
                                <option value="512">512</option><option value="1024">1024</option>
                                <option value="2048">2048</option><option value="4096">4096</option>
                                <option value="__custom__">自定义...</option>
                            </select>
                            <input type="text" id="mdxSegmentSizeCustom" placeholder="输入自定义值" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                            <div class="param-desc">默认大小为 256。音质可能会因你的选择而有所不同。</div>
                        </div>
                        <div class="input-group">
                            <label>Overlap（重叠度）</label>
                            <select id="mdxOverlap" onchange="toggleCustom(this,'mdxOverlapCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                                <option value="0.100">0.100</option><option value="0.250">0.250</option>
                                <option value="0.500">0.500</option><option value="0.750">0.750</option>
                                <option value="0.990">0.990</option>
                                <option value="__custom__">自定义...</option>
                            </select>
                            <input type="text" id="mdxOverlapCustom" placeholder="输入自定义值，如 0.350" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                            <div class="param-desc">较高的值可能会带来更好的效果，但也会导致处理时间延长。</div>
                        </div>
                    </div>

                    <!-- VR Arc 参数 -->
                    <div id="vrParams" class="arch-params" style="display: none;">
                        <div class="arch-params-label"><i class="fas fa-cog"></i> VR Arc 参数</div>
                        <div class="input-group">
                            <label>Window Size（窗口大小）</label>
                            <select id="vrWindowSize" onchange="toggleCustom(this,'vrWindowSizeCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                                <option value="320">320</option>
                                <option value="512">512</option>
                                <option value="1024">1024</option>
                                <option value="2048">2048</option>
                                <option value="4096">4096</option>
                                <option value="__custom__">自定义...</option>
                            </select>
                            <input type="text" id="vrWindowSizeCustom" placeholder="输入自定义值" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                            <div class="param-desc">选择窗口大小，以平衡音质与处理速度。</div>
                        </div>
                        <div class="input-group">
                            <label>Aggression（分离强度）</label>
                            <select id="vrAggression" onchange="toggleCustom(this,'vrAggressionCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                                <option value="-100">-100</option><option value="-50">-50</option>
                                <option value="-10">-10</option><option value="-5">-5</option>
                                <option value="0">0</option><option value="5">5</option>
                                <option value="10">10</option><option value="50">50</option>
                                <option value="100">100</option>
                                <option value="__custom__">自定义...</option>
                            </select>
                            <input type="text" id="vrAggressionCustom" placeholder="输入自定义值，如 20" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                            <div class="param-desc">数值越大，提取越深入。对于人声和伴奏，通常设置为 5。</div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 通用参数 -->
            <div class="section-card">
                <div class="section-title"><i class="fas fa-th"></i> 通用参数
                    <button class="save-btn" onclick="saveConfig()"><i class="fas fa-check"></i> 保存</button>
                </div>

                <div style="display:flex;flex-direction:column;gap:8px;">
                    <label style="display:flex;align-items:center;gap:8px;font-size:13px;color:#333;cursor:pointer;">
                        <input type="checkbox" id="isGpu" {{ 'checked' if config.is_gpu else '' }}
                               style="width:16px;height:16px;accent-color:#0078d4;cursor:pointer;">
                        <span>使用 GPU 加速</span>
                        <span id="gpuStatus" style="font-size:11px;color:#999;margin-left:auto;"></span>
                    </label>

                    <label style="display:flex;align-items:center;gap:8px;font-size:13px;color:#333;cursor:pointer;">
                        <input type="checkbox" id="isNormalize" {{ 'checked' if config.is_normalization else '' }}
                               style="width:16px;height:16px;accent-color:#0078d4;cursor:pointer;">
                        <span>音频归一化</span>
                    </label>

                    <label id="primaryOnlyRow" style="display:flex;align-items:center;gap:8px;font-size:13px;color:#333;cursor:pointer;">
                        <input type="checkbox" id="isPrimaryOnly" onchange="if(this.checked)document.getElementById('isSecondaryOnly').checked=false"
                               style="width:16px;height:16px;accent-color:#0078d4;cursor:pointer;">
                        <span>仅输出主音轨</span>
                    </label>

                    <label id="secondaryOnlyRow" style="display:flex;align-items:center;gap:8px;font-size:13px;color:#333;cursor:pointer;">
                        <input type="checkbox" id="isSecondaryOnly" onchange="if(this.checked)document.getElementById('isPrimaryOnly').checked=false"
                               style="width:16px;height:16px;accent-color:#0078d4;cursor:pointer;">
                        <span>仅输出副音轨</span>
                    </label>
                </div>

                <div class="input-group" style="margin-top: 12px;">
                    <label>输出格式</label>
                    <select id="saveFormat" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
                        <option value="wav">WAV</option>
                        <option value="flac">FLAC</option>
                        <option value="mp3">MP3</option>
                    </select>
                </div>
            </div>

            <!-- 开始分离 -->
            <button class="action-btn" id="startBtn" onclick="startSeparate()" disabled>
                <i class="fas fa-play"></i> 开始分离
            </button>
        </div>
    </div>

    <!-- 右侧：结果面板 -->
    <div class="right-panel">
        <div class="right-header">
            <h2><i class="fas fa-list" style="margin-right:6px;color:#999;"></i>分离结果</h2>
        </div>
        <div class="right-body" id="rightBody">
            <div class="empty-state" id="emptyState">
                <i class="fas fa-exchange-alt"></i>
                <p>上传音频并选择模型后开始分离</p>
            </div>
            <div id="progressSection" style="display: none;">
                <div class="status-text" id="statusText">正在处理...</div>
                <div class="progress-bar"><div class="progress-fill" id="progressFill" style="width:0%"></div></div>
            </div>
            <div id="resultSection" style="display: none;">
                <div class="top-actions">
                    <button class="top-btn primary" onclick="downloadAll()"><i class="fas fa-folder-plus"></i> 打包下载</button>
                    <button class="top-btn" onclick="cleanupTask()"><i class="fas fa-trash"></i> 清理</button>
                </div>
                <div id="stemsList"></div>
            </div>
        </div>
        <div class="log-area" id="logArea">
            <div class="log-header">
                <span><i class="fas fa-code"></i> 控制台日志</span>
                <button class="log-clear-btn" onclick="clearLogs()" title="清除日志">清除</button>
            </div>
            <div class="log-content" id="logContent"></div>
        </div>
    </div>

    <!-- 设置弹窗 -->
    <div class="modal-overlay" id="settingsModal">
        <div class="settings-modal">
            <div class="modal-header">
                <h3><i class="fas fa-wrench" style="margin-right:6px;color:#999;"></i>UVR 设置</h3>
                <button class="modal-close" onclick="closeSettings()"><i class="fas fa-times-circle"></i></button>
            </div>
            <div class="modal-body">
                <div class="input-group no-icon">
                    <label>项目路径</label>
                    <input type="text" id="uvrPath" placeholder="UVR 项目路径" value="{{ config.uvr_project_path|default('') }}">
                </div>
                <div style="border-top:1px solid #eee;margin:16px 0 12px;padding-top:12px;">
                    <label style="font-size:11px;font-weight:600;color:#999;text-transform:uppercase;letter-spacing:0.06em;display:block;margin-bottom:10px;">模型目录</label>
                </div>
                <div class="input-group">
                    <label>Demucs 模型目录</label>
                    <div class="path-row">
                        <select id="demucsModelDirMode">
                            <option value="absolute">绝对路径</option>
                            <option value="relative">相对路径</option>
                        </select>
                        <input type="text" id="demucsModelDir" placeholder="含 Demucs 模型的文件夹路径">
                    </div>
                </div>
                <div class="input-group">
                    <label>VR Arc 模型目录</label>
                    <div class="path-row">
                        <select id="vrModelDirMode">
                            <option value="absolute">绝对路径</option>
                            <option value="relative">相对路径</option>
                        </select>
                        <input type="text" id="vrModelDir" placeholder="含 VR Arc 模型的文件夹路径">
                    </div>
                </div>
                <div class="input-group">
                    <label>MDX-Net 模型目录</label>
                    <div class="path-row">
                        <select id="mdxModelDirMode">
                            <option value="absolute">绝对路径</option>
                            <option value="relative">相对路径</option>
                        </select>
                        <input type="text" id="mdxModelDir" placeholder="含 MDX-Net 模型的文件夹路径">
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button class="btn btn-outline" onclick="closeSettings()">取消</button>
                <button class="modal-save-btn" onclick="saveSettings()">保存</button>
            </div>
        </div>
    </div>

    <audio id="previewAudio" style="display:none;"></audio>

    <script>
    // ── 状态 ──────────────────────────────────────────────────────────────
    var currentTaskId = null;
    var pollTimer = null;
    var selectedModelPath = '';
    var currentArch = 'Demucs';
    var scannedModels = [];

    // ── 自定义输入切换 ──────────────────────────────────────────────────────
    function toggleCustom(sel, customId) {
        var customEl = document.getElementById(customId);
        if (!customEl) return;
        customEl.style.display = sel.value === '__custom__' ? 'block' : 'none';
        if (sel.value === '__custom__') customEl.focus();
    }

    function getSelectValue(selectId, customId) {
        var sel = document.getElementById(selectId);
        if (!sel) return '';
        if (sel.value === '__custom__') {
            var custom = document.getElementById(customId);
            return custom ? custom.value.trim() : '';
        }
        return sel.value;
    }

    function restoreCustomSelect(selectId, customId, value) {
        var sel = document.getElementById(selectId);
        if (!sel) return;
        // Try matching a preset option
        for (var i = 0; i < sel.options.length; i++) {
            if (sel.options[i].value === value) { sel.selectedIndex = i; return; }
        }
        // No match — use custom
        sel.value = '__custom__';
        var custom = document.getElementById(customId);
        if (custom) { custom.style.display = 'block'; custom.value = value; }
    }

    // ── 设置弹窗 ──────────────────────────────────────────────────────────
    function openSettings() {
        var dirs = window._savedModelDirs || {};
        var dirModes = window._savedModelDirModes || {};
        var uvrPath = document.getElementById('uvrPath');

        if (uvrPath) uvrPath.value = window._savedUvrPath || '';

        ['demucs', 'vr', 'mdx'].forEach(function(k) {
            var mode = document.getElementById(k + 'ModelDirMode');
            var dir = document.getElementById(k + 'ModelDir');
            if (mode) mode.value = dirModes[k] || 'absolute';
            if (dir) dir.value = dirs[k] || '';
        });

        document.getElementById('settingsModal').classList.add('show');
    }

    function closeSettings() {
        document.getElementById('settingsModal').classList.remove('show');
    }

    function saveSettings() {
        var uvrPath = document.getElementById('uvrPath');
        window._savedUvrPath = uvrPath ? uvrPath.value.trim() : '';

        window._savedModelDirs = {};
        window._savedModelDirModes = {};
        ['demucs', 'vr', 'mdx'].forEach(function(k) {
            var dir = document.getElementById(k + 'ModelDir');
            var mode = document.getElementById(k + 'ModelDirMode');
            window._savedModelDirs[k] = dir ? dir.value.trim() : '';
            window._savedModelDirModes[k] = mode ? mode.value : 'absolute';
        });

        updateModelDirDisplay();
        closeSettings();
        saveConfig();
    }

    function updateModelDirDisplay() {
        var dirs = window._savedModelDirs || {};
        var key = archDirKeys[currentArch] || currentArch;
        var dir = dirs[key] || '';
        var modelDirText = document.getElementById('modelDirText');
        var modelArchSelect = document.getElementById('modelArchSelect');

        if (modelDirText) {
            modelDirText.textContent = dir || '未配置模型目录';
            modelDirText.style.color = dir ? '#2e7d32' : '#999';
        }

        if (modelArchSelect) {
            modelArchSelect.value = currentArch;
        }
    }

    // 架构 → 对应的模型目录 config key
    var archDirKeys = {
        'Demucs': 'demucs',
        'VR Arc': 'vr',
        'MDX-Net': 'mdx'
    };

    // ── Toast ─────────────────────────────────────────────────────────────
    function showMessage(text, type, duration) {
        type = type || 'info'; duration = duration || 3000;
        var c = document.getElementById('toastContainer');
        var t = document.createElement('div');
        t.className = 'toast ' + type; t.textContent = text;
        c.appendChild(t);
        setTimeout(function() { t.style.opacity = '0'; t.style.transition = 'opacity 0.3s'; setTimeout(function() { t.remove(); }, 300); }, duration);
    }

    // ── 架构切换 ──────────────────────────────────────────────────────────
    function switchArch(el) {
        // 支持从select元素或tab元素调用
        if (el.tagName === 'SELECT') {
            currentArch = el.value;
        } else {
            document.querySelectorAll('.arch-tab').forEach(function(t) { t.classList.remove('active'); });
            el.classList.add('active');
            currentArch = el.dataset.arch;
        }

        // 同步架构选择器
        var modelArchSelect = document.getElementById('modelArchSelect');
        if (modelArchSelect) {
            modelArchSelect.value = currentArch;
        }

        // 切换架构特异参数面板
        var demucsParams = document.getElementById('demucsParams');
        var mdxParams = document.getElementById('mdxParams');
        var vrParams = document.getElementById('vrParams');
        if (demucsParams) demucsParams.style.display = currentArch === 'Demucs' ? 'block' : 'none';
        if (mdxParams) mdxParams.style.display = currentArch === 'MDX-Net' ? 'block' : 'none';
        if (vrParams) vrParams.style.display = currentArch === 'VR Arc' ? 'block' : 'none';

        // 主/副音轨复选框始终显示
        var primaryOnlyRow = document.getElementById('primaryOnlyRow');
        var secondaryOnlyRow = document.getElementById('secondaryOnlyRow');
        if (primaryOnlyRow) primaryOnlyRow.style.display = 'flex';
        if (secondaryOnlyRow) secondaryOnlyRow.style.display = 'flex';

        // 更新主/副音轨复选框状态
        updateStemOnlyState();

        // MDX-Net/VR Arc 不支持 GPU（需要 cuDNN+ONNX Runtime CUDA）
        var gpuCb = document.getElementById('isGpu');
        if (gpuCb) {
            if (currentArch !== 'Demucs') {
                gpuCb.checked = false;
                gpuCb.disabled = true;
            } else {
                gpuCb.disabled = false;
            }
        }

        // 恢复该架构保存的模型目录
        updateModelDirDisplay();

        // 清空模型选择
        selectedModelPath = '';
        var selectedModelInfo = document.getElementById('selectedModelInfo');
        if (selectedModelInfo) selectedModelInfo.style.display = 'none';
        scannedModels = [];
        var modelSelect = document.getElementById('modelSelect');
        if (modelSelect) {
            modelSelect.innerHTML = '<option value="">-- 请先扫描模型目录 --</option>';
        }
        updateStartBtn();
        // 自动扫描
        autoScanModels();
    }

    function updateStemOnlyState() {
        var demucsStemSelect = document.getElementById('demucsStemSelect');
        var allStems = currentArch === 'Demucs' && demucsStemSelect && demucsStemSelect.value === 'All Stems';
        var pri = document.getElementById('isPrimaryOnly');
        var sec = document.getElementById('isSecondaryOnly');
        if (pri) { pri.disabled = allStems; if (allStems) pri.checked = false; }
        if (sec) { sec.disabled = allStems; if (allStems) sec.checked = false; }
    }

    function autoScanModels() {
        var dirs = window._savedModelDirs || {};
        var key = archDirKeys[currentArch] || currentArch;
        var dir = dirs[key] || '';
        if (!dir) return;
        fetch('/uvr-sep/scan-models', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({model_dir: dir, arch_type: currentArch})
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (!data.success || !data.models || !data.models.length) return;
            scannedModels = data.models;
            var sel = document.getElementById('modelSelect');
            if (!sel) return;
            sel.innerHTML = '<option value="">-- 请选择模型 --</option>';
            scannedModels.forEach(function(m, idx) {
                var opt = document.createElement('option');
                opt.value = idx;
                opt.textContent = m.name + (m.has_meta ? '  [' + m.primary_stem + ']' : '');
                sel.appendChild(opt);
            });
            // 自动选中已保存的模型
            var saved = (window._savedSelectedModels || {})[currentArch] || '';
            if (saved) {
                for (var i = 0; i < scannedModels.length; i++) {
                    if (scannedModels[i].name === saved) {
                        sel.value = String(i);
                        onModelSelect(sel);
                        break;
                    }
                }
            }
        })
        .catch(function() {});
    }

    // ── 上传 ──────────────────────────────────────────────────────────────
    var uploadArea = document.getElementById('uploadArea');
    var audioInput = document.getElementById('audioInput');
    if (uploadArea && audioInput) {
        uploadArea.addEventListener('dragover', function(e) { e.preventDefault(); uploadArea.classList.add('dragover'); });
        uploadArea.addEventListener('dragleave', function() { uploadArea.classList.remove('dragover'); });
        uploadArea.addEventListener('drop', function(e) {
            e.preventDefault(); uploadArea.classList.remove('dragover');
            if (e.dataTransfer.files.length) { audioInput.files = e.dataTransfer.files; showFileInfo(e.dataTransfer.files[0]); }
        });
        audioInput.addEventListener('change', function() { if (audioInput.files.length) showFileInfo(audioInput.files[0]); });
    }

    function showFileInfo(file) {
        var uploadLabel = document.getElementById('uploadLabel');
        if (uploadLabel) uploadLabel.innerHTML = '<span class="filename">' + file.name + '</span>';
        updateStartBtn();
    }

    function updateStartBtn() {
        var hasFile = audioInput && audioInput.files && audioInput.files.length > 0;
        var hasModel = selectedModelPath !== '';
        var startBtn = document.getElementById('startBtn');
        if (startBtn) startBtn.disabled = !(hasFile && hasModel);
    }

    // ── 扫描模型 ──────────────────────────────────────────────────────────
    function scanModels() {
        var dirs = window._savedModelDirs || {};
        var key = archDirKeys[currentArch] || currentArch;
        var dir = dirs[key] || '';
        if (!dir) { showMessage('请先在设置中配置' + currentArch + '模型目录', 'error'); return; }
        fetch('/uvr-sep/scan-models', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({model_dir: dir, arch_type: currentArch})
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (!data.success) { showMessage(data.error, 'error'); return; }
            scannedModels = data.models || [];
            if (!scannedModels.length) {
                showMessage('未找到 ' + currentArch + ' 模型文件', 'info');
                return;
            }
            var sel = document.getElementById('modelSelect');
            if (!sel) return;
            sel.innerHTML = '<option value="">-- 请选择模型 --</option>';
            scannedModels.forEach(function(m, idx) {
                var opt = document.createElement('option');
                opt.value = idx;
                opt.textContent = m.name + (m.has_meta ? '  [' + m.primary_stem + ']' : '');
                sel.appendChild(opt);
            });
            selectedModelPath = '';
            var selectedModelInfo = document.getElementById('selectedModelInfo');
            if (selectedModelInfo) selectedModelInfo.style.display = 'none';
            updateStartBtn();
        })
        .catch(function(e) { showMessage('扫描失败: ' + e, 'error'); });
    }

    function onModelSelect(sel) {
        var idx = parseInt(sel.value);
        if (isNaN(idx) || idx < 0 || idx >= scannedModels.length) {
            selectedModelPath = '';
            var selectedModelInfo = document.getElementById('selectedModelInfo');
            if (selectedModelInfo) selectedModelInfo.style.display = 'none';
            updateStartBtn();
            return;
        }
        var m = scannedModels[idx];
        selectedModelPath = m.path;
        var selectedModelInfo = document.getElementById('selectedModelInfo');
        var selectedModelName = document.getElementById('selectedModelName');
        var selectedModelPathEl = document.getElementById('selectedModelPath');
        if (selectedModelInfo) selectedModelInfo.style.display = 'block';
        if (selectedModelName) selectedModelName.textContent = m.name;
        if (selectedModelPathEl) selectedModelPathEl.textContent = m.path;
        updateStartBtn();
    }

    // ── 开始分离 ──────────────────────────────────────────────────────────
    function startSeparate() {
        if (!audioInput || !audioInput.files || !audioInput.files.length) { showMessage('请上传音频文件', 'error'); return; }
        if (!selectedModelPath) { showMessage('请选择模型', 'error'); return; }

        var formData = new FormData();
        formData.append('audio', audioInput.files[0]);
        formData.append('model_path', selectedModelPath);
        formData.append('process_method', currentArch);

        var isPrimaryOnly = document.getElementById('isPrimaryOnly');
        var isSecondaryOnly = document.getElementById('isSecondaryOnly');
        var isGpu = document.getElementById('isGpu');
        var isNormalize = document.getElementById('isNormalize');

        formData.append('is_primary_stem_only', isPrimaryOnly && isPrimaryOnly.checked ? '1' : '0');
        formData.append('is_secondary_stem_only', isSecondaryOnly && isSecondaryOnly.checked ? '1' : '0');
        formData.append('is_gpu', isGpu && isGpu.checked ? '1' : '0');
        formData.append('is_normalization', isNormalize && isNormalize.checked ? '1' : '0');

        // 架构特异参数
        if (currentArch === 'Demucs') {
            var demucsStemSelect = document.getElementById('demucsStemSelect');
            formData.append('demucs_stems', demucsStemSelect ? demucsStemSelect.value : 'All Stems');
            formData.append('segment', getSelectValue('demucsSegment', 'demucsSegmentCustom') || 'Default');
        } else if (currentArch === 'MDX-Net') {
            formData.append('segment', getSelectValue('mdxSegmentSize', 'mdxSegmentSizeCustom') || 'Default');
            formData.append('mdx_overlap', getSelectValue('mdxOverlap', 'mdxOverlapCustom') || '0.250');
        } else if (currentArch === 'VR Arc') {
            formData.append('vr_window_size', getSelectValue('vrWindowSize', 'vrWindowSizeCustom') || '1024');
            formData.append('vr_aggression', getSelectValue('vrAggression', 'vrAggressionCustom') || '5');
        }

        var startBtn = document.getElementById('startBtn');
        var emptyState = document.getElementById('emptyState');
        var resultSection = document.getElementById('resultSection');
        var progressSection = document.getElementById('progressSection');
        var progressFill = document.getElementById('progressFill');
        var statusText = document.getElementById('statusText');

        if (startBtn) startBtn.disabled = true;
        if (emptyState) emptyState.style.display = 'none';
        if (resultSection) resultSection.style.display = 'none';
        if (progressSection) progressSection.style.display = 'block';
        if (progressFill) progressFill.style.width = '0%';
        if (statusText) statusText.textContent = '正在上传并初始化...';
        var logContent = document.getElementById('logContent');
        if (logContent) logContent.innerHTML = '';

        fetch('/uvr-sep/separate', { method: 'POST', body: formData })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (!data.success) {
                showMessage(data.error, 'error');
                if (progressSection) progressSection.style.display = 'none';
                if (emptyState) emptyState.style.display = '';
                if (startBtn) startBtn.disabled = false;
                return;
            }
            currentTaskId = data.task_id;
            if (statusText) statusText.textContent = '正在分离...';
            pollStatus();
        })
        .catch(function(e) {
            showMessage('请求失败: ' + e, 'error');
            if (progressSection) progressSection.style.display = 'none';
            if (emptyState) emptyState.style.display = '';
            if (startBtn) startBtn.disabled = false;
        });
    }

    // ── 日志 ──────────────────────────────────────────────────────────────
    function renderLogs(logs) {
        var el = document.getElementById('logContent');
        if (!el || !logs || !logs.length) return;
        el.innerHTML = logs.map(function(l) { return l.replace(/</g, '&lt;'); }).join('<br>');
        el.scrollTop = el.scrollHeight;
    }

    function clearLogs() {
        var el = document.getElementById('logContent');
        if (el) el.innerHTML = '';
        if (currentTaskId) {
            fetch('/uvr-sep/clear-logs/' + currentTaskId, { method: 'POST' });
        }
    }

    // ── 轮询状态 ──────────────────────────────────────────────────────────
    function pollStatus() {
        if (!currentTaskId) return;
        if (pollTimer) clearInterval(pollTimer);
        pollTimer = setInterval(function() {
            fetch('/uvr-sep/status/' + currentTaskId)
            .then(function(r) { return r.json(); })
            .then(function(data) {
                if (!data.success) return;
                var progressFill = document.getElementById('progressFill');
                var statusText = document.getElementById('statusText');
                var startBtn = document.getElementById('startBtn');
                var progressSection = document.getElementById('progressSection');
                var emptyState = document.getElementById('emptyState');

                if (progressFill) progressFill.style.width = data.progress + '%';
                if (data.logs) renderLogs(data.logs);
                var devInfo = data.device ? ' [' + data.device + ']' : '';
                if (data.status === 'done') {
                    clearInterval(pollTimer); pollTimer = null;
                    if (statusText) statusText.textContent = '分离完成！' + devInfo;
                    showResults(data.stems);
                    if (startBtn) startBtn.disabled = false;
                } else if (data.status === 'error') {
                    clearInterval(pollTimer); pollTimer = null;
                    showMessage('分离失败: ' + data.error, 'error', 5000);
                    if (progressSection) progressSection.style.display = 'none';
                    if (emptyState) emptyState.style.display = '';
                    if (startBtn) startBtn.disabled = false;
                } else {
                    if (statusText) statusText.textContent = '正在分离... ' + data.progress + '%' + devInfo;
                }
            })
            .catch(function() {});
        }, 1000);
    }

    // ── 显示结果 ──────────────────────────────────────────────────────────
    function showResults(stems) {
        var list = document.getElementById('stemsList');
        if (!list) return;
        list.innerHTML = '';
        stems.forEach(function(s) {
            var iconClass = 'other', icon = 'fa-exchange-alt';
            var name = s.stem.toLowerCase();
            if (name === 'vocals' || name === 'vocal') { iconClass = 'vocals'; icon = 'fa-microphone'; }
            else if (name === 'instrumental' || name === 'no vocals') { iconClass = 'instrumental'; icon = 'fa-folder'; }
            var safeStem = s.stem.replace(/'/g, "\\'");
            var row = document.createElement('div');
            row.className = 'stem-row';
            row.innerHTML =
                '<div class="stem-icon ' + iconClass + '"><i class="fas ' + icon + '"></i></div>' +
                '<div class="stem-info"><div class="stem-name">' + s.stem + '</div><div class="stem-filename">' + s.filename + '</div></div>' +
                '<div style="display:flex;align-items:center;gap:10px;margin-left:auto;">' +
                    '<div class="stem-player active" id="player_' + safeStem + '">' +
                        '<button class="player-btn" id="playerBtn_' + safeStem + '" onclick="playStem(\'' + currentTaskId + '\',\'' + safeStem + '\')" title="播放"><i class="fas fa-play"></i></button>' +
                        '<input type="range" class="player-progress" id="progress_' + safeStem + '" min="0" max="100" value="0" step="0.1"' +
                            ' onmousedown="startSeeking(\'' + safeStem + '\')"' +
                            ' onmouseup="doSeek(\'' + safeStem + '\')"' +
                            ' ontouchstart="startSeeking(\'' + safeStem + '\')"' +
                            ' ontouchend="doSeek(\'' + safeStem + '\')">' +
                        '<span class="player-time" id="time_' + safeStem + '">0:00 / 0:00</span>' +
                    '</div>' +
                    '<div class="stem-actions">' +
                        '<button class="stem-btn" onclick="downloadStem(\'' + currentTaskId + '\',\'' + safeStem + '\')" title="下载"><i class="fas fa-folder-plus"></i></button>' +
                    '</div>' +
                '</div>';
            list.appendChild(row);
        });
        var progressSection = document.getElementById('progressSection');
        var resultSection = document.getElementById('resultSection');
        if (progressSection) progressSection.style.display = 'none';
        if (resultSection) resultSection.style.display = 'block';
    }

    // ── 音频播放控制 ─────────────────────────────────────────────────────
    var currentStem = '';
    var playTimer = null;
    var seeking = false;

    function playStem(taskId, stem) {
        var audio = document.getElementById('previewAudio');
        if (!audio) return;

        // 切换音轨
        if (currentStem !== stem) {
            stopCurrentStem();
            currentStem = stem;
            audio.src = '/uvr-sep/download/' + encodeURIComponent(taskId) + '/' + encodeURIComponent(stem);
        }

        // 播放/暂停切换
        if (audio.paused) {
            audio.play();
        } else {
            audio.pause();
        }
    }

    function stopCurrentStem() {
        var audio = document.getElementById('previewAudio');
        if (!audio) return;
        audio.pause();
        audio.removeAttribute('src');
        if (playTimer) { clearInterval(playTimer); playTimer = null; }
        if (currentStem) {
            var btn = document.getElementById('playerBtn_' + currentStem);
            var prog = document.getElementById('progress_' + currentStem);
            var time = document.getElementById('time_' + currentStem);
            if (btn) { btn.className = 'player-btn'; btn.innerHTML = '<i class="fas fa-play"></i>'; }
            if (prog) prog.value = 0;
            if (time) time.textContent = '0:00 / 0:00';
        }
        currentStem = '';
    }

    // audio 事件
    (function() {
        var audio = document.getElementById('previewAudio');
        if (!audio) return;

        audio.addEventListener('play', function() {
            if (!currentStem) return;
            var btn = document.getElementById('playerBtn_' + currentStem);
            if (btn) { btn.className = 'player-btn playing'; btn.innerHTML = '<i class="fas fa-pause"></i>'; }
            if (playTimer) clearInterval(playTimer);
            playTimer = setInterval(updatePlayProgress, 200);
        });

        audio.addEventListener('pause', function() {
            if (!currentStem) return;
            var btn = document.getElementById('playerBtn_' + currentStem);
            if (btn) { btn.className = 'player-btn'; btn.innerHTML = '<i class="fas fa-play"></i>'; }
            if (playTimer) { clearInterval(playTimer); playTimer = null; }
        });

        audio.addEventListener('ended', function() {
            if (!currentStem) return;
            var btn = document.getElementById('playerBtn_' + currentStem);
            var prog = document.getElementById('progress_' + currentStem);
            var time = document.getElementById('time_' + currentStem);
            if (btn) { btn.className = 'player-btn'; btn.innerHTML = '<i class="fas fa-play"></i>'; }
            if (prog) prog.value = 100;
            if (time) time.textContent = formatPlayerTime(audio.duration) + ' / ' + formatPlayerTime(audio.duration);
            if (playTimer) { clearInterval(playTimer); playTimer = null; }
        });

        audio.addEventListener('loadedmetadata', function() {
            updatePlayProgress();
        });
    })();

    function updatePlayProgress() {
        var audio = document.getElementById('previewAudio');
        if (!audio || !currentStem || isNaN(audio.duration)) return;
        var prog = document.getElementById('progress_' + currentStem);
        var time = document.getElementById('time_' + currentStem);
        if (prog && !seeking) {
            prog.value = audio.currentTime / audio.duration * 100;
        }
        if (time) {
            time.textContent = formatPlayerTime(audio.currentTime) + ' / ' + formatPlayerTime(audio.duration);
        }
    }

    function startSeeking(stem) {
        if (stem === currentStem) seeking = true;
    }

    function doSeek(stem) {
        seeking = false;
        var audio = document.getElementById('previewAudio');
        if (!audio || stem !== currentStem || isNaN(audio.duration)) return;
        var prog = document.getElementById('progress_' + stem);
        if (prog) {
            audio.currentTime = parseFloat(prog.value) / 100 * audio.duration;
        }
    }

    function formatPlayerTime(sec) {
        if (isNaN(sec)) return '0:00';
        var m = Math.floor(sec / 60);
        var s = Math.floor(sec % 60);
        return m + ':' + (s < 10 ? '0' : '') + s;
    }

    function downloadStem(taskId, stem) {
        window.open('/uvr-sep/download/' + encodeURIComponent(taskId) + '/' + encodeURIComponent(stem));
    }

    function downloadAll() { if (currentTaskId) window.open('/uvr-sep/download-all/' + currentTaskId); }

    function cleanupTask() {
        if (!currentTaskId) return;
        fetch('/uvr-sep/cleanup/' + currentTaskId, { method: 'POST' })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (data.success) {
                showMessage('已清理临时文件', 'success');
                var resultSection = document.getElementById('resultSection');
                var emptyState = document.getElementById('emptyState');
                if (resultSection) resultSection.style.display = 'none';
                if (emptyState) emptyState.style.display = '';
                currentTaskId = null;
            }
        });
    }

    // ── 保存配置 ──────────────────────────────────────────────────────────
    function saveConfig() {
        var modelDirs = window._savedModelDirs || {};
        var dirModes = window._savedModelDirModes || {};
        var uvrPath = document.getElementById('uvrPath');
        var isGpu = document.getElementById('isGpu');
        var isNormalize = document.getElementById('isNormalize');
        var isPrimaryOnly = document.getElementById('isPrimaryOnly');
        var isSecondaryOnly = document.getElementById('isSecondaryOnly');
        var saveFormat = document.getElementById('saveFormat');
        var demucsStemSelect = document.getElementById('demucsStemSelect');

        var cfg = {
            uvr_project_path: uvrPath ? uvrPath.value.trim() : '',
            demucs_model_dir: modelDirs['demucs'] || '',
            vr_model_dir: modelDirs['vr'] || '',
            mdx_model_dir: modelDirs['mdx'] || '',
            demucs_model_dir_mode: dirModes['demucs'] || 'absolute',
            vr_model_dir_mode: dirModes['vr'] || 'absolute',
            mdx_model_dir_mode: dirModes['mdx'] || 'absolute',
            arch_type: currentArch,
            is_gpu: isGpu ? isGpu.checked : false,
            is_normalization: isNormalize ? isNormalize.checked : false,
            is_primary_stem_only: isPrimaryOnly ? isPrimaryOnly.checked : false,
            is_secondary_stem_only: isSecondaryOnly ? isSecondaryOnly.checked : false,
            save_format: saveFormat ? saveFormat.value : 'wav',
            demucs_stems: demucsStemSelect ? demucsStemSelect.value : 'All Stems',
            demucs_segment: getSelectValue('demucsSegment', 'demucsSegmentCustom') || 'Default',
            mdx_segment_size: getSelectValue('mdxSegmentSize', 'mdxSegmentSizeCustom') || 'Default',
            mdx_overlap: parseFloat(getSelectValue('mdxOverlap', 'mdxOverlapCustom')) || 0.25,
            vr_window_size: parseInt(getSelectValue('vrWindowSize', 'vrWindowSizeCustom')) || 1024,
            vr_aggression: parseInt(getSelectValue('vrAggression', 'vrAggressionCustom')) || 5,
        };

        // 保存选中模型（按架构区分）
        var archKey = {'Demucs':'demucs_selected_model','MDX-Net':'mdx_selected_model','VR Arc':'vr_selected_model'};
        cfg.demucs_selected_model = window._savedSelectedModels ? (window._savedSelectedModels['Demucs'] || '') : '';
        cfg.mdx_selected_model = window._savedSelectedModels ? (window._savedSelectedModels['MDX-Net'] || '') : '';
        cfg.vr_selected_model = window._savedSelectedModels ? (window._savedSelectedModels['VR Arc'] || '') : '';

        // 更新当前架构的值
        if (selectedModelPath && archKey[currentArch]) {
            var modelSelect = document.getElementById('modelSelect');
            if (modelSelect && modelSelect.value) {
                var idx = parseInt(modelSelect.value);
                if (!isNaN(idx) && idx >= 0 && idx < scannedModels.length) {
                    cfg[archKey[currentArch]] = scannedModels[idx].name;
                }
            }
        }

        // 同步到内存
        if (!window._savedSelectedModels) window._savedSelectedModels = {};
        window._savedSelectedModels[currentArch] = cfg[archKey[currentArch]] || '';

        fetch('/uvr-sep/config', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(cfg)
        })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (data.success) showMessage('配置已保存', 'success');
            else showMessage('保存失败', 'error');
        })
        .catch(function(e) { showMessage('保存失败: ' + e, 'error'); });
    }

    // ── 初始化 ────────────────────────────────────────────────────────────
    function initPage() {
        var cfg = {
            uvr_project_path: {{ config.uvr_project_path|default("")|tojson }},
            arch_type: {{ config.arch_type|default("Demucs")|tojson }},
            model_dir_mode: {{ config.model_dir_mode|default("absolute")|tojson }},
            demucs_model_dir: {{ config.demucs_model_dir|default("")|tojson }},
            vr_model_dir: {{ config.vr_model_dir|default("")|tojson }},
            mdx_model_dir: {{ config.mdx_model_dir|default("")|tojson }},
            demucs_segment: {{ config.demucs_segment|default("Default")|tojson }},
            demucs_stems: {{ config.demucs_stems|default("All Stems")|tojson }},
            mdx_segment_size: {{ config.mdx_segment_size|default("Default")|tojson }},
            mdx_overlap: {{ config.mdx_overlap|default(0.25) }},
            vr_window_size: {{ config.vr_window_size|default(1024) }},
            vr_aggression: {{ config.vr_aggression|default(5) }},
            save_format: {{ config.save_format|default("wav")|tojson }},
            is_gpu: {{ 'true' if config.is_gpu else 'false' }},
            is_normalization: {{ 'true' if config.is_normalization else 'false' }},
            is_primary_stem_only: {{ 'true' if config.is_primary_stem_only else 'false' }},
            is_secondary_stem_only: {{ 'true' if config.is_secondary_stem_only else 'false' }},
            demucs_selected_model: {{ config.demucs_selected_model|default("")|tojson }},
            mdx_selected_model: {{ config.mdx_selected_model|default("")|tojson }},
            vr_selected_model: {{ config.vr_selected_model|default("")|tojson }},
            demucs_model_dir_mode: {{ config.demucs_model_dir_mode|default("absolute")|tojson }},
            vr_model_dir_mode: {{ config.vr_model_dir_mode|default("absolute")|tojson }},
            mdx_model_dir_mode: {{ config.mdx_model_dir_mode|default("absolute")|tojson }},
        };

        // 保存各架构的模型目录
        window._savedModelDirs = {
            'demucs': cfg.demucs_model_dir,
            'vr': cfg.vr_model_dir,
            'mdx': cfg.mdx_model_dir
        };

        window._savedModelDirModes = {
            'demucs': cfg.demucs_model_dir_mode,
            'vr': cfg.vr_model_dir_mode,
            'mdx': cfg.mdx_model_dir_mode
        };

        window._savedUvrPath = cfg.uvr_project_path;

        // 保存各架构的已选模型
        window._savedSelectedModels = {
            'Demucs': cfg.demucs_selected_model,
            'MDX-Net': cfg.mdx_selected_model,
            'VR Arc': cfg.vr_selected_model
        };

        // 恢复架构选择
        currentArch = cfg.arch_type;
        document.querySelectorAll('.arch-tab').forEach(function(t) {
            t.classList.toggle('active', t.dataset.arch === currentArch);
        });

        // 切换面板
        var demucsParams = document.getElementById('demucsParams');
        var mdxParams = document.getElementById('mdxParams');
        var vrParams = document.getElementById('vrParams');
        if (demucsParams) demucsParams.style.display = currentArch === 'Demucs' ? 'block' : 'none';
        if (mdxParams) mdxParams.style.display = currentArch === 'MDX-Net' ? 'block' : 'none';
        if (vrParams) vrParams.style.display = currentArch === 'VR Arc' ? 'block' : 'none';

        // 主/副音轨复选框始终显示
        var primaryOnlyRow = document.getElementById('primaryOnlyRow');
        var secondaryOnlyRow = document.getElementById('secondaryOnlyRow');
        if (primaryOnlyRow) primaryOnlyRow.style.display = 'flex';
        if (secondaryOnlyRow) secondaryOnlyRow.style.display = 'flex';

        // 恢复模型目录显示
        updateModelDirDisplay();

        // 恢复 Demucs 参数
        var demucsStemSel = document.getElementById('demucsStemSelect');
        if (demucsStemSel) {
            for (var i = 0; i < demucsStemSel.options.length; i++) {
                if (demucsStemSel.options[i].value === cfg.demucs_stems) { demucsStemSel.selectedIndex = i; break; }
            }
        }
        restoreCustomSelect('demucsSegment', 'demucsSegmentCustom', cfg.demucs_segment);

        // 恢复 MDX 参数
        restoreCustomSelect('mdxSegmentSize', 'mdxSegmentSizeCustom', cfg.mdx_segment_size);
        restoreCustomSelect('mdxOverlap', 'mdxOverlapCustom', cfg.mdx_overlap.toFixed(3));

        // 恢复 VR 参数
        restoreCustomSelect('vrWindowSize', 'vrWindowSizeCustom', String(cfg.vr_window_size));
        restoreCustomSelect('vrAggression', 'vrAggressionCustom', String(cfg.vr_aggression));

        // 恢复通用参数
        var isGpu = document.getElementById('isGpu');
        var isNormalize = document.getElementById('isNormalize');
        var isPrimaryOnly = document.getElementById('isPrimaryOnly');
        var isSecondaryOnly = document.getElementById('isSecondaryOnly');
        if (isGpu) isGpu.checked = cfg.is_gpu;
        if (isNormalize) isNormalize.checked = cfg.is_normalization;
        if (isPrimaryOnly) isPrimaryOnly.checked = cfg.is_primary_stem_only;
        if (isSecondaryOnly) isSecondaryOnly.checked = cfg.is_secondary_stem_only;
        var formatSel = document.getElementById('saveFormat');
        if (formatSel) {
            for (var i = 0; i < formatSel.options.length; i++) {
                if (formatSel.options[i].value === cfg.save_format) { formatSel.selectedIndex = i; break; }
            }
        }

        // 更新主/副音轨复选框状态（All Stems 时禁用）
        updateStemOnlyState();

        // MDX-Net/VR Arc 禁用 GPU
        if (isGpu) {
            if (currentArch !== 'Demucs') { isGpu.checked = false; isGpu.disabled = true; }
        }

        // 自动扫描当前架构的模型目录
        autoScanModels();

        // 检测 GPU 状态
        var gpuStatus = document.getElementById('gpuStatus');
        if (gpuStatus) {
            fetch('/uvr-sep/gpu-status').then(function(r){return r.json();}).then(function(d){
                if (d.torch_cuda) {
                    gpuStatus.style.color = '#2e7d32';
                    gpuStatus.textContent = '✓ PyTorch CUDA 可用' + (d.gpu_name ? ' (' + d.gpu_name + ')' : '');
                } else if (d.cuda_available) {
                    gpuStatus.style.color = '#f57c00';
                    gpuStatus.textContent = '⚠ PyTorch 无 CUDA (需重装 torch)';
                } else {
                    gpuStatus.style.color = '#c62828';
                    gpuStatus.textContent = '✗ CUDA 不可用';
                }
            });
        }
    }

    // DOM加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initPage);
    } else {
        initPage();
    }
    </script>
</body>
</html>
