Optimize the Front-End Console Log Display Logic
This commit is contained in:
+108
-73
@@ -1234,7 +1234,6 @@
|
||||
let backendReachable = false;
|
||||
const consoleLayerApps = ['insight', 'media', 'query', 'forum', 'report'];
|
||||
const consoleLayers = {};
|
||||
const consoleLayerScrollPositions = {};
|
||||
let activeConsoleLayer = currentApp;
|
||||
const logRenderers = {};
|
||||
|
||||
@@ -1367,12 +1366,16 @@
|
||||
this.autoScrollEnabled = true;
|
||||
this.resumeDelay = 3000; // 手动滚动后重新自动滚动的延迟(降低到3秒)
|
||||
this.resumeTimer = null;
|
||||
this.flushTimer = null; // 批处理定时器
|
||||
this.lastRenderHash = null; // 用于检测内容是否真正变化
|
||||
this.scrollLocked = false; // 防止滚动冲突的锁
|
||||
this.needsScroll = false; // 标记是否需要滚动
|
||||
this.lastScrollTime = 0; // 上次滚动时间,用于节流
|
||||
this.scrollThrottle = 100; // 滚动节流时间(毫秒)
|
||||
this.scrollHandler = null; // 存储滚动处理器引用
|
||||
// 预创建占位符,避免每次渲染都创建
|
||||
this.beforeSpacer = null;
|
||||
this.afterSpacer = null;
|
||||
this.attachScroll();
|
||||
}
|
||||
|
||||
@@ -1386,7 +1389,7 @@
|
||||
if (scrollTimer) clearTimeout(scrollTimer);
|
||||
scrollTimer = setTimeout(() => {
|
||||
this.handleUserScroll();
|
||||
}, 150); // 增加防抖时间到150ms
|
||||
}, 100); // 减少防抖时间到100ms,提高响应速度
|
||||
};
|
||||
|
||||
this.scrollElement.addEventListener('scroll', this.scrollHandler, { passive: true });
|
||||
@@ -1400,6 +1403,10 @@
|
||||
this.rafId = null;
|
||||
}
|
||||
this.clearResumeTimer();
|
||||
if (this.flushTimer) {
|
||||
clearTimeout(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
|
||||
// 移除事件监听器
|
||||
if (this.scrollElement && this.scrollHandler) {
|
||||
@@ -1419,6 +1426,10 @@
|
||||
});
|
||||
this.pool = [];
|
||||
|
||||
// 清理占位符
|
||||
this.beforeSpacer = null;
|
||||
this.afterSpacer = null;
|
||||
|
||||
// 清空容器
|
||||
if (this.container) {
|
||||
this.container.innerHTML = '';
|
||||
@@ -1494,11 +1505,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 延迟解锁,避免立即触发 scroll 事件导致循环
|
||||
// 缩短锁释放延迟,从150ms减少到50ms,提高响应速度
|
||||
setTimeout(() => {
|
||||
this.scrollLocked = false;
|
||||
this.needsScroll = false; // 滚动完成后重置标志
|
||||
}, 150);
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1513,9 +1524,18 @@
|
||||
}
|
||||
|
||||
this.pending.push({ text, className });
|
||||
// 增加批处理阈值到 100,减少渲染频率
|
||||
if (this.pending.length > 100) {
|
||||
// 优化批处理策略:超过50行或等待时间超过200ms后flush
|
||||
if (this.pending.length >= 50) {
|
||||
this.flush();
|
||||
} else if (this.pending.length === 1) {
|
||||
// 第一条消息时,设置一个定时器在200ms后自动flush
|
||||
if (this.flushTimer) {
|
||||
clearTimeout(this.flushTimer);
|
||||
}
|
||||
this.flushTimer = setTimeout(() => {
|
||||
this.flush();
|
||||
this.scheduleRender();
|
||||
}, 200);
|
||||
}
|
||||
this.maybeTrim();
|
||||
this.scheduleRender();
|
||||
@@ -1535,6 +1555,11 @@
|
||||
|
||||
flush() {
|
||||
if (!this.pending.length) return;
|
||||
// 清理批处理定时器
|
||||
if (this.flushTimer) {
|
||||
clearTimeout(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
this.lines.push(...this.pending);
|
||||
this.pending = [];
|
||||
this.maybeTrim();
|
||||
@@ -1572,12 +1597,18 @@
|
||||
this.container.innerHTML = '';
|
||||
// 清空时也清理pool
|
||||
this.pool = [];
|
||||
this.beforeSpacer = null;
|
||||
this.afterSpacer = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算内容哈希,只在内容真正变化时才更新 DOM
|
||||
const contentHash = `${total}-${this.lines[total - 1].text}`;
|
||||
// 改进内容哈希:包含总行数、前5行和后5行的摘要
|
||||
const hashSample = total <= 10
|
||||
? this.lines.map(l => l.text).join('|')
|
||||
: this.lines.slice(0, 5).map(l => l.text).join('|') + '|' +
|
||||
this.lines.slice(-5).map(l => l.text).join('|');
|
||||
const contentHash = `${total}-${hashSample}`;
|
||||
if (this.lastRenderHash === contentHash) {
|
||||
// 内容没有变化,只需要处理滚动(如果需要的话)
|
||||
if (this.needsScroll && this.autoScrollEnabled) {
|
||||
@@ -1619,25 +1650,29 @@
|
||||
this.pool.push(node);
|
||||
}
|
||||
|
||||
// 复用或创建占位符(避免每次重建)
|
||||
if (!this.beforeSpacer) {
|
||||
this.beforeSpacer = document.createElement('div');
|
||||
this.beforeSpacer.dataset.spacer = 'before';
|
||||
} else if (!this.beforeSpacer.parentNode) {
|
||||
// 如果占位符被意外移除,标记需要重建DOM
|
||||
this.beforeSpacer = document.createElement('div');
|
||||
this.beforeSpacer.dataset.spacer = 'before';
|
||||
}
|
||||
this.beforeSpacer.style.height = `${beforeHeight}px`;
|
||||
|
||||
if (!this.afterSpacer) {
|
||||
this.afterSpacer = document.createElement('div');
|
||||
this.afterSpacer.dataset.spacer = 'after';
|
||||
} else if (!this.afterSpacer.parentNode) {
|
||||
this.afterSpacer = document.createElement('div');
|
||||
this.afterSpacer.dataset.spacer = 'after';
|
||||
}
|
||||
this.afterSpacer.style.height = `${afterHeight}px`;
|
||||
|
||||
// 使用DocumentFragment来减少DOM重绘
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// 更新或创建前置占位符
|
||||
let beforeSpacer = this.container.querySelector('[data-spacer="before"]');
|
||||
if (!beforeSpacer) {
|
||||
beforeSpacer = document.createElement('div');
|
||||
beforeSpacer.dataset.spacer = 'before';
|
||||
}
|
||||
beforeSpacer.style.height = `${beforeHeight}px`;
|
||||
|
||||
// 更新或创建后置占位符
|
||||
let afterSpacer = this.container.querySelector('[data-spacer="after"]');
|
||||
if (!afterSpacer) {
|
||||
afterSpacer = document.createElement('div');
|
||||
afterSpacer.dataset.spacer = 'after';
|
||||
}
|
||||
afterSpacer.style.height = `${afterHeight}px`;
|
||||
|
||||
// 只更新可见区域的节点
|
||||
for (let idx = start; idx < end; idx++) {
|
||||
const line = this.lines[idx];
|
||||
@@ -1653,11 +1688,26 @@
|
||||
fragment.appendChild(node);
|
||||
}
|
||||
|
||||
// 一次性更新 DOM
|
||||
// 优化DOM更新:只在必要时清空容器
|
||||
// 检查容器是否需要重建(比如占位符丢失)
|
||||
const needsRebuild = !this.beforeSpacer.parentNode || !this.afterSpacer.parentNode;
|
||||
if (needsRebuild) {
|
||||
this.container.innerHTML = '';
|
||||
this.container.appendChild(beforeSpacer);
|
||||
this.container.appendChild(this.beforeSpacer);
|
||||
this.container.appendChild(fragment);
|
||||
this.container.appendChild(afterSpacer);
|
||||
this.container.appendChild(this.afterSpacer);
|
||||
} else {
|
||||
// 增量更新:只更新可见节点部分
|
||||
// 移除旧的可见节点
|
||||
const existingNodes = Array.from(this.container.querySelectorAll('.console-line'));
|
||||
existingNodes.forEach(node => {
|
||||
if (node.parentNode === this.container) {
|
||||
this.container.removeChild(node);
|
||||
}
|
||||
});
|
||||
// 在占位符之间插入新节点
|
||||
this.container.insertBefore(fragment, this.afterSpacer);
|
||||
}
|
||||
|
||||
// 只在有标记且自动滚动启用时才滚动到底部
|
||||
if (this.needsScroll && this.autoScrollEnabled) {
|
||||
@@ -2483,7 +2533,7 @@
|
||||
// 检查Report Engine是否被锁定
|
||||
if (app === 'report') {
|
||||
const reportButton = document.querySelector(`[data-app="report"]`);
|
||||
if (reportButton.classList.contains('locked')) {
|
||||
if (reportButton && reportButton.classList.contains('locked')) {
|
||||
showMessage('需等待其余三个Agent工作完毕', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -2493,62 +2543,37 @@
|
||||
document.querySelectorAll('.app-button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`[data-app="${app}"]`).classList.add('active');
|
||||
const targetButton = document.querySelector(`[data-app="${app}"]`);
|
||||
if (targetButton) {
|
||||
targetButton.classList.add('active');
|
||||
}
|
||||
|
||||
// 更新当前应用
|
||||
currentApp = app;
|
||||
|
||||
// 切换控制台层(不添加系统提示,避免频繁输出)
|
||||
setActiveConsoleLayer(app);
|
||||
|
||||
// 根据应用类型处理不同的显示逻辑
|
||||
// 更新嵌入页面(右侧内容区域)
|
||||
updateEmbeddedPage(app);
|
||||
|
||||
// 加载对应的控制台输出(只在必要时加载)
|
||||
if (app === 'forum') {
|
||||
// 切换到论坛模式
|
||||
document.getElementById('embeddedHeader').textContent = 'Forum Engine - 多智能体交流';
|
||||
|
||||
// 显示论坛容器,隐藏其他内容
|
||||
document.getElementById('forumContainer').classList.add('active');
|
||||
document.getElementById('reportContainer').classList.remove('active');
|
||||
|
||||
// 追加提示并加载forum日志
|
||||
appendConsoleTextLine('forum', '[系统] 切换到论坛模式');
|
||||
loadForumLog();
|
||||
|
||||
} else if (app === 'report') {
|
||||
// 切换到报告模式
|
||||
document.getElementById('embeddedHeader').textContent = 'Report Agent - 最终报告生成';
|
||||
|
||||
// 显示报告容器,隐藏其他内容
|
||||
document.getElementById('reportContainer').classList.add('active');
|
||||
document.getElementById('forumContainer').classList.remove('active');
|
||||
|
||||
// 追加提示并加载report日志
|
||||
appendConsoleTextLine('report', '[系统] 切换到报告生成模式');
|
||||
loadReportLog();
|
||||
|
||||
// 只在报告界面未初始化时才重新加载
|
||||
const reportContent = document.getElementById('reportContent');
|
||||
if (!reportContent || reportContent.children.length === 0) {
|
||||
loadReportInterface();
|
||||
}
|
||||
|
||||
// 切换到report页面时检查是否可以自动生成报告
|
||||
setTimeout(() => {
|
||||
checkReportLockStatus();
|
||||
}, 500);
|
||||
|
||||
} else {
|
||||
// 切换到普通Engine模式
|
||||
document.getElementById('embeddedHeader').textContent = agentTitles[app] || appNames[app];
|
||||
|
||||
// 隐藏论坛和报告容器
|
||||
document.getElementById('forumContainer').classList.remove('active');
|
||||
document.getElementById('reportContainer').classList.remove('active');
|
||||
|
||||
// 追加提示并加载新的控制台输出
|
||||
appendConsoleTextLine(app, '[系统] 切换到 ' + appNames[app]);
|
||||
loadConsoleOutput(app);
|
||||
}
|
||||
|
||||
// 更新嵌入页面
|
||||
updateEmbeddedPage(app);
|
||||
}
|
||||
|
||||
// 存储最后显示的行数,避免重复加载
|
||||
@@ -2575,15 +2600,15 @@
|
||||
layer.style.display = 'none';
|
||||
}
|
||||
|
||||
logRenderers[app] = new LogVirtualList(layer);
|
||||
container.appendChild(layer);
|
||||
consoleLayers[app] = layer;
|
||||
logRenderers[app] = new LogVirtualList(layer);
|
||||
|
||||
// 初始提示仅在渲染器内部渲染,不保留在 DOM
|
||||
logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`);
|
||||
});
|
||||
|
||||
container.scrollTop = container.scrollHeight;
|
||||
// 不需要手动设置滚动位置,LogVirtualList会处理
|
||||
}
|
||||
|
||||
function getConsoleLayer(app) {
|
||||
@@ -2613,24 +2638,33 @@
|
||||
const container = getConsoleContainer();
|
||||
if (!container) return;
|
||||
|
||||
// 如果已经是当前激活的层,跳过
|
||||
if (activeConsoleLayer === app && consoleLayers[app] && consoleLayers[app].style.display === 'block') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 隐藏旧的激活层
|
||||
if (activeConsoleLayer && consoleLayers[activeConsoleLayer]) {
|
||||
consoleLayerScrollPositions[activeConsoleLayer] = container.scrollTop;
|
||||
consoleLayers[activeConsoleLayer].classList.remove('active');
|
||||
consoleLayers[activeConsoleLayer].style.display = 'none';
|
||||
}
|
||||
|
||||
// 获取或创建目标层
|
||||
const targetLayer = getConsoleLayer(app);
|
||||
if (!targetLayer) return;
|
||||
|
||||
// 显示新的激活层
|
||||
targetLayer.style.display = 'block';
|
||||
targetLayer.classList.add('active');
|
||||
activeConsoleLayer = app;
|
||||
|
||||
const storedScroll = consoleLayerScrollPositions[app];
|
||||
if (typeof storedScroll === 'number') {
|
||||
container.scrollTop = storedScroll;
|
||||
} else {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
// 触发一次渲染以确保内容正确显示
|
||||
const renderer = logRenderers[app];
|
||||
if (renderer) {
|
||||
// 使用 requestAnimationFrame 确保在下一帧渲染,避免闪烁
|
||||
requestAnimationFrame(() => {
|
||||
renderer.scheduleRender(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2839,6 +2873,7 @@
|
||||
function updateEmbeddedPage(app) {
|
||||
const header = document.getElementById('embeddedHeader');
|
||||
const content = document.getElementById('embeddedContent');
|
||||
if (!header || !content) return;
|
||||
|
||||
// 如果是Forum Engine,直接显示论坛界面
|
||||
if (app === 'forum') {
|
||||
@@ -2942,7 +2977,7 @@
|
||||
}
|
||||
|
||||
placeholder.innerHTML = `
|
||||
<div style="margin-bottom: 10px;">${appNames[app]} 未运行</div>
|
||||
<div style="margin-bottom: 10px;">${appNames[app] || app} 未运行</div>
|
||||
<div style="font-size: 12px;">状态: ${appStatus[app]}</div>
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user