// @ts-nocheck
// static/app-enhanced.js - 修复版，正确实现Token实时更新
import katex from 'katex';
import { mapActions, mapState, mapWritableState } from 'pinia';
import ChatArea from './components/chat/ChatArea.vue';
import ConversationSidebar from './components/sidebar/ConversationSidebar.vue';
import LeftPanel from './components/panels/LeftPanel.vue';
import FocusPanel from './components/panels/FocusPanel.vue';
import TokenDrawer from './components/token/TokenDrawer.vue';
import PersonalizationDrawer from './components/personalization/PersonalizationDrawer.vue';
import LiquidGlassWidget from './components/experiments/LiquidGlassWidget.vue';
import QuickMenu from './components/input/QuickMenu.vue';
import InputComposer from './components/input/InputComposer.vue';
import AppShell from './components/shell/AppShell.vue';
import ImagePicker from './components/overlay/ImagePicker.vue';
import ConversationReviewDialog from './components/overlay/ConversationReviewDialog.vue';
import { useUiStore } from './stores/ui';
import { useConversationStore } from './stores/conversation';
import { useChatStore } from './stores/chat';
import { useInputStore } from './stores/input';
import { useToolStore } from './stores/tool';
import { useResourceStore } from './stores/resource';
import { useUploadStore } from './stores/upload';
import { useFileStore } from './stores/file';
import { useSubAgentStore } from './stores/subAgent';
import { useFocusStore } from './stores/focus';
import { usePersonalizationStore } from './stores/personalization';
import { useModelStore } from './stores/model';
import { useChatActionStore } from './stores/chatActions';
import { useMonitorStore } from './stores/monitor';
import { usePolicyStore } from './stores/policy';
import { ICONS, TOOL_CATEGORY_ICON_MAP } from './utils/icons';
import { initializeLegacySocket } from './composables/useLegacySocket';
import { useConnectionStore } from './stores/connection';
import { useEasterEgg } from './composables/useEasterEgg';
import { renderMarkdown as renderMarkdownHelper } from './composables/useMarkdownRenderer';
import {
    formatTokenCount,
    formatBytes,
    formatPercentage,
    formatRate,
    formatResetTime,
    formatQuotaValue,
    quotaTypeLabel,
    buildQuotaResetSummary,
    isQuotaExceeded as isQuotaExceededUtil,
    buildQuotaToastMessage
} from './utils/formatters';
import {
    getToolIcon,
    getToolAnimationClass,
    getToolStatusText as baseGetToolStatusText,
    getToolDescription,
    cloneToolArguments,
    buildToolLabel,
    formatSearchTopic,
    formatSearchTime,
    getLanguageClass
} from './utils/chatDisplay';
import {
    scrollToBottom as scrollToBottomHelper,
    conditionalScrollToBottom as conditionalScrollToBottomHelper,
    toggleScrollLock as toggleScrollLockHelper,
    normalizeScrollLock,
    scrollThinkingToBottom as scrollThinkingToBottomHelper
} from './composables/useScrollControl';
import {
    startResize as startPanelResize,
    handleResize as handlePanelResize,
    stopResize as stopPanelResize
} from './composables/usePanelResize';

