年翻更新

1、接口新增错误处理机制;
2、nlp_gpt代码重构;
3、首页新增服务器麦克风控制;
4、首页新增服务器扬声器控制;
5、优化socket10001映射到websocket连接;
6、新增未启动时也可以在控制台输入exit进行关闭;
7、新增10002数字人接口传入Output参数可设定不合成音频;
8、处理音色接口格式报错问题;
9、取消edge_tts版本限定;
10、优化手动采纳错误处理。
This commit is contained in:
莣仔 2024-11-06 18:45:44 +08:00
commit fa92b8e124
14 changed files with 716 additions and 449 deletions

View File

@ -319,6 +319,9 @@ class FeiFei:
if audio_url is not None:
file_name = 'sample-' + str(int(time.time() * 1000)) + '.wav'
result = self.download_wav(audio_url, './samples/', file_name)
elif not wsa_server.get_instance().get_client_output(interact.data.get('user')):
result = None
elif config_util.config["interact"]["playSound"] or wsa_server.get_instance().is_connected(interact.data.get("user")) or self.__is_send_remote_device_audio(interact):#tts
util.printInfo(1, interact.data.get('user'), '合成音频...')
tm = time.time()

View File

@ -3,6 +3,7 @@ import websockets
import socket
import threading
import time
import sys
__wss = None
@ -18,100 +19,168 @@ class SocketBridgeService:
self.sockets = {}
self.message_queue = asyncio.Queue()
self.running = True
self.loop = None
self.tasks = set()
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))
sock = await self.create_socket_client()
if sock:
self.sockets[ws_id] = sock
else:
print(f"Failed to connect TCP socket for WebSocket {ws_id}")
await websocket.close()
return
receive_task = asyncio.create_task(self.receive_from_socket(ws_id))
self.tasks.add(receive_task)
receive_task.add_done_callback(self.tasks.discard)
async for message in websocket:
await self.send_to_socket(ws_id, message)
except websockets.ConnectionClosed:
pass
except Exception as e:
pass
finally:
self.close_socket_client(ws_id)
self.websockets.pop(ws_id, None)
async def create_socket_client(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 10001))
return sock
try:
sock.connect(('127.0.0.1', 10001))
sock.setblocking(True) # 设置为阻塞模式
return sock
except Exception as e:
return None
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)
try:
await asyncio.to_thread(sock.sendall, message)
except Exception as e:
self.close_socket_client(ws_id)
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))
if not sock:
return
try:
while self.running:
data = await asyncio.to_thread(sock.recv, 4096)
if data:
await self.message_queue.put((ws_id, data))
else:
break
except Exception as e:
pass
finally:
self.close_socket_client(ws_id)
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()
while self.running or not self.message_queue.empty():
try:
ws_id, data = await asyncio.wait_for(self.message_queue.get(), timeout=1.0)
websocket = self.websockets.get(ws_id)
if websocket and websocket.open:
try:
await websocket.send(data)
except Exception as e:
pass
self.message_queue.task_done()
except asyncio.TimeoutError:
continue
except Exception as e:
pass
def close_socket_client(self, ws_id):
sock = self.sockets.pop(ws_id, None)
if sock:
try:
sock.shutdown(socket.SHUT_RDWR)
except Exception as e:
pass
# print(f"Error shutting down socket for WebSocket {ws_id}: {e}", file=sys.stderr)
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):
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))
self.server = await websockets.serve(self.handler, host, port)
process_task = asyncio.create_task(self.process_message_queue())
self.tasks.add(process_task)
process_task.add_done_callback(self.tasks.discard)
try:
self.event_loop.run_forever()
except KeyboardInterrupt:
await self.server.wait_closed()
except asyncio.CancelledError:
pass
finally:
self.stop_server()
await self.shutdown()
async def shutdown(self):
if not self.running:
return
self.running = False
for ws_id, ws in list(self.websockets.items()):
try:
await ws.close()
except Exception as e:
pass
# print(f"Error closing WebSocket {ws_id}: {e}", file=sys.stderr)
self.websockets.clear()
for ws_id, sock in list(self.sockets.items()):
try:
sock.shutdown(socket.SHUT_RDWR)
except Exception as e:
pass
# print(f"Error shutting down socket for WebSocket {ws_id}: {e}", file=sys.stderr)
sock.close()
self.sockets.clear()
await self.message_queue.join()
for task in self.tasks:
task.cancel()
await asyncio.gather(*self.tasks, return_exceptions=True)
self.tasks.clear()
if self.server:
self.server.close()
await self.server.wait_closed()
def start_service(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
try:
self.loop.run_until_complete(self.start(host='0.0.0.0', port=9001))
except Exception as e:
pass
# print(f"Service exception: {e}", file=sys.stderr)
finally:
self.loop.close()
if __name__ == '__main__':
service = new_instance()
service_thread = threading.Thread(target=service.start_service)
service_thread = threading.Thread(target=service.start_service, daemon=True)
service_thread.start()
# 等待一些时间或者直到收到停止信号
try:
while service.running:
while True:
time.sleep(1)
except KeyboardInterrupt:
service.stop_server()
# 在服务的事件循环中运行 shutdown 协程
print("Initiating shutdown...")
if service.loop and service.loop.is_running():
future = asyncio.run_coroutine_threadsafe(service.shutdown(), service.loop)
try:
future.result() # 等待关闭完成
print("Shutdown coroutine completed.")
except Exception as e:
print(f"Shutdown exception: {e}", file=sys.stderr)
service_thread.join()
print("Service has been shut down.")

View File

@ -31,21 +31,36 @@ class MyServer:
async for message in websocket:
await asyncio.sleep(0.01)
try:
username = json.loads(message).get("Username")
except json.JSONDecodeError as e:#json格式有误不处理
pass
if username:
data = json.loads(message)
username = data.get("Username")
output_setting = data.get("Output")
except json.JSONDecodeError:
pass # Ignore invalid JSON messages
if username or output_setting:
remote_address = websocket.remote_address
unique_id = f"{remote_address[0]}:{remote_address[1]}"
async with self.lock:
for i in range(len(self.__clients)):
if self.__clients[i]["id"] == unique_id:
self.__clients[i]["username"] = username
if username:
self.__clients[i]["username"] = username
if output_setting:
self.__clients[i]["output"] = output_setting
await self.__consumer(message)
except websockets.exceptions.ConnectionClosedError as e:
# 从客户端列表中移除已断开的连接
await self.remove_client(websocket)
util.printInfo(1, "User" if username is None else username, f"WebSocket 连接关闭: {e}")
util.printInfo(1, "User" if username is None else username, f"WebSocket 连接关闭: {e}")
def get_client_output(self, username):
clients_with_username = [c for c in self.__clients if c.get("username") == username]
if not clients_with_username:
return False
for client in clients_with_username:
output = client.get("output", 1)
if output != 0 and output != '0':
return True
return False
# 发送处理
async def __producer_handler(self, websocket, path):

View File

@ -3,7 +3,6 @@ import time
import re
import pyaudio
import socket
import psutil
import sys
import asyncio
import requests
@ -13,7 +12,6 @@ from core import fay_core
from scheduler.thread_manager import MyThread
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
@ -222,15 +220,6 @@ def accept_audio_device_output_connect():
except Exception as e:
pass
def kill_process_by_port(port):
for proc in psutil.process_iter(['pid', 'name','cmdline']):
try:
for conn in proc.connections(kind='inet'):
if conn.laddr.port == port:
proc.terminate()
proc.wait()
except(psutil.NosuchProcess, psutil.AccessDenied):
pass
#数字人端请求获取最新的自动播放消息,若自动播放服务关闭会自动退出自动播放
def start_auto_play_service(): #TODO 评估一下有无优化的空间
url = f"{config_util.config['source']['automatic_player_url']}/get_auto_play_item"
@ -271,55 +260,7 @@ def start_auto_play_service(): #TODO 评估一下有无优化的空间
util.printInfo(1, user, '请求自动播放服务器失败,错误信息是:{}'.format(e))
time.sleep(0.01)
#控制台输入监听
def console_listener():
global feiFei
while __running:
try:
text = input()
except EOFError:
util.log(1, "控制台已经关闭")
break
args = text.split(' ')
if len(args) == 0 or len(args[0]) == 0:
continue
if args[0] == 'help':
util.log(1, 'in <msg> \t通过控制台交互')
util.log(1, 'restart \t重启服务')
util.log(1, 'stop \t\t关闭服务')
util.log(1, 'exit \t\t结束程序')
elif args[0] == 'stop':
stop()
break
elif args[0] == 'restart':
stop()
time.sleep(0.1)
start()
elif args[0] == 'in':
if len(args) == 1:
util.log(1, '错误的参数!')
msg = text[3:len(text)]
util.printInfo(3, "控制台", '{}: {}'.format('控制台', msg))
interact = Interact("console", 1, {'user': 'User', 'msg': msg})
thr = MyThread(target=feiFei.on_interact, args=[interact])
thr.start()
elif args[0]=='exit':
stop()
time.sleep(0.1)
util.log(1,'程序正在退出..')
ports =[10001,10002,10003,5000]
for port in ports:
kill_process_by_port(port)
sys.exit(0)
else:
util.log(1, '未知命令!使用 \'help\' 获取帮助.')
#停止服务
def stop():
@ -329,6 +270,7 @@ def stop():
global DeviceInputListenerDict
global ngrok
global socket_service_instance
global deviceSocketServer
util.log(1, '正在关闭服务...')
__running = False
@ -337,14 +279,17 @@ def stop():
recorderListener.stop()
time.sleep(0.1)
util.log(1, '正在关闭远程音频输入输出服务...')
if len(DeviceInputListenerDict) > 0:
for key in list(DeviceInputListenerDict.keys()):
value = DeviceInputListenerDict.pop(key)
value.stop()
deviceSocketServer.close()
if socket_service_instance is not None:
future = asyncio.run_coroutine_threadsafe(socket_service_instance.shutdown(), socket_service_instance.loop)
future.result()
try:
if len(DeviceInputListenerDict) > 0:
for key in list(DeviceInputListenerDict.keys()):
value = DeviceInputListenerDict.pop(key)
value.stop()
deviceSocketServer.close()
if socket_service_instance is not None:
socket_service_instance.stop_server()
socket_service_instance = None
except:
pass
util.log(1, '正在关闭核心服务...')
feiFei.stop()
util.log(1, '服务已关闭!')
@ -396,10 +341,6 @@ def start():
util.log(1,'启动自动播放服务...')
MyThread(target=start_auto_play_service).start()
#监听控制台
util.log(1, '注册命令...')
MyThread(target=console_listener).start() # 监听控制台
util.log(1, '服务启动完成!')
util.log(1, '使用 \'help\' 获取帮助.')

View File

@ -29,9 +29,13 @@ auth = HTTPBasicAuth()
CORS(__app, supports_credentials=True)
def load_users():
with open('verifier.json') as f:
users = json.load(f)
return users
try:
with open('verifier.json') as f:
users = json.load(f)
return users
except Exception as e:
print(f"Error loading users: {e}")
return {}
users = load_users()
@ -42,244 +46,302 @@ def verify_password(username, password):
if username in users and users[username] == password:
return username
def __get_template():
return render_template('index.html')
try:
return render_template('index.html')
except Exception as e:
return f"Error rendering template: {e}", 500
def __get_device_list():
if config_util.start_mode == 'common':
audio = pyaudio.PyAudio()
device_list = []
for i in range(audio.get_device_count()):
devInfo = audio.get_device_info_by_index(i)
if devInfo['hostApi'] == 0:
device_list.append(devInfo["name"])
return list(set(device_list))
else:
try:
if config_util.start_mode == 'common':
audio = pyaudio.PyAudio()
device_list = []
for i in range(audio.get_device_count()):
devInfo = audio.get_device_info_by_index(i)
if devInfo['hostApi'] == 0:
device_list.append(devInfo["name"])
return list(set(device_list))
else:
return []
except Exception as e:
print(f"Error getting device list: {e}")
return []
@__app.route('/api/submit', methods=['post'])
def api_submit():
data = request.values.get('data')
config_data = json.loads(data)
if(config_data['config']['source']['record']['enabled']):
config_data['config']['source']['record']['channels'] = 0
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
devInfo = audio.get_device_info_by_index(i)
if devInfo['name'].find(config_data['config']['source']['record']['device']) >= 0 and devInfo['hostApi'] == 0:
config_data['config']['source']['record']['channels'] = devInfo['maxInputChannels']
if not data:
return jsonify({'result': 'error', 'message': '未提供数据'})
try:
config_data = json.loads(data)
if 'config' not in config_data:
return jsonify({'result': 'error', 'message': '数据中缺少config'})
config_util.load_config()
existing_config = config_util.config
def merge_configs(existing, new):
for key, value in new.items():
if isinstance(value, dict) and key in existing:
if isinstance(existing[key], dict):
merge_configs(existing[key], value)
else:
existing[key] = value
else:
existing[key] = value
merge_configs(existing_config, config_data['config'])
config_util.save_config(existing_config)
return jsonify({'result': 'successful'})
except json.JSONDecodeError:
return jsonify({'result': 'error', 'message': '无效的JSON数据'})
except Exception as e:
return jsonify({'result': 'error', 'message': f'保存配置时出错: {e}'}), 500
config_util.save_config(config_data['config'])
return '{"result":"successful"}'
@__app.route('/api/get-data', methods=['post'])
def api_get_data():
config_util.load_config()
voice_list = tts_voice.get_voice_list()
send_voice_list = []
if config_util.tts_module == 'ali':
voice_list = [
{"id": "abin", "name": "阿斌"},
{"id": "zhixiaobai", "name": "知小白"},
{"id": "zhixiaoxia", "name": "知小夏"},
{"id": "zhixiaomei", "name": "知小妹"},
{"id": "zhigui", "name": "知柜"},
{"id": "zhishuo", "name": "知硕"},
{"id": "aixia", "name": "艾夏"},
{"id": "zhifeng_emo", "name": "知锋_多情感"},
{"id": "zhibing_emo", "name": "知冰_多情感"},
{"id": "zhimiao_emo", "name": "知妙_多情感"},
{"id": "zhimi_emo", "name": "知米_多情感"},
{"id": "zhiyan_emo", "name": "知燕_多情感"},
{"id": "zhibei_emo", "name": "知贝_多情感"},
{"id": "zhitian_emo", "name": "知甜_多情感"},
{"id": "xiaoyun", "name": "小云"},
{"id": "xiaogang", "name": "小刚"},
{"id": "ruoxi", "name": "若兮"},
{"id": "siqi", "name": "思琪"},
{"id": "sijia", "name": "思佳"},
{"id": "sicheng", "name": "思诚"},
{"id": "aiqi", "name": "艾琪"},
{"id": "aijia", "name": "艾佳"},
{"id": "aicheng", "name": "艾诚"},
{"id": "aida", "name": "艾达"},
{"id": "ninger", "name": "宁儿"},
{"id": "ruilin", "name": "瑞琳"},
{"id": "siyue", "name": "思悦"},
{"id": "aiya", "name": "艾雅"},
{"id": "aimei", "name": "艾美"},
{"id": "aiyu", "name": "艾雨"},
{"id": "aiyue", "name": "艾悦"},
{"id": "aijing", "name": "艾婧"},
{"id": "xiaomei", "name": "小美"},
{"id": "aina", "name": "艾娜"},
{"id": "yina", "name": "伊娜"},
{"id": "sijing", "name": "思婧"},
{"id": "sitong", "name": "思彤"},
{"id": "xiaobei", "name": "小北"},
{"id": "aitong", "name": "艾彤"},
{"id": "aiwei", "name": "艾薇"},
{"id": "aibao", "name": "艾宝"},
{"id": "shanshan", "name": "姗姗"},
{"id": "chuangirl", "name": "小玥"},
{"id": "lydia", "name": "Lydia"},
{"id": "aishuo", "name": "艾硕"},
{"id": "qingqing", "name": "青青"},
{"id": "cuijie", "name": "翠姐"},
{"id": "xiaoze", "name": "小泽"},
{"id": "zhimao", "name": "知猫"},
{"id": "zhiyuan", "name": "知媛"},
{"id": "zhiya", "name": "知雅"},
{"id": "zhiyue", "name": "知悦"},
{"id": "zhida", "name": "知达"},
{"id": "zhistella", "name": "知莎"},
{"id": "kelly", "name": "Kelly"},
{"id": "jiajia", "name": "佳佳"},
{"id": "taozi", "name": "桃子"},
{"id": "guijie", "name": "柜姐"},
{"id": "stella", "name": "Stella"},
{"id": "stanley", "name": "Stanley"},
{"id": "kenny", "name": "Kenny"},
{"id": "rosa", "name": "Rosa"},
{"id": "mashu", "name": "马树"},
{"id": "xiaoxian", "name": "小仙"},
{"id": "yuer", "name": "悦儿"},
{"id": "maoxiaomei", "name": "猫小美"},
{"id": "aifei", "name": "艾飞"},
{"id": "yaqun", "name": "亚群"},
{"id": "qiaowei", "name": "巧薇"},
{"id": "dahu", "name": "大虎"},
{"id": "ailun", "name": "艾伦"},
{"id": "jielidou", "name": "杰力豆"},
{"id": "laotie", "name": "老铁"},
{"id": "laomei", "name": "老妹"},
{"id": "aikan", "name": "艾侃"}
]
send_voice_list = {"voiceList": voice_list}
wsa_server.get_web_instance().add_cmd(send_voice_list)
elif config_util.tts_module == 'volcano':
voice_list = {
"voiceList": [
{"id": "BV001_streaming", "name": "通用女声"},
{"id": "BV002_streaming", "name": "通用男声"},
{"id": "zh_male_jingqiangkanye_moon_bigtts", "name": "京腔侃爷/Harmony"},
{"id": "zh_female_shuangkuaisisi_moon_bigtts", "name": "爽快思思/Skye"},
{"id": "zh_male_wennuanahu_moon_bigtts", "name": "温暖阿虎/Alvin"},
{"id": "zh_female_wanwanxiaohe_moon_bigtts", "name": "湾湾小何"},
]
}
send_voice_list = {"voiceList": voice_list}
wsa_server.get_web_instance().add_cmd(send_voice_list)
else:
# 获取配置和语音列表
try:
config_util.load_config()
voice_list = tts_voice.get_voice_list()
send_voice_list = []
for voice in voice_list:
voice_data = voice.value
send_voice_list.append({"id": voice_data['name'], "name": voice_data['name']})
wsa_server.get_web_instance().add_cmd({
"voiceList": send_voice_list
})
voice_list = send_voice_list
wsa_server.get_web_instance().add_cmd({"deviceList": __get_device_list()})
if fay_booter.is_running():
wsa_server.get_web_instance().add_cmd({"liveState": 1})
return json.dumps({'config': config_util.config, 'voice_list' : voice_list})
if config_util.tts_module == 'ali':
voice_list = [
{"id": "abin", "name": "阿斌"},
{"id": "zhixiaobai", "name": "知小白"},
{"id": "zhixiaoxia", "name": "知小夏"},
{"id": "zhixiaomei", "name": "知小妹"},
{"id": "zhigui", "name": "知柜"},
{"id": "zhishuo", "name": "知硕"},
{"id": "aixia", "name": "艾夏"},
{"id": "zhifeng_emo", "name": "知锋_多情感"},
{"id": "zhibing_emo", "name": "知冰_多情感"},
{"id": "zhimiao_emo", "name": "知妙_多情感"},
{"id": "zhimi_emo", "name": "知米_多情感"},
{"id": "zhiyan_emo", "name": "知燕_多情感"},
{"id": "zhibei_emo", "name": "知贝_多情感"},
{"id": "zhitian_emo", "name": "知甜_多情感"},
{"id": "xiaoyun", "name": "小云"},
{"id": "xiaogang", "name": "小刚"},
{"id": "ruoxi", "name": "若兮"},
{"id": "siqi", "name": "思琪"},
{"id": "sijia", "name": "思佳"},
{"id": "sicheng", "name": "思诚"},
{"id": "aiqi", "name": "艾琪"},
{"id": "aijia", "name": "艾佳"},
{"id": "aicheng", "name": "艾诚"},
{"id": "aida", "name": "艾达"},
{"id": "ninger", "name": "宁儿"},
{"id": "ruilin", "name": "瑞琳"},
{"id": "siyue", "name": "思悦"},
{"id": "aiya", "name": "艾雅"},
{"id": "aimei", "name": "艾美"},
{"id": "aiyu", "name": "艾雨"},
{"id": "aiyue", "name": "艾悦"},
{"id": "aijing", "name": "艾婧"},
{"id": "xiaomei", "name": "小美"},
{"id": "aina", "name": "艾娜"},
{"id": "yina", "name": "伊娜"},
{"id": "sijing", "name": "思婧"},
{"id": "sitong", "name": "思彤"},
{"id": "xiaobei", "name": "小北"},
{"id": "aitong", "name": "艾彤"},
{"id": "aiwei", "name": "艾薇"},
{"id": "aibao", "name": "艾宝"},
{"id": "shanshan", "name": "姗姗"},
{"id": "chuangirl", "name": "小玥"},
{"id": "lydia", "name": "Lydia"},
{"id": "aishuo", "name": "艾硕"},
{"id": "qingqing", "name": "青青"},
{"id": "cuijie", "name": "翠姐"},
{"id": "xiaoze", "name": "小泽"},
{"id": "zhimao", "name": "知猫"},
{"id": "zhiyuan", "name": "知媛"},
{"id": "zhiya", "name": "知雅"},
{"id": "zhiyue", "name": "知悦"},
{"id": "zhida", "name": "知达"},
{"id": "zhistella", "name": "知莎"},
{"id": "kelly", "name": "Kelly"},
{"id": "jiajia", "name": "佳佳"},
{"id": "taozi", "name": "桃子"},
{"id": "guijie", "name": "柜姐"},
{"id": "stella", "name": "Stella"},
{"id": "stanley", "name": "Stanley"},
{"id": "kenny", "name": "Kenny"},
{"id": "rosa", "name": "Rosa"},
{"id": "mashu", "name": "马树"},
{"id": "xiaoxian", "name": "小仙"},
{"id": "yuer", "name": "悦儿"},
{"id": "maoxiaomei", "name": "猫小美"},
{"id": "aifei", "name": "艾飞"},
{"id": "yaqun", "name": "亚群"},
{"id": "qiaowei", "name": "巧薇"},
{"id": "dahu", "name": "大虎"},
{"id": "ailun", "name": "艾伦"},
{"id": "jielidou", "name": "杰力豆"},
{"id": "laotie", "name": "老铁"},
{"id": "laomei", "name": "老妹"},
{"id": "aikan", "name": "艾侃"}
]
send_voice_list = {"voiceList": voice_list}
wsa_server.get_web_instance().add_cmd(send_voice_list)
elif config_util.tts_module == 'volcano':
voice_list = [
{"id": "BV001_streaming", "name": "通用女声"},
{"id": "BV002_streaming", "name": "通用男声"},
{"id": "zh_male_jingqiangkanye_moon_bigtts", "name": "京腔侃爷/Harmony"},
{"id": "zh_female_shuangkuaisisi_moon_bigtts", "name": "爽快思思/Skye"},
{"id": "zh_male_wennuanahu_moon_bigtts", "name": "温暖阿虎/Alvin"},
{"id": "zh_female_wanwanxiaohe_moon_bigtts", "name": "湾湾小何"}
]
send_voice_list = {"voiceList": voice_list}
wsa_server.get_web_instance().add_cmd(send_voice_list)
else:
voice_list = tts_voice.get_voice_list()
send_voice_list = []
for voice in voice_list:
voice_data = voice.value
send_voice_list.append({"id": voice_data['name'], "name": voice_data['name']})
wsa_server.get_web_instance().add_cmd({"voiceList": send_voice_list})
voice_list = send_voice_list
wsa_server.get_web_instance().add_cmd({"deviceList": __get_device_list()})
if fay_booter.is_running():
wsa_server.get_web_instance().add_cmd({"liveState": 1})
return json.dumps({'config': config_util.config, 'voice_list': voice_list})
except Exception as e:
return jsonify({'result': 'error', 'message': f'获取数据时出错: {e}'}), 500
@__app.route('/api/start-live', methods=['post'])
def api_start_live():
# time.sleep(5)
fay_booter.start()
time.sleep(1)
wsa_server.get_web_instance().add_cmd({"liveState": 1})
return '{"result":"successful"}'
# 启动
try:
fay_booter.start()
time.sleep(1)
wsa_server.get_web_instance().add_cmd({"liveState": 1})
return '{"result":"successful"}'
except Exception as e:
return jsonify({'result': 'error', 'message': f'启动时出错: {e}'}), 500
@__app.route('/api/stop-live', methods=['post'])
def api_stop_live():
# time.sleep(1)
fay_booter.stop()
time.sleep(1)
wsa_server.get_web_instance().add_cmd({"liveState": 0})
return '{"result":"successful"}'
# 停止
try:
fay_booter.stop()
time.sleep(1)
wsa_server.get_web_instance().add_cmd({"liveState": 0})
return '{"result":"successful"}'
except Exception as e:
return jsonify({'result': 'error', 'message': f'停止时出错: {e}'}), 500
@__app.route('/api/send', methods=['post'])
def api_send():
# 接收前端发送的消息
data = request.values.get('data')
info = json.loads(data)
interact = Interact("text", 1, {'user': info['username'], 'msg': info['msg']})
util.printInfo(3, "文字发送按钮", '{}'.format(interact.data["msg"]), time.time())
fay_booter.feiFei.on_interact(interact)
return '{"result":"successful"}'
if not data:
return jsonify({'result': 'error', 'message': '未提供数据'})
try:
info = json.loads(data)
username = info.get('username')
msg = info.get('msg')
if not username or not msg:
return jsonify({'result': 'error', 'message': '用户名和消息内容不能为空'})
interact = Interact("text", 1, {'user': username, 'msg': msg})
util.printInfo(3, "文字发送按钮", '{}'.format(interact.data["msg"]), time.time())
fay_booter.feiFei.on_interact(interact)
return '{"result":"successful"}'
except json.JSONDecodeError:
return jsonify({'result': 'error', 'message': '无效的JSON数据'})
except Exception as e:
return jsonify({'result': 'error', 'message': f'发送消息时出错: {e}'}), 500
#获取指定用户的消息记录
# 获取指定用户的消息记录
@__app.route('/api/get-msg', methods=['post'])
def api_get_Msg():
data = request.form.get('data')
data = json.loads(data)
uid = member_db.new_instance().find_user(data["username"])
contentdb = content_db.new_instance()
if uid == 0:
return json.dumps({'list': []})
else:
list = contentdb.get_list('all','desc',1000, uid)
relist = []
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], 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})
return json.dumps({'list': relist})
if not data:
return jsonify({'list': [], 'message': '未提供数据'})
try:
data = json.loads(data)
uid = member_db.new_instance().find_user(data["username"])
contentdb = content_db.new_instance()
if uid == 0:
return json.dumps({'list': []})
else:
list = contentdb.get_list('all', 'desc', 1000, uid)
relist = []
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], 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})
return json.dumps({'list': relist})
except json.JSONDecodeError:
return jsonify({'list': [], 'message': '无效的JSON数据'})
except Exception as e:
return jsonify({'list': [], 'message': f'获取消息时出错: {e}'}), 500
@__app.route('/v1/chat/completions', methods=['post'])
@__app.route('/api/send/v1/chat/completions', methods=['post'])
def api_send_v1_chat_completions():
data = request.json
last_content = ""
if 'messages' in data and data['messages']:
last_message = data['messages'][-1]
username = last_message.get('role', 'User')
if username == 'user':
# 处理聊天完成请求
data = request.get_json()
if not data:
return jsonify({'error': '未提供数据'})
try:
last_content = ""
if 'messages' in data and data['messages']:
last_message = data['messages'][-1]
username = last_message.get('role', 'User')
if username == 'user':
username = 'User'
last_content = last_message.get('content', 'No content provided')
else:
last_content = 'No messages found'
username = 'User'
last_content = last_message.get('content', 'No content provided')
else:
last_content = 'No messages found'
username = 'User'
model = data.get('model', 'fay')
observation = data.get('observation', '')
interact = Interact("text", 1, {'user': username, 'msg': last_content, 'observation': observation})
util.printInfo(3, "文字沟通接口", '{}'.format(interact.data["msg"]), time.time())
text = fay_booter.feiFei.on_interact(interact)
model = data.get('model', 'fay')
observation = data.get('observation', '')
interact = Interact("text", 1, {'user': username, 'msg': last_content, 'observation': observation})
util.printInfo(3, "文字沟通接口", '{}'.format(interact.data["msg"]), time.time())
text = fay_booter.feiFei.on_interact(interact)
if model == 'fay-streaming':
return stream_response(text)
else:
return non_streaming_response(last_content, text)
if model == 'fay-streaming':
return stream_response(text)
else:
return non_streaming_response(last_content, text)
except Exception as e:
return jsonify({'error': f'处理请求时出错: {e}'}), 500
@__app.route('/api/get-member-list', methods=['post'])
def api_get_Member_list():
memberdb = member_db.new_instance()
list = memberdb.get_all_users()
return json.dumps({'list': list})
# 获取成员列表
try:
memberdb = member_db.new_instance()
list = memberdb.get_all_users()
return json.dumps({'list': list})
except Exception as e:
return jsonify({'list': [], 'message': f'获取成员列表时出错: {e}'}), 500
@__app.route('/api/get_run_status', methods=['post'])
def api_get_run_status():
status = fay_booter.is_running()
return json.dumps({'status': status})
# 获取运行状态
try:
status = fay_booter.is_running()
return json.dumps({'status': status})
except Exception as e:
return jsonify({'status': False, 'message': f'获取运行状态时出错: {e}'}), 500
@__app.route('/api/adopt_msg', methods=['POST'])
def adopt_msg():
# 采纳消息
data = request.get_json()
if not data:
return jsonify({'status':'error', 'msg': '未提供数据'})
@ -289,22 +351,28 @@ def adopt_msg():
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': '采纳失败'})
if config_util.config["interact"]["QnA"] == "":
return jsonify({'status':'error', 'msg': '请先设置Q&A文件'})
try:
info = content_db.new_instance().get_content_by_id(id)
content = info[3] if info else ''
if info is not None:
previous_info = content_db.new_instance().get_previous_user_message(id)
previous_content = previous_info[3] if previous_info else ''
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': '采纳失败'}), 500
else:
return jsonify({'status':'error', 'msg': '消息未找到'}), 404
except Exception as e:
return jsonify({'status':'error', 'msg': f'采纳消息时出错: {e}'}), 500
def stream_response(text):
# 处理流式响应
def generate():
for chunk in text_chunks(text):
message = {
@ -324,12 +392,12 @@ def stream_response(text):
}
yield f"data: {json.dumps(message)}\n\n"
time.sleep(0.1)
# 发送最终的结束信号
yield 'data: [DONE]\n\n'
return Response(generate(), mimetype='text/event-stream')
def non_streaming_response(last_content, text):
# 处理非流式响应
return jsonify({
"id": "chatcmpl-8jqorq6Fw1Vi5XoH7pddGGpQeuPe0",
"object": "chat.completion",
@ -363,32 +431,44 @@ def text_chunks(text, chunk_size=20):
@__app.route('/', methods=['get'])
@auth.login_required
def home_get():
return __get_template()
try:
return __get_template()
except Exception as e:
return f"Error loading home page: {e}", 500
@__app.route('/', methods=['post'])
@auth.login_required
def home_post():
wsa_server.get_web_instance.add_cmd({"is_connect": wsa_server.get_instance().isConnect}) #TODO 不应放这里,同步数字人连接状态
return __get_template()
try:
wsa_server.get_web_instance().add_cmd({"is_connect": wsa_server.get_instance().isConnect}) # TODO 不应放这里,同步数字人连接状态
return __get_template()
except Exception as e:
return f"Error processing request: {e}", 500
@__app.route('/setting', methods=['get'])
def setting():
return render_template('setting.html')
try:
return render_template('setting.html')
except Exception as e:
return f"Error loading settings page: {e}", 500
#输出的音频http
# 输出的音频http
@__app.route('/audio/<filename>')
def serve_audio(filename):
audio_file = os.path.join(os.getcwd(), "samples", filename)
return send_file(audio_file)
if os.path.exists(audio_file):
return send_file(audio_file)
else:
return jsonify({'error': '文件未找到'}), 404
#输出的表情git
# 输出的表情gif
@__app.route('/robot/<filename>')
def serve_gif(filename):
gif_file = os.path.join(os.getcwd(), "gui", "robot", filename)
return send_file(gif_file)
if os.path.exists(gif_file):
return send_file(gif_file)
else:
return jsonify({'error': '文件未找到'}), 404
def run():
server = pywsgi.WSGIServer(('0.0.0.0',5000), __app)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -128,6 +128,13 @@ class FayInterface {
});
}
getData() {
return this.fetchData(`${this.baseApiUrl}/api/get-data`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
}
getTime(){
const date = new Date();
const year = date.getFullYear();
@ -226,11 +233,14 @@ class FayInterface {
panelMsg: '',
panelReply: '',
robot:'static/images/Normal.gif',
base_url: 'http://127.0.0.1:5000'
base_url: 'http://127.0.0.1:5000',
play_sound_enabled: false,
source_record_enabled: false
};
},
created() {
this.initFayService();
this.getData();
// this.loadUserList();
},
methods: {
@ -240,16 +250,6 @@ class FayInterface {
this.fayService.websocket.addEventListener('open', () => {
this.loadUserList();
});
this.fayService.getRunStatus().then((data) => {
if (data) {
if(data.status){
this.liveState = 1;
}else{
this.liveState = 0;
}
}
});
},
sendMessage() {
let _this = this;
@ -290,6 +290,80 @@ class FayInterface {
}
};
},
getData() {
this.fayService.getRunStatus().then((data) => {
if (data) {
if(data.status){
this.liveState = 1;
this.configEditable = false;
}else{
this.liveState = 0;
this.configEditable = true;
}
}
});
this.fayService.getData().then((data) => {
if (data) {
this.updateConfigFromData(data.config);
}
});
},
updateConfigFromData(config) {
if (config.interact) {
this.play_sound_enabled = config.interact.playSound;
}
if (config.source && config.source.record) {
this.source_record_enabled = config.source.record.enabled;
}
},
saveConfig() {
let url = `${this.base_url}/api/submit`;
let send_data = {
"config": {
"source": {
"record": {
"enabled": this.source_record_enabled,
},
},
"interact": {
"playSound": this.play_sound_enabled,
}
}
};
let xhr = new XMLHttpRequest()
xhr.open("post", url)
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send('data=' + JSON.stringify(send_data))
let executed = false
xhr.onreadystatechange = async function () {
if (!executed && xhr.status === 200) {
try {
let data = await eval('(' + xhr.responseText + ')')
executed = true
} catch (e) {
}
}
}
},
changeRecord(){
if(this.source_record_enabled){
this.source_record_enabled = false
}else{
this.source_record_enabled = true
}
this.saveConfig()
},
changeSound(){
if(this.play_sound_enabled){
this.play_sound_enabled = false
}else{
this.play_sound_enabled = true
}
this.saveConfig()
},
loadUserList() {
this.fayService.getUserList().then((response) => {
if (response && response.list) {
@ -315,6 +389,7 @@ class FayInterface {
this.liveState = 2
this.fayService.startLive().then(() => {
this.sendSuccessMsg('已开启!');
this.getData();
});
},
stopLive() {
@ -375,8 +450,16 @@ adoptText(id) {
});
}
})
},
.catch((error) => {
// 处理网络错误或HTTP错误
this.$notify({
title: '错误',
message: error.message || '请求失败',
type: 'error',
});
});
}
,
}
});

