// 会话管理模块
import {getCurrentSessionId} from './dom-utils.js';
import {customAlert, customConfirm} from './modal.js';
import {highlightCodeBlocks} from './highlight.js';
import {scrollToBottom} from './dom-utils.js';

let pendingSessionElement = null;

export function getPendingSessionElement() {
    return pendingSessionElement;
}

export async function createNewSession(sessionList, sessionSidebar, sidebarOverlay, switchSession) {
    const existingPending = document.querySelector('.session-item.pending');
    if (existingPending) {
        await customAlert('已有待创建的新会话,请先发送消息完成创建', '提示');
        document.querySelectorAll('.session-item').forEach(item => item.classList.remove('active'));
        existingPending.classList.add('active');
        return;
    }

    try {
        // 尝试切换到空会话,但不阻止创建临时会话项(即使失败也继续)
        try {
            await switchSession('');
        } catch (err) {
            console.warn('切换空会话时出错,但继续创建新会话:', err);
        }

        document.querySelectorAll('.session-item').forEach(item => item.classList.remove('active'));

        const tempSessionId = 'temp_' + Date.now();
        const tempSession = document.createElement('div');
        tempSession.className = 'session-item pending active';
        tempSession.dataset.sessionId = tempSessionId;
        tempSession.dataset.isNewSession = 'true';
        tempSession.innerHTML = `
            <div class="session-info">
                <div class="session-name">新建会话...</div>
                <div class="session-meta">
                    <span class="session-time">${new Date().toLocaleString('zh-CN', {
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit'
        })}</span>
                    <span class="session-count">0 条</span>
                </div>
            </div>
        `;
        tempSession.querySelector('.session-info').addEventListener('click', async () => {
            if (tempSession.dataset.isNewSession === 'true') {
                const currentId = getCurrentSessionId() || '';
                if (currentId !== '') {
                    try {
                        await switchSession('');
                        document.querySelectorAll('.session-item').forEach(i => i.classList.remove('active'));
                        tempSession.classList.add('active');
                    } catch (err) {
                        console.warn('切换回新会话时出错:', err);
                    }
                }
            }
        });

        if (sessionList) {
            sessionList.insertBefore(tempSession, sessionList.firstChild);
        }
        pendingSessionElement = tempSession;

        // 确保 currentSessionId 隐藏域被清空
        const currentSessionIdEl = document.getElementById('currentSessionId');
        if (currentSessionIdEl) {
            currentSessionIdEl.value = '';
        }

        if (window.innerWidth <= 600) {
            sessionSidebar.classList.remove('show');
            if (sidebarOverlay) sidebarOverlay.classList.remove('show');
        }

    } catch (err) {
        console.error('❌ 创建会话异常:', err);
        await customAlert('创建新会话失败,请重试');
    }
}

export async function deleteSession(sessionId, switchSessionCallback, refreshSessionListCallback) {
    if (!await customConfirm('确定要删除这个会话吗?')) {
        return;
    }
    try {
        const res = await fetch(`/AiChat/DeleteSession?sessionId=${encodeURIComponent(sessionId)}`, {method: 'DELETE'});
        const result = await res.json();
        if (result.success) {
            const isCurrent = (sessionId === getCurrentSessionId());

            // 调用刷新会话列表的回调(传递所有必要的参数)
            if (refreshSessionListCallback && typeof refreshSessionListCallback === 'function') {
                if (refreshSessionListCallback.length === 0) {
                    await refreshSessionListCallback();
                } else {
                    await refreshSessionListCallback();
                }
            }

            if (isCurrent) {
                const first = document.querySelector('.session-item');
                if (first && first.dataset.sessionId) {
                    const firstSessionId = first.dataset.sessionId;
                    // switchSessionCallback 是一个包装函数,只需要传递 sessionId
                    if (switchSessionCallback && typeof switchSessionCallback === 'function') {
                        await switchSessionCallback(firstSessionId);
                    }
                } else {
                    // 如果没有其他会话,创建新会话
                    await createNewSession(
                        document.getElementById('sessionList'),
                        document.getElementById('sessionSidebar'),
                        document.getElementById('sidebarOverlay'),
                        switchSessionCallback
                    );
                }
            }
        } else {
            await customAlert(result["msg"] || result.message || '删除失败');
        }
    } catch (err) {
        console.error('删除会话失败:', err);
        await customAlert('删除失败: ' + (err.message || err));
    }
}

