小橄榄界面优化
@ -83,5 +83,6 @@ async def main():
 | 
				
			|||||||
    worker_task = asyncio.create_task(worker())
 | 
					    worker_task = asyncio.create_task(worker())
 | 
				
			||||||
    await worker_task
 | 
					    await worker_task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 使用 asyncio 运行主函数
 | 
					# 使用 asyncio 获取事件循环并运行主函数
 | 
				
			||||||
asyncio.run(main())
 | 
					loop = asyncio.get_event_loop()
 | 
				
			||||||
 | 
					loop.run_until_complete(main())
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								config.json
									
									
									
									
									
								
							
							
						
						@ -1,32 +1,32 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "attribute": {
 | 
					    "attribute": {
 | 
				
			||||||
        "age": "\u6210\u5e74",
 | 
					        "age": "\u5e7c\u5e74",
 | 
				
			||||||
        "birth": "Github",
 | 
					        "birth": "\u5195\u5b81\u5143\u5347\u519c\u4e1a",
 | 
				
			||||||
        "constellation": "\u6c34\u74f6\u5ea7",
 | 
					        "constellation": "\u6a44\u6984\u661f",
 | 
				
			||||||
        "contact": "qq467665317",
 | 
					        "contact": "\u5929\u7801\u56e2\u961f",
 | 
				
			||||||
        "gender": "\u5973",
 | 
					        "gender": "\u5973",
 | 
				
			||||||
        "hobby": "\u53d1\u5446",
 | 
					        "hobby": "\u53d1\u5446",
 | 
				
			||||||
        "job": "\u52a9\u7406",
 | 
					        "job": "\u5c0f\u52a9\u624b",
 | 
				
			||||||
        "name": "\u83f2\u83f2",
 | 
					        "name": "\u5c0f\u6a44\u6984",
 | 
				
			||||||
        "voice": "\u6653\u6653(edge)",
 | 
					        "voice": "\u6653\u6653(edge)",
 | 
				
			||||||
        "zodiac": "\u86c7"
 | 
					        "zodiac": "\u6a44\u6984"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "interact": {
 | 
					    "interact": {
 | 
				
			||||||
        "QnA": "qa.csv",
 | 
					        "QnA": "qa.csv",
 | 
				
			||||||
        "maxInteractTime": 15,
 | 
					        "maxInteractTime": 15,
 | 
				
			||||||
        "perception": {
 | 
					        "perception": {
 | 
				
			||||||
            "chat": 10,
 | 
					            "chat": 20,
 | 
				
			||||||
            "follow": 10,
 | 
					            "follow": 20,
 | 
				
			||||||
            "gift": 10,
 | 
					            "gift": 20,
 | 
				
			||||||
            "indifferent": 10,
 | 
					            "indifferent": 20,
 | 
				
			||||||
            "join": 10
 | 
					            "join": 20
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "playSound": false,
 | 
					        "playSound": true,
 | 
				
			||||||
        "visualization": false
 | 
					        "visualization": false
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "items": [],
 | 
					    "items": [],
 | 
				
			||||||
    "source": {
 | 
					    "source": {
 | 
				
			||||||
        "automatic_player_status": false,
 | 
					        "automatic_player_status": true,
 | 
				
			||||||
        "automatic_player_url": "http://127.0.0.1:6000",
 | 
					        "automatic_player_url": "http://127.0.0.1:6000",
 | 
				
			||||||
        "liveRoom": {
 | 
					        "liveRoom": {
 | 
				
			||||||
            "enabled": true,
 | 
					            "enabled": true,
 | 
				
			||||||
@ -34,10 +34,10 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "record": {
 | 
					        "record": {
 | 
				
			||||||
            "device": "",
 | 
					            "device": "",
 | 
				
			||||||
            "enabled": false
 | 
					            "enabled": true
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "wake_word": "\u4f60\u597d",
 | 
					        "wake_word": "\u5c0f\u6a44\u6984",
 | 
				
			||||||
        "wake_word_enabled": false,
 | 
					        "wake_word_enabled": true,
 | 
				
			||||||
        "wake_word_type": "front"
 | 
					        "wake_word_type": "front"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -220,7 +220,7 @@ class WebServer(MyServer):
 | 
				
			|||||||
        pass
 | 
					        pass
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def on_connect_handler(self):
 | 
					    def on_connect_handler(self):
 | 
				
			||||||
        self.add_cmd({"panelMsg": "使用提示:Fay可以独立使用,启动数字人将自动对接。"})
 | 
					        self.add_cmd({"panelMsg": "和我说“小橄榄”+你的问题来和我互动哦"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_send_handler(self, message):
 | 
					    def on_send_handler(self, message):
 | 
				
			||||||
        return message
 | 
					        return message
 | 
				
			||||||
 | 
				
			|||||||
@ -23,12 +23,15 @@ from core.interact import Interact
 | 
				
			|||||||
from core import member_db
 | 
					from core import member_db
 | 
				
			||||||
import fay_booter
 | 
					import fay_booter
 | 
				
			||||||
from flask_httpauth import HTTPBasicAuth
 | 
					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
 | 
					from core import qa_service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__app = Flask(__name__)
 | 
					__app = Flask(__name__)
 | 
				
			||||||
auth = HTTPBasicAuth()
 | 
					auth = HTTPBasicAuth()
 | 
				
			||||||
CORS(__app, supports_credentials=True)
 | 
					CORS(__app, supports_credentials=True)
 | 
				
			||||||
 | 
					socketio = SocketIO(__app, async_mode='gevent', cors_allowed_origins="*")
 | 
				
			||||||
def load_users():
 | 
					def load_users():
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with open('verifier.json') as f:
 | 
					        with open('verifier.json') as f:
 | 
				
			||||||
@ -434,6 +437,61 @@ def text_chunks(text, chunk_size=20):
 | 
				
			|||||||
    for chunk in chunks:
 | 
					    for chunk in chunks:
 | 
				
			||||||
        yield chunk
 | 
					        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'])
 | 
					@__app.route('/', methods=['get'])
 | 
				
			||||||
@auth.login_required
 | 
					@auth.login_required
 | 
				
			||||||
def home_get():
 | 
					def home_get():
 | 
				
			||||||
@ -458,6 +516,10 @@ def setting():
 | 
				
			|||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return f"Error loading settings page: {e}", 500
 | 
					        return f"Error loading settings page: {e}", 500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@__app.route('/chat')
 | 
				
			||||||
 | 
					def chat():
 | 
				
			||||||
 | 
					    return render_template('chat.html')
 | 
				
			||||||
# 输出的音频http
 | 
					# 输出的音频http
 | 
				
			||||||
@__app.route('/audio/<filename>')
 | 
					@__app.route('/audio/<filename>')
 | 
				
			||||||
def serve_audio(filename):
 | 
					def serve_audio(filename):
 | 
				
			||||||
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 1.6 MiB  | 
							
								
								
									
										132
									
								
								gui/static/css/chat.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -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; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 525 KiB  | 
| 
		 Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 MiB  | 
| 
		 Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 9.5 KiB  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 1.6 MiB  | 
							
								
								
									
										206
									
								
								gui/static/js/chat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -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',
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }}})
 | 
				
			||||||
							
								
								
									
										45
									
								
								gui/templates/chat.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="zh-CN">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <title>聊天窗口</title>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="{{ url_for('static', filename='css/chat.css') }}">
 | 
				
			||||||
 | 
					    <!-- 引入 Socket.IO 客户端库 -->
 | 
				
			||||||
 | 
					    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.min.js" integrity="sha512-OE+tNYgJBSy/R8gSSzIjcC+3hkT0lj3cPnOeYv50E6n0Pzv1tq9I7YytCTv19Uaz1Lr5un2ngc7IQXBuT7GqdQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
 | 
				
			||||||
 | 
					    <!-- 引入 Vue.js -->
 | 
				
			||||||
 | 
					    <script src="{{ url_for('static', filename='js/vue.js') }}"></script>
 | 
				
			||||||
 | 
					    <!-- 引入 Element UI -->
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="{{ url_for('static', filename='css/element/theme-chalk.css') }}" />
 | 
				
			||||||
 | 
					    <script src="{{ url_for('static', filename='js/element-ui.js') }}"></script>
 | 
				
			||||||
 | 
					    <!-- 引入 chat.js -->
 | 
				
			||||||
 | 
					    <script src="{{ url_for('static', filename='js/chat.js') }}" defer></script>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div id="chat-app">
 | 
				
			||||||
 | 
					        <div id="chat-widget">
 | 
				
			||||||
 | 
					            <button id="toggle-chat" @click="toggleChat">打开聊天</button>
 | 
				
			||||||
 | 
					            <div id="chat-container" class="hidden">
 | 
				
			||||||
 | 
					                <div id="messages">
 | 
				
			||||||
 | 
					                    <!-- 消息气泡将动态添加到这里 -->
 | 
				
			||||||
 | 
					                    <div v-for="(message, index) in messages" :key="index" class="message" :class="message.type">
 | 
				
			||||||
 | 
					                        <div class="bubble">
 | 
				
			||||||
 | 
					                            [[ message.content ]]
 | 
				
			||||||
 | 
					                            <div class="message-time">[[ message.timetext ]]</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div id="status-indicator">[[ status ]]</div>
 | 
				
			||||||
 | 
					                <div id="input-area">
 | 
				
			||||||
 | 
					                    <button id="record-btn" @click="toggleRecording">
 | 
				
			||||||
 | 
					                        <img id="record-icon" :src="isRecording ? '/static/images/recording.png' : '/static/images/record.png'" alt="录音">
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    <input type="text" id="message-input" placeholder="请输入内容..." v-model="newMessage">
 | 
				
			||||||
 | 
					                    <button id="send-btn" @click="sendMessage">
 | 
				
			||||||
 | 
					                        <img src="{{ url_for('static', filename='images/send.png') }}" alt="发送">
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
    <meta charset="UTF-8" />
 | 
					    <meta charset="UTF-8" />
 | 
				
			||||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
    <title>Fay数字人</title>
 | 
					    <title>小橄榄</title>
 | 
				
			||||||
    <link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}" />
 | 
					    <link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}" />
 | 
				
			||||||
    <link rel="icon" href="{{ url_for('static',filename='images/favicon.ico') }}" type="image/x-icon">
 | 
					    <link rel="icon" href="{{ url_for('static',filename='images/favicon.ico') }}" type="image/x-icon">
 | 
				
			||||||
    <script src="{{ url_for('static',filename='js/vue.js') }}"></script>
 | 
					    <script src="{{ url_for('static',filename='js/vue.js') }}"></script>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										31
									
								
								qa.csv
									
									
									
									
									
								
							
							
						
						@ -1 +1,30 @@
 | 
				
			|||||||
你好,你好!有什么我可以帮助你的吗?
 | 
					你好,你好,我是小橄榄!有什么我可以帮助你的吗
 | 
				
			||||||
 | 
					我们现在在哪里,我们现在在冕宁元升农业的展览厅,可以参观游览我们先进的油橄榄产业园区哦!
 | 
				
			||||||
 | 
					介绍一下基地,元升集团油橄榄种植基地是中国目前最大的油橄榄种植庄园,目前整个庄园面积已接近30000亩。
 | 
				
			||||||
 | 
					介绍一下元升集团,2011年,冕宁元升农业董事长林春福跟随周恩来总理的脚步,在冕宁地区开启了油橄榄庄园打造之路。经过十年的发展,冕宁元升农业目前已成为国家林业局示范基地、国家林业重点龙头企业、四川省第一种植庄园、四川省脱贫标杆企业,获得各种荣誉奖项200余项。
 | 
				
			||||||
 | 
					油橄榄的根系主要由哪几部分构成,油橄榄的根系主要由主根、侧根和须根组成。
 | 
				
			||||||
 | 
					油橄榄的侧根有什么作用,油橄榄的侧根从主根分出,主要功能是扩展根系范围,帮助吸收水分和养分。
 | 
				
			||||||
 | 
					须根根据什么不同分为哪两种类型,须根按其功能与结构不同分为两种类型。
 | 
				
			||||||
 | 
					吸收根的寿命受哪些因素影响,吸收根的寿命受土壤类型、水分供应、养分供应和温度等因素的影响。
 | 
				
			||||||
 | 
					水分供应对吸收根的寿命有何影响,适当的水分供应有助于维持根系的健康和生长,而过多的水分或水分不足都可能导致根系受损,缩短吸收根的寿命。
 | 
				
			||||||
 | 
					油橄榄的须根在沙壤土中主要分布在哪个深度,油橄榄的须根主要分布在20-40厘米的土层。
 | 
				
			||||||
 | 
					在沙壤土中栽培油橄榄时,应该注意什么,应注意保持适宜的土壤湿度和良好的排水条件,以支持根系健康发展。
 | 
				
			||||||
 | 
					油橄榄的根系在黏土中的生长形态是怎样的,油橄榄的根系在黏土中通常呈现较深且分布广泛的生长形态。
 | 
				
			||||||
 | 
					油橄榄的根系在黏土中的生长形态对其栽培有什么影响,油橄榄的根系在黏土中的深入生长有助于稳定植株,提高其抗旱能力和吸收养分的能力,有利于油橄榄的健康生长。
 | 
				
			||||||
 | 
					土壤黏粒含量对油橄榄根系生长有何影响,土壤黏粒含量会影响土壤的排水性和通气性,进而影响油橄榄根系的生长。
 | 
				
			||||||
 | 
					如何改善土壤黏粒含量以促进油橄榄根系的健康生长,可以通过改良土壤结构、增加有机质含量等方法来改善土壤黏粒含量,从而提高土壤的排水性和通气性,促进油橄榄根系的健康生长。
 | 
				
			||||||
 | 
					未经改良的黏土种植油橄榄对产量有何影响,未经改良的黏土种植油橄榄通常会导致产量降低,因为黏重土壤排水不良,根部氧气供应不足。
 | 
				
			||||||
 | 
					黏土种植油橄榄的经济效果如何,黏土种植油橄榄的经济效果不佳,因为土壤条件不利于树木生长和果实发育,可能导致较低的产量和品质下降。
 | 
				
			||||||
 | 
					油橄榄在粉沙黏土中的生长结果如何,油橄榄在粉沙黏土中的结果情况良好,能够正常生长。
 | 
				
			||||||
 | 
					油橄榄适合在哪种土壤中生长,油橄榄适合在粉沙黏土中生长,这种土壤有利于其生长发育。
 | 
				
			||||||
 | 
					土壤的哪个物理因子对油橄榄的生长至关重要,土壤的排水性能是影响油橄榄生长的关键物理因子之一。
 | 
				
			||||||
 | 
					在油橄榄栽培中,土壤的哪个特性需要特别关注,在油橄榄栽培中,需要特别关注土壤的通气性,因为它直接影响到根部的氧气供应和整体生长状况。
 | 
				
			||||||
 | 
					土壤通气孔隙度对油橄榄生长有什么影响,土壤通气孔隙度直接影响油橄榄的根系呼吸和养分吸收,进而影响其生长。
 | 
				
			||||||
 | 
					如何改善土壤通气孔隙度以利于油橄榄栽培,可以通过添加有机质、定期翻耕和避免土壤板结等方法来改善土壤通气孔隙度,从而优化油橄榄的生长环境。
 | 
				
			||||||
 | 
					油橄榄生长需要什么样的土壤条件,油橄榄生长主要受土壤的排水性能、pH值和肥力的影响。
 | 
				
			||||||
 | 
					油橄榄对土壤的pH值有什么要求,油橄榄偏好中性到微碱性的土壤,pH值在6.0到8.5之间较为适宜。
 | 
				
			||||||
 | 
					土壤渗透性对油橄榄生长有何影响,土壤渗透性影响水分和养分的供应,进而影响油橄榄的生长。
 | 
				
			||||||
 | 
					如何改善土壤的渗透性以利于油橄榄栽培,通过增加有机质含量、避免土壤压实以及使用适当的排水方法可以改善土壤渗透性。
 | 
				
			||||||
 | 
					如何改善土壤坚实度过高对油橄榄须根的影响,在土壤坚实度超过4.5kg/cm²的情况下,油橄榄的须根会受到影响。可以通过松土、增加有机质含量等方法来降低土壤坚实度,从而改善油橄榄须根的生长环境。
 | 
				
			||||||
 | 
					小橄榄介绍一下元升集团,2011年,冕宁元升农业董事长林春福跟随周恩来总理的脚步,在冕宁地区开启了油橄榄庄园打造之路。经过十年的发展,冕宁元升农业目前已成为国家林业局示范基地、国家林业重点龙头企业、四川省第一种植庄园、四川省脱贫标杆企业,获得各种荣誉奖项200余项。
 | 
				
			||||||
 | 
					这里的油橄榄有哪些品种,有柯基、阿布桑娜、豆果、克拉蒂这几种主要品种在园区种植
 | 
				
			||||||
		
		
			
  | 
@ -3,7 +3,6 @@ numpy
 | 
				
			|||||||
pyaudio~=0.2.11
 | 
					pyaudio~=0.2.11
 | 
				
			||||||
websockets~=10.2
 | 
					websockets~=10.2
 | 
				
			||||||
ws4py~=0.5.1
 | 
					ws4py~=0.5.1
 | 
				
			||||||
PyQt5==5.15.10
 | 
					 | 
				
			||||||
PyQt5-sip==12.13.0
 | 
					PyQt5-sip==12.13.0
 | 
				
			||||||
PyQtWebEngine==5.15.6 
 | 
					PyQtWebEngine==5.15.6 
 | 
				
			||||||
flask~=3.0.0
 | 
					flask~=3.0.0
 | 
				
			||||||
 | 
				
			|||||||