Showing
3 changed files
with
86 additions
and
18 deletions
| @@ -45,6 +45,8 @@ import asyncio | @@ -45,6 +45,8 @@ import asyncio | ||
| 45 | import torch | 45 | import torch |
| 46 | from typing import Dict | 46 | from typing import Dict |
| 47 | from logger import logger | 47 | from logger import logger |
| 48 | +from pydub import AudioSegment | ||
| 49 | +from io import BytesIO | ||
| 48 | 50 | ||
| 49 | 51 | ||
| 50 | app = Flask(__name__) | 52 | app = Flask(__name__) |
| @@ -156,8 +158,10 @@ async def humanaudio(request): | @@ -156,8 +158,10 @@ async def humanaudio(request): | ||
| 156 | try: | 158 | try: |
| 157 | params = await request.json() | 159 | params = await request.json() |
| 158 | sessionid = int(params.get('sessionid', 0)) | 160 | sessionid = int(params.get('sessionid', 0)) |
| 159 | - fileobj = params['file_url'] | ||
| 160 | - if fileobj.startswith("http"): | 161 | + fileobj = params.get('file_url') |
| 162 | + | ||
| 163 | + # 获取音频文件数据 | ||
| 164 | + if isinstance(fileobj, str) and fileobj.startswith("http"): | ||
| 161 | async with aiohttp.ClientSession() as session: | 165 | async with aiohttp.ClientSession() as session: |
| 162 | async with session.get(fileobj) as response: | 166 | async with session.get(fileobj) as response: |
| 163 | if response.status == 200: | 167 | if response.status == 200: |
| @@ -167,9 +171,18 @@ async def humanaudio(request): | @@ -167,9 +171,18 @@ async def humanaudio(request): | ||
| 167 | content_type="application/json", | 171 | content_type="application/json", |
| 168 | text=json.dumps({"code": -1, "msg": "Error downloading file"}) | 172 | text=json.dumps({"code": -1, "msg": "Error downloading file"}) |
| 169 | ) | 173 | ) |
| 174 | + # 根据 URL 后缀判断是否为 MP3 文件 | ||
| 175 | + is_mp3 = fileobj.lower().endswith('.mp3') | ||
| 170 | else: | 176 | else: |
| 171 | filename = fileobj.filename | 177 | filename = fileobj.filename |
| 172 | filebytes = fileobj.file.read() | 178 | filebytes = fileobj.file.read() |
| 179 | + is_mp3 = filename.lower().endswith('.mp3') | ||
| 180 | + | ||
| 181 | + if is_mp3: | ||
| 182 | + audio = AudioSegment.from_file(BytesIO(filebytes), format="mp3") | ||
| 183 | + out_io = BytesIO() | ||
| 184 | + audio.export(out_io, format="wav") | ||
| 185 | + filebytes = out_io.getvalue() | ||
| 173 | 186 | ||
| 174 | nerfreals[sessionid].put_audio_file(filebytes) | 187 | nerfreals[sessionid].put_audio_file(filebytes) |
| 175 | 188 |
| @@ -8,15 +8,18 @@ def llm_response(message,nerfreal:BaseReal): | @@ -8,15 +8,18 @@ def llm_response(message,nerfreal:BaseReal): | ||
| 8 | from openai import OpenAI | 8 | from openai import OpenAI |
| 9 | client = OpenAI( | 9 | client = OpenAI( |
| 10 | # 如果您没有配置环境变量,请在此处用您的API Key进行替换 | 10 | # 如果您没有配置环境变量,请在此处用您的API Key进行替换 |
| 11 | - api_key=os.getenv("DASHSCOPE_API_KEY"), | 11 | + # api_key=os.getenv("DASHSCOPE_API_KEY"), |
| 12 | + api_key = "localkey", | ||
| 13 | + base_url="http://127.0.0.1:5000/v1" | ||
| 12 | # 填写DashScope SDK的base_url | 14 | # 填写DashScope SDK的base_url |
| 13 | - base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", | 15 | + # base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", |
| 14 | ) | 16 | ) |
| 15 | end = time.perf_counter() | 17 | end = time.perf_counter() |
| 16 | logger.info(f"llm Time init: {end-start}s") | 18 | logger.info(f"llm Time init: {end-start}s") |
| 17 | completion = client.chat.completions.create( | 19 | completion = client.chat.completions.create( |
| 18 | - model="qwen-plus", | ||
| 19 | - messages=[{'role': 'system', 'content': 'You are a helpful assistant.'}, | 20 | + model="fay-streaming", |
| 21 | + # model="qwen-plus", | ||
| 22 | + messages=[{'role': 'system', 'content': '你是小艺,是由艺云展陈开发的AI语音聊天机器人,回答风格精简。'}, | ||
| 20 | {'role': 'user', 'content': message}], | 23 | {'role': 'user', 'content': message}], |
| 21 | stream=True, | 24 | stream=True, |
| 22 | # 通过以下设置,在流式输出的最后一行展示token使用信息 | 25 | # 通过以下设置,在流式输出的最后一行展示token使用信息 |
| @@ -267,6 +267,7 @@ | @@ -267,6 +267,7 @@ | ||
| 267 | 267 | ||
| 268 | <!-- 主内容区域 --> | 268 | <!-- 主内容区域 --> |
| 269 | <div id="main-content"> | 269 | <div id="main-content"> |
| 270 | + <input type="hidden" id="username" value="User"> | ||
| 270 | <div id="media"> | 271 | <div id="media"> |
| 271 | <h2>艺云展陈</h2> | 272 | <h2>艺云展陈</h2> |
| 272 | <audio id="audio" autoplay="true"></audio> | 273 | <audio id="audio" autoplay="true"></audio> |
| @@ -278,10 +279,7 @@ | @@ -278,10 +279,7 @@ | ||
| 278 | <script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.4.js"></script> | 279 | <script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.4.js"></script> |
| 279 | <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script> | 280 | <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script> |
| 280 | <script type="text/javascript" charset="utf-8"> | 281 | <script type="text/javascript" charset="utf-8"> |
| 281 | - var ws; | ||
| 282 | - var reconnectInterval = 5000; | ||
| 283 | - var maxReconnectAttempts = 10; | ||
| 284 | - var reconnectAttempts = 0; | 282 | + |
| 285 | $(document).ready(function() { | 283 | $(document).ready(function() { |
| 286 | // 侧边栏切换功能 | 284 | // 侧边栏切换功能 |
| 287 | $('#sidebar-toggle').click(function() { | 285 | $('#sidebar-toggle').click(function() { |
| @@ -389,30 +387,78 @@ | @@ -389,30 +387,78 @@ | ||
| 389 | 387 | ||
| 390 | // WebSocket connection to Fay digital avatar (port 10002) | 388 | // WebSocket connection to Fay digital avatar (port 10002) |
| 391 | var ws; | 389 | var ws; |
| 392 | - var reconnectInterval = 5000; | ||
| 393 | - var maxReconnectAttempts = 10; | 390 | + var reconnectInterval = 5000; // 每5秒尝试重连一次 |
| 391 | + var maxReconnectAttempts = 10; // 最大重连次数 | ||
| 394 | var reconnectAttempts = 0; | 392 | var reconnectAttempts = 0; |
| 395 | 393 | ||
| 394 | + function generateUsername() { | ||
| 395 | + var username = 'User' + Math.floor(Math.random() * 10000); | ||
| 396 | + return username; | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + function setUsername() { | ||
| 400 | + var storedUsername = localStorage.getItem('username'); | ||
| 401 | + if (!storedUsername) { | ||
| 402 | + storedUsername = generateUsername(); | ||
| 403 | + localStorage.setItem('username', storedUsername); | ||
| 404 | + } | ||
| 405 | + $('#username').val(storedUsername); // Use the username as the session ID | ||
| 406 | + } | ||
| 407 | + setUsername(); | ||
| 396 | function connectWebSocket() { | 408 | function connectWebSocket() { |
| 397 | var host = window.location.hostname; | 409 | var host = window.location.hostname; |
| 398 | - ws = new WebSocket("ws://" + host + ":10002"); | ||
| 399 | - | 410 | + ws = new WebSocket("ws://127.0.0.1:10002"); |
| 400 | ws.onopen = function() { | 411 | ws.onopen = function() { |
| 401 | console.log('Connected to WebSocket on port 10002'); | 412 | console.log('Connected to WebSocket on port 10002'); |
| 402 | - reconnectAttempts = 0; | 413 | + reconnectAttempts = 0; // 重置重连次数 |
| 403 | 414 | ||
| 404 | - var loginMessage = JSON.stringify({ "Username": "User" }); | 415 | + // 在连接成功后发送 {"Username": "User"} |
| 416 | + var loginMessage = JSON.stringify({ "Username": $('#username').val() }); | ||
| 405 | ws.send(loginMessage); | 417 | ws.send(loginMessage); |
| 406 | loginMessage = JSON.stringify({ "Output": "1" }); | 418 | loginMessage = JSON.stringify({ "Output": "1" }); |
| 407 | ws.send(loginMessage); | 419 | ws.send(loginMessage); |
| 408 | }; | 420 | }; |
| 409 | 421 | ||
| 422 | + | ||
| 423 | + function addMessage(text, type = "right") { | ||
| 424 | + const chatOverlay = document.getElementById("chatOverlay"); | ||
| 425 | + const messageDiv = document.createElement("div"); | ||
| 426 | + messageDiv.classList.add("message", type); | ||
| 427 | + | ||
| 428 | + const avatar = document.createElement("img"); | ||
| 429 | + avatar.classList.add("avatar"); | ||
| 430 | + avatar.src = type === "right" ? "images/avatar-right.png" : "images/avatar-left.png"; | ||
| 431 | + | ||
| 432 | + const textContainer = document.createElement("div"); | ||
| 433 | + textContainer.classList.add("text-container"); | ||
| 434 | + | ||
| 435 | + const textElement = document.createElement("div"); | ||
| 436 | + textElement.classList.add("text"); | ||
| 437 | + textElement.textContent = text; | ||
| 438 | + | ||
| 439 | + const timeElement = document.createElement("div"); | ||
| 440 | + timeElement.classList.add("time"); | ||
| 441 | + timeElement.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | ||
| 442 | + | ||
| 443 | + textContainer.appendChild(textElement); | ||
| 444 | + textContainer.appendChild(timeElement); | ||
| 445 | + | ||
| 446 | + messageDiv.appendChild(avatar); | ||
| 447 | + messageDiv.appendChild(textContainer); | ||
| 448 | + | ||
| 449 | + chatOverlay.appendChild(messageDiv); | ||
| 450 | + | ||
| 451 | + // 自动滚动到底部 | ||
| 452 | + chatOverlay.scrollTop = chatOverlay.scrollHeight; | ||
| 453 | + } | ||
| 454 | + | ||
| 410 | ws.onmessage = function(e) { | 455 | ws.onmessage = function(e) { |
| 411 | var messageData = JSON.parse(e.data); | 456 | var messageData = JSON.parse(e.data); |
| 412 | 457 | ||
| 413 | if (messageData.Data && messageData.Data.Key) { | 458 | if (messageData.Data && messageData.Data.Key) { |
| 414 | if(messageData.Data.Key == "audio"){ | 459 | if(messageData.Data.Key == "audio"){ |
| 415 | var value = messageData.Data.HttpValue; | 460 | var value = messageData.Data.HttpValue; |
| 461 | + console.log('Value:', value); | ||
| 416 | fetch('/humanaudio', { | 462 | fetch('/humanaudio', { |
| 417 | body: JSON.stringify({ | 463 | body: JSON.stringify({ |
| 418 | file_url: value, | 464 | file_url: value, |
| @@ -423,6 +469,9 @@ | @@ -423,6 +469,9 @@ | ||
| 423 | }, | 469 | }, |
| 424 | method: 'POST' | 470 | method: 'POST' |
| 425 | }); | 471 | }); |
| 472 | + }else if (messageData.Data.Key == "text") { | ||
| 473 | + var reply = messageData.Data.Value; | ||
| 474 | + addMessage(reply, "left"); | ||
| 426 | } | 475 | } |
| 427 | } | 476 | } |
| 428 | 477 | ||
| @@ -435,7 +484,7 @@ | @@ -435,7 +484,7 @@ | ||
| 435 | 484 | ||
| 436 | ws.onerror = function(e) { | 485 | ws.onerror = function(e) { |
| 437 | console.error('WebSocket error:', e); | 486 | console.error('WebSocket error:', e); |
| 438 | - ws.close(); | 487 | + ws.close(); // 关闭连接并尝试重连 |
| 439 | }; | 488 | }; |
| 440 | } | 489 | } |
| 441 | 490 | ||
| @@ -443,6 +492,9 @@ | @@ -443,6 +492,9 @@ | ||
| 443 | if (reconnectAttempts < maxReconnectAttempts) { | 492 | if (reconnectAttempts < maxReconnectAttempts) { |
| 444 | reconnectAttempts++; | 493 | reconnectAttempts++; |
| 445 | console.log('Attempting to reconnect... (Attempt ' + reconnectAttempts + ')'); | 494 | console.log('Attempting to reconnect... (Attempt ' + reconnectAttempts + ')'); |
| 495 | + if(parseInt(document.getElementById('is_open').value) == 1){ | ||
| 496 | + stop() | ||
| 497 | + } | ||
| 446 | setTimeout(function() { | 498 | setTimeout(function() { |
| 447 | connectWebSocket(); | 499 | connectWebSocket(); |
| 448 | }, reconnectInterval); | 500 | }, reconnectInterval); |
| @@ -451,7 +503,7 @@ | @@ -451,7 +503,7 @@ | ||
| 451 | } | 503 | } |
| 452 | } | 504 | } |
| 453 | 505 | ||
| 454 | - // Initialize WebSocket connection when document is ready | 506 | + // 初始化 WebSocket 连接 |
| 455 | connectWebSocket(); | 507 | connectWebSocket(); |
| 456 | }); | 508 | }); |
| 457 | </script> | 509 | </script> |
-
Please register or login to post a comment