// ========== 全局变量 ==========
let busy = false, currentSessionId = null, editingSessionId = null, toolIdCounter = 0;
let editingModelKey = null, editingProviderId = null, confirmCallback = null;
let editingMsgId = null, editingMsgRole = null;
// 全局工具数据存储（用于查看完整参数）
let globalToolsData = {};

// 同步工具数据到全局存储
function syncToolToGlobal(tool) {
    if (tool && tool.id) {
        globalToolsData[tool.id] = tool;
    }
}
let currentModelKey = null;
let currentReasoningEffort = 'default';  // 思考程度：default, minimal, low, medium, high
let currentThinkingMode = 'auto';  // 思考模式：disabled, enabled, auto
let chatModels = [];  // 全局存储对话模型列表
let editingMCPId = null;
let toolsEnabled = true;  // 从后端获取
let currentPersonaId = null;  // 当前选中的角色卡 ID（从 localStorage 读取）
window.ttsAutoPlay = false;  // TTS 自动播放状态
window.ttsMode = 'continuous';  // TTS 模式：'continuous'(连续) 或 'sentence'(分句)
let currentTTSManager = null;  // 当前的 TTS 管理器实例
window.hasTTSConfig = false;  // TTS 配置是否存在

// ========== 移动端侧边栏控制 ==========
function toggleMobileSidebar() {
    const sidebar = document.querySelector('.sidebar');
    const overlay = document.getElementById('mobileSidebarOverlay');
    sidebar.classList.toggle('mobile-open');
    overlay.classList.toggle('show');

    // 关闭工具栏下拉菜单
    document.querySelectorAll('.toolbar-dropdown').forEach(d => d.classList.remove('open'));
}

function closeMobileSidebar() {
    const sidebar = document.querySelector('.sidebar');
    const overlay = document.getElementById('mobileSidebarOverlay');
    sidebar.classList.remove('mobile-open');
    overlay.classList.remove('show');

    // 关闭工具栏下拉菜单
    document.querySelectorAll('.toolbar-dropdown').forEach(d => d.classList.remove('open'));
}

// ========== 工具函数 ==========
function getUser() { 
    // 优先从 localStorage 获取当前用户
    const savedUser = localStorage.getItem('nex_current_user');
    if (savedUser && savedUser.trim()) {
        return savedUser.trim();
    }
    // 降级：从输入框获取
    const input = document.getElementById('usernameInput');
    return input ? (input.value.trim() || 'guest') : 'guest';
}
function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }

// Markdown 渲染配置
if (typeof marked !== 'undefined') {
    // 自定义渲染器
    const renderer = new marked.Renderer();
    
    // 自定义代码块渲染器，添加复制按钮
    renderer.code = function(code, infostring) {
        // 兼容新旧版本的 marked
        // 新版本：code 是对象 {text, lang, escaped}
        // 旧版本：code 是字符串，infostring 是语言
        let codeText, lang;
        if (typeof code === 'object' && code.text) {
            codeText = code.text;
            lang = code.lang || 'text';
        } else {
            codeText = code;
            lang = infostring || 'text';
        }
        
        const codeId = 'code-' + Date.now() + '-' + Math.random().toString(36).substring(2, 9);
        const escapedCode = codeText.replace(/&/g, '&amp;')
                                .replace(/</g, '&lt;')
                                .replace(/>/g, '&gt;')
                                .replace(/"/g, '&quot;')
                                .replace(/'/g, '&#39;');
        
        return '<div class="code-block-wrapper">' +
            '<div class="code-block-header">' +
            '<span class="code-block-lang">' + lang + '</span>' +
            '<button class="code-copy-btn" onclick="copyCodeBlock(\'' + codeId + '\')" title="复制代码">' +
            '<i class="ri-file-copy-line"></i>' +
            '</button>' +
            '</div>' +
            '<pre><code id="' + codeId + '" class="language-' + lang + '">' + escapedCode + '</code></pre>' +
            '</div>';
    };
    
    // 自定义链接渲染器，让所有链接在新窗口打开
    renderer.link = function(href, title, text) {
        // 兼容新旧版本的 marked
        let linkHref, linkTitle, linkText;
        if (typeof href === 'object' && href.href) {
            linkHref = href.href;
            linkTitle = href.title;
            linkText = href.text;
        } else {
            linkHref = href;
            linkTitle = title;
            linkText = text;
        }
        
        const titleAttr = linkTitle ? ` title="${escapeHtml(linkTitle)}"` : '';
        return `<a href="${linkHref}"${titleAttr} target="_blank" rel="noopener noreferrer">${linkText}</a>`;
    };
    
    marked.setOptions({
        breaks: true,        // 支持换行
        gfm: true,           // GitHub 风格 Markdown
        renderer: renderer,  // 使用自定义渲染器
        headerIds: false,    // 禁用标题 ID（避免样式冲突）
        mangle: false        // 禁用邮箱地址混淆
    });
}

// LaTeX 公式预处理：保护公式不被 Markdown 解析器破坏
function protectLatex(text) {
    const placeholders = [];
    let idx = 0;
    
    // 保护块级公式 $$...$$ 和 \[...\]
    text = text.replace(/\$\$([\s\S]*?)\$\$/g, (match, p1) => {
        placeholders.push({ type: 'block', content: p1 });
        return `%%LATEX_BLOCK_${idx++}%%`;
    });
    text = text.replace(/\\\[([\s\S]*?)\\\]/g, (match, p1) => {
        placeholders.push({ type: 'block', content: p1 });
        return `%%LATEX_BLOCK_${idx++}%%`;
    });
    
    // 保护行内公式 $...$ 和 \(...\)
    // 改进：只匹配包含 LaTeX 特征的内容（反斜杠、上下标、花括号等）
    // 避免误匹配表格中的普通数字和货币符号
    text = text.replace(/\$([^\$\n]*?[\\^_{}][\s\S]*?)\$/g, (match, p1) => {
        placeholders.push({ type: 'inline', content: p1 });
        return `%%LATEX_INLINE_${idx++}%%`;
    });
    text = text.replace(/\\\(([\s\S]*?)\\\)/g, (match, p1) => {
        placeholders.push({ type: 'inline', content: p1 });
        return `%%LATEX_INLINE_${idx++}%%`;
    });
    
    return { text, placeholders };
}

// 还原 LaTeX 公式并渲染
function restoreAndRenderLatex(html, placeholders) {
    if (!placeholders.length) return html;
    
    placeholders.forEach((p, i) => {
        let rendered;
        try {
            if (typeof katex !== 'undefined') {
                rendered = katex.renderToString(p.content, {
                    displayMode: p.type === 'block',
                    throwOnError: false,
                    trust: true
                });
            } else {
                // KaTeX 未加载，显示原始公式
                rendered = p.type === 'block' ? `$$${p.content}$$` : `$${p.content}$`;
            }
        } catch (e) {
            console.error('LaTeX 渲染失败:', e);
            rendered = `<span class="latex-error" title="${escapeHtml(e.message)}">${escapeHtml(p.content)}</span>`;
        }
        
        const placeholder = p.type === 'block' ? `%%LATEX_BLOCK_${i}%%` : `%%LATEX_INLINE_${i}%%`;
        html = html.replace(placeholder, rendered);
    });
    
    return html;
}

function fmt(t) {
    if (!t) return '';
    
    // 先保护代码块，避免代码块内的内容被 LaTeX 处理
    const codeBlocks = [];
    let codeBlockIndex = 0;
    let protectedText = t.replace(/```[\s\S]*?```/g, (match) => {
        const placeholder = `__CODEBLOCK_${codeBlockIndex}__`;
        codeBlocks[codeBlockIndex] = match;
        codeBlockIndex++;
        return placeholder;
    });
    
    // 保护 LaTeX 公式（只在非代码块区域）
    const { text: latexProtectedText, placeholders } = protectLatex(protectedText);
    
    // 还原代码块
    let finalText = latexProtectedText;
    codeBlocks.forEach((block, i) => {
        finalText = finalText.replace(`__CODEBLOCK_${i}__`, block);
    });
    
    // 使用 marked 渲染 Markdown（会自动处理代码块）
    let html;
    if (typeof marked !== 'undefined') {
        try {
            html = marked.parse(finalText);
        } catch (e) {
            console.error('Markdown 渲染失败:', e);
            html = finalText;
        }
    } else {
        // 降级处理：简单的文本格式化
        html = escapeHtml(finalText);
        html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
        html = html.replace(/\n/g, '<br>');
    }
    
    // 还原并渲染 LaTeX 公式
    html = restoreAndRenderLatex(html, placeholders);
    
    return html;
}

// 复制代码块
function copyCodeBlock(codeId) {
    const codeElement = document.getElementById(codeId);
    if (!codeElement) return;
    
    const text = codeElement.textContent;
    navigator.clipboard.writeText(text).then(() => {
        showToast('代码已复制到剪贴板', 'success', 2000);
    }).catch(err => {
        console.error('复制失败:', err);
        showToast('复制失败', 'error', 2000);
    });
}

// ========== Toast 通知系统 ==========
const toastIcons = {
    success: 'ri-checkbox-circle-line',
    error: 'ri-close-circle-line',
    warning: 'ri-alert-line',
    info: 'ri-information-line'
};

function showToast(message, type = 'info', duration = 3000) {
    const container = document.getElementById('toastContainer');
    const toast = document.createElement('div');
    toast.className = 'toast';
    toast.innerHTML = `
                <i class="toast-icon ${type} ${toastIcons[type] || toastIcons.info}"></i>
                <div class="toast-content">${escapeHtml(message)}</div>
                <button class="toast-close" onclick="this.parentElement.remove()">
                    <i class="ri-close-line"></i>
                </button>
            `;
    container.appendChild(toast);
    if (duration > 0) {
        setTimeout(() => {
            toast.classList.add('hiding');
            setTimeout(() => toast.remove(), 300);
        }, duration);
    }
}

// ========== Alert 对话框系统 ==========
const alertIcons = {
    error: 'ri-close-circle-line',
    warning: 'ri-alert-line',
    info: 'ri-information-line'
};

function showAlert(message, title = '提示', type = 'info') {
    const modal = document.getElementById('alertModal');
    const iconEl = document.getElementById('alertIcon');
    const titleEl = document.getElementById('alertTitle');
    const bodyEl = document.getElementById('alertBody');
    iconEl.className = 'alert-icon ' + type + ' ' + (alertIcons[type] || alertIcons.info);
    titleEl.textContent = title;

    // 支持 HTML 内容 - 检查是否包含 HTML 标签
    const hasHtml = message.indexOf('<') !== -1 && message.indexOf('>') !== -1;
    if (hasHtml) {
        bodyEl.innerHTML = message;
    } else {
        bodyEl.textContent = message;
    }

    modal.classList.add('show');
}

function closeAlert() {
    const modal = document.getElementById('alertModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
}

// ========== 主题 ==========
const iconSun = '<i class="ri-sun-line"></i>';
const iconMoon = '<i class="ri-moon-line"></i>';
const iconEdit = '<i class="ri-edit-line"></i>';
const iconDelete = '<i class="ri-delete-bin-line"></i>';
const iconCopy = '<i class="ri-file-copy-line"></i>';
const iconVolume = '<i class="ri-volume-up-line"></i>';
const iconRefresh = '<i class="ri-refresh-line"></i>';
const iconTokens = '<i class="ri-upload-line"></i>';
const iconBrain = '<i class="ri-lightbulb-ai-line"></i>';
const iconSettings = '<i class="ri-settings-3-line"></i>';
const iconPlus = '<i class="ri-add-line"></i>';
const iconWarning = '<i class="ri-alert-line"></i>';
const iconEditLg = '<i class="ri-edit-line"></i>';
const iconTool = '<i class="ri-tools-line"></i>';
const iconServer = '<i class="ri-server-line"></i>';
const iconLink = '<i class="ri-link"></i>';
const iconEye = '<i class="ri-eye-line"></i>';

// 模型标签配置 - 使用 Remix Icon
const MODEL_TAGS = {
    'tool': { label: '工具', color: '#10b981', icon: '<i class="ri-tools-line"></i>' },
    'reasoning': { label: '推理', color: '#f59e0b', icon: '<i class="ri-lightbulb-ai-line"></i>' },
    'vision': { label: '视觉', color: '#6366f1', icon: '<i class="ri-eye-line"></i>' }
};

function renderModelTags(tags) {
    if (!tags || tags.length === 0) return '';
    return tags.map(tag => {
        const config = MODEL_TAGS[tag] || { label: tag, color: '#6b7280', icon: '' };
        return `<span class="model-tag" style="background:${config.color}20;color:${config.color};border:1px solid ${config.color}40;">${config.icon}</span>`;
    }).join('');
}

// 模型类型配置
const MODEL_TYPES = {
    'chat': { label: '对话', color: '#6366f1', icon: '<i class="ri-chat-3-line"></i>' },
    'embedding': { label: '嵌入', color: '#8b5cf6', icon: '<i class="ri-cpu-line"></i>' }
};

function renderModelType(type) {
    const config = MODEL_TYPES[type] || MODEL_TYPES['chat'];
    return `<span class="model-type-tag" style="background:${config.color}20;color:${config.color};border:1px solid ${config.color}40;">${config.icon} ${config.label}</span>`;
}

function toggleTheme() {
    const html = document.documentElement;
    const current = localStorage.getItem('nex_theme');
    let newTheme;
    if (current === 'auto' || !current) {
        newTheme = 'light';
    } else if (current === 'light') {
        newTheme = 'dark';
    } else {
        newTheme = 'auto';
    }
    localStorage.setItem('nex_theme', newTheme);
    applyTheme(newTheme);
    updateThemeIcon();
}

function applyTheme(theme) {
    const html = document.documentElement;
    if (theme === 'auto') {
        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        html.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
    } else {
        html.setAttribute('data-theme', theme);
    }
}

function updateThemeIcon() {
    const btn = document.getElementById('themeBtn');
    if (!btn) return; // 如果按钮不存在，直接返回
    const saved = localStorage.getItem('nex_theme') || 'auto';
    const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
    if (saved === 'auto') {
        btn.innerHTML = '<i class="ri-computer-line"></i>';
        btn.title = '跟随系统 (点击切换到浅色)';
    } else if (isDark) {
        btn.innerHTML = iconSun;
        btn.title = '深色模式 (点击切换到跟随系统)';
    } else {
        btn.innerHTML = iconMoon;
        btn.title = '浅色模式 (点击切换到深色)';
    }
}

async function initTheme() {
    // 设置默认值
    window.avatarMode = 'icon';  // icon, text, hide
    window.currentPersonaAvatar = '';
    
    // 先应用 localStorage 中的主题（避免闪烁）
    const localTheme = localStorage.getItem('nex_theme') || 'auto';
    applyTheme(localTheme);
    updateThemeIcon();

    // 监听系统主题变化
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
        if (localStorage.getItem('nex_theme') === 'auto') {
            applyTheme('auto');
        }
    });

    // 从数据库加载样式设置（会覆盖 localStorage）
    await loadStyleSettings();
}

async function loadCustomAvatar() {
    // 角色卡头像在 updatePersonaDisplay 中加载
}

function updateAvatarPreview() {
    // 不再需要全局头像预览
}

function setAvatarMode(mode) {
    window.avatarMode = mode;
    fetch('/nex/settings', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ settings: { avatar_mode: mode } })
    });
    updateAvatarModeUI();
    // 自动刷新消息
    if (currentSessionId) {
        loadMessages(currentSessionId);
    }
}

function updateAvatarModeUI() {
    document.getElementById('avatarIcon')?.classList.toggle('active', window.avatarMode === 'icon');
    document.getElementById('avatarText')?.classList.toggle('active', window.avatarMode === 'text');
    document.getElementById('avatarHide')?.classList.toggle('active', window.avatarMode === 'hide');
}
// ========== 样式设置 ==========
async function loadStyleSettings() {
    try {
        const r = await fetch('/nex/settings');
        const d = await r.json();
        const settings = d.data || {};
        console.log('加载样式设置:', settings);

        // 应用主题模式（优先级最高，先应用）
        if (settings.theme_mode && settings.theme_mode !== '') {
            localStorage.setItem('nex_theme', settings.theme_mode);
            applyTheme(settings.theme_mode);
            updateThemeIcon();
        }

        // 应用主题色
        if (settings.accent_color && settings.accent_color !== '') {
            console.log('应用主题色:', settings.accent_color);
            applyAccentColor(settings.accent_color);
        }

        // 应用字体大小
        if (settings.font_size && settings.font_size !== '') {
            console.log('应用字体大小:', settings.font_size);
            applyFontSize(settings.font_size);
        }

        // 应用头像模式设置
        if (settings.avatar_mode && settings.avatar_mode !== '') {
            window.avatarMode = settings.avatar_mode;
        } else {
            window.avatarMode = 'icon';  // 默认消息图标模式
        }
    } catch (e) { console.error('加载样式设置失败', e); }
}

function setThemeMode(mode) {
    localStorage.setItem('nex_theme', mode);
    applyTheme(mode);
    updateThemeIcon();
    updateStylePanelUI();
    saveStyleSetting('theme_mode', mode);
}

function setAccentColor(color) {
    applyAccentColor(color);
    // 更新颜色选项的选中状态
    document.querySelectorAll('.color-option').forEach(btn => {
        btn.classList.remove('active');
        if (!btn.classList.contains('custom')) {
            const btnColor = getComputedStyle(btn).getPropertyValue('--color').trim();
            if (btnColor === color) btn.classList.add('active');
        }
    });
    // 重置自定义按钮
    const customBtn = document.getElementById('customColorBtn');
    if (customBtn) {
        customBtn.style.background = '';
        customBtn.classList.remove('active');
    }
    // 隐藏自定义输入区域
    const customInput = document.getElementById('customColorInput');
    if (customInput) customInput.style.display = 'none';

    saveStyleSetting('accent_color', color);
}

function showCustomColorInput() {
    const customInput = document.getElementById('customColorInput');
    const customText = document.getElementById('customColorText');
    const previewBox = document.getElementById('customColorPreviewBox');
    const currentColor = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim();

    if (customInput.style.display === 'none' || customInput.style.display === '') {
        customInput.style.display = 'flex';
        customText.value = currentColor;
        previewBox.style.background = currentColor;
        customText.focus();
        customText.select();
    } else {
        customInput.style.display = 'none';
    }
}

function previewCustomColor(value) {
    const previewBox = document.getElementById('customColorPreviewBox');
    // 验证是否是有效的十六进制颜色
    if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
        previewBox.style.background = value;
    } else if (/^[0-9A-Fa-f]{6}$/.test(value)) {
        previewBox.style.background = '#' + value;
    }
}

function applyCustomColorFromInput() {
    let value = document.getElementById('customColorText').value.trim();
    // 自动补全 #
    if (/^[0-9A-Fa-f]{6}$/.test(value)) {
        value = '#' + value;
    }
    // 验证格式
    if (!/^#[0-9A-Fa-f]{6}$/.test(value)) {
        showToast('请输入有效的颜色值，如 #6366f1', 'warning');
        return;
    }

    applyAccentColor(value);
    // 取消所有预设颜色的选中
    document.querySelectorAll('.color-option:not(.custom)').forEach(btn => btn.classList.remove('active'));
    // 设置自定义按钮的颜色和选中状态
    const customBtn = document.getElementById('customColorBtn');
    if (customBtn) {
        customBtn.style.background = value;
        customBtn.classList.add('active');
    }
    saveStyleSetting('accent_color', value);
    showToast('颜色已应用', 'success');
}

function applyAccentColor(color) {
    if (!color || color === '') return;
    // 确保颜色格式正确
    color = color.trim().toLowerCase();
    if (!color.startsWith('#')) {
        color = '#' + color;
    }
    console.log('应用主题色到CSS:', color);
    document.documentElement.style.setProperty('--accent', color);
    // 计算hover颜色（稍微深一点）
    const hoverColor = adjustColor(color, -20);
    document.documentElement.style.setProperty('--accent-hover', hoverColor);
    // 更新用户消息背景色
    document.documentElement.style.setProperty('--user', color);
}

function adjustColor(color, amount) {
    // 简单的颜色调整函数
    const hex = color.replace('#', '');
    const r = Math.max(0, Math.min(255, parseInt(hex.substr(0, 2), 16) + amount));
    const g = Math.max(0, Math.min(255, parseInt(hex.substr(2, 2), 16) + amount));
    const b = Math.max(0, Math.min(255, parseInt(hex.substr(4, 2), 16) + amount));
    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}

function setFontSize(size) {
    applyFontSize(size);
    updateStylePanelUI();
    saveStyleSetting('font_size', size);
}

function applyFontSize(size) {
    if (!size || size === '') return;
    const sizes = { small: '14px', medium: '16px', large: '18px' };
    const fontSize = sizes[size] || '16px';
    console.log('应用字体大小到CSS:', fontSize);
    document.documentElement.style.setProperty('font-size', fontSize);
}

async function saveStyleSetting(key, value) {
    console.log('保存样式设置:', key, '=', value);
    try {
        const r = await fetch('/nex/settings', {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ settings: { [key]: value } })
        });
        const d = await r.json();
        console.log('保存结果:', d);
    } catch (e) { console.error('保存样式设置失败', e); }
}
function updateStylePanelUI() {
    // 更新主题模式按钮
    const themeMode = localStorage.getItem('nex_theme') || 'auto';
    document.getElementById('themeLight')?.classList.remove('active');
    document.getElementById('themeDark')?.classList.remove('active');
    document.getElementById('themeAuto')?.classList.remove('active');
    if (themeMode === 'light') document.getElementById('themeLight')?.classList.add('active');
    else if (themeMode === 'dark') document.getElementById('themeDark')?.classList.add('active');
    else document.getElementById('themeAuto')?.classList.add('active');

    // 更新颜色选择
    const currentColor = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim().toLowerCase();
    const presetColors = ['#6366f1', '#8b5cf6', '#ec4899', '#ef4444', '#f97316', '#eab308', '#22c55e', '#14b8a6', '#0ea5e9'];
    let isPreset = false;

    document.querySelectorAll('.color-option:not(.custom)').forEach(btn => {
        btn.classList.remove('active');
        const btnColor = getComputedStyle(btn).getPropertyValue('--color').trim().toLowerCase();
        if (btnColor === currentColor) {
            btn.classList.add('active');
            isPreset = true;
        }
    });

    // 如果不是预设颜色，显示为自定义
    const customBtn = document.getElementById('customColorBtn');
    const customInput = document.getElementById('customColorInput');
    const customText = document.getElementById('customColorText');
    const previewBox = document.getElementById('customColorPreviewBox');

    if (customBtn) {
        if (!isPreset && currentColor && currentColor !== '#6366f1') {
            customBtn.style.background = currentColor;
            customBtn.classList.add('active');
        } else {
            customBtn.style.background = '';
            customBtn.classList.remove('active');
        }
    }
    if (customText) customText.value = currentColor;
    if (previewBox) previewBox.style.background = currentColor;

    // 更新字体大小按钮
    const fontSize = document.documentElement.style.fontSize || '16px';
    const sizeMap = { '14px': 0, '16px': 1, '18px': 2 };
    const currentSizeIdx = sizeMap[fontSize] ?? 1;
    const fontSizeSection = document.querySelectorAll('#panel-style .style-section')[2];
    if (fontSizeSection) {
        fontSizeSection.querySelectorAll('.style-option').forEach((btn, i) => {
            btn.classList.toggle('active', i === currentSizeIdx);
        });
    }

    // 更新头像模式按钮
    updateAvatarModeUI();
    updateAvatarPreview();
}

async function resetStyleSettings() {
    // 重置为默认值
    localStorage.setItem('nex_theme', 'auto');
    applyTheme('auto');
    applyAccentColor('#6366f1');
    document.documentElement.style.removeProperty('font-size');
    window.avatarMode = 'icon';
    updateStylePanelUI();

    // 保存到数据库
    try {
        await fetch('/nex/settings', {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ settings: { theme_mode: 'auto', accent_color: '#6366f1', font_size: 'medium', avatar_mode: 'icon' } })
        });
        showToast('已恢复默认设置', 'success');
        // 刷新消息列表以应用头像设置
        if (typeof loadMessages === 'function' && currentSessionId) {
            loadMessages(currentSessionId);
        }
    } catch (e) {
        console.error('重置失败', e);
        showToast('重置失败', 'error');
    }
}

// ========== 工具卡片 ==========

// 格式化参数显示（可视化）
// 尝试从不完整的 JSON 中提取已完成的字段
function parsePartialJson(jsonStr) {
    try {
        // 首先尝试完整解析
        return { success: true, data: JSON.parse(jsonStr), partial: false };
    } catch (e) {
        // 完整解析失败，尝试提取部分字段
        try {
            let fixedJson = jsonStr.trim();
            
            // 如果不是以 { 开头，无法解析
            if (!fixedJson.startsWith('{')) {
                return { success: false, data: null, partial: false };
            }
            
            // 尝试修复不完整的 JSON
            // 1. 计算未转义的引号数量（更兼容的方式）
            let unescapedQuotes = 0;
            let escaped = false;
            for (let i = 0; i < fixedJson.length; i++) {
                if (fixedJson[i] === '\\' && !escaped) {
                    escaped = true;
                } else if (fixedJson[i] === '"' && !escaped) {
                    unescapedQuotes++;
                } else {
                    escaped = false;
                }
            }
            
            // 如果在字符串中（奇数个未转义的引号），补充引号
            if (unescapedQuotes % 2 === 1) {
                fixedJson += '"';
            }
            
            // 2. 移除最后一个不完整的字段（如果有逗号）
            const lastCommaIndex = fixedJson.lastIndexOf(',');
            if (lastCommaIndex > 0) {
                // 检查逗号后是否有完整的键值对
                const afterComma = fixedJson.substring(lastCommaIndex + 1).trim();
                // 如果逗号后没有完整的键值对（没有冒号或冒号后没有值），移除它
                if (!afterComma.includes(':') || afterComma.endsWith(':') || afterComma.match(/:\s*$/)) {
                    fixedJson = fixedJson.substring(0, lastCommaIndex);
                }
            }
            
            // 3. 补充缺失的括号
            const openBraces = (fixedJson.match(/\{/g) || []).length;
            const closeBraces = (fixedJson.match(/\}/g) || []).length;
            const openBrackets = (fixedJson.match(/\[/g) || []).length;
            const closeBrackets = (fixedJson.match(/\]/g) || []).length;
            
            for (let i = 0; i < openBrackets - closeBrackets; i++) {
                fixedJson += ']';
            }
            for (let i = 0; i < openBraces - closeBraces; i++) {
                fixedJson += '}';
            }
            
            const parsed = JSON.parse(fixedJson);
            return { success: true, data: parsed, partial: true };
        } catch (e2) {
            return { success: false, data: null, partial: false };
        }
    }
}

function formatToolArgs(args, isPartial) {
    if (!args || typeof args !== 'object') {
        return `<pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre>`;
    }
    
    // 如果是空对象，显示加载提示
    if (Object.keys(args).length === 0) {
        return '<div style="color: var(--text2); font-size: 0.8rem;">正在接收参数...</div>';
    }
    
    let html = '<div class="tool-args-visual">';
    
    // 如果是部分解析，显示提示
    if (isPartial) {
        html += '<div class="args-partial-hint" style="color: var(--text2); font-size: 0.75rem; margin-bottom: 6px; font-style: italic;">⏳ 正在接收更多参数...</div>';
    }
    
    for (const [key, value] of Object.entries(args)) {
        html += '<div class="tool-arg-item" style="animation: slide-in 0.2s ease-out;">';
        html += `<div class="tool-arg-key">${escapeHtml(key)}</div>`;
        html += formatSingleArgValue(value);
        html += '</div>';
    }
    
    html += '</div>';
    return html;
}

function createToolCardHtml(name, args, result, id, receiving, executing, argsBuffer, parseSuccess, isPartial) {
    const isDone = result !== undefined;
    const isReceiving = receiving === true;
    const isExecuting = executing === true;
    const isLoading = !name || name === '';  // 工具名为空表示正在加载
    
    // 确定状态（优先级：接收中 > 执行中 > 完成）
    let statusClass = 'receiving';
    let statusText = '接收中';
    if (isDone) {
        statusClass = 'done';
        statusText = '完成';
    } else if (isExecuting) {
        statusClass = 'executing';
        statusText = '执行中';
    } else if (isReceiving) {
        statusClass = 'receiving';
        statusText = '接收中';
    }
    
    // 优化：对于超长结果，截断显示并添加提示
    let displayResult = result;
    let resultTruncated = false;
    if (result && result.length > 100000) {
        displayResult = result.substring(0, 100000);
        resultTruncated = true;
    }
    
    // 决定如何显示参数
    let argsHtml;
    if (isLoading) {
        // 工具名还在加载
        argsHtml = '<div style="color: var(--text2); font-size: 0.8rem;">正在接收参数...</div>';
    } else if (isReceiving && !parseSuccess && argsBuffer) {
        // 正在接收且未成功解析，显示原始 JSON
        argsHtml = `<div class="tool-args-loading">
            <div style="color: var(--text2); font-size: 0.8rem; margin-bottom: 4px;">正在接收参数...</div>
            <pre class="args-buffer" style="background: var(--card); padding: 8px; border-radius: var(--radius-sm); font-size: 0.75rem; max-height: 100px; overflow: auto; font-family: 'SF Mono', Monaco, monospace; white-space: pre-wrap; word-break: break-all;">${escapeHtml(argsBuffer)}</pre>
        </div>`;
    } else {
        // 已解析成功或执行中/完成，显示格式化参数
        argsHtml = formatToolArgs(args, isPartial);
    }
    
    return `<div class="tool-card ${isReceiving ? 'loading' : ''}" id="${id}">
        <div class="tool-header" onclick="viewFullToolArgs('${id}')">
            <div class="tool-title">
                <span class="icon">${iconTool}</span>
                <span>${escapeHtml(name || '加载中...')}</span>
                <span class="tool-status ${statusClass}">${statusText}</span>
            </div>
            <button class="tool-view-full-btn" onclick="event.stopPropagation(); viewFullToolArgs('${id}')" title="查看详情">
                <i class="ri-fullscreen-line"></i>
            </button>
        </div>
    </div>`;
}

function toggleTool(id) { document.getElementById(id)?.classList.toggle('open'); }

function updateToolStatus(id, status) {
    const card = document.getElementById(id);
    if (!card) return;
    
    const statusEl = card.querySelector('.tool-status');
    if (statusEl) {
        // 移除所有状态类
        statusEl.classList.remove('receiving', 'executing', 'done');
        
        if (status === 'receiving') {
            statusEl.classList.add('receiving');
            statusEl.textContent = '接收中';
        } else if (status === 'executing') {
            statusEl.classList.add('executing');
            statusEl.textContent = '执行中';
        } else if (status === 'done') {
            statusEl.classList.add('done');
            statusEl.textContent = '完成';
        }
    }
    
    // 移除加载动画
    if (status !== 'receiving') {
        card.classList.remove('loading');
    }
}

