// SignalR连接模块
import {config} from './config.js';
import {addMessage} from './message.js';
import {highlightCodeBlocks} from './highlight.js';
import {scrollToBottom} from './dom-utils.js';
import {renderMarkdown} from './markdown.js';
let connection = null;
let isConnected = false;
let isLoading = false;
let currentAssistantMessage = null;
let currentAssistantRaw = '';
let cancelBtn = null;
export function getConnection() {
return connection;
}
export function getIsConnected() {
return isConnected;
}
export function getIsLoading() {
return isLoading;
}
export function setIsLoading(value) {
isLoading = value;
}
export function getCurrentAssistantMessage() {
return currentAssistantMessage;
}
export function setCurrentAssistantMessage(value) {
currentAssistantMessage = value;
}
export function getCurrentAssistantRaw() {
return currentAssistantRaw;
}
export function setCurrentAssistantRaw(value) {
currentAssistantRaw = value;
}
export function getCancelBtn() {
return cancelBtn;
}
export function setCancelBtn(value) {
cancelBtn = value;
}
export function setConnectionStatus(connected, statusEl, updateInputState) {
isConnected = connected;
if (statusEl) {
statusEl.textContent = connected ? '已连接' : '未连接';
statusEl.className = `status ${connected ? 'connected' : 'disconnected'}`;
}
if (updateInputState) {
updateInputState();
}
}
export async function initConnection(chatArea, statusEl, updateInputState, refreshSessionListCallback, getCurrentSessionId) {
connection = new signalR
.HubConnectionBuilder()
.withUrl(config.hubUrl)
.withAutomaticReconnect([0, 0, 1000, 3000, 5000, 10000])
.build();
connection.on('NewSessionCreated', (sessionId) => {
console.log('✅ 新会话已创建,SessionId:', sessionId);
const currentSessionIdEl = document.getElementById('currentSessionId');
if (currentSessionIdEl) currentSessionIdEl.value = sessionId;
// pendingSessionElement 处理在 session.js 中
});
connection.on('SessionNameGenerated', async (data) => {
let sessionId, sessionName;
if (typeof data === 'object' && data !== null) {
sessionId = data.sessionId;
sessionName = data.sessionName;
} else {
sessionId = data;
}
console.log('✅ 会话名称已生成,SessionId:', sessionId, 'SessionName:', sessionName);
// 动态导入 updateSessionTitle 函数
const { updateSessionTitle } = await import('./session.js');
// 尝试直接更新标题,将 sessionName 作为第四个参数传入
const updated = await updateSessionTitle(sessionId, null, refreshSessionListCallback, sessionName);
if (!updated && refreshSessionListCallback) {
// 如果直接更新失败,则刷新整个列表
refreshSessionListCallback();
}
});
connection.on('StreamDelta', (delta) => {
if (currentAssistantMessage) {
const threshold = 100;
const isAtBottom = chatArea ? (chatArea.scrollHeight - chatArea.scrollTop - chatArea.clientHeight < threshold) : true;
currentAssistantRaw += delta;
// 保存操作栏和原始内容元素(在 innerHTML 之前)
const actionRow = currentAssistantMessage.querySelector('.message-actions');
const ts = currentAssistantMessage.querySelector('.timestamp');
// 保存取消按钮的引用(如果存在)
const cancelBtnRef = cancelBtn;
// 重新渲染 Markdown 内容(这会清空 innerHTML)
currentAssistantMessage.innerHTML = renderMarkdown(currentAssistantRaw);
// 重新创建操作栏(因为 innerHTML 已经清空了所有内容)
let newActionRow = document.createElement('div');
newActionRow.className = 'message-actions';
// 如果有取消按钮,先添加取消按钮(在最前面)
if (cancelBtnRef) {
newActionRow.appendChild(cancelBtnRef);
}
// 如果有原来的 actionRow,尝试恢复其中的按钮
if (actionRow) {
// 复制按钮
const copyBtn = actionRow.querySelector('.btn-copy-markdown');
if (copyBtn) {
newActionRow.appendChild(copyBtn.cloneNode(true));
} else {
// 如果没有,创建一个新的复制按钮
const newCopyBtn = document.createElement('button');
newCopyBtn.className = 'btn-copy-markdown';
newCopyBtn.title = '复制原始内容';
newCopyBtn.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
newActionRow.appendChild(newCopyBtn);
}
} else {
// 如果没有原来的 actionRow,创建新的复制按钮
const newCopyBtn = document.createElement('button');
newCopyBtn.className = 'btn-copy-markdown';
newCopyBtn.title = '复制原始内容';
newCopyBtn.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
newActionRow.appendChild(newCopyBtn);
}
// 创建或更新 rawEl(必须在 actionRow 内部,在复制按钮之后)
const newRawEl = document.createElement('div');
newRawEl.className = 'raw-markdown';
newRawEl.style.display = 'none';
newRawEl.textContent = currentAssistantRaw;
newActionRow.appendChild(newRawEl);
// 删除按钮(如果有的话,但流式生成时通常没有)
if (actionRow) {
const deleteBtn = actionRow.querySelector('.btn-delete-message');
if (deleteBtn) {
newActionRow.appendChild(deleteBtn.cloneNode(true));
}
}
// 恢复操作栏
currentAssistantMessage.appendChild(newActionRow);
// 恢复或新建 timestamp
if (ts) {
currentAssistantMessage.appendChild(ts);
} else {
let newTs = document.createElement('div');
newTs.className = 'timestamp';
newTs.textContent = new Date().toLocaleString();
currentAssistantMessage.appendChild(newTs);
}
// 异步高亮代码块(不阻塞流式更新)
highlightCodeBlocks(currentAssistantMessage).catch(err => {
console.warn('代码高亮失败:', err);
});
if (isAtBottom) {
scrollToBottom(chatArea);
}
}
});
connection.on('StreamCompleted', () => {
isLoading = false;
if (cancelBtn) {
cancelBtn.remove();
cancelBtn = null;
}
if (currentAssistantMessage) {
highlightCodeBlocks(currentAssistantMessage).catch(err => {
console.warn('代码高亮失败:', err);
});
}
currentAssistantMessage = null;
currentAssistantRaw = '';
if (updateInputState) {
updateInputState();
}
if (refreshSessionListCallback) {
refreshSessionListCallback();
}
});
connection.on('StreamCancelled', () => {
isLoading = false;
if (cancelBtn) {
cancelBtn.remove();
cancelBtn = null;
}
currentAssistantMessage = null;
currentAssistantRaw = '';
if (updateInputState) {
updateInputState();
}
addMessage(chatArea, 'assistant', '已取消生成');
});
connection.on('SystemError', (errorData) => {
isLoading = false;
if (cancelBtn) {
cancelBtn.remove();
cancelBtn = null;
}
currentAssistantMessage = null;
currentAssistantRaw = '';
addMessage(chatArea, 'error', (errorData && errorData.content) || '发生错误');
if (updateInputState) {
updateInputState();
}
});
connection.onreconnecting(() => setConnectionStatus(false, statusEl, updateInputState));
connection.onreconnected(() => setConnectionStatus(true, statusEl, updateInputState));
connection.onclose(() => {
setConnectionStatus(false, statusEl, updateInputState);
setTimeout(() => initConnection(chatArea, statusEl, updateInputState, refreshSessionListCallback, getCurrentSessionId), 5000);
});
try {
await connection.start();
setConnectionStatus(true, statusEl, updateInputState);
console.log('📡 SignalR 连接已建立');
} catch (err) {
console.error('❌ 连接失败:', err);
setConnectionStatus(false, statusEl, updateInputState);
setTimeout(() => initConnection(chatArea, statusEl, updateInputState, refreshSessionListCallback, getCurrentSessionId), 5000);
}
}
评论加载中...