This commit is contained in:
zpc 2025-03-30 08:13:40 +08:00
parent bca4397d01
commit b42d730cdb
2 changed files with 138 additions and 102 deletions

View File

@ -919,8 +919,15 @@ namespace ShengShengBuXi.Hubs
Text = text,
Timestamp = DateTime.Now
});
if (!_presetSentences.Contains(text))
{
_presetSentences.Add(text);
}
_presetSentences.Add(text);
_presetSentences2.Add(text);
if (!_presetSentences2.Contains(text))
{
_presetSentences2.Add(text);
}
return false;
}
@ -1145,17 +1152,16 @@ namespace ShengShengBuXi.Hubs
// 检查队列是否为空
if (_displayTextQueue.IsEmpty)
{
// 检查是否需要添加预设文本
if (DateTime.Now.Subtract(_lastRealUserSpeakTime).TotalSeconds > 30)
if (!_manualScreenControlEnabled)
{
AddFakeTextToQueue();
}
else
{
_logger.LogInformation("显示文本队列为空,且不需要添加预设文本");
// 如果队列为空且不需要添加预设文本,返回空
await Clients.Caller.SendAsync("ReceiveDisplayText", "");
return;
}
}
// 从队列中选择最高优先级的消息
@ -2170,7 +2176,8 @@ namespace ShengShengBuXi.Hubs
/// <returns>处理任务</returns>
public async Task SavePresetSentences(List<string> sentences)
{
sentences = sentences.Where(it => !string.IsNullOrEmpty(it)).ToList();
sentences = sentences.Where(it => !string.IsNullOrEmpty(it)).Distinct().ToList();
if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
{
_logger.LogWarning($"未注册的客户端尝试保存预设句子列表: {Context.ConnectionId}");

View File

@ -33,11 +33,15 @@
</li> -->
<li class="nav-item" role="presentation"></li>
<button class="nav-link" id="recordings-tab" data-bs-toggle="tab" data-bs-target="#recordings"
type="button" role="tab" aria-controls="recordings" aria-selected="false">用户留言</button>
type="button" role="tab" aria-controls="recordings" aria-selected="false">
用户留言
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="clients-tab" data-bs-toggle="tab" data-bs-target="#clients"
type="button" role="tab" aria-controls="clients" aria-selected="false">客户端</button>
type="button" role="tab" aria-controls="clients" aria-selected="false">
客户端
</button>
</li>
<!-- <li class="nav-item" role="presentation">
@ -50,20 +54,26 @@
</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>
data-bs-target="#user-records" type="button" role="tab" aria-controls="user-records"
aria-selected="false">
用户记录
</button>
</li>
<!-- 添加大屏文本选项卡 -->
<li class="nav-item" role="presentation">
<button class="nav-link" id="screen-text-tab" data-bs-toggle="tab"
data-bs-target="#screen-text" type="button" role="tab" aria-controls="screen-text"
aria-selected="false">大屏文本->文本1</button>
data-bs-target="#screen-text" type="button" role="tab" aria-controls="screen-text"
aria-selected="false">
大屏文本->文本1
</button>
</li>
<!-- 添加大屏文本2选项卡 -->
<li class="nav-item" role="presentation">
<button class="nav-link" id="screen-text2-tab" data-bs-toggle="tab"
data-bs-target="#screen-text2" type="button" role="tab" aria-controls="screen-text2"
aria-selected="false">大屏文本->文本2</button>
data-bs-target="#screen-text2" type="button" role="tab" aria-controls="screen-text2"
aria-selected="false">
大屏文本->文本2
</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
@ -72,11 +82,17 @@
<form id="config-form" class="mt-3">
<div class="d-flex justify-content-end mb-3">
<button type="button" class="btn btn-success me-2"
onclick="saveConfig()">保存配置</button>
onclick="saveConfig()">
保存配置
</button>
<button type="button" class="btn btn-primary me-2"
onclick="getLatestConfig()">刷新配置</button>
onclick="getLatestConfig()">
刷新配置
</button>
<button type="button" class="btn btn-secondary"
onclick="exportConfig()">导出配置</button>
onclick="exportConfig()">
导出配置
</button>
</div>
<div class="row">
@ -86,52 +102,52 @@
<div class="mb-3">
<label for="serverUrl" class="form-label">服务器地址</label>
<input type="text" class="form-control" id="serverUrl"
name="network.serverUrl">
name="network.serverUrl">
<div class="form-text">WebSocket服务器地址</div>
</div>
<div class="mb-3">
<label for="reconnectAttempts" class="form-label">重连尝试次数</label>
<input type="number" class="form-control" id="reconnectAttempts"
name="network.reconnectAttempts">
name="network.reconnectAttempts">
</div>
<div class="mb-3">
<label for="reconnectDelayMs" class="form-label">重连延迟(毫秒)</label>
<input type="number" class="form-control" id="reconnectDelayMs"
name="network.reconnectDelayMs">
name="network.reconnectDelayMs">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="enableSpeechToText"
name="network.enableSpeechToText">
name="network.enableSpeechToText">
<label class="form-check-label" for="enableSpeechToText">启用语音识别</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="enableAudioStreaming"
name="network.enableAudioStreaming">
name="network.enableAudioStreaming">
<label class="form-check-label" for="enableAudioStreaming">启用音频流传输</label>
</div>
<div class="mb-3">
<label for="heartbeatIntervalSeconds" class="form-label">心跳间隔(秒)</label>
<input type="number" class="form-control" id="heartbeatIntervalSeconds"
name="network.heartbeatIntervalSeconds">
name="network.heartbeatIntervalSeconds">
</div>
<h5 class="mt-4">拨号配置</h5>
<div class="mb-3">
<label for="minDigitsToDialOut" class="form-label">拨号最小位数</label>
<input type="number" class="form-control" id="minDigitsToDialOut"
name="dial.minDigitsToDialOut">
name="dial.minDigitsToDialOut">
<div class="form-text">拨号所需的最小位数</div>
</div>
<div class="mb-3">
<label for="autoDialOutAfterSeconds" class="form-label">自动拨出等待时间(秒)</label>
<input type="number" class="form-control" id="autoDialOutAfterSeconds"
name="dial.autoDialOutAfterSeconds">
name="dial.autoDialOutAfterSeconds">
<div class="form-text">无新按键后自动拨出的等待秒数</div>
</div>
<div class="mb-3">
<label for="resetTimeoutSeconds" class="form-label">重置超时时间(秒)</label>
<input type="number" class="form-control" id="resetTimeoutSeconds"
name="dial.resetTimeoutSeconds">
name="dial.resetTimeoutSeconds">
<div class="form-text">位数不足时,无操作重置的等待秒数</div>
</div>
</div>
@ -142,71 +158,71 @@
<div class="mb-3">
<label for="recordingFolder" class="form-label">录音保存文件夹</label>
<input type="text" class="form-control" id="recordingFolder"
name="recording.recordingFolder">
name="recording.recordingFolder">
</div>
<div class="mb-3">
<label for="recordingDeviceNumber" class="form-label">录音设备编号</label>
<input type="number" class="form-control" id="recordingDeviceNumber"
name="recording.recordingDeviceNumber">
name="recording.recordingDeviceNumber">
</div>
<div class="mb-3">
<label for="sampleRate" class="form-label">采样率</label>
<input type="number" class="form-control" id="sampleRate"
name="recording.sampleRate">
name="recording.sampleRate">
</div>
<div class="mb-3">
<label for="channels" class="form-label">声道数</label>
<input type="number" class="form-control" id="channels"
name="recording.channels">
name="recording.channels">
<div class="form-text">1=单声道2=立体声</div>
</div>
<div class="mb-3">
<label for="bufferMilliseconds" class="form-label">缓冲区大小(毫秒)</label>
<input type="number" class="form-control" id="bufferMilliseconds"
name="recording.bufferMilliseconds">
name="recording.bufferMilliseconds">
</div>
<div class="mb-3">
<label for="silenceThreshold" class="form-label">静音检测阈值</label>
<input type="number" class="form-control" id="silenceThreshold"
name="recording.silenceThreshold" step="0.01">
name="recording.silenceThreshold" step="0.01">
</div>
<div class="mb-3">
<label for="silenceTimeoutSeconds" class="form-label">静音超时时间(秒)</label>
<input type="number" class="form-control" id="silenceTimeoutSeconds"
name="recording.silenceTimeoutSeconds">
name="recording.silenceTimeoutSeconds">
<div class="form-text">无声音自动挂断的时间</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="allowUserHangup"
name="recording.allowUserHangup">
name="recording.allowUserHangup">
<label class="form-check-label" for="allowUserHangup">允许用户手动挂断</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="uploadRecordingToServer"
name="recording.uploadRecordingToServer">
name="recording.uploadRecordingToServer">
<label class="form-check-label"
for="uploadRecordingToServer">上传录音文件到服务器</label>
for="uploadRecordingToServer">上传录音文件到服务器</label>
</div>
<div class="mb-3">
<label for="recordingsFolder" class="form-label">服务器录音路径</label>
<input type="text" class="form-control" id="recordingsFolder"
name="recording.recordingsFolder">
name="recording.recordingsFolder">
</div>
<div class="mb-3">
<label for="fileNameFormat" class="form-label">录音文件名格式</label>
<input type="text" class="form-control" id="fileNameFormat"
name="recording.fileNameFormat">
name="recording.fileNameFormat">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input"
id="autoCleanupOldRecordings" name="recording.autoCleanupOldRecordings">
id="autoCleanupOldRecordings" name="recording.autoCleanupOldRecordings">
<label class="form-check-label"
for="autoCleanupOldRecordings">自动清理旧录音</label>
for="autoCleanupOldRecordings">自动清理旧录音</label>
</div>
<div class="mb-3">
<label for="keepRecordingsDays" class="form-label">保留录音天数</label>
<input type="number" class="form-control" id="keepRecordingsDays"
name="recording.keepRecordingsDays">
name="recording.keepRecordingsDays">
<div class="form-text">启用自动清理时有效</div>
</div>
</div>
@ -219,37 +235,37 @@
<div class="mb-3">
<label for="audioBasePath" class="form-label">音频文件基础路径</label>
<input type="text" class="form-control" id="audioBasePath"
name="audioFiles.audioBasePath">
name="audioFiles.audioBasePath">
</div>
<div class="mb-3">
<label for="waitingToneFile" class="form-label">等待嘟音文件</label>
<input type="text" class="form-control" id="waitingToneFile"
name="audioFiles.waitingToneFile">
name="audioFiles.waitingToneFile">
</div>
<div class="mb-3">
<label for="waitForPickupFile" class="form-label">等待接电话音频</label>
<input type="text" class="form-control" id="waitForPickupFile"
name="audioFiles.waitForPickupFile">
name="audioFiles.waitForPickupFile">
</div>
<div class="mb-3">
<label for="phonePickupFile" class="form-label">电话接起音频</label>
<input type="text" class="form-control" id="phonePickupFile"
name="audioFiles.phonePickupFile">
name="audioFiles.phonePickupFile">
</div>
<div class="mb-3">
<label for="promptUserRecordFile" class="form-label">提示用户录音音频</label>
<input type="text" class="form-control" id="promptUserRecordFile"
name="audioFiles.promptUserRecordFile">
name="audioFiles.promptUserRecordFile">
</div>
<div class="mb-3">
<label for="beepPromptFile" class="form-label">提示音文件</label>
<input type="text" class="form-control" id="beepPromptFile"
name="audioFiles.beepPromptFile">
name="audioFiles.beepPromptFile">
</div>
<div class="mb-3">
<label for="digitToneFileTemplate" class="form-label">数字按键音频模板</label>
<input type="text" class="form-control" id="digitToneFileTemplate"
name="audioFiles.digitToneFileTemplate">
name="audioFiles.digitToneFileTemplate">
<div class="form-text">例如: {0}.mp3用于数字0-9的按键音</div>
</div>
</div>
@ -260,19 +276,19 @@
<div class="mb-3">
<label for="waitForPickupMinSeconds" class="form-label">等待接听最小时间(秒)</label>
<input type="number" class="form-control" id="waitForPickupMinSeconds"
name="callFlow.waitForPickupMinSeconds">
name="callFlow.waitForPickupMinSeconds">
<div class="form-text">等待接电话音频的最小持续时间</div>
</div>
<div class="mb-3">
<label for="waitForPickupMaxSeconds" class="form-label">等待接听最大时间(秒)</label>
<input type="number" class="form-control" id="waitForPickupMaxSeconds"
name="callFlow.waitForPickupMaxSeconds">
name="callFlow.waitForPickupMaxSeconds">
<div class="form-text">等待接电话音频的最大持续时间</div>
</div>
<div class="mb-3">
<label for="playPickupProbability" class="form-label">接听概率</label>
<input type="number" class="form-control" id="playPickupProbability"
name="callFlow.playPickupProbability" step="0.01" min="0" max="1">
name="callFlow.playPickupProbability" step="0.01" min="0" max="1">
<div class="form-text">播放电话接起音频的概率0-1之间</div>
</div>
</div>
@ -319,7 +335,9 @@
<div class="card-header d-flex justify-content-between">
<span id="currentPlayingFile">当前播放:</span>
<button class="btn btn-sm btn-outline-secondary"
onclick="closeAudioPlayer()">关闭</button>
onclick="closeAudioPlayer()">
关闭
</button>
</div>
<div class="card-body">
<audio id="audioPlayer" controls class="w-100">
@ -346,7 +364,7 @@
</tbody>
</table>
</div>
<!-- 分页控制 -->
<div class="d-flex justify-content-between align-items-center mt-3">
<div>
@ -387,15 +405,21 @@
<!-- 显示配置 -->
<div class="tab-pane fade" id="display-config" role="tabpanel"
aria-labelledby="display-config-tab">
aria-labelledby="display-config-tab">
<div class="mt-3">
<div class="d-flex justify-content-end mb-3">
<button type="button" class="btn btn-success me-2"
onclick="saveDisplayConfig()">保存配置</button>
onclick="saveDisplayConfig()">
保存配置
</button>
<button type="button" class="btn btn-primary me-2"
onclick="getDisplayConfig()">刷新配置</button>
onclick="getDisplayConfig()">
刷新配置
</button>
<button type="button" class="btn btn-secondary"
onclick="resetDisplayConfig()">重置默认值</button>
onclick="resetDisplayConfig()">
重置默认值
</button>
</div>
<form id="display-config-form" class="mt-3">
@ -409,23 +433,23 @@
<div class="card-body">
<div class="mb-3">
<label for="leftTurnPageHeight"
class="form-label">翻页高度比例</label>
class="form-label">翻页高度比例</label>
<input type="number" class="form-control"
id="leftTurnPageHeight" step="0.1" min="0.1" max="1.0"
value="0.8">
id="leftTurnPageHeight" step="0.1" min="0.1" max="1.0"
value="0.8">
<div class="form-text">页面高度比例值范围0.1-1.0</div>
</div>
<div class="mb-3">
<label for="leftFontSize" class="form-label">字体大小</label>
<input type="text" class="form-control" id="leftFontSize"
value="16px">
value="16px">
<div class="form-text">左侧历史记录字体大小例如16px</div>
</div>
<div class="mb-3">
<label for="leftTypewriterSpeed"
class="form-label">打字效果速度</label>
class="form-label">打字效果速度</label>
<input type="number" class="form-control"
id="leftTypewriterSpeed" min="10" max="500" value="50">
id="leftTypewriterSpeed" min="10" max="500" value="50">
<div class="form-text">数值越小,打字效果速度越快</div>
</div>
</div>
@ -442,7 +466,7 @@
<div class="mb-3">
<label for="rightFontSize" class="form-label">字体大小</label>
<input type="text" class="form-control" id="rightFontSize"
value="24px">
value="24px">
<div class="form-text">右侧主要显示文本的字体大小</div>
</div>
<div class="mb-3">
@ -463,9 +487,9 @@
</div>
<div class="mb-3">
<label for="rightTypewriterSpeed"
class="form-label">打字效果速度</label>
class="form-label">打字效果速度</label>
<input type="number" class="form-control"
id="rightTypewriterSpeed" min="10" max="500" value="50">
id="rightTypewriterSpeed" min="10" max="500" value="50">
<div class="form-text">数值越小,打字效果速度越快</div>
</div>
</div>
@ -481,50 +505,50 @@
<div class="card-body">
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input"
id="waterEffectEnabled" checked>
id="waterEffectEnabled" checked>
<label class="form-check-label"
for="waterEffectEnabled">启用水波纹效果</label>
for="waterEffectEnabled">启用水波纹效果</label>
</div>
<div class="mb-3">
<label for="waterMinInterval"
class="form-label">最小间隔(毫秒)</label>
class="form-label">最小间隔(毫秒)</label>
<input type="number" class="form-control" id="waterMinInterval"
min="100" max="5000" value="800">
min="100" max="5000" value="800">
</div>
<div class="mb-3">
<label for="waterMaxInterval"
class="form-label">最大间隔(毫秒)</label>
class="form-label">最大间隔(毫秒)</label>
<input type="number" class="form-control" id="waterMaxInterval"
min="100" max="10000" value="2000">
min="100" max="10000" value="2000">
</div>
<div class="mb-3">
<label for="waterSimultaneousDrops"
class="form-label">同时涟漪数量</label>
class="form-label">同时涟漪数量</label>
<input type="number" class="form-control"
id="waterSimultaneousDrops" min="1" max="10" value="3">
id="waterSimultaneousDrops" min="1" max="10" value="3">
</div>
<div class="mb-3">
<label for="waterFadeOutSpeed" class="form-label">淡出速度</label>
<input type="number" class="form-control" id="waterFadeOutSpeed"
min="0.01" max="1" step="0.01" value="0.1">
min="0.01" max="1" step="0.01" value="0.1">
</div>
<div class="mb-3">
<label for="waterCenterBias" class="form-label">中心倾向</label>
<input type="number" class="form-control" id="waterCenterBias"
min="0" max="1" step="0.1" value="0.5">
min="0" max="1" step="0.1" value="0.5">
<div class="form-text">0表示随机1表示集中在中心</div>
</div>
<div class="mb-3">
<label for="waterLargeDropProbability"
class="form-label">大水滴概率</label>
class="form-label">大水滴概率</label>
<input type="number" class="form-control"
id="waterLargeDropProbability" min="0" max="1" step="0.1"
value="0.2">
id="waterLargeDropProbability" min="0" max="1" step="0.1"
value="0.2">
</div>
<div class="mb-3">
<label for="waterLargeDropSize" class="form-label">大水滴大小</label>
<input type="number" class="form-control"
id="waterLargeDropSize" min="3" max="20" value="9">
id="waterLargeDropSize" min="3" max="20" value="9">
</div>
</div>
</div>
@ -539,7 +563,9 @@
<div class="mt-3">
<div class="d-flex justify-content-end mb-3">
<button type="button" class="btn btn-primary"
onclick="getRealUserRecords()">刷新记录</button>
onclick="getRealUserRecords()">
刷新记录
</button>
</div>
<div class="table-responsive">
@ -560,7 +586,7 @@
</div>
</div>
</div>
<!-- 大屏文本1 -->
<div class="tab-pane fade" id="screen-text" role="tabpanel" aria-labelledby="screen-text-tab">
<div class="mt-3">
@ -605,7 +631,7 @@
</div>
@section Scripts {
@* <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.6/signalr.min.js"></script> *@
@* <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.6/signalr.min.js"></script> *@
<script src="~/lib/microsoft-signalr/signalr.min.js"></script>
<script>
@ -1171,7 +1197,7 @@
}
log("正在获取最近录音列表...");
// 重置分页和搜索
currentPage = 1;
searchKeyword = '';
@ -1185,9 +1211,9 @@
function fetchRecordingsPage(page) {
const skip = (page - 1) * pageSize;
const take = pageSize;
log(`请求第${page}页录音,跳过${skip}条,获取${take}条`);
connection.invoke("GetRecordingsPage", skip, take, searchKeyword)
.then(response => {
if (response && response.items && response.totalCount) {
@ -1203,7 +1229,7 @@
.catch(err => {
// 如果新方法不存在,回退到旧方法
log("GetRecordingsPage方法不存在回退到旧方法: " + err);
// 兼容旧方法,获取所有录音后在前端筛选
connection.invoke("GetRecentRecordings", 3000)
.then(() => {
@ -1220,10 +1246,10 @@
function updatePaginationInfo() {
const start = (currentPage - 1) * pageSize + 1;
const end = Math.min(start + pageSize - 1, totalRecordings);
document.getElementById('pagination-info').textContent =
document.getElementById('pagination-info').textContent =
totalRecordings > 0 ? `显示 ${start}-${end},共 ${totalRecordings} 条` : `共 0 条`;
// 更新按钮状态
document.getElementById('prev-page').disabled = currentPage <= 1;
document.getElementById('next-page').disabled = end >= totalRecordings;
@ -1233,11 +1259,11 @@
function searchRecordings() {
const keyword = document.getElementById('recording-search').value.toLowerCase().trim();
searchKeyword = keyword;
// 重置到第一页并请求数据
currentPage = 1;
fetchRecordingsPage(currentPage);
log(`搜索关键词: "${keyword}"`);
}
@ -1331,17 +1357,17 @@
function playRecordingInPage(fileName, rowElement) {
// 关闭任何已打开的播放器
closeAudioPlayer();
// 创建一个新的播放器行
const playerRow = document.createElement("tr");
playerRow.id = "inline-player-row";
playerRow.className = "audio-player-row";
// 创建带有播放器的单元格
const playerCell = document.createElement("td");
playerCell.colSpan = 5;
playerCell.className = "p-0";
// 创建播放器卡片
const playerCard = document.createElement("div");
playerCard.className = "card";
@ -1349,7 +1375,7 @@
playerCard.style.borderRadius = "0";
playerCard.style.boxShadow = "inset 0 3px 6px rgba(0,0,0,0.1)";
playerCard.style.backgroundColor = "#f8f9fa";
// 创建卡片内容
playerCard.innerHTML = `
<div class="card-body py-2">
@ -1363,16 +1389,16 @@
</audio>
</div>
`;
// 组装播放器行
playerCell.appendChild(playerCard);
playerRow.appendChild(playerCell);
// 插入到表格中点击的行的后面
const targetRow = rowElement.closest('tr');
if (targetRow && targetRow.parentNode) {
targetRow.parentNode.insertBefore(playerRow, targetRow.nextSibling);
// 将选中行标记为活动
targetRow.classList.add("table-primary");
}
@ -1388,16 +1414,16 @@
if (activeRow) {
activeRow.classList.remove("table-primary");
}
// 移除播放器行
playerRow.remove();
}
// 隐藏原来的顶部播放器(兼容性保留)
const audioPlayerCard = document.getElementById("audioPlayerCard");
if (audioPlayerCard) {
audioPlayerCard.style.display = "none";
// 停止原播放器
const audioPlayer = document.getElementById("audioPlayer");
if (audioPlayer) {
@ -1614,6 +1640,9 @@
case 2:
clientType = "显示器";
break;
case 3:
clientType = "大屏";
break;
}
row.innerHTML = `