From 8efec0355db1afa8e72ddc454b6ff6fb84ecdf78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=A3=E4=BB=94?= Date: Wed, 6 Nov 2024 18:34:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B9=B4=E7=BF=BB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、qa自动缓存改为手动采纳; 2、socket10001映射到websocket 9001; 3、修复声音沟通接口无法收音问题; 4、修复阿里云不稳定问题; --- asr/ali_nls.py | 3 + asr/funasr.py | 1 + core/content_db.py | 142 +++++++++++++++++++++++----------- core/recorder.py | 11 ++- core/socket_bridge_service.py | 118 ++++++++++++++++++++++++++++ fay_booter.py | 16 ++++ gui/flask_server.py | 93 +++++++++++++++++++++- gui/static/css/index.css | 97 +++++++++++++++++++++-- gui/static/images/adopt.png | Bin 0 -> 1845 bytes gui/static/images/adopted.png | Bin 0 -> 2038 bytes gui/static/js/index.js | 42 ++++++++-- gui/templates/index.html | 10 ++- 12 files changed, 470 insertions(+), 63 deletions(-) create mode 100644 core/socket_bridge_service.py create mode 100644 gui/static/images/adopt.png create mode 100644 gui/static/images/adopted.png diff --git a/asr/ali_nls.py b/asr/ali_nls.py index a41b8f4..df072ca 100644 --- a/asr/ali_nls.py +++ b/asr/ali_nls.py @@ -58,6 +58,7 @@ class ALiNls: self.__URL = 'wss://nls-gateway-cn-shenzhen.aliyuncs.com/ws/v1' self.__ws = None self.__frames = [] + self.started = False self.__closing = False self.__task_id = '' self.done = False @@ -86,6 +87,8 @@ class ALiNls: data = json.loads(message) header = data['header'] name = header['name'] + if name == 'TranscriptionStarted': + self.started = True if name == 'SentenceEnd': self.done = True self.finalResults = data['payload']['result'] diff --git a/asr/funasr.py b/asr/funasr.py index 7f81d38..6e69732 100644 --- a/asr/funasr.py +++ b/asr/funasr.py @@ -27,6 +27,7 @@ class FunASR: self.__reconnect_delay = 1 self.__reconnecting = False self.username = username + self.started = True # 收到websocket消息的处理 diff --git a/core/content_db.py b/core/content_db.py index 313d2a6..9e18323 100644 --- a/core/content_db.py +++ b/core/content_db.py @@ -3,12 +3,13 @@ import time import threading import functools from utils import util + def synchronized(func): - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - with self.lock: - return func(self, *args, **kwargs) - return wrapper + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + with self.lock: + return func(self, *args, **kwargs) + return wrapper __content_tb = None def new_instance(): @@ -18,73 +19,122 @@ def new_instance(): __content_tb.init_db() return __content_tb - class Content_Db: def __init__(self) -> None: self.lock = threading.Lock() - - - #初始化 + # 初始化数据库 def init_db(self): conn = sqlite3.connect('fay.db') c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS T_Msg - (id INTEGER PRIMARY KEY autoincrement, - type char(10), - way char(10), - content TEXT NOT NULL, - createtime Int, - username TEXT DEFAULT 'User', - uid Int);''') + (id INTEGER PRIMARY KEY AUTOINCREMENT, + type CHAR(10), + way CHAR(10), + content TEXT NOT NULL, + createtime INT, + username TEXT DEFAULT 'User', + uid INT);''') + # 对话采纳记录表 + c.execute('''CREATE TABLE IF NOT EXISTS T_Adopted + (id INTEGER PRIMARY KEY AUTOINCREMENT, + msg_id INTEGER UNIQUE, + adopted_time INT, + FOREIGN KEY(msg_id) REFERENCES T_Msg(id));''') conn.commit() conn.close() - - - - #添加对话 + # 添加对话 @synchronized - def add_content(self,type,way,content,username='User',uid=0): + def add_content(self, type, way, content, username='User', uid=0): conn = sqlite3.connect("fay.db") cur = conn.cursor() try: - cur.execute("insert into T_Msg (type,way,content,createtime,username,uid) values (?,?,?,?,?,?)",(type, way, content, time.time(), username, uid)) + cur.execute("INSERT INTO T_Msg (type, way, content, createtime, username, uid) VALUES (?, ?, ?, ?, ?, ?)", + (type, way, content, int(time.time()), username, uid)) conn.commit() - except: - util.log(1, "请检查参数是否有误") - conn.close() - return 0 + last_id = cur.lastrowid + except Exception as e: + util.log(1, "请检查参数是否有误: {}".format(e)) + conn.close() + return 0 conn.close() - return cur.lastrowid - - + return last_id - #获取对话内容 + # 根据ID查询对话记录 @synchronized - def get_list(self,way,order,limit,uid=0): + def get_content_by_id(self, msg_id): + conn = sqlite3.connect("fay.db") + cur = conn.cursor() + cur.execute("SELECT * FROM T_Msg WHERE id = ?", (msg_id,)) + record = cur.fetchone() + conn.close() + return record + + # 添加对话采纳记录 + @synchronized + def adopted_message(self, msg_id): + conn = sqlite3.connect('fay.db') + cur = conn.cursor() + # 检查消息ID是否存在 + cur.execute("SELECT 1 FROM T_Msg WHERE id = ?", (msg_id,)) + if cur.fetchone() is None: + util.log(1, "消息ID不存在") + conn.close() + return False + try: + cur.execute("INSERT INTO T_Adopted (msg_id, adopted_time) VALUES (?, ?)", (msg_id, int(time.time()))) + conn.commit() + except sqlite3.IntegrityError: + util.log(1, "该消息已被采纳") + conn.close() + return False + conn.close() + return True + + # 获取对话内容 + @synchronized + def get_list(self, way, order, limit, uid=0): conn = sqlite3.connect("fay.db") cur = conn.cursor() where_uid = "" if int(uid) != 0: - where_uid = f" and uid = {uid} " - if(way == 'all'): - cur.execute("select type,way,content,createtime,datetime(createtime, 'unixepoch', 'localtime') as timetext,username from T_Msg where 1 "+where_uid+" order by id "+order+" limit ?",(limit,)) - elif(way == 'notappended'): - cur.execute("select type,way,content,createtime,datetime(createtime, 'unixepoch', 'localtime') as timetext,username from T_Msg where way != 'appended' "+where_uid+" order by id "+order+" limit ?",(limit,)) + where_uid = f" AND T_Msg.uid = {uid} " + base_query = f""" + SELECT T_Msg.type, T_Msg.way, T_Msg.content, T_Msg.createtime, + datetime(T_Msg.createtime, 'unixepoch', 'localtime') AS timetext, + T_Msg.username,T_Msg.id, + CASE WHEN T_Adopted.msg_id IS NOT NULL THEN 1 ELSE 0 END AS is_adopted + FROM T_Msg + LEFT JOIN T_Adopted ON T_Msg.id = T_Adopted.msg_id + WHERE 1 {where_uid} + """ + if way == 'all': + query = base_query + f" ORDER BY T_Msg.id {order} LIMIT ?" + cur.execute(query, (limit,)) + elif way == 'notappended': + query = base_query + f" AND T_Msg.way != 'appended' ORDER BY T_Msg.id {order} LIMIT ?" + cur.execute(query, (limit,)) else: - cur.execute("select type,way,content,createtime,datetime(createtime, 'unixepoch', 'localtime') as timetext,username from T_Msg where way = ? "+where_uid+" order by id "+order+" limit ?",(way,limit,)) - + query = base_query + f" AND T_Msg.way = ? ORDER BY T_Msg.id {order} LIMIT ?" + cur.execute(query, (way, limit)) list = cur.fetchall() conn.close() return list + - - - - - - - - + @synchronized + def get_previous_user_message(self, msg_id): + conn = sqlite3.connect("fay.db") + cur = conn.cursor() + cur.execute(""" + SELECT id, type, way, content, createtime, datetime(createtime, 'unixepoch', 'localtime') AS timetext, username + FROM T_Msg + WHERE id < ? AND type != 'fay' + ORDER BY id DESC + LIMIT 1 + """, (msg_id,)) + record = cur.fetchone() + conn.close() + return record diff --git a/core/recorder.py b/core/recorder.py index 82d0b0e..d7c0346 100644 --- a/core/recorder.py +++ b/core/recorder.py @@ -202,7 +202,7 @@ class Recorder: while self.__running: try: record = cfg.config['source']['record'] - if not record['enabled']: + if not record['enabled'] and not self.is_remote: time.sleep(0.1) continue self.is_reading = True @@ -215,7 +215,6 @@ class Recorder: self.__running = False if not data: continue - #是否可以拾音,不可以就掉弃录音 can_listen = True #没有开唤醒,但面板或数字人正在播音时不能拾音 @@ -229,7 +228,7 @@ class Recorder: if can_listen == False:#掉弃录音 data = None continue - + #计算音量是否满足激活拾音 level = audioop.rms(data, 2) if len(self.__history_data) >= 10:#保存激活前的音频,以免信息掉失 @@ -245,9 +244,11 @@ class Recorder: elif history_percentage < self.__dynamic_threshold: self.__dynamic_threshold += (history_percentage - self.__dynamic_threshold) * 1 + #激活拾音 if percentage > self.__dynamic_threshold: last_speaking_time = time.time() + if not self.__processing and not isSpeaking and time.time() - last_mute_time > _ATTACK: isSpeaking = True #用户正在说话 util.printInfo(1, self.username,"聆听中...") @@ -259,7 +260,9 @@ class Recorder: concatenated_audio.clear() self.__aLiNls = self.asrclient() try: - self.__aLiNls.start() + task_id = self.__aLiNls.start() + while not self.__aLiNls.started: + time.sleep(0.01) except Exception as e: print(e) util.printInfo(1, self.username, "aliyun asr 连接受限") diff --git a/core/socket_bridge_service.py b/core/socket_bridge_service.py new file mode 100644 index 0000000..7c87376 --- /dev/null +++ b/core/socket_bridge_service.py @@ -0,0 +1,118 @@ +import asyncio +import websockets +import socket +import threading +import time + +__wss = None + +def new_instance(): + global __wss + if __wss is None: + __wss = SocketBridgeService() + return __wss + +class SocketBridgeService: + def __init__(self): + self.websockets = {} + self.sockets = {} + self.message_queue = asyncio.Queue() + self.running = True + self.server = None + self.event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.event_loop) + + async def handler(self, websocket, path): + ws_id = id(websocket) + self.websockets[ws_id] = websocket + try: + if ws_id not in self.sockets: + self.sockets[ws_id] = await self.create_socket_client() + asyncio.create_task(self.receive_from_socket(ws_id)) + async for message in websocket: + await self.send_to_socket(ws_id, message) + except websockets.ConnectionClosed: + pass + finally: + self.close_socket_client(ws_id) + + async def create_socket_client(self): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', 10001)) + return sock + + async def send_to_socket(self, ws_id, message): + sock = self.sockets.get(ws_id) + if sock: + asyncio.create_task(self.socket_send(sock, message)) + + async def socket_send(self, sock, message): + await asyncio.to_thread(sock.sendall, message) + + async def receive_from_socket(self, ws_id): + sock = self.sockets.get(ws_id) + while True: + data = await asyncio.to_thread(sock.recv, 1024) + if data: + await self.message_queue.put((ws_id, data)) + + async def process_message_queue(self): + while True: + if not self.running: + break + ws_id, data = await self.message_queue.get() + websocket = self.websockets.get(ws_id) + if websocket.open: + await websocket.send(data) + self.message_queue.task_done() + + def close_socket_client(self, ws_id): + sock = self.sockets.pop(ws_id, None) + if sock: + sock.close() + + async def start(self, host='0.0.0.0', port=9001): + self.server = await websockets.serve(self.handler, host, port, loop=self.event_loop) + asyncio.create_task(self.process_message_queue()) + await asyncio.Future() + + async def shutdown(self): + self.running = False + if self.server: + for ws in self.websockets.values(): + await ws.close() + if hasattr(self.server, 'close'): + self.server.close() + await asyncio.gather(*[w.wait_closed() for w in self.websockets.values()]) + for sock in self.sockets.values(): + sock.close() + if self.server: + await self.server.wait_closed() + + def stop_server(self): + print("Stopping server...") + self.event_loop.call_soon_threadsafe(self.shutdown) + self.event_loop.run_until_complete(self.shutdown()) + self.event_loop.close() + + def start_service(self): + self.event_loop.run_until_complete(self.start(host='0.0.0.0', port=9001)) + try: + self.event_loop.run_forever() + except KeyboardInterrupt: + pass + finally: + self.stop_server() + +if __name__ == '__main__': + service = new_instance() + service_thread = threading.Thread(target=service.start_service) + service_thread.start() + + # 等待一些时间或者直到收到停止信号 + try: + while service.running: + time.sleep(1) + except KeyboardInterrupt: + service.stop_server() + service_thread.join() \ No newline at end of file diff --git a/fay_booter.py b/fay_booter.py index b443b90..48340ac 100644 --- a/fay_booter.py +++ b/fay_booter.py @@ -5,6 +5,7 @@ import pyaudio import socket import psutil import sys +import asyncio import requests from core.interact import Interact from core.recorder import Recorder @@ -14,6 +15,7 @@ from utils import util, config_util, stream_util from core.wsa_server import MyServer from scheduler.thread_manager import MyThread from core import wsa_server +from core import socket_bridge_service feiFei: fay_core.FeiFei = None recorderListener: Recorder = None @@ -21,6 +23,8 @@ __running = False deviceSocketServer = None DeviceInputListenerDict = {} ngrok = None +socket_service_instance = None + #启动状态 def is_running(): @@ -324,6 +328,7 @@ def stop(): global __running global DeviceInputListenerDict global ngrok + global socket_service_instance util.log(1, '正在关闭服务...') __running = False @@ -337,6 +342,11 @@ def stop(): value = DeviceInputListenerDict.pop(key) value.stop() deviceSocketServer.close() + # 关闭 socket_bridge_service + if socket_service_instance is not None: + util.log(3, '正在关闭 socket_bridge_service...') + future = asyncio.run_coroutine_threadsafe(socket_service_instance.shutdown(), socket_service_instance.loop) + future.result() # 等待关闭完成 util.log(1, '正在关闭核心服务...') feiFei.stop() util.log(1, '服务已关闭!') @@ -347,6 +357,7 @@ def start(): global feiFei global recorderListener global __running + global socket_service_instance util.log(1, '开启服务...') __running = True @@ -379,6 +390,11 @@ def start(): deviceSocketThread = MyThread(target=accept_audio_device_output_connect) deviceSocketThread.start() + util.log(3, '启动 socket_bridge_service...') + socket_service_instance = socket_bridge_service.new_instance() + socket_bridge_service_Thread = MyThread(target=socket_service_instance.start_service) + socket_bridge_service_Thread.start() + #启动自动播放服务 util.log(1,'启动自动播放服务...') MyThread(target=start_auto_play_service).start() diff --git a/gui/flask_server.py b/gui/flask_server.py index f122f24..93968fc 100644 --- a/gui/flask_server.py +++ b/gui/flask_server.py @@ -22,7 +22,7 @@ from core.interact import Interact from core import member_db import fay_booter from flask_httpauth import HTTPBasicAuth - +from core import qa_service __app = Flask(__name__) auth = HTTPBasicAuth() @@ -233,7 +233,7 @@ def api_get_Msg(): i = len(list)-1 while i >= 0: timetext = datetime.datetime.fromtimestamp(list[i][3]).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] - relist.append(dict(type=list[i][0], way=list[i][1], content=list[i][2], createtime=list[i][3], timetext=timetext, username=list[i][5])) + relist.append(dict(type=list[i][0], way=list[i][1], content=list[i][2], createtime=list[i][3], timetext=timetext, username=list[i][5], id=list[i][6], is_adopted=list[i][7])) i -= 1 if fay_booter.is_running(): wsa_server.get_web_instance().add_cmd({"liveState": 1}) @@ -277,6 +277,95 @@ def api_get_run_status(): status = fay_booter.is_running() return json.dumps({'status': status}) +def handle_audio(content): + try: + config_util.load_config() + if config_util.tts_module == 'ali': + from tts.ali_tss import Speech + elif config_util.tts_module == 'gptsovits': + from tts.gptsovits import Speech + elif config_util.tts_module == 'gptsovits_v3': + from tts.gptsovits_v3 import Speech + elif config_util.tts_module == 'volcano': + from tts.volcano_tts import Speech + else: + from tts.ms_tts_sdk import Speech + + sp = Speech() + sp.connect() + file_url = sp.to_sample(content.replace("*", ""), 'gentle') + audio_path = f'http://{config_util.fay_url}:5000/audio/' + os.path.basename(file_url) + sp.close() + return audio_path + except Exception as e: + print(f"生成音频时出错:{e}") + return None + +@__app.route('/api/adopt_msg', methods=['POST']) +def adopt_msg(): + data = request.get_json() + if not data: + return jsonify({'status':'error', 'msg': '未提供数据'}) + + id = data.get('id') + + if not id: + return jsonify({'status':'error', 'msg': 'id不能为空'}) + + info = content_db.new_instance().get_content_by_id(id) + content = info[3] + if info is not None: + previous_info = content_db.new_instance().get_previous_user_message(id) + previous_content = previous_info[3] + result = content_db.new_instance().adopted_message(id) + if result: + qa_service.QAService().record_qapair(previous_content, content) + return jsonify({'status': 'success', 'msg': '采纳成功'}) + else: + return jsonify({'status':'error', 'msg': '采纳失败'}) + else: + return jsonify({'status':'error', 'msg': '采纳失败'}) + + +@__app.route('/api/generate_video', methods=['POST']) +def generate_video(): + data = request.get_json() + if not data: + return jsonify({'status':'error', 'msg': '未提供数据'}) + + flag = data.get('flag') + content = data.get('content') + audio_path = data.get('audio_path', '') + + if not flag or not content: + return jsonify({'status':'error', 'msg': '标识和内容不能为空'}) + + if audio_path == '': + audio_path = handle_audio(content) + if not audio_path: + return jsonify({'status':'error', 'msg': '音频生成失败,请稍后再试'}) + try: + send_data = { + 'flag': flag, + 'content': content, + 'audio_path': audio_path, + 'human':'num1' + } + response = requests.post('http://127.0.0.1:10010/generate', json=send_data) + response_data = response.json() + + if response.status_code == 201: + return jsonify({'status': 'success', 'msg': '数据生成请求已提交'}), 201 + elif response.status_code == 400: + return jsonify({'status': 'error', 'msg': response_data.get('msg')}), 400 + elif response.status_code == 409: + return jsonify({'status': 'error', 'msg': response_data.get('msg')}), 409 + else: + return jsonify({'status': 'error', 'msg': '生成失败,请稍后再试'}), response.status_code + except requests.exceptions.RequestException: + return jsonify({'status': 'error', 'msg': '生成失败,请稍后再试'}) + + def stream_response(text): def generate(): diff --git a/gui/static/css/index.css b/gui/static/css/index.css index fb9aba7..aac541c 100644 --- a/gui/static/css/index.css +++ b/gui/static/css/index.css @@ -204,19 +204,23 @@ html { .Userchange{ background-color: #FFFFFF; - height: 40px; - font-size: 12px; + height: 40px; + font-size: 12px; + border-top: 1px solid #bed1fc; + width: 1358px; /* 设置菜单容器的宽度,可根据实际情况调整 */ + overflow: hidden; /* 隐藏超出容器的内容 */ + position: relative; } .inputmessage{ - margin-left:15% ; + margin-left:290px ; width: 760px; background: #f9fbff; border-radius: 70px; height: 73px; box-shadow: -10px 0 15px rgba(0, 0, 0, 0.1); position: absolute; - top: 70%; + top: 675px; z-index: 2; } @@ -276,4 +280,87 @@ html { .tag.selected { background-color: #f4f7ff; color: #0064fb; - } \ No newline at end of file + } + #prevButton{background-color: #FFFFFF; border: none; + z-index: 1; + position: absolute; + top: 50%; + transform: translateY(-50%); + } + + #nextButton {background-color: #FFFFFF; border: none; + position: absolute; + top: 50%; + transform: translateY(-50%); + } + + #prevButton { + left: 0; + } + + #nextButton { + right: 0; + } + + .menu-container { + width: 800px; /* 设置菜单容器的宽度,可根据实际情况调整 */ + overflow: hidden; /* 隐藏超出容器的内容 */ + position: relative; + } + + .menu li { + margin-right: 20px; /* 菜单项之间的间距,可调整 */ + } + + .menu li a { + text-decoration: none; + color: black; + } + + .menu { background-color: #FFFFFF; + /* display: flex; */ + white-space: nowrap; + display: flex; + transition: transform 0.3s ease; /* 添加过渡效果,使滑动更平滑 */ + list-style: none; + padding: 0 50px 0 50px; + margin: 0; + display: flex; + transition: transform 0.3s ease; /* 添加过渡效果,使滑动更平滑 */ + } + + .adopt{border: none;background: none;} + + .what-time{vertical-align:top;line-height: 25px;} + + .answer-container { + border: 1px solid #ccc; + padding: 10px; + margin: 10px; + background-color: #f9f9f9; + } + + .adopt-button { + display: inline-block; + cursor: pointer; + position: relative; + } + + .adopt-button img { + width: 21px; + height: 21px; + display: block; + } + + .adopt-button:hover::after { + content: "采纳"; + position: absolute; + top: -30px; + left: 0; + background-color: #000; + color: #fff; + padding: 5px 10px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + } \ No newline at end of file diff --git a/gui/static/images/adopt.png b/gui/static/images/adopt.png new file mode 100644 index 0000000000000000000000000000000000000000..33ef7343c35e64d426db25683288b9062eb19d35 GIT binary patch literal 1845 zcmV-52g>+~P)Px*?@2^KR9HvVn177iMHRr`o4vK`j}tZehe+ZXkZP{Iy`48#>)Ixs$RAa)Eeh!w zNfZoJ#2+cfXe1H;L5)#U5UULh#@_;hp*1Ez(8TL`+;?VnFQK&{C<$sIZM3D>-g(`5 z?ptRs-}`odoVy#5mwc1m@65dUeBZoz@68~*NCXGTX{~)3Gp{Wa3dh&4U;p$Vbq@^G zIkz1Ez77BvM!@|<^!`D?4g}O%dj%rim4yl+e0gGG;^slX4g}OWcMt%|0)&XaW#)HA zxPJk_I1&A!U)a7u-mR2+Fk1Xq#+dj0PeHx+Q_TF6=A^k?PLGd| z%jP*poO8bbfXkz=-mR3nX?%SA@&6XMqeZQ?=jC#_i$e%&5pf+euLFS10PxDxM0<(o zx^@#=YhPcFu@A`jW}g^q4>R*2Wwu7rzeQ$&HchANwP&LC51~N>iRwreKf1rTDubw_oUuG%FNr0F;A`l zjhpIw0B~)p^D{&waj>OSDwUB^sq|F4cIVu?0N}n<+&#d|mm6c2(qo)+p8t=)u(0@>Wj4=bfUP%4#9q;*+q zKaYrCNxhq1T3VXgxN+l(sM0bsW%77K2NA94>%Ety8I`syY^73}2qF9u0M3_oW}Z_@ zZP!{$cw3h2Jc#&E2w}5rM^zKEG0LB!X@_Xi4v!qn*K=<}^ysbj8hVcE-w=s;6ZD_BIFLPSZya()v3-KMpk zZ?So9MZ{0We)pU_d2(uUa`J3DfA3o6dGFIP_PfTIk7s|y9XF>>DCnhEu zwP;o(T7(e(9CiLoYdzJCqbq2H<*4X8MD&SPN~0U>1%Rb`O>nI-M*Os;#LYGrLBFcC z{zBiN)oQg=tJQo|bORAdSZXQL)6>Jl!^5FiEdI08#MNqbv{tJ<8vEQyL|69>nk30J zN~t@dqDzSA;ciO1wM#1X-q&KkSt5FSw=rFp-Z}Se0B9^XGoNpaIoeI>YTBK1M*v_g zGe2jHX%rrvq${ZR{vb2Ub2cs1OVjD>A?_!U07Jyy?OKIb8bnj zwYMN*V-q3brCRHUdIqgmRBx?URE@JbBJR^#e=M!cIVV-nE&y0QxV4zwhqQnAE1rlD>%zG+#_&2-oB6k#QFG-}IwX}Ul9)UP}F zRJ%6s{WSn^9Wys?_a0eXT%0VI%V&D_(eyy?{Z&#(NQ~?SFgqsRU#%$=3ipqWj&?nk jugao>0P9!Biw5-H^27yLFGmC&00000NkvXXu0mjfgHwrH literal 0 HcmV?d00001 diff --git a/gui/static/images/adopted.png b/gui/static/images/adopted.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8c5e8dee1db965cffa0e214c78d48aff535a86 GIT binary patch literal 2038 zcmVPx+ut`KgR9HvFSbvNZcNPD<*}X$-upzOrvA1<+@9=s%d)HuF72}WA-|-5zjn!+E zSg5Un?5s-~1Pu`rP)j3sGkdhLP(@-3El~_1Z7Q}_trB9RJ=j@Jk! z7!fuSR`N+^x^tR6!pjb%8&5_@UYYMYZ2=meP&bi;AJhcHwAqMhTQ5IQ-XbLmua7Y! z;euftX3$A+5^EBYWlwc3z=ZEOi$cUn_+df5^)E$AUl-W^xUK9;)|#HlXJWv~NOP z5|S{+z{1id;;$O9jL%guwQMrI@IOWSZv^zZG7f#g=+=HY8-SH)%|*-Qpt^-5e6Os2 z%#igD)-n}0)r|!F(0xZZY((|tb6IlM9_``eg3dgNyy<}inC>%T+OFC_*IrEs{~o}5 zU97|hY=ly?EdmXJ4!nB*75vtP2uq;|*oincT}zT%BHcwxgB~_yZEYhj#S7{u$iZ(b zDtMV~?2$&4upcHvE&vn+Yfg?e^;pb2x`%?#-?#E5_pVU&Ks2sZ%Xr=w=!Ojya) z%=BnOImQ$dO%k{Oz#=%$WaZK4%bO91o9d$ktZug0^!K!n9p0Gh*3Y!67dO>a#25m> z&9$uzL6vMY^F=CmJP)*>^a=q520sxFhYyTJhX2`uPEC=%eCneT;d3DF0iy>5@q0zx zurqHUC9jTwFyIO?o(0h)2vf|?6oCp79IF|B<_g%;>-k4=L1$;U2aeqXq8@PR1H2Lp z;LpON93c6}ji~;+`7YcJ{dgg*kYi^{`xWN|(4PRf&ofRAEzfpqhug7OwGA)#5+jq1 z>1w?-`U~m}wu8s~LP2EEKt9!LOLzgy@ASb|)$DeFj$5g1#QYu4mhITRW7%|SoxGtb|}`k=_z3)?_h`agV5nAvcvb}x-&m*4@mVvV%%S$7;QD8+U9E8r6z|T z2k2`59rI=*mf2d#qZ7$NmS`V0qi5M!4U~af|`3z#Yrw5)YnUlDVkl)x99B z3~=(f;)Mmb{r$S_Zw43dnoKXevS^Ej<)*;=-G;3Hy!wOcHYJICYJiCaXXCC?jiYbe zOx{hPp9P422t?-r4Ea0*|1}g2uc%eYl)SnXgzr{lX(%ge{t2O}M?HZ)bKPwcq=Ny4 zy9`;ccu{1GiR2oVN?rVnL6{&&%d^pulhyA;Q~gHK79ZsfcBV(HWsmw?GgZN#+g)S8 zrkt$*sM?5PrqTfKsG5I`!!($S)V?XXelmScv1m_uI)bw5Jn#GRO@p^!xAIWv= zFVxd-4%F|iXws0iYg+}Nh&*#3a{FHz`Nt+W&b61S4uh^f?35xr>_Hjoz=RG?% z`cL-grLMkeGz0poNH;U+-r`#9M*|Gpy> zy6R|T_^nw36$*-}9s*Fgfn!X8Xh(G;-746DsbrUZiPi&1SLuA)koE2L>@=-RLQ!~D zlIVJ7=c!9?>pXbs-9Pp_`Bni|%k`V { const chatContainer = vueInstance.$el.querySelector('.chatmessage'); @@ -308,7 +309,7 @@ class FayInterface { selectUser(user) { this.selectedUser = user; this.fayService.websocket.send(JSON.stringify({ "Username": user[1] })); - this.loadMessageHistory(user[1]); + this.loadMessageHistory(user[1], 'common'); }, startLive() { this.liveState = 2 @@ -323,11 +324,11 @@ class FayInterface { }); }, - loadMessageHistory(username) { + loadMessageHistory(username, type) { this.fayService.getMessageHistory(username).then((response) => { if (response) { this.messages = response; - console.log(this.messages); + if(type == 'common'){ this.$nextTick(() => { const chatContainer = this.$el.querySelector('.chatmessage'); if (chatContainer) { @@ -335,6 +336,7 @@ class FayInterface { } }); } + } }); }, sendSuccessMsg(message) { @@ -344,7 +346,37 @@ class FayInterface { type: 'success', }); -} +} , +adoptText(id) { + // 调用采纳接口 + this.fayService.fetchData(`${this.base_url}/api/adopt_msg`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ id }) // 发送采纳请求 + }) + .then((response) => { + if (response && response.status === 'success') { + // 处理成功的响应 + this.$notify({ + title: '成功', + message: response.msg, // 显示成功消息 + type: 'success', + }); + + this.loadMessageHistory(this.selectedUser[1], 'adopt'); + } else { + // 处理失败的响应 + this.$notify({ + title: '失败', + message: response ? response.msg : '请求失败', + type: 'error', + }); + } + }) + +}, } }); \ No newline at end of file diff --git a/gui/templates/index.html b/gui/templates/index.html index 13a95aa..35b4687 100644 --- a/gui/templates/index.html +++ b/gui/templates/index.html @@ -40,7 +40,15 @@ 接收者头像
[[item.content]]
-
[[item.timetext]]
+
[[item.timetext]] +
+ 采纳图标 +
+
+ 采纳图标 +
+ +