把设备的三个(服务器、数字人、远程音频)连接状态放到首页logo中间。

This commit is contained in:
guo zebin
2025-12-17 16:02:47 +08:00
parent 2ef7c2aa9e
commit 017ed36429
5 changed files with 300 additions and 181 deletions

View File

@@ -373,6 +373,43 @@ def api_get_run_status():
except Exception as e:
return jsonify({'status': False, 'message': f'获取运行状态时出错: {e}'}), 500
@__app.route('/api/get-system-status', methods=['get'])
def api_get_system_status():
# 获<><E88EB7><EFBFBD>系统各组件连接状态
try:
username = request.args.get('username')
server_status = True
# 数字人状态 (HumanServer 10002)
# 检查指定用户是否连接了数字人端
digital_human_status = False
try:
wsa_instance = wsa_server.get_instance()
if wsa_instance and username:
digital_human_status = wsa_instance.is_connected(username)
except Exception:
digital_human_status = False
# 远程音频状态 (Socket 10001)
# 检查指定用户是否连接了远程音频
remote_audio_status = False
try:
if username and hasattr(fay_booter, 'DeviceInputListenerDict'):
for listener in fay_booter.DeviceInputListenerDict.values():
if listener.username == username:
remote_audio_status = True
break
except Exception:
remote_audio_status = False
return jsonify({
'server': server_status,
'digital_human': digital_human_status,
'remote_audio': remote_audio_status
})
except Exception as e:
return jsonify({'server': False, 'digital_human': False, 'remote_audio': False, 'error': str(e)}), 500
@__app.route('/api/adopt-msg', methods=['POST'])
def adopt_msg():
# 采纳消息

View File