export async function switchSession(sessionId, chatMain, updateDomReferences, bindChatEvents, setConnectionStatus, isConnected, updateInputState) {
    // 确保参数有效
    if (!chatMain) {
        chatMain = document.getElementById('chatMain');
    }
    if (!updateDomReferences || typeof updateDomReferences !== 'function') {
        console.error('switchSession: updateDomReferences 参数无效');
        await customAlert('切换会话失败:参数错误');
        return;
    }
    if (!bindChatEvents || typeof bindChatEvents !== 'function') {
        console.error('switchSession: bindChatEvents 参数无效');
        await customAlert('切换会话失败:参数错误');
        return;
    }

    const currentSessionId = getCurrentSessionId();
    // 处理空字符串的情况:如果都是空字符串,也认为是相同会话
    const normalizedSessionId = sessionId || '';
    const normalizedCurrentId = currentSessionId || '';

    if (normalizedSessionId === normalizedCurrentId) {
        console.log('⚠️ 已经在当前会话中,无需切换');
        if (window.innerWidth <= 600) {
            const sessionSidebar = document.getElementById('sessionSidebar');
            const sidebarOverlay = document.getElementById('sidebarOverlay');
            if (sessionSidebar) sessionSidebar.classList.remove('show');
            if (sidebarOverlay) sidebarOverlay.classList.remove('show');
        }
        return;
    }

    console.log('🔄 切换会话:', normalizedSessionId || '(新会话)');

    try {
        const res = await fetch(`/AiChat/ChatMessages?sessionId=${encodeURIComponent(normalizedSessionId)}`);
        if (!res.ok) {
            const errorText = await res.text().catch(() => '');
            console.error('切换会话请求失败:', res.status, errorText);
            // 如果是创建新会话(空 sessionId),允许继续,不显示错误
            if (normalizedSessionId === '') {
                console.warn('新会话请求失败,但允许继续创建临时会话');
                return;
            } else {
                await customAlert(`切换会话失败: ${res.status} ${res.statusText}`);
                return;
            }
        }

        const html = await res.text();

        if (chatMain) {
            chatMain.innerHTML = html;

            // 使用传入的 updateDomReferences 函数获取 DOM 引用
            const domRefs = updateDomReferences && typeof updateDomReferences === 'function'
                ? updateDomReferences()
                : null;

            if (!domRefs) {
                console.error('无法获取 DOM 引用');
                await customAlert('切换会话失败:无法获取 DOM 元素');
                return;
            }

            // 确保参数有效
            if (bindChatEvents && typeof bindChatEvents === 'function') {
                bindChatEvents(domRefs);
            }

            if (domRefs.statusEl && setConnectionStatus && typeof setConnectionStatus === 'function') {
                const connectionStatus = typeof isConnected === 'function' ? isConnected() : isConnected;
                // 创建一个更新输入状态的函数,使用当前的 domRefs
                const updateInputStateForSwitch = () => {
                    if (updateInputState && typeof updateInputState === 'function') {
                        updateInputState(domRefs);
                    }
                };
                setConnectionStatus(connectionStatus, domRefs.statusEl, updateInputStateForSwitch);
            } else if (updateInputState && typeof updateInputState === 'function') {
                // 即使没有 statusEl,也要更新输入状态
                updateInputState(domRefs);
            }

            if (domRefs.chatArea) {
                highlightCodeBlocks(domRefs.chatArea).catch(err => {
                    console.warn('代码高亮失败:', err);
                });
                scrollToBottom(domRefs.chatArea);
            }
        }

        // 更新会话列表的选中状态(包括空会话的情况)
        if (normalizedSessionId === '') {
            // 对于新会话,清除所有选中状态
            document.querySelectorAll('.session-item').forEach(item => {
                item.classList.remove('active');
            });
        } else {
            document.querySelectorAll('.session-item').forEach(item => {
                item.classList.toggle('active', item.dataset.sessionId === normalizedSessionId);
            });
        }

    } catch (err) {
        console.error('切换会话失败:', err);
        // 如果是创建新会话(空 sessionId),允许继续,不显示错误
        if (normalizedSessionId === '') {
            console.warn('新会话切换失败,但允许继续创建临时会话:', err);
        } else {
            await customAlert(`切换会话失败: ${err.message || err}`);
        }
    }

    if (window.innerWidth <= 600) {
        const sessionSidebar = document.getElementById('sessionSidebar');
        const sidebarOverlay = document.getElementById('sidebarOverlay');
        if (sessionSidebar) sessionSidebar.classList.remove('show');
        if (sidebarOverlay) sidebarOverlay.classList.remove('show');
    }
}

