Skip to content

Commit

Permalink
新增:聊天页 新增 对话打断功能,支持自定义 打断词、打断时清除什么类型的数据等。
Browse files Browse the repository at this point in the history
  • Loading branch information
Ikaros-521 committed Oct 25, 2024
1 parent ee4ae65 commit d0ca64b
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 6 deletions.
15 changes: 15 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,21 @@
"CHANNELS": 1,
"RATE": 16000,
"show_chat_log": true,
"interrupt_talk": {
"enable": false,
"keywords": [
"停一下",
"住嘴",
"别说了",
"打住打住",
"等一下等一下"
],
"clean_type": [
"message_queue",
"voice_tmp_path_queue",
"audio_play"
]
},
"baidu": {
"app_id": "",
"api_key": "",
Expand Down
15 changes: 15 additions & 0 deletions config.json.bak
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,21 @@
"CHANNELS": 1,
"RATE": 16000,
"show_chat_log": true,
"interrupt_talk": {
"enable": false,
"keywords": [
"停一下",
"住嘴",
"别说了",
"打住打住",
"等一下等一下"
],
"clean_type": [
"message_queue",
"voice_tmp_path_queue",
"audio_play"
]
},
"baidu": {
"app_id": "",
"api_key": "",
Expand Down
44 changes: 42 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,29 @@ def audio_listen(volume_threshold=800.0, silence_threshold=15):
wf.writeframes(b''.join(frames))"""
return frames

# 处理聊天逻辑
# 处理聊天逻辑 传入ASR后的文本内容
def talk_handle(content: str):
global is_talk_awake

def clear_queue_and_stop_audio_play(message_queue: bool=True, voice_tmp_path_queue: bool=True, stop_audio_play: bool=True):
"""
清空队列 或 停止播放音频
"""
if message_queue:
ret = my_handle.clear_queue("message_queue")
if ret:
logger.info("清空待合成消息队列成功!")
else:
logger.error("清空待合成消息队列失败!")
if voice_tmp_path_queue:
ret = my_handle.clear_queue("voice_tmp_path_queue")
if ret:
logger.info("清空待播放音频队列成功!")
else:
logger.error("清空待播放音频队列失败!")
if stop_audio_play:
ret = my_handle.stop_audio("pygame", True, True)

try:
# 检查并切换聊天唤醒状态
def check_talk_awake(content: str):
Expand Down Expand Up @@ -536,7 +555,7 @@ def check_talk_awake(content: str):
# 赋值给data
data["content"] = content

# 首次触发切换模式
# 首次触发切换模式 播放唤醒文案
if check_resp["first"]:
# 随机获取文案 TODO: 如果此功能测试成功,所有的类似功能都将使用此函数简化代码
resp_json = common.get_random_str_in_list_and_format(
Expand All @@ -549,12 +568,33 @@ def check_talk_awake(content: str):
data["insert_index"] = -1
my_handle.reread_handle(data)
else:
# 如果启用了“打断对话”功能
if config.get("talk", "interrupt_talk", "enable"):
# 判断文本内容是否包含中断词
interrupt_word = common.find_substring_in_list(
data["content"], config.get("talk", "interrupt_talk", "keywords")
)
if interrupt_word:
logger.info(f"[聊天中断] 命中中断词:{interrupt_word}")
# 从配置中获取需要清除的数据类型
clean_type = config.get("talk", "interrupt_talk", "clean_type")
# 各类型数据是否清除
message_queue = "message_queue" in clean_type
voice_tmp_path_queue = "voice_tmp_path_queue" in clean_type
stop_audio_play = "stop_audio_play" in clean_type

clear_queue_and_stop_audio_play(message_queue, voice_tmp_path_queue, stop_audio_play)
return False

# 传递给my_handle进行进行后续一系列的处理
my_handle.process_data(data, "talk")

# 单次唤醒情况下,唤醒后关闭
if config.get("talk", "wakeup_sleep", "mode") == "单次唤醒":
is_talk_awake = False
# 睡眠情况下
else:
# 首次进入睡眠 播放睡眠文案
if check_resp["first"]:
resp_json = common.get_random_str_in_list_and_format(
ori_list=config.get(
Expand Down
50 changes: 46 additions & 4 deletions utils/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,50 @@ def __init__(self, config_path, type=1):
if self.config.get("visual_body") == "live2d-TTS-LLM-GPT-SoVITS-Vtuber":
pass

# 启动WS,对接live2d-TTS-LLM-GPT-SoVITS-Vtuber
# def
# 清空 待合成消息队列|待播放音频队列
def clear_queue(self, type: str="message_queue"):
"""清空 待合成消息队列|待播放音频队列
Args:
type (str, optional): 队列类型. Defaults to "message_queue".
Returns:
bool: 清空结果
"""
try:
if type == "voice_tmp_path_queue":
if len(Audio.voice_tmp_path_queue) == 0:
return True
with self.voice_tmp_path_queue_lock:
Audio.voice_tmp_path_queue.clear()
return True
elif type == "message_queue":
if len(Audio.message_queue) == 0:
return True
with self.message_queue_lock:
Audio.message_queue.clear()
return True
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"清空{type}队列失败:{e}")
return False

# 停止音频播放
def stop_audio(self, type: str="pygame", mixer_normal: bool=True, mixer_copywriting: bool=True):
try:
if type == "pygame":
if mixer_normal:
Audio.mixer_normal.music.stop()
logger.info("停止普通音频播放")
if mixer_copywriting:
Audio.mixer_copywriting.music.stop()
logger.info("停止文案音频播放")
return True
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"停止音频播放失败:{e}")
return False


# 判断 等待合成消息队列|待播放音频队列 数是否小于或大于某个值,就返回True
def is_queue_less_or_greater_than(self, type: str="message_queue", less: int=None, greater: int=None):
Expand Down Expand Up @@ -604,12 +646,12 @@ def get_priority_level(data_json):

# 待播放音频数量大于首次播放阈值 且 处于首次播放情况下:
if len(Audio.voice_tmp_path_queue) >= int(self.config.get("filter", "voice_tmp_path_queue_min_start_play")) and \
Audio.voice_tmp_path_queue_not_empty_flag == False:
Audio.voice_tmp_path_queue_not_empty_flag is False:
Audio.voice_tmp_path_queue_not_empty_flag = True
# 生产者通过notify()通知消费者列表中有新的消息
Audio.voice_tmp_path_queue_not_empty.notify()
# 非首次触发情况下,有数据就触发消费者播放
elif Audio.voice_tmp_path_queue_not_empty_flag == True:
elif Audio.voice_tmp_path_queue_not_empty_flag:
# 生产者通过notify()通知消费者列表中有新的消息
Audio.voice_tmp_path_queue_not_empty.notify()

Expand Down
26 changes: 26 additions & 0 deletions utils/my_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,32 @@ def __init__(self, config_path):
except Exception as e:
logger.error(traceback.format_exc())

# 清空 待合成消息队列|待播放音频队列
def clear_queue(self, type: str="message_queue"):
"""清空 待合成消息队列|待播放音频队列
Args:
type (str, optional): 队列类型. Defaults to "message_queue".
Returns:
bool: 清空结果
"""
try:
return My_handle.audio.clear_queue(type)
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"清空{type}队列失败:{e}")
return False

# 停止音频播放
def stop_audio(self, type: str="pygame", mixer_normal: bool=True, mixer_copywriting: bool=True):
try:
return My_handle.audio.stop_audio(type, mixer_normal, mixer_copywriting)
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"停止音频播放失败:{e}")
return False

# 周期性触发数据处理,每秒执行一次,进行计时
def periodic_trigger_data_handle(self):
def get_last_n_items(data_list: list, num: int):
Expand Down
51 changes: 51 additions & 0 deletions webui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,18 @@ def update_config(config_mapping, config, config_data, type="common_config"):
聊天
"""
if True:
tmp_arr = []
for index in range(len(talk_interrupt_clean_type_var)):
if talk_interrupt_clean_type_var[str(index)].value:
tmp_arr.append(
common.find_keys_by_value(
talk_interrupt_clean_type_mapping,
talk_interrupt_clean_type_var[str(index)].text
)[0]
)
# logger.info(tmp_arr)
config_data["talk"]["interrupt_talk"]["clean_type"] = tmp_arr

config_mapping = {
"talk": {
"key_listener_enable": (switch_talk_key_listener_enable, 'bool'),
Expand All @@ -2961,6 +2973,10 @@ def update_config(config_mapping, config, config_data, type="common_config"):
"CHANNELS": (input_talk_silence_CHANNELS, 'int'),
"RATE": (input_talk_silence_RATE, 'int'),
"show_chat_log": (switch_talk_show_chat_log, 'bool'),
"interrupt_talk": {
"enable": (switch_talk_interrupt_talk_enable, 'bool'),
"keywords": (textarea_talk_interrupt_talk_keywords, 'textarea'),
},
"wakeup_sleep": {
"enable": (switch_talk_wakeup_sleep_enable, 'bool'),
"mode": (select_talk_wakeup_sleep_mode, 'str'),
Expand Down Expand Up @@ -6594,6 +6610,41 @@ async def talk_chat_box_tuning():
button_talk_chat_box_tuning = ui.button('调教', on_click=lambda: talk_chat_box_tuning(), color=button_internal_color).style(button_internal_css).tooltip("发送文本给LLM,但不会进行TTS等操作")
button_talk_chat_box_reread_first = ui.button('直接复读-插队首', on_click=lambda: talk_chat_box_reread(0, "reread_top_priority"), color=button_internal_color).style(button_internal_css).tooltip("最高优先级 发送文本给内部机制,触发TTS 直接复读类型的消息")

with ui.expansion('对话打断', icon="settings", value=True).classes('w-2/3'):
with ui.row():
switch_talk_interrupt_talk_enable = ui.switch('启用', value=config.get("talk", "interrupt_talk", "enable")).style(switch_internal_css)
textarea_talk_interrupt_talk_keywords = ui.textarea(
label='打断关键词',
placeholder='如:等一下、住嘴 多个请换行分隔',
value=textarea_data_change(config.get("talk", "interrupt_talk", "keywords"))
).style("width:200px;").tooltip("打断关键词,当语句中包含出现这些词时,会中断对话,具体清除内容根据清除类型自定义")

with ui.card().style(card_css):
ui.label("清除类型")
with ui.row():
talk_interrupt_clean_type_list = [
"message_queue",
"voice_tmp_path_queue",
"audio_play"
]
talk_interrupt_clean_type_mapping = {
"message_queue": "待合成消息队列",
"voice_tmp_path_queue": "待播放音频队列",
"audio_play": "正在播放中的音频",
}
talk_interrupt_clean_type_var = {}

for index, talk_interrupt_clean_type in enumerate(talk_interrupt_clean_type_list):
if talk_interrupt_clean_type in config.get("talk", "interrupt_talk", "clean_type"):
talk_interrupt_clean_type_var[str(index)] = ui.checkbox(
text=talk_interrupt_clean_type_mapping[talk_interrupt_clean_type],
value=True
)
else:
talk_interrupt_clean_type_var[str(index)] = ui.checkbox(
text=talk_interrupt_clean_type_mapping[talk_interrupt_clean_type],
value=False
)
with ui.expansion('语音唤醒与睡眠', icon="settings", value=True).classes('w-2/3'):
with ui.row():
switch_talk_wakeup_sleep_enable = ui.switch('启用', value=config.get("talk", "wakeup_sleep", "enable")).style(switch_internal_css)
Expand Down

0 comments on commit d0ca64b

Please sign in to comment.