import json import socket import subprocess import re import requests from typing import Dict, Any from core.utils import tts, llm, intent, memory, vad, asr TAG = __name__ def get_local_ip(): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Connect to Google's DNS servers s.connect(("8.8.8.8", 80)) local_ip = s.getsockname()[0] s.close() return local_ip except Exception as e: return "127.0.0.1" def is_private_ip(ip_addr): """ Check if an IP address is a private IP address (compatible with IPv4 and IPv6). @param {string} ip_addr - The IP address to check. @return {bool} True if the IP address is private, False otherwise. """ try: # Validate IPv4 or IPv6 address format if not re.match( r"^(\d{1,3}\.){3}\d{1,3}$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", ip_addr ): return False # Invalid IP address format # IPv4 private address ranges if "." in ip_addr: # IPv4 address ip_parts = list(map(int, ip_addr.split("."))) if ip_parts[0] == 10: return True # 10.0.0.0/8 range elif ip_parts[0] == 172 and 16 <= ip_parts[1] <= 31: return True # 172.16.0.0/12 range elif ip_parts[0] == 192 and ip_parts[1] == 168: return True # 192.168.0.0/16 range elif ip_addr == "127.0.0.1": return True # Loopback address elif ip_parts[0] == 169 and ip_parts[1] == 254: return True # Link-local address 169.254.0.0/16 else: return False # Not a private IPv4 address else: # IPv6 address ip_addr = ip_addr.lower() if ip_addr.startswith("fc00:") or ip_addr.startswith("fd00:"): return True # Unique Local Addresses (FC00::/7) elif ip_addr == "::1": return True # Loopback address elif ip_addr.startswith("fe80:"): return True # Link-local unicast addresses (FE80::/10) else: return False # Not a private IPv6 address except (ValueError, IndexError): return False # IP address format error or insufficient segments def get_ip_info(ip_addr, logger): try: if is_private_ip(ip_addr): ip_addr = "" url = f"https://whois.pconline.com.cn/ipJson.jsp?json=true&ip={ip_addr}" resp = requests.get(url).json() ip_info = {"city": resp.get("city")} return ip_info except Exception as e: logger.bind(tag=TAG).error(f"Error getting client ip info: {e}") return {} def write_json_file(file_path, data): """将数据写入 JSON 文件""" with open(file_path, "w", encoding="utf-8") as file: json.dump(data, file, ensure_ascii=False, indent=4) def is_punctuation_or_emoji(char): """检查字符是否为空格、指定标点或表情符号""" # 定义需要去除的中英文标点(包括全角/半角) punctuation_set = { ",", ",", # 中文逗号 + 英文逗号 "。", ".", # 中文句号 + 英文句号 "!", "!", # 中文感叹号 + 英文感叹号 "-", "-", # 英文连字符 + 中文全角横线 "、", # 中文顿号 } if char.isspace() or char in punctuation_set: return True # 检查表情符号(保留原有逻辑) code_point = ord(char) emoji_ranges = [ (0x1F600, 0x1F64F), (0x1F300, 0x1F5FF), (0x1F680, 0x1F6FF), (0x1F900, 0x1F9FF), (0x1FA70, 0x1FAFF), (0x2600, 0x26FF), (0x2700, 0x27BF), ] return any(start <= code_point <= end for start, end in emoji_ranges) def get_string_no_punctuation_or_emoji(s): """去除字符串首尾的空格、标点符号和表情符号""" chars = list(s) # 处理开头的字符 start = 0 while start < len(chars) and is_punctuation_or_emoji(chars[start]): start += 1 # 处理结尾的字符 end = len(chars) - 1 while end >= start and is_punctuation_or_emoji(chars[end]): end -= 1 return "".join(chars[start : end + 1]) def remove_punctuation_and_length(text): # 全角符号和半角符号的Unicode范围 full_width_punctuations = ( "!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~" ) half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~' space = " " # 半角空格 full_width_space = " " # 全角空格 # 去除全角和半角符号以及空格 result = "".join( [ char for char in text if char not in full_width_punctuations and char not in half_width_punctuations and char not in space and char not in full_width_space ] ) if result == "Yeah": return 0, "" return len(result), result def check_model_key(modelType, modelKey): if "你" in modelKey: raise ValueError( "你还没配置" + modelType + "的密钥,请检查一下所使用的LLM是否配置了密钥" ) return True def parse_string_to_list(value, separator=";"): """ 将输入值转换为列表 Args: value: 输入值,可以是 None、字符串或列表 separator: 分隔符,默认为分号 Returns: list: 处理后的列表 """ if value is None or value == "": return [] elif isinstance(value, str): return [item.strip() for item in value.split(separator) if item.strip()] elif isinstance(value, list): return value return [] def check_ffmpeg_installed(): ffmpeg_installed = False try: # 执行ffmpeg -version命令,并捕获输出 result = subprocess.run( ["ffmpeg", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True, # 如果返回码非零则抛出异常 ) # 检查输出中是否包含版本信息(可选) output = result.stdout + result.stderr if "ffmpeg version" in output.lower(): ffmpeg_installed = True return False except (subprocess.CalledProcessError, FileNotFoundError): # 命令执行失败或未找到 ffmpeg_installed = False if not ffmpeg_installed: error_msg = "您的电脑还没正确安装ffmpeg\n" error_msg += "\n建议您:\n" error_msg += "1、按照项目的安装文档,正确进入conda环境\n" error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n" raise ValueError(error_msg) def extract_json_from_string(input_string): """提取字符串中的 JSON 部分""" pattern = r"(\{.*\})" match = re.search(pattern, input_string, re.DOTALL) # 添加 re.DOTALL if match: return match.group(1) # 返回提取的 JSON 字符串 return None def initialize_modules( logger, config: Dict[str, Any], init_vad=False, init_asr=False, init_llm=False, init_tts=False, init_memory=False, init_intent=False, ) -> Dict[str, Any]: """ 初始化所有模块组件 Args: config: 配置字典 Returns: Dict[str, Any]: 包含所有初始化后的模块的字典 """ modules = {} # 初始化TTS模块 if init_tts: select_tts_module = config["selected_module"]["TTS"] tts_type = ( select_tts_module if "type" not in config["TTS"][select_tts_module] else config["TTS"][select_tts_module]["type"] ) modules["tts"] = tts.create_instance( tts_type, config["TTS"][select_tts_module], str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"), ) logger.bind(tag=TAG).info(f"初始化组件: tts成功 {select_tts_module}") # 初始化LLM模块 if init_llm: select_llm_module = config["selected_module"]["LLM"] llm_type = ( select_llm_module if "type" not in config["LLM"][select_llm_module] else config["LLM"][select_llm_module]["type"] ) modules["llm"] = llm.create_instance( llm_type, config["LLM"][select_llm_module], ) logger.bind(tag=TAG).info(f"初始化组件: llm成功 {select_llm_module}") # 初始化Intent模块 if init_intent: select_intent_module = config["selected_module"]["Intent"] intent_type = ( select_intent_module if "type" not in config["Intent"][select_intent_module] else config["Intent"][select_intent_module]["type"] ) modules["intent"] = intent.create_instance( intent_type, config["Intent"][select_intent_module], ) logger.bind(tag=TAG).info(f"初始化组件: intent成功 {select_intent_module}") # 初始化Memory模块 if init_memory: select_memory_module = config["selected_module"]["Memory"] memory_type = ( select_memory_module if "type" not in config["Memory"][select_memory_module] else config["Memory"][select_memory_module]["type"] ) modules["memory"] = memory.create_instance( memory_type, config["Memory"][select_memory_module], ) logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}") # 初始化VAD模块 if init_vad: select_vad_module = config["selected_module"]["VAD"] vad_type = ( select_vad_module if "type" not in config["VAD"][select_vad_module] else config["VAD"][select_vad_module]["type"] ) modules["vad"] = vad.create_instance( vad_type, config["VAD"][select_vad_module], ) logger.bind(tag=TAG).info(f"初始化组件: vad成功 {select_vad_module}") # 初始化ASR模块 if init_asr: select_asr_module = config["selected_module"]["ASR"] asr_type = ( select_asr_module if "type" not in config["ASR"][select_asr_module] else config["ASR"][select_asr_module]["type"] ) modules["asr"] = asr.create_instance( asr_type, config["ASR"][select_asr_module], str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"), ) logger.bind(tag=TAG).info(f"初始化组件: asr成功 {select_asr_module}") # 初始化自定义prompt if config.get("prompt", None) is not None: modules["prompt"] = config["prompt"] logger.bind(tag=TAG).info(f"初始化组件: prompt成功 {modules['prompt'][:50]}...") return modules