function updateToolName(id, name) {
    const card = document.getElementById(id);
    if (!card) return;
    const titleSpan = card.querySelector('.tool-title span:nth-child(2)');
    if (titleSpan) {
        titleSpan.textContent = name || '加载中...';
    }
}

function updateToolArgs(id, args, isPartial) {
    const card = document.getElementById(id);
    if (!card) return;
    const argsContainer = card.querySelector('.tool-args-container');
    if (!argsContainer) return;
    
    // 检查是否已经有可视化容器
    let visualDiv = argsContainer.querySelector('.tool-args-visual');
    
    if (!visualDiv) {
        // 第一次创建，直接设置 HTML
        argsContainer.innerHTML = formatToolArgs(args, isPartial);
        return;
    }
    
    // 已存在，进行增量更新
    // 1. 更新或添加提示信息
    let hintDiv = visualDiv.querySelector('.args-partial-hint');
    if (isPartial) {
        if (!hintDiv) {
            hintDiv = document.createElement('div');
            hintDiv.className = 'args-partial-hint';
            hintDiv.style.cssText = 'color: var(--text2); font-size: 0.75rem; margin-bottom: 6px; font-style: italic;';
            hintDiv.textContent = '⏳ 正在接收更多参数...';
            visualDiv.insertBefore(hintDiv, visualDiv.firstChild);
        }
    } else {
        // 不是部分解析，移除提示
        if (hintDiv) hintDiv.remove();
    }
    
    // 2. 获取现有参数键
    const existingItems = new Map();
    visualDiv.querySelectorAll('.tool-arg-item').forEach(item => {
        const keyEl = item.querySelector('.tool-arg-key');
        if (keyEl) {
            existingItems.set(keyEl.textContent, item);
        }
    });
    
    // 3. 遍历新参数，更新或添加
    const newKeys = Object.keys(args);
    for (let i = 0; i < newKeys.length; i++) {
        const key = newKeys[i];
        const value = args[key];
        
        if (existingItems.has(key)) {
            // 已存在，检查值是否变化
            const item = existingItems.get(key);
            const valueEl = item.querySelector('.tool-arg-value');
            if (valueEl) {
                const newValueHtml = formatSingleArgValue(value);
                // 简单比较：检查类名是否相同（类型是否变化）
                const currentClass = valueEl.className;
                const newClass = newValueHtml.match(/class="([^"]+)"/)?.[1] || '';
                
                // 如果类型变化或是对象/数组（内容可能变化），更新
                if (currentClass !== newClass || currentClass.includes('tool-arg-code')) {
                    valueEl.outerHTML = newValueHtml;
                }
            }
            existingItems.delete(key); // 标记为已处理
        } else {
            // 新参数，添加到正确位置
            const newItem = document.createElement('div');
            newItem.className = 'tool-arg-item';
            newItem.style.animation = 'slide-in 0.2s ease-out';
            newItem.innerHTML = `
                <div class="tool-arg-key">${escapeHtml(key)}</div>
                ${formatSingleArgValue(value)}
            `;
            
            // 找到正确的插入位置（保持顺序）
            const allItems = visualDiv.querySelectorAll('.tool-arg-item');
            let inserted = false;
            for (let j = 0; j < allItems.length; j++) {
                const itemKey = allItems[j].querySelector('.tool-arg-key')?.textContent;
                const itemIndex = newKeys.indexOf(itemKey);
                if (itemIndex > i) {
                    visualDiv.insertBefore(newItem, allItems[j]);
                    inserted = true;
                    break;
                }
            }
            if (!inserted) {
                visualDiv.appendChild(newItem);
            }
        }
    }
}

// 格式化单个参数值
function formatSingleArgValue(value, maxLength = 200) {
    const valueType = typeof value;
    const isObject = valueType === 'object' && value !== null;
    const isArray = Array.isArray(value);
    
    if (isObject || isArray) {
        const jsonStr = JSON.stringify(value, null, 2);
        const isTooLong = jsonStr.length > maxLength;
        
        if (isTooLong) {
            const preview = jsonStr.substring(0, maxLength);
            // 使用 data-value 属性存储，避免 HTML 属性转义问题
            const dataId = 'arg-' + Math.random().toString(36).substr(2, 9);
            // 临时存储到全局对象
            if (!window.argValueStore) window.argValueStore = {};
            window.argValueStore[dataId] = value;
            
            return `<div class="tool-arg-value tool-arg-code">
                <pre>${escapeHtml(preview)}...</pre>
                <button class="view-full-btn" data-arg-id="${dataId}" onclick="viewFullArgValueById(event, '${dataId}')">查看全部</button>
            </div>`;
        }
        return `<div class="tool-arg-value tool-arg-code"><pre>${escapeHtml(jsonStr)}</pre></div>`;
    } else if (valueType === 'boolean') {
        return `<div class="tool-arg-value tool-arg-boolean ${value ? 'true' : 'false'}">
            <span class="bool-icon">${value ? '✓' : '✗'}</span>
            <span class="bool-text">${value}</span>
        </div>`;
    } else if (valueType === 'number') {
        return `<div class="tool-arg-value tool-arg-number">${escapeHtml(String(value))}</div>`;
    } else if (valueType === 'string') {
        const isTooLong = value.length > maxLength;
        if (isTooLong) {
            const displayValue = value.substring(0, maxLength);
            // 使用 data-value 属性存储
            const dataId = 'arg-' + Math.random().toString(36).substr(2, 9);
            if (!window.argValueStore) window.argValueStore = {};
            window.argValueStore[dataId] = value;
            
            return `<div class="tool-arg-value tool-arg-string">
                ${escapeHtml(displayValue)}...
                <button class="view-full-btn" data-arg-id="${dataId}" onclick="viewFullArgValueById(event, '${dataId}')">查看全部</button>
            </div>`;
        }
        return `<div class="tool-arg-value tool-arg-string">${escapeHtml(value)}</div>`;
    } else {
        return `<div class="tool-arg-value">${escapeHtml(String(value))}</div>`;
    }
}

// 查看完整工具参数（支持实时更新）
let currentArgModalToolId = null;
let currentArgModalUpdateInterval = null;
let currentArgModalViewMode = 'visual'; // 'visual' 或 'code'

function viewFullToolArgs(toolId) {
    currentArgModalToolId = toolId;
    currentArgModalViewMode = 'visual'; // 默认可视化模式
    
    // 从全局存储获取工具数据
    const tool = globalToolsData[toolId];
    if (!tool) {
        showToast('未找到工具数据', 'error');
        return;
    }
    
    // 检测是否为移动设备
    const isMobile = window.innerWidth <= 768;
    const maxWidth = isMobile ? '95%' : '900px';
    
    // 创建模态框
    const modal = document.createElement('div');
    modal.id = 'argModal';
    modal.className = 'arg-modal';
    modal.innerHTML = `
        <div class="arg-modal-overlay" onclick="closeArgModal()"></div>
        <div class="arg-modal-content" style="max-width: ${maxWidth};">
            <div class="arg-modal-header">
                <h3 style="margin: 0; font-size: 1.1rem; font-weight: 600;">
                    <i class="ri-tools-line" style="margin-right: 8px;"></i>
                    ${escapeHtml(tool.name || '工具详情')}
                </h3>
                <button class="arg-modal-close" onclick="closeArgModal()" title="关闭">
                    <i class="ri-close-line"></i>
                </button>
            </div>
            <div class="arg-modal-body">
                <!-- 参数部分 -->
                <div class="modal-section">
                    <div class="modal-section-header">
                        <h4 style="margin: 0; font-size: 1rem; font-weight: 600;">参数</h4>
                        <div class="view-mode-toggle">
                            <button class="view-mode-btn active" data-mode="visual" onclick="switchArgViewMode('visual')">
                                <i class="ri-layout-grid-line"></i> 可视化
                            </button>
                            <button class="view-mode-btn" data-mode="code" onclick="switchArgViewMode('code')">
                                <i class="ri-braces-line"></i> 代码
                            </button>
                        </div>
                    </div>
                    <div id="modalArgsContainer" class="modal-args-container"></div>
                </div>
                
                <!-- 结果部分 -->
                <div class="modal-section" id="modalResultSection" style="display: none;">
                    <div class="modal-section-header">
                        <h4 style="margin: 0; font-size: 1rem; font-weight: 600;">执行结果</h4>
                    </div>
                    <div id="modalResultContainer" class="modal-result-container"></div>
                </div>
            </div>
            <div class="arg-modal-footer">
                <button class="btn-secondary" onclick="copyModalContent()">
                    <i class="ri-file-copy-line"></i> 复制全部
                </button>
                <button class="btn-primary" onclick="closeArgModal()">关闭</button>
            </div>
        </div>
    `;
    
    document.body.appendChild(modal);
    
    // 移动端：防止背景滚动
    if (isMobile) {
        document.body.style.overflow = 'hidden';
    }
    
    // 渲染参数和结果
    updateModalContent();
    
    // 如果正在接收，启动定时更新
    if (tool.receiving || tool.isPartial) {
        currentArgModalUpdateInterval = setInterval(() => {
            const currentTool = globalToolsData[currentArgModalToolId];
            if (!currentTool || (!currentTool.receiving && !currentTool.isPartial)) {
                // 停止更新
                clearInterval(currentArgModalUpdateInterval);
                currentArgModalUpdateInterval = null;
            }
            updateModalContent();
        }, 200); // 每200ms更新一次
    }
    
    // 添加动画
    setTimeout(() => modal.classList.add('show'), 10);
}

// 切换参数查看模式
function switchArgViewMode(mode) {
    currentArgModalViewMode = mode;
    
    // 更新按钮状态
    document.querySelectorAll('.view-mode-btn').forEach(btn => {
        if (btn.dataset.mode === mode) {
            btn.classList.add('active');
        } else {
            btn.classList.remove('active');
        }
    });
    
    // 重新渲染参数
    updateModalContent();
}

// 更新模态框内容
function updateModalContent() {
    const argsContainer = document.getElementById('modalArgsContainer');
    const resultSection = document.getElementById('modalResultSection');
    const resultContainer = document.getElementById('modalResultContainer');
    
    if (!argsContainer || !currentArgModalToolId) return;
    
    const tool = globalToolsData[currentArgModalToolId];
    if (!tool) return;
    
    // 显示状态
    let statusHtml = '';
    if (tool.receiving) {
        statusHtml = '<div style="color: #3b82f6; font-size: 0.85rem; margin-bottom: 12px; padding: 8px 12px; background: rgba(59, 130, 246, 0.1); border-radius: var(--radius-sm); display: inline-flex; align-items: center; gap: 6px;"><span class="status-dot" style="width: 8px; height: 8px; background: #3b82f6; border-radius: 50%; animation: pulse 1.5s ease-in-out infinite;"></span>正在接收参数...</div>';
    } else if (tool.isPartial) {
        statusHtml = '<div style="color: var(--warning); font-size: 0.85rem; margin-bottom: 12px; padding: 8px 12px; background: var(--warning-light); border-radius: var(--radius-sm); display: inline-flex; align-items: center; gap: 6px;"><i class="ri-time-line"></i>部分参数（正在接收更多...）</div>';
    }
    
    // 渲染参数
    let argsHtml = '';
    if (currentArgModalViewMode === 'visual') {
        // 可视化模式
        if (tool.args && Object.keys(tool.args).length > 0) {
            argsHtml = '<div class="tool-args-visual">';
            for (const [key, value] of Object.entries(tool.args)) {
                const valueStr = typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value);
                // 使用 data-* 属性存储数据，避免 onclick 中的转义问题
                const dataId = 'arg-' + Math.random().toString(36).substr(2, 9);
                if (!window.argCopyStore) window.argCopyStore = {};
                window.argCopyStore[dataId] = { key: key, value: valueStr };
                
                argsHtml += '<div class="tool-arg-item" style="margin-bottom: 12px; position: relative;">';
                argsHtml += `<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">`;
                argsHtml += `<div class="tool-arg-key" style="font-size: 0.9rem;">${escapeHtml(key)}</div>`;
                argsHtml += `<button class="mini-copy-btn" onclick="copyArgValueById('${dataId}')" title="复制此参数">
                    <i class="ri-file-copy-line"></i>
                </button>`;
                argsHtml += `</div>`;
                argsHtml += formatSingleArgValue(value, Infinity); // 不限制长度
                argsHtml += '</div>';
            }
            argsHtml += '</div>';
        } else if (tool.argsBuffer) {
            argsHtml = `<pre style="background: var(--bg2); padding: 12px; border-radius: var(--radius-sm); overflow: auto; font-family: 'SF Mono', Monaco, monospace; font-size: 0.85rem; line-height: 1.5;">${escapeHtml(tool.argsBuffer)}</pre>`;
        } else {
            argsHtml = '<div style="color: var(--text2); font-size: 0.9rem;">暂无参数</div>';
        }
    } else {
        // 代码模式
        let codeContent = '';
        if (tool.args && Object.keys(tool.args).length > 0) {
            codeContent = JSON.stringify(tool.args, null, 2);
        } else if (tool.argsBuffer) {
            codeContent = tool.argsBuffer;
        } else {
            codeContent = '{}';
        }
        argsHtml = `<div style="position: relative;">
            <button class="code-copy-btn" onclick="copyCodeContent('args')" title="复制参数代码">
                <i class="ri-file-copy-line"></i>
            </button>
            <pre id="argsCodeContent" style="background: var(--bg2); padding: 12px 40px 12px 12px; border-radius: var(--radius-sm); overflow: auto; font-family: 'SF Mono', Monaco, monospace; font-size: 0.85rem; line-height: 1.5; max-height: 400px;">${escapeHtml(codeContent)}</pre>
        </div>`;
    }
    
    argsContainer.innerHTML = statusHtml + argsHtml;
    
    // 渲染结果
    if (tool.result !== undefined && resultSection && resultContainer) {
        resultSection.style.display = 'block';
        
        let resultHtml = '';
        if (tool.result) {
            // 检查结果长度
            const resultStr = String(tool.result);
            let displayResult = resultStr;
            let truncated = false;
            
            if (resultStr.length > 100000) {
                displayResult = resultStr.substring(0, 100000);
                truncated = true;
            }
            
            resultHtml = `<div style="position: relative;">`;
            if (truncated) {
                resultHtml += `<div style="color: var(--warning); font-size: 0.85rem; margin-bottom: 8px; padding: 8px 12px; background: var(--warning-light); border-radius: var(--radius-sm);">
                    <i class="ri-alert-line"></i> 结果过长，已截断显示前 100KB
                </div>`;
            }
            resultHtml += `<button class="code-copy-btn" onclick="copyCodeContent('result')" title="复制执行结果">
                <i class="ri-file-copy-line"></i>
            </button>
            <pre id="resultCodeContent" style="background: var(--bg2); padding: 12px 40px 12px 12px; border-radius: var(--radius-sm); overflow: auto; font-family: 'SF Mono', Monaco, monospace; font-size: 0.85rem; line-height: 1.5; max-height: 400px;">${escapeHtml(displayResult)}</pre>
            </div>`;
        } else {
            resultHtml = '<div style="color: var(--text2); font-size: 0.9rem;">无结果</div>';
        }
        
        resultContainer.innerHTML = resultHtml;
    } else if (resultSection) {
        resultSection.style.display = 'none';
    }
}

// 通过 ID 复制单个参数值（只复制值，不包含参数名）
function copyArgValueById(dataId) {
    if (!window.argCopyStore || !window.argCopyStore[dataId]) {
        showToast('参数数据未找到', 'error');
        return;
    }
    
    const { key, value } = window.argCopyStore[dataId];
    // 只复制值，不包含参数名和冒号
    const text = value;
    
    navigator.clipboard.writeText(text).then(() => {
        showToast(`已复制参数 "${key}"`, 'success');
    }).catch(() => {
        showToast('复制失败', 'error');
    });
}

// 复制单个参数值（保留用于兼容，只复制值不包含参数名）
function copyArgValue(key, valueStr) {
    try {
        const value = JSON.parse(valueStr);
        // 只复制值，不包含参数名和冒号
        const text = String(value);
        navigator.clipboard.writeText(text).then(() => {
            showToast(`已复制参数 "${key}"`, 'success');
        }).catch(() => {
            showToast('复制失败', 'error');
        });
    } catch (e) {
        // 只复制值，不包含参数名和冒号
        navigator.clipboard.writeText(valueStr).then(() => {
            showToast(`已复制参数 "${key}"`, 'success');
        }).catch(() => {
            showToast('复制失败', 'error');
        });
    }
}

// 复制代码内容
function copyCodeContent(type) {
    let content = '';
    
    if (type === 'args') {
        const pre = document.getElementById('argsCodeContent');
        if (pre) {
            content = pre.textContent;
        }
    } else if (type === 'result') {
        const pre = document.getElementById('resultCodeContent');
        if (pre) {
            content = pre.textContent;
        }
    }
    
    if (content) {
        navigator.clipboard.writeText(content).then(() => {
            showToast('已复制到剪贴板', 'success');
        }).catch(() => {
            showToast('复制失败', 'error');
        });
    }
}

// 关闭参数模态框
function closeArgModal() {
    const modal = document.getElementById('argModal');
    if (modal) {
        modal.classList.remove('show');
        setTimeout(() => modal.remove(), 300);
    }
    
    // 清除更新定时器
    if (currentArgModalUpdateInterval) {
        clearInterval(currentArgModalUpdateInterval);
        currentArgModalUpdateInterval = null;
    }
    currentArgModalToolId = null;
    currentArgModalViewMode = 'visual';
}

// 复制模态框中的所有内容
function copyModalContent() {
    if (!currentArgModalToolId) return;
    
    const tool = globalToolsData[currentArgModalToolId];
    if (!tool) return;
    
    let text = '';
    
    // 添加工具名
    text += `工具: ${tool.name || '未知'}\n\n`;
    
    // 添加参数
    text += '参数:\n';
    if (tool.args && Object.keys(tool.args).length > 0) {
        text += JSON.stringify(tool.args, null, 2);
    } else if (tool.argsBuffer) {
        text += tool.argsBuffer;
    } else {
        text += '无';
    }
    
    // 添加结果
    if (tool.result !== undefined) {
        text += '\n\n结果:\n';
        text += String(tool.result);
    }
    
    if (text) {
        navigator.clipboard.writeText(text).then(() => {
            showToast('已复制到剪贴板', 'success');
        }).catch(() => {
            showToast('复制失败', 'error');
        });
    }
}

// 通过 ID 查看完整参数值
function viewFullArgValueById(event, dataId) {
    event.stopPropagation();
    
    if (!window.argValueStore || !window.argValueStore[dataId]) {
        showToast('参数数据未找到', 'error');
        return;
    }
    
    const value = window.argValueStore[dataId];
    const valueType = typeof value;
    const isObject = valueType === 'object' && value !== null;
    
    let content = '';
    if (isObject) {
        // 对象或数组：格式化显示
        content = `<pre style="margin: 0; padding: 16px; background: var(--bg2); border-radius: var(--radius-sm); overflow: auto; max-height: 70vh; font-family: 'SF Mono', Monaco, monospace; font-size: 0.85rem; line-height: 1.5;">${escapeHtml(JSON.stringify(value, null, 2))}</pre>`;
    } else {
        // 字符串：保留换行和格式
        content = `<div style="margin: 0; padding: 16px; background: var(--bg2); border-radius: var(--radius-sm); overflow: auto; max-height: 70vh; white-space: pre-wrap; word-break: break-word; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 0.9rem; line-height: 1.6;">${escapeHtml(String(value))}</div>`;
    }
    
    showSimpleModal('完整参数', content, value);
}

// 查看完整参数值（弹窗）- 保留用于兼容性
function viewFullArgValue(event, valueJson) {
    event.stopPropagation();
    
    try {
        const value = JSON.parse(valueJson);
        const valueType = typeof value;
        const isObject = valueType === 'object' && value !== null;
        
        let content = '';
        if (isObject) {
            // 对象或数组：格式化显示
            content = `<pre style="margin: 0; padding: 16px; background: var(--bg2); border-radius: var(--radius-sm); overflow: auto; max-height: 70vh; font-family: 'SF Mono', Monaco, monospace; font-size: 0.85rem; line-height: 1.5;">${escapeHtml(JSON.stringify(value, null, 2))}</pre>`;
        } else {
            // 字符串：保留换行和格式
            content = `<div style="margin: 0; padding: 16px; background: var(--bg2); border-radius: var(--radius-sm); overflow: auto; max-height: 70vh; white-space: pre-wrap; word-break: break-word; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 0.9rem; line-height: 1.6;">${escapeHtml(value)}</div>`;
        }
        
        showSimpleModal('完整参数', content, value);
    } catch (e) {
        showToast('解析参数失败', 'error');
    }
}

// 显示简单模态框（用于单个值）
function showSimpleModal(title, content, rawValue) {
    // 移除已存在的模态框
    const existingModal = document.getElementById('simpleModal');
    if (existingModal) existingModal.remove();
    
    const modal = document.createElement('div');
    modal.id = 'simpleModal';
    modal.className = 'arg-modal';
    
    // 存储原始值用于复制
    modal.dataset.rawValue = typeof rawValue === 'string' ? rawValue : JSON.stringify(rawValue, null, 2);
    
    modal.innerHTML = `
        <div class="arg-modal-overlay" onclick="closeSimpleModal()"></div>
        <div class="arg-modal-content">
            <div class="arg-modal-header">
                <h3 style="margin: 0; font-size: 1.1rem; font-weight: 600;">${escapeHtml(title)}</h3>
                <button class="arg-modal-close" onclick="closeSimpleModal()" title="关闭">
                    <i class="ri-close-line"></i>
                </button>
            </div>
            <div class="arg-modal-body">
                ${content}
            </div>
            <div class="arg-modal-footer">
                <button class="btn-secondary" onclick="copySimpleModalContent()">
                    <i class="ri-file-copy-line"></i> 复制
                </button>
                <button class="btn-primary" onclick="closeSimpleModal()">关闭</button>
            </div>
        </div>
    `;
    
    document.body.appendChild(modal);
    
    // 添加动画
    setTimeout(() => modal.classList.add('show'), 10);
}

// 关闭简单模态框
function closeSimpleModal() {
    const modal = document.getElementById('simpleModal');
    if (modal) {
        modal.classList.remove('show');
        setTimeout(() => modal.remove(), 300);
    }
}

// 复制简单模态框内容
function copySimpleModalContent() {
    const modal = document.getElementById('simpleModal');
    if (!modal) return;
    
    // 优先使用存储的原始值
    const rawValue = modal.dataset.rawValue;
    if (rawValue) {
        navigator.clipboard.writeText(rawValue).then(() => {
            showToast('已复制到剪贴板', 'success');
        }).catch(() => {
            showToast('复制失败', 'error');
        });
        return;
    }
    
    // 降级方案：从 DOM 提取
    const content = modal.querySelector('.arg-modal-body pre, .arg-modal-body div');
    if (content) {
        const text = content.textContent;
        navigator.clipboard.writeText(text).then(() => {
            showToast('已复制到剪贴板', 'success');
        }).catch(() => {
            showToast('复制失败', 'error');
        });
    }
}

function updateToolArgsLoading(id, argsBuffer) {
    const card = document.getElementById(id);
    if (!card) return;
    const argsContainer = card.querySelector('.tool-args-container');
    if (!argsContainer) return;
    
    // 检查是否已经有加载容器
    let loadingDiv = argsContainer.querySelector('.tool-args-loading');
    if (!loadingDiv) {
        // 创建加载容器（只创建一次）
        argsContainer.innerHTML = `<div class="tool-args-loading">
            <div style="color: var(--text2); font-size: 0.8rem; margin-bottom: 4px;">正在接收参数...</div>
            <pre class="args-buffer" style="background: var(--card); padding: 8px; border-radius: var(--radius-sm); font-size: 0.75rem; max-height: 100px; overflow: auto; font-family: 'SF Mono', Monaco, monospace; white-space: pre-wrap; word-break: break-all;"></pre>
        </div>`;
        loadingDiv = argsContainer.querySelector('.tool-args-loading');
    }
    
    // 只更新 pre 的 textContent，避免重新创建 DOM（防止闪烁）
    const pre = loadingDiv.querySelector('.args-buffer');
    if (pre) {
        // 使用 textContent 而不是 innerHTML，避免重排
        // 只在内容真正改变时更新
        if (pre.textContent !== argsBuffer) {
            pre.textContent = argsBuffer;
            // 自动滚动到底部，显示最新内容
            pre.scrollTop = pre.scrollHeight;
        }
    }
}

function updateToolResult(id, result) {
    const card = document.getElementById(id);
    if (!card) {
        console.warn('[updateToolResult] 工具卡片未找到:', id);
        return;
    }
    const statusEl = card.querySelector('.tool-status');
    if (statusEl) {
        statusEl.className = 'tool-status done';
        statusEl.textContent = '完成';
    }
    const rs = card.querySelector('.tool-result');
    if (rs) { 
        rs.style.display = 'block'; 
        const pre = rs.querySelector('pre');
        if (pre) {
            // 优化：对于超长结果，使用 textContent 而不是 innerHTML，避免渲染卡顿
            if (result && result.length > 50000) {
                // 超长结果，分批渲染
                pre.textContent = '';
                const chunkSize = 10000;
                let offset = 0;
                
                const renderChunk = () => {
                    if (offset < result.length) {
                        const chunk = result.substring(offset, offset + chunkSize);
                        pre.textContent += chunk;
                        offset += chunkSize;
                        // 使用 requestIdleCallback 或 setTimeout 避免阻塞
                        if (window.requestIdleCallback) {
                            requestIdleCallback(renderChunk);
                        } else {
                            setTimeout(renderChunk, 0);
                        }
                    }
                };
                renderChunk();
            } else {
                // 正常长度，直接设置
                pre.textContent = result;
            }
        }
    }
}

// ========== 思考过程卡片 ==========
function createThinkingCardHtml(content, id, isDone = true) {
    return `<div class="tool-card thinking" id="${id}">
        <div class="tool-header" onclick="toggleThinking('${id}')">
            <div class="tool-title"><span class="icon">${iconBrain}</span><span>深度思考</span>
                <span class="tool-status ${isDone ? 'done' : 'running'}">${isDone ? '完成' : '思考中...'}</span></div>
            <span class="tool-arrow">▼</span>
        </div>
        <div class="tool-body"><div class="tool-content">
            <div class="tool-section"><div class="tool-section-title">思考过程</div><pre id="${id}-content">${escapeHtml(content || '')}</pre></div>
        </div></div>
    </div>`;
}

function toggleThinking(id) { document.getElementById(id)?.classList.toggle('open'); }

function updateThinkingContent(id, content, isDone = false) {
    const card = document.getElementById(id);
    if (!card) return;
    const pre = document.getElementById(id + '-content');
    if (pre) pre.textContent = content;
    if (isDone) {
        card.querySelector('.tool-status').className = 'tool-status done';
        card.querySelector('.tool-status').textContent = '完成';
    }
}

// ========== 会话列表（角色卡分组） ==========
let allGroupedData = [];
let expandedPersonas = new Set(['default']);

// ========== 角色卡选择器 ==========
function getPersonaColor(id) {
    const colors = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'];
    return colors[id % colors.length];
}

function getInitial(name) {
    return name ? name.charAt(0).toUpperCase() : '?';
}

function updatePersonaSelectorDisplay() {
    const displayEl = document.getElementById('currentPersonaDisplay');
    if (!displayEl) return;

    if (currentPersonaId === null) {
        displayEl.textContent = '默认助手';
    } else {
        // 从全局变量中查找角色卡
        const persona = window.allPersonas?.find(p => p.id === currentPersonaId);
        if (persona) {
            displayEl.textContent = persona.name;
        } else {
            displayEl.textContent = '默认助手';
        }
    }
}

async function loadPersonaSelector() {
    try {
        const r = await fetch('/nex/personas');
        const d = await r.json();
        const personas = d.data || [];

        // 保存到全局变量供其他函数使用
        window.allPersonas = personas;

        // 从 localStorage 读取当前角色卡
        const savedPersonaId = localStorage.getItem('currentPersonaId');

        // 如果没有保存的或已删除，使用 null（默认助手）
        if (savedPersonaId === 'null' || savedPersonaId === null) {
            currentPersonaId = null;
        } else if (!personas.find(p => p.id == savedPersonaId)) {
            currentPersonaId = null;
        } else {
            currentPersonaId = parseInt(savedPersonaId);
        }

        // 更新选择器显示
        updatePersonaSelectorDisplay();

        // 渲染下拉菜单
        renderPersonaDropdown(personas);

        // 加载当前角色卡的会话（添加 await）
        await loadSessions();
    } catch (e) {
        console.error('加载角色卡列表失败', e);
    }
}