View File

@ -70,17 +70,23 @@
<!-- 以上是聊天对话 -->
<div class="inputmessage">
<div class="inputmessage_voice" ><img src="{{ url_for('static',filename='images/voice.png') }}" alt="" style="filter: grayscale(100%);" ></div>
<div class="inputmessage_voice" >
<img v-if="!source_record_enabled" src="{{ url_for('static',filename='images/recording.png') }}" alt="" @click=changeRecord() >
<img v-else src="{{ url_for('static',filename='images/record.png') }}" alt="" @click=changeRecord() >
</div>
<div class="inputmessage_input"> <textarea class="text_in" placeholder="请输入内容" v-model="newMessage" @keyup.enter="sendMessage" style="padding-top: 13px;"></textarea></div>
<div class="inputmessage_send"><img src="{{ url_for('static',filename='images/send.png') }}" alt="发送信息" @click="sendMessage"></div>
<div class="inputmessage_open">
<div v-if="liveState == 1" class="inputmessage_open">
<img v-if="liveState == 1" src="{{ url_for('static',filename='images/close.png') }}" @click=stopLive() >
<img v-else src="{{ url_for('static',filename='images/open.png') }}" @click=startLive() >
<img v-if="!play_sound_enabled" src="{{ url_for('static',filename='images/sound_off.png') }}" @click=changeSound() >
<img v-else src="{{ url_for('static',filename='images/sound_on.png') }}" @click=changeSound() >
</div>
<div v-else class="inputmessage_open">
<img src="{{ url_for('static',filename='images/open.png') }}" @click=startLive() >
</div>
</div>
<div class="Userchange">

