1 line
20 KiB
JSON
1 line
20 KiB
JSON
{"repo": "brenchcc/cloudpet", "files": {"toolbox.py": {"851962d055215bda70e04e7d6e894fe9da71317a": {"t": 1785768952.3324454, "n": 16788, "h": "296610eba8f6c9ec", "c": "import markdown\nimport importlib\nimport traceback\nimport inspect\nimport re\nfrom latex2mathml.converter import convert as tex2mathml\nfrom functools import wraps, lru_cache\n############################### 插件输入输出接驳区 #######################################\nclass ChatBotWithCookies(list):\n def __init__(self, cookie):\n self._cookies = cookie\n\n def write_list(self, list):\n for t in list:\n self.append(t)\n\n def get_list(self):\n return [t for t in self]\n\n def get_cookies(self):\n return self._cookies\n\ndef ArgsGeneralWrapper(f):\n \"\"\"\n 装饰器函数,用于重组输入参数,改变输入参数的顺序与结构。\n \"\"\"\n def decorated(cookies, max_length, llm_model, txt, txt2, top_p, temperature, chatbot, history, system_prompt, *args):\n txt_passon = txt\n if txt == \"\" and txt2 != \"\": txt_passon = txt2\n # 引入一个有cookie的chatbot\n cookies.update({\n 'top_p':top_p, \n 'temperature':temperature,\n })\n llm_kwargs = {\n 'api_key': cookies['api_key'],\n 'llm_model': llm_model,\n 'top_p':top_p, \n 'max_length': max_length,\n 'temperature':temperature,\n }\n plugin_kwargs = {\n # 目前还没有\n }\n chatbot_with_cookie = ChatBotWithCookies(cookies)\n chatbot_with_cookie.write_list(chatbot)\n yield from f(txt_passon, llm_kwargs, plugin_kwargs, chatbot_with_cookie, history, system_prompt, *args)\n return decorated\n\ndef update_ui(chatbot, history, msg='正常', **kwargs): # 刷新界面\n \"\"\"\n 刷新用户界面\n \"\"\"\n assert isinstance(chatbot, ChatBotWithCookies), \"在传递chatbot的过程中不要将其丢弃。必要时,可用clear将其清空,然后用for+append循环重新赋值。\"\n yield chatbot.get_cookies(), chatbot, history, msg\n\ndef CatchException(f):\n \"\"\"\n 装饰器函数,捕捉函数f中的异常并封装到一个生成器中返回,并显示到聊天当中。\n \"\"\"\n @wraps(f)\n def decorated(txt, top_p, temperature, chatbot, history, systemPromptTxt, WEB_PORT):\n try:\n yield from f(txt, top_p, temperature, chatbot, history, systemPromptTxt, WEB_PORT)\n except Exception as e:\n from check_proxy import check_proxy\n from toolbox import get_conf\n proxies, = get_conf('proxies')\n tb_str = '```\\n' + traceback.format_exc() + '```'\n if chatbot is None or len(chatbot) == 0:\n chatbot = [[\"插件调度异常\", \"异常原因\"]]\n chatbot[-1] = (chatbot[-1][0],\n f\"[Local Message] 实验性函数调用出错: \\n\\n{tb_str} \\n\\n当前代理可用性: \\n\\n{check_proxy(proxies)}\")\n yield from update_ui(chatbot=chatbot, history=history, msg=f'异常 {e}') # 刷新界面\n return decorated\n\n\ndef HotReload(f):\n \"\"\"\n HotReload的装饰器函数,用于实现Python函数插件的热更新。\n 函数热更新是指在不停止程序运行的情况下,更新函数代码,从而达到实时更新功能。\n 在装饰器内部,使用wraps(f)来保留函数的元信息,并定义了一个名为decorated的内部函数。\n 内部函数通过使用importlib模块的reload函数和inspect模块的getmodule函数来重新加载并获取函数模块,\n 然后通过getattr函数获取函数名,并在新模块中重新加载函数。\n 最后,使用yield from语句返回重新加载过的函数,并在被装饰的函数上执行。\n 最终,装饰器函数返回内部函数。这个内部函数可以将函数的原始定义更新为最新版本,并执行函数的新版本。\n \"\"\"\n @wraps(f)\n def decorated(*args, **kwargs):\n fn_name = f.__name__\n f_hot_reload = getattr(importlib.reload(inspect.getmodule(f)), fn_name)\n yield from f_hot_reload(*args, **kwargs)\n return decorated\n\n\n####################################### 其他小工具 #####################################\n\ndef get_reduce_token_percent(text):\n \"\"\"\n * 此函数未来将被弃用\n \"\"\"\n try:\n # text = \"maximum context length is 4097 tokens. However, your messages resulted in 4870 tokens\"\n pattern = r\"(\\d+)\\s+tokens\\b\"\n match = re.findall(pattern, text)\n EXCEED_ALLO = 500 # 稍微留一点余地,否则在回复时会因余量太少出问题\n max_limit = float(match[0]) - EXCEED_ALLO\n current_tokens = float(match[1])\n ratio = max_limit/current_tokens\n assert ratio > 0 and ratio < 1\n return ratio, str(int(current_tokens-max_limit))\n except:\n return 0.5, '不详'\n\n\n\ndef write_results_to_file(history, file_name=None):\n \"\"\"\n 将对话记录history以Markdown格式写入文件中。如果没有指定文件名,则使用当前时间生成文件名。\n \"\"\"\n import os\n import time\n if file_name is None:\n # file_name = time.strftime(\"chatGPT分析报告%Y-%m-%d-%H-%M-%S\", time.localtime()) + '.md'\n file_name = 'chatGPT分析报告' + \\\n time.strftime(\"%Y-%m-%d-%H-%M-%S\", time.localtime()) + '.md'\n os.makedirs('./gpt_log/', exist_ok=True)\n with open(f'./gpt_log/{file_name}', 'w', encoding='utf8') as f:\n f.write('# chatGPT 分析报告\\n')\n for i, content in enumerate(history):\n try: # 这个bug没找到触发条件,暂时先这样顶一下\n if type(content) != str:\n content = str(content)\n except:\n continue\n if i % 2 == 0:\n f.write('## ')\n f.write(content)\n f.write('\\n\\n')\n res = '以上材料已经被写入' + os.path.abspath(f'./gpt_log/{file_name}')\n print(res)\n return res\n\n\ndef regular_txt_to_markdown(text):\n \"\"\"\n 将普通文本转换为Markdown格式的文本。\n \"\"\"\n text = text.replace('\\n', '\\n\\n')\n text = text.replace('\\n\\n\\n', '\\n\\n')\n text = text.replace('\\n\\n\\n', '\\n\\n')\n return text\n\n\n\n\ndef report_execption(chatbot, history, a, b):\n \"\"\"\n 向chatbot中添加错误信息\n \"\"\"\n chatbot.append((a, b))\n history.append(a)\n history.append(b)\n\n\ndef text_divide_paragraph(text):\n \"\"\"\n 将文本按照段落分隔符分割开,生成带有段落标签的HTML代码。\n \"\"\"\n if '```' in text:\n # careful input\n return text\n else:\n # wtf input\n lines = text.split(\"\\n\")\n for i, line in enumerate(lines):\n lines[i] = lines[i].replace(\" \", \" \")\n text = \"</br>\".join(lines)\n return text\n\n\ndef markdown_convertion(txt):\n \"\"\"\n 将Markdown格式的文本转换为HTML格式。如果包含数学公式,则先将公式转换为HTML格式。\n \"\"\"\n pre = '<div class=\"markdown-body\">'\n suf = '</div>'\n markdown_extension_configs = {\n 'mdx_math': {\n 'enable_dollar_delimiter': True,\n 'use_gitlab_delimiters': False,\n },\n }\n find_equation_pattern = r'<script type=\"math/tex(?:.*?)>(.*?)</script>'\n\n def tex2mathml_catch_exception(content, *args, **kwargs):\n try:\n content = tex2mathml(content, *args, **kwargs)\n except:\n content = content\n return content\n\n def replace_math_no_render(match):\n content = match.group(1)\n if 'mode=display' in match.group(0):\n content = content.replace('\\n', '</br>')\n return f\"<font color=\\\"#00FF00\\\">$$</font><font color=\\\"#FF00FF\\\">{content}</font><font color=\\\"#00FF00\\\">$$</font>\"\n else:\n return f\"<font color=\\\"#00FF00\\\">$</font><font color=\\\"#FF00FF\\\">{content}</font><font color=\\\"#00FF00\\\">$</font>\"\n\n def replace_math_render(match):\n content = match.group(1)\n if 'mode=display' in match.group(0):\n if '\\\\begin{aligned}' in content:\n content = content.replace('\\\\begin{aligned}', '\\\\begin{array}')\n content = content.replace('\\\\end{aligned}', '\\\\end{array}')\n content = content.replace('&', ' ')\n content = tex2mathml_catch_exception(content, display=\"block\")\n return content\n else:\n return tex2mathml_catch_exception(content)\n \n def markdown_bug_hunt(content):\n \"\"\"\n 解决一个mdx_math的bug(单$包裹begin命令时多余<script>)\n \"\"\"\n content = content.replace('<script type=\"math/tex\">\\n<script type=\"math/tex; mode=display\">', '<script type=\"math/tex; mode=display\">')\n content = content.replace('</script>\\n</script>', '</script>')\n return content\n \n\n if ('$' in txt) and ('```' not in txt): # 有$标识的公式符号,且没有代码段```的标识\n # convert everything to html format\n split = markdown.markdown(text='---')\n convert_stage_1 = markdown.markdown(text=txt, extensions=['mdx_math', 'fenced_code', 'tables', 'sane_lists'], extension_configs=markdown_extension_configs)\n convert_stage_1 = markdown_bug_hunt(convert_stage_1)\n # re.DOTALL: Make the '.' special character match any character at all, including a newline; without this flag, '.' will match anything except a newline. Corresponds to the inline flag (?s).\n # 1. convert to easy-to-copy tex (do not render math)\n convert_stage_2_1, n = re.subn(find_equation_pattern, replace_math_no_render, convert_stage_1, flags=re.DOTALL)\n # 2. convert to rendered equation\n convert_stage_2_2, n = re.subn(find_equation_pattern, replace_math_render, convert_stage_1, flags=re.DOTALL)\n # cat them together\n return pre + convert_stage_2_1 + f'{split}' + convert_stage_2_2 + suf\n else:\n return pre + markdown.markdown(txt, extensions=['fenced_code', 'codehilite', 'tables', 'sane_lists']) + suf\n\n\ndef close_up_code_segment_during_stream(gpt_reply):\n \"\"\"\n 在gpt输出代码的中途(输出了前面的```,但还没输出完后面的```),补上后面的```\n \n Args:\n gpt_reply (str): GPT模型返回的回复字符串。\n\n Returns:\n str: 返回一个新的字符串,将输出代码片段的“后面的```”补上。\n\n \"\"\"\n if '```' not in gpt_reply:\n return gpt_reply\n if gpt_reply.endswith('```'):\n return gpt_reply\n\n # 排除了以上两个情况,我们\n segments = gpt_reply.split('```')\n n_mark = len(segments) - 1\n if n_mark % 2 == 1:\n # print('输出代码片段中!')\n return gpt_reply+'\\n```'\n else:\n return gpt_reply\n\n\ndef format_io(self, y):\n \"\"\"\n 将输入和输出解析为HTML格式。将y中最后一项的输入部分段落化,并将输出部分的Markdown和数学公式转换为HTML格式。\n \"\"\"\n if y is None or y == []:\n return []\n i_ask, gpt_reply = y[-1]\n i_ask = text_divide_paragraph(i_ask) # 输入部分太自由,预处理一波\n gpt_reply = close_up_code_segment_during_stream(gpt_reply) # 当代码输出半截的时候,试着补上后个```\n y[-1] = (\n None if i_ask is None else markdown.markdown(i_ask, extensions=['fenced_code', 'tables']),\n None if gpt_reply is None else markdown_convertion(gpt_reply)\n )\n return y\n\n\ndef find_free_port():\n \"\"\"\n 返回当前系统中可用的未使用端口。\n \"\"\"\n import socket\n from contextlib import closing\n with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:\n s.bind(('', 0))\n s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n return s.getsockname()[1]\n\n\ndef extract_archive(file_path, dest_dir):\n import zipfile\n import tarfile\n import os\n # Get the file extension of the input file\n file_extension = os.path.splitext(file_path)[1]\n\n # Extract the archive based on its extension\n if file_extension == '.zip':\n with zipfile.ZipFile(file_path, 'r') as zipobj:\n zipobj.extractall(path=dest_dir)\n print(\"Successfully extracted zip archive to {}\".format(dest_dir))\n\n elif file_extension in ['.tar', '.gz', '.bz2']:\n with tarfile.open(file_path, 'r:*') as tarobj:\n tarobj.extractall(path=dest_dir)\n print(\"Successfully extracted tar archive to {}\".format(dest_dir))\n\n # 第三方库,需要预先pip install rarfile\n # 此外,Windows上还需要安装winrar软件,配置其Path环境变量,如\"C:\\Program Files\\WinRAR\"才可以\n elif file_extension == '.rar':\n try:\n import rarfile\n with rarfile.RarFile(file_path) as rf:\n rf.extractall(path=dest_dir)\n print(\"Successfully extracted rar archive to {}\".format(dest_dir))\n except:\n print(\"Rar format requires additional dependencies to install\")\n return '\\n\\n需要安装pip install rarfile来解压rar文件'\n\n # 第三方库,需要预先pip install py7zr\n elif file_extension == '.7z':\n try:\n import py7zr\n with py7zr.SevenZipFile(file_path, mode='r') as f:\n f.extractall(path=dest_dir)\n print(\"Successfully extracted 7z archive to {}\".format(dest_dir))\n except:\n print(\"7z format requires additional dependencies to install\")\n return '\\n\\n需要安装pip install py7zr来解压7z文件'\n else:\n return ''\n return ''\n\n\ndef find_recent_files(directory):\n \"\"\"\n me: find files that is created with in one minutes under a directory with python, write a function\n gpt: here it is!\n \"\"\"\n import os\n import time\n current_time = time.time()\n one_minute_ago = current_time - 60\n recent_files = []\n\n for filename in os.listdir(directory):\n file_path = os.path.join(directory, filename)\n if file_path.endswith('.log'):\n continue\n created_time = os.path.getmtime(file_path)\n if created_time >= one_minute_ago:\n if os.path.isdir(file_path):\n continue\n recent_files.append(file_path)\n\n return recent_files\n\n\ndef on_file_uploaded(files, chatbot, txt, txt2, checkboxes):\n if len(files) == 0:\n return chatbot, txt\n import shutil\n import os\n import time\n import glob\n from toolbox import extract_archive\n try:\n shutil.rmtree('./private_upload/')\n except:\n pass\n time_tag = time.strftime(\"%Y-%m-%d-%H-%M-%S\", time.localtime())\n os.makedirs(f'private_upload/{time_tag}', exist_ok=True)\n err_msg = ''\n for file in files:\n file_origin_name = os.path.basename(file.orig_name)\n shutil.copy(file.name, f'private_upload/{time_tag}/{file_origin_name}')\n err_msg += extract_archive(f'private_upload/{time_tag}/{file_origin_name}',\n dest_dir=f'private_upload/{time_tag}/{file_origin_name}.extract')\n moved_files = [fp for fp in glob.glob(\n 'private_upload/**/*', recursive=True)]\n if \"底部输入区\" in checkboxes:\n txt = \"\"\n txt2 = f'private_upload/{time_tag}'\n else:\n txt = f'private_upload/{time_tag}'\n txt2 = \"\"\n moved_files_str = '\\t\\n\\n'.join(moved_files)\n chatbot.append(['我上传了文件,请查收',\n f'[Local Message] 收到以下文件: \\n\\n{moved_files_str}' +\n f'\\n\\n调用路径参数已自动修正到: \\n\\n{txt}' +\n f'\\n\\n现在您点击任意“红颜色”标识的函数插件时,以上文件将被作为输入参数'+err_msg])\n return chatbot, txt, txt2\n\n\ndef on_report_generated(files, chatbot):\n from toolbox import find_recent_files\n report_files = find_recent_files('gpt_log')\n if len(report_files) == 0:\n return None, chatbot\n # files.extend(report_files)\n chatbot.append(['汇总报告如何远程获取?', '汇总报告已经添加到右侧“文件上传区”(可能处于折叠状态),请查收。'])\n return report_files, chatbot\n\ndef is_openai_api_key(key):\n API_MATCH = re.match(r\"sk-[a-zA-Z0-9]{48}$\", key)\n return bool(API_MATCH)\n\ndef is_api2d_key(key):\n if key.startswith('fk') and len(key) == 41:\n return True\n else:\n return False\n\ndef is_any_api_key(key):\n if ',' in key:\n keys = key.split(',')\n for k in keys:\n if is_any_api_key(k): return True\n return False\n else:\n return is_openai_api_key(key) or is_api2d_key(key)\n\n\ndef select_api_key(keys, llm_model):\n import random\n avail_key_list = []\n key_list = keys.split(',')\n\n if llm_model.startswith('gpt-'):\n for k in key_list:\n if is_openai_api_key(k): avail_key_list.append(k)\n\n if llm_model.startswith('api2d-'):\n for k in key_list:\n if is_api2d_key(k): avail_key_list.append(k)\n\n if len(avail_key_list) == 0:\n raise RuntimeError(f\"您提供的api-key不满足要求,不包含任何可用于{llm_model}的api-key。\")\n\n api_key = random.choice(avail_key_list) # 随机负载均衡\n return api_key\n\n@lru_cache(maxsize=128)\ndef read_single_conf_with_lru_cache(arg):\n from colorful import print亮红, print亮绿\n try:\n r = getattr(importlib.import_module('config_private'), arg)\n except:\n r = getattr(importlib.import_module('config'), arg)\n\n # 在读取API_KEY时,检查一下是不是忘了改config\n if arg == 'API_KEY':\n if is_any_api_key(r):\n print亮绿(f\"[API_KEY] 您的 API_KEY 是: {r[:15]}*** API_KEY 导入成功\")\n else:\n print亮红( \"[API_KEY] 正确的 API_KEY 是'sk'开头的51位密钥(OpenAI),或者 'fk'开头的41位密钥,请在config文件中修改API密钥之后再运行。\")\n\n if arg == 'proxies':\n if r is None:\n print亮红('[PROXY] 网络代理状态:未配置。无代理状态下很可能无法访问OpenAI家族的模型。建议:检查USE_PROXY选项是否修改。')\n else:\n print亮绿('[PROXY] 网络代理状态:已配置。配置信息如下:', r)\n assert isinstance(r, dict), 'proxies格式错误,请注意proxies选项的格式,不要遗漏括号。'\n return r\n\n\ndef get_conf(*args):\n # 建议您复制一个config_private.py放自己的秘密, 如API和代理网址, 避免不小心传github被别人看到\n res = []\n for arg in args:\n r = read_single_conf_with_lru_cache(arg)\n res.append(r)\n return res\n\n\ndef clear_line_break(txt):\n txt = txt.replace('\\n', ' ')\n txt = txt.replace(' ', ' ')\n txt = txt.replace(' ', ' ')\n return txt\n\n\nclass DummyWith():\n \"\"\"\n 这段代码定义了一个名为DummyWith的空上下文管理器,\n 它的作用是……额……没用,即在代码结构不变得情况下取代其他的上下文管理器。\n 上下文管理器是一种Python对象,用于与with语句一起使用,\n 以确保一些资源在代码块执行期间得到正确的初始化和清理。\n 上下文管理器必须实现两个方法,分别为 __enter__()和 __exit__()。 \n 在上下文执行开始的情况下,__enter__()方法会在代码块被执行前被调用,\n 而在上下文执行结束时,__exit__()方法则会被调用。\n \"\"\"\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc_value, traceback):\n return\n"}}}} |