function renderPersonaDropdown(personas) {
    const dropdown = document.getElementById('personaDropdown');
    if (!dropdown) return;

    // 添加"默认助手"选项
    const allPersonas = [
        { id: null, name: '默认助手', avatar: null },
        ...personas
    ];

    dropdown.innerHTML = allPersonas.map(p => {
        const isActive = (p.id === null && currentPersonaId === null) || (p.id === currentPersonaId);
        const bgColor = p.id !== null ? getPersonaColor(p.id) : 'var(--text2)';
        const iconHtml = p.avatar
            ? `<img src="${p.avatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
            : getInitial(p.name);

        // 处理 null id - 使用字符串 'null' 在 HTML 中，然后在 selectPersona 中转换
        const onclickCode = p.id === null 
            ? `selectPersona(null)` 
            : `selectPersona(${p.id})`;

        return `
            <div class="persona-dropdown-item ${isActive ? 'active' : ''}" onclick="${onclickCode}">
                <div class="persona-dropdown-item-icon" style="background:${p.avatar ? 'transparent' : bgColor}">${iconHtml}</div>
                <div class="persona-dropdown-item-name">${escapeHtml(p.name)}</div>
            </div>
        `;
    }).join('');
}

function togglePersonaSelector() {
    const dropdown = document.getElementById('personaDropdown');
    if (!dropdown) return;

    dropdown.classList.toggle('open');

    // 点击其他地方关闭
    if (dropdown.classList.contains('open')) {
        document.addEventListener('click', closePersonaDropdown, { once: true });
    }
}

function closePersonaDropdown(e) {
    const dropdown = document.getElementById('personaDropdown');
    const selector = document.getElementById('personaSelector');
    if (dropdown && selector && !dropdown.contains(e.target) && !selector.contains(e.target)) {
        dropdown.classList.remove('open');
    }
}

async function selectPersona(personaId) {
    // 关闭下拉菜单
    const dropdown = document.getElementById('personaDropdown');
    if (dropdown) dropdown.classList.remove('open');

    // 如果选择相同的角色卡，不做处理
    if (personaId === currentPersonaId) return;

    // 保存到 localStorage
    localStorage.setItem('currentPersonaId', personaId);
    currentPersonaId = personaId;

    // 清空当前会话
    currentSessionId = null;
    localStorage.removeItem('currentSessionId');

    // 更新选择器显示
    updatePersonaSelectorDisplay();

    // 重新渲染下拉菜单（更新选中状态）
    renderPersonaDropdown(window.allPersonas || []);

    // 加载该角色卡的会话列表
    await loadSessions();
}
// ========== 会话加载 ==========

async function loadSessions() {
    await loadPersonaSessions(currentPersonaId);
}

async function loadPersonaSessions(personaId) {
    try {
        // 根据角色卡获取会话
        // 注意：personaId 为 null 时也要传递参数，以便只获取默认助手的会话
        const url = personaId !== undefined && personaId !== null 
            ? `/nex/sessions?persona_id=${personaId}` 
            : '/nex/sessions?persona_id=null';
        const r = await fetch(url);
        const d = await r.json();
        const sessions = d.data?.sessions || d.data || [];

        // 渲染会话列表
        renderSessionList(sessions);

        // 如果没有当前会话，尝试恢复上次的会话
        if (!currentSessionId) {
            const savedSessionId = localStorage.getItem('currentSessionId');
            
            // 检查保存的会话是否在当前列表中
            if (savedSessionId && sessions.find(s => s.id == savedSessionId)) {
                currentSessionId = parseInt(savedSessionId);
            } else if (sessions.length > 0) {
                // 如果保存的会话不存在，选择第一个
                currentSessionId = sessions[0].id;
                localStorage.setItem('currentSessionId', currentSessionId);
            }
            
            if (currentSessionId) {
                // 重新渲染列表以更新高亮
                renderSessionList(sessions);
                // 并行加载角色信息和消息
                await Promise.all([
                    updatePersonaDisplay(),
                    loadMessages(currentSessionId)
                ]);
            } else {
                // 没有会话时显示欢迎界面
                document.getElementById('sessionTitle').textContent = 'NexAgent';
                showWelcomeState();
            }
        } else {
            // 检查当前会话是否还存在
            if (!sessions.find(s => s.id === currentSessionId)) {
                // 当前会话已被删除，清空
                currentSessionId = null;
                localStorage.removeItem('currentSessionId');
                
                // 如果还有其他会话，选择第一个
                if (sessions.length > 0) {
                    currentSessionId = sessions[0].id;
                    localStorage.setItem('currentSessionId', currentSessionId);
                    renderSessionList(sessions);
                    await Promise.all([
                        updatePersonaDisplay(),
                        loadMessages(currentSessionId)
                    ]);
                } else {
                    // 没有会话了
                    document.getElementById('sessionTitle').textContent = 'NexAgent';
                    showWelcomeState();
                }
            } else {
                // 当前会话仍然存在，刷新显示
                await Promise.all([
                    updatePersonaDisplay(),
                    loadMessages(currentSessionId)
                ]);
            }
        }
    } catch (e) {
        console.error('加载会话失败', e);
        showToast('加载会话失败: ' + e.message, 'error');
    }
}

async function switchSession(sessionId) {
    if (currentSessionId === sessionId) return;
    currentSessionId = sessionId;
    
    // 保存当前会话到 localStorage
    localStorage.setItem('currentSessionId', sessionId);
    
    // 立即更新 UI 状态
    document.querySelectorAll('.persona-session-item').forEach(item => {
        item.classList.toggle('active', parseInt(item.dataset.id) === sessionId);
    });
    closeMobileSidebar();
    
    // 显示加载状态
    const msgsContainer = document.getElementById('msgs');
    msgsContainer.innerHTML = '<div style="padding:40px;text-align:center;color:var(--text2);"><i class="ri-loader-4-line" style="font-size:2rem;animation:spin 1s linear infinite;"></i><div style="margin-top:12px;">加载中...</div></div>';
    
    // 并行加载角色信息和消息
    await Promise.all([
        updatePersonaDisplay(),
        loadMessages(sessionId)
    ]);
}

function renderSessionList(sessions) {
    const list = document.getElementById('personaList');
    const searchText = document.getElementById('sessionSearch')?.value?.toLowerCase().trim() || '';

    let filteredSessions = sessions;
    if (searchText) {
        filteredSessions = sessions.filter(s =>
            s.name.toLowerCase().includes(searchText) ||
            (s.user && s.user.toLowerCase().includes(searchText))
        );
    }

    if (filteredSessions.length === 0) {
        list.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center">暂无会话<br><span style="font-size:0.85rem">点击"新建"开始</span></div>';
        return;
    }

    list.innerHTML = filteredSessions.map(s => `
        <div class="persona-session-item ${s.id === currentSessionId ? 'active' : ''}" data-id="${s.id}" onclick="switchSession(${s.id})">
            <div class="persona-session-info">
                <div class="persona-session-name">${escapeHtml(s.name)}</div>
                <div class="persona-session-meta">${s.message_count || 0}条消息</div>
            </div>
            <div class="persona-session-actions">
                <button class="btn-icon" onclick="event.stopPropagation();editSession(${s.id},'${escapeHtml(s.name).replace(/'/g, "\\'")}')" title="编辑">${iconEdit}</button>
                <button class="btn-icon delete" onclick="event.stopPropagation();deleteSession(${s.id})" title="删除">${iconDelete}</button>
            </div>
        </div>
    `).join('');
}

function filterSessions() {
    loadPersonaSessions(currentPersonaId);
}

async function refreshPersonaList() {
    await loadPersonaSelector();
    await loadSessions();
}

async function updateSessionList() {
    await loadPersonaSessions(currentPersonaId);
}

// ========== 输入工具栏 ==========
function toggleToolbarDropdown(id) {
    const dropdown = document.getElementById(id);
    const wasOpen = dropdown.classList.contains('open');

    // 关闭所有下拉菜单
    document.querySelectorAll('.toolbar-dropdown').forEach(d => d.classList.remove('open'));

    // 如果之前是关闭的，则打开
    if (!wasOpen) {
        dropdown.classList.add('open');
        
        // 移动端调整菜单位置
        if (window.innerWidth <= 600) {
            const menu = dropdown.querySelector('.toolbar-dropdown-menu');
            if (menu) {
                const inputArea = document.querySelector('.input-area');
                if (inputArea) {
                    const inputRect = inputArea.getBoundingClientRect();
                    menu.style.bottom = (window.innerHeight - inputRect.top + 8) + 'px';
                }
            }
        }
        
        // 加载对应的内容
        if (id === 'toolbarModelDropdown') loadToolbarModels();
        if (id === 'toolbarUserDropdown') loadUsers();
        else if (id === 'toolbarToolsDropdown') loadToolbarTools();
        else if (id === 'toolbarPersonaDropdown') loadToolbarPersonas();
    }
}

async function loadToolbarModels() {
    const menu = document.getElementById('toolbarModelMenu');
    try {
        const r = await fetch('/nex/models');
        const d = await r.json();
        chatModels = d.data.models.filter(m => m.model_type !== 'embedding');

        if (chatModels.length === 0) {
            menu.innerHTML = '<div class="toolbar-empty">暂无可用模型</div>';
            return;
        }

        // 按供应商分组
        const grouped = {};
        chatModels.forEach(m => {
            const provider = m.provider_name || '未知供应商';
            if (!grouped[provider]) grouped[provider] = [];
            grouped[provider].push(m);
        });

        let html = '<div class="toolbar-dropdown-header">选择模型</div>';
        for (const [provider, models] of Object.entries(grouped)) {
            html += `<div class="toolbar-dropdown-subtitle">${escapeHtml(provider)}</div>`;
            html += models.map(m => `
                    <div class="toolbar-dropdown-item ${m.key === currentModelKey ? 'active' : ''}" onclick="selectToolbarModel('${m.key}')">
                        <div class="toolbar-dropdown-item-icon">
                            <i class="ri-stack-line"></i>
                        </div>
                        <div class="toolbar-dropdown-item-info">
                            <div class="toolbar-dropdown-item-name">${escapeHtml(m.name)}</div>
                            <div class="toolbar-dropdown-item-desc">${escapeHtml(m.model)}</div>
                        </div>
                    </div>
                `).join('');
        }
        menu.innerHTML = html;
    } catch (e) { menu.innerHTML = '<div class="toolbar-empty">加载失败</div>'; }
}

async function selectToolbarModel(key) {
    document.getElementById('toolbarModelDropdown').classList.remove('open');
    if (key === currentModelKey) return;
    currentModelKey = key;
    localStorage.setItem('currentModelKey', key);
    // 重新加载模型列表以更新显示
    await loadModels();
    // 更新思考程度按钮显示状态
    updateReasoningDropdownVisibility();
}

function updateToolbarModelName() {
    const el = document.getElementById('toolbarModelName');
    if (!el) return;
    const current = chatModels.find(m => m.key === currentModelKey);
    if (current) {
        el.textContent = current.name.length > 10 ? current.name.substring(0, 10) + '...' : current.name;
    } else {
        el.textContent = '模型';
    }
}

// 思考选项选择（统一处理思考模式和程度）
function selectThinkingOption(option) {
    document.getElementById('toolbarReasoningDropdown').classList.remove('open');
    
    // 根据选项设置对应的参数
    // default: 不传任何参数
    // auto: thinking=auto, 不传reasoning_effort
    // disabled: thinking=disabled, 不传reasoning_effort
    // minimal/low/medium/high: thinking=enabled, reasoning_effort=对应值
    
    const optionMap = {
        'default': { thinking: null, effort: null, display: '默认' },
        'auto': { thinking: 'auto', effort: null, display: '自动' },
        'disabled': { thinking: 'disabled', effort: null, display: '关闭' },
        'minimal': { thinking: 'enabled', effort: 'minimal', display: '最低' },
        'low': { thinking: 'enabled', effort: 'low', display: '低' },
        'medium': { thinking: 'enabled', effort: 'medium', display: '中' },
        'high': { thinking: 'enabled', effort: 'high', display: '高' }
    };
    
    const config = optionMap[option];
    if (config) {
        currentThinkingMode = config.thinking;
        currentReasoningEffort = config.effort;
        localStorage.setItem('thinkingOption', option);
        document.getElementById('toolbarReasoningLevel').textContent = config.display;
    }
}

// 更新思考程度下拉框的显示状态
function updateReasoningDropdownVisibility() {
    const dropdown = document.getElementById('toolbarReasoningDropdown');
    if (!dropdown) return;

    // 获取当前模型信息
    const currentModel = chatModels.find(m => m.key === currentModelKey);

    // 判断是否是思考模型（有 reasoning 标签）
    const isReasoningModel = currentModel && currentModel.tags && currentModel.tags.includes('reasoning');
    // Claude 不支持 thinking_mode 参数，隐藏思考设置
    const isClaudeProvider = currentModel && currentModel.provider_type === 'claude';

    // 显示或隐藏思考程度按钮
    dropdown.style.display = (isReasoningModel && !isClaudeProvider) ? '' : 'none';
}

async function loadToolbarTools() {
    const menu = document.getElementById('toolbarToolsMenu');
    try {
        const r = await fetch('/nex/tools');
        const d = await r.json();
        const tools = d.data || [];
        toolsEnabled = d.enabled !== false;
        
        // 只统计启用的工具
        const enabledTools = tools.filter(t => t.enabled);
        const enabledCount = enabledTools.length;

        const badge = document.getElementById('toolbarToolsCount');
        if (toolsEnabled && enabledCount > 0) {
            badge.textContent = enabledCount;
            badge.style.display = '';
        } else {
            badge.textContent = '';
            badge.style.display = 'none';
        }

        // 工具总开关
        let html = `
            <div class="toolbar-dropdown-item" style="justify-content:space-between;cursor:default;">
                <div style="display:flex;align-items:center;gap:8px;">
                    <div class="toolbar-dropdown-item-icon">
                        <i class="ri-tools-line"></i>
                    </div>
                    <span>启用工具调用</span>
                </div>
                <label class="toggle-switch" style="margin:0;">
                    <input type="checkbox" ${toolsEnabled ? 'checked' : ''} onchange="toggleToolsEnabled(this.checked)">
                    <span class="toggle-slider"></span>
                </label>
            </div>
        `;

        // 可用工具标题 + 刷新按钮
        html += `
            <div class="toolbar-dropdown-header" style="display:flex;justify-content:space-between;align-items:center;">
                <span>可用工具 <span style="color:var(--accent)">${enabledCount}</span></span>
                <button class="btn-icon" onclick="reloadTools(event)" title="重新加载工具" style="width:24px;height:24px;padding:0;">
                    <i class="ri-refresh-line"></i>
                </button>
            </div>
        `;

        if (enabledCount === 0) {
            html += '<div class="toolbar-empty">暂无可用工具</div>';
            menu.innerHTML = html;
            return;
        }

        // 按类型分组（只显示启用的）
        const builtinTools = enabledTools.filter(t => t.type === 'builtin');
        const mcpTools = enabledTools.filter(t => t.type === 'mcp');
        const customTools = enabledTools.filter(t => t.type === 'custom');
        const pluginTools = enabledTools.filter(t => t.type === 'plugin');

        if (builtinTools.length > 0) {
            html += builtinTools.map(t => renderToolbarToolItem(t, '内置')).join('');
        }
        if (mcpTools.length > 0) {
            html += mcpTools.map(t => renderToolbarToolItem(t, 'MCP')).join('');
        }
        if (customTools.length > 0) {
            html += customTools.map(t => renderToolbarToolItem(t, '自定义')).join('');
        }
        if (pluginTools.length > 0) {
            html += pluginTools.map(t => renderToolbarToolItem(t, '插件')).join('');
        }

        menu.innerHTML = html;
    } catch (e) { menu.innerHTML = '<div class="toolbar-empty">加载失败</div>'; }
}

async function reloadTools(e) {
    e.stopPropagation();
    try {
        const r = await fetch('/nex/tools/reload', { method: 'POST' });
        const d = await r.json();
        if (d.data.errors && d.data.errors.length > 0) {
            showToast(`加载完成，${d.data.errors.length} 个工具出错`, 'warning');
        } else {
            showToast(`已加载 ${d.data.loaded.length} 个自定义工具`, 'success');
        }
        await loadToolbarTools();
        await loadToolbarToolsCount();
    } catch (e) {
        showToast('刷新失败', 'error');
    }
}

async function toggleToolsEnabled(enabled) {
    try {
        await fetch(`/nex/tools/toggle?enabled=${enabled}`, { method: 'PUT' });
        toolsEnabled = enabled;
        // 更新角标显示
        await loadToolbarToolsCount();
    } catch (e) {
        showToast('切换失败', 'error');
    }
}

function renderToolbarToolItem(tool, typeLabel) {
    let iconHtml;
    if (tool.type === 'mcp') {
        iconHtml = '<i class="ri-link"></i>';
    } else if (tool.type === 'plugin') {
        iconHtml = '<i class="ri-puzzle-line"></i>';
    } else {
        iconHtml = '<i class="ri-tools-line"></i>';
    }

    return `
                <div class="toolbar-dropdown-item" title="${escapeHtml(tool.description || '')}">
                    <div class="toolbar-dropdown-item-icon">${iconHtml}</div>
                    <div class="toolbar-dropdown-item-info">
                        <div class="toolbar-dropdown-item-name">${escapeHtml(tool.name)}</div>
                        <div class="toolbar-dropdown-item-desc">${typeLabel} · ${escapeHtml((tool.description || '').substring(0, 30))}${(tool.description || '').length > 30 ? '...' : ''}</div>
                    </div>
                </div>
            `;
}

async function loadToolbarPersonas() {
    const menu = document.getElementById('toolbarPersonaMenu');
    try {
        const r = await fetch('/nex/personas');
        const d = await r.json();
        const personas = d.data || [];

        let html = '<div class="toolbar-dropdown-header">切换角色卡</div>';
        html += `
                    <div class="toolbar-dropdown-item ${!currentSessionPersonaId ? 'active' : ''}" onclick="selectToolbarPersona(null)">
                        <div class="toolbar-dropdown-item-icon">
                            <i class="ri-user-line"></i>
                        </div>
                        <div class="toolbar-dropdown-item-info">
                            <div class="toolbar-dropdown-item-name">默认助手</div>
                            <div class="toolbar-dropdown-item-desc">通用对话</div>
                        </div>
                    </div>
                `;

        html += personas.map(p => {
            const iconHtml = p.avatar 
                ? `<img src="${p.avatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
                : `<span style="color:white;font-size:0.75rem;font-weight:600;">${p.name.charAt(0).toUpperCase()}</span>`;
            return `
                    <div class="toolbar-dropdown-item ${currentSessionPersonaId === p.id ? 'active' : ''}" onclick="selectToolbarPersona(${p.id})">
                        <div class="toolbar-dropdown-item-icon" style="background:${p.avatar ? 'transparent' : getPersonaColor(p.id)}">
                            ${iconHtml}
                        </div>
                        <div class="toolbar-dropdown-item-info">
                            <div class="toolbar-dropdown-item-name">${escapeHtml(p.name)}</div>
                            <div class="toolbar-dropdown-item-desc">${escapeHtml((p.system_prompt || '').substring(0, 30))}${(p.system_prompt || '').length > 30 ? '...' : ''}</div>
                        </div>
                    </div>
                `;
        }).join('');

        menu.innerHTML = html;
    } catch (e) { menu.innerHTML = '<div class="toolbar-empty">加载失败</div>'; }
}

async function selectToolbarPersona(personaId) {
    document.getElementById('toolbarPersonaDropdown').classList.remove('open');
    if (!currentSessionId) {
        showToast('请先选择或创建会话', 'warning');
        return;
    }
    try {
        await fetch(`/nex/sessions/${currentSessionId}/persona`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ persona_id: personaId })
        });
        currentSessionPersonaId = personaId;
        // 先更新角色显示（设置 window.currentPersonaAvatar），再刷新消息
        await updatePersonaDisplay();
        await loadMessages(currentSessionId);
        // 刷新侧边栏
        await loadPersonaSessions(currentPersonaId);
    } catch (e) { showToast('切换失败: ' + e.message, 'error'); }
}

// 初始化时加载工具数量
async function loadToolbarToolsCount() {
    try {
        const r = await fetch('/nex/tools');
        const d = await r.json();
        toolsEnabled = d.enabled !== false;
        const tools = d.data || [];
        // 只统计启用的工具
        const enabledCount = tools.filter(t => t.enabled).length;
        const badge = document.getElementById('toolbarToolsCount');
        if (toolsEnabled && enabledCount > 0) {
            badge.textContent = enabledCount;
            badge.style.display = '';
        } else {
            badge.textContent = '';
            badge.style.display = 'none';
        }
    } catch (e) { }
}

// ========== 用户管理 ==========
async function loadUsers() {
    try {
        const r = await fetch('/nex/users');
        const d = await r.json();
        const userList = document.getElementById('userList');
        
        if (d.code === 0 && d.data.length > 0) {
            userList.innerHTML = d.data.map(u => `
                <div class="toolbar-dropdown-item" style="display:flex;justify-content:space-between;align-items:center;">
                    <div style="flex:1;cursor:pointer;" onclick="switchUser('${escapeHtml(u.name)}')">
                        <div class="toolbar-dropdown-item-text">${escapeHtml(u.name)}</div>
                    </div>
                    <button onclick="deleteUser('${escapeHtml(u.name)}');event.stopPropagation();" 
                        style="padding:4px 8px;background:transparent;border:none;color:var(--text2);cursor:pointer;border-radius:var(--radius-sm);"
                        title="删除">
                        <i class="ri-delete-bin-line"></i>
                    </button>
                </div>
            `).join('');
        } else {
            userList.innerHTML = '<div style="padding:10px;text-align:center;color:var(--text2);font-size:0.85rem;">暂无用户</div>';
        }
    } catch (e) {
        console.error('加载用户列表失败:', e);
    }
}

async function saveAndSwitchUser() {
    const input = document.getElementById('usernameInput');
    const name = input.value.trim();
    
    if (!name) {
        showToast('用户名不能为空', 'warning');
        return;
    }
    
    try {
        // 保存新用户到数据库
        const r = await fetch('/nex/users', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ name })
        });
        const d = await r.json();
        
        if (d.code === 0) {
            switchUser(name);
            await loadUsers();
            input.value = '';
        } else {
            showToast(d.message || '保存失败', 'error');
        }
    } catch (e) {
        showToast('保存失败: ' + e.message, 'error');
    }
}

function switchUser(name) {
    const displayName = name.trim() || 'guest';
    document.getElementById('usernameInput').value = '';
    document.getElementById('toolbarUserName').textContent = displayName.length > 8 ? displayName.substring(0, 8) + '...' : displayName;
    document.getElementById('toolbarUserDropdown').classList.remove('open');
    
    // 只在浏览器端保存当前用户
    localStorage.setItem('nex_current_user', displayName);
    
    showToast('已切换到用户: ' + displayName, 'success');
}

async function deleteUser(name) {
    showDeleteConfirm('删除用户', `确定要删除用户 "${name}" 吗？`, async () => {
        try {
            const r = await fetch(`/nex/users/${encodeURIComponent(name)}`, {
                method: 'DELETE'
            });
            const d = await r.json();
            
            if (d.code === 0) {
                showToast('删除成功', 'success');
                await loadUsers();
            } else {
                showToast(d.message || '删除失败', 'error');
            }
        } catch (e) {
            showToast('删除失败: ' + e.message, 'error');
        }
    });
}

// 更新用户名显示（兼容旧代码）
function updateUserName(name) {
    switchUser(name);
}

// 系统提示词开关状态
let systemPromptEnabled = false;
let systemPromptContent = '';

// 切换系统提示词
function toggleSystemPrompt() {
    systemPromptEnabled = !systemPromptEnabled;
    const btn = document.getElementById('systemPromptToggle');
    if (systemPromptEnabled) {
        btn.classList.add('active');
        showToast('系统提示词已启用', 'success');
    } else {
        btn.classList.remove('active');
        showToast('系统提示词已禁用', 'info');
    }
    localStorage.setItem('nex_system_prompt_enabled', systemPromptEnabled);
}

// 切换 TTS 自动播放
async function toggleAutoPlay() {
    window.ttsAutoPlay = !window.ttsAutoPlay;
    const btn = document.getElementById('ttsAutoPlayBtn');
    const icon = btn.querySelector('i');
    
    if (window.ttsAutoPlay) {
        btn.classList.add('active');
        icon.className = 'ri-volume-up-line';
    } else {
        btn.classList.remove('active');
        icon.className = 'ri-volume-mute-line';
    }
    
    // 保存状态到 localStorage
    localStorage.setItem('nex_tts_auto_play', window.ttsAutoPlay);
}

// 初始化 TTS 自动播放状态
async function initTTSAutoPlay() {
    try {
        // 检查 TTS 配置是否存在
        const configR = await fetch('/nex/tts/config');
        const configD = await configR.json();
        
        // 检查是否有 api_key_masked（后端返回的是掩码后的 key）
        window.hasTTSConfig = configD.code === 0 && 
                            configD.data && 
                            configD.data.api_key_masked && 
                            configD.data.api_key_masked.trim() !== '';
        
        if (window.hasTTSConfig) {
            // 有 TTS 配置，显示按钮
            const btn = document.getElementById('ttsAutoPlayBtn');
            if (btn) {
                btn.style.display = 'inline-flex';
            }
            
            // 从 localStorage 恢复状态
            const savedState = localStorage.getItem('nex_tts_auto_play');
            window.ttsAutoPlay = savedState === 'true';
            
            // 更新按钮状态
            if (btn) {
                const icon = btn.querySelector('i');
                if (window.ttsAutoPlay) {
                    btn.classList.add('active');
                    icon.className = 'ri-volume-up-line';
                } else {
                    btn.classList.remove('active');
                    icon.className = 'ri-volume-mute-line';
                }
            }
        }
    } catch (e) {
        console.error('初始化 TTS 自动播放状态失败', e);
        window.hasTTSConfig = false;
    }
}

// 加载系统提示词
async function loadSystemPrompt() {
    try {
        const r = await fetch('/nex/prompts');
        const d = await r.json();
        systemPromptContent = d.data?.prompt || '';
        // 恢复开关状态
        systemPromptEnabled = localStorage.getItem('nex_system_prompt_enabled') === 'true';
        if (systemPromptEnabled) {
            document.getElementById('systemPromptToggle')?.classList.add('active');
        }
    } catch (e) { console.error('加载系统提示词失败', e); }
}

// 获取系统提示词（供发送消息时使用）
function getSystemPromptIfEnabled() {
    return systemPromptEnabled ? systemPromptContent : '';
}

// ========== 初始化 ==========
async function init() {
    await initTheme();
    
    // 先加载用户列表，根据是否有用户决定使用哪个用户名
    const usersResponse = await fetch('/nex/users');
    const usersData = await usersResponse.json();
    const hasUsers = usersData.code === 0 && usersData.data && usersData.data.length > 0;
    
    let currentUser = 'guest';
    if (hasUsers) {
        // 有用户记录：从 localStorage 读取上次使用的用户
        const savedUser = localStorage.getItem('nex_current_user');
        if (savedUser) {
            // 验证 localStorage 中的用户是否还存在于数据库中
            const userExists = usersData.data.some(u => u.name === savedUser);
            if (userExists) {
                currentUser = savedUser;
            } else {
                // 用户已被删除，清除 localStorage 并使用 guest
                localStorage.removeItem('nex_current_user');
                currentUser = 'guest';
            }
        } else {
            // 如果没有设置，使用 guest
            currentUser = 'guest';
        }
    } else {
        // 如果没有用户记录，清除 localStorage 并使用 guest
        localStorage.removeItem('nex_current_user');
    }
    
    // 保存当前用户到 localStorage（确保一致性）
    localStorage.setItem('nex_current_user', currentUser);
    
    // 更新工具栏用户名显示
    document.getElementById('toolbarUserName').textContent = currentUser.length > 8 ? currentUser.substring(0, 8) + '...' : currentUser;
    
    // 加载用户列表
    await loadUsers();
    await loadModels();
    // 先初始化角色卡选择器
    await loadPersonaSelector();
    // 先初始化 TTS 状态，再加载会话（会话加载会触发消息渲染）
    await initTTSAutoPlay();
    await loadVersion();
    await loadToolbarToolsCount();
    await loadSystemPrompt();
    updateToolbarModelName();

    const inp = document.getElementById('inp');
    // 清空输入框，防止浏览器自动填充
    inp.value = '';
    // 延迟再次清空，防止某些浏览器扩展延迟填充
    setTimeout(() => { if (inp.value && !inp.dataset.userInput) inp.value = ''; }, 100);
    setTimeout(() => { if (inp.value && !inp.dataset.userInput) inp.value = ''; }, 500);
    
    inp.addEventListener('keydown', e => {
        // 移动端回车换行，桌面端 Shift+Enter 换行
        const isMobile = window.innerWidth <= 600;
        if (e.key === 'Enter') {
            if (isMobile) {
                // 移动端：回车换行，不发送
                // 默认行为就是换行，不需要处理
            } else if (!e.shiftKey) {
                // 桌面端：回车发送，Shift+Enter 换行
                e.preventDefault();
                send();
            }
        }
    });
    // 标记用户输入
    inp.addEventListener('input', e => {
        inp.dataset.userInput = 'true';
        autoResizeTextarea(e);
    });
    // 输入框自动高度
    inp.addEventListener('input', autoResizeTextarea);

    // 点击其他地方关闭下拉菜单
    document.addEventListener('click', e => {
        // 关闭工具栏下拉菜单
        document.querySelectorAll('.toolbar-dropdown').forEach(d => {
            if (!d.contains(e.target)) d.classList.remove('open');
        });

        // 关闭自定义选择器
        document.querySelectorAll('.custom-select').forEach(s => {
            if (!s.contains(e.target)) s.classList.remove('open');
        });

        // 关闭嵌入模型下拉菜单
        const embedDropdown = document.getElementById('embedModelDropdown');
        const embedMenu = document.getElementById('embedModelMenu');
        if (embedDropdown && embedMenu) {
            // 检查点击是否在下拉按钮或菜单内
            if (!embedDropdown.contains(e.target) && !embedMenu.contains(e.target)) {
                embedDropdown.classList.remove('open');
                // 恢复菜单的相对定位
                embedMenu.style.position = '';
                embedMenu.style.top = '';
                embedMenu.style.left = '';
                embedMenu.style.bottom = '';
            }
        }
    });
}

// ========== 角色卡下拉框 ==========
let currentSessionPersonaId = null;

function togglePersonaDropdown() {
    document.getElementById('personaDropdown').classList.toggle('open');
    loadPersonaMenu();
}

async function loadPersonaMenu() {
    try {
        const r = await fetch('/nex/personas');
        const d = await r.json();
        const menu = document.getElementById('personaMenu');
        let html = `<div class="dropdown-item ${!currentSessionPersonaId ? 'active' : ''}" onclick="selectSessionPersona(null)">
                    <div class="dropdown-item-name">默认</div>
                </div>`;
        html += d.data.map(p => `
                    <div class="dropdown-item ${currentSessionPersonaId === p.id ? 'active' : ''}" onclick="selectSessionPersona(${p.id})">
                        <div class="dropdown-item-name">${escapeHtml(p.name)}</div>
                    </div>
                `).join('');
        menu.innerHTML = html;
    } catch (e) { console.error('加载角色卡失败', e); }
}

async function selectSessionPersona(personaId) {
    document.getElementById('personaDropdown').classList.remove('open');
    if (!currentSessionId) {
        showToast('请先选择或创建会话', 'warning');
        return;
    }
    try {
        await fetch(`/nex/sessions/${currentSessionId}/persona`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ persona_id: personaId })
        });
        currentSessionPersonaId = personaId;
        // 先更新角色显示（设置 window.currentPersonaAvatar），再刷新消息
        await updatePersonaDisplay();
        await loadMessages(currentSessionId);
        // 刷新侧边栏
        await loadPersonaSessions(currentPersonaId);
    } catch (e) { showToast('切换失败: ' + e.message, 'error'); }
}

async function updatePersonaDisplay() {
    if (!currentSessionId) {
        currentSessionPersonaId = null;
        window.currentPersonaAvatar = '';
        return;
    }
    try {
        const r = await fetch(`/nex/sessions/${currentSessionId}/persona`);
        const d = await r.json();
        if (d.data) {
            currentSessionPersonaId = d.data.id;
            window.currentPersonaAvatar = d.data.avatar || '';
        } else {
            currentSessionPersonaId = null;
            window.currentPersonaAvatar = '';
        }
    } catch (e) {
        currentSessionPersonaId = null;
        window.currentPersonaAvatar = '';
    }
}

// 已废弃：setPersonaName 函数被 updatePersonaSelectorDisplay 替代

// ========== 输入框自动高度 ==========
function autoResizeTextarea() {
    const inp = document.getElementById('inp');
    inp.style.height = 'auto';
    inp.style.height = Math.min(inp.scrollHeight, 200) + 'px';
}

