1057 lines
29 KiB
HTML
1057 lines
29 KiB
HTML
<!DOCTYPE HTML>
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||
<link rel="shortcut icon" type="image/png" href="assets/icon.png">
|
||
<title>家庭管理语音AI页面</title>
|
||
</head>
|
||
|
||
<body>
|
||
<!-- 讯飞 -->
|
||
<script src="./js/voice.js"></script>
|
||
<script src="./js/crypto-js.min.js"></script>
|
||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||
<script src="js/w_md5.js"></script>
|
||
|
||
<!-- 构建界面 -->
|
||
<div class="main">
|
||
<div class="content">
|
||
<!-- 加载遮罩 -->
|
||
<div class="dialog" id="dialog" style="display: none;">
|
||
<!-- <div class="overlay" id="overlay"></div> -->
|
||
<div class="modal" id="modal">
|
||
<div class="ld"><em></em></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 视频 -->
|
||
<div class="video-wrap">
|
||
<div id="shadow">
|
||
<div id="shadow-text">模型正在启动……</div>
|
||
</div>
|
||
<video id="myVideo" muted loop autoplay playsinline>
|
||
<source src="./demo/ple.mp4" type="video/mp4">
|
||
</video>
|
||
</div>
|
||
<audio
|
||
id="myAudio"
|
||
autoplay
|
||
controls
|
||
style="width: 100% height: 10px">
|
||
</audio>
|
||
|
||
<div class="status">
|
||
<input class="voice-input" type="search" name="voice" id="status-txt" style="pointer-events: none"/>
|
||
</div>
|
||
|
||
<div id="AiButton">
|
||
<!-- 录制 -->
|
||
<div class="buttons startRec">点击说话</div>
|
||
|
||
<!-- 暂停 -->
|
||
<div class="buttons endRec">中止</div>
|
||
|
||
<!-- 停止 -->
|
||
<div class="buttons stopRec">暂停</div>
|
||
</div>
|
||
|
||
<!-- 讯飞测试 -->
|
||
<div class="voice">
|
||
<div id="marquee">
|
||
<span id="voice-txt">我是您的健康助手, 请问您有什么帮助吗?</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
marquee("marquee", "voice-txt");
|
||
function marquee(p, s) {
|
||
var scrollWidth = document.getElementById(p).offsetWidth;
|
||
var textWidth = document.getElementById(s).offsetWidth;
|
||
var i = scrollWidth;
|
||
function change() {
|
||
i--;
|
||
if (i < -textWidth) {
|
||
i = scrollWidth;
|
||
}
|
||
document.getElementById(s).style.left = i + "px";
|
||
window.requestAnimationFrame(change);
|
||
}
|
||
window.requestAnimationFrame(change);
|
||
}
|
||
</script>
|
||
<script>
|
||
var Items = ['血糖', '睡眠', '血氧', '血压', '尿酸', '梅拖', '心率', '体温', '心电图', '身体成份', '运动', '血脂', '血液成分'];
|
||
var Question = '';
|
||
var Subtitles = '';
|
||
|
||
function getURLParameter(name) {
|
||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||
results = regex.exec(location.search);
|
||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||
}
|
||
|
||
// SEE事件解析
|
||
function extractStopEvent(sseString) {
|
||
// 使用正则表达式和字符串的 split 方法来分割事件
|
||
// 假设每个事件由空行分隔(\n\n),但请注意这取决于实际的换行符
|
||
const events = sseString.split(/\r?\n\r?\n/);
|
||
let stopEvent = null;
|
||
|
||
// 遍历每个事件
|
||
events.forEach(event => {
|
||
if (!event.trim()) {
|
||
// 忽略空字符串
|
||
return;
|
||
}
|
||
// 假设 data 字段是最后一个字段,并且可能跨越多行
|
||
let dataLines = [];
|
||
let isDataLine = false;
|
||
|
||
// 分解当前事件为行
|
||
const lines = event.split(/\r?\n/);
|
||
|
||
// 遍历每一行来找到 data 字段
|
||
lines.forEach(line => {
|
||
if (line.trim().startsWith('data:')) {
|
||
isDataLine = true;
|
||
dataLines.push(line.trim().slice(5)); // 移除 'data:' 前缀
|
||
} else if (isDataLine && !line.trim()) {
|
||
// 如果在 data 字段后遇到了空行,则停止收集
|
||
isDataLine = false;
|
||
} else if (isDataLine) {
|
||
// 继续收集 data 字段的行(跨越多行的情况)
|
||
dataLines.push(line.trim());
|
||
}
|
||
});
|
||
|
||
const dataJson = dataLines.join('');
|
||
try {
|
||
if (dataJson) {
|
||
const eventData = JSON.parse(dataJson);
|
||
if (eventData.output?.finish_reason === "stop") {
|
||
stopEvent = eventData;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('解析 data 字段为 JSON 时出错:', error);
|
||
}
|
||
});
|
||
|
||
// 如果没有找到 finish_reason 为 "stop" 的事件,则返回 null 或其他默认值
|
||
return stopEvent;
|
||
}
|
||
</script>
|
||
|
||
<!-- 讯飞语音识别 -->
|
||
<script>
|
||
window.onload = function () {
|
||
var videoElement = document.getElementById('myVideo');
|
||
var startTime = 5; // 开始时间(以秒为单位)
|
||
var endTime = 10; // 结束时间(以秒为单位)
|
||
var timeUpdateListener; // 保存timeupdate事件的监听器
|
||
|
||
// var onLine = 'false';
|
||
|
||
// // 在线离线
|
||
// function checkNetworkStatus() {
|
||
// if (navigator.onLine) {
|
||
// onLine = true;
|
||
// console.log('在线');
|
||
// } else {
|
||
// onLine = false;
|
||
// console.log('离线');
|
||
// }
|
||
// }
|
||
|
||
// 初始检查网络状态
|
||
// checkNetworkStatus();
|
||
|
||
// 遮罩视频
|
||
var shadowDom = document.getElementById('shadow');
|
||
var shadowText = document.getElementById('shadow-text');
|
||
|
||
// 指定段落
|
||
function playVideoSegment(startTime, endTime) {
|
||
videoElement.currentTime = startTime;
|
||
setTimeout(() => {
|
||
videoElement.play();
|
||
}, 50);
|
||
|
||
// 添加timeupdate事件监听器
|
||
timeUpdateListener = function() {
|
||
if (videoElement.currentTime >= endTime) {
|
||
videoElement.currentTime = startTime;
|
||
}
|
||
};
|
||
videoElement.addEventListener('timeupdate', timeUpdateListener);
|
||
}
|
||
|
||
// 重新播放
|
||
function replayVideoSegment(startTime, endTime) {
|
||
stopVideo();
|
||
playVideoSegment(startTime, endTime);
|
||
}
|
||
|
||
// 停止播放
|
||
function stopVideo() {
|
||
videoElement.pause();
|
||
videoElement.currentTime = 0;
|
||
|
||
// 移除timeupdate事件监听器
|
||
if (timeUpdateListener) {
|
||
videoElement.removeEventListener('timeupdate', timeUpdateListener);
|
||
timeUpdateListener = null;
|
||
}
|
||
}
|
||
|
||
// 当前AI视频循环
|
||
replayVideoSegment(0, 60);
|
||
|
||
// 获取 audio 元素的引用
|
||
var audioElement = document.getElementById('myAudio');
|
||
var isPlaying = false; // 是否播放
|
||
audioElement.muted = true; // 先静音
|
||
|
||
// 获取页面元素
|
||
var element = document.getElementById("elementId");
|
||
|
||
// 获取遮罩和弹窗元素
|
||
var dialog = document.getElementById('dialog');
|
||
|
||
// 点击事件
|
||
var startRec = document.getElementsByClassName('startRec')[0];
|
||
var endRec = document.getElementsByClassName('endRec')[0];
|
||
var stopRec = document.getElementsByClassName('stopRec')[0];
|
||
|
||
endRec.style.display = "none";
|
||
stopRec.style.display = "none";
|
||
|
||
var token = null;
|
||
let times = null;
|
||
|
||
// 个人信息
|
||
var user = userInfo(JSON.parse(getURLParameter('user')));
|
||
var apitoken = getURLParameter('token');
|
||
var deviceId = getURLParameter('deviceId');
|
||
var loading = getURLParameter('loading');
|
||
|
||
setTimeout(() => {
|
||
if (loading) {
|
||
shadowDom.style.display = 'none';
|
||
} else {
|
||
alert('异常')
|
||
shadowText.innerHTML = '环境异常,请检查网络'
|
||
}
|
||
}, 500);
|
||
|
||
|
||
// 获取微软token
|
||
fetch("https://eastasia.api.cognitive.microsoft.com/sts/v1.0/issueToken", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737'
|
||
},
|
||
}).then(async(response) => {
|
||
token = await response.text();
|
||
}).catch(e => {});
|
||
|
||
|
||
// 文本输入框
|
||
const statusTxt = document.querySelector('#status-txt');
|
||
const voiceTxt = document.querySelector('#voice-txt');
|
||
|
||
// 防止多次请求
|
||
var isCallbackExecuted = false;
|
||
|
||
|
||
/*
|
||
* 给予数据文字标识
|
||
*/
|
||
let exampleData = JSON.parse(getURLParameter('data'));
|
||
for (let i = 0; i < exampleData.length; i++) {
|
||
exampleData[i].name = demoData[exampleData[i].type]
|
||
};
|
||
|
||
// 过滤掉非对象类型的值
|
||
const filteredData = Object.entries(exampleData).filter(([key, value]) => typeof value === 'object');
|
||
|
||
// 将键值对转换为所需格式的数组
|
||
const arrayOfObjects = filteredData.map(([key, value]) => ({
|
||
type: key,
|
||
...value
|
||
}));
|
||
|
||
/*
|
||
* 实例化迅飞语音听写(流式版)WebAPI
|
||
*/
|
||
var seconds = 5;
|
||
var gaptime = null;
|
||
var voice = new Voice({
|
||
// 服务接口认证信息
|
||
appId: '5f4ffdeb',
|
||
apiSecret: 'OGIwM2RlMjBkOTI5Mzk5YTJlMzUwODI5',
|
||
apiKey: '0b17a761b6b7174b789f639119d7e29a',
|
||
onWillStatusChange: function (oldStatus, newStatus) {},
|
||
onTextChange: function (text) {
|
||
// 2秒钟内没有说话,就自动关闭
|
||
// 如果已有定时器在运行,则清除它
|
||
if (gaptime) {
|
||
clearInterval(gaptime);
|
||
gaptime = null;
|
||
};
|
||
// 重置倒计时
|
||
seconds = 5;
|
||
|
||
gaptime = setInterval(() => {
|
||
seconds--;
|
||
if (text) {
|
||
clearInterval(gaptime);
|
||
gaptime = null;
|
||
seconds = 5;
|
||
|
||
// 2秒钟内没有说话,就自动关闭
|
||
if (text) {
|
||
if (!isCallbackExecuted) {
|
||
clearTimeout(times);
|
||
times = setTimeout(() => {
|
||
// 监听识别结果的变化
|
||
voiceTxt.innerText = text;
|
||
marquee("marquee", "voice-txt");
|
||
statusTxt.value = '正在听…';
|
||
|
||
console.log(new Date().getTime(), '收到文字')
|
||
|
||
const params = { msg: text };
|
||
|
||
// 判断关键字是否存在
|
||
const result = parseHealthQuery(params.msg);
|
||
isCallbackExecuted = true;
|
||
console.log(new Date().getTime(), 1)
|
||
|
||
if (result) {
|
||
DetailDay(result.dataKey, result.date).then(res => {
|
||
let TargetData = res.data.data;
|
||
|
||
if (result.dataKey == 'ECGData') {
|
||
TargetData.map(item => {
|
||
delete item.data_msg.wavefrom;
|
||
delete item.data_msg.list;
|
||
})
|
||
}
|
||
|
||
if (result.dataKey == 'bloodLiquid') {
|
||
let uricAcidVal = [];
|
||
let lowDensity = [];
|
||
let triacylglycerol = [];
|
||
let highDensity = [];
|
||
let cholesterol = [];
|
||
TargetData.map(item => {
|
||
item.data_msg.cholesterol = cholesterol.push((item.data_msg.cholesterol/100).toFixed(2));
|
||
item.data_msg.highDensity = highDensity.push((item.data_msg.highDensity/100).toFixed(2));
|
||
item.data_msg.triacylglycerol = triacylglycerol.push((item.data_msg.triacylglycerol/100).toFixed(2));
|
||
item.data_msg.uricAcidVal = uricAcidVal.push((item.data_msg.uricAcidVal/10).toFixed(2));
|
||
item.data_msg.lowDensity = lowDensity.push((item.data_msg.lowDensity/100).toFixed(2));
|
||
})
|
||
|
||
TargetData = '尿酸: ' + JSON.stringify(uricAcidVal) + ',高密度脂蛋白: ' + JSON.stringify(highDensity) + ',低密度脂蛋白: ' + JSON.stringify(lowDensity) + ',甘油三脂: ' + JSON.stringify(triacylglycerol) + ',总胆固醇: ' + JSON.stringify(cholesterol);
|
||
}
|
||
|
||
Question = `请模仿全科医生的口吻与我: ${user}对话,我最近测量的${result.dataKey}数据为${JSON.stringify(TargetData)}, #提示:data_msg为值,hour_minute为检测时间。#提示:“[]”表示数据为空,请在小程序上传数据。#限制:回复不要带英文,要都转化成汉语。#限制:回复内容控制在150字。#限制:忽略“压力指数、疲劳指数、心肌炎风险、冠心病风险和动脉硬化”等数据。提示:回答尿酸时单位转换成“微摩尔/升”`
|
||
|
||
console.log(Question, '问题=========================问题');
|
||
|
||
/*
|
||
* 调用接口 传递关键信息 文字转语音
|
||
*/
|
||
const xhr = new XMLHttpRequest();
|
||
const url = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';
|
||
const apiKey = 'sk-cbb9b5ff44374fa2a8a258160ebb292d';
|
||
|
||
// 打开请求,设置为异步
|
||
xhr.open('POST', url, true);
|
||
|
||
// 设置请求头
|
||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||
xhr.setRequestHeader('Authorization', 'Bearer ' + apiKey);
|
||
xhr.setRequestHeader('X-DashScope-SSE', 'enable');
|
||
|
||
// 处理流式数据的接收(使用 progress 事件)
|
||
xhr.onprogress = function () {
|
||
const data = xhr.responseText;
|
||
|
||
// SSE 数据解析处理
|
||
const lines = data.split('\n');
|
||
lines.forEach(line => {
|
||
if (line.startsWith('data:')) {
|
||
const jsonStr = line.substring(5); // 去掉 'data:' 前缀
|
||
const parsedData = JSON.parse(jsonStr);
|
||
|
||
// 解析 content 内容
|
||
const content = parsedData.output.choices[0].message.content;
|
||
if (content) {
|
||
// 检查内容是否已经存在
|
||
if (!Subtitles.includes(content)) {
|
||
Subtitles += content;
|
||
}
|
||
};
|
||
}
|
||
});
|
||
};
|
||
|
||
// 检查请求完成
|
||
xhr.onload = function () {
|
||
if (xhr.status === 200) {
|
||
statusTxt.value = '正在说话';
|
||
RequestMicrosoft();
|
||
}
|
||
};
|
||
|
||
// 错误处理
|
||
xhr.onerror = function () {
|
||
console.error('An error occurred during the transaction', xhr.statusText);
|
||
};
|
||
|
||
/*
|
||
* 关键字转换
|
||
*/
|
||
const requestBody = {
|
||
model: 'qwen-turbo',
|
||
input: {
|
||
messages: [
|
||
{"role": "system", "content": Question},
|
||
{"role": "user", "content": `请问我${params.msg}正常吗`}
|
||
]
|
||
},
|
||
parameters: {
|
||
result_format: 'message',
|
||
incremental_output: true
|
||
}
|
||
};
|
||
|
||
xhr.send(JSON.stringify(requestBody));
|
||
isCallbackExecuted = true;
|
||
return;
|
||
})
|
||
} else {
|
||
const xhr = new XMLHttpRequest();
|
||
const url = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';
|
||
const apiKey = 'sk-cbb9b5ff44374fa2a8a258160ebb292d';
|
||
|
||
// 打开请求,设置为异步
|
||
xhr.open('POST', url, true);
|
||
|
||
// 设置请求头
|
||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||
xhr.setRequestHeader('Authorization', 'Bearer ' + apiKey);
|
||
xhr.setRequestHeader('X-DashScope-SSE', 'enable');
|
||
|
||
// 处理流式数据的接收(使用 progress 事件)
|
||
xhr.onprogress = function () {
|
||
const data = xhr.responseText;
|
||
|
||
// SSE 数据解析处理
|
||
const lines = data.split('\n');
|
||
lines.forEach(line => {
|
||
if (line.startsWith('data:')) {
|
||
const jsonStr = line.substring(5); // 去掉 'data:' 前缀
|
||
const parsedData = JSON.parse(jsonStr);
|
||
|
||
// 解析 content 内容
|
||
const content = parsedData.output.choices[0].message.content;
|
||
if (content) {
|
||
// 检查内容是否已经存在
|
||
if (!Subtitles.includes(content)) {
|
||
Subtitles += content;
|
||
}
|
||
};
|
||
}
|
||
});
|
||
};
|
||
|
||
// 检查请求完成
|
||
xhr.onload = function () {
|
||
if (xhr.status === 200) {
|
||
statusTxt.value = '正在说话';
|
||
RequestMicrosoft();
|
||
}
|
||
};
|
||
|
||
// 错误处理
|
||
xhr.onerror = function () {
|
||
console.error('An error occurred during the transaction', xhr.statusText);
|
||
};
|
||
|
||
// 发送请求
|
||
const requestBody = {
|
||
model: 'qwen-turbo',
|
||
input: {
|
||
messages: [
|
||
{"role": "system", "content": `请模仿全科医生的口吻与我对话`},
|
||
{"role": "user", "content": params.msg}
|
||
]
|
||
},
|
||
parameters: {
|
||
result_format: 'message',
|
||
incremental_output: true
|
||
}
|
||
};
|
||
|
||
xhr.send(JSON.stringify(requestBody));
|
||
isCallbackExecuted = true;
|
||
return;
|
||
}
|
||
}, 2000);
|
||
}
|
||
}
|
||
} else if (seconds == 0){
|
||
clearInterval(gaptime);
|
||
closeShibie();
|
||
return;
|
||
}
|
||
}, 1000);
|
||
}
|
||
});
|
||
|
||
|
||
// 开始识别
|
||
startRec.addEventListener("click", function() {
|
||
startShibie();
|
||
});
|
||
|
||
// 关闭识别
|
||
endRec.addEventListener("click", function() {
|
||
closeShibie();
|
||
|
||
// 直接开始
|
||
setTimeout(() => {
|
||
startShibie();
|
||
}, 100);
|
||
});
|
||
|
||
// 暂停识别
|
||
stopRec.addEventListener("click", function() {
|
||
stopPlay();
|
||
});
|
||
|
||
function stopPlay() {
|
||
/**暂停播放**/
|
||
if (audioElement.paused) {
|
||
audioElement.play();
|
||
videoElement.play();
|
||
isPlaying = true;
|
||
stopRec.textContent = '暂停';
|
||
statusTxt.value = '正在播放';
|
||
endRec.style.display = 'block';
|
||
} else {
|
||
audioElement.pause();
|
||
videoElement.pause();
|
||
isPlaying = false;
|
||
stopRec.textContent = '继续播放';
|
||
statusTxt.value = '已暂停';
|
||
endRec.style.display = 'none';
|
||
}
|
||
};
|
||
|
||
function startShibie() {
|
||
/**开始识别**/
|
||
|
||
statusTxt.value = '正在听…';
|
||
voiceTxt.innerText = '';
|
||
stopRec.textContent = '暂停';
|
||
|
||
isCallbackExecuted = false;
|
||
|
||
// 先静音即可处理解决(提前做交互)
|
||
audioElement.muted = false;
|
||
audioElement.currentTime = 0;
|
||
audioElement.pause();
|
||
|
||
voice.start();
|
||
|
||
startRec.style.display = 'none';
|
||
endRec.style.display = 'none';
|
||
stopRec.style.display = 'none';
|
||
showModal();
|
||
}
|
||
|
||
function closeShibie() {
|
||
/**关闭识别**/
|
||
voiceTxt.innerText = '';
|
||
statusTxt.value = '';
|
||
stopRec.textContent = '暂停';
|
||
voice.stop();
|
||
// 音频
|
||
audioElement.pause();
|
||
audioElement.currentTime = 0;
|
||
// 视频
|
||
replayVideoSegment(0, 60);
|
||
|
||
isCallbackExecuted = false;
|
||
|
||
startRec.style.display = 'block';
|
||
endRec.style.display = 'none';
|
||
stopRec.style.display = 'none';
|
||
hideModal()
|
||
}
|
||
|
||
// 显示弹窗和遮罩
|
||
function showModal() {
|
||
modal.style.display = 'block';
|
||
dialog.style.display = 'block';
|
||
}
|
||
|
||
// 隐藏弹窗和遮罩
|
||
function hideModal() {
|
||
modal.style.display = 'none';
|
||
dialog.style.display = 'none';
|
||
}
|
||
|
||
// 查询详情数据
|
||
async function DetailDay(type, data) {
|
||
try {
|
||
// const response = await fetch('https://test.sc2.agrimedia.cn/watch/device/getDeviceListDays', {
|
||
const response = await fetch('https://ai.agrimedia.cn/watch/device/getDeviceListDays', {
|
||
method: 'POST',
|
||
headers: {
|
||
'ApiToken': apitoken,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
"device_real_time": data,
|
||
"device_id": deviceId,
|
||
"type": type
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Network response was not ok: ' + response.statusText);
|
||
}
|
||
|
||
const result = await response.json();
|
||
return result;
|
||
} catch (error) {
|
||
console.error('There was a problem with your fetch operation:', error);
|
||
throw error; // 可选:重新抛出错误以便上层可以捕获并处理
|
||
}
|
||
}
|
||
|
||
// 请求微软文字转语音
|
||
function RequestMicrosoft() {
|
||
fetch("https://eastasia.tts.speech.microsoft.com/cognitiveservices/v1", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token,
|
||
'Ocp-Apim-Subscription-Key': '58e9b39b8f6f48fe8d01f85b727ff737',
|
||
'Content-Type': 'application/ssml+xml',
|
||
'X-Microsoft-OutputFormat': 'audio-24khz-48kbitrate-mono-mp3'
|
||
},
|
||
responseType: 'arraybuffer',
|
||
body: `<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
|
||
<voice name="zh-CN-XiaoxiaoNeural">
|
||
<mstts:express-as style="Default" >
|
||
<prosody rate="0%" pitch="0%">
|
||
${filterString(Subtitles, ['*', ' '])}
|
||
</prosody>
|
||
</mstts:express-as>
|
||
</voice>
|
||
</speak> `,
|
||
}).then(async(response) => {
|
||
startRec.style.display = "none";
|
||
endRec.style.display = "block";
|
||
stopRec.style.display = "block";
|
||
voiceTxt.innerText = '';
|
||
NextPlayVideo(response, filterString(Subtitles, ['*', ' ']));
|
||
}).catch(e => {
|
||
hideModal();
|
||
Subtitles = "";
|
||
});
|
||
}
|
||
|
||
// 字幕播放视频等操作
|
||
async function NextPlayVideo(response, str) {
|
||
const content_bytes = await response.arrayBuffer();
|
||
const blob = new Blob([content_bytes], { type: 'audio/mp3' });
|
||
const blobUrl = URL.createObjectURL(blob);
|
||
|
||
statusTxt.value = '正在说话';
|
||
|
||
// 字幕文字
|
||
voiceTxt.innerText = str;
|
||
marquee("marquee", "voice-txt");
|
||
|
||
// 设置音频源
|
||
audioElement.src = blobUrl;
|
||
|
||
// 播放音频
|
||
audioElement.play();
|
||
|
||
console.log(new Date().getTime(), '播放音频')
|
||
|
||
// 循环视频
|
||
replayVideoSegment(60, 120);
|
||
|
||
// 监听播放技术
|
||
myAudio.addEventListener('ended', function() {
|
||
console.log('音频播放已结束!');
|
||
// 播放结束重新开始监听
|
||
replayVideoSegment(0, 60);
|
||
voice.stop();
|
||
startShibie();
|
||
});
|
||
|
||
Subtitles = "";
|
||
}
|
||
|
||
// 拿到的数据移除*
|
||
function filterString(str, charsToRemove) {
|
||
// 这里的正则表达式是通过将charsToRemove数组中的字符转换为字符类(character class)来构建的
|
||
// 例如,如果charsToRemove是['*', ' '],则正则表达式将是/[* ]/g
|
||
const regex = new RegExp(`[${charsToRemove.join('')}]`, 'g');
|
||
// 使用replace方法和正则表达式来移除所有匹配的字符
|
||
return str.replace(regex, '');
|
||
}
|
||
|
||
// 个人信息
|
||
function userInfo(user) {
|
||
if (user) {
|
||
return `年龄${user.birthday}, 身高${user.height}, 体重${user.weight}`
|
||
}
|
||
}
|
||
|
||
// 解析文字
|
||
function parseHealthQuery(query, demoData) {
|
||
if (!demoData) {
|
||
demoData = {
|
||
bloodGlucose: "血糖",
|
||
SleepDatas: "睡眠",
|
||
bloodOxygen: ['血氧', '血氧饱和度'],
|
||
bloodPressure: '血压',
|
||
meiTuo: '梅脱',
|
||
pulseReat: '心率',
|
||
bodyTemperature: '体温',
|
||
ECGData: '心电图',
|
||
bloodLiquid: ['血脂', '血液', '尿酸', '胆固醇', '甘油三脂', '高密度脂蛋白', '低密度脂蛋白'],
|
||
bodyData: '身体成分',
|
||
stepIndex: ['运动', '步数']
|
||
};
|
||
}
|
||
|
||
// 获取当前日期
|
||
const today = new Date();
|
||
const year = today.getFullYear();
|
||
const month = today.getMonth() + 1; // 月份从0开始,所以需要+1
|
||
const day = today.getDate();
|
||
|
||
// 初始化日期为今天
|
||
let targetDate = new Date(year, month, day);
|
||
|
||
// 检查时间词并设置目标日期
|
||
if (query.includes("昨天")) {
|
||
targetDate.setDate(day - 1);
|
||
} else if (query.includes("前天")) {
|
||
targetDate.setDate(day - 2);
|
||
}
|
||
|
||
const formattedDate = `${targetDate.getFullYear()}-${('0' + targetDate.getMonth()).slice(-2)}-${('0' + targetDate.getDate()).slice(-2)}`;
|
||
|
||
// 创建一个辅助函数来检查 bloodLiquid 对象是否包含查询字符串
|
||
function isBloodLiquidMatch(bloodLiquidData, query) {
|
||
return bloodLiquidData.some(element => query.includes(element))
|
||
}
|
||
|
||
// 在查询逻辑中使用这个辅助函数
|
||
for (let key in demoData) {
|
||
if (typeof demoData[key] === 'object' && key === 'bloodLiquid' || typeof demoData[key] === 'object' && key === 'stepIndex' || typeof demoData[key] === 'object' && key === 'bloodOxygen' ) {
|
||
if (isBloodLiquidMatch(demoData[key], query)) {
|
||
key === 'stepIndex'? key = 'step_split' : ''; // 修改步数参数
|
||
return {
|
||
dataKey: key,
|
||
date: formattedDate
|
||
};
|
||
}
|
||
} else if (query.includes(demoData[key])) {
|
||
return {
|
||
dataKey: key,
|
||
date: formattedDate
|
||
};
|
||
}
|
||
}
|
||
|
||
// 如果没有找到匹配的数据项,则返回null或错误信息(这里选择返回null)
|
||
return null;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
}
|
||
.content {
|
||
width: 100%;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background-image: url('https://img.agrimedia.cn/bmsc/bg-cideo-tuya.png');
|
||
background-size: 100% 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.video-wrap {
|
||
width: 80%;
|
||
top: 10%;
|
||
height: auto;
|
||
background-image: url('https://img.agrimedia.cn/bmsc/bg-cideo-tuya.png');
|
||
background-size: 100% 100%;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
#shadow {
|
||
position: fixed;
|
||
z-index: 99999;
|
||
width: 100%;
|
||
height: 100vh;
|
||
left: 0;
|
||
top: 0;
|
||
background-color: #06193a;
|
||
}
|
||
|
||
#shadow-text {
|
||
width: 100%;
|
||
height: 100vh;
|
||
position: absolute;
|
||
text-align: center;
|
||
line-height: 100vh;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
margin: 0 auto;
|
||
color: #fff;
|
||
}
|
||
|
||
#myVideo {
|
||
width: 100%;
|
||
height: 100% !important;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
#myAudio {
|
||
position: absolute;
|
||
top: 0px;
|
||
left: 0px;
|
||
opacity: 0;
|
||
}
|
||
|
||
#AiButton {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
#AiButton > img {
|
||
width: 120px;
|
||
height: 30px;
|
||
}
|
||
|
||
.status > .btn {
|
||
text-align: center;
|
||
font-size: 30px;
|
||
color: #fff;
|
||
}
|
||
|
||
.buttons {
|
||
border: 2px solid #fff;
|
||
color: #fff;
|
||
font-size: 28px;
|
||
padding: 4px 20px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
/* 遮罩样式 */
|
||
.overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
|
||
display: none; /* 初始隐藏 */
|
||
z-index: 9999; /* 设置为最高层级 */
|
||
}
|
||
|
||
/* 弹窗样式 */
|
||
.modal {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%); /* 居中对齐 */
|
||
padding: 20px;
|
||
border-radius: 5px;
|
||
text-align: center;
|
||
z-index: 99999;
|
||
}
|
||
|
||
.ld {
|
||
background: #000;
|
||
transform: rotate(45deg);
|
||
transform-origin: center;
|
||
margin-top: calc(var(--dis) / -1.41421); /* Math.sqrt(2) */
|
||
|
||
--dis: 20px; /*圆尺寸和偏移量*/
|
||
--dur: 2s; /*动画时长*/
|
||
}
|
||
|
||
.ld em {
|
||
position: relative;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.ld::before {
|
||
background-color: #38597A;
|
||
}
|
||
|
||
.ld::after {
|
||
background-color: #3FEBFF;
|
||
}
|
||
|
||
.ld em::before {
|
||
background-color: #87B2DD;
|
||
}
|
||
|
||
.ld em::after {
|
||
background-color: #58D5FF;
|
||
}
|
||
|
||
.ld::before,
|
||
.ld::after,
|
||
.ld em::before,
|
||
.ld em::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: var(--dis);
|
||
height: var(--dis);
|
||
border-radius: var(--dis);
|
||
left: 0;
|
||
top: 0;
|
||
animation-duration: var(--dur);
|
||
animation-iteration-count: infinite;
|
||
animation-fill-mode: both;
|
||
--dir: 1;
|
||
--tr: calc(var(--dir) * var(--dis));
|
||
--ttr: calc(-1 * var(--tr));
|
||
--tx: translateX(var(--tr));
|
||
--ttx: translateX(var(--ttr));
|
||
--ty: translateY(var(--tr));
|
||
--tty: translateY(var(--ttr));
|
||
}
|
||
.ld::before,
|
||
.ld::after {
|
||
--dir: -1;
|
||
}
|
||
|
||
.ld::before,
|
||
.ld em::before {
|
||
transform: var(--ttx);
|
||
animation-name: tx;
|
||
}
|
||
.ld::after,
|
||
.ld em::after {
|
||
transform: var(--tty);
|
||
animation-name: ty;
|
||
}
|
||
|
||
@keyframes tx {
|
||
50% {
|
||
transform: var(--tx);
|
||
}
|
||
}
|
||
|
||
@keyframes ty {
|
||
50% {
|
||
transform: var(--ty);
|
||
}
|
||
}
|
||
|
||
.rule {
|
||
width: 1px;
|
||
background: #aaa;
|
||
position: fixed;
|
||
left: 50%;
|
||
top: 0;
|
||
bottom: 0;
|
||
}
|
||
.rule::after {
|
||
content: "";
|
||
height: 1px;
|
||
background: #aaa;
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 0;
|
||
right: 0;
|
||
}
|
||
.runRec {
|
||
display: none;
|
||
}
|
||
|
||
.voice-input {
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
height: 50px;
|
||
background-color: none;
|
||
line-height: 50px;
|
||
border: 0;/*清除自带的2px的边框*/
|
||
padding: 0;/*清除自带的padding间距*/
|
||
outline: none;/*清除input点击之后的黑色边框*/
|
||
text-align: center;
|
||
background-color: transparent;
|
||
color: #fff;
|
||
font-size: 26px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.voice {
|
||
position: fixed;
|
||
bottom: 0px;
|
||
width: 100%;
|
||
/* background-color: #000; */
|
||
}
|
||
|
||
#marquee {
|
||
display: block;
|
||
width: 100%;
|
||
height: 60px;
|
||
margin: 0 auto;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#voice-txt {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 100%;
|
||
line-height: 60px;
|
||
font-size:29px;
|
||
color: #fff;
|
||
display: block;
|
||
word-break: keep-all;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
</style>
|
||
|
||
</body>
|
||
</html> |