Toggle navigation
Toggle navigation
This project
Loading...
Sign in
万朱浩
/
Venue-Ops
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
戒酒的李白
2025-08-24 02:05:56 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
92537703f3574678b1349df5e4305b0865edcb2e
92537703
1 parent
5fdb1e44
Initial setup of web app.
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
903 additions
and
0 deletions
app.py
requirements.txt
templates/index.html
app.py
0 → 100644
View file @
9253770
"""
Flask主应用 - 统一管理三个Streamlit应用
"""
import
os
import
sys
import
subprocess
import
time
import
json
import
threading
from
datetime
import
datetime
from
queue
import
Queue
,
Empty
from
flask
import
Flask
,
render_template
,
request
,
jsonify
,
Response
from
flask_socketio
import
SocketIO
,
emit
import
signal
import
atexit
import
requests
app
=
Flask
(
__name__
)
app
.
config
[
'SECRET_KEY'
]
=
'weibo_analysis_system_2024'
socketio
=
SocketIO
(
app
,
cors_allowed_origins
=
"*"
)
# 全局变量存储进程信息
processes
=
{
'insight'
:
{
'process'
:
None
,
'port'
:
8501
,
'status'
:
'stopped'
,
'output'
:
[]},
'media'
:
{
'process'
:
None
,
'port'
:
8502
,
'status'
:
'stopped'
,
'output'
:
[]},
'query'
:
{
'process'
:
None
,
'port'
:
8503
,
'status'
:
'stopped'
,
'output'
:
[]}
}
# 输出队列
output_queues
=
{
'insight'
:
Queue
(),
'media'
:
Queue
(),
'query'
:
Queue
()
}
def
read_process_output
(
process
,
app_name
):
"""读取进程输出并放入队列"""
while
True
:
try
:
if
process
.
poll
()
is
not
None
:
break
output
=
process
.
stdout
.
readline
()
if
output
:
line
=
output
.
decode
(
'utf-8'
,
errors
=
'ignore'
)
.
strip
()
if
line
:
timestamp
=
datetime
.
now
()
.
strftime
(
'
%
H:
%
M:
%
S'
)
formatted_line
=
f
"[{timestamp}] {line}"
# 添加到输出列表(保持最近100行)
processes
[
app_name
][
'output'
]
.
append
(
formatted_line
)
if
len
(
processes
[
app_name
][
'output'
])
>
100
:
processes
[
app_name
][
'output'
]
.
pop
(
0
)
# 发送到前端
socketio
.
emit
(
'console_output'
,
{
'app'
:
app_name
,
'line'
:
formatted_line
})
except
Exception
as
e
:
print
(
f
"Error reading output for {app_name}: {e}"
)
break
def
start_streamlit_app
(
app_name
,
script_path
,
port
):
"""启动Streamlit应用"""
try
:
if
processes
[
app_name
][
'process'
]
is
not
None
:
return
False
,
"应用已经在运行"
# 检查文件是否存在
if
not
os
.
path
.
exists
(
script_path
):
return
False
,
f
"文件不存在: {script_path}"
cmd
=
[
sys
.
executable
,
'-m'
,
'streamlit'
,
'run'
,
script_path
,
'--server.port'
,
str
(
port
),
'--server.headless'
,
'true'
,
'--browser.gatherUsageStats'
,
'false'
,
'--logger.level'
,
'info'
]
# 使用当前工作目录而不是脚本目录
process
=
subprocess
.
Popen
(
cmd
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
,
bufsize
=
1
,
universal_newlines
=
False
,
cwd
=
os
.
getcwd
()
)
processes
[
app_name
][
'process'
]
=
process
processes
[
app_name
][
'status'
]
=
'starting'
processes
[
app_name
][
'output'
]
=
[]
# 启动输出读取线程
output_thread
=
threading
.
Thread
(
target
=
read_process_output
,
args
=
(
process
,
app_name
),
daemon
=
True
)
output_thread
.
start
()
return
True
,
f
"{app_name} 应用启动中..."
except
Exception
as
e
:
return
False
,
f
"启动失败: {str(e)}"
def
stop_streamlit_app
(
app_name
):
"""停止Streamlit应用"""
try
:
if
processes
[
app_name
][
'process'
]
is
None
:
return
False
,
"应用未运行"
process
=
processes
[
app_name
][
'process'
]
process
.
terminate
()
# 等待进程结束
try
:
process
.
wait
(
timeout
=
5
)
except
subprocess
.
TimeoutExpired
:
process
.
kill
()
process
.
wait
()
processes
[
app_name
][
'process'
]
=
None
processes
[
app_name
][
'status'
]
=
'stopped'
return
True
,
f
"{app_name} 应用已停止"
except
Exception
as
e
:
return
False
,
f
"停止失败: {str(e)}"
def
check_app_status
():
"""检查应用状态"""
for
app_name
,
info
in
processes
.
items
():
if
info
[
'process'
]
is
not
None
:
if
info
[
'process'
]
.
poll
()
is
None
:
# 进程仍在运行,检查端口是否可访问
try
:
response
=
requests
.
get
(
f
"http://localhost:{info['port']}"
,
timeout
=
2
)
if
response
.
status_code
==
200
:
info
[
'status'
]
=
'running'
else
:
info
[
'status'
]
=
'starting'
except
requests
.
exceptions
.
RequestException
:
info
[
'status'
]
=
'starting'
except
Exception
:
info
[
'status'
]
=
'starting'
else
:
# 进程已结束
info
[
'process'
]
=
None
info
[
'status'
]
=
'stopped'
def
wait_for_app_startup
(
app_name
,
max_wait_time
=
30
):
"""等待应用启动完成"""
import
time
start_time
=
time
.
time
()
while
time
.
time
()
-
start_time
<
max_wait_time
:
info
=
processes
[
app_name
]
if
info
[
'process'
]
is
None
:
return
False
,
"进程已停止"
if
info
[
'process'
]
.
poll
()
is
not
None
:
return
False
,
"进程启动失败"
try
:
response
=
requests
.
get
(
f
"http://localhost:{info['port']}"
,
timeout
=
2
)
if
response
.
status_code
==
200
:
info
[
'status'
]
=
'running'
return
True
,
"启动成功"
except
:
pass
time
.
sleep
(
1
)
return
False
,
"启动超时"
def
cleanup_processes
():
"""清理所有进程"""
for
app_name
in
processes
:
stop_streamlit_app
(
app_name
)
# 注册清理函数
atexit
.
register
(
cleanup_processes
)
@app.route
(
'/'
)
def
index
():
"""主页"""
return
render_template
(
'index.html'
)
@app.route
(
'/api/status'
)
def
get_status
():
"""获取所有应用状态"""
check_app_status
()
return
jsonify
({
app_name
:
{
'status'
:
info
[
'status'
],
'port'
:
info
[
'port'
],
'output_lines'
:
len
(
info
[
'output'
])
}
for
app_name
,
info
in
processes
.
items
()
})
@app.route
(
'/api/start/<app_name>'
)
def
start_app
(
app_name
):
"""启动指定应用"""
if
app_name
not
in
processes
:
return
jsonify
({
'success'
:
False
,
'message'
:
'未知应用'
})
script_paths
=
{
'insight'
:
'SingleEngineApp/insight_engine_streamlit_app.py'
,
'media'
:
'SingleEngineApp/media_engine_streamlit_app.py'
,
'query'
:
'SingleEngineApp/query_engine_streamlit_app.py'
}
success
,
message
=
start_streamlit_app
(
app_name
,
script_paths
[
app_name
],
processes
[
app_name
][
'port'
]
)
if
success
:
# 等待应用启动
startup_success
,
startup_message
=
wait_for_app_startup
(
app_name
,
15
)
if
not
startup_success
:
message
+=
f
" 但启动检查失败: {startup_message}"
return
jsonify
({
'success'
:
success
,
'message'
:
message
})
@app.route
(
'/api/stop/<app_name>'
)
def
stop_app
(
app_name
):
"""停止指定应用"""
if
app_name
not
in
processes
:
return
jsonify
({
'success'
:
False
,
'message'
:
'未知应用'
})
success
,
message
=
stop_streamlit_app
(
app_name
)
return
jsonify
({
'success'
:
success
,
'message'
:
message
})
@app.route
(
'/api/output/<app_name>'
)
def
get_output
(
app_name
):
"""获取应用输出"""
if
app_name
not
in
processes
:
return
jsonify
({
'success'
:
False
,
'message'
:
'未知应用'
})
return
jsonify
({
'success'
:
True
,
'output'
:
processes
[
app_name
][
'output'
]
})
@app.route
(
'/api/search'
,
methods
=
[
'POST'
])
def
search
():
"""统一搜索接口"""
data
=
request
.
get_json
()
query
=
data
.
get
(
'query'
,
''
)
.
strip
()
if
not
query
:
return
jsonify
({
'success'
:
False
,
'message'
:
'搜索查询不能为空'
})
# 检查哪些应用正在运行
check_app_status
()
running_apps
=
[
name
for
name
,
info
in
processes
.
items
()
if
info
[
'status'
]
==
'running'
]
if
not
running_apps
:
return
jsonify
({
'success'
:
False
,
'message'
:
'没有运行中的应用'
})
# 向运行中的应用发送搜索请求
results
=
{}
api_ports
=
{
'insight'
:
8601
,
'media'
:
8602
,
'query'
:
8603
}
for
app_name
in
running_apps
:
try
:
api_port
=
api_ports
[
app_name
]
# 调用Streamlit应用的API端点
response
=
requests
.
post
(
f
"http://localhost:{api_port}/api/search"
,
json
=
{
'query'
:
query
},
timeout
=
10
)
if
response
.
status_code
==
200
:
results
[
app_name
]
=
response
.
json
()
else
:
results
[
app_name
]
=
{
'success'
:
False
,
'message'
:
'API调用失败'
}
except
Exception
as
e
:
results
[
app_name
]
=
{
'success'
:
False
,
'message'
:
str
(
e
)}
return
jsonify
({
'success'
:
True
,
'query'
:
query
,
'results'
:
results
})
@socketio.on
(
'connect'
)
def
handle_connect
():
"""客户端连接"""
emit
(
'status'
,
'Connected to Flask server'
)
@socketio.on
(
'request_status'
)
def
handle_status_request
():
"""请求状态更新"""
check_app_status
()
emit
(
'status_update'
,
{
app_name
:
{
'status'
:
info
[
'status'
],
'port'
:
info
[
'port'
]
}
for
app_name
,
info
in
processes
.
items
()
})
if
__name__
==
'__main__'
:
# 启动时自动启动所有Streamlit应用
print
(
"正在启动Streamlit应用..."
)
script_paths
=
{
'insight'
:
'SingleEngineApp/insight_engine_streamlit_app.py'
,
'media'
:
'SingleEngineApp/media_engine_streamlit_app.py'
,
'query'
:
'SingleEngineApp/query_engine_streamlit_app.py'
}
for
app_name
,
script_path
in
script_paths
.
items
():
print
(
f
"检查文件: {script_path}"
)
if
os
.
path
.
exists
(
script_path
):
print
(
f
"启动 {app_name}..."
)
success
,
message
=
start_streamlit_app
(
app_name
,
script_path
,
processes
[
app_name
][
'port'
])
print
(
f
"{app_name}: {message}"
)
if
success
:
print
(
f
"等待 {app_name} 启动完成..."
)
startup_success
,
startup_message
=
wait_for_app_startup
(
app_name
,
30
)
print
(
f
"{app_name} 启动检查: {startup_message}"
)
else
:
print
(
f
"错误: {script_path} 不存在"
)
print
(
"所有应用启动完成,启动Flask服务器..."
)
try
:
# 启动Flask应用
socketio
.
run
(
app
,
host
=
'0.0.0.0'
,
port
=
5000
,
debug
=
False
)
except
KeyboardInterrupt
:
print
(
"
\n
正在关闭应用..."
)
cleanup_processes
()
...
...
requirements.txt
View file @
9253770
...
...
@@ -30,3 +30,11 @@ uuid>=1.30
pytest>=7.4.0
black>=23.0.0
flake8>=6.0.0
# Flask Web应用
flask==2.3.3
flask-socketio==5.3.6
streamlit==1.28.1
requests==2.31.0
python-socketio==5.8.0
eventlet==0.33.3
\ No newline at end of file
...
...
templates/index.html
0 → 100644
View file @
9253770
<!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
;
min-height
:
100vh
;
display
:
flex
;
flex-direction
:
column
;
border
:
2px
solid
#000000
;
}
/* 搜索框区域 */
.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
);
}
/* 嵌入页面区域 */
.embedded-section
{
flex
:
2
;
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
;
}
/* 控制台输出区域 */
.console-section
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
background-color
:
#ffffff
;
}
/* 应用切换按钮 */
.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
;
white-space
:
pre-wrap
;
word-break
:
break-all
;
}
.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
:
20px
;
padding
:
10px
20px
;
border
:
2px
solid
#000000
;
background-color
:
#ffffff
;
z-index
:
1000
;
opacity
:
0
;
transform
:
translateX
(
100%
);
transition
:
all
0.3s
ease
;
}
.message.show
{
opacity
:
1
;
transform
:
translateX
(
0
);
}
.message.error
{
background-color
:
#ffeeee
;
border-color
:
#ff0000
;
}
.message.success
{
background-color
:
#eeffee
;
border-color
:
#00ff00
;
}
</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
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>
</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'
};
// 初始化
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
initializeSocket
();
initializeEventListeners
();
updateTime
();
setInterval
(
updateTime
,
1000
);
checkStatus
();
setInterval
(
checkStatus
,
5000
);
});
// 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
);
}
});
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> 搜索中...'
;
fetch
(
'/api/search'
,
{
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
,
},
body
:
JSON
.
stringify
({
query
:
query
})
})
.
then
(
response
=>
response
.
json
())
.
then
(
data
=>
{
if
(
data
.
success
)
{
showMessage
(
'搜索请求已发送到所有运行中的应用'
,
'success'
);
}
else
{
showMessage
(
data
.
message
||
'搜索失败'
,
'error'
);
}
})
.
catch
(
error
=>
{
console
.
error
(
'搜索错误:'
,
error
);
showMessage
(
'搜索请求失败'
,
'error'
);
})
.
finally
(()
=>
{
button
.
disabled
=
false
;
button
.
innerHTML
=
'搜索'
;
});
}
// 切换应用
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
;
// 清空并加载新的控制台输出
document
.
getElementById
(
'consoleOutput'
).
innerHTML
=
'<div class="console-line">[系统] 切换到 '
+
app
+
' 应用</div>'
;
loadConsoleOutput
(
app
);
// 更新嵌入页面
updateEmbeddedPage
(
app
);
}
// 加载控制台输出
function
loadConsoleOutput
(
app
)
{
fetch
(
`
/
api
/
output
/
$
{
app
}
`
)
.
then
(
response
=>
response
.
json
())
.
then
(
data
=>
{
if
(
data
.
success
&&
data
.
output
.
length
>
0
)
{
const
consoleOutput
=
document
.
getElementById
(
'consoleOutput'
);
data
.
output
.
forEach
(
line
=>
{
const
div
=
document
.
createElement
(
'div'
);
div
.
className
=
'console-line'
;
div
.
textContent
=
line
;
consoleOutput
.
appendChild
(
div
);
});
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
);
// 保持最近100行
const
lines
=
consoleOutput
.
children
;
if
(
lines
.
length
>
100
)
{
consoleOutput
.
removeChild
(
lines
[
0
]);
}
consoleOutput
.
scrollTop
=
consoleOutput
.
scrollHeight
;
}
// 更新嵌入页面
function
updateEmbeddedPage
(
app
)
{
const
header
=
document
.
getElementById
(
'embeddedHeader'
);
const
content
=
document
.
getElementById
(
'embeddedContent'
);
const
appNames
=
{
insight
:
'Insight Engine - 私有数据库分析'
,
media
:
'Media Engine - 多模态能力'
,
query
:
'Query Engine - 网页搜索'
};
header
.
textContent
=
appNames
[
app
]
||
app
;
// 如果应用正在运行,显示iframe
if
(
appStatus
[
app
]
===
'running'
)
{
const
ports
=
{
insight
:
8501
,
media
:
8502
,
query
:
8503
};
content
.
innerHTML
=
`
<
iframe
src
=
"http://localhost:${ports[app]}"
style
=
"width: 100%; height: 100%; border: none;"
><
/iframe>`
;
}
else
{
content
.
innerHTML
=
`
<
div
style
=
"display: flex; align-items: center; justify-content: center; height: 100%; color: #666; flex-direction: column;"
>
<
div
style
=
"margin-bottom: 10px;"
>
$
{
appNames
[
app
]}
未运行
<
/div
>
<
div
style
=
"font-size: 12px;"
>
状态
:
$
{
appStatus
[
app
]}
<
/div
>
<
/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
))
{
appStatus
[
app
]
=
info
.
status
;
const
indicator
=
document
.
getElementById
(
`
status
-
$
{
app
}
`
);
if
(
indicator
)
{
indicator
.
className
=
`
status
-
indicator
$
{
info
.
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'
);
message
.
textContent
=
text
;
message
.
className
=
`
message
$
{
type
}
`
;
message
.
classList
.
add
(
'show'
);
setTimeout
(()
=>
{
message
.
classList
.
remove
(
'show'
);
},
3000
);
}
</script>
</body>
</html>
...
...
Please
register
or
login
to post a comment