@@ -765,7 +765,31 @@ html {
max-width: 150px;
}
.prestart-content-inline .message-image-thumbnail {
max-width: 150px;
max-height: 100px;
/* 状态指示器样式 */
.status-indicators {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
gap: 6px;
z-index: 100;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ccc; /* 默认灰色 */
cursor: pointer;
transition: all 0.3s;
border: 1px solid #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.status-dot.connected {
background-color: #4CAF50; /* 绿色 */
box-shadow: 0 0 4px #4CAF50;
}

View File

@@ -326,6 +326,12 @@ new Vue({
isThinkPanelMinimized: false,
mcpOnlineStatus: false,
mcpCheckTimer: null,
systemStatus: {
server: false,
digital_human: false,
remote_audio: false
},
systemStatusTimer: null,
};
},
watch: {
@@ -337,8 +343,51 @@ new Vue({
this.startUserListTimer();
this.checkMcpStatus();
this.startMcpStatusTimer();
this.startSystemStatusTimer();
},
methods: {
// 检查系统各组件连接状态
checkSystemStatus() {
let username = '';
if (this.selectedUser && this.selectedUser.length > 1) {
username = this.selectedUser[1];
}
const statusUrl = `${this.base_url}/api/get-system-status?username=${encodeURIComponent(username)}`;
fetch(statusUrl)
.then(response => response.json())
.then(data => {
this.systemStatus = {
server: data.server,
digital_human: data.digital_human,
remote_audio: data.remote_audio
};
})
.catch(error => {
console.warn('获取系统状态失败:', error);
this.systemStatus = {
server: false,
digital_human: false,
remote_audio: false
};
});
},
// 启动系统状态检查定时器
startSystemStatusTimer() {
// 立即执行一次
this.checkSystemStatus();
if (this.systemStatusTimer) {
clearInterval(this.systemStatusTimer);
}
// 每3秒检查一次
this.systemStatusTimer = setInterval(() => {
this.checkSystemStatus();
}, 3000);
},
initFayService() {
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsHost = window.location.hostname;
@@ -521,6 +570,10 @@ new Vue({
clearInterval(this.mcpCheckTimer);
this.mcpCheckTimer = null;
}
if (this.systemStatusTimer) {
clearInterval(this.systemStatusTimer);
this.systemStatusTimer = null;
}
},
selectUser(user) {
this.selectedUser = user;

View File

@@ -17,7 +17,12 @@
<body>
<div id="app" class="main_bg">
<div class="main_left">
<div class="main_left_logo">
<div class="main_left_logo" style="position: relative;">
<div class="status-indicators">
<div class="status-dot" :class="{ 'connected': systemStatus.server }" title="服务器"></div>
<div class="status-dot" :class="{ 'connected': systemStatus.digital_human }" title="数字人"></div>
<div class="status-dot" :class="{ 'connected': systemStatus.remote_audio }" title="远程音频"></div>
</div>
<img src="{{ url_for('static',filename='images/Logo.png') }}" alt="">
</div>

View File

@@ -1,178 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fay数字人</title>
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static',filename='css/setting.css') }}"/>
<link rel="icon" href="{{ url_for('static',filename='images/favicon.ico') }}" type="image/x-icon">
<!-- 引入样式 -->
<link rel="stylesheet" href="{{ url_for('static',filename='css/element/index.css') }}">
</head>
<body>
<div class="main_bg" id="app">
<div class="main_left">
<div class="main_left_logo" ><img src="{{ url_for('static',filename='images/Logo.png') }}" alt="">
</div>
<div class="main_left_menu">
<ul>
<li class="changeImg"><a href="/"><span class="iconimg1">消息</span></a></li>
<li class="changeImg2"><a href="/setting"><span class="iconimg2">人设</span></a></li>
<li class="changeImg3" :class="{'mcp-online': mcpOnlineStatus}"><a :href="'http://' + hostname + ':5010/Page3'"><span class="iconimg3"><span class="mcp-online-indicator"></span>MCP</span></a></li>
</ul>
</div>
<div class="main_left_emoji"><img src="{{ url_for('static',filename='images/emoji.png') }}" alt="" >
</div>
</div>
<div class="main_right">
<div class="setting_fay_top"><span style="color: #000; font-size: 20px; padding-right: 10px;">人设</span><span v-if="isConnected" style="color: #f56c6c;">已连接数字人</span><span style="margin-left: 10px;color:#f56c6c;" v-if="remoteAudioConnected">已连接远程音频</span> </div>
<!-- 以上是即时信息显示 -->
<div class="setting_fay">
<div class="setting_name">
<ul>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;名:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_name" placeholder="请输入内容" ></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;别:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_gender" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;龄:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_age" placeholder="请输入内容" /></li>
<li> <span class="font_name">出生地:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_birth" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;肖:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_zodiac" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;座:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_constellation" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;位:</span>
<select class="section_1" :disabled="!configEditable" v-model="attribute_position" placeholder="请选择定位">
<option value="客服">客服</option>
<option value="陪伴">陪伴</option>
<option value="教培">教培</option>
<option value="娱乐">娱乐</option>
<option value="销售">销售</option>
<option value="助理">助理</option>
</select>
</li>
</ul>
</div>
<div class="setting_work">
<ul>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;标:</span>
<select class="section_1" :disabled="!configEditable" v-model="attribute_goal" placeholder="请选择目标">
<option value="解决问题">解决问题</option>
<option value="陪伴情感">陪伴情感</option>
<option value="提供知识">提供知识</option>
<option value="促成交易">促成交易</option>
<option value="工作协助">工作协助</option>
</select>
</li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;业:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_job" placeholder="请输入内容"/></li>
<li> <span class="font_name">&nbsp;联系方式:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_contact" placeholder="请输入内容" /></li>
<li> <span class="font_name">Q&A文件:</span><input class="section_1" :disabled="!configEditable" v-model="QnA" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;充:</span><textarea v-model="attribute_additional" class="section_1" style="height: 165px;vertical-align: middle;"></textarea></li>
</ul>
</div>
<div class="setting_rup">
<div class="setting_wakeup">
<ul>
<li> <span class="font_name">唤醒模式:</span>
<el-switch @change=saveConfig()
v-model="wake_word_enabled"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;词:</span>
<input v-model="wake_word" placeholder="请输入内容(以,隔开)" :disabled="!configEditable" class="section_1" /></li>
<li> <span class="font_name">唤醒方式:</span>
<select class="section_1" v-model="wake_word_type" :disabled="!configEditable">
<option v-for="item in wake_word_type_options" :key="item.value"
:label="item.label" :value="item.value">
</option>
</select></li>
</ul>
</div>
<div class="microphone">
<div class="microphone_group1">
<span class="font_name">服务器扬声器</span>
<el-switch v-model="play_sound_enabled" active-color="#13ce66" inactive-color="#ff4949" @change=saveConfig()> </el-switch>
</div>
<div class="microphone_group1" >
<span class="font_name">服务器麦克风</span>
<el-switch v-model="source_record_enabled" active-color="#13ce66" inactive-color="#ff4949" @change=saveConfig()> </el-switch>
</div>
</div>
<div class="setting_wakeup">
<ul>
<li> <span class="font_name">声音选择:</span>
<select v-model="attribute_voice" placeholder="请选择" class="section_1" style="height: 39px;" :disabled="!configEditable">
<option v-for="item in voiceList" :key="item.value"
:label="item.label" :value="item.value">
</option>
</select></li>
<li style="display: flex;"> <span class="font_name" style="line-height: 36px;">&nbsp;&nbsp;&nbsp;&nbsp;:</span>
<el-slider style="width: 230px;" v-model="interact_perception_follow" :disabled="!configEditable"></el-slider></li>
<li> <span class="font_name">认知隔离:</span>
<el-switch v-model="memory_isolate_by_user" :disabled="use_bionic_memory" active-color="#13ce66" inactive-color="#ff4949" @change=saveConfig()></el-switch>
<span style="font-size: 12px; color: #666; margin-left: 10px;">开启后每个用户将拥有独立记忆</span>
</li>
<li> <span class="font_name">仿生记忆:</span>
<el-switch v-model="use_bionic_memory" active-color="#13ce66" inactive-color="#ff4949" @change="onBionicMemoryChange"></el-switch>
<span style="font-size: 12px; color: #666; margin-left: 10px;">开启后使用仿生记忆系统(人格克隆不可用)</span>
</li>
</ul>
</div>
</div>
</div>
<div class="setting_autoplay">
<div><el-switch
v-model="automatic_player_status" @change=saveConfig()
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch><span class="font_name" style="margin-left: 10px;">自动播报:</span>
<input class="section_2" v-model="automatic_player_url" placeholder="http://127.0.0.1:6000" :disabled="!configEditable" />
</div>
</div>
<div style="margin-top: 40px; position: relative; width: 100%; text-align: center;">
<div style="display: inline-block; position: absolute; left: 30%; transform: translateX(-50%);">
<el-button style="width: 160px; height: 50px;" :disabled="!configEditable" @click=saveConfig()>保存配置</el-button>
</div>
<div style="display: inline-block; position: absolute; left: 70%; transform: translateX(-50%);">
<!-- <el-button type="primary" style="width: 160px;height: 50px;background-color: #007aff;">开 启</el-button> -->
<el-button v-if="liveState == 1" type="success" class="btn_close"
style="width: 160px; height: 50px; background-color: #007aff;" @click=stopLive()>关闭(运行中)</el-button>
<el-button v-else-if="liveState == 2" type="primary" plain disabled
style="width: 160px; height: 50px; background-color: #007aff;">正在开启...</el-button>
<el-button v-else-if="liveState == 3" type="success" plain disabled
style="width: 160px; height: 50px; background-color: #007aff;">正在关闭...</el-button>
<el-button v-else type="primary" style="width: 160px; height: 50px; background-color: #007aff;"
@click=startLive()>开启</el-button>
</div>
</div>
<div style="margin-top: 100px; position: relative; width: 100%; text-align: center;">
<div style="display: inline-block; position: absolute; left: 30%; transform: translateX(-50%);">
<a href="javascript:void(0)" @click="clearMemory" style="color: #007aff; font-size: 14px; text-decoration: none;">清除记忆</a>
</div>
<div style="display: inline-block; position: absolute; left: 70%; transform: translateX(-50%);">
<a href="javascript:void(0)" @click="clonePersonality" :style="{color: use_bionic_memory ? '#ccc' : '#007aff', fontSize: '14px', textDecoration: 'none', cursor: use_bionic_memory ? 'not-allowed' : 'pointer'}">克隆(赋予)人格</a>
</div>
</div>
<div style="height: 60px;"></div> <!-- 添加空间以避免内容被覆盖 -->
</div>
</div>
</body>
<!-- import Vue before Element -->
<script src="{{ url_for('static',filename='js/vue.js') }}"></script>
<script src="{{ url_for('static',filename='js/element.js') }}"></script> <!-- 这里需要确保先引入 Element -->
<script src="{{ url_for('static',filename='js/setting.js') }}"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fay数字人</title>
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}"/>
<link rel="stylesheet" href="{{ url_for('static',filename='css/setting.css') }}"/>
<link rel="icon" href="{{ url_for('static',filename='images/favicon.ico') }}" type="image/x-icon">
<!-- 引入样式 -->
<link rel="stylesheet" href="{{ url_for('static',filename='css/element/index.css') }}">
</head>
<body>
<div class="main_bg" id="app">
<div class="main_left">
<div class="main_left_logo" ><img src="{{ url_for('static',filename='images/Logo.png') }}" alt="">
</div>
<div class="main_left_menu">
<ul>
<li class="changeImg"><a href="/"><span class="iconimg1">消息</span></a></li>
<li class="changeImg2"><a href="/setting"><span class="iconimg2">人设</span></a></li>
<li class="changeImg3" :class="{'mcp-online': mcpOnlineStatus}"><a :href="'http://' + hostname + ':5010/Page3'"><span class="iconimg3"><span class="mcp-online-indicator"></span>MCP</span></a></li>
</ul>
</div>
<div class="main_left_emoji"><img src="{{ url_for('static',filename='images/emoji.png') }}" alt="" >
</div>
</div>
<div class="main_right">
<div class="setting_fay_top"><span style="color: #000; font-size: 20px; padding-right: 10px;">人设</span></div>
<!-- 以上是即时信息显示 -->
<div class="setting_fay">
<div class="setting_name">
<ul>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;名:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_name" placeholder="请输入内容" ></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;别:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_gender" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;龄:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_age" placeholder="请输入内容" /></li>
<li> <span class="font_name">出生地:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_birth" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;肖:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_zodiac" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;座:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_constellation" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;位:</span>
<select class="section_1" :disabled="!configEditable" v-model="attribute_position" placeholder="请选择定位">
<option value="客服">客服</option>
<option value="陪伴">陪伴</option>
<option value="教培">教培</option>
<option value="娱乐">娱乐</option>
<option value="销售">销售</option>
<option value="助理">助理</option>
</select>
</li>
</ul>
</div>
<div class="setting_work">
<ul>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;标:</span>
<select class="section_1" :disabled="!configEditable" v-model="attribute_goal" placeholder="请选择目标">
<option value="解决问题">解决问题</option>
<option value="陪伴情感">陪伴情感</option>
<option value="提供知识">提供知识</option>
<option value="促成交易">促成交易</option>
<option value="工作协助">工作协助</option>
</select>
</li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;业:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_job" placeholder="请输入内容"/></li>
<li> <span class="font_name">&nbsp;联系方式:</span><input class="section_1" :disabled="!configEditable" v-model="attribute_contact" placeholder="请输入内容" /></li>
<li> <span class="font_name">Q&A文件:</span><input class="section_1" :disabled="!configEditable" v-model="QnA" placeholder="请输入内容" /></li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;充:</span><textarea v-model="attribute_additional" class="section_1" style="height: 165px;vertical-align: middle;"></textarea></li>
</ul>
</div>
<div class="setting_rup">
<div class="setting_wakeup">
<ul>
<li> <span class="font_name">唤醒模式:</span>
<el-switch @change=saveConfig()
v-model="wake_word_enabled"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</li>
<li> <span class="font_name">&nbsp;&nbsp;&nbsp;词:</span>
<input v-model="wake_word" placeholder="请输入内容(以,隔开)" :disabled="!configEditable" class="section_1" /></li>
<li> <span class="font_name">唤醒方式:</span>
<select class="section_1" v-model="wake_word_type" :disabled="!configEditable">
<option v-for="item in wake_word_type_options" :key="item.value"
:label="item.label" :value="item.value">
</option>
</select></li>
</ul>
</div>
<div class="microphone">
<div class="microphone_group1">
<span class="font_name">服务器扬声器</span>
<el-switch v-model="play_sound_enabled" active-color="#13ce66" inactive-color="#ff4949" @change=saveConfig()> </el-switch>
</div>
<div class="microphone_group1" >
<span class="font_name">服务器麦克风</span>
<el-switch v-model="source_record_enabled" active-color="#13ce66" inactive-color="#ff4949" @change=saveConfig()> </el-switch>
</div>
</div>
<div class="setting_wakeup">
<ul>
<li> <span class="font_name">声音选择:</span>
<select v-model="attribute_voice" placeholder="请选择" class="section_1" style="height: 39px;" :disabled="!configEditable">
<option v-for="item in voiceList" :key="item.value"
:label="item.label" :value="item.value">
</option>
</select></li>
<li style="display: flex;"> <span class="font_name" style="line-height: 36px;">&nbsp;&nbsp;&nbsp;&nbsp;:</span>
<el-slider style="width: 230px;" v-model="interact_perception_follow" :disabled="!configEditable"></el-slider></li>
<li> <span class="font_name">认知隔离:</span>
<el-switch v-model="memory_isolate_by_user" :disabled="use_bionic_memory" active-color="#13ce66" inactive-color="#ff4949" @change=saveConfig()></el-switch>
<span style="font-size: 12px; color: #666; margin-left: 10px;">开启后每个用户将拥有独立记忆</span>
</li>
<li> <span class="font_name">仿生记忆:</span>
<el-switch v-model="use_bionic_memory" active-color="#13ce66" inactive-color="#ff4949" @change="onBionicMemoryChange"></el-switch>
<span style="font-size: 12px; color: #666; margin-left: 10px;">开启后使用仿生记忆系统(试验功能,人格克隆不可用)</span>
</li>
</ul>
</div>
</div>
</div>
<div class="setting_autoplay">
<div><el-switch
v-model="automatic_player_status" @change=saveConfig()
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch><span class="font_name" style="margin-left: 10px;">自动播报:</span>
<input class="section_2" v-model="automatic_player_url" placeholder="http://127.0.0.1:6000" :disabled="!configEditable" />
</div>
</div>
<div style="margin-top: 40px; position: relative; width: 100%; text-align: center;">
<div style="display: inline-block; position: absolute; left: 30%; transform: translateX(-50%);">
<el-button style="width: 160px; height: 50px;" :disabled="!configEditable" @click=saveConfig()>保存配置</el-button>
</div>
<div style="display: inline-block; position: absolute; left: 70%; transform: translateX(-50%);">
<!-- <el-button type="primary" style="width: 160px;height: 50px;background-color: #007aff;">开 启</el-button> -->
<el-button v-if="liveState == 1" type="success" class="btn_close"
style="width: 160px; height: 50px; background-color: #007aff;" @click=stopLive()>关闭(运行中)</el-button>
<el-button v-else-if="liveState == 2" type="primary" plain disabled
style="width: 160px; height: 50px; background-color: #007aff;">正在开启...</el-button>
<el-button v-else-if="liveState == 3" type="success" plain disabled
style="width: 160px; height: 50px; background-color: #007aff;">正在关闭...</el-button>
<el-button v-else type="primary" style="width: 160px; height: 50px; background-color: #007aff;"
@click=startLive()>开启</el-button>
</div>
</div>
<div style="margin-top: 100px; position: relative; width: 100%; text-align: center;">
<div style="display: inline-block; position: absolute; left: 30%; transform: translateX(-50%);">
<a href="javascript:void(0)" @click="clearMemory" style="color: #007aff; font-size: 14px; text-decoration: none;">清除记忆</a>
</div>
<div style="display: inline-block; position: absolute; left: 70%; transform: translateX(-50%);">
<a href="javascript:void(0)" @click="clonePersonality" :style="{color: use_bionic_memory ? '#ccc' : '#007aff', fontSize: '14px', textDecoration: 'none', cursor: use_bionic_memory ? 'not-allowed' : 'pointer'}">克隆(赋予)人格</a>
</div>
</div>
<div style="height: 60px;"></div> <!-- 添加空间以避免内容被覆盖 -->
</div>
</div>
</body>
<!-- import Vue before Element -->
<script src="{{ url_for('static',filename='js/vue.js') }}"></script>
<script src="{{ url_for('static',filename='js/element.js') }}"></script> <!-- 这里需要确保先引入 Element -->
<script src="{{ url_for('static',filename='js/setting.js') }}"></script>
</html>