function resetTextareaHeight() {
    const inp = document.getElementById('inp');
    inp.style.height = 'auto';
}

// ========== 版本号 ==========
async function loadVersion() {
    try {
        const r = await fetch('/nex/version');
        const d = await r.json();
        if (d.code === 0 && d.data.version) {
            document.getElementById('appVersion').textContent = d.data.version;
        }
    } catch (e) { console.error('获取版本号失败', e); }
}

// ========== 模型管理 ==========
async function loadModels() {
    try {
        const r = await fetch('/nex/models');
        const d = await r.json();
        // 只显示对话模型（嵌入模型不能用于对话）
        chatModels = d.data.models.filter(m => m.model_type !== 'embedding');

        if (chatModels.length === 0) {
            currentModelKey = null;
            localStorage.removeItem('currentModelKey');
        } else {
            // 从 localStorage 读取上次选择的模型
            const savedKey = localStorage.getItem('currentModelKey');
            const savedModel = savedKey ? chatModels.find(m => m.key === savedKey) : null;

            if (savedModel) {
                currentModelKey = savedModel.key;
            } else {
                // 没有保存的或已删除，选第一个
                currentModelKey = chatModels[0].key;
                localStorage.setItem('currentModelKey', currentModelKey);
            }
        }
        updateToolbarModelName();
        updateReasoningDropdownVisibility();  // 更新思考程度按钮显示状态
    } catch { }
}

// ========== 服务商管理 ==========
let currentProviderIdEdit = null;
let currentMCPIdEdit = null;
let selectedProviderId = null;
let isLoadingProviders = false;  // 防止重复加载

async function loadProviderListPanel() {
    if (isLoadingProviders) return;  // 如果正在加载，直接返回
    isLoadingProviders = true;
    
    try {
        const r = await fetch('/nex/providers');
        const d = await r.json();
        const panel = document.getElementById('providerListPanel');
        if (d.data.length === 0) {
            panel.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center">暂无服务商<br><span style="font-size:0.85rem">点击上方按钮添加</span></div>';
            return;
        }
        panel.innerHTML = d.data.map(p => `
                    <div class="provider-item ${selectedProviderId === p.id ? 'active' : ''}" onclick="selectProvider('${p.id}')">
                        <div class="provider-item-name">${escapeHtml(p.name)}</div>
                        <div class="provider-item-detail">${escapeHtml(p.base_url)}</div>
                    </div>
                `).join('');
    } catch (e) { 
        showToast('加载失败: ' + e.message, 'error'); 
    } finally {
        isLoadingProviders = false;
    }
}

function showAddProvider() {
    currentProviderIdEdit = null;
    selectedProviderId = null;
    window.currentProviderType = 'openai';  // 默认OpenAI
    document.getElementById('providerDetailTitle').textContent = '添加服务商';
    document.getElementById('providerIdInput').value = '';
    document.getElementById('providerIdInput').disabled = false;
    document.getElementById('providerNameInput').value = '';
    document.getElementById('providerApiKeyInput').value = '';
    document.getElementById('providerApiKeyInput').placeholder = 'sk-...';
    document.getElementById('providerBaseUrlInput').value = '';
    // 设置类型按钮状态
    document.getElementById('providerTypeOpenAI').classList.add('active');
    document.getElementById('providerTypeClaude').classList.remove('active');
    document.getElementById('providerViewMode').style.display = 'none';
    document.getElementById('providerEditMode').style.display = 'block';
    document.getElementById('providerDeleteRow').style.display = 'none';
    openSettingsDetail('detail-provider');
}

async function selectProvider(id) {
    selectedProviderId = id;
    try {
        const r = await fetch(`/nex/providers/${id}`);
        const d = await r.json();

        // 更新列表高亮
        await loadProviderListPanel();

        // 显示查看模式
        document.getElementById('providerDetailTitle').textContent = d.data.name;
        document.getElementById('providerViewName').textContent = d.data.name;
        document.getElementById('providerViewUrl').textContent = d.data.base_url;
        document.getElementById('providerViewKey').textContent = 'API Key: ' + d.data.api_key_masked;
        document.getElementById('providerViewMode').style.display = 'block';
        document.getElementById('providerEditMode').style.display = 'none';
        openSettingsDetail('detail-provider');

        await loadProviderModels(id);
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

function switchToProviderEditMode() {
    if (!selectedProviderId) return;
    currentProviderIdEdit = selectedProviderId;
    fetch(`/nex/providers/${selectedProviderId}`).then(r => r.json()).then(d => {
        document.getElementById('providerDetailTitle').textContent = '编辑服务商';
        document.getElementById('providerIdInput').value = selectedProviderId;
        document.getElementById('providerIdInput').disabled = true;
        document.getElementById('providerNameInput').value = d.data.name;
        document.getElementById('providerApiKeyInput').value = '';
        document.getElementById('providerApiKeyInput').placeholder = d.data.api_key_masked + ' (留空不修改)';
        document.getElementById('providerBaseUrlInput').value = d.data.base_url;
        // 设置类型按钮状态
        window.currentProviderType = d.data.provider_type || 'openai';
        document.getElementById('providerTypeOpenAI').classList.toggle('active', window.currentProviderType === 'openai');
        document.getElementById('providerTypeClaude').classList.toggle('active', window.currentProviderType === 'claude');
        document.getElementById('providerViewMode').style.display = 'none';
        document.getElementById('providerEditMode').style.display = 'block';
        document.getElementById('providerDeleteRow').style.display = 'block';
    });
}

function cancelProviderEdit() {
    if (selectedProviderId) {
        selectProvider(selectedProviderId);
    } else {
        closeProviderDetail();
    }
}

function setProviderType(type) {
    window.currentProviderType = type;
    document.getElementById('providerTypeOpenAI').classList.toggle('active', type === 'openai');
    document.getElementById('providerTypeClaude').classList.toggle('active', type === 'claude');
}

async function loadProviderModels(providerId) {
    try {
        const r = await fetch('/nex/models');
        const d = await r.json();
        const models = d.data.models.filter(m => m.provider_id === providerId);
        const list = document.getElementById('providerModelsList');
        if (models.length === 0) {
            list.innerHTML = '<div style="color:var(--text2);font-size:0.85rem;text-align:center;padding:12px;">暂无模型</div>';
            return;
        }

        // 分离对话模型和嵌入模型
        const chatModels = models.filter(m => m.model_type !== 'embedding');
        const embeddingModels = models.filter(m => m.model_type === 'embedding');

        let html = '';

        // 对话模型
        if (chatModels.length > 0) {
            html += '<div class="model-section-title">对话模型</div>';
            html += chatModels.map(m => `
                        <div class="model-item" onclick="selectModelForEdit('${m.key}')">
                            <div class="model-item-name">${escapeHtml(m.name)} ${renderModelTags(m.tags)}</div>
                            <div class="model-item-detail">${escapeHtml(m.model)}</div>
                        </div>
                    `).join('');
        }

        // 嵌入模型
        if (embeddingModels.length > 0) {
            if (chatModels.length > 0) {
                html += '<div class="model-section-divider"></div>';
            }
            html += '<div class="model-section-title">嵌入模型</div>';
            html += embeddingModels.map(m => `
                        <div class="model-item embedding" onclick="selectModelForEdit('${m.key}')">
                            <div class="model-item-name">${escapeHtml(m.name)}</div>
                            <div class="model-item-detail">${escapeHtml(m.model)}</div>
                        </div>
                    `).join('');
        }

        list.innerHTML = html;
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

async function saveProviderInline() {
    const id = document.getElementById('providerIdInput').value.trim();
    const name = document.getElementById('providerNameInput').value.trim();
    const apiKey = document.getElementById('providerApiKeyInput').value.trim();
    const baseUrl = document.getElementById('providerBaseUrlInput').value.trim();
    const providerType = window.currentProviderType || 'openai';

    if (!id || !name || !baseUrl) { showAlert('请填写完整信息', '提示', 'warning'); return; }
    if (!currentProviderIdEdit && !apiKey) { showAlert('请填写 API Key', '提示', 'warning'); return; }

    try {
        if (currentProviderIdEdit) {
            const body = { name, base_url: baseUrl, provider_type: providerType };
            if (apiKey) body.api_key = apiKey;
            await fetch(`/nex/providers/${currentProviderIdEdit}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
        } else {
            await fetch('/nex/providers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, name, api_key: apiKey, base_url: baseUrl, provider_type: providerType }) });
            selectedProviderId = id;
        }
        showToast('保存成功', 'success');
        closeProviderDetail();
        if (selectedProviderId) {
            await selectProvider(selectedProviderId);
        } else {
            await loadProviderListPanel();
        }
    } catch (e) { showToast('保存失败: ' + e.message, 'error'); }
}

async function deleteCurrentProvider() {
    if (!currentProviderIdEdit) {
        showToast('无法删除：未选择服务商', 'error');
        return;
    }
    
    showDeleteConfirm('删除服务商', `确定要删除服务商 "${currentProviderIdEdit}" 吗？该服务商下的所有模型也将被删除。`, async () => {
        try {
            const r = await fetch(`/nex/providers/${currentProviderIdEdit}`, { method: 'DELETE' });
            const d = await r.json();
            if (d.code === 0) {
                showToast('删除成功', 'success');
                closeProviderDetail();
                await loadProviderListPanel();
                await loadModels();
            } else {
                showToast('删除失败: ' + (d.message || '未知错误'), 'error');
            }
        } catch (e) {
            showToast('删除失败: ' + e.message, 'error');
        }
    });
}

function closeProviderDetail() {
    closeSettingsDetail('detail-provider', () => {
        currentProviderIdEdit = null;
        selectedProviderId = null;
        loadProviderListPanel();
    });
}

// 通用关闭详情面板函数（带动画）
function closeSettingsDetail(detailId, callback) {
    const detail = document.getElementById(detailId);
    if (!detail || !detail.classList.contains('active')) {
        if (callback) callback();
        return;
    }
    
    // 移除模态框的 with-detail 类
    const modalContent = document.querySelector('#settingsModal .settings-modal-content');
    if (modalContent) {
        modalContent.classList.remove('with-detail');
    }
    
    detail.classList.add('closing');
    setTimeout(() => {
        detail.classList.remove('active', 'closing');
        if (callback) callback();
    }, 200);
}

// 通用打开详情面板函数（带模态框宽度动画）
function openSettingsDetail(detailId) {
    const detail = document.getElementById(detailId);
    if (detail) {
        detail.classList.add('active');
        
        // 添加模态框宽度变化
        const modalContent = document.querySelector('#settingsModal .settings-modal-content');
        if (modalContent) {
            modalContent.classList.add('with-detail');
        }
    }
}

// ========== 供应商模型列表弹窗 ==========
let allProviderModels = [];  // 存储所有模型数据
let currentModelFilter = 'all';  // 当前筛选类型

async function showProviderModelsModal() {
    if (!selectedProviderId) return;

    const modal = document.getElementById('providerModelsModal');
    const content = document.getElementById('providerModelsListContent');
    const countEl = document.getElementById('providerModelsCount');

    // 获取供应商名称
    try {
        const providerR = await fetch(`/nex/providers/${selectedProviderId}`);
        const providerD = await providerR.json();
        document.getElementById('providerModelsModalTitle').textContent = `${providerD.data.name} 模型列表`;
    } catch {
        document.getElementById('providerModelsModalTitle').textContent = '模型列表';
    }

    // 重置搜索和筛选
    document.getElementById('providerModelsSearch').value = '';
    setModelFilter('all');

    modal.classList.add('show');
    content.innerHTML = '<div style="padding:60px;text-align:center;color:var(--text2);"><span class="typing"><span></span><span></span><span></span></span> 正在加载模型列表...</div>';
    countEl.textContent = '';

    await loadProviderModelsData();
}

async function loadProviderModelsData() {
    const content = document.getElementById('providerModelsListContent');
    const countEl = document.getElementById('providerModelsCount');

    try {
        const r = await fetch(`/nex/providers/${selectedProviderId}/models`);
        const d = await r.json();

        if (d.code !== 0) {
            content.innerHTML = `<div style="padding:60px;text-align:center;color:var(--error);">获取失败: ${escapeHtml(d.detail || '未知错误')}</div>`;
            return;
        }

        allProviderModels = d.data || [];
        renderProviderModelsModal();
    } catch (e) {
        content.innerHTML = `<div style="padding:60px;text-align:center;color:var(--error);">获取失败: ${escapeHtml(e.message)}</div>`;
    }
}

async function refreshProviderModels() {
    const btn = document.getElementById('refreshModelsBtn');
    btn.classList.add('loading');

    const content = document.getElementById('providerModelsListContent');
    content.innerHTML = '<div style="padding:60px;text-align:center;color:var(--text2);"><span class="typing"><span></span><span></span><span></span></span> 正在刷新...</div>';

    await loadProviderModelsData();
    btn.classList.remove('loading');
    showToast('刷新成功', 'success');
}

function setModelFilter(filter) {
    currentModelFilter = filter;
    // 更新标签页状态
    document.querySelectorAll('.modal-filter-tab').forEach(tab => {
        tab.classList.toggle('active', tab.dataset.filter === filter);
    });
    renderProviderModelsModal();
}

function filterProviderModels() {
    renderProviderModelsModal();
}

function renderProviderModelsModal() {
    const content = document.getElementById('providerModelsListContent');
    const countEl = document.getElementById('providerModelsCount');
    const searchText = document.getElementById('providerModelsSearch').value.toLowerCase().trim();

    // 筛选模型
    let filtered = allProviderModels.filter(m => {
        // 标签页筛选
        if (currentModelFilter === 'embedding') {
            if (m.model_type !== 'embedding') return false;
        } else if (currentModelFilter === 'reasoning') {
            if (m.model_type === 'embedding') return false;
            if (!m.capabilities || !m.capabilities.includes('reasoning')) return false;
        } else if (currentModelFilter === 'vision') {
            if (m.model_type === 'embedding') return false;
            if (!m.capabilities || !m.capabilities.includes('vision')) return false;
        } else if (currentModelFilter === 'tool') {
            if (m.model_type === 'embedding') return false;
            if (!m.capabilities || !m.capabilities.includes('tool')) return false;
        }
        // 搜索筛选
        if (searchText && !m.id.toLowerCase().includes(searchText)) return false;
        return true;
    });

    if (filtered.length === 0) {
        content.innerHTML = '<div style="padding:60px;text-align:center;color:var(--text2);">没有找到匹配的模型</div>';
        countEl.textContent = `共 ${allProviderModels.length} 个模型`;
        return;
    }

    // 分组显示（仅在"全部"标签页时分组）
    if (currentModelFilter === 'all') {
        const chatModels = filtered.filter(m => m.model_type !== 'embedding');
        const embeddingModels = filtered.filter(m => m.model_type === 'embedding');

        let html = '';

        if (chatModels.length > 0) {
            html += '<div class="model-section-title" style="padding:10px 14px;background:var(--card);">对话模型 (' + chatModels.length + ')</div>';
            html += chatModels.map(m => renderProviderModelItem(m)).join('');
        }

        if (embeddingModels.length > 0) {
            if (chatModels.length > 0) {
                html += '<div class="model-section-divider" style="margin:0;"></div>';
            }
            html += '<div class="model-section-title" style="padding:10px 14px;background:var(--card);">嵌入模型 (' + embeddingModels.length + ')</div>';
            html += embeddingModels.map(m => renderProviderModelItem(m)).join('');
        }

        content.innerHTML = html;
    } else {
        content.innerHTML = filtered.map(m => renderProviderModelItem(m)).join('');
    }

    countEl.textContent = `显示 ${filtered.length} / ${allProviderModels.length} 个模型`;
}

function renderProviderModelItem(m) {
    const isEmbedding = m.model_type === 'embedding';
    const capsHtml = !isEmbedding && m.capabilities && m.capabilities.length > 0
        ? renderModelTags(m.capabilities)
        : '';

    return `
                <div class="provider-model-item ${isEmbedding ? 'embedding' : ''}" onclick="addModelFromBrowser('${escapeHtml(m.id)}', '${m.model_type || 'chat'}', ${JSON.stringify(m.capabilities || []).replace(/"/g, '&quot;')})">
                    <div class="provider-model-item-info">
                        <div class="provider-model-item-name">
                            ${escapeHtml(m.id)} ${capsHtml}
                        </div>
                        ${m.owned_by ? `<div class="provider-model-item-meta">${escapeHtml(m.owned_by)}</div>` : ''}
                    </div>
                    <div class="provider-model-item-action">
                        <button class="btn-add-small" onclick="event.stopPropagation();addModelFromBrowser('${escapeHtml(m.id)}', '${m.model_type || 'chat'}', ${JSON.stringify(m.capabilities || []).replace(/"/g, '&quot;')})">添加</button>
                    </div>
                </div>
            `;
}

function addModelFromBrowser(modelId, modelType, capabilities) {
    closeProviderModelsModal();
    showAddModelForProvider();

    // 填充表单
    document.getElementById('modelIdInput').value = modelId;
    const displayName = modelId
        .replace(/-/g, ' ')
        .replace(/_/g, ' ')
        .split(' ')
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join(' ');
    document.getElementById('modelDisplayNameInput').value = displayName;
    document.getElementById('modelTypeInput').value = modelType;
    onModelTypeChange();

    if (modelType !== 'embedding' && capabilities && capabilities.length > 0) {
        setModelTags(capabilities);
    } else {
        setModelTags([]);
    }
}

function closeProviderModelsModal() {
    const modal = document.getElementById('providerModelsModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
    allProviderModels = [];
}

// ========== 模型管理（内联） ==========
let editingModelProviderIdInline = null;
let providerModelsCache = null;  // 缓存供应商模型列表

function showAddModelForProvider() {
    if (!selectedProviderId) return;
    editingModelProviderIdInline = selectedProviderId;
    editingModelKey = null;
    document.getElementById('modelDetailTitle').textContent = '添加模型';
    document.getElementById('modelTypeInput').value = 'chat';
    document.getElementById('modelIdInput').value = '';
    document.getElementById('modelIdInput').disabled = false;
    document.getElementById('modelDisplayNameInput').value = '';
    setModelTags([]);
    document.getElementById('modelTagsSection').style.display = 'block';
    document.getElementById('modelDeleteRow').style.display = 'none';

    openSettingsDetail('detail-model');
}

function onModelTypeChange() {
    const modelType = document.getElementById('modelTypeInput').value;
    const tagsSection = document.getElementById('modelTagsSection');
    if (modelType === 'embedding') {
        tagsSection.style.display = 'none';
        // 清除所有标签
        setModelTags([]);
    } else {
        tagsSection.style.display = 'block';
    }
}

function setModelTags(tags) {
    const checkboxes = document.querySelectorAll('#modelTagsGroup .tag-checkbox');
    checkboxes.forEach(cb => {
        const input = cb.querySelector('input');
        const isChecked = tags && tags.includes(input.value);
        input.checked = isChecked;
        cb.classList.toggle('active', isChecked);
    });
}

function getModelTags() {
    const tags = [];
    document.querySelectorAll('#modelTagsGroup .tag-checkbox input:checked').forEach(input => {
        tags.push(input.value);
    });
    return tags;
}

// 标签复选框点击事件
document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('#modelTagsGroup .tag-checkbox').forEach(cb => {
        cb.addEventListener('click', () => {
            const input = cb.querySelector('input');
            input.checked = !input.checked;
            cb.classList.toggle('active', input.checked);
        });
    });
    
    // 恢复保存的思考选项
    const savedOption = localStorage.getItem('thinkingOption');
    if (savedOption) {
        selectThinkingOption(savedOption);
    }
});

async function selectModelForEdit(key) {
    try {
        const r = await fetch(`/nex/models/${key}`);
        const d = await r.json();
        editingModelKey = key;
        editingModelProviderIdInline = d.data.provider_id;
        document.getElementById('modelDetailTitle').textContent = '编辑模型';
        document.getElementById('modelTypeInput').value = d.data.model_type || 'chat';
        document.getElementById('modelIdInput').value = d.data.model_id;
        document.getElementById('modelIdInput').disabled = false;
        document.getElementById('modelDisplayNameInput').value = d.data.display_name;
        setModelTags(d.data.tags || []);
        // 根据模型类型显示/隐藏能力标签
        onModelTypeChange();
        document.getElementById('modelDeleteRow').style.display = 'block';
        openSettingsDetail('detail-model');
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

async function saveModelInline() {
    const modelType = document.getElementById('modelTypeInput').value;
    const modelId = document.getElementById('modelIdInput').value.trim();
    const displayName = document.getElementById('modelDisplayNameInput').value.trim();
    // 嵌入模型不保存标签
    const tags = modelType === 'embedding' ? [] : getModelTags();

    if (!modelId || !displayName) { showAlert('请填写模型ID和显示名称', '提示', 'warning'); return; }

    try {
        if (editingModelKey) {
            const body = { model_id: modelId, display_name: displayName, tags: tags, model_type: modelType };
            const r = await fetch(`/nex/models/${editingModelKey}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
            if (!r.ok) { const d = await r.json(); throw new Error(d.detail); }
            const d = await r.json();
            // 更新 editingModelKey 为新的 key
            if (d.data && d.data.new_key) {
                editingModelKey = d.data.new_key;
            }
        } else {
            const r = await fetch('/nex/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider_id: editingModelProviderIdInline, model_id: modelId, display_name: displayName, tags: tags, model_type: modelType }) });
            if (!r.ok) { const d = await r.json(); throw new Error(d.detail); }
        }
        showToast('保存成功', 'success');
        closeModelDetail();
        if (selectedProviderId) {
            await selectProvider(selectedProviderId);
        }
        await loadModels();
    } catch (e) { showToast('保存失败: ' + e.message, 'error'); }
}

function deleteModelInline() {
    if (!editingModelKey) return;
    showDeleteConfirm('删除模型', '确定要删除此模型吗？', async () => {
        try {
            const r = await fetch(`/nex/models/${editingModelKey}`, { method: 'DELETE' });
            if (!r.ok) { const d = await r.json(); throw new Error(d.detail); }
            closeModelDetail();
            if (selectedProviderId) {
                await selectProvider(selectedProviderId);
            }
            await loadModels();
        } catch (e) { showToast('删除失败: ' + e.message, 'error'); }
    });
}

function closeModelDetail() {
    closeSettingsDetail('detail-model', () => {
        editingModelKey = null;
        editingModelProviderIdInline = null;
    });
}

// ========== 设置面板 ==========
function openSettings() {
    document.getElementById('settingsModal').classList.add('show');
    switchSettingsNav('about');
}

function closeSettings() {
    const modal = document.getElementById('settingsModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 250);
    closeProviderDetail();
    closeMCPDetail();
    closeModelDetail();
    closePersonaDetail();
}

function switchSettingsNav(name) {
    document.querySelectorAll('.settings-nav-item').forEach(t => t.classList.remove('active'));
    document.querySelectorAll('.settings-panel').forEach(t => t.classList.remove('active'));
    document.querySelector(`.settings-nav-item[onclick="switchSettingsNav('${name}')"]`).classList.add('active');
    document.getElementById('panel-' + name).classList.add('active');
    document.querySelectorAll('.settings-detail').forEach(d => d.classList.remove('active'));
    
    // 切换模态框宽度类
    const modalContent = document.querySelector('#settingsModal .settings-modal-content');
    if (modalContent) {
        // 移除所有面板类
        modalContent.className = modalContent.className.replace(/panel-\w+/g, '').trim();
        // 添加当前面板类
        modalContent.classList.add('panel-' + name);
        // 移除详情面板宽度
        modalContent.classList.remove('with-detail');
    }
    
    if (name === 'providers') loadProviderListPanel();
    if (name === 'tools') loadToolsListPanel();
    if (name === 'mcp') loadMCPListPanel();
    if (name === 'personas') loadPersonaListPanel();
    if (name === 'style') updateStylePanelUI();
    if (name === 'memory') loadMemoryPanel();
    if (name === 'openapi') loadOpenAPIConfigsPanel();
    if (name === 'about') loadAboutPanel();
    if (name === 'stt') loadSTTPanel();
    if (name === 'tts') loadTTSPanel();
    if (name === 'plugins') loadPluginsPanel();
}

// 加载关于面板
async function loadAboutPanel() {
    try {
        const r = await fetch('/nex/version');
        const d = await r.json();
        document.getElementById('appVersion').textContent = d.data?.version || '-';
    } catch (e) { }
}

// ========== 记忆管理 ==========
let editingMemoryId = null;
let currentEmbedModelId = null;

async function loadMemoryPanel() {
    // 加载记忆开关状态
    try {
        const r = await fetch('/nex/settings/memory_enabled');
        const d = await r.json();
        document.getElementById('memoryEnabledToggle').checked = d.data.value === 'true';
    } catch (e) { }

    // 加载嵌入模型列表
    await loadEmbedModelDropdown();

    // 加载记忆列表
    await loadMemoryList();
}

async function loadEmbedModelDropdown() {
    try {
        const r = await fetch('/nex/embedding/status');
        const d = await r.json();
        const models = d.data.models || [];
        currentEmbedModelId = d.data.model_id;

        const nameEl = document.getElementById('embedModelName');
        const menuEl = document.getElementById('embedModelMenu');

        if (models.length === 0) {
            nameEl.textContent = '未配置';
            menuEl.innerHTML = '<div class="toolbar-empty">请先在服务商中添加嵌入模型</div>';
        } else {
            const currentModel = models.find(m => m.id === currentEmbedModelId);
            nameEl.textContent = currentModel ? currentModel.name : models[0].name;

            let html = '<div class="toolbar-dropdown-header">选择嵌入模型</div>';
            html += models.map(m => `
                        <div class="toolbar-dropdown-item ${m.id === currentEmbedModelId ? 'active' : ''}" onclick="selectEmbedModel('${escapeHtml(m.id)}', '${escapeHtml(m.name)}')">
                            <div class="toolbar-dropdown-item-icon">
                                <i class="ri-cpu-line"></i>
                            </div>
                            <div class="toolbar-dropdown-item-info">
                                <div class="toolbar-dropdown-item-name">${escapeHtml(m.name)}</div>
                            </div>
                        </div>
                    `).join('');
            menuEl.innerHTML = html;
        }
    } catch (e) {
        document.getElementById('embedModelName').textContent = '加载失败';
    }
}

function toggleEmbedModelDropdown(event) {
    if (event) event.stopPropagation();
    const dropdown = document.getElementById('embedModelDropdown');
    const menu = document.getElementById('embedModelMenu');
    const btn = dropdown.querySelector('.toolbar-btn');
    
    const isOpen = dropdown.classList.contains('open');
    
    // 先关闭其他下拉菜单
    document.querySelectorAll('.toolbar-dropdown.open').forEach(d => {
        if (d !== dropdown) d.classList.remove('open');
    });
    
    if (isOpen) {
        dropdown.classList.remove('open');
        // 把菜单移回原位
        dropdown.appendChild(menu);
    } else {
        // 把菜单移到body下，避免被overflow:hidden裁剪
        document.body.appendChild(menu);
        
        // 计算按钮位置，使用fixed定位
        const rect = btn.getBoundingClientRect();
        menu.style.position = 'fixed';
        menu.style.top = (rect.bottom + 6) + 'px';
        menu.style.left = rect.left + 'px';
        menu.style.bottom = 'auto';
        menu.style.zIndex = '1300';
        menu.style.display = 'block';
        menu.style.opacity = '1';
        menu.style.transform = 'translateY(0)';
        dropdown.classList.add('open');
    }
}

async function selectEmbedModel(modelId, modelName) {
    document.getElementById('embedModelName').textContent = modelName;
    const embedDropdown = document.getElementById('embedModelDropdown');
    const embedMenu = document.getElementById('embedModelMenu');
    embedDropdown.classList.remove('open');
    // 恢复菜单的相对定位
    embedMenu.style.position = '';
    embedMenu.style.top = '';
    embedMenu.style.left = '';
    embedMenu.style.bottom = '';
    currentEmbedModelId = modelId;
    await setEmbeddingModel(modelId);
}

async function setEmbeddingModel(modelKey) {
    try {
        await fetch(`/nex/embedding/model?model_key=${encodeURIComponent(modelKey)}`, { method: 'PUT' });
    } catch (e) {
        showToast('设置失败', 'error');
    }
}

async function loadMemoryList() {
    const panel = document.getElementById('memoryListPanel');
    try {
        const user = getUser();
        const r = await fetch(`/nex/memories?user=${encodeURIComponent(user)}&limit=50`);
        const d = await r.json();
        if (d.data.length === 0) {
            panel.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center;font-size:0.9rem;">暂无记忆<br><span style="font-size:0.8rem;">AI 会自动记住重要信息，或手动添加</span></div>';
            return;
        }
        panel.innerHTML = d.data.map(m => `
                    <div class="provider-item" onclick="editMemory(${m.id})" style="margin-bottom:8px;">
                        <div class="provider-item-name" style="display:flex;justify-content:space-between;align-items:center;">
                            <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(m.content.substring(0, 40))}${m.content.length > 40 ? '...' : ''}</span>
                            <span style="font-size:0.7rem;color:var(--accent);margin-left:8px;">重要度: ${m.importance}</span>
                        </div>
                        <div class="provider-item-detail">${escapeHtml(m.created_at.substring(0, 10))}</div>
                    </div>
                `).join('');
    } catch (e) {
        panel.innerHTML = '<div style="padding:20px;color:var(--error);text-align:center;">加载失败</div>';
    }
}

async function toggleMemoryEnabled(enabled) {
    try {
        await fetch('/nex/settings', {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ settings: { memory_enabled: enabled ? 'true' : 'false' } })
        });
    } catch (e) {
        showToast('设置失败', 'error');
    }
}

function showAddMemory() {
    editingMemoryId = null;
    
    // 创建自定义的添加记忆对话框
    const currentUser = getUser();
    const modalHtml = `
        <div class="alert-modal show" id="addMemoryModal" style="z-index:2000;">
            <div class="alert-content" style="min-width:400px;">
                <div class="alert-header">
                    <i class="ri-brain-line alert-icon info"></i>
                    <h4>添加记忆</h4>
                </div>
                <div class="alert-body" style="text-align:left;">
                    <div style="margin-bottom:12px;">
                        <label style="display:block;font-size:0.85rem;color:var(--text2);margin-bottom:6px;">用户名</label>
                        <input type="text" id="memoryUserInput" value="${currentUser}" placeholder="输入用户名"
                            style="width:100%;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:0.9rem;">
                    </div>
                    <div style="margin-bottom:12px;">
                        <label style="display:block;font-size:0.85rem;color:var(--text2);margin-bottom:6px;">记忆内容</label>
                        <textarea id="memoryContentInput" placeholder="输入要记住的内容" rows="4"
                            style="width:100%;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:0.9rem;resize:vertical;"></textarea>
                    </div>
                    <div>
                        <label style="display:block;font-size:0.85rem;color:var(--text2);margin-bottom:6px;">重要度 (1-10)</label>
                        <input type="number" id="memoryImportanceInput" value="5" min="1" max="10"
                            style="width:100%;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:0.9rem;">
                    </div>
                </div>
                <div class="alert-actions">
                    <button class="alert-btn-cancel" onclick="closeAddMemoryModal()">取消</button>
                    <button class="alert-btn-ok" onclick="submitAddMemory()">添加</button>
                </div>
            </div>
        </div>
    `;
    
    document.body.insertAdjacentHTML('beforeend', modalHtml);
    document.getElementById('memoryContentInput').focus();
}