export async function refreshSessionList(switchSessionCallback, updateDomReferencesCallback, bindChatEventsCallback, setConnectionStatusCallback, getIsConnectedCallback, updateInputStateCallback) {
    try {
        const response = await fetch('/AiChat/Sessions');
        const result = await response.json();
        if (result.success && result.data) {
            renderSessionList(result.data, switchSessionCallback, updateDomReferencesCallback, bindChatEventsCallback, setConnectionStatusCallback, getIsConnectedCallback, updateInputStateCallback);
        }
    } catch (err) {
        console.error('刷新会话列表失败:', err);
    }
}

/**
 * 更新会话元素的辅助函数
 */
function applySessionUpdate(element, sessionId, name, time, count, switchSessionCallback, refreshSessionListCallback) {
    const nameEl = element.querySelector('.session-name');
    const timeEl = element.querySelector('.session-time');
    const countEl = element.querySelector('.session-count');
    
    if (nameEl) {
        nameEl.textContent = name;
        nameEl.title = name;
        nameEl.style.fontStyle = 'normal';
    }
    if (timeEl && time) {
        timeEl.textContent = new Date(time).toLocaleString('zh-CN', {
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit'
        });
    }
    if (countEl && count !== undefined) {
        countEl.textContent = `${count} 条`;
    }
    
    element.classList.remove('pending');
    
    // 更新点击事件
    const sessionInfo = element.querySelector('.session-info');
    if (sessionInfo) {
        const newSessionInfo = sessionInfo.cloneNode(true);
        sessionInfo.parentNode.replaceChild(newSessionInfo, sessionInfo);
        newSessionInfo.addEventListener('click', async () => {
            if (switchSessionCallback && typeof switchSessionCallback === 'function') {
                await switchSessionCallback(sessionId);
            }
        });
    }
    
    // 确保有删除按钮
    if (!element.querySelector('.btn-delete-session')) {
        const btn = document.createElement('button');
        btn.className = 'btn-delete-session';
        btn.textContent = '🗑️';
        btn.title = '删除会话';
        btn.onclick = async (e) => {
            e.stopPropagation();
            await deleteSession(sessionId, switchSessionCallback, refreshSessionListCallback);
        };
        element.appendChild(btn);
    }
}

/**
 * 更新指定会话的标题
 * @param {string} sessionId - 会话ID
 * @param {Function} switchSessionCallback - 切换会话的回调
 * @param {Function} refreshSessionListCallback - 刷新会话列表的回调(可选)
 * @param {string} [sessionName] - 会话名称(可选,如果有则直接更新不请求接口)
 */
export async function updateSessionTitle(sessionId, switchSessionCallback, refreshSessionListCallback, sessionName) {
    if (!sessionId || !pendingSessionElement) {
        return false;
    }
    
    // 如果 pending 元素的 sessionId 不匹配,说明不是要更新的会话
    if (pendingSessionElement.dataset.sessionId !== sessionId) {
        return false;
    }
    
    if (sessionName) {
        console.log('✅ 使用 SignalR 提供的会话名称更新:', sessionId, sessionName);
        applySessionUpdate(
            pendingSessionElement,
            sessionId,
            sessionName,
            new Date(),
            2,
            switchSessionCallback,
            refreshSessionListCallback
        );
        return true;
    }

    try {
        // 获取会话列表,查找对应的会话
        const response = await fetch('/AiChat/Sessions');
        const result = await response.json();
        if (result.success && result.data) {
            const matched = result.data.find(s => s.sessionId === sessionId);
            if (matched) {
                console.log('✅ 找到匹配的会话,更新标题:', sessionId, matched['sessionName']);
                
                applySessionUpdate(
                    pendingSessionElement,
                    sessionId,
                    matched['sessionName'] || '未命名会话',
                    matched['lastUpdateTime'],
                    matched['messageCount'],
                    switchSessionCallback,
                    refreshSessionListCallback
                );
                
                return true;
            } else {
                console.log('⚠️ 未找到匹配的会话,SessionId:', sessionId);
            }
        }
    } catch (err) {
        console.error('更新会话标题失败:', err);
    }
    
    return false;
}

