Files
bettafish-company/templates/index.html
T
2025-08-25 21:08:42 +08:00

1168 lines
40 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>致力于打造简洁通用的舆情分析平台</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: #ffffff;
color: #000000;
line-height: 1.6;
overflow-x: hidden;
}
.container {
max-width: 100vw;
height: 100vh; /* 固定高度为视口高度 */
display: flex;
flex-direction: column;
border: 2px solid #000000;
overflow: hidden; /* 防止整体滚动 */
}
/* 搜索框区域 */
.search-section {
border-bottom: 2px solid #000000;
padding: 20px;
background-color: #ffffff;
}
.search-title {
font-size: 24px;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
letter-spacing: 1px;
}
.search-box {
display: flex;
max-width: 800px;
margin: 0 auto;
border: 2px solid #000000;
}
.search-input {
flex: 1;
padding: 15px;
border: none;
outline: none;
font-size: 16px;
background-color: #ffffff;
}
.search-button {
padding: 15px 30px;
border: none;
border-left: 2px solid #000000;
background-color: #000000;
color: #ffffff;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s ease;
}
.search-button:hover {
background-color: #333333;
}
.search-button:disabled {
background-color: #666666;
cursor: not-allowed;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
height: calc(100vh - 140px);
min-height: 0; /* 允许子元素缩小 */
}
/* 嵌入页面区域 */
.embedded-section {
flex: 1.8; /* 稍微缩小左侧区域 */
border-right: 2px solid #000000;
background-color: #ffffff;
position: relative;
}
.embedded-header {
padding: 15px;
border-bottom: 2px solid #000000;
background-color: #ffffff;
font-weight: bold;
text-align: center;
}
.embedded-content {
height: calc(100% - 60px);
position: relative;
overflow: hidden;
}
/* 控制台输出区域 */
.console-section {
flex: 1.2; /* 稍微扩大右侧区域 */
display: flex;
flex-direction: column;
background-color: #ffffff;
min-height: 0; /* 允许子元素缩小 */
overflow: hidden; /* 防止内容溢出 */
}
/* 应用切换按钮 */
.app-switcher {
display: flex;
border-bottom: 2px solid #000000;
}
.app-button {
flex: 1;
padding: 15px;
border: none;
border-right: 2px solid #000000;
background-color: #ffffff;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
position: relative;
}
.app-button:last-child {
border-right: none;
}
.app-button.active {
background-color: #000000;
color: #ffffff;
}
.app-button:not(.active):hover {
background-color: #f0f0f0;
}
.status-indicator {
position: absolute;
top: 5px;
right: 5px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ff0000;
}
.status-indicator.running {
background-color: #00ff00;
}
.status-indicator.starting {
background-color: #ffff00;
}
/* 控制台输出 */
.console-output {
flex: 1;
padding: 15px;
background-color: #000000;
color: #00ff00;
font-family: 'Courier New', monospace;
font-size: 12px;
overflow-y: auto;
overflow-x: hidden;
white-space: pre-wrap;
word-break: break-all;
min-height: 0; /* 允许内容缩小 */
}
.console-line {
margin-bottom: 2px;
}
/* 状态信息 */
.status-bar {
padding: 10px 20px;
border-top: 2px solid #000000;
background-color: #ffffff;
font-size: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.loading {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #000000;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 响应式设计 */
@media (max-width: 768px) {
.main-content {
flex-direction: column;
height: auto;
}
.embedded-section {
border-right: none;
border-bottom: 2px solid #000000;
height: 400px;
}
.console-section {
height: 300px;
}
}
/* 消息提示 */
.message {
position: fixed;
top: 20px;
right: 0px;
padding: 15px 20px;
border: 2px solid #000000;
background-color: #ffffff;
color: #000000;
font-weight: bold;
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 1000;
}
.message.show {
transform: translateX(-5%);
}
.message.success {
border-color: #00aa00;
background-color: #f0fff0;
}
.message.error {
border-color: #aa0000;
background-color: #fff0f0;
}
/* Forum Engine 专用样式 */
.forum-container {
display: none;
height: 100%;
flex-direction: column;
}
.forum-container.active {
display: flex;
}
.forum-chat-area {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
overflow-y: auto;
}
.forum-message {
margin-bottom: 20px;
min-width: 200px;
max-width: 85%;
padding: 15px;
border: 2px solid #000000;
border-radius: 0;
word-wrap: break-word;
}
.forum-message.user {
align-self: flex-end;
background-color: #000000;
color: #ffffff;
}
.forum-message.system {
align-self: flex-start;
background-color:rgb(182 182 182);
color: #000000;
border-color:rgb(62 62 62);
}
.forum-message.agent {
align-self: stretch; /* 占满整个宽度 */
color: #000000;
width: 100%; /* 确保占满宽度 */
max-width: 100%; /* 移除最大宽度限制 */
margin: 0 0 20px 0; /* 移除左右边距 */
}
/* 不同Engine的颜色区分 */
        .forum-message.agent:has(.forum-message-header:contains("Query Engine")) {
            background-color: #eaf1f8;
            border-color: #608ab1;
        }
.forum-message.agent:has(.forum-message-header:contains("QUERY Engine")) {
            background-color: #eaf1f8;
            border-color: #608ab1;
    }
        .forum-message.agent:has(.forum-message-header:contains("Insight Engine")) {
            background-color: #f2ebf3;
            border-color: #8e6a9f;
        }
.forum-message.agent:has(.forum-message-header:contains("INSIGHT Engine")) {
            background-color: #f2ebf3;
            border-color: #8e6a9f;
        }
        .forum-message.agent:has(.forum-message-header:contains("Media Engine")) {
            background-color: #ebf2ea;
            border-color: #6a9a6e;
        }
.forum-message.agent:has(.forum-message-header:contains("MEDIA Engine")) {
            background-color: #ebf2ea;
            border-color: #6a9a6e;
        }
        /* 备用方案:通过JavaScript添加的类 */
        .forum-message.query-engine {
            background-color: #eaf1f8;
            border-color: #608ab1;
        }
        .forum-message.insight-engine {
            background-color: #f2ebf3;
            border-color: #8e6a9f;
        }
        .forum-message.media-engine {
            background-color: #ebf2ea;
            border-color: #6a9a6e;
        }
.forum-message.agent.QUERY {
background-color: #eaf1f8;
border-color: #608ab1;
}
.forum-message.agent.query-engine {
background-color: #eaf1f8;
border-color: #608ab1;
}
        .forum-message.agent.MEDIA {
            background-color: #ebf2ea;
            border-color: #6a9a6e;
        }
.forum-message.agent.media-engine {
background-color: #ebf2ea;
border-color: #6a9a6e;
}
.forum-message.agent.INSIGHT {
background-color: #f2ebf3;
border-color: #8e6a9f;
}
.forum-message.agent.insight-engine {
background-color: #f2ebf3;
border-color: #8e6a9f;
}
.forum-message-header {
font-weight: bold;
margin-bottom: 8px;
font-size: 12px;
opacity: 0.8;
}
.forum-message-content {
line-height: 1.4;
word-wrap: break-word;
}
.forum-timestamp {
font-size: 10px;
opacity: 0.6;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<!-- 搜索框区域 -->
<div class="search-section">
<div class="search-title">致力于打造简洁通用的舆情分析平台</div>
<div class="search-box">
<input type="text" class="search-input" id="searchInput" placeholder="请输入要分析的内容...">
<button class="search-button" id="searchButton">开始</button>
</div>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<!-- 嵌入页面区域 -->
<div class="embedded-section">
<div class="embedded-header" id="embeddedHeader">嵌入的页面</div>
<div class="embedded-content" id="embeddedContent">
<!-- 论坛聊天界面 -->
<div class="forum-container" id="forumContainer">
<div class="forum-chat-area" id="forumChatArea">
<!-- 动态添加的Engine对话消息将在这里显示 -->
</div>
</div>
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #666;">
<span>默认只显示第一个页面 - 点击按钮切换页面</span>
</div>
</div>
</div>
<!-- 控制台输出区域 -->
<div class="console-section">
<!-- 应用切换按钮 -->
<div class="app-switcher">
<button class="app-button active" data-app="insight">
<span class="status-indicator" id="status-insight"></span>
Insight Engine
</button>
<button class="app-button" data-app="media">
<span class="status-indicator" id="status-media"></span>
Media Engine
</button>
<button class="app-button" data-app="query">
<span class="status-indicator" id="status-query"></span>
Query Engine
</button>
<button class="app-button" data-app="forum">
<span class="status-indicator running" id="status-forum"></span>
Forum Engine
</button>
</div>
<!-- 控制台输出 -->
<div class="console-output" id="consoleOutput">
<div class="console-line">[系统] 等待连接...</div>
</div>
</div>
</div>
<!-- 状态栏 -->
<div class="status-bar">
<span id="connectionStatus">连接中...</span>
<span id="systemTime"></span>
</div>
</div>
<!-- 消息提示 -->
<div class="message" id="message"></div>
<script>
// 全局变量
let socket;
let currentApp = 'insight';
let appStatus = {
insight: 'stopped',
media: 'stopped',
query: 'stopped',
forum: 'running' // Forum Engine 默认运行
};
// 应用名称映射
const appNames = {
insight: 'Insight Engine',
media: 'Media Engine',
query: 'Query Engine',
forum: 'Forum Engine'
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeSocket();
initializeEventListeners();
updateTime();
setInterval(updateTime, 1000);
checkStatus();
setInterval(checkStatus, 5000);
// 定期刷新控制台输出
setInterval(() => {
refreshConsoleOutput();
}, 1000);
// 定期刷新论坛对话(实时更新)
setInterval(() => {
refreshForumMessages();
}, 2000);
// 初始化论坛相关功能
initializeForum();
// 延迟预加载iframe以确保应用启动完成
setTimeout(() => {
preloadIframes();
}, 3000);
});
// Socket.IO连接
function initializeSocket() {
socket = io();
socket.on('connect', function() {
updateConnectionStatus('已连接');
socket.emit('request_status');
});
socket.on('disconnect', function() {
updateConnectionStatus('连接断开');
});
socket.on('console_output', function(data) {
// 处理控制台输出
if (data.app === currentApp) {
addConsoleOutput(data.line);
}
// 如果是forum的输出,同时也处理为论坛消息
if (data.app === 'forum') {
const parsed = parseForumMessage(data.line);
if (parsed) {
addForumMessage(parsed);
}
}
});
socket.on('forum_message', function(data) {
addForumMessage(data);
});
socket.on('status_update', function(data) {
updateAppStatus(data);
});
}
// 事件监听器
function initializeEventListeners() {
// 搜索按钮
document.getElementById('searchButton').addEventListener('click', performSearch);
document.getElementById('searchInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
performSearch();
}
});
// 应用切换按钮
document.querySelectorAll('.app-button').forEach(button => {
button.addEventListener('click', function() {
const app = this.dataset.app;
switchToApp(app);
});
});
}
// 执行搜索
function performSearch() {
const query = document.getElementById('searchInput').value.trim();
if (!query) {
showMessage('请输入搜索内容', 'error');
return;
}
const button = document.getElementById('searchButton');
button.disabled = true;
button.innerHTML = '<span class="loading"></span> 搜索中...';
// 确保所有iframe已初始化
if (!iframesInitialized) {
preloadIframes();
}
// 向所有运行中的应用发送搜索请求(通过刷新iframe传递参数)
let totalRunning = 0;
const ports = { insight: 8501, media: 8502, query: 8503 };
Object.keys(appStatus).forEach(app => {
if (appStatus[app] === 'running' && preloadedIframes[app]) {
totalRunning++;
// 构建搜索URL
const searchUrl = `http://localhost:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`;
console.log(`${app} 发送搜索请求: ${searchUrl}`);
// 直接更新主iframe的src来传递搜索参数
preloadedIframes[app].src = searchUrl;
}
});
if (totalRunning === 0) {
button.disabled = false;
button.innerHTML = '搜索';
showMessage('没有运行中的应用,无法执行搜索', 'error');
} else {
button.disabled = false;
button.innerHTML = '搜索';
showMessage(`搜索请求已发送到 ${totalRunning} 个应用,页面将刷新以开始研究`, 'success');
}
}
// 切换应用
function switchToApp(app) {
if (app === currentApp) return;
// 更新按钮状态
document.querySelectorAll('.app-button').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-app="${app}"]`).classList.add('active');
currentApp = app;
// 根据应用类型处理不同的显示逻辑
if (app === 'forum') {
// 切换到论坛模式
document.getElementById('embeddedHeader').textContent = 'Forum Engine - 论坛对话';
// 显示论坛容器,隐藏其他内容
document.getElementById('forumContainer').classList.add('active');
// 清空控制台并加载forum日志
document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到论坛模式</div>';
loadForumLog();
} else {
// 切换到普通Engine模式
document.getElementById('embeddedHeader').textContent = appNames[app];
// 隐藏论坛容器
document.getElementById('forumContainer').classList.remove('active');
// 清空并加载新的控制台输出
document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + appNames[app] + '</div>';
// 重置行计数
lastLineCount[app] = 0;
loadConsoleOutput(app);
}
// 更新嵌入页面
updateEmbeddedPage(app);
}
// 存储最后显示的行数,避免重复加载
let lastLineCount = {};
// 加载控制台输出
function loadConsoleOutput(app) {
if (app === 'forum') {
loadForumLog();
return;
}
fetch(`/api/output/${app}`)
.then(response => response.json())
.then(data => {
if (data.success && data.output.length > 0) {
const consoleOutput = document.getElementById('consoleOutput');
// 只添加新的行
const lastCount = lastLineCount[app] || 0;
const newLines = data.output.slice(lastCount);
newLines.forEach(line => {
const div = document.createElement('div');
div.className = 'console-line';
div.textContent = line;
consoleOutput.appendChild(div);
});
lastLineCount[app] = data.output.length;
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
})
.catch(error => {
console.error('加载输出失败:', error);
});
}
// 刷新当前应用的控制台输出
function refreshConsoleOutput() {
if (currentApp === 'forum') {
refreshForumLog();
return;
}
if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') {
fetch(`/api/output/${currentApp}`)
.then(response => response.json())
.then(data => {
if (data.success && data.output.length > 0) {
const consoleOutput = document.getElementById('consoleOutput');
// 只添加新的行
const lastCount = lastLineCount[currentApp] || 0;
const newLines = data.output.slice(lastCount);
if (newLines.length > 0) {
newLines.forEach(line => {
const div = document.createElement('div');
div.className = 'console-line';
div.textContent = line;
consoleOutput.appendChild(div);
});
lastLineCount[currentApp] = data.output.length;
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
}
})
.catch(error => {
console.error('刷新输出失败:', error);
});
}
}
// 添加控制台输出
function addConsoleOutput(line) {
const consoleOutput = document.getElementById('consoleOutput');
const div = document.createElement('div');
div.className = 'console-line';
div.textContent = line;
consoleOutput.appendChild(div);
// 自动滚动到底部显示最新内容
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
// 预加载的iframe存储
let preloadedIframes = {};
let iframesInitialized = false;
// 预加载所有iframe(只执行一次)
function preloadIframes() {
if (iframesInitialized) return;
const ports = { insight: 8501, media: 8502, query: 8503 };
const content = document.getElementById('embeddedContent');
for (const [app, port] of Object.entries(ports)) {
const iframe = document.createElement('iframe');
iframe.src = `http://localhost:${port}`;
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none';
iframe.style.position = 'absolute';
iframe.style.top = '0';
iframe.style.left = '0';
iframe.style.display = 'none';
iframe.id = `iframe-${app}`;
// 直接添加到content区域
content.appendChild(iframe);
preloadedIframes[app] = iframe;
console.log(`预加载 ${app} 应用完成`);
}
iframesInitialized = true;
console.log('所有iframe预加载完成,准备进行无缝切换');
}
// 更新嵌入页面
function updateEmbeddedPage(app) {
const header = document.getElementById('embeddedHeader');
const content = document.getElementById('embeddedContent');
// 如果是Forum Engine,直接显示论坛界面
if (app === 'forum') {
header.textContent = 'Forum Engine - 论坛对话';
// 隐藏所有iframe
if (typeof preloadedIframes !== 'undefined') {
Object.values(preloadedIframes).forEach(iframe => {
iframe.style.display = 'none';
});
}
// 移除占位符
const placeholder = content.querySelector('.status-placeholder');
if (placeholder) {
placeholder.remove();
}
// 显示论坛容器
document.getElementById('forumContainer').classList.add('active');
return;
}
// 隐藏论坛容器
document.getElementById('forumContainer').classList.remove('active');
header.textContent = appNames[app] || app;
// 如果应用正在运行,显示对应的iframe
if (appStatus[app] === 'running') {
// 确保iframe已初始化
if (!iframesInitialized) {
preloadIframes();
}
// 隐藏所有iframe
Object.values(preloadedIframes).forEach(iframe => {
iframe.style.display = 'none';
});
// 移除占位符
const placeholder = content.querySelector('.status-placeholder');
if (placeholder) {
placeholder.remove();
}
// 显示当前应用的iframe
if (preloadedIframes[app]) {
preloadedIframes[app].style.display = 'block';
console.log(`切换到 ${app} 应用 - 无刷新切换`);
}
} else {
// 隐藏所有iframe
Object.values(preloadedIframes).forEach(iframe => {
iframe.style.display = 'none';
});
// 显示状态信息
let placeholder = content.querySelector('.status-placeholder');
if (!placeholder) {
placeholder = document.createElement('div');
placeholder.className = 'status-placeholder';
placeholder.style.cssText = 'display: flex; align-items: center; justify-content: center; height: 100%; color: #666; flex-direction: column; position: absolute; top: 0; left: 0; width: 100%;';
content.appendChild(placeholder);
}
placeholder.innerHTML = `
<div style="margin-bottom: 10px;">${appNames[app]} 未运行</div>
<div style="font-size: 12px;">状态: ${appStatus[app]}</div>
`;
}
}
// 检查应用状态
function checkStatus() {
fetch('/api/status')
.then(response => response.json())
.then(data => {
updateAppStatus(data);
})
.catch(error => {
console.error('状态检查失败:', error);
});
}
// 更新应用状态
function updateAppStatus(data) {
for (const [app, info] of Object.entries(data)) {
// 适配实际的API格式:{app: {status: string, port: int, output_lines: int}}
const status = info.status === 'running' ? 'running' : 'stopped';
appStatus[app] = status;
const indicator = document.getElementById(`status-${app}`);
if (indicator) {
indicator.className = `status-indicator ${status}`;
}
}
// 如果当前显示的应用状态发生变化,更新嵌入页面
updateEmbeddedPage(currentApp);
}
// 更新连接状态
function updateConnectionStatus(status) {
document.getElementById('connectionStatus').textContent = status;
}
// 更新时间
function updateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN');
document.getElementById('systemTime').textContent = timeString;
}
// 显示消息
function showMessage(text, type = 'info') {
const message = document.getElementById('message');
// 清除之前的定时器
if (message.hideTimer) {
clearTimeout(message.hideTimer);
}
message.textContent = text;
message.className = `message ${type}`;
message.classList.add('show');
message.hideTimer = setTimeout(() => {
message.classList.remove('show');
// 延迟清除内容,等待动画完成
setTimeout(() => {
message.textContent = '';
message.className = 'message';
}, 300);
}, 3000);
}
// Forum Engine 相关函数
let forumLogLineCount = 0;
// 实时刷新论坛消息(适用于所有页面)
function refreshForumMessages() {
fetch('/api/forum/log')
.then(response => response.json())
.then(data => {
if (data.success && data.log_lines.length > forumLogLineCount) {
console.log(`Forum: 发现新消息,当前行数: ${data.log_lines.length}, 上次处理: ${forumLogLineCount}`);
// 只处理新增的日志行
const newLines = data.log_lines.slice(forumLogLineCount);
newLines.forEach((line, index) => {
console.log(`Forum: 处理新行 ${forumLogLineCount + index + 1}: ${line}`);
const parsed = parseForumMessage(line);
if (parsed) {
console.log(`Forum: 解析成功,添加消息:`, parsed);
addForumMessage(parsed);
}
});
forumLogLineCount = data.log_lines.length;
}
})
.catch(error => {
console.error('刷新论坛消息失败:', error);
});
}
// 初始化论坛功能
function initializeForum() {
// 初始化时加载一次论坛日志
refreshForumMessages();
}
// 加载论坛日志
function loadForumLog() {
fetch('/api/forum/log')
.then(response => response.json())
.then(data => {
if (data.success) {
// 清空对话区
const chatArea = document.getElementById('forumChatArea');
chatArea.innerHTML += `
<div class="forum-message system">
<div class="forum-message-header">SYSTEM</div>
<div class="forum-message-content">ForumEngine 论坛已启动,ID98fiaw4324dwadhsl21awhs908147</div>
<div class="forum-timestamp">${new Date().toLocaleTimeString('zh-CN')}</div>
</div>
`;
// 加载控制台日志
const consoleOutput = document.getElementById('consoleOutput');
consoleOutput.innerHTML = '<div class="console-line">[系统] Forum Engine 日志输出</div>';
if (data.log_lines && data.log_lines.length > 0) {
data.log_lines.forEach(line => {
const div = document.createElement('div');
div.className = 'console-line';
div.textContent = line;
consoleOutput.appendChild(div);
// 解析并添加到对话区
const parsed = parseForumMessage(line);
//if (parsed) {
//addForumMessage(parsed);
//}
});
// 重置计数器以确保后续消息能正确显示
forumLogLineCount = data.log_lines.length;
} else {
// 如果没有日志,重置计数器
forumLogLineCount = 0;
}
// 如果有解析的消息,直接使用
if (data.parsed_messages && data.parsed_messages.length > 0) {
data.parsed_messages.forEach(message => {
// addForumMessage(message);
});
}
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
})
.catch(error => {
console.error('加载论坛日志失败:', error);
});
}
// 刷新论坛日志
function refreshForumLog() {
fetch('/api/forum/log')
.then(response => response.json())
.then(data => {
if (data.success && data.log_lines.length > forumLogLineCount) {
const consoleOutput = document.getElementById('consoleOutput');
// 只添加新的行
const newLines = data.log_lines.slice(forumLogLineCount);
newLines.forEach(line => {
const div = document.createElement('div');
div.className = 'console-line';
div.textContent = line;
consoleOutput.appendChild(div);
// 如果是论坛对话内容,也显示到左侧对话区
const parsed = parseForumMessage(line);
if (parsed) {
addForumMessage(parsed);
}
});
forumLogLineCount = data.log_lines.length;
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
})
.catch(error => {
console.error('刷新论坛日志失败:', error);
});
}
// 解析论坛消息并添加到对话区
function parseForumMessage(logLine) {
try {
// 解析日志行格式: [HH:MM:SS] [SOURCE] content
const timeMatch = logLine.match(/^\[(\d{2}:\d{2}:\d{2})\]/);
if (!timeMatch) return null;
const timestamp = timeMatch[1];
const restContent = logLine.substring(timeMatch[0].length).trim();
// 解析源标签
const sourceMatch = restContent.match(/^\[([^\]]+)\]\s*(.*)$/);
if (!sourceMatch) return null;
const source = sourceMatch[1];
const content = sourceMatch[2];
// 只处理三个Engine的消息,过滤掉系统消息和空内容
if (!['QUERY', 'INSIGHT', 'MEDIA'].includes(source.toUpperCase()) ||
!content || content.includes('=== ForumEgine')) {
return null;
}
// 根据源类型确定消息类型
const messageType = 'agent';
let displayName = '';
switch(source.toUpperCase()) {
case 'INSIGHT':
displayName = 'Insight Engine';
break;
case 'MEDIA':
displayName = 'Media Engine';
break;
case 'QUERY':
displayName = 'Query Engine';
break;
}
// 处理内容中的转义字符
const displayContent = content.replace(/\\n/g, '\n').replace(/\\r/g, '');
// 返回解析后的消息对象
return {
type: messageType,
source: displayName,
content: displayContent,
timestamp: timestamp
};
} catch (error) {
console.error('解析论坛消息失败:', error);
return null;
}
}
// 添加论坛消息到对话区
function addForumMessage(data) {
const chatArea = document.getElementById('forumChatArea');
const messageDiv = document.createElement('div');
const messageType = data.type || 'system';
messageDiv.className = `forum-message ${messageType}`;
// 根据来源添加特定的CSS类用于颜色区分
if (data.source) {
const sourceClass = data.source.toLowerCase().replace(/\s+/g, '-');
messageDiv.classList.add(sourceClass);
// 添加具体的engine类
if (data.source.toLowerCase().includes('query')) {
messageDiv.classList.add('query-engine');
} else if (data.source.toLowerCase().includes('insight')) {
messageDiv.classList.add('insight-engine');
} else if (data.source.toLowerCase().includes('media')) {
messageDiv.classList.add('media-engine');
}
}
// 构建消息头部,显示来源名称
const headerText = data.sender || data.source || getMessageHeader(messageType);
messageDiv.innerHTML = `
<div class="forum-message-header">${headerText}</div>
<div class="forum-message-content">${formatMessageContent(data.content)}</div>
<div class="forum-timestamp">${data.timestamp || new Date().toLocaleTimeString('zh-CN')}</div>
`;
chatArea.appendChild(messageDiv);
// 自动滚动到底部
chatArea.scrollTop = chatArea.scrollHeight;
}
// 格式化消息内容
function formatMessageContent(content) {
if (!content) return '';
// 将换行符转换为HTML换行
return content.replace(/\n/g, '<br>');
}
// 获取消息头部
function getMessageHeader(type) {
switch(type) {
case 'user': return '用户';
case 'agent': return 'AI助手';
case 'system': return '系统';
default: return '未知';
}
}
</script>
</body>
</html>