function closeAddMemoryModal() {
    const modal = document.getElementById('addMemoryModal');
    if (modal) modal.remove();
}

async function submitAddMemory() {
    const user = document.getElementById('memoryUserInput').value.trim();
    const content = document.getElementById('memoryContentInput').value.trim();
    const importance = parseInt(document.getElementById('memoryImportanceInput').value) || 5;
    
    if (!user) {
        showToast('用户名不能为空', 'warning');
        return;
    }
    
    if (!content) {
        showToast('记忆内容不能为空', 'warning');
        return;
    }
    
    try {
        const r = await fetch(`/nex/memories?user=${encodeURIComponent(user)}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content, importance })
        });
        if (!r.ok) {
            const d = await r.json();
            throw new Error(d.detail);
        }
        closeAddMemoryModal();
        await loadMemoryList();
        showToast('记忆已添加', 'success');
    } catch (e) {
        showToast('添加失败: ' + e.message, 'error');
    }
}

function editMemory(id) {
    editingMemoryId = id;
    fetch(`/nex/memories/${id}`).then(r => r.json()).then(d => {
        const m = d.data;
        document.getElementById('memoryDetailContent').textContent = m.content;
        document.getElementById('memoryDetailMeta').textContent = `用户: ${m.user} | 重要度: ${m.importance} | 创建时间: ${m.created_at.substring(0, 19).replace('T', ' ')}`;
        document.getElementById('memoryDetailModal').classList.add('show');
    }).catch(e => showToast('加载失败', 'error'));
}

function closeMemoryDetail() {
    const modal = document.getElementById('memoryDetailModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
}

async function deleteCurrentMemory() {
    if (!editingMemoryId) return;
    try {
        await fetch(`/nex/memories/${editingMemoryId}`, { method: 'DELETE' });
        closeMemoryDetail();
        await loadMemoryList();
    } catch (e) {
        showToast('删除失败', 'error');
    }
}

// 简单的输入对话框
function showPrompt(title, message, defaultValue, callback) {
    const html = `
                <div style="margin-bottom:16px;">${escapeHtml(message)}</div>
                <textarea id="promptInput" style="width:100%;height:80px;padding:10px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:0.9rem;resize:none;">${escapeHtml(defaultValue)}</textarea>
            `;
    document.getElementById('alertTitle').textContent = title;
    document.getElementById('alertBody').innerHTML = html;
    document.getElementById('alertModal').querySelector('.alert-actions').innerHTML = `
                <button class="alert-btn-ok" style="background:var(--card);color:var(--text);" onclick="closeAlert()">取消</button>
                <button class="alert-btn-ok" onclick="const v=document.getElementById('promptInput').value;closeAlert();(${callback.toString()})(v)">确定</button>
            `;
    document.getElementById('alertModal').classList.add('show');
    setTimeout(() => document.getElementById('promptInput')?.focus(), 100);
}

// ========== 角色卡管理 ==========
let editingPersonaId = null;

async function loadPersonaListPanel() {
    try {
        const r = await fetch('/nex/personas');
        const d = await r.json();
        const panel = document.getElementById('personaListPanel');
        if (d.data.length === 0) {
            panel.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center">暂无角色卡<br><span style="font-size:0.85rem">点击上方按钮添加</span></div>';
            return;
        }
        panel.innerHTML = d.data.map(p => `
                    <div class="provider-item ${editingPersonaId === p.id ? 'active' : ''}" onclick="editPersona(${p.id})">
                        <div class="provider-item-name">${escapeHtml(p.name)}</div>
                        <div class="provider-item-detail" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px;">${escapeHtml(p.system_prompt.substring(0, 50))}${p.system_prompt.length > 50 ? '...' : ''}</div>
                    </div>
                `).join('');
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

function showAddPersona() {
    editingPersonaId = null;
    document.getElementById('personaDetailTitle').textContent = '添加角色卡';
    document.getElementById('personaNameInput').value = '';
    document.getElementById('personaPromptInput').value = '';
    document.getElementById('personaMaxTokensInput').value = '';
    document.getElementById('personaTemperatureInput').value = '';
    document.getElementById('personaTopPInput').value = '';
    document.getElementById('personaAvatarData').value = '';
    document.getElementById('personaDeleteRow').style.display = 'none';
    updatePersonaAvatarPreview('', 'P');
    openSettingsDetail('detail-persona');
}

async function editPersona(id) {
    editingPersonaId = id;
    try {
        const r = await fetch(`/nex/personas/${id}`);
        const d = await r.json();
        document.getElementById('personaDetailTitle').textContent = '编辑角色卡';
        document.getElementById('personaNameInput').value = d.data.name;
        document.getElementById('personaPromptInput').value = d.data.system_prompt;
        document.getElementById('personaMaxTokensInput').value = d.data.max_tokens || '';
        document.getElementById('personaTemperatureInput').value = d.data.temperature !== null ? d.data.temperature : '';
        document.getElementById('personaTopPInput').value = d.data.top_p !== null ? d.data.top_p : '';
        document.getElementById('personaAvatarData').value = d.data.avatar || '';
        document.getElementById('personaDeleteRow').style.display = 'block';
        updatePersonaAvatarPreview(d.data.avatar, d.data.name.charAt(0).toUpperCase(), getPersonaColor(id));
        openSettingsDetail('detail-persona');
        await loadPersonaListPanel();
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

function updatePersonaAvatarPreview(avatar, letter, bgColor) {
    const preview = document.getElementById('personaAvatarPreview');
    const letterEl = document.getElementById('personaAvatarLetter');
    if (avatar) {
        preview.innerHTML = `<img src="${avatar}" style="width:100%;height:100%;object-fit:cover;">`;
    } else {
        preview.style.background = bgColor || 'var(--accent)';
        preview.innerHTML = `<span style="color:white;font-weight:600;font-size:1rem;">${letter || 'P'}</span>`;
    }
}

function previewPersonaAvatar(input) {
    const file = input.files[0];
    if (!file) return;
    
    if (file.size > 500 * 1024) {
        showToast('图片大小不能超过500KB', 'error');
        input.value = '';
        return;
    }
    
    const reader = new FileReader();
    reader.onload = function(e) {
        document.getElementById('personaAvatarData').value = e.target.result;
        updatePersonaAvatarPreview(e.target.result);
    };
    reader.readAsDataURL(file);
}

function clearPersonaAvatar() {
    document.getElementById('personaAvatarData').value = '';
    document.getElementById('personaAvatarUpload').value = '';
    const name = document.getElementById('personaNameInput').value || 'P';
    const bgColor = editingPersonaId ? getPersonaColor(editingPersonaId) : 'var(--accent)';
    updatePersonaAvatarPreview('', name.charAt(0).toUpperCase(), bgColor);
}

async function savePersonaInline() {
    const name = document.getElementById('personaNameInput').value.trim();
    const systemPrompt = document.getElementById('personaPromptInput').value.trim();
    if (!name || !systemPrompt) { showAlert('请填写完整信息', '提示', 'warning'); return; }

    const maxTokensVal = document.getElementById('personaMaxTokensInput').value.trim();
    const temperatureVal = document.getElementById('personaTemperatureInput').value.trim();
    const topPVal = document.getElementById('personaTopPInput').value.trim();
    const avatarVal = document.getElementById('personaAvatarData').value;

    const body = { 
        name, 
        system_prompt: systemPrompt, 
        avatar: avatarVal || null,
        max_tokens: maxTokensVal ? parseInt(maxTokensVal) : null,
        temperature: temperatureVal ? parseFloat(temperatureVal) : null,
        top_p: topPVal ? parseFloat(topPVal) : null
    };

    try {
        if (editingPersonaId) {
            await fetch(`/nex/personas/${editingPersonaId}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(body)
            });
        } else {
            await fetch('/nex/personas', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(body)
            });
        }
        showToast('保存成功', 'success');
        closePersonaDetail();
        await loadPersonaListPanel();
        // 刷新侧边栏和消息列表以显示新头像
        await loadPersonaSessions(currentPersonaId);
        if (currentSessionId) {
            await updatePersonaDisplay();
            await loadMessages(currentSessionId);
        }
    } catch (e) { showToast('保存失败: ' + e.message, 'error'); }
}

function deletePersonaInline() {
    if (!editingPersonaId) return;
    showDeleteConfirm('删除角色卡', '确定要删除此角色卡吗？使用该角色卡的会话将恢复默认提示词。', async () => {
        try {
            await fetch(`/nex/personas/${editingPersonaId}`, { method: 'DELETE' });
            showToast('删除成功', 'success');
            closePersonaDetail();
            await loadPersonaListPanel();
        } catch (e) { showToast('删除失败: ' + e.message, 'error'); }
    });
}

function closePersonaDetail() {
    closeSettingsDetail('detail-persona', () => {
        editingPersonaId = null;
    });
}

// ========== 工具管理 ==========
async function loadToolsListPanel() {
    const panel = document.getElementById('toolsListPanel');
    try {
        const r = await fetch('/nex/tools');
        const d = await r.json();
        const tools = d.data || [];
        const globalEnabled = d.enabled !== false;
        
        if (tools.length === 0) {
            panel.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center">暂无工具</div>';
            return;
        }
        
        const builtinTools = tools.filter(t => t.type === 'builtin');
        const customTools = tools.filter(t => t.type === 'custom');
        const mcpTools = tools.filter(t => t.type === 'mcp');
        const pluginTools = tools.filter(t => t.type === 'plugin');
        
        let html = `
            <div class="memory-toggle-box" style="margin-bottom:16px;">
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <div>
                        <div style="font-weight:500;">启用工具调用</div>
                        <div style="font-size:0.8rem;color:var(--text2);margin-top:4px;">关闭后所有工具将不可用（包括MCP和插件）</div>
                    </div>
                    <label class="toggle-switch">
                        <input type="checkbox" ${globalEnabled ? 'checked' : ''} onchange="toggleToolsEnabledFromSettings(this.checked)">
                        <span class="toggle-slider"></span>
                    </label>
                </div>
            </div>
        `;
        
        // 内置工具
        if (builtinTools.length > 0) {
            const allBuiltinEnabled = builtinTools.every(t => t.enabled);
            html += `
                <div class="settings-panel-header" style="padding:8px 0;border-bottom:1px solid var(--border);">
                    <span style="font-size:0.85rem;color:var(--text2);">内置工具 (${builtinTools.length})</span>
                    <button class="btn-add-small" onclick="toggleAllToolsType('builtin', ${!allBuiltinEnabled})">${allBuiltinEnabled ? '全部关闭' : '全部开启'}</button>
                </div>
            `;
            html += builtinTools.map(t => renderToolItem(t, true)).join('');
        }
        
        // 自定义工具
        if (customTools.length > 0) {
            const allCustomEnabled = customTools.every(t => t.enabled);
            html += `
                <div class="settings-panel-header" style="padding:8px 0;border-bottom:1px solid var(--border);margin-top:12px;">
                    <span style="font-size:0.85rem;color:var(--text2);">自定义工具 (${customTools.length})</span>
                    <button class="btn-add-small" onclick="toggleAllToolsType('custom', ${!allCustomEnabled})">${allCustomEnabled ? '全部关闭' : '全部开启'}</button>
                </div>
            `;
            html += customTools.map(t => renderToolItem(t, true)).join('');
        }
        
        // 插件工具 - 按插件分组
        if (pluginTools.length > 0) {
            // 按插件分组
            const pluginGroups = {};
            pluginTools.forEach(t => {
                // 从工具名提取插件名（假设格式为 plugin_xxx_toolname）
                const parts = t.name.split('_');
                const pluginName = parts.length > 2 ? parts[1] : 'unknown';
                if (!pluginGroups[pluginName]) {
                    pluginGroups[pluginName] = [];
                }
                pluginGroups[pluginName].push(t);
            });
            
            Object.keys(pluginGroups).forEach(pluginName => {
                const tools = pluginGroups[pluginName];
                html += `
                    <div style="margin-top:20px;padding-top:16px;border-top:2px solid var(--border);">
                        <div class="settings-panel-header" style="padding:8px 0;border-bottom:1px solid var(--border);">
                            <span style="font-size:0.85rem;color:var(--text2);">插件: ${escapeHtml(pluginName)} (${tools.length})</span>
                            <div style="font-size:0.75rem;color:var(--text3);margin-top:2px;">在插件页面管理开关</div>
                        </div>
                        ${tools.map(t => renderToolItem(t, false)).join('')}
                    </div>
                `;
            });
        }
        
        // MCP 工具 - 按服务器分组
        if (mcpTools.length > 0) {
            // 按 MCP 服务器分组
            const mcpGroups = {};
            mcpTools.forEach(t => {
                // 从工具名提取服务器名（假设格式为 mcp_serverid_toolname）
                const parts = t.name.split('_');
                const serverName = parts.length > 2 ? parts[1] : 'unknown';
                if (!mcpGroups[serverName]) {
                    mcpGroups[serverName] = [];
                }
                mcpGroups[serverName].push(t);
            });
            
            Object.keys(mcpGroups).forEach(serverName => {
                const tools = mcpGroups[serverName];
                html += `
                    <div style="margin-top:20px;padding-top:16px;border-top:2px solid var(--border);">
                        <div class="settings-panel-header" style="padding:8px 0;border-bottom:1px solid var(--border);">
                            <span style="font-size:0.85rem;color:var(--text2);">MCP: ${escapeHtml(serverName)} (${tools.length})</span>
                            <div style="font-size:0.75rem;color:var(--text3);margin-top:2px;">在MCP页面管理开关</div>
                        </div>
                        ${tools.map(t => renderToolItem(t, false)).join('')}
                    </div>
                `;
            });
        }
        
        panel.innerHTML = html;
    } catch (e) {
        panel.innerHTML = '<div style="padding:20px;color:var(--error);text-align:center">加载失败</div>';
    }
}

function renderToolItem(tool, showToggle) {
    return `
        <div class="provider-item" onclick="showToolDetail('${escapeHtml(tool.name)}', '${escapeHtml(tool.type)}')" style="cursor:pointer;">
            <div style="display:flex;justify-content:space-between;align-items:center;width:100%;">
                <div style="flex:1;min-width:0;">
                    <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">
                        <div class="provider-item-name">${escapeHtml(tool.name)}</div>
                    </div>
                    <div class="provider-item-detail">${escapeHtml((tool.description || '无描述').substring(0, 60))}${(tool.description || '').length > 60 ? '...' : ''}</div>
                </div>
                ${showToggle ? `
                    <label class="toggle-switch" style="margin-left:12px;" onclick="event.stopPropagation();">
                        <input type="checkbox" ${tool.enabled ? 'checked' : ''} onchange="toggleSingleTool('${escapeHtml(tool.name)}', this.checked)">
                        <span class="toggle-slider"></span>
                    </label>
                ` : `
                    <i class="ri-arrow-right-s-line" style="font-size:1.2rem;color:var(--text2);margin-left:12px;"></i>
                `}
            </div>
        </div>
    `;
}

async function showToolDetail(toolName, toolType) {
    try {
        const r = await fetch('/nex/tools');
        const d = await r.json();
        const tool = (d.data || []).find(t => t.name === toolName);
        
        if (!tool) {
            showToast('工具不存在', 'error');
            return;
        }
        
        // 提取来源信息（插件名或MCP服务器名）
        let sourceInfo = '';
        if (toolType === 'plugin' || toolType === 'mcp') {
            const parts = tool.name.split('_');
            if (parts.length > 2) {
                const sourceName = parts[1];
                sourceInfo = toolType === 'plugin' ? ` - ${sourceName}` : ` - ${sourceName}`;
            }
        }
        
        // 构建参数列表HTML
        let paramsHtml = '';
        if (tool.parameters && tool.parameters.properties) {
            const props = tool.parameters.properties;
            const required = tool.parameters.required || [];
            
            paramsHtml = Object.keys(props).map(key => {
                const param = props[key];
                const isRequired = required.includes(key);
                return `
                    <div style="padding:12px;background:var(--bg);border-radius:var(--radius-sm);margin-bottom:8px;">
                        <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
                            <code style="background:var(--card);padding:2px 6px;border-radius:4px;font-size:0.85rem;">${escapeHtml(key)}</code>
                            ${isRequired ? '<span style="color:var(--error);font-size:0.75rem;">*必填</span>' : '<span style="color:var(--text3);font-size:0.75rem;">可选</span>'}
                            ${param.type ? `<span style="color:var(--text3);font-size:0.75rem;">${param.type}</span>` : ''}
                        </div>
                        <div style="font-size:0.85rem;color:var(--text2);">${escapeHtml(param.description || '无描述')}</div>
                        ${param.enum ? `<div style="font-size:0.75rem;color:var(--text3);margin-top:4px;">可选值: ${param.enum.join(', ')}</div>` : ''}
                    </div>
                `;
            }).join('');
        } else {
            paramsHtml = '<div style="padding:12px;text-align:center;color:var(--text2);font-size:0.85rem;">无参数</div>';
        }
        
        // 类型显示文本
        const typeDisplayMap = {
            'builtin': '内置',
            'custom': '自定义',
            'plugin': '插件',
            'mcp': 'MCP'
        };
        const typeDisplay = typeDisplayMap[toolType] || toolType;
        
        // 只有内置和自定义工具显示启用状态
        const showEnabledStatus = toolType === 'builtin' || toolType === 'custom';
        
        const modalHtml = `
            <div class="alert-modal show" id="toolDetailModal" style="z-index:2000;">
                <div class="alert-content" style="min-width:500px;max-width:600px;">
                    <div class="alert-header">
                        <i class="ri-tools-line alert-icon info"></i>
                        <h4>工具详情</h4>
                    </div>
                    <div class="alert-body" style="text-align:left;max-height:500px;overflow-y:auto;">
                        <div style="margin-bottom:16px;">
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:6px;">工具名称</div>
                            <div style="font-size:1.1rem;font-weight:500;">${escapeHtml(tool.name)}</div>
                        </div>
                        
                        <div style="margin-bottom:16px;">
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:6px;">类型</div>
                            <div style="display:flex;gap:8px;align-items:center;">
                                <span style="padding:4px 12px;background:var(--accent-light);color:var(--accent);border-radius:12px;font-size:0.85rem;">${escapeHtml(typeDisplay)}${escapeHtml(sourceInfo)}</span>
                                ${showEnabledStatus ? (tool.enabled 
                                    ? '<span style="padding:4px 12px;background:var(--success-light);color:var(--success);border-radius:12px;font-size:0.85rem;">已启用</span>'
                                    : '<span style="padding:4px 12px;background:var(--text3);color:var(--text2);border-radius:12px;font-size:0.85rem;">已禁用</span>') : ''}
                            </div>
                        </div>
                        
                        <div style="margin-bottom:16px;">
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:6px;">描述</div>
                            <div style="padding:12px;background:var(--bg);border-radius:var(--radius-sm);font-size:0.9rem;line-height:1.6;">
                                ${escapeHtml(tool.description || '无描述')}
                            </div>
                        </div>
                        
                        <div>
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:8px;">参数列表</div>
                            ${paramsHtml}
                        </div>
                    </div>
                    <div class="alert-actions">
                        <button class="alert-btn-ok" onclick="closeToolDetail()">关闭</button>
                    </div>
                </div>
            </div>
        `;
        
        document.body.insertAdjacentHTML('beforeend', modalHtml);
    } catch (e) {
        showToast('加载工具详情失败', 'error');
    }
}

function closeToolDetail() {
    const modal = document.getElementById('toolDetailModal');
    if (modal) modal.remove();
}

async function toggleSingleTool(name, enabled) {
    try {
        await fetch(`/nex/tools/${encodeURIComponent(name)}/toggle?enabled=${enabled}`, { method: 'PUT' });
        await loadToolbarToolsCount();
        // 重新加载工具列表以更新"全部开启/关闭"按钮状态
        await loadToolsListPanel();
    } catch (e) {
        showToast('切换失败', 'error');
    }
}

async function toggleAllToolsType(type, enabled) {
    try {
        await fetch(`/nex/tools/toggle-all?enabled=${enabled}&tool_type=${type}`, { method: 'PUT' });
        await loadToolsListPanel();
        await loadToolbarToolsCount();
    } catch (e) {
        showToast('切换失败', 'error');
    }
}

async function reloadToolsFromSettings() {
    try {
        const r = await fetch('/nex/tools/reload', { method: 'POST' });
        const d = await r.json();
        if (d.data.errors && d.data.errors.length > 0) {
            showToast(`加载完成，${d.data.errors.length} 个工具出错`, 'warning');
        } else {
            showToast(`已加载 ${d.data.loaded.length} 个自定义工具`, 'success');
        }
        await loadToolsListPanel();
        await loadToolbarToolsCount();
    } catch (e) {
        showToast('刷新失败', 'error');
    }
}

async function toggleToolsEnabledFromSettings(enabled) {
    await toggleToolsEnabled(enabled);
    await loadToolsListPanel();
    updateMCPPanelTitle();
}

function updateMCPPanelTitle() {
    const title = document.querySelector('#panel-mcp .settings-panel-header h4');
    if (title) {
        title.textContent = toolsEnabled ? 'MCP 服务器' : 'MCP 服务器（工具调用已被关闭）';
    }
}

// ========== MCP 服务器管理 ==========
async function loadMCPListPanel() {
    updateMCPPanelTitle();
    try {
        const r = await fetch('/nex/mcp/servers');
        const d = await r.json();
        const panel = document.getElementById('mcpListPanel');
        if (d.data.length === 0) {
            panel.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center">暂无 MCP 服务器<br><span style="font-size:0.85rem">点击上方按钮添加</span></div>';
            return;
        }
        panel.innerHTML = d.data.map(function (s) {
            var statusClass = s.connected ? 'connected' : 'disconnected';
            var statusText = s.connected ? '已连接' : '未连接';
            var isEnabled = s.enabled !== false;
            return `<div class="mcp-item ${currentMCPIdEdit === s.id ? 'active' : ''}" onclick="selectMCP('${s.id}')">
                        <label class="toggle-switch" onclick="event.stopPropagation()">
                            <input type="checkbox" ${isEnabled ? 'checked' : ''} onchange="toggleMCPEnabled('${s.id}', this.checked)">
                            <span class="toggle-slider"></span>
                        </label>
                        <div class="mcp-item-content">
                            <div class="mcp-item-name"><span class="status-dot ${statusClass}"></span>${escapeHtml(s.name)}</div>
                            <div class="mcp-item-detail">${statusText} · ${s.tool_count || 0} 个工具</div>
                        </div>
                        <button class="btn-icon delete" onclick="event.stopPropagation();deleteMCPServer('${s.id}')" title="删除" style="margin-left:8px;">${iconDelete}</button>
                    </div>`;
        }).join('');
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

async function deleteMCPServer(id) {
    showDeleteConfirm('删除MCP服务器', '确定要删除这个 MCP 服务器吗？', async () => {
        try {
            const r = await fetch(`/nex/mcp/servers/${id}`, { method: 'DELETE' });
            if (!r.ok) {
                const d = await r.json();
                throw new Error(d.message || '删除失败');
            }
            showToast('删除成功', 'success');
            await loadMCPListPanel();
            await loadToolbarToolsCount();
            // 如果当前正在编辑这个服务器，关闭详情面板
            if (currentMCPIdEdit === id) {
                currentMCPIdEdit = null;
                closeSettingsDetail('detail-mcp');
            }
        } catch (e) {
            showToast('删除失败: ' + e.message, 'error');
        }
    });
}

// 从详情页面删除当前MCP服务器
async function deleteCurrentMCP() {
    if (!currentMCPIdEdit) {
        showToast('无法删除：未选择MCP服务器', 'error');
        return;
    }
    await deleteMCPServer(currentMCPIdEdit);
}

async function toggleMCPEnabled(id, enabled) {
    try {
        showToast(enabled ? '正在连接...' : '正在断开...', 'info', 2000);
        const r = await fetch('/nex/mcp/servers/' + id, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ enabled })
        });
        if (!r.ok) {
            showToast('操作失败', 'error');
            await loadMCPListPanel();
            return;
        }
        // 等待一下让后台连接完成
        await new Promise(resolve => setTimeout(resolve, 1000));
        await loadMCPListPanel();
        await loadToolbarToolsCount(); // 刷新工具数量
        // 检查连接状态
        const statusR = await fetch('/nex/mcp/servers');
        const statusD = await statusR.json();
        const server = statusD.data.find(s => s.id === id);
        if (enabled) {
            if (server && server.connected) {
                showToast('连接成功', 'success');
            } else {
                showToast('连接中，请稍后刷新查看状态', 'warning');
            }
        } else {
            showToast('已禁用', 'info');
        }
    } catch (e) { showToast('操作失败: ' + e.message, 'error'); await loadMCPListPanel(); }
}

function showAddMCPInline() {
    currentMCPIdEdit = null;
    document.getElementById('mcpDetailTitle').textContent = '添加 MCP 服务器';
    document.getElementById('mcpIdInput').value = '';
    document.getElementById('mcpIdInput').disabled = false;
    document.getElementById('mcpNameInput').value = '';
    document.getElementById('mcpTypeInput').value = 'sse';
    document.getElementById('mcpUrlInput').value = '';
    document.getElementById('mcpHeadersInput').value = '';
    document.getElementById('mcpDeleteRow').style.display = 'none';
    openSettingsDetail('detail-mcp');
}

async function selectMCP(id) {
    currentMCPIdEdit = id;
    try {
        const r = await fetch('/nex/mcp/servers');
        const d = await r.json();
        const server = d.data.find(s => s.id === id);
        if (!server) { showAlert('服务器不存在', '错误', 'error'); return; }
        document.getElementById('mcpDetailTitle').textContent = '编辑 MCP 服务器';
        document.getElementById('mcpIdInput').value = id;
        document.getElementById('mcpIdInput').disabled = true;
        document.getElementById('mcpNameInput').value = server.name;
        document.getElementById('mcpTypeInput').value = server.server_type || 'sse';
        document.getElementById('mcpUrlInput').value = server.url;
        document.getElementById('mcpHeadersInput').value = server.headers ? JSON.stringify(server.headers, null, 2) : '';
        document.getElementById('mcpDeleteRow').style.display = 'block';
        openSettingsDetail('detail-mcp');
        await loadMCPListPanel();
    } catch (e) { showToast('加载失败: ' + e.message, 'error'); }
}

async function saveMCPInline() {
    const id = document.getElementById('mcpIdInput').value.trim();
    const name = document.getElementById('mcpNameInput').value.trim();
    const serverType = document.getElementById('mcpTypeInput').value;
    const url = document.getElementById('mcpUrlInput').value.trim();
    const headersText = document.getElementById('mcpHeadersInput').value.trim();

    if (!id || !name || !url) { showAlert('请填写完整信息', '提示', 'warning'); return; }

    let headers = null;
    if (headersText) {
        try { headers = JSON.parse(headersText); }
        catch (e) { showAlert('请求头格式错误', '格式错误', 'error'); return; }
    }

    try {
        if (currentMCPIdEdit) {
            await fetch('/nex/mcp/servers/' + currentMCPIdEdit, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, url, server_type: serverType, headers }) });
        } else {
            const r = await fetch('/nex/mcp/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, name, url, server_type: serverType, headers }) });
            const d = await r.json();
            if (d.code !== 0) { showToast('添加失败: ' + (d.detail || d.message), 'error'); return; }
            if (!d.data.connected) { showToast('添加成功，正在后台连接...', 'warning', 5000); }
            currentMCPIdEdit = id;
        }
        showToast('保存成功', 'success');
        closeMCPDetail();
    } catch (e) { showToast('保存失败: ' + e.message, 'error'); }
}

function closeMCPDetail() {
    closeSettingsDetail('detail-mcp', () => {
        currentMCPIdEdit = null;
        loadMCPListPanel();
        loadToolbarToolsCount();
    });
}

// ========== 确认弹窗 ==========
function showConfirm(title, text, callback) {
    document.getElementById('confirmModalTitle').innerHTML = iconWarning + ' ' + title;
    document.getElementById('confirmText').textContent = text;
    confirmCallback = callback;
    document.getElementById('confirmModal').classList.add('show');
}