export function renderSessionList(sessions, switchSessionCallback, updateDomReferencesCallback, bindChatEventsCallback, setConnectionStatusCallback, getIsConnectedCallback, updateInputStateCallback) {
    const sessionList = document.getElementById('sessionList');
    if (!sessionList) return;
    const pending = pendingSessionElement;

    sessionList.innerHTML = '';
    if (pending) {
        // 如果 pending 有真实的 sessionId(不是 temp_ 开头),尝试从服务器数据中更新
        if (pending.dataset.sessionId && !pending.dataset.sessionId.startsWith('temp_')) {
            const realId = pending.dataset.sessionId;
            const matched = sessions.find(s => s.sessionId === realId);
            if (matched) {
                console.log('✅ 找到匹配的会话,更新标题:', realId, matched['sessionName']);
                // 更新 pending 会话项的信息
                const nameEl = pending.querySelector('.session-name');
                const timeEl = pending.querySelector('.session-time');
                const countEl = pending.querySelector('.session-count');
                
                if (nameEl) {
                    const sessionName = matched['sessionName'] || '未命名会话';
                    nameEl.textContent = sessionName;
                    nameEl.title = sessionName;
                    nameEl.style.fontStyle = 'normal';
                }
                if (timeEl) {
                    const updateTime = matched['lastUpdateTime'];
                    if (updateTime) {
                        timeEl.textContent = new Date(updateTime).toLocaleString('zh-CN', {
                            month: '2-digit',
                            day: '2-digit',
                            hour: '2-digit',
                            minute: '2-digit'
                        });
                    }
                }
                if (countEl) {
                    const messageCount = matched['messageCount'] !== undefined ? matched['messageCount'] : 0;
                    countEl.textContent = `${messageCount} 条`;
                }

                pending.classList.remove('pending');
                // 更新点击事件
                const sessionInfo = pending.querySelector('.session-info');
                if (sessionInfo) {
                    // 移除旧的事件监听器
                    const newSessionInfo = sessionInfo.cloneNode(true);
                    sessionInfo.parentNode.replaceChild(newSessionInfo, sessionInfo);
                    newSessionInfo.addEventListener('click', async () => {
                        // switchSessionCallback 是包装函数,只需要传递 sessionId
                        if (switchSessionCallback && typeof switchSessionCallback === 'function') {
                            await switchSessionCallback(realId);
                        }
                    });

                    // 更新删除按钮的事件
                    const deleteBtn = pending.querySelector('.btn-delete-session');
                    if (deleteBtn) {
                        const newDeleteBtn = deleteBtn.cloneNode(true);
                        deleteBtn.parentNode.replaceChild(newDeleteBtn, deleteBtn);
                        newDeleteBtn.onclick = async (e) => {
                            e.stopPropagation();
                            await deleteSession(realId, switchSessionCallback, (switchCallback, updateCallback, bindCallback, statusCallback, connectedCallback, inputStateCallback) => {
                                refreshSessionList(switchCallback, updateCallback, bindCallback, statusCallback, connectedCallback, inputStateCallback);
                            });
                        };
                    }
                }

                // 确保有删除按钮
                if (!pending.querySelector('.btn-delete-session')) {
                    const btn = document.createElement('button');
                    btn.className = 'btn-delete-session';
                    btn.textContent = '🗑️';
                    btn.title = '删除会话';
                    btn.onclick = async (e) => {
                        e.stopPropagation();
                        await deleteSession(realId, switchSessionCallback, (switchCallback, updateCallback, bindCallback, statusCallback, connectedCallback, inputStateCallback) => {
                            refreshSessionList(switchCallback, updateCallback, bindCallback, statusCallback, connectedCallback, inputStateCallback);
                        });
                    };
                    pending.appendChild(btn);
                }

                // 从 sessions 数组中移除已匹配的项
                sessions = sessions.filter(s => s.sessionId !== realId);
            } else {
                console.log('⚠️ 未找到匹配的会话,SessionId:', realId, '服务器返回的会话列表:', sessions.map(s => s.sessionId));
            }
        }
        // 将 pending 元素插入到列表开头
        sessionList.appendChild(pending);
    }

    sessions.forEach(session => {
        const div = document.createElement('div');
        div.className = `session-item ${session.sessionId === getCurrentSessionId() ? 'active' : ''}`;
        div.dataset.sessionId = session.sessionId;
        div.innerHTML = `
            <div class="session-info">
                <div class="session-name" title="${session['sessionName']}">${session['sessionName']}</div>
                <div class="session-meta">
                    <span class="session-time">${new Date(session['lastUpdateTime']).toLocaleString('zh-CN', {
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit'
        })}</span>
                    <span class="session-count">${session['messageCount']} 条</span>
                </div>
            </div>
            <button class="btn-delete-session" title="删除会话">🗑️</button>
        `;
        div.querySelector('.session-info').onclick = async () => {
            // switchSessionCallback 是包装函数,只需要传递 sessionId
            if (switchSessionCallback && typeof switchSessionCallback === 'function') {
                await switchSessionCallback(session.sessionId);
            }
        };
        div.querySelector('.btn-delete-session').onclick = async (e) => {
            e.stopPropagation();
            await deleteSession(session.sessionId, switchSessionCallback, () => {
                refreshSessionList(switchSessionCallback, updateDomReferencesCallback, bindChatEventsCallback, setConnectionStatusCallback, getIsConnectedCallback, updateInputStateCallback);
            });
        };
        sessionList.appendChild(div);
    });
}