function normalizeShowImageSrc(src: string) {
    if (!src) return '';
    const trimmed = src.trim();
    if (/^https?:\/\//i.test(trimmed)) return trimmed;
    if (trimmed.startsWith('/user_upload/')) return trimmed;
    // 兼容容器内部路径：/workspace/.../user_upload/xxx.png 或 /workspace/user_upload/xxx
    const idx = trimmed.toLowerCase().indexOf('/user_upload/');
    if (idx >= 0) {
        return '/user_upload/' + trimmed.slice(idx + '/user_upload/'.length);
    }
    if (trimmed.startsWith('/') || trimmed.startsWith('./') || trimmed.startsWith('../')) {
        return trimmed;
    }
    return '';
}

function isSafeImageSrc(src: string) {
    return !!normalizeShowImageSrc(src);
}

function escapeHtml(input: string) {
    return input
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

function renderShowImages(root: ParentNode | null = document) {
    if (!root) return;
    // 处理因自闭合解析导致的嵌套：把子 show_image 平铺到父后面
    const nested = Array.from(root.querySelectorAll('show_image show_image')).reverse();
    nested.forEach(child => {
        const parent = child.parentElement;
        if (parent && parent !== root) {
            parent.after(child);
        }
    });
    const nodes = Array.from(root.querySelectorAll('show_image:not([data-rendered])')).reverse();
    nodes.forEach(node => {
        // 将 show_image 内误被包裹的内容移动到当前节点之后，保持原有顺序
        if (node.parentNode && node.firstChild) {
            const parent = node.parentNode;
            const ref = node.nextSibling; // 可能为 null，insertBefore 会当 append
            const children = Array.from(node.childNodes);
            children.forEach(child => parent.insertBefore(child, ref));
        }

        const rawSrc = node.getAttribute('src') || '';
        const mappedSrc = normalizeShowImageSrc(rawSrc);
        if (!mappedSrc) {
            node.setAttribute('data-rendered', '1');
            node.setAttribute('data-rendered-error', 'invalid-src');
            return;
        }
        const alt = node.getAttribute('alt') || '';
        const safeAlt = escapeHtml(alt.trim());
        const figure = document.createElement('figure');
        figure.className = 'chat-inline-image';

        const img = document.createElement('img');
        img.loading = 'lazy';
        img.src = mappedSrc;
        img.alt = safeAlt;
        img.onerror = () => {
            figure.classList.add('chat-inline-image--error');
            const tip = document.createElement('div');
            tip.className = 'chat-inline-image__error';
            tip.textContent = '图片加载失败';
            figure.appendChild(tip);
        };
        figure.appendChild(img);

        if (safeAlt) {
            const caption = document.createElement('figcaption');
            caption.innerHTML = safeAlt;
            figure.appendChild(caption);
        }

        node.replaceChildren(figure);
        node.setAttribute('data-rendered', '1');
    });
}

let showImageObserver: MutationObserver | null = null;

function setupShowImageObserver() {
    if (showImageObserver) return;
    const container = document.querySelector('.messages-area') || document.body;
    if (!container) return;
    renderShowImages(container);
    showImageObserver = new MutationObserver(() => renderShowImages(container));
    showImageObserver.observe(container, { childList: true, subtree: true });
}

function teardownShowImageObserver() {
    if (showImageObserver) {
        showImageObserver.disconnect();
        showImageObserver = null;
    }
}

window.katex = katex;

function updateViewportHeightVar() {
    const docEl = document.documentElement;
    const visualViewport = window.visualViewport;

    if (visualViewport) {
        const vh = visualViewport.height;
        const bottomInset = Math.max(
            0,
            (window.innerHeight || docEl.clientHeight || vh) - visualViewport.height - visualViewport.offsetTop
        );
        docEl.style.setProperty('--app-viewport', `${vh}px`);
        docEl.style.setProperty('--app-bottom-inset', `${bottomInset}px`);
    } else {
        const height = window.innerHeight || docEl.clientHeight;
        if (height) {
            docEl.style.setProperty('--app-viewport', `${height}px`);
        }
        docEl.style.setProperty('--app-bottom-inset', 'env(safe-area-inset-bottom, 0px)');
    }
}

updateViewportHeightVar();
window.addEventListener('resize', updateViewportHeightVar);
window.addEventListener('orientationchange', updateViewportHeightVar);
window.addEventListener('pageshow', updateViewportHeightVar);
if (window.visualViewport) {
    window.visualViewport.addEventListener('resize', updateViewportHeightVar);
    window.visualViewport.addEventListener('scroll', updateViewportHeightVar);
}

const ENABLE_APP_DEBUG_LOGS = true;
function debugLog(...args) {
    if (!ENABLE_APP_DEBUG_LOGS) return;
    try {
        console.log('[app]', ...args);
    } catch (e) {
        /* ignore logging errors */
    }
}
// 临时排查对话切换问题的调试输出
const TRACE_CONV = true;
const traceLog = (...args) => {
    if (!TRACE_CONV) return;
    try {
        console.log('[conv-trace]', ...args);
    } catch (e) {
        // ignore
    }
};

const appOptions = {
        data() {
            return {
                // 路由相关
                initialRouteResolved: false,
                dropToolEvents: false,
                
                // 工具状态跟踪
                preparingTools: new Map(),
                activeTools: new Map(),
                toolActionIndex: new Map(),
                toolStacks: new Map(),
                // 当前任务是否仍在进行中（用于保持输入区的“停止”状态）
                taskInProgress: false,
                // 记录上一次成功加载历史的对话ID，防止初始化阶段重复加载导致动画播放两次
                lastHistoryLoadedConversationId: null,
                
                // ==========================================
                // 对话管理相关状态
                // ==========================================
                
                // 搜索功能
                // ==========================================
                // Token统计相关状态（修复版）
                // ==========================================
                
                // 对话压缩状态
                compressing: false,
                skipConversationLoadedEvent: false,
                skipConversationHistoryReload: false,
                _scrollListenerReady: false,
                historyLoading: false,
                historyLoadingFor: null,
                historyLoadSeq: 0,
                blankHeroActive: false,
                blankHeroExiting: false,
                blankWelcomeText: '',
                lastBlankConversationId: null,
                // 对话标题打字效果
                titleTypingText: '',
                titleTypingTarget: '',
                titleTypingTimer: null,
                titleReady: false,
                suppressTitleTyping: false,
                blankWelcomePool: [
                    '有什么可以帮忙的？',
                    '想了解些热点吗？',
                    '要我帮你完成作业吗？',
                    '整点代码？',
                    '随便聊点什么？',
                    '想让我帮你整理一下思路吗？',
                    '要不要我帮你写个小工具？',
                    '发我一句话，我来接着做。'
                ],
                mobileViewportQuery: null,
                modeMenuOpen: false,
                modelMenuOpen: false,
                imageEntries: [],
                imageLoading: false,
                conversationHasImages: false,
                conversationListRequestSeq: 0,
                conversationListRefreshToken: 0,

                // 工具控制菜单
                icons: ICONS,
                toolCategoryIcons: TOOL_CATEGORY_ICON_MAP,

                // 对话回顾
                reviewDialogOpen: false,
                reviewSelectedConversationId: null,
                reviewSubmitting: false,
                reviewPreviewLines: [],
                reviewPreviewLoading: false,
                reviewPreviewError: null,
                reviewPreviewLimit: 20,
                reviewSendToModel: true,
                reviewGeneratedPath: null
            }
        },
        
        created() {
            const actionStore = useChatActionStore();
            actionStore.registerDependencies({
                pushToast: (payload) => this.uiPushToast(payload),
                autoResizeInput: () => this.autoResizeInput(),
                focusComposer: () => {
                    const inputEl = this.getComposerElement('stadiumInput');
                    if (inputEl && typeof inputEl.focus === 'function') {
                        inputEl.focus();
                    }
                },
                isConnected: () => this.isConnected,
                getSocket: () => this.socket,
                downloadResource: (url, filename) => this.downloadResource(url, filename)
            });
        },
        
            async mounted() {
            debugLog('Vue应用已挂载');
            if (window.ensureCsrfToken) {
                window.ensureCsrfToken().catch((err) => {
                    console.warn('CSRF token 初始化失败:', err);
                });
            }
            // 并行启动路由解析与实时连接，避免等待路由加载阻塞思考模式同步
            const routePromise = this.bootstrapRoute();
            const socketPromise = this.initSocket();
            await routePromise;
            await socketPromise;
            this.$nextTick(() => {
                this.ensureScrollListener();
                // 刷新后若无输出，自动解锁滚动锁定
                normalizeScrollLock(this);
            });
            setupShowImageObserver();
            
            // 立即加载初始数据（并行获取状态，优先同步运行模式）
            this.loadInitialData();
            
            document.addEventListener('click', this.handleClickOutsideQuickMenu);
            document.addEventListener('click', this.handleClickOutsidePanelMenu);
            document.addEventListener('click', this.handleClickOutsideMobileMenu);
            window.addEventListener('popstate', this.handlePopState);
            window.addEventListener('keydown', this.handleMobileOverlayEscape);
            this.setupMobileViewportWatcher();

            this.subAgentFetch();
            this.subAgentStartPolling();

            this.$nextTick(() => {
                this.autoResizeInput();
            });
            this.resourceBindContainerVisibilityWatcher();
            this.resourceStartContainerStatsPolling();
            this.resourceStartProjectStoragePolling();
            this.resourceStartUsageQuotaPolling();
        },

        computed: {
            ...mapWritableState(useConnectionStore, [
                'isConnected',
                'socket',
                'stopRequested',
                'projectPath',
                'agentVersion',
                'thinkingMode',
                'runMode'
            ]),
            ...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']),
            ...mapWritableState(useUiStore, [
                'sidebarCollapsed',
                'workspaceCollapsed',
                'chatDisplayMode',
                'panelMode',
                'panelMenuOpen',
                'leftWidth',
                'rightWidth',
                'rightCollapsed',
                'isResizing',
                'resizingPanel',
                'minPanelWidth',
                'maxPanelWidth',
                'quotaToast',
                'toastQueue',
                'confirmDialog',
                'easterEgg',
                'isMobileViewport',
                'mobileOverlayMenuOpen',
                'activeMobileOverlay'
            ]),
            ...mapWritableState(useConversationStore, [
                'conversations',
                'conversationsLoading',
                'hasMoreConversations',
                'loadingMoreConversations',
                'currentConversationId',
                'currentConversationTitle',
                'searchQuery',
                'searchTimer',
                'conversationsOffset',
                'conversationsLimit'
            ]),
            ...mapWritableState(useModelStore, ['currentModelKey']),
            ...mapState(useModelStore, ['models']),
            ...mapWritableState(useChatStore, [
                'messages',
                'currentMessageIndex',
                'streamingMessage',
                'expandedBlocks',
                'autoScrollEnabled',
                'userScrolling',
                'thinkingScrollLocks'
            ]),
            ...mapWritableState(useInputStore, [
                'inputMessage',
                'inputLineCount',
                'inputIsMultiline',
                'inputIsFocused',
                'quickMenuOpen',
                'toolMenuOpen',
                'settingsOpen',
                'imagePickerOpen',
                'selectedImages'
            ]),
            resolvedRunMode() {
                const allowed = ['fast', 'thinking', 'deep'];
                if (allowed.includes(this.runMode)) {
                    return this.runMode;
                }
                return this.thinkingMode ? 'thinking' : 'fast';
            },
            policyUiBlocks() {
                const store = usePolicyStore();
                return store.uiBlocks || {};
            },
            adminDisabledModels() {
                const store = usePolicyStore();
                return store.disabledModelSet;
            },
            modelOptions() {
                const disabledSet = this.adminDisabledModels || new Set();
                const options = this.models || [];
                return options.map((opt) => ({
                    ...opt,
                    disabled: disabledSet.has(opt.key)
                })).filter((opt) => true);
            },
            titleRibbonVisible() {
                return !this.isMobileViewport && this.chatDisplayMode === 'chat';
            },
            ...mapWritableState(useToolStore, [
                'preparingTools',
                'activeTools',
                'toolActionIndex',
                'toolStacks',
                'toolSettings',
                'toolSettingsLoading'
            ]),
            ...mapWritableState(useResourceStore, [
                'tokenPanelCollapsed',
                'currentContextTokens',
                'currentConversationTokens',
                'projectStorage',
                'containerStatus',
                'containerNetRate',
                'usageQuota'
            ]),
            ...mapWritableState(useFocusStore, ['focusedFiles']),
            ...mapWritableState(useUploadStore, ['uploading'])
            ,
            ...mapState(useMonitorStore, {
                monitorIsLocked: (store) => store.isLocked
            })
            ,
            displayModeSwitchDisabled() {
                return !!this.policyUiBlocks.block_virtual_monitor;
            },
            displayLockEngaged() {
                return false;
            },
            streamingUi() {
                return this.streamingMessage || this.hasPendingToolActions();
            },
            composerBusy() {
                const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
                return this.streamingUi || this.taskInProgress || monitorLock || this.stopRequested;
            },
            composerHeroActive() {
                return (this.blankHeroActive || this.blankHeroExiting) && !this.composerInteractionActive;
            },
            composerInteractionActive() {
                const hasText = !!(this.inputMessage && this.inputMessage.trim().length > 0);
                const hasImages = Array.isArray(this.selectedImages) && this.selectedImages.length > 0;
                return this.quickMenuOpen || hasText || hasImages;
            }
        },

        beforeUnmount() {
            document.removeEventListener('click', this.handleClickOutsideQuickMenu);
            document.removeEventListener('click', this.handleClickOutsidePanelMenu);
            document.removeEventListener('click', this.handleClickOutsideMobileMenu);
            window.removeEventListener('popstate', this.handlePopState);
            window.removeEventListener('keydown', this.handleMobileOverlayEscape);
            this.teardownMobileViewportWatcher();
            this.subAgentStopPolling();
            this.resourceStopContainerStatsPolling();
            this.resourceStopProjectStoragePolling();
            this.resourceStopUsageQuotaPolling();
            teardownShowImageObserver();
            if (this.titleTypingTimer) {
                clearInterval(this.titleTypingTimer);
                this.titleTypingTimer = null;
            }
            const cleanup = this.destroyEasterEggEffect(true);
            if (cleanup && typeof cleanup.catch === 'function') {
                cleanup.catch(() => {});
            }
        },

            watch: {
            inputMessage() {
                this.autoResizeInput();
            },
            messages: {
                deep: true,
                handler() {
                    this.refreshBlankHeroState();
                }
            },
            currentConversationTitle(newVal, oldVal) {
                const target = (newVal && newVal.trim()) || '';
                if (this.suppressTitleTyping) {
                    this.titleTypingText = target;
                    this.titleTypingTarget = target;
                    return;
                }
                const previous = (oldVal && oldVal.trim()) || (this.titleTypingText && this.titleTypingText.trim()) || '';
                const placeholderPrev = !previous || previous === '新对话';
                const placeholderTarget = !target || target === '新对话';
                const animate = placeholderPrev && !placeholderTarget; // 仅从空/占位切换到真实标题时动画
                this.startTitleTyping(target, { animate });
            },
            currentConversationId: {
                immediate: false,
                handler(newValue, oldValue) {
                        debugLog('currentConversationId 变化', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
                        traceLog('watch:currentConversationId', {
                            oldValue,
                            newValue,
                            skipConversationHistoryReload: this.skipConversationHistoryReload,
                            historyLoading: this.historyLoading,
                            historyLoadingFor: this.historyLoadingFor,
                            historyLoadSeq: this.historyLoadSeq
                        });
                        this.refreshBlankHeroState();
                        this.logMessageState('watch:currentConversationId', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
                        if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
                            return;
                        }
                        if (this.skipConversationHistoryReload) {
                        this.skipConversationHistoryReload = false;
                        return;
                    }
                    if (oldValue && newValue === oldValue) {
                        return;
                    }
                    this.fetchAndDisplayHistory();
                    this.fetchConversationTokenStatistics();
                    this.updateCurrentContextTokens();
                }
            }
            ,
            fileTree: {
                immediate: true,
                handler(newValue) {
                    this.monitorSyncDesktop(newValue);
                }
            }
        },
        
        methods: {
            ensureScrollListener() {
                if (this._scrollListenerReady) {
                    return;
                }
                const area = this.getMessagesAreaElement();
                if (!area) {
                    return;
                }
                this.initScrollListener();
                this._scrollListenerReady = true;
            },
            setupMobileViewportWatcher() {
                if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
                    this.updateMobileViewportState(false);
                    return;
                }
                const query = window.matchMedia('(max-width: 768px)');
                this.mobileViewportQuery = query;
                this.updateMobileViewportState(query.matches);
                if (typeof query.addEventListener === 'function') {
                    query.addEventListener('change', this.handleMobileViewportQueryChange);
                } else if (typeof query.addListener === 'function') {
                    query.addListener(this.handleMobileViewportQueryChange);
                }
            },
            teardownMobileViewportWatcher() {
                const query = this.mobileViewportQuery;
                if (!query) {
                    return;
                }
                if (typeof query.removeEventListener === 'function') {
                    query.removeEventListener('change', this.handleMobileViewportQueryChange);
                } else if (typeof query.removeListener === 'function') {
                    query.removeListener(this.handleMobileViewportQueryChange);
                }
                this.mobileViewportQuery = null;
            },
            handleMobileViewportQueryChange(event) {
                this.updateMobileViewportState(event.matches);
            },
            updateMobileViewportState(isMobile) {
                this.uiSetMobileViewport(!!isMobile);
                if (!isMobile) {
                    this.uiSetMobileOverlayMenuOpen(false);
                    this.closeMobileOverlay();
                }
            },
            toggleMobileOverlayMenu() {
                if (!this.isMobileViewport) {
                    return;
                }
                this.uiToggleMobileOverlayMenu();
            },
            openMobileOverlay(target) {
                if (!this.isMobileViewport) {
                    return;
                }
                if (this.activeMobileOverlay === target) {
                    this.closeMobileOverlay();
                    return;
                }
                if (this.activeMobileOverlay === 'conversation') {
                    this.uiSetSidebarCollapsed(true);
                }
                if (target === 'conversation') {
                    this.uiSetSidebarCollapsed(false);
                }
                this.uiSetActiveMobileOverlay(target);
                this.uiSetMobileOverlayMenuOpen(false);
            },
            closeMobileOverlay() {
                if (!this.activeMobileOverlay) {
                    this.uiCloseMobileOverlay();
                    return;
                }
                if (this.activeMobileOverlay === 'conversation') {
                    this.uiSetSidebarCollapsed(true);
                }
                this.uiCloseMobileOverlay();
            },
            applyPolicyUiLocks() {
                const policyStore = usePolicyStore();
                const blocks = policyStore.uiBlocks;
                if (blocks.collapse_workspace) {
                    this.uiSetWorkspaceCollapsed(true);
                }
                if (blocks.block_virtual_monitor && this.chatDisplayMode === 'monitor') {
                    this.uiSetChatDisplayMode('chat');
                }
            },
            isPolicyBlocked(key: string, message?: string) {
                const policyStore = usePolicyStore();
                if (policyStore.uiBlocks[key]) {
                    this.uiPushToast({
                        title: '已被管理员禁用',
                        message: message || '被管理员强制禁用',
                        type: 'warning'
                    });
                    return true;
                }
                return false;
            },
            handleClickOutsideMobileMenu(event) {
                if (!this.isMobileViewport || !this.mobileOverlayMenuOpen) {
                    return;
                }
                const trigger = this.$refs.mobilePanelTrigger;
                if (trigger && typeof trigger.contains === 'function' && trigger.contains(event.target)) {
                    return;
                }
                this.uiSetMobileOverlayMenuOpen(false);
            },
            handleWorkspaceToggle() {
                if (this.isMobileViewport) {
                    return;
                }
                if (this.isPolicyBlocked('collapse_workspace', '工作区已被管理员强制折叠')) {
                    this.uiSetWorkspaceCollapsed(true);
                    return;
                }
                const nextState = !this.workspaceCollapsed;
                this.uiSetWorkspaceCollapsed(nextState);
                if (nextState) {
                    this.uiSetPanelMenuOpen(false);
                }
            },
            handleDisplayModeToggle() {
                if (this.displayModeSwitchDisabled) {
                    // 显式提示管理员禁用
                    this.isPolicyBlocked('block_virtual_monitor', '虚拟显示器已被管理员禁用');
                    return;
                }
                if (this.chatDisplayMode === 'chat' && this.isPolicyBlocked('block_virtual_monitor', '虚拟显示器已被管理员禁用')) {
                    return;
                }
                const next = this.chatDisplayMode === 'chat' ? 'monitor' : 'chat';
                this.uiSetChatDisplayMode(next);
            },
            handleMobileOverlayEscape(event) {
                if (event.key !== 'Escape' || !this.isMobileViewport) {
                    return;
                }
                if (this.mobileOverlayMenuOpen) {
                    this.uiSetMobileOverlayMenuOpen(false);
                    return;
                }
                if (this.activeMobileOverlay) {
                    this.closeMobileOverlay();
                }
            },
            ...mapActions(useUiStore, {
                uiToggleSidebar: 'toggleSidebar',
                uiSetSidebarCollapsed: 'setSidebarCollapsed',
                uiSetWorkspaceCollapsed: 'setWorkspaceCollapsed',
                uiToggleWorkspaceCollapsed: 'toggleWorkspaceCollapsed',
                uiSetChatDisplayMode: 'setChatDisplayMode',
                uiSetPanelMode: 'setPanelMode',
                uiSetPanelMenuOpen: 'setPanelMenuOpen',
                uiTogglePanelMenu: 'togglePanelMenu',
                uiSetMobileViewport: 'setIsMobileViewport',
                uiSetMobileOverlayMenuOpen: 'setMobileOverlayMenuOpen',
                uiToggleMobileOverlayMenu: 'toggleMobileOverlayMenu',
                uiSetActiveMobileOverlay: 'setActiveMobileOverlay',
                uiCloseMobileOverlay: 'closeMobileOverlay',
                uiPushToast: 'pushToast',
                uiUpdateToast: 'updateToast',
                uiDismissToast: 'dismissToast',
                uiShowQuotaToastMessage: 'showQuotaToastMessage',
                uiDismissQuotaToast: 'dismissQuotaToast',
                uiRequestConfirm: 'requestConfirm',
                uiResolveConfirm: 'resolveConfirm'
            }),
            ...mapActions(useModelStore, {
                modelSet: 'setModel'
            }),
            ...mapActions(useChatStore, {
                chatExpandBlock: 'expandBlock',
                chatCollapseBlock: 'collapseBlock',
                chatClearExpandedBlocks: 'clearExpandedBlocks',
                chatSetThinkingLock: 'setThinkingLock',
                chatClearThinkingLocks: 'clearThinkingLocks',
                chatSetScrollState: 'setScrollState',
                chatEnableAutoScroll: 'enableAutoScroll',
                chatDisableAutoScroll: 'disableAutoScroll',
                chatToggleScrollLockState: 'toggleScrollLockState',
                chatAddUserMessage: 'addUserMessage',
                chatStartAssistantMessage: 'startAssistantMessage',
                chatStartThinkingAction: 'startThinkingAction',
                chatAppendThinkingChunk: 'appendThinkingChunk',
                chatCompleteThinkingAction: 'completeThinking',
                chatStartTextAction: 'startTextAction',
                chatAppendTextChunk: 'appendTextChunk',
                chatCompleteTextAction: 'completeText',
                chatAddSystemMessage: 'addSystemMessage',
                chatAddAppendPayloadAction: 'addAppendPayloadAction',
                chatAddModifyPayloadAction: 'addModifyPayloadAction',
                chatEnsureAssistantMessage: 'ensureAssistantMessage'
            }),
            ...mapActions(useInputStore, {
                inputSetFocused: 'setInputFocused',
                inputToggleQuickMenu: 'toggleQuickMenu',
                inputCloseMenus: 'closeMenus',
                inputOpenQuickMenu: 'openQuickMenu',
                inputSetQuickMenuOpen: 'setQuickMenuOpen',
                inputToggleToolMenu: 'toggleToolMenu',
                inputSetToolMenuOpen: 'setToolMenuOpen',
                inputToggleSettingsMenu: 'toggleSettingsMenu',
                inputSetSettingsOpen: 'setSettingsOpen',
                inputSetMessage: 'setInputMessage',
                inputClearMessage: 'clearInputMessage',
                inputSetLineCount: 'setInputLineCount',
                inputSetMultiline: 'setInputMultiline',
                inputSetImagePickerOpen: 'setImagePickerOpen',
                inputSetSelectedImages: 'setSelectedImages',
                inputClearSelectedImages: 'clearSelectedImages',
                inputRemoveSelectedImage: 'removeSelectedImage'
            }),
            ...mapActions(useToolStore, {
                toolRegisterAction: 'registerToolAction',
                toolUnregisterAction: 'unregisterToolAction',
                toolFindAction: 'findToolAction',
                toolTrackAction: 'trackToolAction',
                toolReleaseAction: 'releaseToolAction',
                toolGetLatestAction: 'getLatestActiveToolAction',
                toolResetTracking: 'resetToolTracking',
                toolSetSettings: 'setToolSettings',
                toolSetSettingsLoading: 'setToolSettingsLoading'
            }),
            ...mapActions(useResourceStore, {
                resourceUpdateCurrentContextTokens: 'updateCurrentContextTokens',
                resourceFetchConversationTokenStatistics: 'fetchConversationTokenStatistics',
                resourceSetCurrentContextTokens: 'setCurrentContextTokens',
                resourceToggleTokenPanel: 'toggleTokenPanel',
                resourceApplyStatusSnapshot: 'applyStatusSnapshot',
                resourceUpdateContainerStatus: 'updateContainerStatus',
                resourceStartContainerStatsPolling: 'startContainerStatsPolling',
                resourceStopContainerStatsPolling: 'stopContainerStatsPolling',
                resourceStartProjectStoragePolling: 'startProjectStoragePolling',
                resourceStopProjectStoragePolling: 'stopProjectStoragePolling',
                resourceStartUsageQuotaPolling: 'startUsageQuotaPolling',
                resourceStopUsageQuotaPolling: 'stopUsageQuotaPolling',
                resourcePollContainerStats: 'pollContainerStats',
                resourceBindContainerVisibilityWatcher: 'bindContainerVisibilityWatcher',
                resourcePollProjectStorage: 'pollProjectStorage',
                resourceFetchUsageQuota: 'fetchUsageQuota',
                resourceResetTokenStatistics: 'resetTokenStatistics',
                resourceSetUsageQuota: 'setUsageQuota'
            }),
            ...mapActions(useUploadStore, {
                uploadHandleSelected: 'handleSelectedFiles'
            }),
            ...mapActions(useFileStore, {
                fileFetchTree: 'fetchFileTree',
                fileSetTreeFromResponse: 'setFileTreeFromResponse',
                fileFetchTodoList: 'fetchTodoList',
                fileSetTodoList: 'setTodoList',
                fileHideContextMenu: 'hideContextMenu'
            }),
            ...mapActions(useMonitorStore, {
                monitorSyncDesktop: 'syncDesktopFromTree',
                monitorResetVisual: 'resetVisualState',
                monitorResetSpeech: 'resetSpeechBuffer',
                monitorShowSpeech: 'enqueueModelSpeech',
                monitorShowThinking: 'enqueueModelThinking',
                monitorEndModelOutput: 'endModelOutput',
                monitorShowPendingReply: 'showPendingReply',
                monitorPreviewTool: 'previewToolIntent',
                monitorQueueTool: 'enqueueToolEvent',
                monitorResolveTool: 'resolveToolResult'
            }),
            ...mapActions(useSubAgentStore, {
                subAgentFetch: 'fetchSubAgents',
                subAgentStartPolling: 'startPolling',
                subAgentStopPolling: 'stopPolling'
            }),
            ...mapActions(useFocusStore, {
                focusFetchFiles: 'fetchFocusedFiles',
                focusSetFiles: 'setFocusedFiles'
            }),
            ...mapActions(usePersonalizationStore, {
                personalizationOpenDrawer: 'openDrawer'
            }),
            getInputComposerRef() {
                return this.$refs.inputComposer || null;
            },
            getComposerElement(field) {
                const composer = this.getInputComposerRef();
                const unwrap = (value: any) => {
                    if (!value) {
                        return null;
                    }
                    if (value instanceof HTMLElement) {
                        return value;
                    }
                    if (typeof value === 'object' && 'value' in value && !(value instanceof Window)) {
                        return value.value;
                    }
                    return value;
                };
                if (composer && composer[field]) {
                    return unwrap(composer[field]);
                }
                if (this.$refs && this.$refs[field]) {
                    return unwrap(this.$refs[field]);
                }
                return null;
            },
            getMessagesAreaElement() {
                const ref = this.$refs.messagesArea;
                if (!ref) {
                    return null;
                }
                if (ref instanceof HTMLElement) {
                    return ref;
                }
                if (ref.rootEl) {
                    return ref.rootEl.value || ref.rootEl;
                }
                if (ref.$el && ref.$el.querySelector) {
                    const el = ref.$el.querySelector('.messages-area');
                    if (el) {
                        return el;
                    }
                }
                return null;
            },
            getThinkingContentElement(blockId) {
                const chatArea = this.$refs.messagesArea;
                if (chatArea && typeof chatArea.getThinkingRef === 'function') {
                    const el = chatArea.getThinkingRef(blockId);
                    if (el) {
                        return el;
                    }
                }
                const refName = `thinkingContent-${blockId}`;
                const elRef = this.$refs[refName];
                if (Array.isArray(elRef)) {
                    return elRef[0] || null;
                }
                return elRef || null;
            },
            isOutputActive() {
                return !!(this.streamingMessage || this.taskInProgress || this.hasPendingToolActions());
            },
            logMessageState(action, extra = {}) {
                const count = Array.isArray(this.messages) ? this.messages.length : 'N/A';
                debugLog('[Messages]', {
                    action,
                    count,
                    conversationId: this.currentConversationId,
                    streaming: this.streamingMessage,
                    ...extra
                });
            },
            iconStyle(iconKey, size) {
                const iconPath = this.icons ? this.icons[iconKey] : null;
                if (!iconPath) {
                    return {};
                }
                const style = { '--icon-src': `url(${iconPath})` };
                if (size) {
                    style['--icon-size'] = size;
                }
                return style;
            },

            toolCategoryIcon(categoryId) {
                return this.toolCategoryIcons[categoryId] || 'settings';
            },

            openGuiFileManager() {
                if (this.isPolicyBlocked('block_file_manager', '文件管理器已被管理员禁用')) {
                    return;
                }
                window.open('/file-manager', '_blank');
            },

            findMessageByAction(action) {
                if (!action) {
                    return null;
                }
                for (const message of this.messages) {
                    if (!message.actions) {
                        continue;
                    }
                    if (message.actions.includes(action)) {
                        return message;
                    }
                }
                return null;
            },

            renderMarkdown(content, isStreaming = false) {
                return renderMarkdownHelper(content, isStreaming);
            },

            hasContainerStats() {
                return !!(
                    this.containerStatus &&
                    this.containerStatus.mode === 'docker' &&
                    this.containerStatus.stats
                );
            },

            containerStatusClass() {
                if (!this.containerStatus) {
                    return 'status-pill--host';
                }
                if (this.containerStatus.mode !== 'docker') {
                    return 'status-pill--host';
                }
                const rawStatus = (
                    this.containerStatus.state &&
                    (this.containerStatus.state.status || this.containerStatus.state.Status)
                ) || '';
                const status = String(rawStatus).toLowerCase();
                if (status.includes('running')) {
                    return 'status-pill--running';
                }
                if (status.includes('paused')) {
                    return 'status-pill--stopped';
                }
                if (status.includes('exited') || status.includes('dead')) {
                    return 'status-pill--stopped';
                }
                return 'status-pill--running';
            },

            containerStatusText() {
                if (!this.containerStatus) {
                    return '未知';
                }
                if (this.containerStatus.mode !== 'docker') {
                    return '宿主机模式';
                }
                const rawStatus = (
                    this.containerStatus.state &&
                    (this.containerStatus.state.status || this.containerStatus.state.Status)
                ) || '';
                const status = String(rawStatus).toLowerCase();
                if (status.includes('running')) {
                    return '运行中';
                }
                if (status.includes('paused')) {
                    return '已暂停';
                }
                if (status.includes('exited') || status.includes('dead')) {
                    return '已停止';
                }
                return rawStatus || '容器模式';
            },

            formatTime(value) {
                if (!value) {
                    return '未知时间';
                }
                let date;
                if (typeof value === 'number') {
                    date = new Date(value);
                } else if (typeof value === 'string') {
                    const parsed = Date.parse(value);
                    if (!Number.isNaN(parsed)) {
                        date = new Date(parsed);
                    } else {
                        const numeric = Number(value);
                        if (!Number.isNaN(numeric)) {
                            date = new Date(numeric);
                        }
                    }
                } else if (value instanceof Date) {
                    date = value;
                }
                if (!date || Number.isNaN(date.getTime())) {
                    return String(value);
                }
                const now = Date.now();
                const diff = now - date.getTime();
                if (diff < 60000) {
                    return '刚刚';
                }
                if (diff < 3600000) {
                    const mins = Math.floor(diff / 60000);
                    return `${mins} 分钟前`;
                }
                if (diff < 86400000) {
                    const hours = Math.floor(diff / 3600000);
                    return `${hours} 小时前`;
                }
                const formatter = new Intl.DateTimeFormat('zh-CN', {
                    month: '2-digit',
                    day: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit'
                });
                return formatter.format(date);
            },

            async bootstrapRoute() {
                // 在路由解析期间抑制标题动画，避免预置“新对话”闪烁
                this.suppressTitleTyping = true;
                this.titleReady = false;
                this.currentConversationTitle = '';
                this.titleTypingText = '';
                const path = window.location.pathname.replace(/^\/+/, '');
                if (!path || path === 'new') {
                    this.currentConversationId = null;
                    this.currentConversationTitle = '新对话';
                    this.titleReady = true;
                    this.suppressTitleTyping = false;
                    this.startTitleTyping('新对话', { animate: false });
                    this.initialRouteResolved = true;
                    return;
                }

                const convId = path.startsWith('conv_') ? path : `conv_${path}`;
                try {
                    const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' });
                    const result = await resp.json();
                    if (result.success) {
                        this.currentConversationId = convId;
                        this.currentConversationTitle = result.title || '';
                        this.titleReady = true;
                        this.suppressTitleTyping = false;
                        this.startTitleTyping(this.currentConversationTitle, { animate: false });
                        history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`);
                    } else {
                        history.replaceState({}, '', '/new');
                        this.currentConversationId = null;
                        this.currentConversationTitle = '新对话';
                        this.titleReady = true;
                        this.suppressTitleTyping = false;
                        this.startTitleTyping('新对话', { animate: false });
                    }
                } catch (error) {
                    console.warn('初始化路由失败:', error);
                    history.replaceState({}, '', '/new');
                    this.currentConversationId = null;
                    this.currentConversationTitle = '新对话';
                    this.titleReady = true;
                    this.suppressTitleTyping = false;
                    this.startTitleTyping('新对话', { animate: false });
                } finally {
                    this.initialRouteResolved = true;
                }
            },

            handlePopState(event) {
                const state = event.state || {};
                const convId = state.conversationId;
                if (!convId) {
                    this.currentConversationId = null;
                    this.currentConversationTitle = '新对话';
                    this.logMessageState('handlePopState:clear-messages-no-conversation');
                    this.messages = [];
                    this.logMessageState('handlePopState:after-clear-no-conversation');
                    this.resetAllStates('handlePopState:no-conversation');
                    this.resetTokenStatistics();
                    return;
                }
                this.loadConversation(convId);
            },

            stripConversationPrefix(conversationId) {
                if (!conversationId) return '';
                return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId;
            },

            async downloadFile(path) {
                if (!path) {
                    this.fileHideContextMenu();
                    return;
                }
                const url = `/api/download/file?path=${encodeURIComponent(path)}`;
                const name = path.split('/').pop() || 'file';
                await this.downloadResource(url, name);
            },

            async downloadFolder(path) {
                if (!path) {
                    this.fileHideContextMenu();
                    return;
                }
                const url = `/api/download/folder?path=${encodeURIComponent(path)}`;
                const segments = path.split('/').filter(Boolean);
                const folderName = segments.length ? segments.pop() : 'folder';
                await this.downloadResource(url, `${folderName}.zip`);
            },

            async downloadResource(url, filename) {
                try {
                    const response = await fetch(url);
                    if (!response.ok) {
                        let message = response.statusText;
                        try {
                            const errorData = await response.json();
                            message = errorData.error || errorData.message || message;
                        } catch (err) {
                            message = await response.text();
                        }
                        this.uiPushToast({
                            title: '下载失败',
                            message: message || '无法完成下载',
                            type: 'error'
                        });
                        return;
                    }

                    const blob = await response.blob();
                    const downloadName = filename || 'download';
                    const link = document.createElement('a');
                    const href = URL.createObjectURL(blob);
                    link.href = href;
                    link.download = downloadName;
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    URL.revokeObjectURL(href);
                } catch (error) {
                    console.error('下载失败:', error);
                    this.uiPushToast({
                        title: '下载失败',
                        message: error.message || String(error),
                        type: 'error'
                    });
                } finally {
                    this.fileHideContextMenu();
                }
            },

            initScrollListener() {
                const messagesArea = this.getMessagesAreaElement();
                if (!messagesArea) {
                    console.warn('消息区域未找到');
                    return;
                }
                this._scrollListenerReady = true;
                
                let isProgrammaticScroll = false;
                const bottomThreshold = 12;
                
                this._setScrollingFlag = (flag) => {
                    isProgrammaticScroll = !!flag;
                };
                
                messagesArea.addEventListener('scroll', () => {
                    if (isProgrammaticScroll) {
                        return;
                    }
                    
                    const scrollTop = messagesArea.scrollTop;
                    const scrollHeight = messagesArea.scrollHeight;
                    const clientHeight = messagesArea.clientHeight;
                    const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold;
                    
                    const activeLock = this.autoScrollEnabled && this.isOutputActive();
                    
                    // 锁定且当前有输出时，强制贴底
                    if (activeLock) {
                        if (!isAtBottom) {
                            if (typeof this._setScrollingFlag === 'function') {
                                this._setScrollingFlag(true);
                            }
                            messagesArea.scrollTop = messagesArea.scrollHeight;
                            if (typeof this._setScrollingFlag === 'function') {
                                setTimeout(() => this._setScrollingFlag && this._setScrollingFlag(false), 16);
                            }
                        }
                        // 保持锁定状态下 userScrolling 为 false
                        this.chatSetScrollState({ userScrolling: false });
                        return;
                    }

                    // 未锁定或无输出：允许自由滚动，仅记录位置
                    this.chatSetScrollState({ userScrolling: !isAtBottom });
                });
            },
            
            async initSocket() {
                await initializeLegacySocket(this);
            },

            cleanupStaleToolActions() {
                this.messages.forEach(msg => {
                    if (!msg.actions) {
                        return;
                    }
                    msg.actions.forEach(action => {
                        if (action.type !== 'tool' || !action.tool) {
                            return;
                        }
                        if (['running', 'preparing'].includes(action.tool.status)) {
                            action.tool.status = 'stale';
                            action.tool.message = action.tool.message || '已被新的响应中断';
                            this.toolUnregisterAction(action);
                        }
                    });
                });
                this.preparingTools.clear();
                this.toolActionIndex.clear();
            },

            hasPendingToolActions() {
                const mapHasEntries = map => map && typeof map.size === 'number' && map.size > 0;
                if (mapHasEntries(this.preparingTools) || mapHasEntries(this.activeTools)) {
                    return true;
                }
                if (!Array.isArray(this.messages)) {
                    return false;
                }
                return this.messages.some(msg => {
                    if (!msg || msg.role !== 'assistant' || !Array.isArray(msg.actions)) {
                        return false;
                    }
                    return msg.actions.some(action => {
                        if (!action || action.type !== 'tool' || !action.tool) {
                            return false;
                        }
                        if (action.tool.awaiting_content) {
                            return true;
                        }
                        const status =
                            typeof action.tool.status === 'string'
                                ? action.tool.status.toLowerCase()
                                : '';
                        return !status || ['preparing', 'running', 'pending', 'queued'].includes(status);
                    });
                });
            },

            maybeResetStreamingState(reason = 'unspecified') {
                if (!this.streamingMessage) {
                    return false;
                }
                if (this.hasPendingToolActions()) {
                    return false;
                }
                this.streamingMessage = false;
                this.stopRequested = false;
                debugLog('流式状态已结束', { reason });
                return true;
            },

            // 完整重置所有状态
            resetAllStates(reason = 'unspecified', options: { preserveMonitorWindows?: boolean } = {}) {
                debugLog('重置所有前端状态', { reason, conversationId: this.currentConversationId });
                this.logMessageState('resetAllStates:before-cleanup', { reason });
                this.fileHideContextMenu();
                this.monitorResetVisual({
                    preserveBubble: true,
                    preservePointer: true,
                    preserveWindows: !!options?.preserveMonitorWindows,
                    preserveQueue: !!options?.preserveMonitorWindows
                });
                
                // 重置消息和流状态
                this.streamingMessage = false;
                this.currentMessageIndex = -1;
                this.stopRequested = false;
                this.taskInProgress = false;
                this.dropToolEvents = false;
                
                // 清理工具状态
                this.toolResetTracking();
                
                // 新增：将所有未完成的工具标记为已完成
                this.messages.forEach(msg => {
                    if (msg.role === 'assistant' && msg.actions) {
                        msg.actions.forEach(action => {
                            if (action.type === 'tool' && 
                                (action.tool.status === 'preparing' || action.tool.status === 'running')) {
                                action.tool.status = 'completed';
                            }
                        });
                    }
                });
                
                // 清理Markdown缓存
                if (this.markdownCache) {
                    this.markdownCache.clear();
                }
                this.chatClearThinkingLocks();
                
                // 强制更新视图
                this.$forceUpdate();
                
                this.inputSetSettingsOpen(false);
                this.inputSetToolMenuOpen(false);
                this.inputSetQuickMenuOpen(false);
                this.modeMenuOpen = false;
                this.inputSetLineCount(1);
                this.inputSetMultiline(false);
                this.inputClearMessage();
                this.inputClearSelectedImages();
                this.inputSetImagePickerOpen(false);
                this.imageEntries = [];
                this.imageLoading = false;
                this.conversationHasImages = false;
                this.toolSetSettingsLoading(false);
                this.toolSetSettings([]);
                
                debugLog('前端状态重置完成');
                this._scrollListenerReady = false;
                this.$nextTick(() => {
                    this.ensureScrollListener();
                });

                // 重置已加载对话标记，便于后续重新加载新对话历史
                this.lastHistoryLoadedConversationId = null;

                this.logMessageState('resetAllStates:after-cleanup', { reason });
            },

            scheduleResetAfterTask(reason = 'unspecified', options: { preserveMonitorWindows?: boolean } = {}) {
                const start = Date.now();
                const maxWait = 4000;
                const interval = 200;
                const tryReset = () => {
                    if (!this.monitorIsLocked || Date.now() - start >= maxWait) {
                        this.resetAllStates(reason, options);
                        return;
                    }
                    setTimeout(tryReset, interval);
                };
                tryReset();
            },
            
            // 重置Token统计

            openRealtimeTerminal() {
                const { protocol, hostname, port } = window.location;
                const target = `${protocol}//${hostname}${port ? ':' + port : ''}/terminal`;
                window.open(target, '_blank');
            },

            resetTokenStatistics() {
                this.resourceResetTokenStatistics();
            },
            
            async loadInitialData() {
                try {
                    debugLog('加载初始数据...');

                    // 并行拉取文件/待办，优先获取状态以尽快同步运行模式
                    const treePromise = this.fileFetchTree();
                    const focusPromise = this.focusFetchFiles();
                    const todoPromise = this.fileFetchTodoList();

                    const statusResponse = await fetch('/api/status');
                    const statusData = await statusResponse.json();
                    this.projectPath = statusData.project_path || '';
                    this.agentVersion = statusData.version || this.agentVersion;
                    this.thinkingMode = !!statusData.thinking_mode;
                    this.applyStatusSnapshot(statusData);
                    // 立即更新配额和运行模式，避免等待其他慢接口
                    this.fetchUsageQuota();
                    // 拉取管理员策略
                    const policyStore = usePolicyStore();
                    await policyStore.fetchPolicy();
                    this.applyPolicyUiLocks();

                    // 获取当前对话信息
                    const statusConversationId = statusData.conversation && statusData.conversation.current_id;
                    if (statusConversationId && !this.currentConversationId) {
                        this.skipConversationHistoryReload = true;
                        // 首次从状态恢复对话时，避免 socket 的 conversation_loaded 再次触发历史加载
                        this.skipConversationLoadedEvent = true;
                        this.suppressTitleTyping = true;
                        this.titleReady = false;
                        this.currentConversationTitle = '';
                        this.titleTypingText = '';
                        this.currentConversationId = statusConversationId;

                        // 如果有当前对话，尝试获取标题和历史
                        try {
                            const convResponse = await fetch(`/api/conversations/current`);
                            const convData = await convResponse.json();
                            if (convData.success && convData.data) {
                                this.currentConversationTitle = convData.data.title;
                                this.titleReady = true;
                                this.suppressTitleTyping = false;
                                this.startTitleTyping(this.currentConversationTitle, { animate: false });
                            } else {
                                this.titleReady = true;
                                this.suppressTitleTyping = false;
                                const fallbackTitle = this.currentConversationTitle || '新对话';
                                this.currentConversationTitle = fallbackTitle;
                                this.startTitleTyping(fallbackTitle, { animate: false });
                            }
                            // 初始化时调用一次，因为 skipConversationHistoryReload 会阻止 watch 触发
                            if (
                                this.lastHistoryLoadedConversationId !== this.currentConversationId ||
                                !Array.isArray(this.messages) ||
                                this.messages.length === 0
                            ) {
                                await this.fetchAndDisplayHistory();
                            }
                            // 获取当前对话的Token统计
                            this.fetchConversationTokenStatistics();
                            this.updateCurrentContextTokens();
                        } catch (e) {
                            console.warn('获取当前对话标题失败:', e);
                            this.titleReady = true;
                            this.suppressTitleTyping = false;
                            this.startTitleTyping(this.currentConversationTitle || '新对话', '');
                        }
                    }

                    // 等待其他加载项完成（允许部分失败不阻塞模式切换）
                    await Promise.allSettled([treePromise, focusPromise, todoPromise]);
                    await this.loadToolSettings(true);

                    debugLog('初始数据加载完成');
                } catch (error) {
                    console.error('加载初始数据失败:', error);
                }
            },

            // ==========================================
            // Token / 资源状态封装（Pinia store）
            // ==========================================
            
            async updateCurrentContextTokens() {
                await this.resourceUpdateCurrentContextTokens(this.currentConversationId);
            },
            
            async fetchConversationTokenStatistics() {
                await this.resourceFetchConversationTokenStatistics(this.currentConversationId);
            },
            
            toggleTokenPanel() {
                this.resourceToggleTokenPanel();
            },

            applyStatusSnapshot(status) {
                this.resourceApplyStatusSnapshot(status);
                if (status && typeof status.thinking_mode !== 'undefined') {
                    this.thinkingMode = !!status.thinking_mode;
                }
                if (status && typeof status.run_mode === 'string') {
                    this.runMode = status.run_mode;
                } else if (status && typeof status.thinking_mode !== 'undefined') {
                    this.runMode = status.thinking_mode ? 'thinking' : 'fast';
                }
                if (status && typeof status.model_key === 'string') {
                    this.modelSet(status.model_key);
                }
                if (status && typeof status.has_images !== 'undefined') {
                    this.conversationHasImages = !!status.has_images;
                }
            },

            updateContainerStatus(status) {
                this.resourceUpdateContainerStatus(status);
            },

            pollContainerStats() {
                return this.resourcePollContainerStats();
            },

            startContainerStatsPolling() {
                this.resourceStartContainerStatsPolling();
            },

            stopContainerStatsPolling() {
                this.resourceStopContainerStatsPolling();
            },

            pollProjectStorage() {
                return this.resourcePollProjectStorage();
            },

            startProjectStoragePolling() {
                this.resourceStartProjectStoragePolling();
            },

            stopProjectStoragePolling() {
                this.resourceStopProjectStoragePolling();
            },

            fetchUsageQuota() {
                return this.resourceFetchUsageQuota();
            },

            startUsageQuotaPolling() {
                this.resourceStartUsageQuotaPolling();
            },

            stopUsageQuotaPolling() {
                this.resourceStopUsageQuotaPolling();
            },
            
            // ==========================================
            // 对话管理核心功能
            // ==========================================
            
            async loadConversationsList() {
                const queryOffset = this.conversationsOffset;
                const queryLimit = this.conversationsLimit;
                const refreshToken = queryOffset === 0 ? ++this.conversationListRefreshToken : this.conversationListRefreshToken;
                const requestSeq = ++this.conversationListRequestSeq;
                this.conversationsLoading = true;
                try {
                    const response = await fetch(`/api/conversations?limit=${queryLimit}&offset=${queryOffset}`);
                    const data = await response.json();
                    
                    if (data.success) {
                        if (refreshToken !== this.conversationListRefreshToken) {
                            debugLog('忽略已过期的对话列表响应', {
                                requestSeq,
                                responseOffset: queryOffset
                            });
                            return;
                        }

                        if (queryOffset === 0) {
                            this.conversations = data.data.conversations;
                        } else {
                            this.conversations.push(...data.data.conversations);
                        }
                        if (this.currentConversationId) {
                            this.promoteConversationToTop(this.currentConversationId);
                        }
                        this.hasMoreConversations = data.data.has_more;
                        debugLog(`已加载 ${this.conversations.length} 个对话`);

                        if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) {
                            const latestConversation = this.conversations[0];
                            if (latestConversation && latestConversation.id) {
                                await this.loadConversation(latestConversation.id);
                            }
                        }
                    } else {
                        console.error('加载对话列表失败:', data.error);
                    }
                } catch (error) {
                    console.error('加载对话列表异常:', error);
                } finally {
                    if (refreshToken === this.conversationListRefreshToken) {
                        this.conversationsLoading = false;
                    }
                }
            },
            
            async loadMoreConversations() {
                if (this.loadingMoreConversations || !this.hasMoreConversations) return;
                
                this.loadingMoreConversations = true;
                this.conversationsOffset += this.conversationsLimit;
                await this.loadConversationsList();
                this.loadingMoreConversations = false;
            },
            
            async loadConversation(conversationId, options = {}) {
                const force = Boolean(options.force);
                debugLog('加载对话:', conversationId);
                traceLog('loadConversation:start', { conversationId, currentConversationId: this.currentConversationId, force });
                this.logMessageState('loadConversation:start', { conversationId, force });
                this.suppressTitleTyping = true;
                this.titleReady = false;
                this.currentConversationTitle = '';
                this.titleTypingText = '';
                
                if (!force && conversationId === this.currentConversationId) {
                    debugLog('已是当前对话，跳过加载');
                    traceLog('loadConversation:skip-same', { conversationId });
                    this.suppressTitleTyping = false;
                    this.titleReady = true;
                    return;
                }
                
                try {
                    // 1. 调用加载API
                    const response = await fetch(`/api/conversations/${conversationId}/load`, {
                        method: 'PUT'
                    });
                    const result = await response.json();
                    
                    if (result.success) {
                        debugLog('对话加载API成功:', result);
                        traceLog('loadConversation:api-success', { conversationId, title: result.title });
                        
                        // 2. 更新当前对话信息
                        this.skipConversationHistoryReload = true;
                        this.currentConversationId = conversationId;
                        this.currentConversationTitle = result.title;
                        this.titleReady = true;
                        this.suppressTitleTyping = false;
                        this.startTitleTyping(this.currentConversationTitle, { animate: false });
                        this.promoteConversationToTop(conversationId);
                        history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`);
                        this.skipConversationLoadedEvent = true;
                        
                        // 3. 重置UI状态
                        this.resetAllStates(`loadConversation:${conversationId}`);
                        this.subAgentFetch();
                        this.fetchTodoList();

                        // 4. 立即加载历史和统计，确保列表切换后界面同步更新
                        await this.fetchAndDisplayHistory();
                        this.fetchConversationTokenStatistics();
                        this.updateCurrentContextTokens();
                        traceLog('loadConversation:after-history', {
                            conversationId,
                            messagesLen: Array.isArray(this.messages) ? this.messages.length : 'n/a'
                        });
                        
                    } else {
                        console.error('对话加载失败:', result.message);
                        this.suppressTitleTyping = false;
                        this.titleReady = true;
                        this.uiPushToast({
                            title: '加载对话失败',
                            message: result.message || '服务器未返回成功状态',
                            type: 'error'
                        });
                    }
                } catch (error) {
                    console.error('加载对话异常:', error);
                    traceLog('loadConversation:error', { conversationId, error: error?.message || String(error) });
                    this.suppressTitleTyping = false;
                    this.titleReady = true;
                    this.uiPushToast({
                        title: '加载对话异常',
                        message: error.message || String(error),
                        type: 'error'
                    });
                }
            },

            promoteConversationToTop(conversationId) {
                if (!Array.isArray(this.conversations) || !conversationId) {
                    return;
                }
                const index = this.conversations.findIndex(conv => conv && conv.id === conversationId);
                if (index > 0) {
                    const [selected] = this.conversations.splice(index, 1);
                    this.conversations.unshift(selected);
                }
            },
            
            // ==========================================
            // 关键功能：获取并显示历史对话内容
            // ==========================================
            async fetchAndDisplayHistory(options = {}) {
                const { force = false } = options as { force?: boolean };
                const targetConversationId = this.currentConversationId;
                if (!targetConversationId || targetConversationId.startsWith('temp_')) {
                    debugLog('没有当前对话ID，跳过历史加载');
                    this.refreshBlankHeroState();
                    return;
                }

                // 若同一对话正在加载，直接复用；若是切换对话则允许并发但后来的请求会赢
                if (this.historyLoading && this.historyLoadingFor === targetConversationId) {
                    debugLog('同一对话历史正在加载，跳过重复请求');
                    return;
                }

                // 已经有完整历史且非强制刷新时，避免重复加载导致动画播放两次
                const alreadyHydrated =
                    !force &&
                    this.lastHistoryLoadedConversationId === targetConversationId &&
                    Array.isArray(this.messages) &&
                    this.messages.length > 0;
                if (alreadyHydrated) {
                    debugLog('历史已加载，跳过重复请求');
                    this.logMessageState('fetchAndDisplayHistory:skip-duplicate', {
                        conversationId: targetConversationId
                    });
                    return;
                }

                const loadSeq = ++this.historyLoadSeq;
                this.historyLoading = true;
                this.historyLoadingFor = targetConversationId;
                try {
                    debugLog('开始获取历史对话内容...');
                    this.logMessageState('fetchAndDisplayHistory:start', { conversationId: this.currentConversationId });
                    
                    try {
                        // 使用专门的API获取对话消息历史
                        const messagesResponse = await fetch(`/api/conversations/${targetConversationId}/messages`);
                        
                        if (!messagesResponse.ok) {
                            console.warn('无法获取消息历史，尝试备用方法');
                            // 备用方案：通过状态API获取
                            const statusResponse = await fetch('/api/status');
                            const status = await statusResponse.json();
                            debugLog('系统状态:', status);
                            this.applyStatusSnapshot(status);
                            
                            // 如果状态中有对话历史字段
                            if (status.conversation_history && Array.isArray(status.conversation_history)) {
                                this.renderHistoryMessages(status.conversation_history);
                                return;
                            }
                            
                            debugLog('备用方案也无法获取历史消息');
                            return;
                        }
                        
                        // 如果在等待期间用户已切换到其他对话，则丢弃结果
                        if (loadSeq !== this.historyLoadSeq || this.currentConversationId !== targetConversationId) {
                            debugLog('检测到对话已切换，丢弃过期的历史加载结果');
                            return;
                        }
                        
                        const messagesData = await messagesResponse.json();
                        debugLog('获取到消息数据:', messagesData);
                        
                        if (messagesData.success && messagesData.data && messagesData.data.messages) {
                            const messages = messagesData.data.messages;
                            debugLog(`发现 ${messages.length} 条历史消息`);
                            
                            if (messages.length > 0) {
                                // 清空当前显示的消息
                                this.logMessageState('fetchAndDisplayHistory:before-clear-existing');
                                this.messages = [];
                                this.logMessageState('fetchAndDisplayHistory:after-clear-existing');
                                
                                // 渲染历史消息 - 这是关键功能
                                this.renderHistoryMessages(messages);
                                
                                // 滚动到底部
                                this.$nextTick(() => {
                                    this.scrollToBottom();
                                });
                                
                                debugLog('历史对话内容显示完成');
                            } else {
                                debugLog('对话存在但没有历史消息');
                                this.logMessageState('fetchAndDisplayHistory:no-history-clear');
                                this.messages = [];
                                this.logMessageState('fetchAndDisplayHistory:no-history-cleared');
                            }
                        } else {
                            debugLog('消息数据格式不正确:', messagesData);
                            this.logMessageState('fetchAndDisplayHistory:invalid-data-clear');
                            this.messages = [];
                            this.logMessageState('fetchAndDisplayHistory:invalid-data-cleared');
                        }
                        
                    } catch (error) {
                        console.error('获取历史对话失败:', error);
                        debugLog('尝试不显示错误弹窗，仅在控制台记录');
                        // 不显示alert，避免打断用户体验
                        this.logMessageState('fetchAndDisplayHistory:error-clear', { error: error?.message || String(error) });
                            this.messages = [];
                            this.logMessageState('fetchAndDisplayHistory:error-cleared');
                        }
                    } finally {
                        // 仅在本次加载仍是最新请求时清除 loading 状态
                        if (loadSeq === this.historyLoadSeq) {
                            this.historyLoading = false;
                            this.historyLoadingFor = null;
                        }
                        this.refreshBlankHeroState();
                    }
            },
            
            // ==========================================
            // 关键功能：渲染历史消息
            // ==========================================
            renderHistoryMessages(historyMessages) {
                debugLog('开始渲染历史消息...', historyMessages);
                debugLog('历史消息数量:', historyMessages.length);
                this.logMessageState('renderHistoryMessages:start', { historyCount: historyMessages.length });
                
                if (!Array.isArray(historyMessages)) {
                    console.error('历史消息不是数组格式');
                    return;
                }
                
                let currentAssistantMessage = null;
                let historyHasImages = false;
                
                historyMessages.forEach((message, index) => {
                    debugLog(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message);
                    const meta = message.metadata || {};
                    if (message.role === 'user' && meta.system_injected_image) {
                        debugLog('跳过系统代发的图片消息（仅用于模型查看，不在前端展示）');
                        return;
                    }
                    
                    if (message.role === 'user') {
                        // 用户消息 - 先结束之前的assistant消息
                        if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
                            this.messages.push(currentAssistantMessage);
                            currentAssistantMessage = null;
                        }
                        const images = message.images || (message.metadata && message.metadata.images) || [];
                        if (Array.isArray(images) && images.length) {
                            historyHasImages = true;
                        }
                        this.messages.push({
                            role: 'user',
                            content: message.content || '',
                            images
                        });
                        debugLog('添加用户消息:', message.content?.substring(0, 50) + '...');
                        
                    } else if (message.role === 'assistant') {
                        // AI消息 - 如果没有当前assistant消息，创建一个
                        if (!currentAssistantMessage) {
                            currentAssistantMessage = {
                                role: 'assistant',
                                actions: [],
                                streamingThinking: '',
                                streamingText: '',
                                currentStreamingType: null,
                                activeThinkingId: null,
                                awaitingFirstContent: false,
                                generatingLabel: ''
                            };
                        }
                        
                        const content = message.content || '';
                        const reasoningText = (message.reasoning_content || '').trim();
                        
                        if (reasoningText) {
                            const blockId = `history-thinking-${Date.now()}-${Math.random().toString(36).slice(2)}`;
                            currentAssistantMessage.actions.push({
                                id: `history-think-${Date.now()}-${Math.random()}`,
                                type: 'thinking',
                                content: reasoningText,
                                streaming: false,
                                timestamp: Date.now(),
                                blockId
                            });
                            debugLog('添加思考内容:', reasoningText.substring(0, 50) + '...');
                        }
                        
                        // 处理普通文本内容（移除思考标签后的内容）
                        const metadata = message.metadata || {};
                        const appendPayloadMeta = metadata.append_payload;
                        const modifyPayloadMeta = metadata.modify_payload;
                        const isAppendMessage = message.name === 'append_to_file';
                        const isModifyMessage = message.name === 'modify_file';
                        const containsAppendMarkers = /<<<\s*(APPEND|MODIFY)/i.test(content || '') || /<<<END_\s*(APPEND|MODIFY)>>>/i.test(content || '');
                        
                        const textContent = content.trim();
                            
                        if (appendPayloadMeta) {
                            currentAssistantMessage.actions.push({
                                id: `history-append-payload-${Date.now()}-${Math.random()}`,
                                type: 'append_payload',
                                append: {
                                    path: appendPayloadMeta.path || '未知文件',
                                    forced: !!appendPayloadMeta.forced,
                                    success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success,
                                    lines: appendPayloadMeta.lines ?? null,
                                    bytes: appendPayloadMeta.bytes ?? null
                                },
                                timestamp: Date.now()
                            });
                            debugLog('添加append占位信息:', appendPayloadMeta.path);
                        } else if (modifyPayloadMeta) {
                            currentAssistantMessage.actions.push({
                                id: `history-modify-payload-${Date.now()}-${Math.random()}`,
                                type: 'modify_payload',
                                modify: {
                                    path: modifyPayloadMeta.path || '未知文件',
                                    total: modifyPayloadMeta.total_blocks ?? null,
                                    completed: modifyPayloadMeta.completed || [],
                                    failed: modifyPayloadMeta.failed || [],
                                    forced: !!modifyPayloadMeta.forced,
                                    details: modifyPayloadMeta.details || []
                                },
                                timestamp: Date.now()
                            });
                            debugLog('添加modify占位信息:', modifyPayloadMeta.path);
                        }
                        
                        if (textContent && !appendPayloadMeta && !modifyPayloadMeta && !isAppendMessage && !isModifyMessage && !containsAppendMarkers) {
                            currentAssistantMessage.actions.push({
                                id: `history-text-${Date.now()}-${Math.random()}`,
                                type: 'text',
                                content: textContent,
                                streaming: false,
                                timestamp: Date.now()
                            });
                            debugLog('添加文本内容:', textContent.substring(0, 50) + '...');
                        }
                        
                        // 处理工具调用
                        if (message.tool_calls && Array.isArray(message.tool_calls)) {
                            message.tool_calls.forEach((toolCall, tcIndex) => {
                                let arguments_obj = {};
                                try {
                                    arguments_obj = typeof toolCall.function.arguments === 'string' 
                                        ? JSON.parse(toolCall.function.arguments || '{}')
                                        : (toolCall.function.arguments || {});
                                } catch (e) {
                                    console.warn('解析工具参数失败:', e);
                                    arguments_obj = {};
                                }
                                
                                currentAssistantMessage.actions.push({
                                    id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`,
                                    type: 'tool',
                                    tool: {
                                        id: toolCall.id,
                                        name: toolCall.function.name,
                                        arguments: arguments_obj,
                                        intent_full: arguments_obj.intent || '',
                                        intent_rendered: arguments_obj.intent || '',
                                        status: 'preparing',
                                        result: null
                                    },
                                    timestamp: Date.now()
                                });
                                debugLog('添加工具调用:', toolCall.function.name);
                            });
                        }
                        
                    } else if (message.role === 'tool') {
                        // 工具结果 - 更新当前assistant消息中对应的工具
                        if (currentAssistantMessage) {
                            // 查找对应的工具action - 使用更灵活的匹配
                            let toolAction = null;
                            
                            // 优先按tool_call_id匹配
                            if (message.tool_call_id) {
                                toolAction = currentAssistantMessage.actions.find(action => 
                                    action.type === 'tool' && 
                                    action.tool.id === message.tool_call_id
                                );
                            }
                            
                            // 如果找不到，按name匹配最后一个同名工具
                            if (!toolAction && message.name) {
                                const sameNameTools = currentAssistantMessage.actions.filter(action => 
                                    action.type === 'tool' && 
                                    action.tool.name === message.name
                                );
                                toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个
                            }
                            
                            if (toolAction) {
                                // 解析工具结果（优先使用JSON，其次使用元数据的 tool_payload，以保证搜索结果在刷新后仍可展示）
                                let result;
                                try {
                                    result = JSON.parse(message.content);
                                } catch (e) {
                                    if (message.metadata && message.metadata.tool_payload) {
                                        result = message.metadata.tool_payload;
                                    } else {
                                        result = {
                                            output: message.content,
                                            success: true
                                        };
                                    }
                                }
                                
                                toolAction.tool.status = 'completed';
                                toolAction.tool.result = result;
                                if (result && typeof result === 'object') {
                                    if (result.error) {
                                        toolAction.tool.message = result.error;
                                    } else if (result.message && !toolAction.tool.message) {
                                        toolAction.tool.message = result.message;
                                    }
                                }
                                if (message.name === 'append_to_file' && result && result.message) {
                                    toolAction.tool.message = result.message;
                                }
                                debugLog(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`);
                                
                                // append_to_file 的摘要在 append_payload 占位中呈现，此处无需重复
                            } else {
                                console.warn('找不到对应的工具调用:', message.name, message.tool_call_id);
                            }
                        }
                        
                    } else {
                        // 其他类型消息（如system）- 先结束当前assistant消息
                        if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
                            this.messages.push(currentAssistantMessage);
                            currentAssistantMessage = null;
                        }
                        
                        debugLog('处理其他类型消息:', message.role);
                        this.messages.push({
                            role: message.role,
                            content: message.content || ''
                        });
                    }
                });
                
                // 处理最后一个assistant消息
                if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
                    this.messages.push(currentAssistantMessage);
                }

                this.conversationHasImages = historyHasImages;
                
                debugLog(`历史消息渲染完成，共 ${this.messages.length} 条消息`);
                this.logMessageState('renderHistoryMessages:after-render');
                this.lastHistoryLoadedConversationId = this.currentConversationId || null;
                
                // 强制更新视图
                this.$forceUpdate();
                
                // 确保滚动到底部
                this.$nextTick(() => {
                    this.scrollToBottom();
                    setTimeout(() => {
                        const blockCount = this.$el && this.$el.querySelectorAll
                            ? this.$el.querySelectorAll('.message-block').length
                            : 'N/A';
                        debugLog('[Messages] DOM 渲染统计', {
                            blocks: blockCount,
                            conversationId: this.currentConversationId
                        });
                    }, 0);
                });
            },
            
            async createNewConversation() {
                debugLog('创建新对话...');
                traceLog('createNewConversation:start', {
                    currentConversationId: this.currentConversationId,
                    convCount: Array.isArray(this.conversations) ? this.conversations.length : 'n/a'
                });
                this.logMessageState('createNewConversation:start');
                
                try {
                    const response = await fetch('/api/conversations', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                            thinking_mode: this.thinkingMode,
                            mode: this.runMode
                        })
                    });
                    
                    const result = await response.json();
                    
                    if (result.success) {
                        const newConversationId = result.conversation_id;
                        debugLog('新对话创建成功:', newConversationId);
                        traceLog('createNewConversation:created', { newConversationId });
                        
                        // 在本地列表插入占位，避免等待刷新
                        const placeholder = {
                            id: newConversationId,
                            title: '新对话',
                            updated_at: new Date().toISOString(),
                            total_messages: 0,
                            total_tools: 0
                        };
                        this.conversations = [
                            placeholder,
                            ...this.conversations.filter(conv => conv && conv.id !== newConversationId)
                        ];
                        
                        // 直接加载新对话，确保状态一致
                        // 如果 socket 事件已把 currentConversationId 设置为新ID，则强制加载一次以同步状态
                        await this.loadConversation(newConversationId, { force: true });
                        traceLog('createNewConversation:after-load', {
                            newConversationId,
                            currentConversationId: this.currentConversationId
                        });

                        // 刷新对话列表获取最新统计
                        this.conversationsOffset = 0;
                        await this.loadConversationsList();
                        traceLog('createNewConversation:after-refresh', {
                            newConversationId,
                            conversationsLen: Array.isArray(this.conversations) ? this.conversations.length : 'n/a'
                        });
                    } else {
                        console.error('创建对话失败:', result.message);
                        this.uiPushToast({
                            title: '创建对话失败',
                            message: result.message || '服务器未返回成功状态',
                            type: 'error'
                        });
                    }
                } catch (error) {
                    console.error('创建对话异常:', error);
                    this.uiPushToast({
                        title: '创建对话异常',
                        message: error.message || String(error),
                        type: 'error'
                    });
                }
            },

            async deleteConversation(conversationId) {
                const confirmed = await this.confirmAction({
                    title: '删除对话',
                    message: '确定要删除这个对话吗？删除后无法恢复。',
                    confirmText: '删除',
                    cancelText: '取消'
                });
                if (!confirmed) {
                    return;
                }
            
                debugLog('删除对话:', conversationId);
                this.logMessageState('deleteConversation:start', { conversationId });
                
                try {
                    const response = await fetch(`/api/conversations/${conversationId}`, {
                        method: 'DELETE'
                    });
                    
                    const result = await response.json();
                    
                    if (result.success) {
                        debugLog('对话删除成功');
                        
                        // 如果删除的是当前对话，清空界面
                        if (conversationId === this.currentConversationId) {
                            this.logMessageState('deleteConversation:before-clear', { conversationId });
                            this.messages = [];
                            this.logMessageState('deleteConversation:after-clear', { conversationId });
                            this.currentConversationId = null;
                            this.currentConversationTitle = '';
                            this.resetAllStates(`deleteConversation:${conversationId}`);
                            this.resetTokenStatistics();
                            history.replaceState({}, '', '/new');
                        }
                        
                        // 刷新对话列表
                        this.conversationsOffset = 0;
                        await this.loadConversationsList();
                        
                    } else {
                        console.error('删除对话失败:', result.message);
                        this.uiPushToast({
                            title: '删除对话失败',
                            message: result.message || '服务器未返回成功状态',
                            type: 'error'
                        });
                    }
                } catch (error) {
                    console.error('删除对话异常:', error);
                    this.uiPushToast({
                        title: '删除对话异常',
                        message: error.message || String(error),
                        type: 'error'
                    });
                }
            },

            async duplicateConversation(conversationId) {
                debugLog('复制对话:', conversationId);
                try {
                    const response = await fetch(`/api/conversations/${conversationId}/duplicate`, {
                        method: 'POST'
                    });

                    const result = await response.json();

                    if (response.ok && result.success) {
                        const newId = result.duplicate_conversation_id;
                        if (newId) {
                            this.currentConversationId = newId;
                        }

                        this.conversationsOffset = 0;
                        await this.loadConversationsList();
                    } else {
                        const message = result.message || result.error || '复制失败';
                        this.uiPushToast({
                            title: '复制对话失败',
                            message,
                            type: 'error'
                        });
                    }
                } catch (error) {
                    console.error('复制对话异常:', error);
                    this.uiPushToast({
                        title: '复制对话异常',
                        message: error.message || String(error),
                        type: 'error'
                    });
                }
            },
            
            searchConversations() {
                // 简单的搜索功能，实际实现可以调用搜索API
                if (this.searchTimer) {
                    clearTimeout(this.searchTimer);
                }
                
                this.searchTimer = setTimeout(() => {
                    if (this.searchQuery.trim()) {
                        debugLog('搜索对话:', this.searchQuery);
                        // TODO: 实现搜索API调用
                        // this.searchConversationsAPI(this.searchQuery);
                    } else {
                        // 清空搜索，重新加载全部对话
                        this.conversationsOffset = 0;
                        this.loadConversationsList();
                    }
                }, 300);
            },

            handleSidebarSearch(value) {
                this.searchQuery = value;
                this.searchConversations();
            },
            
            handleMobileOverlaySelect(conversationId) {
                this.loadConversation(conversationId);
                this.closeMobileOverlay();
            },
            handleMobilePersonalClick() {
                this.closeMobileOverlay();
                this.uiSetMobileOverlayMenuOpen(false);
                this.openPersonalPage();
            },
            
            toggleSidebar() {
                if (this.isMobileViewport && this.activeMobileOverlay === 'conversation') {
                    this.closeMobileOverlay();
                    return;
                }
                this.uiToggleSidebar();
            },

            togglePanelMenu() {
                this.uiTogglePanelMenu();
            },

            selectPanelMode(mode) {
                this.uiSetPanelMode(mode);
                this.uiSetPanelMenuOpen(false);
            },

            openPersonalPage() {
                if (this.isPolicyBlocked('block_personal_space', '个人空间已被管理员禁用')) {
                    return;
                }
                this.personalizationOpenDrawer();
            },

            fetchTodoList() {
                return this.fileFetchTodoList();
            },

            fetchSubAgents() {
                return this.subAgentFetch();
            },

            async toggleThinkingMode() {
                await this.handleCycleRunMode();
            },

            handleQuickModeToggle() {
                if (!this.isConnected || this.streamingMessage) {
                    return;
                }
                this.handleCycleRunMode();
            },

            triggerFileUpload() {
                if (this.uploading) {
                    return;
                }
                const input = this.getComposerElement('fileUploadInput');
                if (input) {
                    input.click();
                }
            },

            handleFileSelected(files) {
                const policyStore = usePolicyStore();
                if (policyStore.uiBlocks?.block_upload) {
                    this.uiPushToast({
                        title: '上传被禁用',
                        message: '已被管理员禁用上传功能',
                        type: 'warning'
                    });
                    return;
                }
                this.uploadHandleSelected(files);
            },

            handleSendOrStop() {
                if (this.composerBusy) {
                    this.stopTask();
                } else {
                    this.sendMessage();
                }
            },

            sendMessage() {
                if (this.streamingUi || !this.isConnected) {
                    return;
                }

                const text = (this.inputMessage || '').trim();
                const images = Array.isArray(this.selectedImages) ? this.selectedImages.slice(0, 9) : [];
                const hasText = text.length > 0;
                const hasImages = images.length > 0;

                if (!hasText && !hasImages) {
                    return;
                }

                const quotaType = this.thinkingMode ? 'thinking' : 'fast';
                if (this.isQuotaExceeded(quotaType)) {
                    this.showQuotaToast({ type: quotaType });
                    return;
                }

                if (hasImages && this.currentModelKey !== 'qwen3-vl-plus') {
                    this.uiPushToast({
                        title: '当前模型不支持图片',
                        message: '请切换到 Qwen-VL 再发送图片',
                        type: 'error'
                    });
                    return;
                }

                const message = text;
                const isCommand = hasText && !hasImages && message.startsWith('/');
                if (isCommand) {
                    this.socket.emit('send_command', { command: message });
                    this.inputClearMessage();
                    this.inputClearSelectedImages();
                    this.autoResizeInput();
                    return;
                }
                
                const wasBlank = this.isConversationBlank();
                if (wasBlank) {
                    this.blankHeroExiting = true;
                    this.blankHeroActive = true;
                    setTimeout(() => {
                        this.blankHeroExiting = false;
                        this.blankHeroActive = false;
                    }, 320);
                }
                
                // 标记任务进行中，直到任务完成或用户手动停止
                this.taskInProgress = true;
                this.chatAddUserMessage(message, images);
                this.socket.emit('send_message', { message: message, images, conversation_id: this.currentConversationId });
                if (typeof this.monitorShowPendingReply === 'function') {
                    this.monitorShowPendingReply();
                }
                this.inputClearMessage();
                this.inputClearSelectedImages();
                this.inputSetImagePickerOpen(false);
                this.inputSetLineCount(1);
                this.inputSetMultiline(false);
                if (hasImages) {
                    this.conversationHasImages = true;
                }
                if (this.autoScrollEnabled) {
                    this.scrollToBottom();
                }
                this.autoResizeInput();
                
                // 发送消息后延迟更新当前上下文Token（关键修复：恢复原逻辑）
                setTimeout(() => {
                    if (this.currentConversationId) {
                        this.updateCurrentContextTokens();
                    }
                }, 1000);
            },
            
            // 新增：停止任务方法
            stopTask() {
                const canStop = this.composerBusy && !this.stopRequested;
                if (!canStop) {
                    return;
                }

                const shouldDropToolEvents = this.streamingUi;
                this.stopRequested = true;
                this.dropToolEvents = shouldDropToolEvents;
                if (this.socket) {
                    this.socket.emit('stop_task');
                    debugLog('发送停止请求');
                }

                // 立即清理前端状态，避免出现“不可输入也不可停止”的卡死状态
                this.clearPendingTools('user_stop');
                this.streamingMessage = false;
                this.taskInProgress = false;
                this.forceUnlockMonitor('user_stop');
            },

            forceUnlockMonitor(reason = 'unspecified') {
                try {
                    this.monitorResetVisual({
                        preserveBubble: true,
                        preservePointer: true,
                        preserveWindows: true,
                        preserveQueue: false,
                        preservePendingResults: false,
                        preserveAwaitingTools: false
                    });
                    debugLog('Monitor unlocked', { reason });
                } catch (error) {
                    console.warn('强制解锁监控面板失败', error);
                }
            },
            clearPendingTools(reason = 'unspecified') {
                debugLog('清理未完成工具', { reason });
                if (Array.isArray(this.messages)) {
                    this.messages.forEach(msg => {
                        if (!msg || msg.role !== 'assistant' || !Array.isArray(msg.actions)) {
                            return;
                        }
                        msg.actions.forEach(action => {
                            if (action && action.type === 'tool' && action.tool) {
                                action.tool.status = action.tool.status || 'cancelled';
                                action.tool.awaiting_content = false;
                                action.streaming = false;
                            }
                        });
                    });
                }
                this.toolResetTracking();
                this.preparingTools.clear();
                this.activeTools.clear();
                this.toolActionIndex.clear();
                this.toolStacks.clear();
                this.stopRequested = false;
                this.taskInProgress = false;
            },
            
            async clearChat() {
                const confirmed = await this.confirmAction({
                    title: '清除对话',
                    message: '确定要清除所有对话记录吗？该操作不可撤销。',
                    confirmText: '清除',
                    cancelText: '取消'
                });
                if (confirmed) {
                    this.socket.emit('send_command', { command: '/clear' });
                }
            },

            async compressConversation() {
                if (!this.currentConversationId) {
                    this.uiPushToast({
                        title: '无法压缩',
                        message: '当前没有可压缩的对话。',
                        type: 'info'
                    });
                    return;
                }

                if (this.compressing) {
                    return;
                }

                const confirmed = await this.confirmAction({
                    title: '压缩对话',
                    message: '确定要压缩当前对话记录吗？压缩后会生成新的对话副本。',
                    confirmText: '压缩',
                    cancelText: '取消'
                });
                if (!confirmed) {
                    return;
                }

                this.compressing = true;

                try {
                    const response = await fetch(`/api/conversations/${this.currentConversationId}/compress`, {
                        method: 'POST'
                    });

                    const result = await response.json();

                    if (response.ok && result.success) {
                        const newId = result.compressed_conversation_id;
                        if (newId) {
                            this.currentConversationId = newId;
                        }
                        debugLog('对话压缩完成:', result);
                    } else {
                        const message = result.message || result.error || '压缩失败';
                        this.uiPushToast({
                            title: '压缩失败',
                            message,
                            type: 'error'
                        });
                    }
                } catch (error) {
                    console.error('压缩对话异常:', error);
                    this.uiPushToast({
                        title: '压缩对话异常',
                        message: error.message || '请稍后重试',
                        type: 'error'
                    });
                } finally {
                    this.compressing = false;
                }
            },

            toggleToolMenu() {
                if (!this.isConnected) {
                    return;
                }
                if (this.isPolicyBlocked('block_tool_toggle', '工具启用/禁用已被管理员锁定')) {
                    return;
                }
                this.modeMenuOpen = false;
                this.modelMenuOpen = false;
                const nextState = this.inputToggleToolMenu();
                if (nextState) {
                    this.inputSetSettingsOpen(false);
                    if (!this.quickMenuOpen) {
                        this.inputOpenQuickMenu();
                    }
                    this.loadToolSettings(true);
                } else {
                    this.inputSetToolMenuOpen(false);
                }
            },

            toggleQuickMenu() {
                if (!this.isConnected) {
                    return;
                }
                const opened = this.inputToggleQuickMenu();
                if (!opened) {
                    this.modeMenuOpen = false;
                    this.modelMenuOpen = false;
                }
            },

            closeQuickMenu() {
                this.inputCloseMenus();
                this.modeMenuOpen = false;
                this.modelMenuOpen = false;
            },

            async openImagePicker() {
                if (this.currentModelKey !== 'qwen3-vl-plus') {
                    this.uiPushToast({
                        title: '当前模型不支持图片',
                        message: '请选择 Qwen-VL 后再发送图片',
                        type: 'error'
                    });
                    return;
                }
                this.closeQuickMenu();
                this.inputSetImagePickerOpen(true);
                await this.loadWorkspaceImages();
            },

            closeImagePicker() {
                this.inputSetImagePickerOpen(false);
            },

            async loadWorkspaceImages() {
                this.imageLoading = true;
                try {
                    const entries = await this.fetchAllImageEntries('');
                    this.imageEntries = entries;
                    if (!entries.length) {
                        this.uiPushToast({
                            title: '未找到图片',
                            message: '工作区内没有可用的图片文件',
                            type: 'info'
                        });
                    }
                } catch (error) {
                    console.error('加载图片列表失败', error);
                    this.uiPushToast({
                        title: '加载图片失败',
                        message: error?.message || '请稍后重试',
                        type: 'error'
                    });
                } finally {
                    this.imageLoading = false;
                }
            },

            async fetchAllImageEntries(startPath = '') {
                const queue: string[] = [startPath || ''];
                const visited = new Set<string>();
                const results: Array<{ name: string; path: string }> = [];
                const exts = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.svg']);
                const maxFolders = 120;

                while (queue.length && visited.size < maxFolders) {
                    const path = queue.shift() || '';
                    if (visited.has(path)) {
                        continue;
                    }
                    visited.add(path);
                    try {
                        const resp = await fetch(`/api/gui/files/entries?path=${encodeURIComponent(path)}`, {
                            method: 'GET',
                            credentials: 'include',
                            headers: { Accept: 'application/json' }
                        });
                        const data = await resp.json().catch(() => null);
                        if (!data?.success) {
                            continue;
                        }
                        const items = Array.isArray(data?.data?.items) ? data.data.items : [];
                        for (const item of items) {
                            const rawPath =
                                item?.path ||
                                [path, item?.name].filter(Boolean).join('/').replace(/\\/g, '/').replace(/\/{2,}/g, '/');
                            const type = String(item?.type || '').toLowerCase();
                            if (type === 'directory' || type === 'folder') {
                                queue.push(rawPath);
                                continue;
                            }
                            const ext =
                                String(item?.extension || '').toLowerCase() ||
                                (rawPath.includes('.') ? `.${rawPath.split('.').pop()?.toLowerCase()}` : '');
                            if (exts.has(ext)) {
                                results.push({
                                    name: item?.name || rawPath.split('/').pop() || rawPath,
                                    path: rawPath
                                });
                                if (results.length >= 400) {
                                    return results;
                                }
                            }
                        }
                    } catch (error) {
                        console.warn('遍历文件夹失败', path, error);
                    }
                }
                return results;
            },

            handleImagesConfirmed(list) {
                this.inputSetSelectedImages(Array.isArray(list) ? list : []);
                this.inputSetImagePickerOpen(false);
            },
            handleRemoveImage(path) {
                this.inputRemoveSelectedImage(path);
            },

            handleQuickUpload() {
                if (this.uploading || !this.isConnected) {
                    return;
                }
                if (this.isPolicyBlocked('block_upload', '上传功能已被管理员禁用')) {
                    return;
                }
                this.triggerFileUpload();
            },

            toggleModeMenu() {
                if (!this.isConnected || this.streamingMessage) {
                    return;
                }
                const next = !this.modeMenuOpen;
                this.modeMenuOpen = next;
                if (next) {
                    this.modelMenuOpen = false;
                }
                if (next) {
                    this.inputSetToolMenuOpen(false);
                    this.inputSetSettingsOpen(false);
                    if (!this.quickMenuOpen) {
                        this.inputOpenQuickMenu();
                    }
                }
            },

            toggleModelMenu() {
                if (!this.isConnected || this.streamingMessage) {
                    return;
                }
                const next = !this.modelMenuOpen;
                this.modelMenuOpen = next;
                if (next) {
                    this.modeMenuOpen = false;
                    this.inputSetToolMenuOpen(false);
                    this.inputSetSettingsOpen(false);
                    if (!this.quickMenuOpen) {
                        this.inputOpenQuickMenu();
                    }
                }
            },

            async handleModeSelect(mode) {
                if (!this.isConnected || this.streamingMessage) {
                    return;
                }
                await this.setRunMode(mode);
            },

            async handleModelSelect(key) {
                if (!this.isConnected || this.streamingMessage) {
                    return;
                }
                const policyStore = usePolicyStore();
                if (policyStore.isModelDisabled(key)) {
                    this.uiPushToast({
                        title: '模型被禁用',
                        message: '被管理员强制禁用',
                        type: 'warning'
                    });
                    return;
                }
                if (this.conversationHasImages && key !== 'qwen3-vl-plus') {
                    this.uiPushToast({
                        title: '切换失败',
                        message: '当前对话包含图片，仅支持 Qwen-VL',
                        type: 'error'
                    });
                    return;
                }
                const modelStore = useModelStore();
                const prev = this.currentModelKey;
                try {
                    const resp = await fetch('/api/model', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ model_key: key })
                    });
                    const payload = await resp.json();
                    if (!resp.ok || !payload.success) {
                        throw new Error(payload.error || payload.message || '切换失败');
                    }
                    const data = payload.data || {};
                    modelStore.setModel(data.model_key || key);
                    if (data.run_mode) {
                        this.runMode = data.run_mode;
                        this.thinkingMode = data.thinking_mode ?? (data.run_mode !== 'fast');
                    } else {
                        // 前端兼容策略：根据模型特性自动调整运行模式
                        if (key === 'qwen3-vl-plus') {
                            // Qwen-VL 不支持深度思考，若当前为 deep 则回落到思考模式
                            if (this.runMode === 'deep') {
                                this.runMode = 'thinking';
                                this.thinkingMode = true;
                            } else {
                                this.thinkingMode = this.runMode !== 'fast';
                            }
                        } else if (key === 'qwen3-max') {
                            // Qwen-Max 仅快速模式
                            this.runMode = 'fast';
                            this.thinkingMode = false;
                        } else {
                            this.thinkingMode = this.runMode !== 'fast';
                        }
                    }
                    this.uiPushToast({
                        title: '模型已切换',
                        message: modelStore.currentModel?.label || key,
                        type: 'success'
                    });
                } catch (error) {
                    modelStore.setModel(prev);
                    const msg = error instanceof Error ? error.message : String(error || '切换失败');
                    this.uiPushToast({
                        title: '切换模型失败',
                        message: msg,
                        type: 'error'
                    });
                } finally {
                    this.modelMenuOpen = false;
                    this.inputCloseMenus();
                    this.inputSetQuickMenuOpen(false);
                }
            },

            async handleCycleRunMode() {
                const modes: Array<'fast' | 'thinking' | 'deep'> = ['fast', 'thinking', 'deep'];
                const currentMode = this.resolvedRunMode;
                const currentIndex = modes.indexOf(currentMode);
                const nextMode = modes[(currentIndex + 1) % modes.length];
                await this.setRunMode(nextMode);
            },

            async setRunMode(mode, options = {}) {
                if (!this.isConnected || this.streamingMessage) {
                    this.modeMenuOpen = false;
                    return;
                }
                const modelStore = useModelStore();
                const fastOnly = modelStore.currentModel?.fastOnly;
                const currentModelKey = modelStore.currentModel?.key;
                if (fastOnly && mode !== 'fast') {
                    if (!options.suppressToast) {
                        this.uiPushToast({
                            title: '模式不可用',
                            message: 'Qwen-Max只支持快速模式',
                            type: 'warning'
                        });
                    }
                    this.modeMenuOpen = false;
                    this.inputCloseMenus();
                    return;
                }
                // Qwen-VL 不支持深度思考模式
                if (currentModelKey === 'qwen3-vl-plus' && mode === 'deep') {
                    if (!options.suppressToast) {
                        this.uiPushToast({
                            title: '模式不可用',
                            message: 'Qwen-VL 不支持深度思考模式，请使用快速或思考模式',
                            type: 'warning'
                        });
                    }
                    this.modeMenuOpen = false;
                    this.inputCloseMenus();
                    return;
                }
                if (mode === this.resolvedRunMode) {
                    this.modeMenuOpen = false;
                    this.closeQuickMenu();
                    return;
                }
                try {
                    const response = await fetch('/api/thinking-mode', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({ mode })
                    });
                    const payload = await response.json();
                    if (!response.ok || !payload.success) {
                        throw new Error(payload.message || payload.error || '切换失败');
                    }
                    const data = payload.data || {};
                    this.thinkingMode = typeof data.thinking_mode === 'boolean' ? data.thinking_mode : mode !== 'fast';
                    this.runMode = data.mode || mode;
                } catch (error) {
                    console.error('切换运行模式失败:', error);
                    const message = error instanceof Error ? error.message : String(error || '未知错误');
                    this.uiPushToast({
                        title: '切换思考模式失败',
                        message: message || '请稍后重试',
                        type: 'error'
                    });
                } finally {
                    this.modeMenuOpen = false;
                    this.inputCloseMenus();
                }
            },

            handleInputChange() {
                this.autoResizeInput();
            },

            handleInputFocus() {
                this.inputSetFocused(true);
                this.closeQuickMenu();
            },

            handleInputBlur() {
                this.inputSetFocused(false);
            },

            handleRealtimeTerminalClick() {
                if (!this.isConnected) {
                    return;
                }
                if (this.isPolicyBlocked('block_realtime_terminal', '实时终端已被管理员禁用')) {
                    return;
                }
                this.openRealtimeTerminal();
            },

            handleFocusPanelToggleClick() {
                if (!this.isConnected) {
                    return;
                }
                if (this.isPolicyBlocked('block_focus_panel', '聚焦面板已被管理员禁用')) {
                    return;
                }
                this.toggleFocusPanel();
            },

            handleTokenPanelToggleClick() {
                if (!this.currentConversationId) {
                    return;
                }
                if (this.isPolicyBlocked('block_token_panel', '用量统计已被管理员禁用')) {
                    return;
                }
                this.toggleTokenPanel();
            },

            handleCompressConversationClick() {
                if (this.compressing || this.streamingMessage || !this.isConnected) {
                    return;
                }
                if (this.isPolicyBlocked('block_compress_conversation', '压缩对话已被管理员禁用')) {
                    return;
                }
                this.compressConversation();
            },

            openReviewDialog() {
                if (this.isPolicyBlocked('block_conversation_review', '对话引用已被管理员禁用')) {
                    return;
                }
                if (!this.isConnected) {
                    this.uiPushToast({
                        title: '无法使用',
                        message: '当前未连接，无法生成回顾文件',
                        type: 'warning'
                    });
                    return;
                }
                if (!this.conversations.length && !this.conversationsLoading) {
                    this.loadConversationsList();
                }
                const fallback = this.conversations.find((c) => c.id !== this.currentConversationId);
                if (!fallback) {
                    this.uiPushToast({
                        title: '暂无可用对话',
                        message: '没有可供回顾的其他对话记录',
                        type: 'info'
                    });
                    return;
                }
                this.reviewSelectedConversationId = fallback.id;
                this.reviewDialogOpen = true;
                this.reviewPreviewLines = [];
                this.reviewPreviewError = null;
                this.reviewGeneratedPath = null;
                this.loadReviewPreview(fallback.id);
                this.closeQuickMenu();
            },

            handleReviewSelect(id) {
                if (id === this.currentConversationId) {
                    this.uiPushToast({
                        title: '无法引用当前对话',
                        message: '请选择其他对话生成回顾',
                        type: 'warning'
                    });
                    return;
                }
                this.reviewSelectedConversationId = id;
                this.loadReviewPreview(id);
            },

            async handleConfirmReview() {
                if (this.reviewSubmitting) return;
                if (!this.reviewSelectedConversationId) {
                    this.uiPushToast({
                        title: '请选择对话',
                        message: '请选择要生成回顾的对话记录',
                        type: 'info'
                    });
                    return;
                }
                if (this.reviewSelectedConversationId === this.currentConversationId) {
                    this.uiPushToast({
                        title: '无法引用当前对话',
                        message: '请选择其他对话生成回顾',
                        type: 'warning'
                    });
                    return;
                }
                if (!this.currentConversationId) {
                    this.uiPushToast({
                        title: '无法发送',
                        message: '当前没有活跃对话，无法自动发送提示消息',
                        type: 'warning'
                    });
                    return;
                }

                this.reviewSubmitting = true;
                try {
                    const { path, char_count } = await this.generateConversationReview(this.reviewSelectedConversationId);
                    if (!path) {
                        throw new Error('未获取到生成的文件路径');
                    }
                    const count = typeof char_count === 'number' ? char_count : 0;
                    this.reviewGeneratedPath = path;
                    const suggestion =
                        count && count <= 10000
                            ? '建议直接完整阅读。'
                            : '建议使用 read 工具进行搜索或分段阅读。';
                    if (this.reviewSendToModel) {
                        const message = `帮我继续这个任务，对话文件在 ${path}，文件长 ${count || '未知'} 字符，${suggestion} 请阅读文件了解后，不要直接继续工作，而是向我汇报你的理解，然后等我做出指示。`;
                        const sent = this.sendAutoUserMessage(message);
                        if (sent) {
                            this.reviewDialogOpen = false;
                        }
                    } else {
                        this.uiPushToast({
                            title: '回顾文件已生成',
                            message: path,
                            type: 'success'
                        });
                    }
                } catch (error) {
                    const msg = error instanceof Error ? error.message : String(error || '生成失败');
                    this.uiPushToast({
                        title: '生成回顾失败',
                        message: msg,
                        type: 'error'
                    });
                } finally {
                    this.reviewSubmitting = false;
                }
            },

            async loadReviewPreview(conversationId) {
                this.reviewPreviewLoading = true;
                this.reviewPreviewError = null;
                this.reviewPreviewLines = [];
                try {
                    const resp = await fetch(`/api/conversations/${conversationId}/review_preview?limit=${this.reviewPreviewLimit}`);
                    const payload = await resp.json().catch(() => ({}));
                    if (!resp.ok || !payload?.success) {
                        const msg = payload?.message || payload?.error || '获取预览失败';
                        throw new Error(msg);
                    }
                    this.reviewPreviewLines = payload?.data?.preview || [];
                } catch (error) {
                    const msg = error instanceof Error ? error.message : String(error || '获取预览失败');
                    this.reviewPreviewError = msg;
                } finally {
                    this.reviewPreviewLoading = false;
                }
            },

            async generateConversationReview(conversationId) {
                const response = await fetch(`/api/conversations/${conversationId}/review`, {
                    method: 'POST'
                });
                const payload = await response.json().catch(() => ({}));
                if (!response.ok || !payload?.success) {
                    const msg = payload?.message || payload?.error || '生成失败';
                    throw new Error(msg);
                }
                const data = payload.data || payload;
                return {
                    path: data.path || data.file_path || data.relative_path,
                    char_count: data.char_count ?? data.length ?? data.size ?? 0
                };
            },

            sendAutoUserMessage(text) {
                const message = (text || '').trim();
                if (!message || !this.isConnected) {
                    return false;
                }
                const quotaType = this.thinkingMode ? 'thinking' : 'fast';
                if (this.isQuotaExceeded(quotaType)) {
                    this.showQuotaToast({ type: quotaType });
                    return false;
                }
                this.taskInProgress = true;
                this.chatAddUserMessage(message, []);
                if (this.socket) {
                    this.socket.emit('send_message', {
                        message,
                        images: [],
                        conversation_id: this.currentConversationId
                    });
                }
                if (typeof this.monitorShowPendingReply === 'function') {
                    this.monitorShowPendingReply();
                }
                if (this.autoScrollEnabled) {
                    this.scrollToBottom();
                }
                this.autoResizeInput();
                setTimeout(() => {
                    if (this.currentConversationId) {
                        this.updateCurrentContextTokens();
                    }
                }, 1000);
                return true;
            },

            autoResizeInput() {
                this.$nextTick(() => {
                    const textarea = this.getComposerElement('stadiumInput');
                    if (!textarea || !(textarea instanceof HTMLTextAreaElement)) {
                        return;
                    }
                    const previousHeight = textarea.offsetHeight;
                    textarea.style.height = 'auto';
                    const computedStyle = window.getComputedStyle(textarea);
                    const lineHeight = parseFloat(computedStyle.lineHeight || '20') || 20;
                    const maxHeight = lineHeight * 6;
                    const targetHeight = Math.min(textarea.scrollHeight, maxHeight);
                    this.inputSetLineCount(Math.max(1, Math.round(targetHeight / lineHeight)));
                    this.inputSetMultiline(targetHeight > lineHeight * 1.4);
                    if (Math.abs(targetHeight - previousHeight) <= 0.5) {
                        textarea.style.height = `${targetHeight}px`;
                        return;
                    }
                    textarea.style.height = `${previousHeight}px`;
                    void textarea.offsetHeight;
                    requestAnimationFrame(() => {
                        textarea.style.height = `${targetHeight}px`;
                    });
                });
            },

            handleClickOutsideQuickMenu(event) {
                if (!this.quickMenuOpen) {
                    return;
                }
                const shell = this.getComposerElement('stadiumShellOuter') || this.getComposerElement('compactInputShell');
                if (shell && shell.contains(event.target)) {
                    return;
                }
                this.closeQuickMenu();
            },
            
            handleClickOutsidePanelMenu(event) {
                if (!this.panelMenuOpen) {
                    return;
                }
                const leftPanel = this.$refs.leftPanel;
                const wrapper = leftPanel && leftPanel.panelMenuWrapper ? leftPanel.panelMenuWrapper : null;
                if (wrapper && wrapper.contains(event.target)) {
                    return;
                }
                this.uiSetPanelMenuOpen(false);
            },

            isConversationBlank() {
                if (!Array.isArray(this.messages) || !this.messages.length) return true;
                return !this.messages.some(
                    (msg) => msg && (msg.role === 'user' || msg.role === 'assistant')
                );
            },

            pickWelcomeText() {
                const pool = this.blankWelcomePool;
                if (!Array.isArray(pool) || !pool.length) {
                    this.blankWelcomeText = '有什么可以帮忙的？';
                    return;
                }
                const idx = Math.floor(Math.random() * pool.length);
                this.blankWelcomeText = pool[idx];
            },

            refreshBlankHeroState() {
                const isBlank = this.isConversationBlank();
                const currentConv = this.currentConversationId || 'temp';
                const needNewWelcome =
                    !this.blankHeroActive ||
                    this.lastBlankConversationId !== currentConv;

                if (isBlank) {
                    if (needNewWelcome && !this.blankHeroExiting) {
                        this.pickWelcomeText();
                    }
                    this.blankHeroActive = true;
                    this.lastBlankConversationId = currentConv;
                } else {
                    this.blankHeroActive = false;
                    this.blankHeroExiting = false;
                    this.lastBlankConversationId = null;
                }
            },

            applyToolSettingsSnapshot(categories) {
                if (!Array.isArray(categories)) {
                    console.warn('[ToolSettings] Snapshot skipped: categories not array', categories);
                    return;
                }
                const normalized = categories.map((item) => ({
                    id: item.id,
                    label: item.label || item.id,
                    enabled: !!item.enabled,
                    tools: Array.isArray(item.tools) ? item.tools : [],
                    locked: !!item.locked,
                    locked_state: typeof item.locked_state === 'boolean' ? item.locked_state : null
                }));
                debugLog('[ToolSettings] Snapshot applied', {
                    received: categories.length,
                    normalized,
                    anyEnabled: normalized.some(cat => cat.enabled),
                    toolExamples: normalized.slice(0, 3)
                });
                this.toolSetSettings(normalized);
                this.toolSetSettingsLoading(false);
            },

            async loadToolSettings(force = false) {
                if (!this.isConnected && !force) {
                    debugLog('[ToolSettings] Skip load: disconnected & not forced');
                    return;
                }
                if (this.toolSettingsLoading) {
                    debugLog('[ToolSettings] Skip load: already loading');
                    return;
                }
                if (!force && this.toolSettings.length > 0) {
                    debugLog('[ToolSettings] Skip load: already have settings');
                    return;
                }
                debugLog('[ToolSettings] Fetch start', { force, hasConnection: this.isConnected });
                this.toolSetSettingsLoading(true);
                try {
                    const response = await fetch('/api/tool-settings');
                    const data = await response.json();
                    debugLog('[ToolSettings] Fetch response', { status: response.status, data });
                    if (response.ok && data.success && Array.isArray(data.categories)) {
                        this.applyToolSettingsSnapshot(data.categories);
                    } else {
                        console.warn('获取工具设置失败:', data);
                        this.toolSetSettingsLoading(false);
                    }
                } catch (error) {
                    console.error('获取工具设置异常:', error);
                    this.toolSetSettingsLoading(false);
                }
            },

            async updateToolCategory(categoryId, enabled) {
                if (!this.isConnected) {
                    return;
                }
                if (this.toolSettingsLoading) {
                    return;
                }
                const policyStore = usePolicyStore();
                if (policyStore.isCategoryLocked(categoryId)) {
                    this.uiPushToast({
                        title: '无法修改',
                        message: '该工具类别被管理员强制设置',
                        type: 'warning'
                    });
                    return;
                }
                const previousSnapshot = this.toolSettings.map((item) => ({ ...item }));
                const updatedSettings = this.toolSettings.map((item) => {
                    if (item.id === categoryId) {
                        return { ...item, enabled };
                    }
                    return item;
                });
                this.toolSetSettings(updatedSettings);
                try {
                    const response = await fetch('/api/tool-settings', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                            category: categoryId,
                            enabled
                        })
                    });
                    const data = await response.json();
                    if (response.ok && data.success && Array.isArray(data.categories)) {
                        this.applyToolSettingsSnapshot(data.categories);
                    } else {
                        console.warn('更新工具设置失败:', data);
                        if (data && (data.message || data.error)) {
                            this.uiPushToast({
                                title: '无法切换工具',
                                message: data.message || data.error,
                                type: 'warning'
                            });
                        }
                        this.toolSetSettings(previousSnapshot);
                    }
                } catch (error) {
                    console.error('更新工具设置异常:', error);
                    this.toolSetSettings(previousSnapshot);
                }
                this.toolSetSettingsLoading(false);
            },

            toggleSettings() {
                if (!this.isConnected) {
                    return;
                }
                this.modeMenuOpen = false;
                this.modelMenuOpen = false;
                const nextState = this.inputToggleSettingsMenu();
                if (nextState) {
                    this.inputSetToolMenuOpen(false);
                    if (!this.quickMenuOpen) {
                        this.inputOpenQuickMenu();
                    }
                }
            },

            toggleFocusPanel() {
                this.rightCollapsed = !this.rightCollapsed;
                if (!this.rightCollapsed && this.rightWidth < this.minPanelWidth) {
                    this.rightWidth = this.minPanelWidth;
                }
            },

            addSystemMessage(content) {
                this.chatAddSystemMessage(content);
                this.$forceUpdate();
                this.conditionalScrollToBottom();
            },
            startTitleTyping(title: string, options: { animate?: boolean } = {}) {
                if (this.titleTypingTimer) {
                    clearInterval(this.titleTypingTimer);
                    this.titleTypingTimer = null;
                }
                const target = (title || '').trim();
                const animate = options.animate ?? true;
                if (!animate) {
                    this.titleTypingTarget = target;
                    this.titleTypingText = target;
                    return;
                }
                const previous = (this.titleTypingText || '').trim();
                if (previous === target) {
                    this.titleTypingTarget = target;
                    this.titleTypingText = target;
                    return;
                }
                this.titleTypingTarget = target;
                this.titleTypingText = previous;

                const frames: string[] = [];
                for (let i = previous.length; i >= 0; i--) {
                    frames.push(previous.slice(0, i));
                }
                for (let j = 1; j <= target.length; j++) {
                    frames.push(target.slice(0, j));
                }

                let index = 0;
                this.titleTypingTimer = window.setInterval(() => {
                    if (index >= frames.length) {
                        clearInterval(this.titleTypingTimer!);
                        this.titleTypingTimer = null;
                        this.titleTypingText = target;
                        return;
                    }
                    this.titleTypingText = frames[index];
                    index += 1;
                }, 32);
            },
            toggleBlock(blockId) {
                if (!blockId) {
                    return;
                }
                if (this.expandedBlocks && this.expandedBlocks.has(blockId)) {
                    this.chatCollapseBlock(blockId);
                } else {
                    this.chatExpandBlock(blockId);
                }
            },
            handleThinkingScroll(blockId, event) {
                if (!blockId || !event || !event.target) {
                    return;
                }
                const el = event.target;
                const threshold = 12;
                const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
                this.chatSetThinkingLock(blockId, atBottom);
            },
            getToolIcon,
            getToolAnimationClass,
            getToolStatusText(tool: any) {
                const personalization = usePersonalizationStore();
                const intentEnabled =
                    personalization?.form?.tool_intent_enabled ??
                    personalization?.tool_intent_enabled ??
                    true;
                return baseGetToolStatusText(tool, { intentEnabled });
            },
            getToolDescription,
            cloneToolArguments,
            buildToolLabel,
            formatSearchTopic,
            formatSearchTime,
            getLanguageClass,
            
            scrollToBottom() {
                scrollToBottomHelper(this);
            },
            
            conditionalScrollToBottom() {
                conditionalScrollToBottomHelper(this);
            },
            
            toggleScrollLock() {
                toggleScrollLockHelper(this);
            },

            scrollThinkingToBottom(blockId) {
                scrollThinkingToBottomHelper(this, blockId);
            },

            // 面板调整方法
            startResize(panel, event) {
                startPanelResize(this, panel, event);
            },
            
            handleResize(event) {
                handlePanelResize(this, event);
            },
            
            stopResize() {
                stopPanelResize(this);
            },

            async handleEasterEggPayload(payload) {
                const controller = useEasterEgg();
                await controller.handlePayload(payload, this);
            },

            async startEasterEggEffect(effectName, payload = {}) {
                const controller = useEasterEgg();
                await controller.startEffect(effectName, payload, this);
            },

            destroyEasterEggEffect(forceImmediate = false) {
                const controller = useEasterEgg();
                return controller.destroyEffect(forceImmediate);
            },

            finishEasterEggCleanup() {
                const controller = useEasterEgg();
                controller.finishCleanup();
            },
            formatTokenCount,
            formatBytes,
            formatPercentage,
            formatRate,
            quotaTypeLabel,
            formatResetTime,
            formatQuotaValue,

            quotaResetSummary() {
                return buildQuotaResetSummary(this.usageQuota);
            },

            isQuotaExceeded(type) {
                return isQuotaExceededUtil(this.usageQuota, type);
            },

            showQuotaToast(payload) {
                if (!payload) {
                    return;
                }
                const type = payload.type || 'fast';
                const message = buildQuotaToastMessage(type, this.usageQuota, payload.reset_at);
                this.uiShowQuotaToastMessage(message, type);
            },

            confirmAction(options = {}) {
                return this.uiRequestConfirm(options);
            }
        }
    };
    
(appOptions as any).components = {
    ChatArea,
    ConversationSidebar,
    LeftPanel,
    FocusPanel,
    TokenDrawer,
    PersonalizationDrawer,
    LiquidGlassWidget,
    QuickMenu,
    InputComposer,
    AppShell,
    ImagePicker,
    ConversationReviewDialog
};

export default appOptions;
