diff --git a/asr/funasr/ASR_server.py b/asr/funasr/ASR_server.py index c947cbd..bf57212 100644 --- a/asr/funasr/ASR_server.py +++ b/asr/funasr/ASR_server.py @@ -83,5 +83,6 @@ async def main(): worker_task = asyncio.create_task(worker()) await worker_task -# 使用 asyncio 运行主函数 -asyncio.run(main()) +# 使用 asyncio 获取事件循环并运行主函数 +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/cache_data/input.wav b/cache_data/input.wav index 5fb6be6..f990e01 100644 Binary files a/cache_data/input.wav and b/cache_data/input.wav differ diff --git a/config.json b/config.json index a563dc6..9e1d5d0 100644 --- a/config.json +++ b/config.json @@ -1,32 +1,32 @@ { "attribute": { - "age": "\u6210\u5e74", - "birth": "Github", - "constellation": "\u6c34\u74f6\u5ea7", - "contact": "qq467665317", + "age": "\u5e7c\u5e74", + "birth": "\u5195\u5b81\u5143\u5347\u519c\u4e1a", + "constellation": "\u6a44\u6984\u661f", + "contact": "\u5929\u7801\u56e2\u961f", "gender": "\u5973", "hobby": "\u53d1\u5446", - "job": "\u52a9\u7406", - "name": "\u83f2\u83f2", + "job": "\u5c0f\u52a9\u624b", + "name": "\u5c0f\u6a44\u6984", "voice": "\u6653\u6653(edge)", - "zodiac": "\u86c7" + "zodiac": "\u6a44\u6984" }, "interact": { "QnA": "qa.csv", "maxInteractTime": 15, "perception": { - "chat": 10, - "follow": 10, - "gift": 10, - "indifferent": 10, - "join": 10 + "chat": 20, + "follow": 20, + "gift": 20, + "indifferent": 20, + "join": 20 }, - "playSound": false, + "playSound": true, "visualization": false }, "items": [], "source": { - "automatic_player_status": false, + "automatic_player_status": true, "automatic_player_url": "http://127.0.0.1:6000", "liveRoom": { "enabled": true, @@ -34,10 +34,10 @@ }, "record": { "device": "", - "enabled": false + "enabled": true }, - "wake_word": "\u4f60\u597d", - "wake_word_enabled": false, + "wake_word": "\u5c0f\u6a44\u6984", + "wake_word_enabled": true, "wake_word_type": "front" } } \ No newline at end of file diff --git a/core/wsa_server.py b/core/wsa_server.py index 18259e6..3769f69 100644 --- a/core/wsa_server.py +++ b/core/wsa_server.py @@ -220,7 +220,7 @@ class WebServer(MyServer): pass def on_connect_handler(self): - self.add_cmd({"panelMsg": "使用提示:Fay可以独立使用,启动数字人将自动对接。"}) + self.add_cmd({"panelMsg": "和我说“小橄榄”+你的问题来和我互动哦"}) def on_send_handler(self, message): return message diff --git a/gui/flask_server.py b/gui/flask_server.py index e349f80..44325e9 100644 --- a/gui/flask_server.py +++ b/gui/flask_server.py @@ -23,12 +23,15 @@ from core.interact import Interact from core import member_db import fay_booter from flask_httpauth import HTTPBasicAuth +from flask import Flask, render_template, request, jsonify, Response, send_file +from flask_cors import CORS +from flask_socketio import SocketIO, emit from core import qa_service __app = Flask(__name__) auth = HTTPBasicAuth() CORS(__app, supports_credentials=True) - +socketio = SocketIO(__app, async_mode='gevent', cors_allowed_origins="*") def load_users(): try: with open('verifier.json') as f: @@ -434,6 +437,61 @@ def text_chunks(text, chunk_size=20): for chunk in chunks: yield chunk +messages = [] + +@socketio.on('send_message') +def handle_send_message(data): + """ + 处理来自客户端的消息。 + data: { + 'username': '用户或系统', + 'message': '消息内容', + 'type': 'user' 或 'system' + } + """ + timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + message = { + 'id': len(messages) + 1, + 'username': data.get('username', '用户'), + 'content': data.get('message', ''), + 'type': data.get('type', 'user'), + 'timetext': timestamp, + 'is_adopted': 1 # 根据需要调整 + } + messages.append(message) + + # 只保留最近的100条消息以防内存溢出 + if len(messages) > 100: + messages.pop(0) + + # 向所有客户端广播新消息 + emit('receive_message', message, broadcast=True) + + # 系统自动回复(可根据实际需求调整) + system_message_content = f"您说的是:{data.get('message', '')}" + system_message = { + 'id': len(messages) + 1, + 'username': '系统', + 'content': system_message_content, + 'type': 'system', + 'timetext': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), + 'is_adopted': 1 + } + messages.append(system_message) + + emit('receive_message', system_message, broadcast=True) + +@socketio.on('connect') +def handle_connect(): + print('客户端已连接') + # 发送最近三条消息给新连接的客户端 + recent_messages = messages[-3:] + emit('load_messages', recent_messages) + +@socketio.on('disconnect') +def handle_disconnect(): + print('客户端已断开连接') + @__app.route('/', methods=['get']) @auth.login_required def home_get(): @@ -458,6 +516,10 @@ def setting(): except Exception as e: return f"Error loading settings page: {e}", 500 + +@__app.route('/chat') +def chat(): + return render_template('chat.html') # 输出的音频http @__app.route('/audio/') def serve_audio(filename): diff --git a/gui/robot/Normal.jpg b/gui/robot/Normal.jpg index f83e9f4..995b9dc 100644 Binary files a/gui/robot/Normal.jpg and b/gui/robot/Normal.jpg differ diff --git a/gui/static/css/chat.css b/gui/static/css/chat.css new file mode 100644 index 0000000..569967e --- /dev/null +++ b/gui/static/css/chat.css @@ -0,0 +1,132 @@ +/* static/css/chat.css */ + +#chat-widget { + position: fixed; + bottom: 20px; + right: 20px; + width: 300px; + font-family: Arial, sans-serif; + z-index: 1000; +} + +#toggle-chat { + width: 100%; + padding: 10px; + background-color: #00f0ff; + border: none; + color: white; + cursor: pointer; + font-size: 16px; + border-radius: 5px 5px 0 0; +} + +#toggle-chat:hover { + background-color: #00c0d1; +} + +#chat-container { + border: 1px solid #ccc; + border-top: none; + background-color: white; + border-radius: 0 0 10px 10px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + display: flex; + flex-direction: column; + height: 400px; + overflow: hidden; +} + +.hidden { + display: none; +} + +#messages { + flex: 1; + padding: 10px; + overflow-y: auto; + background-color: #f9f9f9; +} + +.message { + display: flex; + margin-bottom: 10px; +} + +.message.user { + justify-content: flex-end; +} + +.message.system { + justify-content: center; +} + +.message .bubble { + max-width: 70%; + padding: 10px; + border-radius: 10px; + position: relative; +} + +.message.user .bubble { + background-color: #d1e7dd; + text-align: right; +} + +.message.system .bubble { + background-color: #f1f1f1; + text-align: center; +} + +#status-indicator { + height: 20px; + text-align: center; + color: #555; + font-size: 14px; +} + +#input-area { + display: flex; + align-items: center; + padding: 10px; + border-top: 1px solid #ccc; + background-color: #fafafa; +} + +#input-area button { + background: none; + border: none; + cursor: pointer; + margin-right: 10px; +} + +#input-area img { + width: 24px; + height: 24px; +} + +#message-input { + flex: 1; + padding: 8px; + border: 1px solid #ccc; + border-radius: 5px; +} + +#message-input:disabled { + background-color: #e0e0e0; +} + +/* 渐隐动画 */ +.fade-out { + opacity: 0; + transition: opacity 0.5s ease-out; +} + +/* 为消息气泡添加淡入效果 */ +.bubble { + animation: fadeIn 0.5s; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} diff --git a/gui/static/images/Bg_pic.png b/gui/static/images/Bg_pic.png deleted file mode 100644 index 3591915..0000000 Binary files a/gui/static/images/Bg_pic.png and /dev/null differ diff --git a/gui/static/images/Fay_send.png b/gui/static/images/Fay_send.png index cd571a0..995b9dc 100644 Binary files a/gui/static/images/Fay_send.png and b/gui/static/images/Fay_send.png differ diff --git a/gui/static/images/Logo.png b/gui/static/images/Logo.png index e1d35f2..9de786d 100644 Binary files a/gui/static/images/Logo.png and b/gui/static/images/Logo.png differ diff --git a/gui/static/images/Normal.gif b/gui/static/images/Normal.gif index ddb1756..995b9dc 100644 Binary files a/gui/static/images/Normal.gif and b/gui/static/images/Normal.gif differ diff --git a/gui/static/js/chat.js b/gui/static/js/chat.js new file mode 100644 index 0000000..bfde505 --- /dev/null +++ b/gui/static/js/chat.js @@ -0,0 +1,206 @@ +// static/js/chat.js + +new Vue({ + el: '#chat-app', + delimiters: ['[[', ']]'], + data() { + return { + socket: null, + messages: [], + newMessage: '', + status: '', // 用于显示“聆听中...”或“思考中...” + isRecording: false, + recognition: null + }; + }, + created() { + this.initSocket(); + this.initSpeechRecognition(); + this.startLive(); // 页面加载后自动启动 live 模式 + }, + methods: { + initSocket() { + // 初始化 SocketIO 连接 + this.socket = io.connect('http://' + document.domain + ':' + location.port + '/'); + + this.socket.on('connect', () => { + console.log('Connected to SocketIO server'); + }); + + this.socket.on('receive_message', (data) => { + this.addMessage(data.type, data.content, data.username, data.timetext); + }); + + this.socket.on('load_messages', (data) => { + data.forEach(message => { + this.addMessage(message.type, message.content, message.username, message.timetext); + }); + }); + + this.socket.on('disconnect', () => { + console.log('Disconnected from SocketIO server'); + }); + }, + initSpeechRecognition() { + if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + this.recognition = new SpeechRecognition(); + this.recognition.lang = 'zh-CN'; + this.recognition.interimResults = false; + this.recognition.maxAlternatives = 1; + + this.recognition.onstart = () => { + this.isRecording = true; + this.status = '聆听中...'; + this.$set(this, 'recordIconSrc', '/static/images/recording.png'); + }; + + this.recognition.onresult = (event) => { + const transcript = event.results[0][0].transcript; + this.addMessage('user', transcript, '您'); + this.socket.emit('send_message', { + username: '您', + message: transcript, + type: 'user' + }); + this.status = '思考中...'; + setTimeout(() => { + this.status = ''; + }, 2000); + }; + + this.recognition.onerror = (event) => { + console.error('语音识别错误:', event.error); + this.status = '语音识别失败'; + this.isRecording = false; + this.$set(this, 'recordIconSrc', '/static/images/record.png'); + }; + + this.recognition.onend = () => { + this.isRecording = false; + this.$set(this, 'recordIconSrc', '/static/images/record.png'); + }; + } else { + alert('当前浏览器不支持语音识别功能'); + } + }, + toggleChat() { + const chatContainer = document.getElementById('chat-container'); + const toggleButton = document.getElementById('toggle-chat'); + if (chatContainer.classList.contains('hidden')) { + chatContainer.classList.remove('hidden'); + toggleButton.textContent = '关闭聊天'; + } else { + chatContainer.classList.add('hidden'); + toggleButton.textContent = '打开聊天'; + } + }, + toggleRecording() { + if (this.recognition) { + if (!this.isRecording) { + this.recognition.start(); + } else { + this.recognition.stop(); + } + } + }, + sendMessage() { + const message = this.newMessage.trim(); + if (message === '') return; + this.addMessage('user', message, '您'); + this.socket.emit('send_message', { + username: '您', + message: message, + type: 'user' + }); + this.newMessage = ''; + this.status = '思考中...'; + setTimeout(() => { + this.status = ''; + }, 2000); + }, + addMessage(type, content, username, timestamp) { + const message = { type, content, username, timetext: timestamp }; + this.messages.push(message); + // 只保留最近三条消息 + if (this.messages.length > 3) { + this.messages.shift(); + } + // 滚动到最新消息 + this.$nextTick(() => { + const messagesDiv = document.getElementById('messages'); + if (messagesDiv) { + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + }); + }, + getCurrentTime() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + }, + startLive() { + // 调用启动 live 模式的 API + fetch('/api/start-live', { + method: 'POST' + }) + .then(response => response.json()) + .then(data => { + if (data.result === 'successful') { + this.$notify({ + title: '成功', + message: '已开启 live 模式!', + type: 'success', + }); + } else { + this.$notify({ + title: '失败', + message: data.message || '启动 live 模式失败', + type: 'error', + }); + } + }) + .catch(error => { + console.error('启动 live 模式时出错:', error); + this.$notify({ + title: '错误', + message: '启动 live 模式时出错', + type: 'error', + }); + }); + }, + stopLive() { + // 调用停止 live 模式的 API + fetch('/api/stop-live', { + method: 'POST' + }) + .then(response => response.json()) + .then(data => { + if (data.result === 'successful') { + this.$notify({ + title: '成功', + message: '已关闭 live 模式!', + type: 'success', + }); + } else { + this.$notify({ + title: '失败', + message: data.message || '停止 live 模式失败', + type: 'error', + }); + } + }) + .catch(error => { + console.error('停止 live 模式时出错:', error); + this.$notify({ + title: '错误', + message: '停止 live 模式时出错', + type: 'error', + }); + }); + }}}) diff --git a/gui/templates/chat.html b/gui/templates/chat.html new file mode 100644 index 0000000..38222cb --- /dev/null +++ b/gui/templates/chat.html @@ -0,0 +1,45 @@ + + + + + 聊天窗口 + + + + + + + + + + + + +
+
+ + +
+
+ + diff --git a/gui/templates/index.html b/gui/templates/index.html index 3358141..ee5a4ff 100644 --- a/gui/templates/index.html +++ b/gui/templates/index.html @@ -4,7 +4,7 @@ - Fay数字人 + 小橄榄 diff --git a/qa.csv b/qa.csv index 6fd3715..2fbd4bb 100644 --- a/qa.csv +++ b/qa.csv @@ -1 +1,30 @@ -你好,你好!有什么我可以帮助你的吗? \ No newline at end of file +你好,你好,我是小橄榄!有什么我可以帮助你的吗 +我们现在在哪里,我们现在在冕宁元升农业的展览厅,可以参观游览我们先进的油橄榄产业园区哦! +介绍一下基地,元升集团油橄榄种植基地是中国目前最大的油橄榄种植庄园,目前整个庄园面积已接近30000亩。 +介绍一下元升集团,2011年,冕宁元升农业董事长林春福跟随周恩来总理的脚步,在冕宁地区开启了油橄榄庄园打造之路。经过十年的发展,冕宁元升农业目前已成为国家林业局示范基地、国家林业重点龙头企业、四川省第一种植庄园、四川省脱贫标杆企业,获得各种荣誉奖项200余项。 +油橄榄的根系主要由哪几部分构成,油橄榄的根系主要由主根、侧根和须根组成。 +油橄榄的侧根有什么作用,油橄榄的侧根从主根分出,主要功能是扩展根系范围,帮助吸收水分和养分。 +须根根据什么不同分为哪两种类型,须根按其功能与结构不同分为两种类型。 +吸收根的寿命受哪些因素影响,吸收根的寿命受土壤类型、水分供应、养分供应和温度等因素的影响。 +水分供应对吸收根的寿命有何影响,适当的水分供应有助于维持根系的健康和生长,而过多的水分或水分不足都可能导致根系受损,缩短吸收根的寿命。 +油橄榄的须根在沙壤土中主要分布在哪个深度,油橄榄的须根主要分布在20-40厘米的土层。 +在沙壤土中栽培油橄榄时,应该注意什么,应注意保持适宜的土壤湿度和良好的排水条件,以支持根系健康发展。 +油橄榄的根系在黏土中的生长形态是怎样的,油橄榄的根系在黏土中通常呈现较深且分布广泛的生长形态。 +油橄榄的根系在黏土中的生长形态对其栽培有什么影响,油橄榄的根系在黏土中的深入生长有助于稳定植株,提高其抗旱能力和吸收养分的能力,有利于油橄榄的健康生长。 +土壤黏粒含量对油橄榄根系生长有何影响,土壤黏粒含量会影响土壤的排水性和通气性,进而影响油橄榄根系的生长。 +如何改善土壤黏粒含量以促进油橄榄根系的健康生长,可以通过改良土壤结构、增加有机质含量等方法来改善土壤黏粒含量,从而提高土壤的排水性和通气性,促进油橄榄根系的健康生长。 +未经改良的黏土种植油橄榄对产量有何影响,未经改良的黏土种植油橄榄通常会导致产量降低,因为黏重土壤排水不良,根部氧气供应不足。 +黏土种植油橄榄的经济效果如何,黏土种植油橄榄的经济效果不佳,因为土壤条件不利于树木生长和果实发育,可能导致较低的产量和品质下降。 +油橄榄在粉沙黏土中的生长结果如何,油橄榄在粉沙黏土中的结果情况良好,能够正常生长。 +油橄榄适合在哪种土壤中生长,油橄榄适合在粉沙黏土中生长,这种土壤有利于其生长发育。 +土壤的哪个物理因子对油橄榄的生长至关重要,土壤的排水性能是影响油橄榄生长的关键物理因子之一。 +在油橄榄栽培中,土壤的哪个特性需要特别关注,在油橄榄栽培中,需要特别关注土壤的通气性,因为它直接影响到根部的氧气供应和整体生长状况。 +土壤通气孔隙度对油橄榄生长有什么影响,土壤通气孔隙度直接影响油橄榄的根系呼吸和养分吸收,进而影响其生长。 +如何改善土壤通气孔隙度以利于油橄榄栽培,可以通过添加有机质、定期翻耕和避免土壤板结等方法来改善土壤通气孔隙度,从而优化油橄榄的生长环境。 +油橄榄生长需要什么样的土壤条件,油橄榄生长主要受土壤的排水性能、pH值和肥力的影响。 +油橄榄对土壤的pH值有什么要求,油橄榄偏好中性到微碱性的土壤,pH值在6.0到8.5之间较为适宜。 +土壤渗透性对油橄榄生长有何影响,土壤渗透性影响水分和养分的供应,进而影响油橄榄的生长。 +如何改善土壤的渗透性以利于油橄榄栽培,通过增加有机质含量、避免土壤压实以及使用适当的排水方法可以改善土壤渗透性。 +如何改善土壤坚实度过高对油橄榄须根的影响,在土壤坚实度超过4.5kg/cm²的情况下,油橄榄的须根会受到影响。可以通过松土、增加有机质含量等方法来降低土壤坚实度,从而改善油橄榄须根的生长环境。 +小橄榄介绍一下元升集团,2011年,冕宁元升农业董事长林春福跟随周恩来总理的脚步,在冕宁地区开启了油橄榄庄园打造之路。经过十年的发展,冕宁元升农业目前已成为国家林业局示范基地、国家林业重点龙头企业、四川省第一种植庄园、四川省脱贫标杆企业,获得各种荣誉奖项200余项。 +这里的油橄榄有哪些品种,有柯基、阿布桑娜、豆果、克拉蒂这几种主要品种在园区种植 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e6abf64..4575793 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ numpy pyaudio~=0.2.11 websockets~=10.2 ws4py~=0.5.1 -PyQt5==5.15.10 PyQt5-sip==12.13.0 PyQtWebEngine==5.15.6 flask~=3.0.0