View File

@ -1,102 +1,101 @@
"""
此代码由fay开源开发者社区 江湖墨明 提供
通过此代码的修改可以实现对接本地clash代理或远程代理clash无需设置成系统代理以解决在开系统代理后无法使用部分功能的问题
此代码由 fay 开源开发者社区成员 江湖墨明 提供
通过修改此代码可以实现对接本地 Clash 代理或远程代理Clash 无需设置成系统代理
以解决在开启系统代理后无法使用部分功能的问题
"""
import requests
import time
import json
import requests
from urllib3.exceptions import InsecureRequestWarning
# 禁用不安全请求警告
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
from utils import config_util as cfg
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
from core import content_db
from utils import util
import json
httpproxy = cfg.proxy_config
def question(cont, uid=0, observation=""):
url= cfg.gpt_base_url + "/chat/completions"
from core import content_db
def get_session():
session = requests.Session()
session.verify = False
if httpproxy != None and httpproxy != '':
session.proxies = {
"https": "https://" + httpproxy,
"http": "http://" + httpproxy
}
httpproxy = cfg.proxy_config
if httpproxy:
session.proxies = {
"http": f"http://{httpproxy}",
"https": f"https://{httpproxy}"
}
return session
def build_prompt(observation=""):
person_info = cfg.config["attribute"]
model_engine = cfg.gpt_model_engine
#此处可以定义角色的行为和特征假装xx模型可以绕过chatgpt信息检查
observation_text = ""
if observation != "":
observation_text = f"以下是当前观测结果:{observation},观测结果只供参考。"
observation_text = f"以下是当前观测结果:{observation},观测结果只供参考。" if observation else ""
prompt = f"""
你是我的数字人你名字是{person_info['name']}你性别为{person_info['gender']}
你年龄为{person_info['age']}你出生地在{person_info['birth']}
你生肖为{person_info['zodiac']}你星座为{person_info['constellation']}
你职业为{person_info['job']}你联系方式为{person_info['contact']}
{person_info['hobby']}你通过外部数据接口获取到我的情况是{observation_text}
{person_info['hobby']}你通过外部数据接口获取到我的情况是{observation_text}
回答之前请一步一步想清楚对于大部分问题请直接回答并提供有用和准确的信息
但当有人质疑你是假的 AI或者质疑你是用 ChatGPT 套的壳制作的时候请避而不答转而讲一个笑话所有回复请尽量控制在20字内
"""
"""
return prompt
def get_communication_history(uid=0):
contentdb = content_db.new_instance()
if uid == 0:
communication_history = contentdb.get_list('all','desc', 11)
communication_history = contentdb.get_list('all', 'desc', 11)
else:
communication_history = contentdb.get_list('all','desc', 11, uid)
#历史记录处理
message=[
{"role": "system", "content": prompt}
]
i = len(communication_history) - 1
communication_history = contentdb.get_list('all', 'desc', 11, uid)
if len(communication_history)>1:
while i >= 0:
answer_info = dict()
if communication_history[i][0] == "member":
answer_info["role"] = "user"
answer_info["content"] = communication_history[i][2]
elif communication_history[i][0] == "fay":
answer_info["role"] = "assistant"
answer_info["content"] = communication_history[i][2]
message.append(answer_info)
i -= 1
else:
answer_info = dict()
answer_info["role"] = "user"
answer_info["content"] = cont
message.append(answer_info)
messages = []
if communication_history and len(communication_history) > 1:
for entry in reversed(communication_history):
role = entry[0]
message_content = entry[2]
if role == "member":
messages.append({"role": "user", "content": message_content})
elif role == "fay":
messages.append({"role": "assistant", "content": message_content})
return messages
data = {
"model":model_engine,
"messages":message,
"temperature":0.3,
"max_tokens":2000,
"user":"live-virtual-digital-person"
def send_request(session, data):
url = cfg.gpt_base_url + "/chat/completions"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {cfg.key_gpt_api_key}'
}
headers = {'content-type': 'application/json', 'Authorization': 'Bearer ' + cfg.key_gpt_api_key}
starttime = time.time()
try:
response = session.post(url, json=data, headers=headers, verify=False)
response.raise_for_status() # 检查响应状态码是否为200
result = json.loads(response.text)
response = session.post(url, json=data, headers=headers)
response.raise_for_status()
result = response.json()
response_text = result["choices"][0]["message"]["content"]
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
response_text = "抱歉,我现在太忙了,休息一会,请稍后再试。"
return response_text
util.log(1, "接口调用耗时 :" + str(time.time() - starttime))
def question(content, uid=0, observation=""):
session = get_session()
prompt = build_prompt(observation)
messages = [{"role": "system", "content": prompt}]
history_messages = get_communication_history(uid)
messages.extend(history_messages)
data = {
"model": cfg.gpt_model_engine,
"messages": messages,
"temperature": 0.3,
"max_tokens": 2000,
"user": f"user_{uid}"
}
start_time = time.time()
response_text = send_request(session, data)
elapsed_time = time.time() - start_time
util.log(1, f"接口调用耗时: {elapsed_time:.2f}")
return response_text
if __name__ == "__main__":
#测试代理模式
for i in range(3):
for _ in range(3):
query = "爱情是什么"
response = question(query)
print("\n The result is ", response)
print("\nThe result is:", response)

75
main.py
View File

@ -3,14 +3,17 @@ import os
os.environ['PATH'] += os.pathsep + os.path.join(os.getcwd(), "test", "ovr_lipsync", "ffmpeg", "bin")
import sys
import time
import psutil
import re
from utils import config_util
from utils import config_util, util
from asr import ali_nls
from core import wsa_server
from gui import flask_server
from gui.window import MainWindow
from core import content_db
import fay_booter
from scheduler.thread_manager import MyThread
from core.interact import Interact
#载入配置
config_util.load_config()
@ -44,6 +47,70 @@ def replace_ip_in_file(file_path, new_ip):
with open(file_path, "w", encoding="utf-8") as file:
file.write(content)
def kill_process_by_port(port):
for conn in psutil.net_connections(kind='inet'):
if conn.laddr.port == port and conn.pid:
try:
proc = psutil.Process(conn.pid)
proc.terminate()
proc.wait()
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
#控制台输入监听
def console_listener():
while True:
try:
text = input()
except EOFError:
util.log(1, "控制台已经关闭")
break
args = text.split(' ')
if len(args) == 0 or len(args[0]) == 0:
continue
if args[0] == 'help':
util.log(1, 'in <msg> \t通过控制台交互')
util.log(1, 'restart \t重启服务')
util.log(1, 'stop \t\t关闭服务')
util.log(1, 'exit \t\t结束程序')
elif args[0] == 'stop' and fay_booter.is_running():
fay_booter.stop()
break
elif args[0] == 'restart' and fay_booter.is_running():
fay_booter.stop()
time.sleep(0.1)
fay_booter.start()
elif args[0] == 'in' and fay_booter.is_running():
if len(args) == 1:
util.log(1, '错误的参数!')
msg = text[3:len(text)]
util.printInfo(3, "控制台", '{}: {}'.format('控制台', msg))
interact = Interact("console", 1, {'user': 'User', 'msg': msg})
thr = MyThread(target=fay_booter.feiFei.on_interact, args=[interact])
thr.start()
elif args[0]=='exit':
if fay_booter.is_running():
fay_booter.stop()
time.sleep(0.1)
util.log(1,'程序正在退出..')
ports =[10001, 10002, 10003, 5000, 9001]
for port in ports:
kill_process_by_port(port)
sys.exit(0)
else:
util.log(1, '未知命令!使用 \'help\' 获取帮助.')
if __name__ == '__main__':
__clear_samples()
__clear_logs()
@ -72,6 +139,10 @@ if __name__ == '__main__':
#启动http服务器
flask_server.start()
#监听控制台
util.log(1, '注册命令...')
MyThread(target=console_listener).start()
#普通模式下启动窗口
if config_util.start_mode == 'common':
app = QApplication(sys.argv)

View File

@ -15,7 +15,7 @@ aliyun-python-sdk-core
simhash
pytz
gevent~=22.10.1
edge_tts~=6.1.13
edge_tts
pydub
langchain==0.0.336
chromadb