This commit is contained in:
zpc 2025-03-29 10:16:44 +08:00
parent e67668188e
commit ef17f5e10b
6 changed files with 457 additions and 59 deletions

View File

@ -3,7 +3,7 @@ chcp 936
setlocal enabledelayedexpansion
:: Debug mode
set DEBUG=1
set DEBUG=0
:: Configuration
set APP_NAME=ShengShengBuXi.ConsoleApp.exe
@ -62,8 +62,6 @@ if !EXIT_CODE! neq 0 (
echo [WARNING] Program exited abnormally >> "%LOG_FILE%"
echo [WARNING] Program exited with code: !EXIT_CODE!
echo [INFO] Please check log file: %LOG_FILE%
echo Press any key to continue...
pause
)
:: Restart delay

View File

@ -93,6 +93,10 @@ namespace ShengShengBuXi.Hubs
private static string _displayConfig = "{}";
private static object _displayConfigLock = new object();
// 控屏开关设置
private static bool _manualScreenControlEnabled = false;
private static readonly string _screenControlSettingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config/screen_control.json");
// 管理员配置保存路径
private static readonly string ConfigDirectory = Path.Combine(Directory.GetCurrentDirectory(), "config");
private static readonly string DisplayConfigPath = Path.Combine(ConfigDirectory, "display.json");
@ -119,6 +123,9 @@ namespace ShengShengBuXi.Hubs
// 加载显示配置
LoadDisplayConfig();
// 加载控屏设置
LoadScreenControlSetting();
}
// 加载显示配置
@ -180,6 +187,51 @@ namespace ShengShengBuXi.Hubs
}
}
// 加载控屏设置
private static void LoadScreenControlSetting()
{
try
{
if (File.Exists(_screenControlSettingPath))
{
string json = File.ReadAllText(_screenControlSettingPath);
var setting = JsonConvert.DeserializeObject<dynamic>(json);
_manualScreenControlEnabled = setting?.IsManual ?? false;
Console.WriteLine($"加载控屏设置成功,当前模式: {(_manualScreenControlEnabled ? "" : "")}");
}
else
{
// 默认设置为自动
_manualScreenControlEnabled = false;
// 保存默认设置
SaveScreenControlSetting();
Console.WriteLine("创建默认控屏设置成功");
}
}
catch (Exception ex)
{
Console.WriteLine($"加载控屏设置出错: {ex.Message}");
_manualScreenControlEnabled = false;
}
}
// 保存控屏设置
private static bool SaveScreenControlSetting()
{
try
{
string json = JsonConvert.SerializeObject(new { IsManual = _manualScreenControlEnabled });
File.WriteAllText(_screenControlSettingPath, json);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"保存控屏设置出错: {ex.Message}");
return false;
}
}
/// <summary>
/// 初始化音频Hub
/// </summary>
@ -987,6 +1039,19 @@ namespace ShengShengBuXi.Hubs
return;
}
// 在手动控屏模式下,只处理真实用户的消息
if (_manualScreenControlEnabled && !highestPriority.Value.IsRealUser)
{
_logger.LogInformation("当前为手动控屏模式,跳过非真实用户消息");
// 从队列中移除该消息,但不发送
if (_displayTextQueue.TryRemove(highestPriority.Key, out _))
{
// 返回空表示没有要显示的文本
await Clients.Caller.SendAsync("ReceiveDisplayText", "");
}
return;
}
// 从队列中移除该消息
if (_displayTextQueue.TryRemove(highestPriority.Key, out var textToDisplay))
{
@ -1591,9 +1656,9 @@ namespace ShengShengBuXi.Hubs
}
// 使用简单的序列化方式,将对象转换为单行文本
string entry = JsonConvert.SerializeObject(new
string entry = JsonConvert.SerializeObject(new RealUserDisplayRecord
{
displayText.Text,
Text = displayText.Text,
Timestamp = displayText.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
});
@ -1607,5 +1672,112 @@ namespace ShengShengBuXi.Hubs
_logger.LogError($"保存真实用户显示文本失败: {ex.Message}");
}
}
/// <summary>
/// 获取当前控屏设置
/// </summary>
/// <returns>是否为手动控屏模式</returns>
public async Task<bool> GetScreenControlSetting()
{
if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
{
_logger.LogWarning($"未注册的客户端尝试获取控屏设置: {Context.ConnectionId}");
throw new HubException("请先注册客户端");
}
_logger.LogInformation($"客户端获取当前控屏设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
return _manualScreenControlEnabled;
}
/// <summary>
/// 更新控屏设置
/// </summary>
/// <param name="isManual">是否为手动控屏模式</param>
/// <returns>更新结果</returns>
public async Task<bool> UpdateScreenControlSetting(bool isManual)
{
if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
{
_logger.LogWarning($"未注册的客户端尝试更新控屏设置: {Context.ConnectionId}");
throw new HubException("请先注册客户端");
}
if (clientInfo.ClientType != ClientType.Monitor && clientInfo.ClientType != ClientType.WebAdmin)
{
_logger.LogWarning($"非监控端或管理端尝试更新控屏设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
throw new HubException("只有监控端或管理端可以更新控屏设置");
}
// 设置模式
_manualScreenControlEnabled = isManual;
// 保存设置
bool success = SaveScreenControlSetting();
if (success)
{
// 通知所有监控客户端更新设置
await Clients.Group("monitor").SendAsync("ScreenControlSettingChanged", isManual);
_logger.LogInformation($"控屏设置已更新为: {(isManual ? "" : "")}");
}
return success;
}
/// <summary>
/// 获取真实用户聊天记录
/// </summary>
/// <returns>用户聊天记录列表</returns>
public async Task<List<RealUserDisplayRecord>> GetRealUserChatRecords()
{
if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
{
_logger.LogWarning($"未注册的客户端尝试获取真实用户聊天记录: {Context.ConnectionId}");
throw new HubException("请先注册客户端");
}
if (clientInfo.ClientType != ClientType.WebAdmin)
{
_logger.LogWarning($"非管理端尝试获取真实用户聊天记录: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
throw new HubException("只有管理端可以获取真实用户聊天记录");
}
var records = new List<RealUserDisplayRecord>();
try
{
if (File.Exists(_realUserDisplayLogsPath))
{
var lines = await File.ReadAllLinesAsync(_realUserDisplayLogsPath);
foreach (var line in lines)
{
try
{
if (!string.IsNullOrWhiteSpace(line))
{
var record = JsonConvert.DeserializeObject<RealUserDisplayRecord>(line);
records.Add(record);
}
}
catch (Exception ex)
{
_logger.LogError($"解析真实用户聊天记录行失败: {ex.Message}, 行内容: {line}");
}
}
}
else
{
_logger.LogWarning($"真实用户聊天记录文件不存在: {_realUserDisplayLogsPath}");
}
}
catch (Exception ex)
{
_logger.LogError($"获取真实用户聊天记录失败: {ex.Message}");
throw new HubException($"获取真实用户聊天记录失败: {ex.Message}");
}
return records;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace ShengShengBuXi.Models
{
/// <summary>
/// 真实用户显示记录模型
/// </summary>
public class RealUserDisplayRecord
{
/// <summary>
/// 显示文本内容
/// </summary>
public string Text { get; set; }
/// <summary>
/// 显示时间戳
/// </summary>
public string Timestamp { get; set; }
}
}

View File

@ -43,6 +43,9 @@
<li class="nav-item" role="presentation">
<button class="nav-link" id="display-config-tab" data-bs-toggle="tab" data-bs-target="#display-config" type="button" role="tab" aria-controls="display-config" aria-selected="false">显示配置</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="user-records-tab" data-bs-toggle="tab" data-bs-target="#user-records" type="button" role="tab" aria-controls="user-records" aria-selected="false">用户记录</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<!-- 系统配置 -->
@ -419,6 +422,32 @@
</form>
</div>
</div>
<!-- 真实用户记录 -->
<div class="tab-pane fade" id="user-records" role="tabpanel" aria-labelledby="user-records-tab">
<div class="mt-3">
<div class="d-flex justify-content-end mb-3">
<button type="button" class="btn btn-primary" onclick="getRealUserRecords()">刷新记录</button>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th width="5%">序号</th>
<th width="20%">时间</th>
<th width="75%">内容</th>
</tr>
</thead>
<tbody id="user-records-list">
<tr>
<td colspan="3" class="text-center">暂无用户记录</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
@ -526,6 +555,7 @@
// 获取客户端列表
//getClientList();
setTimeout(getRecentRecordings, 1000);
setTimeout(getRealUserRecords, 1500);
})
.catch(function(err) {
log("注册为管理员客户端失败: " + err);
@ -1098,9 +1128,17 @@
// 录音保存
connection.on("AudioSavedToFile", (filePath) => {
log("录音已保存: " + filePath);
addRecordingToList(filePath);
showMessage("新录音: " + filePath, "success");
log("录音保存到文件: " + filePath);
showMessage("录音已保存到文件", "success");
// 刷新录音列表
setTimeout(getRecentRecordings, 1000);
});
// 监听用户记录响应
connection.on("ReceiveRealUserRecords", (records) => {
log("接收到真实用户记录,数量:" + (records ? records.length : 0));
updateUserRecordsList(records);
});
// 配置更新结果
@ -1261,47 +1299,6 @@
}
}
// 添加录音到列表
function addRecordingToList(filePath) {
const tableBody = document.getElementById("recording-list");
// 如果是"暂无录音"行,则清空
if (tableBody.innerHTML.includes("暂无录音")) {
tableBody.innerHTML = "";
}
// 从文件路径获取文件名
const fileName = filePath.split('/').pop();
// 创建新行
const row = document.createElement("tr");
row.className = "table-success";
const timestamp = new Date().toLocaleString();
row.innerHTML = `
<td>${fileName}</td>
<td>未知</td>
<td>${timestamp}</td>
<td>
<a href="/recordings/${fileName}" class="btn btn-sm btn-primary" target="_blank">播放</a>
<a href="/recordings/${fileName}" class="btn btn-sm btn-secondary" download>下载</a>
</td>
`;
// 添加到顶部
if (tableBody.firstChild) {
tableBody.insertBefore(row, tableBody.firstChild);
} else {
tableBody.appendChild(row);
}
// 恢复正常样式
setTimeout(() => {
row.className = "";
}, 3000);
}
// 更新录音列表
function updateRecordingsList(recordings) {
log("接收到录音列表,数量:" + (recordings ? recordings.length : 0));
@ -1548,6 +1545,62 @@
showMessage("已重置为默认配置,请点击保存按钮保存", "info");
}
// 获取真实用户记录
function getRealUserRecords() {
if (!connection || connection.state !== signalR.HubConnectionState.Connected) {
showMessage("无法获取用户记录:未连接到服务器", "warning");
return;
}
log("正在获取真实用户记录列表...");
connection.invoke("GetRealUserChatRecords")
.then((records) => {
log("已成功获取真实用户记录,数量:" + (records ? records.length : 0));
updateUserRecordsList(records);
})
.catch(err => {
log("获取真实用户记录失败: " + err);
showMessage("获取真实用户记录失败: " + err, "danger");
});
}
// 更新用户记录列表
function updateUserRecordsList(records) {
const recordsList = document.getElementById("user-records-list");
if (!recordsList) return;
if (!records || records.length === 0) {
recordsList.innerHTML = `<tr><td colspan="3" class="text-center">暂无用户记录</td></tr>`;
return;
}
// 清空列表
recordsList.innerHTML = "";
// 倒序显示记录,最新的在最上面
records.reverse().forEach((record, index) => {
const row = document.createElement("tr");
// 序号列
const indexCell = document.createElement("td");
indexCell.textContent = index + 1;
row.appendChild(indexCell);
// 时间列
const timeCell = document.createElement("td");
timeCell.textContent = record.timestamp || "未知时间";
row.appendChild(timeCell);
// 内容列
const contentCell = document.createElement("td");
contentCell.textContent = record.text || "";
row.appendChild(contentCell);
recordsList.appendChild(row);
});
}
// 页面加载完成后初始化
document.addEventListener("DOMContentLoaded", function() {
log("页面已加载");

View File

@ -328,6 +328,15 @@
*/
function displayText(text) {
if (isAnimating) return;
// 如果接收到空文本,表示在手动控屏模式下跳过了非真实用户消息
if (!text) {
console.log("接收到空文本,请求下一条文本");
// 延迟一段时间后请求下一条文本
const delay = Math.floor(Math.random() * 3000) + 3000; // 4000-6000毫秒
setTimeout(requestNextText, delay);
return;
}
var currentPage = $("#magazine").turn("page");
let r_id = '#r_container_' + (currentPage + 1);

View File

@ -50,6 +50,15 @@
id="volumeControl" style="width: 100px;">
</div>
</div>
<!-- 控屏开关 -->
<div class="btn-group ms-3" role="group">
<input type="radio" class="btn-check" name="screenControl" id="screenControlAuto" value="0" checked>
<label class="btn btn-outline-primary" for="screenControlAuto">自动</label>
<input type="radio" class="btn-check" name="screenControl" id="screenControlManual" value="1">
<label class="btn btn-outline-primary" for="screenControlManual">手动</label>
</div>
</div>
</div>
</div>
@ -212,6 +221,9 @@
// 当前播放的音频元素和按钮引用
let currentAudio = null;
let currentPlayButton = null;
// 控屏开关状态
let isManualScreenControl = false;
// 实时音频相关变量
let audioContext = null;
@ -356,6 +368,9 @@
// 获取当前音频流设置
getServerAudioStreamingSetting();
// 获取当前控屏设置
getServerScreenControlSetting();
// 设置定时刷新显示列表
if (refreshDisplayInterval) {
@ -470,6 +485,23 @@
showMessage(message, "danger");
}
});
// 控屏设置更新消息
connection.on("ScreenControlSettingChanged", (isManual) => {
log(`服务器控屏设置已更改为: ${isManual ? "手动" : "自动"}`);
updateScreenControlUI(isManual);
});
// 控屏设置更新结果
connection.on("ScreenControlSettingUpdated", (success, message) => {
if (success) {
log(`控屏设置更新成功: ${message}`);
showMessage(message, "success");
} else {
log(`控屏设置更新失败: ${message}`);
showMessage(message, "danger");
}
});
}
// 更新通话状态
@ -910,24 +942,108 @@
});
}
// 页面加载完成后初始化
document.addEventListener("DOMContentLoaded", function () {
log("页面已加载");
// 设置音频流开关监听器
function setupAudioStreamingListeners() {
const audioStreamingButtons = document.querySelectorAll('input[name="audioStreaming"]');
audioStreamingButtons.forEach(radio => {
radio.addEventListener('change', function () {
const enabled = parseInt(this.value) === 1;
isAudioStreamEnabled = enabled;
log(`音频流已切换为: ${enabled ? "开启" : "关闭"}`);
// 添加Bootstrap Icons样式
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css';
document.head.appendChild(link);
// 如果开启了音频流,初始化音频上下文
if (enabled && callInProgress) {
initAudioContext();
}
// 调用服务器端方法更新音频流设置
if (connection && connection.state === signalR.HubConnectionState.Connected) {
connection.invoke("UpdateAudioStreaming", enabled)
.then(() => {
showMessage(`已${enabled ? "开启" : "关闭"}音频传输`, "success");
})
.catch(err => {
log(`更新音频流设置失败: ${err}`);
showMessage("切换音频传输失败", "danger");
// 恢复原来的选择
const currentValue = enabled ? 0 : 1;
document.getElementById(`audioStreaming${currentValue}`).checked = true;
isAudioStreamEnabled = !enabled;
});
} else {
log("无法更新音频流设置:未连接到服务器");
showMessage("无法更新音频流设置:未连接到服务器", "warning");
}
});
});
}
// 设置控屏开关监听器
function setupScreenControlListeners() {
const screenControlButtons = document.querySelectorAll('input[name="screenControl"]');
screenControlButtons.forEach(radio => {
radio.addEventListener('change', function () {
const isManual = parseInt(this.value) === 1;
isManualScreenControl = isManual;
log(`控屏模式已切换为: ${isManual ? "手动" : "自动"}`);
// 调用服务器端方法更新控屏设置
if (connection && connection.state === signalR.HubConnectionState.Connected) {
connection.invoke("UpdateScreenControlSetting", isManual)
.then(() => {
showMessage(`已切换为${isManual ? "手动" : "自动"}控屏模式`, "success");
})
.catch(err => {
log(`更新控屏设置失败: ${err}`);
showMessage("切换控屏模式失败", "danger");
// 恢复原来的选择
const currentValue = isManual ? 0 : 1;
document.getElementById(`screenControl${currentValue === 0 ? 'Auto' : 'Manual'}`).checked = true;
});
}
});
});
}
// 设置音量控制监听器
function setupVolumeControlListener() {
const volumeControl = document.getElementById('volumeControl');
if (volumeControl) {
volumeControl.addEventListener('input', function (e) {
currentVolume = parseFloat(e.target.value);
log(`音量已调整为: ${currentVolume * 100}%`);
// 如果已经创建了增益节点,立即应用音量设置
if (audioGainNode) {
audioGainNode.gain.value = currentVolume;
}
// 同时调整已播放的录音音量
if (currentAudio) {
currentAudio.volume = currentVolume;
}
});
}
}
// 初始化页面
document.addEventListener('DOMContentLoaded', function () {
// 初始化SignalR连接
initSignalR();
// 设置显示模式和音频流监听器
// 设置显示模式监听器
setupDisplayModeListeners();
// 设置音频流开关监听器
setupAudioStreamingListeners();
// 设置控屏开关监听器
setupScreenControlListeners();
// 设置音量控制监听器
setupVolumeControl();
setupVolumeControlListener();
// 初始化工具提示
setTimeout(initTooltips, 1000);
@ -1660,5 +1776,35 @@
log(`已添加文本到监控列表: ${shortText}...`);
}
// 更新控屏UI
function updateScreenControlUI(isManual) {
// 更新单选按钮状态
document.getElementById(isManual ? 'screenControlManual' : 'screenControlAuto').checked = true;
isManualScreenControl = isManual;
log(`UI控屏模式已更新为: ${isManual ? "手动" : "自动"}`);
}
// 获取服务器当前的控屏设置
function getServerScreenControlSetting() {
if (!connection || connection.state !== signalR.HubConnectionState.Connected) {
log("无法获取控屏设置:未连接");
return;
}
log("获取服务器当前控屏设置...");
connection.invoke("GetScreenControlSetting")
.then(isManual => {
log(`获取到服务器控屏设置: ${isManual ? "手动" : "自动"}`);
updateScreenControlUI(isManual);
})
.catch(err => {
log(`获取控屏设置失败: ${err}`);
// 默认设置为自动
updateScreenControlUI(false);
});
}
</script>
}