function closeConfirm() {
    const modal = document.getElementById('confirmModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
    confirmCallback = null;
}
function doConfirm() { if (confirmCallback) confirmCallback(); closeConfirm(); }

// ========== 删除确认对话框 ==========
let deleteConfirmCallback = null;

function showDeleteConfirm(title, text, callback) {
    document.getElementById('deleteConfirmTitle').textContent = title;
    document.getElementById('deleteConfirmText').textContent = text;
    deleteConfirmCallback = callback;
    document.getElementById('deleteConfirmModal').classList.add('show');
}

function closeDeleteConfirm() {
    const modal = document.getElementById('deleteConfirmModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
    deleteConfirmCallback = null;
}

function doDeleteConfirm() {
    if (deleteConfirmCallback) deleteConfirmCallback();
    closeDeleteConfirm();
}


function editSession(id, name) {
    editingSessionId = id;
    document.getElementById('editSessionName').value = name;
    document.getElementById('editModalTitle').innerHTML = iconEditLg + ' 编辑会话名称';
    document.getElementById('editModal').classList.add('show');
}

async function saveSessionName() {
    const name = document.getElementById('editSessionName').value.trim();
    if (!name) return;
    try {
        await fetch(`/nex/sessions/${editingSessionId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) });
        closeEditModal();
        await updateSessionList();
    } catch (e) { showToast('保存失败: ' + e.message, 'error'); }
}

function closeEditModal() {
    const modal = document.getElementById('editModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
    editingSessionId = null;
}

function deleteSession(id) {
    showDeleteConfirm('删除会话', '确定要删除这个会话吗？', async function () {
        try {
            await fetch('/nex/sessions/' + id, { method: 'DELETE' });
            const wasCurrentSession = currentSessionId === id;
            if (wasCurrentSession) {
                currentSessionId = null;
                localStorage.removeItem('currentSessionId');
            }
            // 重新加载会话列表
            await loadPersonaSessions(currentPersonaId);
            // 如果删除的是当前会话且没有其他会话可选，清空右侧内容
            if (wasCurrentSession && !currentSessionId) {
                document.getElementById('sessionTitle').textContent = 'NexAgent';
                showWelcomeState();
                // 清空角色卡显示
                currentSessionPersonaId = null;
                window.currentPersonaAvatar = '';
            }
        } catch (e) { 
            console.error('删除会话失败:', e);
            showToast('删除失败: ' + e.message, 'error'); 
        }
    });
}

// ========== 新建会话弹窗 ==========
async function showNewSessionModal() {
    await loadNewSessionPersonaOptions();
    document.getElementById('newSessionName').value = '';
    
    // 设置默认选中当前角色卡
    const personaValue = currentPersonaId || '';
    document.getElementById('newSessionPersonaValue').value = personaValue;
    
    // 获取当前角色名称
    let personaName = '默认（通用助手）';
    if (currentPersonaId) {
        try {
            const r = await fetch('/nex/personas');
            const d = await r.json();
            const persona = d.data.find(p => p.id === currentPersonaId);
            if (persona) {
                personaName = persona.name;
            }
        } catch (e) {
            console.error('获取角色信息失败', e);
        }
    }
    
    document.getElementById('newSessionPersonaSelect').querySelector('.custom-select-text').textContent = personaName;
    
    // 更新选中状态
    document.querySelectorAll('#newSessionPersonaMenu .custom-select-item').forEach(item => {
        item.classList.remove('active');
    });
    const activeItem = personaValue 
        ? document.querySelector(`#newSessionPersonaMenu .custom-select-item[onclick*="'${personaValue}'"]`)
        : document.querySelector('#newSessionPersonaMenu .custom-select-item[onclick*="selectNewSessionPersona(\'\',"]');
    if (activeItem) {
        activeItem.classList.add('active');
    }
    
    document.getElementById('newSessionModal').classList.add('show');
}

function closeNewSessionModal() {
    const modal = document.getElementById('newSessionModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
    document.getElementById('newSessionPersonaSelect').classList.remove('open');
}

async function loadNewSessionPersonaOptions() {
    try {
        const r = await fetch('/nex/personas');
        const d = await r.json();
        const menu = document.getElementById('newSessionPersonaMenu');
        let html = `
                    <div class="custom-select-item active" onclick="selectNewSessionPersona('', '默认（通用助手）')">
                        <div class="custom-select-item-icon" style="background:var(--text2)">
                            <i class="ri-user-line"></i>
                        </div>
                        <span class="custom-select-item-text">默认（通用助手）</span>
                    </div>
                `;
        d.data.forEach(p => {
            const iconHtml = p.avatar 
                ? `<img src="${p.avatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
                : p.name.charAt(0).toUpperCase();
            html += `
                        <div class="custom-select-item" onclick="selectNewSessionPersona('${p.id}', '${escapeHtml(p.name)}')">
                            <div class="custom-select-item-icon" style="background:${p.avatar ? 'transparent' : getPersonaColor(p.id)}">
                                ${iconHtml}
                            </div>
                            <span class="custom-select-item-text">${escapeHtml(p.name)}</span>
                        </div>
                    `;
        });
        menu.innerHTML = html;
    } catch (e) { console.error('加载角色卡失败', e); }
}

function toggleCustomSelect(id) {
    const select = document.getElementById(id);
    const wasOpen = select.classList.contains('open');

    // 关闭所有自定义选择器
    document.querySelectorAll('.custom-select').forEach(s => s.classList.remove('open'));

    if (!wasOpen) {
        select.classList.add('open');
    }
}

// 通用的自定义 select 选项选择函数
function selectCustomOption(selectId, value, text, event) {
    const select = document.getElementById(selectId);
    const valueInput = select.querySelector('input[type="hidden"]');
    const textSpan = select.querySelector('.custom-select-text');
    
    if (valueInput) valueInput.value = value;
    if (textSpan) textSpan.textContent = text;
    select.classList.remove('open');
    
    // 更新选中状态
    select.querySelectorAll('.custom-select-item').forEach(item => {
        item.classList.remove('active');
    });
    if (event && event.currentTarget) {
        event.currentTarget.classList.add('active');
    }
}

function selectNewSessionPersona(value, text) {
    document.getElementById('newSessionPersonaValue').value = value;
    document.getElementById('newSessionPersonaSelect').querySelector('.custom-select-text').textContent = text;
    document.getElementById('newSessionPersonaSelect').classList.remove('open');

    // 更新选中状态
    document.querySelectorAll('#newSessionPersonaMenu .custom-select-item').forEach(item => {
        item.classList.remove('active');
    });
    event.currentTarget.classList.add('active');
}

async function createNewSession() {
    const personaId = document.getElementById('newSessionPersonaValue').value;
    const name = document.getElementById('newSessionName').value.trim() || '新会话';

    try {
        const r = await fetch('/nex/sessions', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ name, user: getUser() })
        });
        const d = await r.json();
        const newSessionId = d.data.session_id;

        if (personaId) {
            await fetch(`/nex/sessions/${newSessionId}/persona`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ persona_id: parseInt(personaId) })
            });
        }

        currentSessionId = newSessionId;
        localStorage.setItem('currentSessionId', newSessionId);
        
        closeNewSessionModal();
        
        // 切换到新会话对应的角色
        const targetPersonaId = personaId ? parseInt(personaId) : null;
        
        // 如果新会话的角色与当前角色不同，需要切换角色
        if (targetPersonaId !== currentPersonaId) {
            currentPersonaId = targetPersonaId;
            localStorage.setItem('currentPersonaId', targetPersonaId || '');
            updatePersonaSelectorDisplay();
            renderPersonaDropdown(window.allPersonas || []);
        }
        
        expandedPersonas.add(targetPersonaId || 'default');
        
        // 加载新角色的会话列表
        await loadPersonaSessions(targetPersonaId);
    } catch (e) { showToast('创建失败: ' + e.message, 'error'); }
}

// ========== 消息管理 ==========
async function loadMessages(sessionId) {
    try {
        // 并行请求消息和会话信息
        const [messagesRes, sessionRes] = await Promise.all([
            fetch('/nex/sessions/' + sessionId + '/messages'),
            fetch('/nex/sessions/' + sessionId)
        ]);
        
        const [messagesData, sessionData] = await Promise.all([
            messagesRes.json(),
            sessionRes.json()
        ]);
        
        document.getElementById('sessionTitle').textContent = sessionData.data.name;
        renderMessages(messagesData.data);
    } catch { }
}

function renderMessages(messages) {
    const container = document.getElementById('msgs');
    if (messages.length === 0) {
        showEmptyState();
        return;
    }
    
    container.innerHTML = messages.map(function (m) {
        if (m.role === 'user') {
            // 使用 base64 编码存储原始内容，避免 HTML 属性转义问题
            var encodedContent = btoa(encodeURIComponent(m.content).replace(/%([0-9A-F]{2})/g, function(match, p1) {
                return String.fromCharCode('0x' + p1);
            }));
            return '<div class="msg-wrapper user">' +
                '<div class="msg user" data-id="' + m.id + '" data-original-content="' + encodedContent + '">' +
                '<div class="msg-content">' + fmt(m.content) + '</div>' +
                '<div class="msg-footer"><span></span><div class="msg-actions">' +
                '<button class="msg-action-btn" onclick="editMsg(' + m.id + ',\'user\')" title="编辑">' + iconEdit + '</button>' +
                '<button class="msg-action-btn delete" onclick="deleteMsg(' + m.id + ')" title="删除">' + iconDelete + '</button>' +
                '</div></div></div></div>';
        } else {
            var content = '';
            var extra = m.extra || {};
            if (extra.content_parts) {
                for (var i = 0; i < extra.content_parts.length; i++) {
                    var part = extra.content_parts[i];
                    if (part.type === 'text') content += fmt(part.content);
                    else if (part.type === 'tool') {
                        var toolId = 'h' + m.id + '-' + toolIdCounter++;
                        // 同步历史工具到全局存储
                        globalToolsData[toolId] = {
                            id: toolId,
                            type: 'tool',
                            name: part.name,
                            args: part.args,
                            result: part.result,
                            receiving: part.receiving || false,
                            executing: part.executing || false,
                            argsBuffer: part.argsBuffer || '',
                            parseSuccess: part.parseSuccess !== false,
                            isPartial: part.isPartial || false
                        };
                        content += createToolCardHtml(part.name, part.args, part.result, toolId, part.receiving, part.executing, part.argsBuffer, part.parseSuccess, part.isPartial);
                    }
                    else if (part.type === 'thinking') content += createThinkingCardHtml(part.content, 'h' + m.id + '-think-' + toolIdCounter++);
                    else if (part.type === 'error') {
                        // 渲染错误消息
                        content += '<div class="error-message" style="padding:12px;background:var(--error-bg,#fee);border:1px solid var(--error,#f44);border-radius:8px;color:var(--error,#f44);margin:8px 0;">' +
                            '<div style="display:flex;align-items:center;gap:8px;font-weight:500;margin-bottom:4px;">' +
                            '<i class="ri-error-warning-line"></i>' +
                            '<span>错误</span>' +
                            '</div>' +
                            '<div style="font-size:0.9rem;line-height:1.5;">' + escapeHtml(part.error) + '</div>' +
                            '</div>';
                    }
                }
            } else {
                content = fmt(m.content);
                if (extra.tool_calls) {
                    for (var j = 0; j < extra.tool_calls.length; j++) {
                        var tc = extra.tool_calls[j];
                        var toolId = 'h' + m.id + '-' + toolIdCounter++;
                        // 同步历史工具到全局存储
                        globalToolsData[toolId] = {
                            id: toolId,
                            type: 'tool',
                            name: tc.name,
                            args: tc.args,
                            result: tc.result,
                            receiving: false,
                            executing: false,
                            argsBuffer: '',
                            parseSuccess: true,
                            isPartial: false
                        };
                        content += createToolCardHtml(tc.name, tc.args, tc.result, toolId, false, false, null, true, false);
                    }
                }
            }
            var tokens = extra.tokens;
            var tokensHtml = tokens ? '<span class="msg-tokens">' + iconTokens + ' ' + tokens.total + ' tokens</span>' : '<span></span>';
            // 如果消息被中断，显示提示
            if (extra.interrupted) {
                tokensHtml = '<span class="msg-tokens">' + iconTokens + ' 输出被中断，无法显示Token消耗</span>';
            }
            var avatarHtml = getAiAvatarHtml();
            // 使用 base64 编码存储原始内容
            var encodedContent = btoa(encodeURIComponent(m.content).replace(/%([0-9A-F]{2})/g, function(match, p1) {
                return String.fromCharCode('0x' + p1);
            }));
            
            // 根据 TTS 配置决定是否显示朗读按钮
            var speakBtn = window.hasTTSConfig 
                ? '<button class="msg-action-btn" onclick="speakMsg(' + m.id + ')" title="朗读">' + iconVolume + '</button>' 
                : '';
            
            return '<div class="msg-wrapper ai">' +
                avatarHtml +
                '<div class="msg ai" data-id="' + m.id + '" data-original-content="' + encodedContent + '">' +
                '<div class="msg-content">' + content + '</div>' +
                '<div class="msg-footer">' + tokensHtml +
                '<div class="msg-actions">' +
                speakBtn +
                '<button class="msg-action-btn" onclick="copyMsg(' + m.id + ')" title="复制">' + iconCopy + '</button>' +
                '<button class="msg-action-btn" onclick="regenerateMsg()" title="重新生成">' + iconRefresh + '</button>' +
                '<button class="msg-action-btn delete" onclick="deleteMsg(' + m.id + ')" title="删除">' + iconDelete + '</button>' +
                '</div></div></div></div>';
        }
    }).join('');
    container.scrollTop = container.scrollHeight;
}

function getAiAvatarHtml() {
    if (window.avatarMode === 'hide') return '';
    
    const defaultIcon = '<i class="ri-chat-3-line"></i>';
    
    // 检查当前角色卡是否有自定义头像
    if (window.currentPersonaAvatar) {
        return `<div class="msg-avatar"><img src="${window.currentPersonaAvatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;"></div>`;
    }
    
    if (window.avatarMode === 'text') {
        // 文字头像模式：显示角色名首字母
        const personaName = document.getElementById('currentPersonaName')?.textContent || '默认';
        const firstLetter = personaName.charAt(0).toUpperCase();
        const bgColor = currentSessionPersonaId ? getPersonaColor(currentSessionPersonaId) : 'var(--text2)';
        return `<div class="msg-avatar" style="background:${bgColor};color:white;font-weight:600;font-size:0.8rem;">${firstLetter}</div>`;
    }
    
    // 消息图标模式（默认）
    return '<div class="msg-avatar">' + defaultIcon + '</div>';
}

function showEmptyState() {
    document.getElementById('msgs').innerHTML = `
        <div class="empty-state">
            <div class="icon">
                <i class="ri-chat-3-line"></i>
            </div>
            <div class="empty-state-title">NexAgent 助手</div>
            <div class="empty-state-status">
                <span class="status-dot"></span>
                在线
            </div>
            <div class="empty-state-desc">输入消息开始对话</div>
        </div>`;
}

function showWelcomeState() {
    document.getElementById('msgs').innerHTML = `
        <div class="empty-state">
            <div class="icon">
                <i class="ri-chat-3-line"></i>
            </div>
            <div class="empty-state-title">欢迎使用 NexAgent</div>
            <div class="empty-state-status">
                <span class="status-dot"></span>
                在线
            </div>
            <div class="empty-state-desc">输入消息开始对话，将自动创建新会话</div>
        </div>`;
}

function editMsg(id, role) {
    editingMsgId = id;
    editingMsgRole = role;
    document.getElementById('editMsgModalTitle').innerHTML = iconEditLg + ' 编辑消息';
    const msgEl = document.querySelector(`.msg[data-id="${id}"] .msg-content`);
    fetch(`/nex/messages/${id}`).then(r => r.json()).then(d => {
        document.getElementById('editMsgContent').value = d.data.content;
        document.getElementById('editMsgModal').classList.add('show');
    });
}

async function saveEditedMsg(regenerate) {
    const content = document.getElementById('editMsgContent').value.trim();
    if (!content) return;

    // 保存当前编辑状态，因为 closeEditMsgModal 会清除它们
    const msgId = editingMsgId;
    const msgRole = editingMsgRole;

    try {
        const r = await fetch(`/nex/messages/${msgId}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content, regenerate: regenerate && msgRole === 'user' })
        });
        const d = await r.json();
        if (d.code !== 0) {
            showAlert('保存失败: ' + (d.detail || d.message), '错误', 'error');
            return;
        }
        closeEditMsgModal();
        if (regenerate && msgRole === 'user' && d.data && d.data.session_id) {
            // 先加载消息（显示更新后的用户消息，AI回复已被删除）
            await loadMessages(d.data.session_id);
            // 重新发送消息，不保存用户消息（因为已存在）
            await sendMessage(content, false);
        } else {
            await loadMessages(currentSessionId);
        }
    } catch (e) { showToast('保存失败: ' + e.message, 'error'); }
}

function closeEditMsgModal() {
    const modal = document.getElementById('editMsgModal');
    modal.classList.add('closing');
    modal.classList.remove('show');
    setTimeout(() => {
        modal.classList.remove('closing');
    }, 200);
    editingMsgId = null;
    editingMsgRole = null;
}

function deleteMsg(id) {
    showDeleteConfirm('删除消息', '确定要删除这条消息吗？', async () => {
        try {
            await fetch(`/nex/messages/${id}`, { method: 'DELETE' });
            await loadMessages(currentSessionId);
        } catch (e) { showToast('删除失败: ' + e.message, 'error'); }
    });
}

async function regenerateMsg() {
    if (!currentSessionId || busy) return;
    try {
        const r = await fetch(`/nex/sessions/${currentSessionId}/regenerate`, { method: 'POST' });
        const d = await r.json();
        if (d.code !== 0) {
            showAlert('重新生成失败: ' + (d.detail || d.message || '未知错误'), '错误', 'error');
            return;
        }
        if (!d.data || !d.data.message) {
            showAlert('没有找到可重新生成的消息', '重新生成失败', 'error');
            return;
        }
        await loadMessages(currentSessionId);
        await sendMessage(d.data.message, false);
    } catch (e) { showToast('重新生成失败: ' + e.message, 'error'); }
}

async function clearMsgs() {
    if (!currentSessionId) return;
    showConfirm('清空消息', '确定要清空当前会话的所有消息吗？', async () => {
        try {
            await fetch(`/nex/sessions/${currentSessionId}/messages`, { method: 'DELETE' });
            await loadMessages(currentSessionId);
            await loadPersonaSessions(currentPersonaId); // 刷新侧边栏显示正确的消息数
        } catch (e) { showToast('清空失败: ' + e.message, 'error'); }
    });
}

// ========== 发送消息 ==========
function send() {
    const inp = document.getElementById('inp');
    const msg = inp.value.trim();
    if (!msg || busy) return;
    inp.value = '';
    inp.dataset.userInput = '';
    resetTextareaHeight();
    sendMessage(msg, true);
}

async function sendMessage(message, saveUserMsg) {
    if (busy) return;
    busy = true;
    document.getElementById('btn').disabled = true;
    
    // 如果没有当前会话，先创建一个
    if (!currentSessionId) {
        try {
            const r = await fetch('/nex/sessions', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name: '新会话', user: getUser() })
            });
            const d = await r.json();
            currentSessionId = d.data.session_id;
            localStorage.setItem('currentSessionId', currentSessionId);
            expandedPersonas.add('default');
            // 更新侧边栏
            await loadPersonaSessions(currentPersonaId);
        } catch (e) {
            showToast('创建会话失败: ' + e.message, 'error');
            busy = false;
            document.getElementById('btn').disabled = false;
            return;
        }
    }
    
    // 创建AbortController用于停止生成
    currentAbortController = new AbortController();
    
    // 显示停止按钮
    const btnEl = document.getElementById('btn');
    btnEl.innerHTML = '<i class="ri-stop-fill"></i>';
    btnEl.disabled = false;
    btnEl.onclick = stopGeneration;

    // 保存消息用于重试
    lastSentMessage = message;

    const container = document.getElementById('msgs');
    if (container.querySelector('.empty-state')) container.innerHTML = '';

    // 检查用户是否在底部附近（用于智能滚动）
    const isNearBottom = () => {
        const threshold = 100; // 距离底部100px以内认为在底部
        return container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
    };

    // 智能滚动：只在用户在底部附近时才自动滚动
    const smartScroll = () => {
        if (isNearBottom()) {
            container.scrollTop = container.scrollHeight;
        }
    };

    const aiAvatarHtml = getAiAvatarHtml();

    if (saveUserMsg) {
        const encodedMessage = btoa(encodeURIComponent(message).replace(/%([0-9A-F]{2})/g, function(match, p1) {
            return String.fromCharCode('0x' + p1);
        }));
        container.innerHTML += `<div class="msg-wrapper user"><div class="msg user" data-original-content="${encodedMessage}"><div class="msg-content">${fmt(message)}</div></div></div>`;
    }

    const aiMsgId = 'ai-' + Date.now();
    container.innerHTML += `<div class="msg-wrapper ai">${aiAvatarHtml}<div class="msg ai" id="${aiMsgId}"><div class="msg-content"><div class="typing"><span></span><span></span><span></span></div></div><div class="msg-footer" style="display:none"><span class="msg-tokens"></span><div class="msg-actions"><button class="msg-action-btn" onclick="copyMsgById('${aiMsgId}')" title="复制">${iconCopy}</button><button class="msg-action-btn" onclick="regenerateMsg()" title="重新生成">${iconRefresh}</button><button class="msg-action-btn delete" onclick="deleteMsgAndReload()" title="删除">${iconDelete}</button></div></div></div></div>`;
    container.scrollTop = container.scrollHeight;

    let totalTokens = 0;
    
    // 停止之前的 TTS 播放
    if (currentTTSManager) {
        currentTTSManager.stop();
        currentTTSManager = null;
    }
    
    // 创建 TTS 管理器实例并立即启动（仅当配置存在且自动播放开启时）
    if (window.ttsAutoPlay && window.hasTTSConfig) {
        currentTTSManager = new StreamTTSManager();
        currentTTSManager.start();
    }
    
    // 声明这些变量在try块外，以便在catch块中使用
    let contentParts = [];
    let currentTextContent = '';
    let currentThinkingContent = '';
    let currentToolId = null;
    let currentThinkingId = null;
    let isThinking = false;

    try {
        // 获取当前模型信息，判断是否支持思考
        const currentModel = chatModels.find(m => m.key === currentModelKey);
        const isReasoningModel = currentModel && currentModel.tags && currentModel.tags.includes('reasoning');
        
        // 构建请求体
        const requestBody = {
            user: getUser(),
            message,
            session_id: currentSessionId,
            stream: true,
            save_user_message: saveUserMsg,
            use_system_prompt: systemPromptEnabled,
            model_key: currentModelKey
        };
        
        // 只有思考模型才传递思考参数
        if (isReasoningModel) {
            if (currentReasoningEffort && currentReasoningEffort !== 'default') {
                requestBody.reasoning_effort = currentReasoningEffort;
            }
            if (currentThinkingMode && currentThinkingMode !== 'auto') {
                requestBody.thinking_mode = currentThinkingMode;
            }
        }
        
        const response = await fetch('/nex/chat', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(requestBody),
            signal: currentAbortController.signal
        });

        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        // 内容流：按顺序保存各种内容块
        const aiMsg = document.getElementById(aiMsgId);
        const contentEl = aiMsg.querySelector('.msg-content');

        // 上次渲染的内容部分数量
        let lastRenderedPartsCount = 0;
        let lastTextContent = '';
        let lastThinkingContent = '';

        // 智能渲染内容的函数 - 只更新变化的部分
        function renderContent() {
            // 移除 typing 动画（如果存在）
            const typingEl = contentEl.querySelector('.typing');
            if (typingEl) {
                typingEl.remove();
            }
            
            // 检查是否有新的内容部分
            const hasNewParts = contentParts.length > lastRenderedPartsCount;
            const textChanged = currentTextContent !== lastTextContent;
            const thinkingChanged = currentThinkingContent !== lastThinkingContent;
            
            // 如果没有任何变化，跳过渲染
            if (!hasNewParts && !textChanged && !thinkingChanged) {
                return;
            }
            
            // 如果只是文本内容变化，只更新文本部分
            if (!hasNewParts && textChanged && !isThinking) {
                // 查找最后一个文本节点并更新
                const textNodes = contentEl.querySelectorAll(':scope > :not(.tool-card):not(.thinking)');
                if (textNodes.length > 0) {
                    const lastTextNode = textNodes[textNodes.length - 1];
                    // 检查是否是文本容器
                    if (!lastTextNode.classList.contains('tool-card') && !lastTextNode.classList.contains('thinking')) {
                        lastTextNode.outerHTML = fmt(currentTextContent);
                        lastTextContent = currentTextContent;
                        return;
                    }
                }
            }
            
            // 如果只是思考内容变化，只更新思考卡片
            if (!hasNewParts && !textChanged && thinkingChanged && isThinking && currentThinkingId) {
                const card = document.getElementById(currentThinkingId);
                if (card) {
                    updateThinkingContent(currentThinkingId, currentThinkingContent, false);
                    lastThinkingContent = currentThinkingContent;
                    return;
                }
            }
            
            // 需要完全重新渲染 - 保存所有卡片的展开状态
            const cardStates = {};
            contentEl.querySelectorAll('.tool-card, .thinking').forEach(card => {
                if (card.id) {
                    cardStates[card.id] = card.classList.contains('open');
                }
            });
            
            let html = '';
            // 按顺序渲染内容块
            for (const part of contentParts) {
                if (part.type === 'thinking') {
                    html += createThinkingCardHtml(part.content, part.id, true);
                } else if (part.type === 'text') {
                    html += fmt(part.content);
                } else if (part.type === 'tool') {
                    html += createToolCardHtml(part.name, part.args, part.result, part.id, part.receiving, part.executing, part.argsBuffer, part.parseSuccess, part.isPartial);
                } else if (part.type === 'error') {
                    // 渲染错误消息
                    html += `<div class="error-message" style="padding:12px;background:var(--error-bg,#fee);border:1px solid var(--error,#f44);border-radius:8px;color:var(--error,#f44);margin:8px 0;">
                        <div style="display:flex;align-items:center;gap:8px;font-weight:500;margin-bottom:4px;">
                            <i class="ri-error-warning-line"></i>
                            <span>错误</span>
                        </div>
                        <div style="font-size:0.9rem;line-height:1.5;">${escapeHtml(part.error)}</div>
                    </div>`;
                }
            }
            // 渲染当前正在进行的思考
            if (isThinking && currentThinkingId) {
                html += createThinkingCardHtml(currentThinkingContent, currentThinkingId, false);
            }
            // 渲染当前正在接收的文本（使用特殊类名以便增量更新）
            if (currentTextContent) {
                html += '<div class="streaming-text">' + fmt(currentTextContent) + '</div>';
            }
            
            contentEl.innerHTML = html;
            
            // 恢复卡片的展开状态
            Object.keys(cardStates).forEach(id => {
                const card = document.getElementById(id);
                if (card && cardStates[id]) {
                    card.classList.add('open');
                }
            });
            
            // 更新记录
            lastRenderedPartsCount = contentParts.length;
            lastTextContent = currentTextContent;
            lastThinkingContent = currentThinkingContent;
        }

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            const text = decoder.decode(value);
            const lines = text.split('\n');
            for (const line of lines) {
                if (line.startsWith('data: ')) {
                    try {
                        const data = JSON.parse(line.slice(6));
                        if (data.type === 'content') {
                            currentTextContent += data.data;
                            // 智能更新：如果只是文本变化，尝试增量更新
                            if (!isThinking && contentParts.length === lastRenderedPartsCount) {
                                // 查找或创建文本容器
                                let textContainer = contentEl.querySelector('.streaming-text');
                                if (!textContainer) {
                                    // 移除 typing 动画（如果存在）
                                    const typingEl = contentEl.querySelector('.typing');
                                    if (typingEl) {
                                        typingEl.remove();
                                    }
                                    // 创建新的文本容器
                                    textContainer = document.createElement('div');
                                    textContainer.className = 'streaming-text';
                                    contentEl.appendChild(textContainer);
                                }
                                // 增量更新文本内容
                                textContainer.innerHTML = fmt(currentTextContent);
                                lastTextContent = currentTextContent;
                            } else {
                                // 需要完全重新渲染
                                renderContent();
                            }
                            // TTS: 将文本传递给管理器
                            if (currentTTSManager) {
                                currentTTSManager.appendText(data.data);
                            }
                        } else if (data.type === 'thinking_start') {
                            // 保存之前的文本（如果有）
                            if (currentTextContent) {
                                contentParts.push({ type: 'text', content: currentTextContent });
                                currentTextContent = '';
                            }
                            // 先设置状态，再渲染
                            isThinking = true;
                            currentThinkingId = 'think-' + toolIdCounter++;
                            currentThinkingContent = '';
                            renderContent();
                        } else if (data.type === 'thinking') {
                            // 累积思考内容
                            currentThinkingContent += data.data;
                            // 尝试直接更新现有卡片的内容，避免重新渲染
                            const card = document.getElementById(currentThinkingId);
                            if (card) {
                                const pre = document.getElementById(currentThinkingId + '-content');
                                if (pre) {
                                    pre.textContent = currentThinkingContent;
                                }
                            } else {
                                // 卡片不存在，需要渲染
                                renderContent();
                            }
                        } else if (data.type === 'thinking_end') {
                            // 先设置 isThinking 为 false，避免重复渲染
                            isThinking = false;
                            // 保存完成的思考到 contentParts
                            if (currentThinkingContent) {
                                contentParts.push({ type: 'thinking', id: currentThinkingId, content: currentThinkingContent });
                            }
                            currentThinkingContent = '';
                            currentThinkingId = null;
                            renderContent();
                        } else if (data.type === 'tool_call_start') {
                            // 工具调用开始（还没有工具名）
                            if (currentTextContent) {
                                contentParts.push({ type: 'text', content: currentTextContent });
                                currentTextContent = '';
                            }
                            currentToolId = 'tool-' + toolIdCounter++;
                            const newTool = { 
                                type: 'tool', 
                                id: currentToolId, 
                                index: data.data.index,
                                name: '', 
                                args: {}, 
                                argsBuffer: '',
                                receiving: true,  // 标记正在接收
                                result: undefined 
                            };
                            contentParts.push(newTool);
                            syncToolToGlobal(newTool); // 同步到全局存储
                            renderContent();
                        } else if (data.type === 'tool_name_delta') {
                            // 工具名增量
                            const tool = contentParts.find(p => p.type === 'tool' && p.id === currentToolId);
                            if (tool) {
                                tool.name = data.data.name;
                                syncToolToGlobal(tool); // 同步到全局存储
                                updateToolName(tool.id, data.data.name);
                            }
                        } else if (data.type === 'tool_args_delta') {
                            // 参数增量
                            const tool = contentParts.find(p => p.type === 'tool' && p.id === currentToolId);
                            if (tool) {
                                // 保存原始参数缓冲区（包含转义字符）
                                tool.argsBuffer = data.data.arguments;
                                
                                // 立即尝试解析参数（实时解析）
                                const parseResult = parsePartialJson(data.data.arguments);
                                
                                if (parseResult.success) {
                                    // 成功解析（完整或部分），更新数据
                                    tool.args = parseResult.data;
                                    tool.parseSuccess = !parseResult.partial;
                                    tool.isPartial = parseResult.partial;
                                    
                                    syncToolToGlobal(tool); // 同步到全局存储
                                    
                                    // 使用防抖避免频繁 DOM 更新（但数据已更新）
                                    clearTimeout(tool.updateTimeout);
                                    tool.updateTimeout = setTimeout(() => {
                                        updateToolArgs(tool.id, tool.args, tool.isPartial);
                                    }, 100); // 100ms 防抖，平衡实时性和性能
                                } else {
                                    // 解析失败，立即显示原始文本
                                    tool.parseSuccess = false;
                                    tool.isPartial = false;
                                    updateToolArgsLoading(tool.id, data.data.arguments);
                                }
                            }
                        } else if (data.type === 'tool_start') {
                            // 工具开始执行
                            // 查找最近的正在接收的工具（流式模式）
                            const existingTool = contentParts.find(p => p.type === 'tool' && p.receiving === true);
                            
                            if (existingTool) {
                                // 流式模式：更新现有工具状态
                                existingTool.receiving = false;
                                existingTool.executing = true;
                                existingTool.name = data.data.name;
                                existingTool.args = data.data.args;
                                existingTool.isPartial = false; // 执行时不再是部分解析
                                // 清除所有超时
                                if (existingTool.parseTimeout) {
                                    clearTimeout(existingTool.parseTimeout);
                                }
                                if (existingTool.updateTimeout) {
                                    clearTimeout(existingTool.updateTimeout);
                                }
                                syncToolToGlobal(existingTool); // 同步到全局存储
                                updateToolStatus(existingTool.id, 'executing');
                                updateToolName(existingTool.id, data.data.name);
                                updateToolArgs(existingTool.id, data.data.args, false); // 立即更新，不是部分解析
                                // 更新 currentToolId 以便后续 tool_end 能找到
                                currentToolId = existingTool.id;
                            } else {
                                // 旧模式：创建新工具卡片（兼容性）
                                if (currentTextContent) {
                                    contentParts.push({ type: 'text', content: currentTextContent });
                                    currentTextContent = '';
                                }
                                currentToolId = 'tool-' + toolIdCounter++;
                                const newTool = { 
                                    type: 'tool', 
                                    id: currentToolId, 
                                    name: data.data.name, 
                                    args: data.data.args, 
                                    executing: true,
                                    result: undefined 
                                };
                                contentParts.push(newTool);
                                syncToolToGlobal(newTool); // 同步到全局存储
                            }
                            renderContent();
                        } else if (data.type === 'tool_end') {
                            // 更新工具结果
                            const tool = contentParts.find(p => p.type === 'tool' && p.id === currentToolId);
                            if (tool) {
                                tool.result = data.data.result;
                                tool.executing = false;
                                tool.receiving = false;
                                syncToolToGlobal(tool); // 同步到全局存储
                            }
                            updateToolResult(currentToolId, data.data.result);
                            renderContent();  // 重新渲染以更新状态
                        } else if (data.type === 'done') {
                            // 保存最后的文本内容
                            if (currentTextContent) {
                                contentParts.push({ type: 'text', content: currentTextContent });
                                currentTextContent = '';
                            }
                            if (data.session_id) {
                                currentSessionId = data.session_id;
                                localStorage.setItem('currentSessionId', currentSessionId);
                            }
                            if (data.tokens) totalTokens = data.tokens.total || 0;
                            // 最终渲染
                            renderContent();
                            // 不在这里调用 finish()，等待 loadMessages 完成后再调用
                        } else if (data.type === 'error') {
                            // 处理错误事件
                            const errorMsg = data.error || '发生未知错误';
                            
                            // 保存已生成的内容（如果有）
                            if (currentTextContent) {
                                contentParts.push({ type: 'text', content: currentTextContent });
                                currentTextContent = '';
                            }
                            if (isThinking && currentThinkingId) {
                                contentParts.push({ type: 'thinking', id: currentThinkingId, content: currentThinkingContent });
                                isThinking = false;
                            }
                            
                            // 添加错误消息到 contentParts
                            contentParts.push({ 
                                type: 'error', 
                                error: errorMsg 
                            });
                            
                            // 渲染错误消息
                            renderContent();
                            
                            // 保存消息到数据库（包含错误信息）
                            const finalContent = contentParts.map(p => {
                                if (p.type === 'text') return p.content;
                                if (p.type === 'error') return `[错误] ${p.error}`;
                                return '';
                            }).join('\n').trim() || '[错误] ' + errorMsg;
                            
                            await fetch('/nex/messages/save', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify({
                                    session_id: currentSessionId,
                                    content: finalContent,
                                    content_parts: contentParts,
                                    interrupted: true  // 标记为被中断/出错
                                })
                            });
                            
                            // 显示持久 toast 提示
                            showToast('对话出错: ' + errorMsg, 'error', 0);  // duration=0 表示持久显示
                            
                            // 停止流式输出
                            break;
                        }
                    } catch { }
                }
            }
            smartScroll();
        }

        // 确保最终内容被渲染
        if (currentTextContent) {
            contentParts.push({ type: 'text', content: currentTextContent });
            renderContent();
        }

        // 流式输出完成后，重新加载消息以获取正确的消息ID和操作栏
        // 等待 loadMessages 完成后再开始 TTS
        loadMessages(currentSessionId).then(() => {
            // 工具栏显示完成后，开始 TTS
            if (currentTTSManager) {
                currentTTSManager.finish();
            }
        });
    } catch (e) {
        // 如果是用户主动停止，不显示错误
        if (e.name === 'AbortError') {
            // 保存已生成的内容
            if (currentTextContent) {
                contentParts.push({ type: 'text', content: currentTextContent });
                currentTextContent = '';
            }
            if (isThinking && currentThinkingId) {
                contentParts.push({ type: 'thinking', id: currentThinkingId, content: currentThinkingContent });
                isThinking = false;
                currentThinkingContent = '';
            }
            // 重新渲染已生成的内容
            let html = '';
            for (const part of contentParts) {
                if (part.type === 'thinking') {
                    html += createThinkingCardHtml(part.content, part.id, true);
                } else if (part.type === 'text') {
                    html += fmt(part.content);
                } else if (part.type === 'tool') {
                    html += createToolCardHtml(part.name, part.args, part.result, part.id, part.receiving, part.executing, part.argsBuffer, part.parseSuccess, part.isPartial);
                }
            }
            let aiMsg = document.getElementById(aiMsgId);
            if (aiMsg) {
                const contentEl = aiMsg.querySelector('.msg-content');
                contentEl.innerHTML = html;
            }
            
            // 构建完整内容
            let fullContent = '';
            for (const part of contentParts) {
                if (part.type === 'text') {
                    fullContent += part.content;
                }
            }
            
            // 显示footer和提示信息
            aiMsg = document.getElementById(aiMsgId);
            if (aiMsg) {
                const footerEl = aiMsg.querySelector('.msg-footer');
                if (footerEl) {
                    footerEl.style.display = 'flex';
                    footerEl.innerHTML = `
                        <span class="msg-tokens">输出被中断，无法显示Token消耗</span>
                        <div class="msg-actions">
                            <button class="msg-action-btn" onclick="copyMsgById('${aiMsgId}')" title="复制">${iconCopy}</button>
                            <button class="msg-action-btn" onclick="regenerateMsg()" title="重新生成">${iconRefresh}</button>
                            <button class="msg-action-btn delete" onclick="deleteMsgAndReload()" title="删除">${iconDelete}</button>
                        </div>
                    `;
                }
            }
            
            // 发送保存请求到服务器
            try {
                await fetch('/nex/messages/save', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        session_id: currentSessionId,
                        content: fullContent,
                        content_parts: contentParts,
                        tokens: totalTokens > 0 ? { total: totalTokens } : null,
                        interrupted: true  // 标记为被中断
                    })
                });
                showToast('已保存已生成的内容', 'info', 2000);
            } catch (saveErr) {
                console.error('保存消息失败:', saveErr);
                showToast('已停止生成，但保存失败', 'warning', 2000);
            }
            // 重新加载消息以显示保存的内容
            await loadMessages(currentSessionId);
            
            // 重新加载后，再次设置footer提示
            setTimeout(() => {
                const container = document.getElementById('msgs');
                const allAiMsgs = container.querySelectorAll('.msg.ai');
                if (allAiMsgs.length > 0) {
                    const lastAiMsg = allAiMsgs[allAiMsgs.length - 1];
                    const footerEl = lastAiMsg.querySelector('.msg-footer');
                    if (footerEl) {
                        footerEl.style.display = 'flex';
                        const msgId = lastAiMsg.getAttribute('data-id') || lastAiMsg.id;
                        footerEl.innerHTML = `
                            <span class="msg-tokens">${iconTokens} 输出被中断，无法显示Token消耗</span>
                            <div class="msg-actions">
                                <button class="msg-action-btn" onclick="copyMsgById('${msgId}')" title="复制">${iconCopy}</button>
                                <button class="msg-action-btn" onclick="regenerateMsg()" title="重新生成">${iconRefresh}</button>
                                <button class="msg-action-btn delete" onclick="deleteMsgAndReload()" title="删除">${iconDelete}</button>
                            </div>
                        `;
                    }
                }
            }, 100);
        } else {
            // 其他错误（网络错误、API错误等）
            console.error('Chat error:', e);
            const aiMsg = document.getElementById(aiMsgId);
            if (aiMsg) {
                const contentEl = aiMsg.querySelector('.msg-content');
                const footerEl = aiMsg.querySelector('.msg-footer');
                
                // 保存已生成的内容（如果有）
                let errorContent = '';
                if (currentTextContent || contentParts.length > 0) {
                    // 渲染已生成的内容
                    let html = '';
                    for (const part of contentParts) {
                        if (part.type === 'thinking') {
                            html += createThinkingCardHtml(part.content, part.id, true);
                        } else if (part.type === 'text') {
                            html += fmt(part.content);
                        } else if (part.type === 'tool') {
                            html += createToolCardHtml(part.name, part.args, part.result, part.id, part.receiving, part.executing, part.argsBuffer, part.parseSuccess, part.isPartial);
                        }
                    }
                    if (currentTextContent) {
                        html += '<div class="streaming-text">' + fmt(currentTextContent) + '</div>';
                    }
                    errorContent = html;
                }
                
                // 添加错误提示
                const errorMsg = e.message || '发生未知错误';
                const errorHtml = `<div class="error-message" style="padding:12px;background:var(--error-bg,#fee);border:1px solid var(--error,#f44);border-radius:8px;color:var(--error,#f44);margin:8px 0;">
                    <div style="display:flex;align-items:center;gap:8px;font-weight:500;margin-bottom:4px;">
                        <i class="ri-error-warning-line"></i>
                        <span>错误</span>
                    </div>
                    <div style="font-size:0.9rem;line-height:1.5;">${escapeHtml(errorMsg)}</div>
                </div>`;
                
                contentEl.innerHTML = errorContent + errorHtml;
                
                // 显示操作栏，提供重试选项
                if (footerEl) {
                    footerEl.style.display = 'flex';
                    footerEl.innerHTML = `
                        <span class="msg-tokens" style="color:var(--error);">请求失败</span>
                        <div class="msg-actions">
                            <button class="msg-action-btn" onclick="retryLastMessage()" title="重试">
                                ${iconRefresh}
                            </button>
                            <button class="msg-action-btn delete" onclick="removeErrorMessage('${aiMsgId}')" title="删除">
                                ${iconDelete}
                            </button>
                        </div>
                    `;
                }
            }
            
            // 显示持久的 toast 提示
            showToast('对话出错: ' + (e.message || '未知错误'), 'error', 0);
        }
    }

    busy = false;
    document.getElementById('btn').disabled = false;
    // 恢复发送按钮
    const btnElRestore = document.getElementById('btn');
    btnElRestore.innerHTML = '<i class="ri-send-plane-fill"></i>';
    btnElRestore.onclick = () => {
        const inp = document.getElementById('inp');
        const msg = inp.value.trim();
        if (msg) { 
            inp.value = ''; 
            resetTextareaHeight();
            sendMessage(msg, true); 
        }
    };
    currentAbortController = null;
    // 不要 await，让它异步执行，避免被 TTS 请求阻塞
    updateSessionList();
}