export function toggleSidebar(forceClose = false) {
    const sessionSidebar = document.getElementById('sessionSidebar');
    const sidebarOverlay = document.getElementById('sidebarOverlay');
    const isMobile = window.innerWidth <= 600;
    if (forceClose) {
        if (isMobile) {
            if (sessionSidebar) sessionSidebar.classList.remove('show');
            if (sidebarOverlay) sidebarOverlay.classList.remove('show');
        } else {
            if (sessionSidebar) sessionSidebar.classList.add('collapsed');
        }
        return;
    }
    if (isMobile) {
        if (sessionSidebar) sessionSidebar.classList.toggle('show');
        if (sidebarOverlay) sidebarOverlay.classList.toggle('show');
    } else {
        if (sessionSidebar) {
            sessionSidebar.classList.toggle('collapsed');
            sessionSidebar.classList.toggle('show');
        }
    }
}

⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

下面把这段“会话管理模块”代码的结构和功能要点分块讲清楚,便于理解每个导出函数的职责、交互流程和重要细节。

总体说明

  • 这是一个负责管理会话列表与会话切换的前端模块(纯前端 DOM 操作 + 与后端的若干接口交互)。
  • 依赖的外部模块/函数:
    • getCurrentSessionId、scrollToBottom(来自 dom-utils.js)
    • customAlert、customConfirm(来自 modal.js,用于提示/确认)
    • highlightCodeBlocks(来自 highlight.js,用于代码高亮)
  • 与后端接口约定(已在代码中使用):
    • GET /AiChat/Sessions — 获取会话列表(返回 JSON)
    • DELETE /AiChat/DeleteSession?sessionId=... — 删除会话(返回 JSON)
    • GET /AiChat/ChatMessages?sessionId=... — 获取某个会话的聊天界面(返回 HTML)

模块级变量

  • pendingSessionElement:模块级变量,保存当前“待创建/临时”会话 DOM 元素(可能是用户刚创建但尚未被后端正式确认的会话)。导出 getPendingSessionElement() 用于读取它。

