| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>livetalking数字人交互平台</title>
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
- <style>
- :root {
- --primary-color: #4361ee;
- --secondary-color: #3f37c9;
- --accent-color: #4895ef;
- --background-color: #f8f9fa;
- --card-bg: #ffffff;
- --text-color: #212529;
- --border-radius: 10px;
- --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- }
- body {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background-color: var(--background-color);
- color: var(--text-color);
- min-height: 100vh;
- padding-top: 20px;
- }
- .dashboard-container {
- max-width: 1400px;
- margin: 0 auto;
- padding: 20px;
- }
- .card {
- background-color: var(--card-bg);
- border-radius: var(--border-radius);
- box-shadow: var(--box-shadow);
- border: none;
- margin-bottom: 20px;
- overflow: hidden;
- }
- .card-header {
- background-color: var(--primary-color);
- color: white;
- font-weight: 600;
- padding: 15px 20px;
- border-bottom: none;
- }
- .video-container {
- position: relative;
- width: 100%;
- background-color: #000;
- border-radius: var(--border-radius);
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- video {
- max-width: 100%;
- max-height: 100%;
- display: block;
- border-radius: var(--border-radius);
- }
- .controls-container {
- padding: 20px;
- }
- .btn-primary {
- background-color: var(--primary-color);
- border-color: var(--primary-color);
- }
- .btn-primary:hover {
- background-color: var(--secondary-color);
- border-color: var(--secondary-color);
- }
- .btn-outline-primary {
- color: var(--primary-color);
- border-color: var(--primary-color);
- }
- .btn-outline-primary:hover {
- background-color: var(--primary-color);
- color: white;
- }
- .form-control {
- border-radius: var(--border-radius);
- padding: 10px 15px;
- border: 1px solid #ced4da;
- }
- .form-control:focus {
- border-color: var(--accent-color);
- box-shadow: 0 0 0 0.25rem rgba(67, 97, 238, 0.25);
- }
- .status-indicator {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- display: inline-block;
- margin-right: 5px;
- }
- .status-connected {
- background-color: #28a745;
- }
- .status-disconnected {
- background-color: #dc3545;
- }
- .status-connecting {
- background-color: #ffc107;
- }
- .mode-indicator {
- position: absolute;
- top: 15px;
- left: 15px;
- padding: 5px 10px;
- border-radius: 20px;
- font-size: 0.8rem;
- font-weight: 600;
- color: white;
- z-index: 10;
- }
- .mode-intro {
- background-color: rgba(67, 97, 238, 0.8);
- }
- .mode-qa {
- background-color: rgba(40, 167, 69, 0.8);
- }
- .asr-container {
- height: 120px;
- overflow-y: auto;
- padding: 15px;
- background-color: #f8f9fa;
- border-radius: var(--border-radius);
- border: 1px solid #ced4da;
- }
- .asr-text {
- margin-bottom: 10px;
- padding: 10px;
- background-color: white;
- border-radius: var(--border-radius);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- }
- .user-message {
- background-color: #e3f2fd;
- border-left: 4px solid var(--primary-color);
- }
- .system-message {
- background-color: #f1f8e9;
- border-left: 4px solid #8bc34a;
- }
- .recording-indicator {
- position: absolute;
- top: 15px;
- right: 15px;
- background-color: rgba(220, 53, 69, 0.8);
- color: white;
- padding: 5px 10px;
- border-radius: 20px;
- font-size: 0.8rem;
- display: none;
- }
- .recording-indicator.active {
- display: flex;
- align-items: center;
- }
- .recording-indicator .blink {
- width: 10px;
- height: 10px;
- background-color: #fff;
- border-radius: 50%;
- margin-right: 5px;
- animation: blink 1s infinite;
- }
- @keyframes blink {
- 0% { opacity: 1; }
- 50% { opacity: 0.3; }
- 100% { opacity: 1; }
- }
- .mode-switch {
- margin-bottom: 20px;
- }
- .nav-tabs .nav-link {
- color: var(--text-color);
- border: none;
- padding: 10px 20px;
- border-radius: var(--border-radius) var(--border-radius) 0 0;
- }
- .nav-tabs .nav-link.active {
- color: var(--primary-color);
- background-color: var(--card-bg);
- border-bottom: 3px solid var(--primary-color);
- font-weight: 600;
- }
- .tab-content {
- padding: 20px;
- background-color: var(--card-bg);
- border-radius: 0 0 var(--border-radius) var(--border-radius);
- }
- .settings-panel {
- padding: 15px;
- background-color: #f8f9fa;
- border-radius: var(--border-radius);
- margin-top: 15px;
- }
- .footer {
- text-align: center;
- margin-top: 30px;
- padding: 20px 0;
- color: #6c757d;
- font-size: 0.9rem;
- }
-
- .voice-record-btn {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- background-color: var(--primary-color);
- color: white;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- transition: all 0.2s ease;
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
- margin: 0 auto;
- }
-
- .voice-record-btn:hover {
- background-color: var(--secondary-color);
- transform: scale(1.05);
- }
-
- .voice-record-btn:active {
- background-color: #dc3545;
- transform: scale(0.95);
- }
-
- .voice-record-btn i {
- font-size: 24px;
- }
-
- .voice-record-label {
- text-align: center;
- margin-top: 10px;
- font-size: 14px;
- color: #6c757d;
- }
-
- .recording-pulse {
- animation: pulse 1.5s infinite;
- }
-
- @keyframes pulse {
- 0% {
- box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7);
- }
- 70% {
- box-shadow: 0 0 0 15px rgba(220, 53, 69, 0);
- }
- 100% {
- box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
- }
- }
- </style>
- </head>
- <body>
- <div class="dashboard-container">
- <div class="row">
- <!-- 视频区域 -->
- <div class="col-lg-12">
- <div class="card">
- <div class="card-body p-0">
- <div class="video-container">
- <video id="video" autoplay playsinline></video>
- <div class="recording-indicator" id="recording-indicator">
- <div class="blink"></div>
- <span>录制中</span>
- </div>
- <div class="mode-indicator" id="mode-indicator" style="display: none;"></div>
- </div>
-
- <div class="controls-container">
- <div class="row">
- <div class="col-md-6 mb-3" style="display: none;">
- <button class="btn btn-primary w-100" id="startBtn">
- <i class="bi bi-play-fill"></i> 开始连接
- </button>
- <button class="btn btn-danger w-100" id="stopBtn" style="display: none;">
- <i class="bi bi-stop-fill"></i> 停止连接
- </button>
- </div>
- </div>
- <!-- 对话模式已移除 - 现在通过独立API /api/chat 调用 -->
- <div class="settings-panel mt-3">
- <div class="row">
- <div class="col-md-12">
- <div class="form-check form-switch mb-3" style="display: none;">
- <input class="form-check-input" type="checkbox" id="use-stun">
- <label class="form-check-label" for="use-stun">使用STUN服务器</label>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 隐藏的会话ID -->
- <input type="hidden" id="sessionid" value="0">
- <script src="client-v2.js"></script>
- <script src="srs.sdk.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
- <script>
- $(document).ready(function() {
- function updateConnectionStatus(status) {
- const statusIndicator = $('#connection-status');
- const statusText = $('#status-text');
-
- statusIndicator.removeClass('status-connected status-disconnected status-connecting');
-
- switch(status) {
- case 'connected':
- statusIndicator.addClass('status-connected');
- statusText.text('已连接');
- break;
- case 'connecting':
- statusIndicator.addClass('status-connecting');
- statusText.text('连接中...');
- break;
- case 'disconnected':
- default:
- statusIndicator.addClass('status-disconnected');
- statusText.text('未连接');
- break;
- }
- }
- // 更新模式指示器
- function updateModeIndicator(mode, playIndex = 1, totalCount = 8) {
- const modeIndicator = $('#mode-indicator');
-
- if (mode === 'intro') {
- modeIndicator.text(`介绍模式 (${playIndex}/${totalCount})`);
- modeIndicator.removeClass('mode-qa').addClass('mode-intro');
- modeIndicator.show();
- } else if (mode === 'qa') {
- modeIndicator.text('问答模式');
- modeIndicator.removeClass('mode-intro').addClass('mode-qa');
- modeIndicator.show();
- } else {
- modeIndicator.hide();
- }
- }
-
- // 计算文本播放时长(毫秒)
- function calculatePlayDuration(text) {
- // 中文字符和英文单词的播放速度不同
- const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
- const englishWords = (text.match(/[a-zA-Z]+/g) || []).length;
- const otherChars = text.length - chineseChars - englishWords;
-
- // 中文约 4-5 字/秒,英文约 3-4 词/秒,其他字符约 5 个/秒
- // 加上 1.5 秒的开头和结尾缓冲时间
- const duration = (chineseChars / 4.5 + englishWords / 3.5 + otherChars / 5) * 1000 + 1500;
-
- // 最小 3 秒,最大 20 秒
- return Math.max(3000, Math.min(20000, duration));
- }
-
- // 处理介绍播放完成,自动播放下一条
- function handleIntroPlayCompleted() {
- if (!isDuringIntro) return;
-
- fetch('/intro_play_completed', {
- body: JSON.stringify({
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- return response.json();
- }).then(function(data) {
- if (data.code === 0 && data.text) {
- console.log('下一条介绍内容开始播放:', data.text);
- // 更新模式指示器
- if (data.mode === 'intro') {
- updateModeIndicator('intro', data.play_index, data.total_count);
- }
- // 根据文本长度计算播放时长,再触发下一条
- const playDuration = calculatePlayDuration(data.text);
- console.log('计算播放时长:', playDuration, 'ms');
- setTimeout(handleIntroPlayCompleted, playDuration);
- } else if (data.code === 0 && !data.text) {
- console.log('介绍内容播放完成');
- // 标记介绍过程结束
- isDuringIntro = false;
- // 隐藏模式指示器
- updateModeIndicator('');
- } else {
- console.error('获取下一条介绍内容失败:', data.msg);
- }
- }).catch(function(error) {
- console.error('获取下一条介绍内容错误:', error);
- });
- }
-
- //播本地生活App商品介绍语音(使用新API)
- function playKnowledgeIntro() {
- fetch('/knowledge_intro', {
- body: JSON.stringify({
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- return response.json();
- }).then(function(data) {
- if (data.code === 0) {
- console.log('商品介绍开始播放:', data.text);
- // 更新模式指示器为介绍模式
- if (data.mode === 'intro') {
- updateModeIndicator('intro', data.play_index, data.total_count);
- }
- // 启动自动续播逻辑,根据文本长度计算播放时长
- const playDuration = calculatePlayDuration(data.text);
- console.log('第一条播放时长:', playDuration, 'ms');
- setTimeout(handleIntroPlayCompleted, playDuration);
- } else {
- console.error('商品介绍播放失败:', data.msg);
- }
- }).catch(function(error) {
- console.error('商品介绍播放错误:', error);
- // 降级到旧的硬编码方式
- fallbackPlayKnowledgeIntro();
- });
- }
-
- // 降级播放函数(兼容旧版本)
- function fallbackPlayKnowledgeIntro() {
- const introText = "欢迎使用本地生活App!我们为您提供丰富的本地商品和服务:美食外卖汇聚各类餐厅美食,快速配送到家;休闲娱乐提供电影院、KTV、游乐场等娱乐场所优惠;生活服务涵盖家政、维修、美容美发等便民服务;附近团购有周边商户超值团购,省钱又方便。现在您可以随时查询商品信息,我们会为您推荐最合适的本地服务!";
-
- fetch('/human', {
- body: JSON.stringify({
- text: introText,
- type: 'echo',
- interrupt: false,
- during_intro: true, //标记为介绍过程中
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- });
- }
- // 智能问答功能
- function handleQuestion(question) {
- //简单的关键词识别来判断应该使用哪个知识库
- const techKeywords = ['AI', '机器学习', '深度学习', '神经网络', 'WebRTC', 'Python', 'JavaScript', '算法', '编程', '技术', '开发', '代码'];
- const lifeKeywords = ['健康', '生活', '饮食', '运动', '娱乐', '旅游', '家庭', '工作', '学习', '情感', '日常'];
- const localLivingKeywords = ['美食', '外卖', '餐厅', '配送', '休闲', '娱乐', '生活服务', '团购', '会员', '积分', '优惠', '客服', '退款', '预约', '支付', '热门商圈', '安全保障', '评价', '售后', '优惠券', 'App', '本地', '生活', '服务'];
-
- let knowledgeBase = '通用';
-
- if (techKeywords.some(keyword => question.includes(keyword))) {
- knowledgeBase = '技术文档库';
- } else if (localLivingKeywords.some(keyword => question.includes(keyword))) {
- knowledgeBase = '本地生活 App';
- } else if (lifeKeywords.some(keyword => question.includes(keyword))) {
- knowledgeBase = '生活百科库';
- }
-
- return knowledgeBase;
- }
-
- // 页面加载后自动连接
- setTimeout(function() {
- updateConnectionStatus('connecting');
-
- // 设置一个标志,用于跟踪 track 事件是否已触发
- let trackEventFired = false;
-
- // 先保存原始的 onWebRTCConnected 回调
- const originalOnConnected = window.onWebRTCConnected || function() {};
-
- // 重写 onWebRTCConnected,在 track 事件后才更新 UI 状态
- window.onWebRTCConnected = function() {
- console.log('✅ WebRTC connected callback triggered');
-
- const video = document.getElementById('video');
- if (video) {
- console.log('Video element found, checking stream...');
- const stream = video.srcObject;
- if (stream) {
- console.log('Stream ID:', stream.id);
- console.log('Video tracks:', stream.getVideoTracks());
- console.log('Audio tracks:', stream.getAudioTracks());
-
- // 检查视频是否有实际数据
- video.onloadedmetadata = function() {
- console.log('Video metadata loaded:', {
- width: video.videoWidth,
- height: video.videoHeight,
- readyState: video.readyState
- });
- };
- } else {
- console.error('❌ No stream attached to video element!');
- }
- }
-
- trackEventFired = true;
- updateConnectionStatus('connected');
-
- // 连接成功后延迟播放介绍
- setTimeout(function() {
- // 标记进入介绍过程
- isDuringIntro = true;
- // 自动播放介绍语音
- playKnowledgeIntro();
- }, 2000);
-
- // 调用原始回调
- if (typeof originalOnConnected === 'function') {
- originalOnConnected();
- }
- };
-
- // 启动 WebRTC 连接
- start();
-
- // 添加定时器检查视频流是否已加载(作为备用方案)
- let connectionCheckTimer = setInterval(function() {
- const video = document.getElementById('video');
- // 检查视频是否有数据
- if (video.readyState >= 3 && video.videoWidth > 0) {
- console.log('Video loaded via readyState check:', video.videoWidth + 'x' + video.videoHeight);
-
- // 如果 track 事件还没触发,也更新状态
- if (!trackEventFired) {
- updateConnectionStatus('connected');
- trackEventFired = true;
-
- // 连接成功
- setTimeout(function() {
- // 标记进入介绍过程
- isDuringIntro = true;
- // 自动播放介绍语音
- playKnowledgeIntro();
- }, 2000);
- }
-
- clearInterval(connectionCheckTimer);
- }
- }, 2000); // 每 2 秒检查一次
-
- // 60 秒后如果还是连接中状态,就停止检查
- setTimeout(function() {
- if (connectionCheckTimer) {
- clearInterval(connectionCheckTimer);
- }
- // 如果 60 秒后 track 事件还没触发,检查连接状态
- if (!trackEventFired) {
- console.warn('⚠️ WebRTC track event not fired after 60 seconds');
- // 尝试重新连接
- updateConnectionStatus('disconnected');
- alert('连接超时,请检查网络连接或刷新页面重试');
- }
- }, 60000);
- }, 500);
- function addChatMessage(message, type = 'user') {
- const messagesContainer = $('#chat-messages');
- const messageClass = type === 'user' ? 'user-message' : 'system-message';
- const sender = type === 'user' ? '您' : '数字人';
-
- const messageElement = $(`
- <div class="asr-text ${messageClass}">
- ${sender}: ${message}
- </div>
- `);
-
- messagesContainer.append(messageElement);
- messagesContainer.scrollTop(messagesContainer[0].scrollHeight);
- }
- // 标记是否在介绍过程中
- let isDuringIntro = false;
-
- // 开始/停止按钮
- $('#startBtn').click(function() {
- updateConnectionStatus('connecting');
- start();
- $(this).hide();
- $('#stopBtn').show();
-
- // 添加定时器检查视频流是否已加载
- let connectionCheckTimer = setInterval(function() {
- const video = document.getElementById('video');
- // 检查视频是否有数据
- if (video.readyState >= 3 && video.videoWidth > 0) {
- updateConnectionStatus('connected');
- clearInterval(connectionCheckTimer);
- // 连接成功
- setTimeout(function() {
- // 标记进入介绍过程
- isDuringIntro = true;
- // 自动播放介绍语音
- playKnowledgeIntro();
- }, 2000);
- }
- }, 2000); // 每2秒检查一次
-
- // 60秒后如果还是连接中状态,就停止检查
- setTimeout(function() {
- if (connectionCheckTimer) {
- clearInterval(connectionCheckTimer);
- }
- }, 60000);
- });
- $('#stopBtn').click(function() {
- stop();
- $(this).hide();
- $('#startBtn').show();
- updateConnectionStatus('disconnected');
- });
- // 录制功能
- $('#btn_start_record').click(function() {
- console.log('Starting recording...');
- fetch('/record', {
- body: JSON.stringify({
- type: 'start_record',
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- if (response.ok) {
- console.log('Recording started.');
- $('#btn_start_record').prop('disabled', true);
- $('#btn_stop_record').prop('disabled', false);
- $('#recording-indicator').addClass('active');
- } else {
- console.error('Failed to start recording.');
- }
- }).catch(function(error) {
- console.error('Error:', error);
- });
- });
- $('#btn_stop_record').click(function() {
- console.log('Stopping recording...');
- fetch('/record', {
- body: JSON.stringify({
- type: 'end_record',
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- if (response.ok) {
- console.log('Recording stopped.');
- $('#btn_start_record').prop('disabled', false);
- $('#btn_stop_record').prop('disabled', true);
- $('#recording-indicator').removeClass('active');
- } else {
- console.error('Failed to stop recording.');
- }
- }).catch(function(error) {
- console.error('Error:', error);
- });
- });
- // 继续播放之前的内容(问答结束后)
- $('#btn_resume_interrupted').click(function() {
- console.log('Continuing playback after QA...');
- fetch('/continue_after_qa', {
- body: JSON.stringify({
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- return response.json();
- }).then(function(data) {
- if (data.code === 0) {
- console.log('Previous content continued:', data.msg);
- // 更新模式指示器为介绍模式
- if (data.mode === 'intro') {
- updateModeIndicator('intro', data.play_index, data.total_count);
- // 标记为介绍过程
- isDuringIntro = true;
- // 启动自动续播逻辑
- setTimeout(handleIntroPlayCompleted, 5000); // 假设每条介绍播放时间约为5秒
- }
- } else {
- console.error('Failed to continue previous content:', data.msg);
- }
- }).catch(function(error) {
- console.error('Error continuing previous content:', error);
- });
- });
- $('#echo-form').on('submit', function(e) {
- e.preventDefault();
- var message = $('#message').val();
- if (!message.trim()) return;
-
- console.log('Sending echo message:', message);
-
- fetch('/human', {
- body: JSON.stringify({
- text: message,
- type: 'echo',
- interrupt: true,
- sessionid: parseInt(document.getElementById('sessionid').value),
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- });
-
- $('#message').val('');
- });
- // 聊天模式表单提交
- $('#chat-form').on('submit', function(e) {
- e.preventDefault();
- var message = $('#chat-message').val();
- var selectedKnowledgeBase = $('#knowledge-base-select').val(); // 获取用户选择的知识库
- if (!message.trim()) return;
-
- // 检查sessionid是否有效
- var sessionid = parseInt(document.getElementById('sessionid').value);
- if (sessionid === 0) {
- return;
- }
-
- console.log('Sending chat message:', message);
- console.log('Selected knowledge base:', selectedKnowledgeBase);
- console.log('Session ID:', sessionid);
-
- // 如果用户选择了特定知识库,使用该知识库;否则智能识别
- const knowledgeBase = selectedKnowledgeBase && selectedKnowledgeBase !== '' ? selectedKnowledgeBase : handleQuestion(message);
-
- // 更新模式指示器为问答模式
- updateModeIndicator('qa');
-
- fetch('/human', {
- body: JSON.stringify({
- text: message,
- type: 'chat',
- interrupt: true,
- during_intro: isDuringIntro, // 根据当前是否在介绍过程中设置
- sessionid: sessionid,
- knowledge_base: knowledgeBase // 添加知识库信息
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- return response.json();
- }).then(function(data) {
- console.log('Response from server:', data);
- }).catch(function(error) {
- console.error('Error sending message:', error);
- });
-
- // 保留isDuringIntro状态,不要立即设置为false,以便后端正确处理中断
- // 只有在介绍播放完成后才设置为false
-
- addChatMessage(message, 'user');
- $('#chat-message').val('');
- });
- // 按住说话功能
- let mediaRecorder;
- let audioChunks = [];
- let isRecording = false;
- let recognition;
-
- // 检查浏览器是否支持语音识别
- const isSpeechRecognitionSupported = 'webkitSpeechRecognition' in window || 'SpeechRecognition' in window;
-
- if (isSpeechRecognitionSupported) {
- recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
- recognition.continuous = true;
- recognition.interimResults = true;
- recognition.lang = 'zh-CN';
-
- recognition.onresult = function(event) {
- let interimTranscript = '';
- let finalTranscript = '';
-
- for (let i = event.resultIndex; i < event.results.length; ++i) {
- if (event.results[i].isFinal) {
- finalTranscript += event.results[i][0].transcript;
- } else {
- interimTranscript += event.results[i][0].transcript;
- $('#chat-message').val(interimTranscript);
- }
- }
-
- if (finalTranscript) {
- $('#chat-message').val(finalTranscript);
- }
- };
-
- recognition.onerror = function(event) {
- console.error('语音识别错误:', event.error);
- };
- }
-
- // 按住说话按钮事件
- $('#voice-record-btn').on('mousedown touchstart', function(e) {
- e.preventDefault();
- startRecording();
- }).on('mouseup mouseleave touchend', function() {
- if (isRecording) {
- stopRecording();
- }
- });
-
- // 开始录音
- function startRecording() {
- if (isRecording) return;
-
- navigator.mediaDevices.getUserMedia({ audio: true })
- .then(function(stream) {
- audioChunks = [];
- mediaRecorder = new MediaRecorder(stream);
-
- mediaRecorder.ondataavailable = function(e) {
- if (e.data.size > 0) {
- audioChunks.push(e.data);
- }
- };
-
- mediaRecorder.start();
- isRecording = true;
-
- $('#voice-record-btn').addClass('recording-pulse');
- $('#voice-record-btn').css('background-color', '#dc3545');
-
- if (recognition) {
- recognition.start();
- }
- })
- .catch(function(error) {
- console.error('无法访问麦克风:', error);
- alert('无法访问麦克风,请检查浏览器权限设置。');
- });
- }
- function stopRecording() {
- if (!isRecording) return;
-
- mediaRecorder.stop();
- isRecording = false;
-
- // 停止所有音轨
- mediaRecorder.stream.getTracks().forEach(track => track.stop());
-
- // 视觉反馈恢复
- $('#voice-record-btn').removeClass('recording-pulse');
- $('#voice-record-btn').css('background-color', '');
-
- // 停止语音识别
- if (recognition) {
- recognition.stop();
- }
-
- // 获取识别的文本并发送
- setTimeout(function() {
- const recognizedText = $('#chat-message').val().trim();
- if (recognizedText) {
- // 更新模式指示器为问答模式
- updateModeIndicator('qa');
-
- // 检查sessionid是否有效
- var sessionid = parseInt(document.getElementById('sessionid').value);
- if (sessionid === 0) {
- return;
- }
-
- // 发送识别的文本
- var selectedKnowledgeBase = $('#knowledge-base-select').val(); // 获取用户选择的知识库
- const knowledgeBase = selectedKnowledgeBase && selectedKnowledgeBase !== '' ? selectedKnowledgeBase : handleQuestion(recognizedText);
- console.log('Sending voice message:', recognizedText);
- console.log('Session ID:', sessionid);
- fetch('/human', {
- body: JSON.stringify({
- text: recognizedText,
- type: 'chat',
- interrupt: true,
- during_intro: isDuringIntro, // 根据当前是否在介绍过程中设置
- sessionid: sessionid,
- knowledge_base: knowledgeBase
- }),
- headers: {
- 'Content-Type': 'application/json'
- },
- method: 'POST'
- }).then(function(response) {
- return response.json();
- }).then(function(data) {
- console.log('Response from server:', data);
- }).catch(function(error) {
- console.error('Error sending message:', error);
- });
-
- // 保留isDuringIntro状态,不要立即设置为false,以便后端正确处理中断
-
- addChatMessage(recognizedText, 'user');
- $('#chat-message').val('');
- }
- }, 500);
- }
- // WebRTC 相关功能
- if (typeof window.onWebRTCConnected === 'function') {
- const originalOnConnected = window.onWebRTCConnected;
- window.onWebRTCConnected = function() {
- updateConnectionStatus('connected');
- if (originalOnConnected) originalOnConnected();
- };
- } else {
- window.onWebRTCConnected = function() {
- updateConnectionStatus('connected');
- };
- }
- // 当连接断开时更新状态
- if (typeof window.onWebRTCDisconnected === 'function') {
- const originalOnDisconnected = window.onWebRTCDisconnected;
- window.onWebRTCDisconnected = function() {
- updateConnectionStatus('disconnected');
- if (originalOnDisconnected) originalOnDisconnected();
- };
- } else {
- window.onWebRTCDisconnected = function() {
- updateConnectionStatus('disconnected');
- };
- }
- // SRS WebRTC播放功能
- var sdk = null; // 全局处理器,用于在重新发布时进行清理
- function startPlay() {
- // 关闭之前的连接
- if (sdk) {
- sdk.close();
- }
-
- sdk = new SrsRtcWhipWhepAsync();
- $('#video').prop('srcObject', sdk.stream);
-
- var host = window.location.hostname;
- var url = "http://" + host + ":1985/rtc/v1/whep/?app=live&stream=livestream";
-
- sdk.play(url).then(function(session) {
- console.log('WebRTC播放已启动,会话ID:', session.sessionid);
- }).catch(function(reason) {
- sdk.close();
- console.error('WebRTC播放失败:', reason);
- });
- }
- });
- </script>
- </body>
- </html>
|