function copyMsgById(id) {
    const msgEl = document.getElementById(id);
    if (msgEl) {
        const encodedContent = msgEl.getAttribute('data-original-content');
        if (encodedContent) {
            try {
                // 解码 base64
                const decoded = atob(encodedContent);
                const decodedContent = decodeURIComponent(decoded.split('').map(function(c) {
                    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
                }).join(''));
                navigator.clipboard.writeText(decodedContent);
                showToast('已复制到剪贴板', 'success', 2000);
            } catch (e) {
                console.error('解码失败:', e);
                // 降级到复制渲染文本
                const msgContentEl = msgEl.querySelector('.msg-content');
                if (msgContentEl) {
                    navigator.clipboard.writeText(msgContentEl.innerText);
                    showToast('已复制到剪贴板', 'success', 2000);
                }
            }
        } else {
            // 降级到复制渲染文本
            const msgContentEl = msgEl.querySelector('.msg-content');
            if (msgContentEl) {
                navigator.clipboard.writeText(msgContentEl.innerText);
                showToast('已复制到剪贴板', 'success', 2000);
            }
        }
    }
}

function copyMsg(id) {
    const msgEl = document.querySelector(`.msg[data-id="${id}"]`);
    if (msgEl) {
        const encodedContent = msgEl.getAttribute('data-original-content');
        if (encodedContent) {
            try {
                // 解码 base64
                const decoded = atob(encodedContent);
                const decodedContent = decodeURIComponent(decoded.split('').map(function(c) {
                    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
                }).join(''));
                navigator.clipboard.writeText(decodedContent);
                showToast('已复制到剪贴板', 'success', 2000);
            } catch (e) {
                console.error('解码失败:', e);
                // 降级到复制渲染文本
                const msgContentEl = msgEl.querySelector('.msg-content');
                if (msgContentEl) {
                    navigator.clipboard.writeText(msgContentEl.innerText);
                    showToast('已复制到剪贴板', 'success', 2000);
                }
            }
        } else {
            // 降级到复制渲染文本
            const msgContentEl = msgEl.querySelector('.msg-content');
            if (msgContentEl) {
                navigator.clipboard.writeText(msgContentEl.innerText);
                showToast('已复制到剪贴板', 'success', 2000);
            }
        }
    }
}

// 朗读消息
async function speakMsg(id) {
    const msgEl = document.querySelector(`.msg[data-id="${id}"]`);
    if (!msgEl) return;
    
    // 获取原始文本内容
    const encodedContent = msgEl.getAttribute('data-original-content');
    let text = '';
    
    if (encodedContent) {
        try {
            // 解码 base64
            const decoded = atob(encodedContent);
            text = decodeURIComponent(decoded.split('').map(function(c) {
                return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
            }).join(''));
        } catch (e) {
            console.error('解码失败:', e);
            // 降级到使用渲染文本
            const msgContentEl = msgEl.querySelector('.msg-content');
            if (msgContentEl) {
                text = msgContentEl.innerText;
            }
        }
    } else {
        // 降级到使用渲染文本
        const msgContentEl = msgEl.querySelector('.msg-content');
        if (msgContentEl) {
            text = msgContentEl.innerText;
        }
    }
    
    if (!text || !text.trim()) {
        showToast('没有可朗读的内容', 'warning');
        return;
    }
    
    // 显示合成提示
    showToast('正在合成语音，请稍候...', 'info');
    
    // 合成并播放
    try {
        const r = await fetch('/nex/tts/synthesize', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: text.trim() })
        });
        
        if (r.ok) {
            const audioBlob = await r.blob();
            const audioUrl = URL.createObjectURL(audioBlob);
            const audio = new Audio(audioUrl);
            
            audio.onended = () => {
                URL.revokeObjectURL(audioUrl);
            };
            
            audio.onerror = (e) => {
                console.error('Audio playback error:', e);
                URL.revokeObjectURL(audioUrl);
                showToast('音频播放失败', 'error');
            };
            
            await audio.play();
            showToast('开始播放', 'success');
        } else {
            const d = await r.json();
            showToast('合成失败: ' + (d.detail || '未知错误'), 'error');
        }
    } catch (e) {
        console.error('TTS error:', e);
        showToast('朗读失败: ' + e.message, 'error');
    }
}

// 保存最后发送的消息，用于重试
let lastSentMessage = null;
// 用于停止输出的AbortController
let currentAbortController = null;

function stopGeneration() {
    if (currentAbortController) {
        currentAbortController.abort();
        currentAbortController = null;
    }
}

function removeErrorMessage(msgId) {
    const msg = document.getElementById(msgId);
    if (msg) msg.remove();
}

async function retryLastMessage() {
    if (!lastSentMessage) {
        showToast('没有可重试的消息', 'warning');
        return;
    }
    // 移除错误消息
    const container = document.getElementById('msgs');
    const errorMsgs = container.querySelectorAll('.msg.ai');
    if (errorMsgs.length > 0) {
        const lastAiMsg = errorMsgs[errorMsgs.length - 1];
        if (lastAiMsg.querySelector('.msg-content span[style*="error"]')) {
            lastAiMsg.remove();
        }
    }
    // 重新发送
    await sendMessage(lastSentMessage, false);
}

async function deleteMsgAndReload() {
    // 删除最后一条AI消息，需要先刷新获取消息ID
    await loadMessages(currentSessionId);
}

// 初始化
init();

// ========== OpenAPI 配置管理 ==========
let currentOpenAPIConfigEdit = null;

async function loadOpenAPIConfigsPanel() {
    try {
        const r = await fetch('/nex/openapi/configs');
        const d = await r.json();
        const panel = document.getElementById('openAPIListPanel');

        if (!panel) {
            console.error('OpenAPI配置面板不存在');
            return;
        }

        if (d.data.length === 0) {
            panel.innerHTML = '<div style="padding:20px;color:var(--text2);text-align:center">暂无OpenAPI配置<br><span style="font-size:0.85rem">点击上方按钮添加</span></div>';
            return;
        }

        panel.innerHTML = d.data.map(config => `
            <div class="mcp-item ${currentOpenAPIConfigEdit === config.api_model_id ? 'active' : ''}" onclick="selectOpenAPIConfig('${escapeHtml(config.api_model_id)}')">
                <div class="mcp-item-content">
                    <div class="mcp-item-name">${escapeHtml(config.api_model_id)}</div>
                    <div class="mcp-item-detail">
                        模型: ${escapeHtml(config.model_name || config.internal_model_key)} · 
                        角色: ${escapeHtml(config.persona_name || '默认')} · 
                        ${config.use_system_prompt ? '启用系统提示词' : '关闭系统提示词'}
                    </div>
                </div>
            </div>
        `).join('');
    } catch (e) {
        showToast('加载失败: ' + e.message, 'error');
    }
}

function showAddOpenAPIConfig() {
    currentOpenAPIConfigEdit = null;
    document.getElementById('openAPIDetailTitle').textContent = '添加OpenAPI配置';
    document.getElementById('openAPIModelIdInput').value = '';
    document.getElementById('openAPIModelIdInput').disabled = false;
    
    // 重置自定义 select
    document.getElementById('openAPIInternalModelValue').value = '';
    document.querySelector('#openAPIInternalModelSelect .custom-select-text').textContent = '选择内部模型';
    document.getElementById('openAPIPersonaValue').value = '';
    document.querySelector('#openAPIPersonaSelect .custom-select-text').textContent = '默认（无角色卡）';
    document.getElementById('openAPIUseSystemPrompt').checked = false;

    // 加载模型和角色卡列表
    loadOpenAPIModelOptions();
    loadOpenAPIPersonaOptions();

    openSettingsDetail('detail-openapi');
}

async function loadOpenAPIModelOptions() {
    try {
        const r = await fetch('/nex/models');
        const d = await r.json();
        const menu = document.getElementById('openAPIInternalModelMenu');

        const chatModels = d.data.models.filter(m => m.model_type !== 'embedding');
        menu.innerHTML = chatModels.map(m => `
            <div class="custom-select-item" onclick="selectCustomOption('openAPIInternalModelSelect', '${m.key}', '${escapeHtml(m.name)} (${escapeHtml(m.provider_name)})', event)">
                ${escapeHtml(m.name)} <span style="color:var(--text2);font-size:0.85rem;">(${escapeHtml(m.provider_name)})</span>
            </div>
        `).join('');
    } catch (e) {
        console.error('加载模型失败', e);
    }
}

async function loadOpenAPIPersonaOptions() {
    try {
        const r = await fetch('/nex/personas');
        const d = await r.json();
        const menu = document.getElementById('openAPIPersonaMenu');

        menu.innerHTML = `
            <div class="custom-select-item" onclick="selectCustomOption('openAPIPersonaSelect', '', '默认（无角色卡）', event)">
                默认（无角色卡）
            </div>
        ` + (d.data || []).map(p => `
            <div class="custom-select-item" onclick="selectCustomOption('openAPIPersonaSelect', '${p.id}', '${escapeHtml(p.name)}', event)">
                ${escapeHtml(p.name)}
            </div>
        `).join('');
    } catch (e) {
        console.error('加载角色卡失败', e);
    }
}

async function selectOpenAPIConfig(apiModelId) {
    currentOpenAPIConfigEdit = apiModelId;

    try {
        const r = await fetch('/nex/openapi/configs');
        const d = await r.json();
        const config = d.data.find(c => c.api_model_id === apiModelId);

        if (!config) {
            showAlert('配置不存在', '错误', 'error');
            return;
        }

        document.getElementById('openAPIDetailTitle').textContent = '编辑OpenAPI配置';
        document.getElementById('openAPIModelIdInput').value = config.api_model_id;
        document.getElementById('openAPIModelIdInput').disabled = true;
        
        // 设置自定义 select 的值
        document.getElementById('openAPIInternalModelValue').value = config.internal_model_key || '';
        document.getElementById('openAPIPersonaValue').value = config.persona_id || '';
        document.getElementById('openAPIUseSystemPrompt').checked = config.use_system_prompt === 1;

        // 加载选项
        await loadOpenAPIModelOptions();
        await loadOpenAPIPersonaOptions();

        // 设置显示文本
        if (config.internal_model_key) {
            const modelText = document.querySelector(`#openAPIInternalModelMenu .custom-select-item[onclick*="${config.internal_model_key}"]`)?.textContent?.trim();
            if (modelText) {
                document.querySelector('#openAPIInternalModelSelect .custom-select-text').textContent = modelText;
            }
        }
        if (config.persona_id) {
            const personaText = document.querySelector(`#openAPIPersonaMenu .custom-select-item[onclick*="${config.persona_id}"]`)?.textContent?.trim();
            if (personaText) {
                document.querySelector('#openAPIPersonaSelect .custom-select-text').textContent = personaText;
            }
        }

        openSettingsDetail('detail-openapi');
        await loadOpenAPIConfigsPanel();
    } catch (e) {
        showToast('加载失败: ' + e.message, 'error');
    }
}