主要导出函数说明(按重要性排序)

  1. createNewSession(sessionList, sessionSidebar, sidebarOverlay, switchSession)
  • 目的:在 UI 上创建一个“待创建的新会话”条目(临时会话),并尽量把界面切换到空会话(用于输入新消息)。
  • 主要逻辑:
    • 若已有一个 class 包含 .session-item.pending 的待创建项,则阻止重复创建并提醒用户先发送消息完成创建,同时把该 pending 项设为 active。
    • 尝试调用 switchSession('') 切换到空会话(空字符串代表新会话/未保存会话),如果切换失败仍继续创建临时项(catch 仅记录警告)。
    • 在 sessionList(传入的 DOM 节点)最前方插入一个临时会话项(data-session-id 为 temp_{timestamp};并标记 data-is-new-session = 'true')。
    • 临时项的点击行为:如果是新会话并且当前页面有已存在的 currentId,会尝试再次 switchSession('') 并将该临时项置为 active(有 try/catch)。
    • 清空隐藏域 currentSessionId(如存在),便于前端表示没有当前会话。
    • 在窄屏(<=600px)上会自动关闭侧栏(移动端 UX)。
  • 错误处理:发生异常时调用 customAlert 提示“创建新会话失败”。
  1. deleteSession(sessionId, switchSessionCallback, refreshSessionListCallback)
  • 目的:删除指定会话并在必要时更新 UI(切换到其他会话或创建新会话)。
  • 主要逻辑:
    • 先用 customConfirm 确认删除。
    • 发起 DELETE 请求到 /AiChat/DeleteSession?sessionId=... 并解析 JSON 返回(result.success)。
    • 如果删除成功:
      • 调用 refreshSessionListCallback 刷新会话列表(如果提供)。
      • 如果删除的是当前正在打开的会话(isCurrent),尝试切换到会话列表中第一个会话(如果存在),否则调用 createNewSession 创建临时会话。
    • 如果删除失败或请求异常,显示 customAlert 错误提示。
  • 注意:switchSessionCallback、refreshSessionListCallback 都以回调形式传入,代码会检查并调用。
  1. switchSession(sessionId, chatMain, updateDomReferences, bindChatEvents, setConnectionStatus, isConnected, updateInputState)
  • 目的:切换到指定会话(sessionId 可为空字符串,表示新/空会话),加载对应聊天界面并绑定事件等。
  • 参数职责:
    • chatMain:外部容器元素(默认 document.getElementById('chatMain'))
    • updateDomReferences:必须的函数,返回当前 chat 界面相关的 DOM 引用集合(供后续使用)
    • bindChatEvents:必须的函数,用来给新加载的 chat 界面绑定事件(比如发送按钮、输入框等)
    • setConnectionStatus:可选,用来显示/设置连接状态的函数
    • isConnected:表示是否连接(可以是函数或布尔)
    • updateInputState:用于根据连接状态更新输入框可用性
  • 主要逻辑:
    • 如果尝试切换到与当前会话相同的 sessionId,则直接返回(仅在移动端关闭侧栏)。
    • 发起 GET /AiChat/ChatMessages?sessionId=... 获取聊天 HTML;如果 status 非 ok,非空 session 时显示错误;若是空 session(新会话),在失败情况下容忍(允许继续创建临时会话)。
    • 将返回的 html 置入 chatMain.innerHTML。
    • 通过 updateDomReferences() 获取新的 DOM 引用(若无法获取,则提示失败)。
    • 调用 bindChatEvents(domRefs) 绑定各种事件(若提供)。
    • 如果提供了 setConnectionStatus,会调用它并传入连接状态、statusEl 和用于更新输入的回调;否则即使没有 statusEl 也会尝试调用 updateInputState(domRefs) 来更新输入框状态。
    • 对 chatArea 调用代码高亮(highlightCodeBlocks)并滚动到底部(scrollToBottom)。
    • 更新会话列表中的选中状态:非空 sessionId 时将对应 .session-item 加 active;空 sessionId 则清除所有 active(表示新会话)。
    • 在移动端同样会收起侧栏。
  • 错误处理:对网络或解析错误会显示提示,空 sessionId 的失败会被容忍并打印警告。
  1. refreshSessionList(switchSessionCallback, updateDomReferencesCallback, bindChatEventsCallback, setConnectionStatusCallback, getIsConnectedCallback, updateInputStateCallback)
  • 目的:从后端获取会话列表并渲染(调用 renderSessionList)。
  • 实现:GET /AiChat/Sessions -> 若返回 result.success && result.data 则调用 renderSessionList 并把回调参数传下去。
  1. updateSessionTitle(sessionId, switchSessionCallback, refreshSessionListCallback, sessionName)
  • 目的:用于把 pendingSessionElement(临时会话)更新为正式会话的显示信息(名称、时间、消息数),通常在后端确认或通过 SignalR 收到名称后调用。
  • 主要逻辑:
    • 仅当 pendingSessionElement 存在且其 dataset.sessionId 与传入 sessionId 匹配时才尝试更新。
    • 如果调用时传入了 sessionName(例如通过 SignalR 主动推送),直接使用并调用 applySessionUpdate 更新 pending DOM。
    • 否则会调用 /AiChat/Sessions 获取列表,在列表中查找匹配的 sessionId 并用服务器数据更新 DOM(包括名字、时间、消息数)。
    • 返回是否成功更新(布尔)。
  1. renderSessionList(sessions, switchSessionCallback, updateDomReferencesCallback, bindChatEventsCallback, setConnectionStatusCallback, getIsConnectedCallback, updateInputStateCallback)
  • 目的:基于传入的会话数组渲染侧栏会话列表,并把 pending(临时)项插入到列表顶部(如果存在)。
  • 细节:
    • 清空 sessionList(DOM id 为 sessionList)的 innerHTML 后重建。
    • 若有 pendingSessionElement:
      • 如果 pending 的 data-session-id 不是 temp_*(说明后端已经返回真实 id 并被写入),尝试在传入的 sessions 中匹配该真实 id:
        • 若匹配到,则把 pending 的显示信息(名称、时间、消息数)替换为服务器值,并移除 pending 的 pending class,确保它成为正式条目;同时为其添加删除按钮与点击切换事件。
        • 从 sessions 数组中移除已匹配的项(避免重复渲染)。
      • 把 pending 元素插入到列表顶部(appendChild)。
    • 遍历剩余 sessions,逐个构造 .session-item DOM,设置 .session-info 点击事件(调用 switchSessionCallback(sessionId)),删除按钮会调用 deleteSession 并刷新会话列表。
  • 注意:render 时会把当前会话 id 跟 getCurrentSessionId() 比对决定 active 类。

