1741 lines
62 KiB
HTML
1741 lines
62 KiB
HTML
<!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;
|
||
}
|
||
|
||
.app-button.locked {
|
||
background-color: #f5f5f5;
|
||
color: #999999;
|
||
cursor: not-allowed;
|
||
position: relative;
|
||
}
|
||
|
||
.app-button.locked:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.app-button.locked::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 50%;
|
||
right: 10px;
|
||
transform: translateY(-50%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.app-button.locked .status-indicator {
|
||
background-color: #cccccc;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* Report Engine 专用样式 */
|
||
.report-container {
|
||
display: none;
|
||
height: 100%;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
.report-container.active {
|
||
display: flex;
|
||
}
|
||
|
||
.report-content {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.report-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
border-bottom: 2px solid #000000;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.report-button {
|
||
padding: 10px 20px;
|
||
border: 2px solid #000000;
|
||
background-color: #ffffff;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.report-button:hover {
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.report-button:disabled {
|
||
background-color: #e0e0e0;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.report-button.primary {
|
||
background-color: #000000;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.report-button.primary:hover {
|
||
background-color: #333333;
|
||
}
|
||
|
||
.report-status {
|
||
padding: 15px;
|
||
margin: 10px 0;
|
||
border: 2px solid #000000;
|
||
background-color: #f8f9fa;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.report-status.loading {
|
||
border-color: #ffa500;
|
||
background-color: #fff8e1;
|
||
}
|
||
|
||
.report-status.success {
|
||
border-color: #00aa00;
|
||
background-color: #f0fff0;
|
||
}
|
||
|
||
.report-status.error {
|
||
border-color: #aa0000;
|
||
background-color: #fff0f0;
|
||
}
|
||
|
||
.report-preview {
|
||
border: 2px solid #000000;
|
||
background-color: #ffffff;
|
||
min-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.report-preview iframe {
|
||
width: 100%;
|
||
height: 100%;
|
||
border: none;
|
||
}
|
||
|
||
.report-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 200px;
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
</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 class="report-container" id="reportContainer">
|
||
<div class="report-content" id="reportContent">
|
||
<!-- 报告内容将在这里显示 -->
|
||
</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>
|
||
<button class="app-button locked" data-app="report" title="需等待其余三个Agent工作完毕">
|
||
<span class="status-indicator" id="status-report"></span>
|
||
Report 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 默认运行
|
||
report: 'stopped' // Report Engine
|
||
};
|
||
|
||
// 应用名称映射
|
||
const appNames = {
|
||
insight: 'Insight Engine',
|
||
media: 'Media Engine',
|
||
query: 'Query Engine',
|
||
forum: 'Forum Engine',
|
||
report: 'Report Engine'
|
||
};
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializeSocket();
|
||
initializeEventListeners();
|
||
updateTime();
|
||
setInterval(updateTime, 1000);
|
||
checkStatus();
|
||
setInterval(checkStatus, 5000);
|
||
|
||
// 初始化Report Engine锁定状态检查
|
||
checkReportLockStatus();
|
||
reportLockCheckInterval = setInterval(checkReportLockStatus, 10000); // 每10秒检查一次
|
||
|
||
// 定期刷新控制台输出
|
||
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;
|
||
|
||
// 检查Report Engine是否被锁定
|
||
if (app === 'report') {
|
||
const reportButton = document.querySelector(`[data-app="report"]`);
|
||
if (reportButton.classList.contains('locked')) {
|
||
showMessage('需等待其余三个Agent工作完毕', 'error');
|
||
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');
|
||
document.getElementById('reportContainer').classList.remove('active');
|
||
|
||
// 清空控制台并加载forum日志
|
||
document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到论坛模式</div>';
|
||
loadForumLog();
|
||
|
||
} else if (app === 'report') {
|
||
// 切换到报告模式
|
||
document.getElementById('embeddedHeader').textContent = 'Report Engine - 智能报告生成';
|
||
|
||
// 显示报告容器,隐藏其他内容
|
||
document.getElementById('reportContainer').classList.add('active');
|
||
document.getElementById('forumContainer').classList.remove('active');
|
||
|
||
// 清空控制台并加载report日志
|
||
document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到报告生成模式</div>';
|
||
loadReportLog();
|
||
loadReportInterface();
|
||
|
||
} else {
|
||
// 切换到普通Engine模式
|
||
document.getElementById('embeddedHeader').textContent = appNames[app];
|
||
|
||
// 隐藏论坛和报告容器
|
||
document.getElementById('forumContainer').classList.remove('active');
|
||
document.getElementById('reportContainer').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;
|
||
}
|
||
|
||
if (app === 'report') {
|
||
loadReportLog();
|
||
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 (currentApp === 'report') {
|
||
refreshReportLog();
|
||
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');
|
||
document.getElementById('reportContainer').classList.remove('active');
|
||
return;
|
||
}
|
||
|
||
// 如果是Report Engine,显示报告界面
|
||
if (app === 'report') {
|
||
header.textContent = 'Report 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('reportContainer').classList.add('active');
|
||
document.getElementById('forumContainer').classList.remove('active');
|
||
return;
|
||
}
|
||
|
||
// 隐藏论坛和报告容器
|
||
document.getElementById('forumContainer').classList.remove('active');
|
||
document.getElementById('reportContainer').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;
|
||
|
||
// Report Engine 相关函数
|
||
let reportLogLineCount = 0;
|
||
let reportLockCheckInterval = null;
|
||
|
||
// 实时刷新论坛消息(适用于所有页面)
|
||
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 论坛已启动,ID:98fiaw4324dwadhsl21awhs908147</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);
|
||
});
|
||
}
|
||
|
||
// 刷新Report Engine日志
|
||
// 检查Report Engine锁定状态
|
||
function checkReportLockStatus() {
|
||
fetch('/api/report/status')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const reportButton = document.querySelector('[data-app="report"]');
|
||
|
||
if (data.success && data.engines_ready) {
|
||
// 文件准备就绪,解锁按钮
|
||
reportButton.classList.remove('locked');
|
||
reportButton.title = 'Report Engine - 智能报告生成\n所有引擎都有新文件,可以生成报告';
|
||
} else {
|
||
// 文件未准备就绪,锁定按钮
|
||
reportButton.classList.add('locked');
|
||
|
||
// 构建详细的提示信息
|
||
let titleInfo = 'Report Engine暂时锁定\n';
|
||
|
||
if (data.missing_files && data.missing_files.length > 0) {
|
||
titleInfo += '等待新文件:\n' + data.missing_files.join('\n');
|
||
} else {
|
||
titleInfo += '等待三个分析引擎都产生新的报告文件';
|
||
}
|
||
|
||
reportButton.title = titleInfo;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('检查Report Engine状态失败:', error);
|
||
// 出错时默认锁定
|
||
const reportButton = document.querySelector('[data-app="report"]');
|
||
reportButton.classList.add('locked');
|
||
reportButton.title = 'Report Engine状态检查失败';
|
||
});
|
||
}
|
||
|
||
function refreshReportLog() {
|
||
fetch('/api/report/log')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success && data.log_lines.length > reportLogLineCount) {
|
||
const consoleOutput = document.getElementById('consoleOutput');
|
||
|
||
// 只添加新的行
|
||
const newLines = data.log_lines.slice(reportLogLineCount);
|
||
newLines.forEach(line => {
|
||
const div = document.createElement('div');
|
||
div.className = 'console-line';
|
||
div.textContent = line;
|
||
consoleOutput.appendChild(div);
|
||
});
|
||
|
||
reportLogLineCount = data.log_lines.length;
|
||
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('刷新Report日志失败:', error);
|
||
});
|
||
}
|
||
|
||
// 加载Report Engine日志
|
||
function loadReportLog() {
|
||
fetch('/api/report/log')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
const consoleOutput = document.getElementById('consoleOutput');
|
||
consoleOutput.innerHTML = '<div class="console-line">[系统] Report 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);
|
||
});
|
||
|
||
// 重置计数器以确保后续消息能正确显示
|
||
reportLogLineCount = data.log_lines.length;
|
||
} else {
|
||
// 如果没有日志,重置计数器
|
||
reportLogLineCount = 0;
|
||
}
|
||
|
||
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('加载Report日志失败:', 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 '未知';
|
||
}
|
||
}
|
||
|
||
// Report Engine 相关函数
|
||
let reportTaskId = null;
|
||
let reportPollingInterval = null;
|
||
|
||
// 加载报告界面
|
||
function loadReportInterface() {
|
||
const reportContent = document.getElementById('reportContent');
|
||
|
||
// 检查ReportEngine状态
|
||
fetch('/api/report/status')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// 更新ReportEngine状态指示器
|
||
const indicator = document.getElementById('status-report');
|
||
if (indicator) {
|
||
if (data.initialized) {
|
||
indicator.className = 'status-indicator running';
|
||
appStatus.report = 'running';
|
||
} else {
|
||
indicator.className = 'status-indicator';
|
||
appStatus.report = 'stopped';
|
||
}
|
||
}
|
||
|
||
// 渲染报告界面
|
||
renderReportInterface(data);
|
||
} else {
|
||
reportContent.innerHTML = `
|
||
<div class="report-status error">
|
||
<strong>错误:</strong> ${data.error}
|
||
</div>
|
||
`;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('加载报告界面失败:', error);
|
||
reportContent.innerHTML = `
|
||
<div class="report-status error">
|
||
<strong>加载失败:</strong> ${error.message}
|
||
</div>
|
||
`;
|
||
});
|
||
}
|
||
|
||
// 渲染报告界面
|
||
function renderReportInterface(statusData) {
|
||
const reportContent = document.getElementById('reportContent');
|
||
|
||
let interfaceHTML = `
|
||
<div class="report-controls">
|
||
<button class="report-button primary" onclick="generateReport()" id="generateBtn">
|
||
生成最终报告
|
||
</button>
|
||
<button class="report-button" onclick="checkReportStatus()" id="statusBtn">
|
||
检查状态
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
// 显示状态信息
|
||
if (statusData.initialized) {
|
||
interfaceHTML += `
|
||
<div class="report-status success">
|
||
<strong>ReportEngine状态:</strong> 已初始化<br>
|
||
<strong>文件检查:</strong> ${statusData.engines_ready ? '准备就绪' : '文件未就绪'}<br>
|
||
<strong>找到文件:</strong> ${statusData.files_found ? statusData.files_found.join(', ') : '无'}<br>
|
||
${statusData.missing_files && statusData.missing_files.length > 0 ?
|
||
`<strong>缺失文件:</strong> ${statusData.missing_files.join(', ')}` : ''}
|
||
</div>
|
||
`;
|
||
} else {
|
||
interfaceHTML += `
|
||
<div class="report-status error">
|
||
<strong>ReportEngine状态:</strong> 未初始化
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 如果有当前任务,显示任务状态
|
||
if (statusData.current_task) {
|
||
interfaceHTML += renderTaskStatus(statusData.current_task);
|
||
}
|
||
|
||
// 添加报告预览区域
|
||
interfaceHTML += `
|
||
<div class="report-preview" id="reportPreview">
|
||
<div class="report-loading">
|
||
点击"生成最终报告"开始生成综合分析报告
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
reportContent.innerHTML = interfaceHTML;
|
||
}
|
||
|
||
// 渲染任务状态
|
||
function renderTaskStatus(task) {
|
||
const statusClass = task.status === 'completed' ? 'success' :
|
||
task.status === 'error' ? 'error' : 'loading';
|
||
|
||
let statusHTML = `
|
||
<div class="report-status ${statusClass}">
|
||
<strong>任务ID:</strong> ${task.task_id}<br>
|
||
<strong>查询:</strong> ${task.query}<br>
|
||
<strong>状态:</strong> ${task.status}<br>
|
||
<strong>进度:</strong> ${task.progress}%<br>
|
||
<strong>创建时间:</strong> ${new Date(task.created_at).toLocaleString()}<br>
|
||
<strong>更新时间:</strong> ${new Date(task.updated_at).toLocaleString()}
|
||
`;
|
||
|
||
if (task.error_message) {
|
||
statusHTML += `<br><strong>错误信息:</strong> ${task.error_message}`;
|
||
}
|
||
|
||
if (task.has_result) {
|
||
statusHTML += `<br><button class="report-button" onclick="viewReport('${task.task_id}')">查看报告</button>`;
|
||
}
|
||
|
||
statusHTML += '</div>';
|
||
return statusHTML;
|
||
}
|
||
|
||
// 生成报告
|
||
function generateReport() {
|
||
const query = document.getElementById('searchInput').value.trim() || '智能舆情分析报告';
|
||
|
||
// 重置日志计数器,因为后台会清空日志文件
|
||
reportLogLineCount = 0;
|
||
|
||
// 清空控制台显示
|
||
const consoleOutput = document.getElementById('consoleOutput');
|
||
consoleOutput.innerHTML = '<div class="console-line">[系统] 开始生成报告,日志已重置</div>';
|
||
|
||
const generateBtn = document.getElementById('generateBtn');
|
||
generateBtn.disabled = true;
|
||
generateBtn.textContent = '生成中...';
|
||
|
||
fetch('/api/report/generate', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ query: query })
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
reportTaskId = data.task_id;
|
||
showMessage('报告生成已启动', 'success');
|
||
|
||
// 立即刷新一次日志以确保同步
|
||
setTimeout(() => {
|
||
refreshReportLog();
|
||
}, 500);
|
||
|
||
// 开始轮询任务状态
|
||
startProgressPolling(data.task_id);
|
||
} else {
|
||
showMessage('启动失败: ' + data.error, 'error');
|
||
generateBtn.disabled = false;
|
||
generateBtn.textContent = '生成最终报告';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('生成报告失败:', error);
|
||
showMessage('生成报告失败: ' + error.message, 'error');
|
||
generateBtn.disabled = false;
|
||
generateBtn.textContent = '生成最终报告';
|
||
});
|
||
}
|
||
|
||
// 开始进度轮询
|
||
function startProgressPolling(taskId) {
|
||
if (reportPollingInterval) {
|
||
clearInterval(reportPollingInterval);
|
||
}
|
||
|
||
reportPollingInterval = setInterval(() => {
|
||
checkTaskProgress(taskId);
|
||
}, 2000);
|
||
}
|
||
|
||
// 检查任务进度
|
||
function checkTaskProgress(taskId) {
|
||
fetch(`/api/report/progress/${taskId}`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
updateProgressDisplay(data.task);
|
||
|
||
// 在检查进度时也刷新日志
|
||
refreshReportLog();
|
||
|
||
if (data.task.status === 'completed') {
|
||
clearInterval(reportPollingInterval);
|
||
showMessage('报告生成完成!', 'success');
|
||
|
||
// 自动显示报告
|
||
viewReport(taskId);
|
||
|
||
// 重新启用生成按钮
|
||
const generateBtn = document.getElementById('generateBtn');
|
||
generateBtn.disabled = false;
|
||
generateBtn.textContent = '生成最终报告';
|
||
} else if (data.task.status === 'error') {
|
||
clearInterval(reportPollingInterval);
|
||
showMessage('报告生成失败: ' + data.task.error_message, 'error');
|
||
|
||
// 重新启用生成按钮
|
||
const generateBtn = document.getElementById('generateBtn');
|
||
generateBtn.disabled = false;
|
||
generateBtn.textContent = '生成最终报告';
|
||
}
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('检查进度失败:', error);
|
||
});
|
||
}
|
||
|
||
// 更新进度显示
|
||
function updateProgressDisplay(task) {
|
||
const reportContent = document.getElementById('reportContent');
|
||
const existingStatus = reportContent.querySelector('.report-status');
|
||
|
||
if (existingStatus) {
|
||
existingStatus.outerHTML = renderTaskStatus(task);
|
||
}
|
||
}
|
||
|
||
// 查看报告
|
||
function viewReport(taskId) {
|
||
const reportPreview = document.getElementById('reportPreview');
|
||
reportPreview.innerHTML = '<div class="report-loading">加载报告中...</div>';
|
||
|
||
fetch(`/api/report/result/${taskId}`)
|
||
.then(response => {
|
||
if (response.ok) {
|
||
return response.text();
|
||
} else {
|
||
throw new Error('报告加载失败');
|
||
}
|
||
})
|
||
.then(htmlContent => {
|
||
// 创建iframe来显示HTML内容
|
||
const iframe = document.createElement('iframe');
|
||
iframe.style.width = '100%';
|
||
iframe.style.height = '600px';
|
||
iframe.style.border = 'none';
|
||
|
||
reportPreview.innerHTML = '';
|
||
reportPreview.appendChild(iframe);
|
||
|
||
// 将HTML内容写入iframe
|
||
iframe.contentDocument.open();
|
||
iframe.contentDocument.write(htmlContent);
|
||
iframe.contentDocument.close();
|
||
})
|
||
.catch(error => {
|
||
console.error('查看报告失败:', error);
|
||
reportPreview.innerHTML = `
|
||
<div class="report-loading">
|
||
报告加载失败: ${error.message}
|
||
</div>
|
||
`;
|
||
});
|
||
}
|
||
|
||
// 检查报告状态
|
||
function checkReportStatus() {
|
||
loadReportInterface();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|