async function saveOpenAPIConfig() {
    const apiModelId = document.getElementById('openAPIModelIdInput').value.trim();
    const internalModelKey = document.getElementById('openAPIInternalModelValue').value;
    const personaId = document.getElementById('openAPIPersonaValue').value;
    const useSystemPrompt = document.getElementById('openAPIUseSystemPrompt').checked;

    if (!apiModelId || !internalModelKey) {
        showAlert('请填写API模型ID和选择内部模型', '提示', 'warning');
        return;
    }

    try {
        if (currentOpenAPIConfigEdit) {
            // 更新
            await fetch(`/nex/openapi/configs/${currentOpenAPIConfigEdit}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    internal_model_key: internalModelKey,
                    persona_id: personaId ? parseInt(personaId) : null,
                    use_system_prompt: useSystemPrompt
                })
            });
        } else {
            // 创建
            await fetch('/nex/openapi/configs', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    api_model_id: apiModelId,
                    internal_model_key: internalModelKey,
                    persona_id: personaId ? parseInt(personaId) : null,
                    use_system_prompt: useSystemPrompt
                })
            });
        }

        showToast('保存成功', 'success');
        closeOpenAPIDetail();
    } catch (e) {
        showToast('保存失败: ' + e.message, 'error');
    }
}

function closeOpenAPIDetail() {
    closeSettingsDetail('detail-openapi', () => {
        currentOpenAPIConfigEdit = null;
        loadOpenAPIConfigsPanel();
    });
}

async function deleteOpenAPIConfig() {
    if (!currentOpenAPIConfigEdit) return;

    showDeleteConfirm('删除配置', '确定要删除这个OpenAPI配置吗？', async function () {
        try {
            await fetch(`/nex/openapi/configs/${currentOpenAPIConfigEdit}`, { method: 'DELETE' });
            showToast('删除成功', 'success');
            closeOpenAPIDetail();
        } catch (e) {
            showToast('删除失败: ' + e.message, 'error');
        }
    });
}


// ========== 语音识别(STT)设置 ==========
let voiceMediaRecorder = null;
let voiceAudioChunks = [];
let voiceRecording = false;
let voiceHoldTimer = null;
let isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

async function loadSTTPanel() {
    // 加载已保存的STT配置
    try {
        const r = await fetch('/nex/stt/config');
        const d = await r.json();
        if (d.data) {
            document.getElementById('sttBaseUrl').value = d.data.base_url || '';
            document.getElementById('sttApiKey').value = '';
            document.getElementById('sttApiKey').placeholder = d.data.api_key_masked || 'sk-...';
            document.getElementById('sttModel').value = d.data.model || '';
        }
    } catch (e) { }
}

async function toggleSTTImportDropdown(event) {
    event.stopPropagation();
    const dropdown = document.getElementById('sttImportDropdown');
    const menu = document.getElementById('sttImportMenu');
    const isOpen = dropdown.classList.contains('open');
    
    // 关闭所有下拉菜单
    document.querySelectorAll('.toolbar-dropdown').forEach(d => d.classList.remove('open'));
    document.querySelectorAll('.toolbar-dropdown-menu').forEach(m => m.classList.remove('show'));
    
    if (!isOpen) {
        // 加载供应商列表
        try {
            const r = await fetch('/nex/providers');
            const d = await r.json();
            if (d.data.length === 0) {
                menu.innerHTML = '<div style="padding:12px;color:var(--text2);font-size:0.85rem;">暂无供应商</div>';
            } else {
                menu.innerHTML = d.data.map(p => `
                    <div class="toolbar-dropdown-item" onclick="importProviderToSTT('${p.id}')">
                        <i class="ri-server-line"></i>${escapeHtml(p.name)}
                    </div>
                `).join('');
            }
        } catch (e) {
            menu.innerHTML = '<div style="padding:12px;color:var(--error);font-size:0.85rem;">加载失败</div>';
        }
        dropdown.classList.add('open');
        menu.classList.add('show');
    }
}

// 点击其他地方关闭 STT 导入下拉菜单
document.addEventListener('click', function(e) {
    const dropdown = document.getElementById('sttImportDropdown');
    if (dropdown && !dropdown.contains(e.target)) {
        dropdown.classList.remove('open');
        document.getElementById('sttImportMenu')?.classList.remove('show');
    }
});

async function importProviderToSTT(providerId) {
    // 关闭下拉菜单
    document.getElementById('sttImportDropdown').classList.remove('open');
    document.getElementById('sttImportMenu').classList.remove('show');
    
    try {
        // 使用专门的导入接口，会返回完整的 API Key
        const r = await fetch(`/nex/stt/import/${providerId}`, { method: 'POST' });
        const d = await r.json();
        if (d.code === 0 && d.data) {
            document.getElementById('sttBaseUrl').value = d.data.base_url || '';
            document.getElementById('sttApiKey').value = '';
            document.getElementById('sttApiKey').placeholder = d.data.api_key_masked || '已导入';
            showToast('已导入供应商配置', 'success');
        } else {
            showToast('导入失败: ' + (d.message || '未知错误'), 'error');
        }
    } catch (e) {
        showToast('导入失败: ' + e.message, 'error');
    }
}

async function saveSTTSettings() {
    const baseUrl = document.getElementById('sttBaseUrl').value.trim();
    const apiKey = document.getElementById('sttApiKey').value.trim();
    const model = document.getElementById('sttModel').value.trim();
    
    if (!baseUrl) {
        showToast('请填写 Base URL', 'warning');
        return;
    }
    if (!model) {
        showToast('请填写模型名称', 'warning');
        return;
    }
    
    try {
        const body = { base_url: baseUrl, model: model };
        if (apiKey) body.api_key = apiKey;
        
        const r = await fetch('/nex/stt/config', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body)
        });
        const d = await r.json();
        if (d.code === 0) {
            showToast('保存成功', 'success');
            loadSTTPanel();
        } else {
            showToast('保存失败: ' + d.message, 'error');
        }
    } catch (e) {
        showToast('保存失败: ' + e.message, 'error');
    }
}

async function clearSTTConfig() {
    showDeleteConfirm('清空STT配置', '确定要清空语音识别配置吗？清空后将无法使用语音输入功能。', async () => {
        try {
            const r = await fetch('/nex/stt/config', { method: 'DELETE' });
            const d = await r.json();
            if (d.code === 0) {
                showToast('配置已清空', 'success');
                // 清空表单
                document.getElementById('sttBaseUrl').value = '';
                document.getElementById('sttApiKey').value = '';
                document.getElementById('sttApiKey').placeholder = 'sk-...';
                document.getElementById('sttModel').value = '';
            } else {
                showToast('清空失败: ' + d.message, 'error');
            }
        } catch (e) {
            showToast('清空失败: ' + e.message, 'error');
        }
    });
}
let sttMediaRecorder = null;
let sttAudioChunks = [];

async function testSTT() {
    const btn = document.getElementById('sttTestBtn');
    const status = document.getElementById('sttTestStatus');
    
    if (sttMediaRecorder && sttMediaRecorder.state === 'recording') {
        // 停止录音
        sttMediaRecorder.stop();
        btn.innerHTML = '<i class="ri-mic-line"></i><span>录音测试</span>';
        status.textContent = '处理中...';
        return;
    }
    
    // 开始录音
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        
        // 尝试使用更兼容的格式
        let mimeType = 'audio/webm';
        if (MediaRecorder.isTypeSupported('audio/mp4')) {
            mimeType = 'audio/mp4';
        } else if (MediaRecorder.isTypeSupported('audio/ogg')) {
            mimeType = 'audio/ogg';
        } else if (MediaRecorder.isTypeSupported('audio/wav')) {
            mimeType = 'audio/wav';
        }
        
        sttMediaRecorder = new MediaRecorder(stream, { mimeType });
        sttAudioChunks = [];
        
        sttMediaRecorder.ondataavailable = (e) => {
            sttAudioChunks.push(e.data);
        };
        
        sttMediaRecorder.onstop = async () => {
            stream.getTracks().forEach(track => track.stop());
            
            // 根据 mimeType 确定文件扩展名
            let ext = 'webm';
            if (mimeType.includes('mp4')) ext = 'm4a';
            else if (mimeType.includes('ogg')) ext = 'ogg';
            else if (mimeType.includes('wav')) ext = 'wav';
            
            const audioBlob = new Blob(sttAudioChunks, { type: mimeType });
            
            // 发送到后端进行识别
            const formData = new FormData();
            formData.append('file', audioBlob, `recording.${ext}`);
            
            try {
                const r = await fetch('/nex/stt/transcribe', {
                    method: 'POST',
                    body: formData
                });
                const d = await r.json();
                if (d.code === 0) {
                    status.textContent = '识别结果: ' + (d.data.text || '(空)');
                    showToast('识别成功', 'success');
                } else {
                    status.textContent = '识别失败: ' + d.message;
                    showToast('识别失败: ' + d.message, 'error');
                }
            } catch (e) {
                status.textContent = '识别失败: ' + e.message;
                showToast('识别失败: ' + e.message, 'error');
            }
        };
        
        sttMediaRecorder.start();
        btn.innerHTML = '<i class="ri-stop-circle-line"></i><span>停止录音</span>';
        status.textContent = '录音中...';
    } catch (e) {
        showToast('无法访问麦克风: ' + e.message, 'error');
        status.textContent = '无法访问麦克风';
    }
}


// ========== 语音合成(TTS)设置 ==========
async function loadTTSPanel() {
    // 加载已保存的TTS配置
    try {
        const r = await fetch('/nex/tts/config');
        const d = await r.json();
        if (d.data) {
            document.getElementById('ttsBaseUrl').value = d.data.base_url || '';
            document.getElementById('ttsApiKey').value = '';
            document.getElementById('ttsApiKey').placeholder = d.data.api_key_masked || 'sk-...';
            document.getElementById('ttsModel').value = d.data.model || '';
            document.getElementById('ttsVoice').value = d.data.voice || '';
        }
    } catch (e) { }
    
    // 加载朗读模式（从 localStorage）
    const savedMode = localStorage.getItem('nex_tts_mode') || 'continuous';
    window.ttsMode = savedMode;
    updateTTSModeUI();
}

function setTTSMode(mode) {
    window.ttsMode = mode;
    localStorage.setItem('nex_tts_mode', mode);
    updateTTSModeUI();
}

function updateTTSModeUI() {
    document.getElementById('ttsModeContinuous')?.classList.toggle('active', window.ttsMode === 'continuous');
    document.getElementById('ttsModeSentence')?.classList.toggle('active', window.ttsMode === 'sentence');
}

async function toggleTTSImportDropdown(event) {
    event.stopPropagation();
    const dropdown = document.getElementById('ttsImportDropdown');
    const menu = document.getElementById('ttsImportMenu');
    const isOpen = dropdown.classList.contains('open');
    
    // 关闭所有下拉菜单
    document.querySelectorAll('.toolbar-dropdown').forEach(d => d.classList.remove('open'));
    document.querySelectorAll('.toolbar-dropdown-menu').forEach(m => m.classList.remove('show'));
    
    if (!isOpen) {
        // 加载供应商列表
        try {
            const r = await fetch('/nex/providers');
            const d = await r.json();
            if (d.data.length === 0) {
                menu.innerHTML = '<div style="padding:12px;color:var(--text2);font-size:0.85rem;">暂无供应商</div>';
            } else {
                menu.innerHTML = d.data.map(p => `
                    <div class="toolbar-dropdown-item" onclick="importProviderToTTS('${p.id}')">
                        <i class="ri-server-line"></i>${escapeHtml(p.name)}
                    </div>
                `).join('');
            }
        } catch (e) {
            menu.innerHTML = '<div style="padding:12px;color:var(--error);font-size:0.85rem;">加载失败</div>';
        }
        dropdown.classList.add('open');
        menu.classList.add('show');
    }
}

// 点击其他地方关闭 TTS 导入下拉菜单
document.addEventListener('click', function(e) {
    const dropdown = document.getElementById('ttsImportDropdown');
    if (dropdown && !dropdown.contains(e.target)) {
        dropdown.classList.remove('open');
        document.getElementById('ttsImportMenu')?.classList.remove('show');
    }
});

async function importProviderToTTS(providerId) {
    // 关闭下拉菜单
    document.getElementById('ttsImportDropdown').classList.remove('open');
    document.getElementById('ttsImportMenu').classList.remove('show');
    
    try {
        // 使用专门的导入接口，会返回完整的 API Key
        const r = await fetch(`/nex/tts/import/${providerId}`, { method: 'POST' });
        const d = await r.json();
        if (d.code === 0 && d.data) {
            document.getElementById('ttsBaseUrl').value = d.data.base_url || '';
            document.getElementById('ttsApiKey').value = '';
            document.getElementById('ttsApiKey').placeholder = d.data.api_key_masked || '已导入';
            showToast('已导入供应商配置', 'success');
        } else {
            showToast('导入失败: ' + (d.message || '未知错误'), 'error');
        }
    } catch (e) {
        showToast('导入失败: ' + e.message, 'error');
    }
}

async function saveTTSSettings() {
    const baseUrl = document.getElementById('ttsBaseUrl').value.trim();
    const apiKey = document.getElementById('ttsApiKey').value.trim();
    const model = document.getElementById('ttsModel').value.trim();
    const voice = document.getElementById('ttsVoice').value.trim();
    
    if (!baseUrl) {
        showToast('请填写 Base URL', 'warning');
        return;
    }
    if (!model) {
        showToast('请填写模型名称', 'warning');
        return;
    }
    if (!voice) {
        showToast('请填写语音', 'warning');
        return;
    }
    
    try {
        const body = { base_url: baseUrl, model: model, voice: voice };
        if (apiKey) body.api_key = apiKey;
        
        const r = await fetch('/nex/tts/config', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body)
        });
        const d = await r.json();
        if (d.code === 0) {
            showToast('保存成功', 'success');
            await loadTTSPanel();
            // 重新初始化 TTS 状态
            await initTTSAutoPlay();
            // 重新渲染消息以显示朗读按钮
            if (currentSessionId) {
                await loadMessages(currentSessionId);
            }
        } else {
            showToast('保存失败: ' + d.message, 'error');
        }
    } catch (e) {
        showToast('保存失败: ' + e.message, 'error');
    }
}

async function clearTTSConfig() {
    showDeleteConfirm('清空TTS配置', '确定要清空语音合成配置吗？清空后将无法使用语音朗读功能。', async () => {
        try {
            const r = await fetch('/nex/tts/config', { method: 'DELETE' });
            const d = await r.json();
            if (d.code === 0) {
                showToast('配置已清空', 'success');
                // 清空表单
                document.getElementById('ttsBaseUrl').value = '';
                document.getElementById('ttsApiKey').value = '';
                document.getElementById('ttsApiKey').placeholder = 'sk-...';
                document.getElementById('ttsModel').value = '';
                document.getElementById('ttsVoice').value = '';
                // 重新初始化 TTS 状态
                await initTTSAutoPlay();
                // 重新渲染消息以隐藏朗读按钮
                if (currentSessionId) {
                    await loadMessages(currentSessionId);
                }
            } else {
                showToast('清空失败: ' + d.message, 'error');
            }
        } catch (e) {
            showToast('清空失败: ' + e.message, 'error');
        }
    });
}

// ========== 流式语音管理器 ==========
class StreamTTSManager {
    constructor() {
        this.buffer = '';           // 文本缓冲区
        this.audioQueue = [];       // 音频 URL 队列
        this.isPlaying = false;     // 是否正在播放
        this.currentAudio = null;   // 当前播放的 Audio 对象
        this.isStopped = false;     // 是否已停止
        this.pendingRequests = 0;   // 当前正在进行的请求数
        this.maxConcurrent = 1;     // 最大并发请求数改为1（串行模式，避免占用连接）
        this.requestQueue = [];     // 待发送的请求队列
        this.checkTimer = null;     // 定时检查器
        this.canSendRequests = false; // 是否可以发送请求（延迟启动）
        this.hasError = false;      // 是否发生过错误
        this.errorCount = 0;        // 错误计数
        this.maxErrors = 3;         // 最大错误次数，超过后停止尝试
    }
    
    // 开始新会话
    start() {
        this.buffer = '';
        this.audioQueue = [];
        this.isPlaying = false;
        this.isStopped = false;
        this.pendingRequests = 0;
        this.requestQueue = [];
        this.canSendRequests = false; // 初始不允许发送请求
        this.hasError = false;
        this.errorCount = 0;
        
        if (this.currentAudio) {
            this.currentAudio.pause();
            this.currentAudio = null;
        }
        
        // 完全移除定时器！不在流式输出过程中处理 TTS
        // 只在 finish() 时一次性处理所有文本
        if (this.checkTimer) {
            clearInterval(this.checkTimer);
            this.checkTimer = null;
        }
    }
    
    // 停止所有播放并清空队列
    stop() {
        this.isStopped = true;
        this.buffer = '';
        this.requestQueue = [];
        
        // 停止定时检查器
        if (this.checkTimer) {
            clearInterval(this.checkTimer);
            this.checkTimer = null;
        }
        
        // 停止当前播放
        if (this.currentAudio) {
            this.currentAudio.pause();
            this.currentAudio = null;
        }
        
        // 清空队列并释放资源
        this.audioQueue.forEach(url => URL.revokeObjectURL(url));
        this.audioQueue = [];
        this.isPlaying = false;
    }
    
    // 添加文本到缓冲区（只添加，不做其他操作）
    appendText(chunk) {
        if (this.isStopped) return;
        this.buffer += chunk;
        // 不调用 _detectSentence，让定时器处理
    }
    
    // 检测句子结束（由定时器调用，不递归）
    _detectSentence() {
        if (this.isStopped) return;
        
        // 只在句号、问号、感叹号处分句，不在逗号处分句
        const sentenceEnders = /[。！？\.!?\n]/;
        
        let match = this.buffer.match(sentenceEnders);
        if (match) {
            const endIndex = match.index + 1;
            const sentence = this.buffer.substring(0, endIndex).trim();
            this.buffer = this.buffer.substring(endIndex);
            
            if (sentence) {
                this._synthesizeAndPlay(sentence);
            }
            
            // 不递归，让定时器在下一次检查时处理剩余文本
            return;
        }
        
        // 如果缓冲区太长（超过200个字符），强制分割
        if (this.buffer.length > 200) {
            // 尝试在逗号处分割
            const commaPattern = /[，,]/;
            match = this.buffer.match(commaPattern);
            if (match) {
                const endIndex = match.index + 1;
                const sentence = this.buffer.substring(0, endIndex).trim();
                this.buffer = this.buffer.substring(endIndex);
                
                if (sentence) {
                    this._synthesizeAndPlay(sentence);
                }
                
                // 不递归，让定时器在下一次检查时处理剩余文本
            }
        }
    }
    
    // 合成语音并加入播放队列（带并发控制）
    _synthesizeAndPlay(text) {
        if (this.isStopped) return;
        
        // 将请求加入队列
        this.requestQueue.push(text);
        
        // 尝试处理队列
        this._processQueue();
    }
    
    // 处理请求队列（控制并发数）
    _processQueue() {
        if (this.isStopped) return;
        
        // 如果错误次数过多，停止处理
        if (this.errorCount >= this.maxErrors) {
            console.warn('TTS: 错误次数过多，停止合成');
            return;
        }
        
        // 如果还不允许发送请求，直接返回
        if (!this.canSendRequests) {
            return;
        }
        
        // 如果已达到最大并发数，等待
        if (this.pendingRequests >= this.maxConcurrent) {
            return;
        }
        
        // 如果队列为空，返回
        if (this.requestQueue.length === 0) {
            return;
        }
        
        // 取出一个请求
        const text = this.requestQueue.shift();
        this.pendingRequests++;
        
        // 立即返回，在下一个事件循环中执行
        setTimeout(() => {
            if (this.isStopped) {
                this.pendingRequests--;
                return;
            }
            
            // 发送请求
            fetch('/nex/tts/synthesize', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ text: text })
            })
            .then(r => {
                if (r.ok && !this.isStopped) {
                    return r.blob();
                }
                throw new Error('TTS request failed');
            })
            .then(audioBlob => {
                if (!this.isStopped) {
                    const audioUrl = URL.createObjectURL(audioBlob);
                    this.audioQueue.push(audioUrl);
                    
                    // 如果当前没有播放，开始播放
                    if (!this.isPlaying) {
                        this._playNext();
                    }
                }
            })
            .catch(e => {
                // 静默失败，不影响文本显示
                console.error('TTS synthesis error:', e);
                this.errorCount++;
                
                // 第2次错误时显示提示
                if (this.errorCount === 2) {
                    showToast('语音合成失败，将继续尝试', 'warning', 2000);
                } else if (this.errorCount >= this.maxErrors) {
                    showToast('语音合成多次失败，已停止', 'error', 3000);
                }
            })
            .finally(() => {
                // 请求完成，减少计数
                this.pendingRequests--;
                // 继续处理队列
                this._processQueue();
            });
        }, 0);
    }
    
    // 播放队列中的下一个音频
    _playNext() {
        if (this.isStopped || this.audioQueue.length === 0) {
            this.isPlaying = false;
            return;
        }
        
        this.isPlaying = true;
        const audioUrl = this.audioQueue.shift();
        const audio = new Audio(audioUrl);
        this.currentAudio = audio;
        
        audio.onended = () => {
            URL.revokeObjectURL(audioUrl);
            this.currentAudio = null;
            
            if (!this.isStopped) {
                this._playNext();
            }
        };
        
        audio.onerror = (e) => {
            console.error('Audio playback error:', e);
            URL.revokeObjectURL(audioUrl);
            this.currentAudio = null;
            
            // 跳过这个音频，播放下一个
            if (!this.isStopped) {
                this._playNext();
            }
        };
        
        audio.play().catch(e => {
            console.error('Audio play error:', e);
            URL.revokeObjectURL(audioUrl);
            this.currentAudio = null;
            
            // 跳过这个音频，播放下一个
            if (!this.isStopped) {
                this._playNext();
            }
        });
    }
    
    // 完成流式输出，处理所有文本并开始 TTS
    finish() {
        if (this.isStopped) return;
        
        // 允许发送请求
        this.canSendRequests = true;
        
        // 使用 setTimeout 确保异步处理，不阻塞 UI
        setTimeout(() => {
            if (this.isStopped) return;
            
            // 根据模式选择处理方式
            if (window.ttsMode === 'continuous') {
                // 连续模式：一次性合成全部文本
                this._processContinuous();
            } else {
                // 分句模式：按句子分开合成
                this._processSentences();
            }
        }, 0);
    }
    
    // 连续模式：一次性合成全部文本
    _processContinuous() {
        if (this.isStopped || !this.buffer) return;
        
        const text = this.buffer.trim();
        this.buffer = '';
        
        if (text) {
            this.requestQueue.push(text);
            this._processQueue();
        }
    }
    
    // 分句模式：按句子分开合成
    _processSentences() {
        if (this.isStopped || !this.buffer) return;
        
        const text = this.buffer;
        this.buffer = '';
        
        // 按句子分割
        const sentenceEnders = /[。！？\.!?\n]+/;
        const sentences = text.split(sentenceEnders).filter(s => s.trim());
        
        // 将所有句子加入队列
        sentences.forEach(sentence => {
            if (sentence.trim()) {
                this.requestQueue.push(sentence.trim());
            }
        });
        
        // 开始处理队列
        this._processQueue();
    }
}

async function testTTS() {
    const btn = document.getElementById('ttsTestBtn');
    const status = document.getElementById('ttsTestStatus');
    const text = document.getElementById('ttsTestText').value.trim();
    
    if (!text) {
        showToast('请输入要合成的文本', 'warning');
        return;
    }
    
    btn.disabled = true;
    status.textContent = '合成中...';
    
    try {
        const r = await fetch('/nex/tts/synthesize', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: text })
        });
        
        if (r.ok) {
            const contentType = r.headers.get('content-type');
            console.log('Content-Type:', contentType);
            
            const audioBlob = await r.blob();
            console.log('Blob size:', audioBlob.size, 'type:', audioBlob.type);
            
            // 检查前几个字节来判断文件格式
            const arrayBuffer = await audioBlob.arrayBuffer();
            const uint8Array = new Uint8Array(arrayBuffer);
            const header = Array.from(uint8Array.slice(0, 12)).map(b => b.toString(16).padStart(2, '0')).join(' ');
            console.log('File header (hex):', header);
            
            // 尝试多种 MIME 类型
            const mimeTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/webm'];
            let played = false;
            
            for (const mimeType of mimeTypes) {
                if (played) break;
                
                console.log('Trying MIME type:', mimeType);
                const testBlob = new Blob([audioBlob], { type: mimeType });
                const audioUrl = URL.createObjectURL(testBlob);
                const audio = new Audio();
                
                await new Promise((resolve) => {
                    audio.onloadedmetadata = () => {
                        console.log('Success with', mimeType, 'duration:', audio.duration);
                        played = true;
                        
                        audio.onended = () => {
                            URL.revokeObjectURL(audioUrl);
                            status.textContent = '播放完成';
                        };
                        
                        audio.play().then(() => {
                            status.textContent = '播放中...';
                            showToast('合成成功', 'success');
                        }).catch(e => {
                            console.error('Play error:', e);
                        });
                        
                        resolve();
                    };
                    
                    audio.onerror = (e) => {
                        console.log('Failed with', mimeType);
                        URL.revokeObjectURL(audioUrl);
                        resolve();
                    };
                    
                    audio.src = audioUrl;
                    
                    // 超时处理
                    setTimeout(() => {
                        if (!played) {
                            URL.revokeObjectURL(audioUrl);
                            resolve();
                        }
                    }, 1000);
                });
            }
            
            if (!played) {
                status.textContent = '播放失败：不支持的音频格式';
                showToast('音频格式不支持', 'error');
            }
        } else {
            const d = await r.json();
            status.textContent = '合成失败: ' + (d.message || '未知错误');
            showToast('合成失败: ' + (d.message || '未知错误'), 'error');
        }
    } catch (e) {
        console.error('TTS error:', e);
        status.textContent = '合成失败: ' + e.message;
        showToast('合成失败: ' + e.message, 'error');
    } finally {
        btn.disabled = false;
    }
}


// ========== 首页语音输入 ==========
function toggleVoiceInput() {
    // 电脑端：点击切换录音状态
    if (!isMobileDevice) {
        if (voiceRecording) {
            stopVoiceRecording();
        } else {
            startVoiceRecording();
        }
    }
}

function startVoiceHold(event) {
    // 手机端：长按开始录音
    if (isMobileDevice) {
        event.preventDefault();
        voiceHoldTimer = setTimeout(() => {
            startVoiceRecording();
        }, 200); // 200ms 后开始录音，避免误触
    }
}

function stopVoiceHold(event) {
    // 手机端：松开停止录音
    if (isMobileDevice) {
        if (voiceHoldTimer) {
            clearTimeout(voiceHoldTimer);
            voiceHoldTimer = null;
        }
        if (voiceRecording) {
            stopVoiceRecording();
        }
    }
}

async function startVoiceRecording() {
    const btn = document.getElementById('voiceBtn');
    
    // 检查浏览器是否支持
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        showToast('您的浏览器不支持语音输入功能，或需要使用 HTTPS 访问', 'error');
        return;
    }
    
    // 检查是否是 HTTPS（localhost 除外）
    if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
        showToast('语音输入需要 HTTPS 连接（或使用 localhost 访问）', 'warning');
        return;
    }
    
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        
        // 尝试使用更兼容的格式
        let mimeType = 'audio/webm';
        if (MediaRecorder.isTypeSupported('audio/mp4')) {
            mimeType = 'audio/mp4';
        } else if (MediaRecorder.isTypeSupported('audio/ogg')) {
            mimeType = 'audio/ogg';
        }
        
        voiceMediaRecorder = new MediaRecorder(stream, { mimeType });
        voiceAudioChunks = [];
        
        voiceMediaRecorder.ondataavailable = (e) => {
            voiceAudioChunks.push(e.data);
        };
        
        voiceMediaRecorder.onstop = async () => {
            stream.getTracks().forEach(track => track.stop());
            
            // 根据 mimeType 确定文件扩展名
            let ext = 'webm';
            if (mimeType.includes('mp4')) ext = 'm4a';
            else if (mimeType.includes('ogg')) ext = 'ogg';
            
            const audioBlob = new Blob(voiceAudioChunks, { type: mimeType });
            
            // 显示处理中状态
            btn.innerHTML = '<i class="ri-loader-4-line"></i>';
            btn.classList.remove('recording');
            
            // 发送到后端进行识别
            const formData = new FormData();
            formData.append('file', audioBlob, `recording.${ext}`);
            
            try {
                const r = await fetch('/nex/stt/transcribe', {
                    method: 'POST',
                    body: formData
                });
                const d = await r.json();
                if (d.code === 0 && d.data.text) {
                    // 将识别结果填入输入框
                    const inp = document.getElementById('inp');
                    const currentText = inp.value;
                    // 如果当前有内容，直接拼接；如果是空白，就替换
                    if (currentText.trim()) {
                        inp.value = currentText + d.data.text;
                    } else {
                        inp.value = d.data.text;
                    }
                    inp.focus();
                    // 触发 input 事件以调整高度
                    inp.dispatchEvent(new Event('input'));
                    showToast('语音识别成功', 'success');
                } else {
                    showToast('识别失败: ' + (d.message || '无结果'), 'error');
                }
            } catch (e) {
                showToast('识别失败: ' + e.message, 'error');
            } finally {
                // 恢复语音按钮状态
                btn.innerHTML = '<i class="ri-voice-ai-line"></i>';
                voiceRecording = false;
            }
        };
        
        voiceMediaRecorder.start();
        voiceRecording = true;
        btn.classList.add('recording');
        btn.innerHTML = '<i class="ri-stop-circle-line"></i>';
        
        if (isMobileDevice) {
            showToast('录音中...松开停止', 'info', 1500);
        }
    } catch (e) {
        let errorMsg = '无法访问麦克风';
        if (e.name === 'NotAllowedError' || e.name === 'PermissionDeniedError') {
            errorMsg = '麦克风权限被拒绝，请在浏览器设置中允许访问';
        } else if (e.name === 'NotFoundError' || e.name === 'DevicesNotFoundError') {
            errorMsg = '未找到麦克风设备';
        } else if (e.name === 'NotReadableError' || e.name === 'TrackStartError') {
            errorMsg = '麦克风被其他应用占用';
        } else if (e.message) {
            errorMsg = '无法访问麦克风: ' + e.message;
        }
        showToast(errorMsg, 'error');
    }
}

function stopVoiceRecording() {
    if (voiceMediaRecorder && voiceMediaRecorder.state === 'recording') {
        voiceMediaRecorder.stop();
    }
}


// ========== 插件系统 ==========
async function loadPluginsPanel() {
    const panel = document.getElementById('pluginsListPanel');
    panel.innerHTML = '<div style="padding:16px;text-align:center;color:var(--text2);">加载中...</div>';
    
    try {
        const r = await fetch('/nex/plugins');
        const d = await r.json();
        
        if (d.code !== 0) {
            panel.innerHTML = `<div style="padding:16px;text-align:center;color:var(--error);">${d.message}</div>`;
            return;
        }
        
        const plugins = d.data || [];
        
        // 检查工具调用是否启用
        const toolsR = await fetch('/nex/tools');
        const toolsD = await toolsR.json();
        const toolsEnabled = toolsD.enabled !== false;
        
        let html = '';
        
        // 如果工具调用被关闭，显示提示
        if (!toolsEnabled) {
            html += `
                <div style="padding:12px;margin:16px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text2);font-size:0.9rem;">
                    <i class="ri-information-line" style="color:var(--warning);"></i>
                    工具调用已关闭，插件工具将不可用
                </div>
            `;
        }
        
        if (plugins.length === 0) {
            panel.innerHTML = html + `
                <div style="padding:40px 20px;text-align:center;">
                    <i class="ri-puzzle-line" style="font-size:3rem;color:var(--text2);opacity:0.3;"></i>
                    <div style="margin-top:12px;color:var(--text2);">暂无插件</div>
                    <div style="margin-top:8px;font-size:0.85rem;color:var(--text2);">
                        在 plugins/ 目录下创建插件文件夹，<a href="https://gitee.com/candy_xt/NexAgent/blob/master/PLUGIN_EXAMPLE.md" target="_blank" style="color:var(--accent);">查看开发教程</a>
                    </div>
                </div>
            `;
            return;
        }
        
        html += '<div style="padding:16px;">';
        plugins.forEach(plugin => {
            const toolsCount = plugin.tools ? plugin.tools.length : 0;
            const isEnabled = plugin.enabled !== false;
            html += `
                <div class="plugin-item ${isEnabled ? '' : 'disabled'}" onclick="togglePluginDetail('${escapeHtml(plugin.id)}')">
                    <div class="plugin-item-header">
                        <div class="plugin-item-icon">
                            <i class="ri-puzzle-line"></i>
                        </div>
                        <div class="plugin-item-info">
                            <div class="plugin-item-name">${escapeHtml(plugin.name)}</div>
                            <div class="plugin-item-meta">${escapeHtml(plugin.id)} • ${toolsCount} 个工具</div>
                        </div>
                        <label class="toggle-switch" onclick="event.stopPropagation()">
                            <input type="checkbox" ${isEnabled ? 'checked' : ''} onchange="togglePluginEnabled('${escapeHtml(plugin.id)}', this.checked)">
                            <span class="toggle-slider"></span>
                        </label>
                        <i class="ri-arrow-down-s-line plugin-item-arrow"></i>
                    </div>
                    <div class="plugin-item-detail" id="plugin-detail-${escapeHtml(plugin.id)}">
                        <div class="plugin-detail-section">
                            <div class="plugin-detail-label">包名</div>
                            <div class="plugin-detail-value">${escapeHtml(plugin.id)}</div>
                        </div>
                        <div class="plugin-detail-section">
                            <div class="plugin-detail-label">描述</div>
                            <div class="plugin-detail-value">${escapeHtml(plugin.description || '无描述')}</div>
                        </div>
                        <div class="plugin-detail-section">
                            <div class="plugin-detail-label">集成工具</div>
                            <div class="plugin-detail-tools">
                                ${toolsCount > 0 ? plugin.tools.map(tool => `
                                    <div class="plugin-tool-item" onclick="event.stopPropagation();showPluginToolDetail('${escapeHtml(tool)}', '${escapeHtml(plugin.id)}')" style="cursor:pointer;display:flex;justify-content:space-between;align-items:center;">
                                        <div style="display:flex;align-items:center;gap:8px;">
                                            <i class="ri-tools-line"></i>
                                            <span>${escapeHtml(tool)}</span>
                                        </div>
                                        <i class="ri-arrow-right-s-line" style="color:var(--text2);"></i>
                                    </div>
                                `).join('') : '<div style="color:var(--text2);font-size:0.85rem;">无工具</div>'}
                            </div>
                        </div>
                    </div>
                </div>
            `;
        });
        html += '</div>';
        
        panel.innerHTML = html;
    } catch (e) {
        panel.innerHTML = `<div style="padding:16px;text-align:center;color:var(--error);">加载失败: ${e.message}</div>`;
    }
}

function togglePluginDetail(pluginId) {
    const detail = document.getElementById(`plugin-detail-${pluginId}`);
    const item = detail.closest('.plugin-item');
    
    if (detail.classList.contains('active')) {
        detail.classList.remove('active');
        item.classList.remove('expanded');
    } else {
        // 关闭其他展开的插件
        document.querySelectorAll('.plugin-item-detail.active').forEach(d => {
            d.classList.remove('active');
            d.closest('.plugin-item').classList.remove('expanded');
        });
        
        detail.classList.add('active');
        item.classList.add('expanded');
    }
}

async function reloadPlugins() {
    try {
        const r = await fetch('/nex/plugins/reload', { method: 'POST' });
        const d = await r.json();
        
        if (d.code === 0) {
            showToast('插件已重载', 'success');
            await loadPluginsPanel();
        } else {
            showToast('重载失败: ' + d.message, 'error');
        }
    } catch (e) {
        showToast('重载失败: ' + e.message, 'error');
    }
}

async function togglePluginEnabled(pluginId, enabled) {
    try {
        const r = await fetch(`/nex/plugins/${pluginId}/toggle?enabled=${enabled}`, { method: 'POST' });
        const d = await r.json();
        
        if (d.code === 0) {
            showToast(enabled ? '插件已启用' : '插件已禁用', 'success');
            await loadPluginsPanel();
        } else {
            showToast('操作失败: ' + d.message, 'error');
            await loadPluginsPanel();
        }
    } catch (e) {
        showToast('操作失败: ' + e.message, 'error');
        await loadPluginsPanel();
    }
}

async function showPluginToolDetail(toolName, pluginId) {
    try {
        // 获取所有工具信息
        const r = await fetch('/nex/tools');
        const d = await r.json();
        const tool = (d.data || []).find(t => t.name === toolName);
        
        if (!tool) {
            showToast('工具不存在', 'error');
            return;
        }
        
        // 构建参数列表HTML
        let paramsHtml = '';
        if (tool.parameters && tool.parameters.properties) {
            const props = tool.parameters.properties;
            const required = tool.parameters.required || [];
            
            paramsHtml = Object.keys(props).map(key => {
                const param = props[key];
                const isRequired = required.includes(key);
                return `
                    <div style="padding:12px;background:var(--bg);border-radius:var(--radius-sm);margin-bottom:8px;">
                        <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
                            <code style="background:var(--card);padding:2px 6px;border-radius:4px;font-size:0.85rem;">${escapeHtml(key)}</code>
                            ${isRequired ? '<span style="color:var(--error);font-size:0.75rem;">*必填</span>' : '<span style="color:var(--text3);font-size:0.75rem;">可选</span>'}
                            ${param.type ? `<span style="color:var(--text3);font-size:0.75rem;">${param.type}</span>` : ''}
                        </div>
                        <div style="font-size:0.85rem;color:var(--text2);">${escapeHtml(param.description || '无描述')}</div>
                        ${param.enum ? `<div style="font-size:0.75rem;color:var(--text3);margin-top:4px;">可选值: ${param.enum.join(', ')}</div>` : ''}
                    </div>
                `;
            }).join('');
        } else {
            paramsHtml = '<div style="padding:12px;text-align:center;color:var(--text2);font-size:0.85rem;">无参数</div>';
        }
        
        const modalHtml = `
            <div class="alert-modal show" id="pluginToolDetailModal" style="z-index:2000;">
                <div class="alert-content" style="min-width:500px;max-width:600px;">
                    <div class="alert-header">
                        <i class="ri-tools-line alert-icon info"></i>
                        <h4>插件工具详情</h4>
                    </div>
                    <div class="alert-body" style="text-align:left;max-height:500px;overflow-y:auto;">
                        <div style="margin-bottom:16px;">
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:6px;">工具名称</div>
                            <div style="font-size:1.1rem;font-weight:500;">${escapeHtml(tool.name)}</div>
                        </div>
                        
                        <div style="margin-bottom:16px;">
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:6px;">所属插件</div>
                            <div style="display:flex;gap:8px;align-items:center;">
                                <span style="padding:4px 12px;background:var(--accent-light);color:var(--accent);border-radius:12px;font-size:0.85rem;">${escapeHtml(pluginId)}</span>
                            </div>
                        </div>
                        
                        <div style="margin-bottom:16px;">
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:6px;">描述</div>
                            <div style="padding:12px;background:var(--bg);border-radius:var(--radius-sm);font-size:0.9rem;line-height:1.6;">
                                ${escapeHtml(tool.description || '无描述')}
                            </div>
                        </div>
                        
                        <div>
                            <div style="font-size:0.85rem;color:var(--text2);margin-bottom:8px;">参数列表</div>
                            ${paramsHtml}
                        </div>
                    </div>
                    <div class="alert-actions">
                        <button class="alert-btn-ok" onclick="closePluginToolDetail()">关闭</button>
                    </div>
                </div>
            </div>
        `;
        
        // 移除旧的弹窗
        const oldModal = document.getElementById('pluginToolDetailModal');
        if (oldModal) oldModal.remove();
        
        // 添加新弹窗
        document.body.insertAdjacentHTML('beforeend', modalHtml);
    } catch (e) {
        showToast('加载工具详情失败: ' + e.message, 'error');
    }
}

function closePluginToolDetail() {
    const modal = document.getElementById('pluginToolDetailModal');
    if (modal) modal.remove();
}