辅助函数 applySessionUpdate(element, sessionId, name, time, count, switchSessionCallback, refreshSessionListCallback)

  • 功能:把给定 element 的名称、时间、消息数和点击行为更新为正式会话状态,并确保存在删除按钮(绑定 deleteSession)。
  • 用在 updateSessionTitle 与 renderSessionList 内部以复用更新逻辑。

toggleSidebar(forceClose = false)

  • UI 工具函数:控制侧栏显示/收起行为,支持移动端(<=600px)和桌面两种样式(collapsed、show)。
  • 如果 forceClose 为 true,则强制关闭侧栏(移动端)或折叠(桌面)。

重要设计/行为说明与注意点

  • pending 概念:模块允许创建“临时会话”项(temp_xxx)并把它保存在 pendingSessionElement。后续当服务器返回正式会话信息(通过刷新列表或 SignalR 推送)时,会把 pending 转为正式会话项。
  • 空 sessionId 的语义:空字符串被视为“新会话 / 未存储的会话”,对切换空会话(switchSession(''))的失败有容忍逻辑(不会阻止 UI 创建临时会话)。
  • 回调驱动:switchSession、renderSessionList、updateSessionTitle 等函数都依赖外部传入的回调(updateDomReferences、bindChatEvents、setConnectionStatus)完成 DOM 绑定和状态设置,这使得模块与具体的聊天 UI 实现解耦。
  • 移动端 UX:多处在 window.innerWidth <= 600 条件下自动收起侧栏,优化移动端交互。
  • 错误提示:网络或逻辑错误大多使用 customAlert 提示;删除通过 customConfirm 确认。
  • 安全性与健壮性:对许多回调都做了类型检查(typeof === 'function'),对 fetch 返回的 res.ok 做了处理;但某些地方仍假定服务端返回符合约定的 JSON/HTML 格式。
  • 多次刷新/并发:代码中会替换节点和 cloneNode 来移除旧事件监听器,这是为了避免重复绑定;但并未显示处理并发创建多个 pending(createNewSession 只会阻止已有 pending 的创建)。

总结

  • 该模块是一个完整的前端会话列表与切换管理实现,负责:
    • 在 UI 中创建临时新会话、把临时会话升级为正式会话;
    • 从后端拉取会话列表并渲染;
    • 切换会话(加载聊天 HTML、绑定事件、设置连接与输入状态、代码高亮、滚动到底);
    • 删除会话并处理切换或创建后续会话;
    • 支持移动端与桌面不同的侧栏行为。
  • 模块将 UI 操作(DOM 构造/事件)、网络请求和外部回调整合起来,保持了和聊天具体实现(DOM refs、事件绑定、SignalR 推送等)的解耦。

如果需要,我可以:

  • 画出函数调用与流程图(文字或图示),
  • 指出可能的 bug/改进点(例如并发/边界条件、错误信息更友好、API 返回校验等),
  • 或者把部分逻辑重构为更小、更易测试的函数。要哪个请告诉我。
评论加载中...