diff --git a/CITATION.cff b/CITATION.cff index c1b2475a4d12546ffe61d3d2530e954cc43a0563..ea3e1503a4aff8e954bb36b1bba6370f81f239f6 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,5 +1,5 @@ cff-version: 1.2.0 -title: ChuanhuChatGPT +title: Chuanhu Chat message: >- If you use this software, please cite it using these metadata. @@ -13,8 +13,8 @@ authors: orcid: https://orcid.org/0009-0005-0357-272X repository-code: 'https://github.com/GaiZhenbiao/ChuanhuChatGPT' url: 'https://github.com/GaiZhenbiao/ChuanhuChatGPT' -abstract: Provided a light and easy to use interface for ChatGPT API +abstract: This software provides a light and easy-to-use interface for ChatGPT API and many LLMs. license: GPL-3.0 -commit: bd0034c37e5af6a90bd9c2f7dd073f6cd27c61af -version: '20230405' -date-released: '2023-04-05' +commit: c6c08bc62ef80e37c8be52f65f9b6051a7eea1fa +version: '20230709' +date-released: '2023-07-09' diff --git a/ChuanhuChatbot.py b/ChuanhuChatbot.py index 890e5c7ec70f26a0452ded3e33cd56f488819932..d498359af5c02037247406830672bcbbdbb7006b 100644 --- a/ChuanhuChatbot.py +++ b/ChuanhuChatbot.py @@ -1,8 +1,11 @@ # -*- coding:utf-8 -*- -import os import logging -import sys +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s", +) +import colorama import gradio as gr from modules import config @@ -10,6 +13,9 @@ from modules.config import * from modules.utils import * from modules.presets import * from modules.overwrites import * +from modules.webui import * +from modules.repo import * +from modules.train_func import * from modules.models.models import get_model logging.getLogger("httpx").setLevel(logging.WARNING) @@ -17,13 +23,13 @@ logging.getLogger("httpx").setLevel(logging.WARNING) gr.Chatbot._postprocess_chat_messages = postprocess_chat_messages gr.Chatbot.postprocess = postprocess -with open("assets/custom.css", "r", encoding="utf-8") as f: - customCSS = f.read() +# with open("web_assets/css/ChuanhuChat.css", "r", encoding="utf-8") as f: +# ChuanhuChatCSS = f.read() def create_new_model(): return get_model(model_name = MODELS[DEFAULT_MODEL], access_key = my_api_key)[0] -with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: +with gr.Blocks(theme=small_and_beautiful_theme) as demo: user_name = gr.State("") promptTemplates = gr.State(load_template(get_template_names(plain=True)[0], mode=2)) user_question = gr.State("") @@ -34,31 +40,45 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: topic = gr.State(i18n("未命名对话历史记录")) with gr.Row(): - gr.HTML(CHUANHU_TITLE, elem_id="app_title") - status_display = gr.Markdown(get_geoip(), elem_id="status_display") - with gr.Row(elem_id="float_display"): - user_info = gr.Markdown(value="getting user info...", elem_id="user_info") - - with gr.Row().style(equal_height=True): + gr.HTML(CHUANHU_TITLE, elem_id="app-title") + status_display = gr.Markdown(get_geoip(), elem_id="status-display") + with gr.Row(elem_id="float-display"): + user_info = gr.Markdown(value="getting user info...", elem_id="user-info") + config_info = gr.HTML(get_html("config_info.html").format(bot_avatar=config.bot_avatar, user_avatar=config.user_avatar), visible=False, elem_id="config-info") + update_info = gr.HTML(get_html("update.html").format( + current_version=repo_tag_html(), + version_time=version_time(), + cancel_btn=i18n("取消"), + update_btn=i18n("更新"), + seenew_btn=i18n("详情"), + ok_btn=i18n("好"), + ), visible=check_update) + + with gr.Row(equal_height=True): with gr.Column(scale=5): with gr.Row(): - chatbot = gr.Chatbot(label="Chuanhu Chat", elem_id="chuanhu_chatbot").style(height="100%") + chatbot = gr.Chatbot(label="Chuanhu Chat", elem_id="chuanhu-chatbot", latex_delimiters=latex_delimiters_set, height=700) with gr.Row(): with gr.Column(min_width=225, scale=12): user_input = gr.Textbox( - elem_id="user_input_tb", - show_label=False, placeholder=i18n("在这里输入") - ).style(container=False) + elem_id="user-input-tb", + show_label=False, placeholder=i18n("在这里输入"), + container=False + ) with gr.Column(min_width=42, scale=1): - submitBtn = gr.Button(value="", variant="primary", elem_id="submit_btn") - cancelBtn = gr.Button(value="", variant="secondary", visible=False, elem_id="cancel_btn") - with gr.Row(): - emptyBtn = gr.Button( - i18n("🧹 新的对话"), elem_id="empty_btn" - ) - retryBtn = gr.Button(i18n("🔄 重新生成")) - delFirstBtn = gr.Button(i18n("🗑️ 删除最旧对话")) - delLastBtn = gr.Button(i18n("🗑️ 删除最新对话")) + submitBtn = gr.Button(value="", variant="primary", elem_id="submit-btn") + cancelBtn = gr.Button(value="", variant="secondary", visible=False, elem_id="cancel-btn") + with gr.Row(elem_id="chatbot-buttons"): + with gr.Column(min_width=120, scale=1): + emptyBtn = gr.Button( + i18n("🧹 新的对话"), elem_id="empty-btn" + ) + with gr.Column(min_width=120, scale=1): + retryBtn = gr.Button(i18n("🔄 重新生成")) + with gr.Column(min_width=120, scale=1): + delFirstBtn = gr.Button(i18n("🗑️ 删除最旧对话")) + with gr.Column(min_width=120, scale=1): + delLastBtn = gr.Button(i18n("🗑️ 删除最新对话")) with gr.Row(visible=False) as like_dislike_area: with gr.Column(min_width=20, scale=1): likeBtn = gr.Button(i18n("👍")) @@ -77,9 +97,9 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: label="API-Key", ) if multi_api_key: - usageTxt = gr.Markdown(i18n("多账号模式已开启,无需输入key,可直接开始对话"), elem_id="usage_display", elem_classes="insert_block") + usageTxt = gr.Markdown(i18n("多账号模式已开启,无需输入key,可直接开始对话"), elem_id="usage-display", elem_classes="insert-block", visible=show_api_billing) else: - usageTxt = gr.Markdown(i18n("**发送消息** 或 **提交key** 以显示额度"), elem_id="usage_display", elem_classes="insert_block") + usageTxt = gr.Markdown(i18n("**发送消息** 或 **提交key** 以显示额度"), elem_id="usage-display", elem_classes="insert-block", visible=show_api_billing) model_select_dropdown = gr.Dropdown( label=i18n("选择模型"), choices=MODELS, multiselect=False, value=MODELS[DEFAULT_MODEL], interactive=True ) @@ -87,15 +107,15 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: label=i18n("选择LoRA模型"), choices=[], multiselect=False, interactive=True, visible=False ) with gr.Row(): - single_turn_checkbox = gr.Checkbox(label=i18n("单轮对话"), value=False) - use_websearch_checkbox = gr.Checkbox(label=i18n("使用在线搜索"), value=False) + single_turn_checkbox = gr.Checkbox(label=i18n("单轮对话"), value=False, elem_classes="switch-checkbox") + use_websearch_checkbox = gr.Checkbox(label=i18n("使用在线搜索"), value=False, elem_classes="switch-checkbox") language_select_dropdown = gr.Dropdown( label=i18n("选择回复语言(针对搜索&索引功能)"), choices=REPLY_LANGUAGES, multiselect=False, value=REPLY_LANGUAGES[0], ) - index_files = gr.Files(label=i18n("上传"), type="file") + index_files = gr.Files(label=i18n("上传"), type="file", elem_id="upload-index-file") two_column = gr.Checkbox(label=i18n("双栏pdf"), value=advance_docs["pdf"].get("two_column", False)) summarize_btn = gr.Button(i18n("总结")) # TODO: 公式ocr @@ -107,8 +127,8 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: placeholder=i18n("在这里输入System Prompt..."), label="System prompt", value=INITIAL_SYSTEM_PROMPT, - lines=10, - ).style(container=False) + lines=10 + ) with gr.Accordion(label=i18n("加载Prompt模板"), open=True): with gr.Column(): with gr.Row(): @@ -118,7 +138,8 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: choices=get_template_names(plain=True), multiselect=False, value=get_template_names(plain=True)[0], - ).style(container=False) + container=False, + ) with gr.Column(scale=1): templateRefreshBtn = gr.Button(i18n("🔄 刷新")) with gr.Row(): @@ -129,7 +150,8 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: get_template_names(plain=True)[0], mode=1 ), multiselect=False, - ).style(container=False) + container=False, + ) with gr.Tab(label=i18n("保存/加载")): with gr.Accordion(label=i18n("保存/加载对话历史记录"), open=True): @@ -139,10 +161,14 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: historyFileSelectDropdown = gr.Dropdown( label=i18n("从列表中加载对话"), choices=get_history_names(plain=True), - multiselect=False + multiselect=False, + container=False, ) - with gr.Column(scale=1): - historyRefreshBtn = gr.Button(i18n("🔄 刷新")) + with gr.Row(): + with gr.Column(min_width=42, scale=1): + historyRefreshBtn = gr.Button(i18n("🔄 刷新")) + with gr.Column(min_width=42, scale=1): + historyDeleteBtn = gr.Button(i18n("🗑️ 删除")) with gr.Row(): with gr.Column(scale=6): saveFileName = gr.Textbox( @@ -150,7 +176,9 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: placeholder=i18n("设置文件名: 默认为.json,可选为.md"), label=i18n("设置保存文件名"), value=i18n("对话历史记录"), - ).style(container=True) + elem_classes="no-container" + # container=False, + ) with gr.Column(scale=1): saveHistoryBtn = gr.Button(i18n("💾 保存对话")) exportMarkdownBtn = gr.Button(i18n("📝 导出为Markdown")) @@ -159,12 +187,32 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: with gr.Column(): downloadFile = gr.File(interactive=True) + with gr.Tab(label=i18n("微调")): + openai_train_status = gr.Markdown(label=i18n("训练状态"), value=i18n("在这里[查看使用介绍](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B#%E5%BE%AE%E8%B0%83-gpt-35)")) + + with gr.Tab(label=i18n("准备数据集")): + dataset_preview_json = gr.JSON(label=i18n("数据集预览"), readonly=True) + dataset_selection = gr.Files(label = i18n("选择数据集"), file_types=[".xlsx", ".jsonl"], file_count="single") + upload_to_openai_btn = gr.Button(i18n("上传到OpenAI"), variant="primary", interactive=False) + + with gr.Tab(label=i18n("训练")): + openai_ft_file_id = gr.Textbox(label=i18n("文件ID"), value="", lines=1, placeholder=i18n("上传到 OpenAI 后自动填充")) + openai_ft_suffix = gr.Textbox(label=i18n("模型名称后缀"), value="", lines=1, placeholder=i18n("可选,用于区分不同的模型")) + openai_train_epoch_slider = gr.Slider(label=i18n("训练轮数(Epochs)"), minimum=1, maximum=100, value=3, step=1, interactive=True) + openai_start_train_btn = gr.Button(i18n("开始训练"), variant="primary", interactive=False) + + with gr.Tab(label=i18n("状态")): + openai_status_refresh_btn = gr.Button(i18n("刷新状态")) + openai_cancel_all_jobs_btn = gr.Button(i18n("取消所有任务")) + add_to_models_btn = gr.Button(i18n("添加训练好的模型到模型列表"), interactive=False) + with gr.Tab(label=i18n("高级")): - gr.Markdown(i18n("# ⚠️ 务必谨慎更改 ⚠️\n\n如果无法使用请恢复默认设置")) - gr.HTML(get_html("appearance_switcher.html").format(label=i18n("切换亮暗色主题")), elem_classes="insert_block") + gr.HTML(get_html("appearance_switcher.html").format(label=i18n("切换亮暗色主题")), elem_classes="insert-block") use_streaming_checkbox = gr.Checkbox( - label=i18n("实时传输回答"), value=True, visible=ENABLE_STREAMING_OPTION + label=i18n("实时传输回答"), value=True, visible=ENABLE_STREAMING_OPTION, elem_classes="switch-checkbox" ) + checkUpdateBtn = gr.Button(i18n("🔄 检查更新..."), visible=check_update) + gr.Markdown(i18n("# ⚠️ 务必谨慎更改 ⚠️"), elem_id="advanced-warning") with gr.Accordion(i18n("参数"), open=False): temperature_slider = gr.Slider( minimum=-0, @@ -192,7 +240,7 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: ) stop_sequence_txt = gr.Textbox( show_label=True, - placeholder=i18n("在这里输入停止符,用英文逗号隔开..."), + placeholder=i18n("停止符,用英文逗号隔开..."), label="stop", value="", lines=1, @@ -244,25 +292,36 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: lines=1, ) - with gr.Accordion(i18n("网络设置"), open=False, visible=False): + with gr.Accordion(i18n("网络参数"), open=False): + gr.Markdown(i18n("---\n⚠️ 为保证API-Key安全,请在配置文件`config.json`中修改网络设置"), elem_id="netsetting-warning") + default_btn = gr.Button(i18n("🔙 恢复默认网络设置")) + # 网络代理 + proxyTxt = gr.Textbox( + show_label=True, + placeholder=i18n("未设置代理..."), + label=i18n("代理地址"), + value=config.http_proxy, + lines=1, + interactive=False, + # container=False, + elem_classes="view-only-textbox no-container", + ) + # changeProxyBtn = gr.Button(i18n("🔄 设置代理地址")) + # 优先展示自定义的api_host apihostTxt = gr.Textbox( show_label=True, - placeholder=i18n("在这里输入API-Host..."), - label="API-Host", + placeholder="api.openai.com", + label="OpenAI API-Host", value=config.api_host or shared.API_HOST, lines=1, + interactive=False, + # container=False, + elem_classes="view-only-textbox no-container", ) - changeAPIURLBtn = gr.Button(i18n("🔄 切换API地址")) - proxyTxt = gr.Textbox( - show_label=True, - placeholder=i18n("在这里输入代理地址..."), - label=i18n("代理地址(示例:http://127.0.0.1:10809)"), - value="", - lines=2, - ) - changeProxyBtn = gr.Button(i18n("🔄 设置代理地址")) - default_btn = gr.Button(i18n("🔙 恢复默认设置")) + # changeAPIURLBtn = gr.Button(i18n("🔄 切换API地址")) + updateChuanhuBtn = gr.Button(visible=False, elem_classes="invisible-btn", elem_id="update-chuanhu-btn") + gr.Markdown(CHUANHU_DESCRIPTION, elem_id="description") gr.HTML(get_html("footer.html").format(versions=versions_html()), elem_id="footer") @@ -323,6 +382,10 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: outputs=[saveFileName, systemPromptTxt, chatbot] ) + refresh_history_args = dict( + fn=get_history_names, inputs=[gr.State(False), user_name], outputs=[historyFileSelectDropdown] + ) + # Chatbot cancelBtn.click(interrupt, [current_model], []) @@ -341,6 +404,7 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: inputs=[current_model], outputs=[chatbot, status_display], show_progress=True, + _js='clearChatbot', ) retryBtn.click(**start_outputing_args).then( @@ -391,7 +455,7 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: keyTxt.change(set_key, [current_model, keyTxt], [user_api_key, status_display], api_name="set_key").then(**get_usage_args) keyTxt.submit(**get_usage_args) single_turn_checkbox.change(set_single_turn, [current_model, single_turn_checkbox], None) - model_select_dropdown.change(get_model, [model_select_dropdown, lora_select_dropdown, user_api_key, temperature_slider, top_p_slider, systemPromptTxt, user_name], [current_model, status_display, chatbot, lora_select_dropdown], show_progress=True, api_name="get_model") + model_select_dropdown.change(get_model, [model_select_dropdown, lora_select_dropdown, user_api_key, temperature_slider, top_p_slider, systemPromptTxt, user_name], [current_model, status_display, chatbot, lora_select_dropdown, user_api_key, keyTxt], show_progress=True, api_name="get_model") model_select_dropdown.change(toggle_like_btn_visibility, [model_select_dropdown], [like_dislike_area], show_progress=False) lora_select_dropdown.change(get_model, [model_select_dropdown, lora_select_dropdown, user_api_key, temperature_slider, top_p_slider, systemPromptTxt, user_name], [current_model, status_display, chatbot], show_progress=True) @@ -425,10 +489,23 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: downloadFile, show_progress=True, ) - historyRefreshBtn.click(get_history_names, [gr.State(False), user_name], [historyFileSelectDropdown]) + historyRefreshBtn.click(**refresh_history_args) + historyDeleteBtn.click(delete_chat_history, [current_model, historyFileSelectDropdown, user_name], [status_display, historyFileSelectDropdown, chatbot], _js='(a,b,c)=>{return showConfirmationDialog(a, b, c);}') historyFileSelectDropdown.change(**load_history_from_file_args) downloadFile.change(upload_chat_history, [current_model, downloadFile, user_name], [saveFileName, systemPromptTxt, chatbot]) + # Train + dataset_selection.upload(handle_dataset_selection, dataset_selection, [dataset_preview_json, upload_to_openai_btn, openai_train_status]) + dataset_selection.clear(handle_dataset_clear, [], [dataset_preview_json, upload_to_openai_btn]) + upload_to_openai_btn.click(upload_to_openai, [dataset_selection], [openai_ft_file_id, openai_train_status], show_progress=True) + + openai_ft_file_id.change(lambda x: gr.update(interactive=True) if len(x) > 0 else gr.update(interactive=False), [openai_ft_file_id], [openai_start_train_btn]) + openai_start_train_btn.click(start_training, [openai_ft_file_id, openai_ft_suffix, openai_train_epoch_slider], [openai_train_status]) + + openai_status_refresh_btn.click(get_training_status, [], [openai_train_status, add_to_models_btn]) + add_to_models_btn.click(add_to_models, [], [model_select_dropdown, openai_train_status], show_progress=True) + openai_cancel_all_jobs_btn.click(cancel_all_jobs, [], [openai_train_status], show_progress=True) + # Advanced max_context_length_slider.change(set_token_upper_limit, [current_model, max_context_length_slider], None) temperature_slider.change(set_temperature, [current_model, temperature_slider], None) @@ -444,15 +521,24 @@ with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo: default_btn.click( reset_default, [], [apihostTxt, proxyTxt, status_display], show_progress=True ) - changeAPIURLBtn.click( - change_api_host, - [apihostTxt], - [status_display], - show_progress=True, - ) - changeProxyBtn.click( - change_proxy, - [proxyTxt], + # changeAPIURLBtn.click( + # change_api_host, + # [apihostTxt], + # [status_display], + # show_progress=True, + # ) + # changeProxyBtn.click( + # change_proxy, + # [proxyTxt], + # [status_display], + # show_progress=True, + # ) + checkUpdateBtn.click(fn=None, _js='manualCheckUpdate') + + # Invisible elements + updateChuanhuBtn.click( + update_chuanhu, + [], [status_display], show_progress=True, ) @@ -469,5 +555,5 @@ if __name__ == "__main__": reload_javascript() demo.queue(concurrency_count=CONCURRENT_COUNT).launch( blocked_paths=["config.json"], - favicon_path="./assets/favicon.ico" + favicon_path="./web_assets/favicon.ico", ) diff --git a/Dockerfile b/Dockerfile index 335c2dba28ba8c365de9306858462a59dea25f28..85d5045d5316ac160277af1e7d60afa823c0f953 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,18 @@ -FROM python:3.9 as builder -RUN apt-get update && apt-get install -y build-essential +FROM python:3.9-slim-buster as builder +RUN apt-get update \ + && apt-get install -y build-essential \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* COPY requirements.txt . COPY requirements_advanced.txt . -RUN pip install --user -r requirements.txt -# RUN pip install --user -r requirements_advanced.txt +RUN pip install --user --no-cache-dir -r requirements.txt +# RUN pip install --user --no-cache-dir -r requirements_advanced.txt -FROM python:3.9 -MAINTAINER iskoldt +FROM python:3.9-slim-buster +LABEL maintainer="iskoldt" COPY --from=builder /root/.local /root/.local ENV PATH=/root/.local/bin:$PATH COPY . /app WORKDIR /app -ENV dockerrun yes -CMD ["python3", "-u", "ChuanhuChatbot.py", "2>&1", "|", "tee", "/var/log/application.log"] +ENV dockerrun=yes +CMD ["python3", "-u", "ChuanhuChatbot.py","2>&1", "|", "tee", "/var/log/application.log"] diff --git a/config_example.json b/config_example.json index 7998f24524eb7d80d1fc8b7048ab64f4dacdd974..0b77caefbb39ef08d6a53b3b40ee67bb8a3b1576 100644 --- a/config_example.json +++ b/config_example.json @@ -1,24 +1,60 @@ { - // 你的OpenAI API Key,一般必填, - // 若缺省填为 "openai_api_key": "" 则必须再在图形界面中填入API Key - "openai_api_key": "", - // 你的xmchat API Key,与OpenAI API Key不同 - "xmchat_api_key": "", - "language": "auto", - // 如果使用代理,请取消注释下面的两行,并替换代理URL - // "https_proxy": "http://127.0.0.1:1079", - // "http_proxy": "http://127.0.0.1:1079", + // 各配置具体说明,见 [https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#配置-configjson] + + //== API 配置 == + "openai_api_key": "", // 你的 OpenAI API Key,一般必填,若空缺则需在图形界面中填入API Key + "google_palm_api_key": "", // 你的 Google PaLM API Key,用于 Google PaLM 对话模型 + "xmchat_api_key": "", // 你的 xmchat API Key,用于 XMChat 对话模型 + "minimax_api_key": "", // 你的 MiniMax API Key,用于 MiniMax 对话模型 + "minimax_group_id": "", // 你的 MiniMax Group ID,用于 MiniMax 对话模型 + "midjourney_proxy_api_base": "https://xxx/mj", // 你的 https://github.com/novicezk/midjourney-proxy 代理地址 + "midjourney_proxy_api_secret": "", // 你的 MidJourney Proxy API Secret,用于鉴权访问 api,可选 + "midjourney_discord_proxy_url": "", // 你的 MidJourney Discord Proxy URL,用于对生成对图进行反代,可选 + "midjourney_temp_folder": "./tmp", // 你的 MidJourney 临时文件夹,用于存放生成的图片,填空则关闭自动下载切图(直接显示MJ的四宫格图) + + + //== Azure == + "openai_api_type": "openai", // 可选项:azure, openai + "azure_openai_api_key": "", // 你的 Azure OpenAI API Key,用于 Azure OpenAI 对话模型 + "azure_openai_api_base_url": "", // 你的 Azure Base URL + "azure_openai_api_version": "2023-05-15", // 你的 Azure OpenAI API 版本 + "azure_deployment_name": "", // 你的 Azure OpenAI Chat 模型 Deployment 名称 + "azure_embedding_deployment_name": "", // 你的 Azure OpenAI Embedding 模型 Deployment 名称 + "azure_embedding_model_name": "text-embedding-ada-002", // 你的 Azure OpenAI Embedding 模型名称 + + //== 基础配置 == + "language": "auto", // 界面语言,可选"auto", "zh-CN", "en-US", "ja-JP", "ko-KR", "sv-SE" "users": [], // 用户列表,[[用户名1, 密码1], [用户名2, 密码2], ...] "local_embedding": false, //是否在本地编制索引 + "hide_history_when_not_logged_in": false, //未登录情况下是否不展示对话历史 + "check_update": true, //是否启用检查更新 "default_model": "gpt-3.5-turbo", // 默认模型 + "bot_avatar": "default", // 机器人头像,可填写图片链接、Data URL (base64),或者"none"(不显示头像) + "user_avatar": "default", // 用户头像,可填写图片链接、Data URL (base64),或者"none"(不显示头像) + + //== API 用量 == + "show_api_billing": false, //是否显示OpenAI API用量(启用需要填写sensitive_id) + "sensitive_id": "", // 你 OpenAI 账户的 Sensitive ID,用于查询 API 用量 + "usage_limit": 120, // 该 OpenAI API Key 的当月限额,单位:美元,用于计算百分比和显示上限 + "legacy_api_usage": false, // 是否使用旧版 API 用量查询接口(OpenAI现已关闭该接口,但是如果你在使用第三方 API,第三方可能仍然支持此接口) + + //== 川虎助理设置 == + "default_chuanhu_assistant_model": "gpt-4", //川虎助理使用的模型,可选gpt-3.5-turbo或者gpt-4等 + "GOOGLE_CSE_ID": "", //谷歌搜索引擎ID,用于川虎助理Pro模式,获取方式请看 https://stackoverflow.com/questions/37083058/programmatically-searching-google-in-python-using-custom-search + "GOOGLE_API_KEY": "", //谷歌API Key,用于川虎助理Pro模式 + "WOLFRAM_ALPHA_APPID": "", //Wolfram Alpha API Key,用于川虎助理Pro模式,获取方式请看 https://products.wolframalpha.com/api/ + "SERPAPI_API_KEY": "", //SerpAPI API Key,用于川虎助理Pro模式,获取方式请看 https://serpapi.com/ + + //== 文档处理与显示 == + "latex_option": "default", // LaTeX 公式渲染策略,可选"default", "strict", "all"或者"disabled" "advance_docs": { "pdf": { - // 是否认为PDF是双栏的 - "two_column": false, - // 是否使用OCR识别PDF中的公式 - "formula_ocr": true + "two_column": false, // 是否认为PDF是双栏的 + "formula_ocr": true // 是否使用OCR识别PDF中的公式 } }, + + //== 高级配置 == // 是否多个API Key轮换使用 "multi_api_key": false, "api_key_list": [ @@ -26,7 +62,12 @@ "sk-xxxxxxxxxxxxxxxxxxxxxxxx2", "sk-xxxxxxxxxxxxxxxxxxxxxxxx3" ], - // 如果使用自定义端口、自定义ip,请取消注释并替换对应内容 + // 自定义OpenAI API Base + // "openai_api_base": "https://api.openai.com", + // 自定义使用代理(请替换代理URL) + // "https_proxy": "http://127.0.0.1:1079", + // "http_proxy": "http://127.0.0.1:1079", + // 自定义端口、自定义ip(请替换对应内容) // "server_name": "0.0.0.0", // "server_port": 7860, // 如果要share到gradio,设置为true diff --git a/locale/en_US.json b/locale/en_US.json index 09f00893344b0b587c4a384f3bcf6d48064e5fa0..17a5aa618ee8e1c4425a7ce69e1d86adfbd24b6c 100644 --- a/locale/en_US.json +++ b/locale/en_US.json @@ -32,24 +32,33 @@ "📝 导出为Markdown": "📝 Export as Markdown", "默认保存于history文件夹": "Default save in history folder", "高级": "Advanced", - "# ⚠️ 务必谨慎更改 ⚠️\n\n如果无法使用请恢复默认设置": "# ⚠️ Caution: Changes require care. ⚠️\n\nIf unable to use, restore default settings.", + "# ⚠️ 务必谨慎更改 ⚠️": "# ⚠️ Caution: Changes require care. ⚠️", "参数": "Parameters", - "在这里输入停止符,用英文逗号隔开...": "Type in stop token here, separated by comma...", + "停止符,用英文逗号隔开...": "Type in stop token here, separated by comma...", "用于定位滥用行为": "Used to locate abuse", "用户名": "Username", - "网络设置": "Network Settings", "在这里输入API-Host...": "Type in API-Host here...", "🔄 切换API地址": "🔄 Switch API Address", - "在这里输入代理地址...": "Type in proxy address here...", - "代理地址(示例:http://127.0.0.1:10809)": "Proxy address (example: http://127.0.0.1:10809)", + "未设置代理...": "No proxy...", + "代理地址": "Proxy address", "🔄 设置代理地址": "🔄 Set Proxy Address", - "🔙 恢复默认设置": "🔙 Restore Default Settings", + "🔙 恢复默认网络设置": "🔙 Reset Network Settings", + "🔄 检查更新...": "🔄 Check for Update...", + "取消": "Cancel", + "更新": "Update", + "详情": "Details", + "好": "OK", + "更新成功,请重启本程序": "Updated successfully, please restart this program", + "更新失败,请尝试[手动更新](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)": "Update failed, please try [manually updating](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)", "川虎Chat 🚀": "Chuanhu Chat 🚀", "开始实时传输回答……": "Start streaming output...", "Token 计数: ": "Token Count: ", - ",本次对话累计消耗了 ": ",Total cost for this dialogue is ", + ",本次对话累计消耗了 ": ", Total cost for this dialogue is ", "**获取API使用情况失败**": "**Failed to get API usage**", + "**获取API使用情况失败**,需在填写`config.json`中正确填写sensitive_id": "**Failed to get API usage**, correct sensitive_id needed in `config.json`", + "**获取API使用情况失败**,sensitive_id错误或已过期": "**Failed to get API usage**, wrong or expired sensitive_id", "**本月使用金额** ": "**Monthly usage** ", + "本月使用金额": "Monthly usage", "获取API使用情况失败:": "Failed to get API usage:", "API密钥更改为了": "The API key is changed to", "JSON解析错误,收到的内容: ": "JSON parsing error, received content: ", @@ -64,10 +73,15 @@ "API key为空,请检查是否输入正确。": "API key is empty, check whether it is entered correctly.", "请输入对话内容。": "Enter the content of the conversation.", "账单信息不适用": "Billing information is not applicable", - "由Bilibili [土川虎虎虎](https://space.bilibili.com/29125536) 和 [明昭MZhao](https://space.bilibili.com/24807452)开发
访问川虎Chat的 [GitHub项目](https://github.com/GaiZhenbiao/ChuanhuChatGPT) 下载最新版脚本": "developor: Bilibili [土川虎虎虎](https://space.bilibili.com/29125536) and [明昭MZhao](https://space.bilibili.com/24807452)\n\nDownload latest code from [GitHub](https://github.com/GaiZhenbiao/ChuanhuChatGPT)", + "由Bilibili [土川虎虎虎](https://space.bilibili.com/29125536)、[明昭MZhao](https://space.bilibili.com/24807452) 和 [Keldos](https://github.com/Keldos-Li) 开发
访问川虎Chat的 [GitHub项目](https://github.com/GaiZhenbiao/ChuanhuChatGPT) 下载最新版脚本": "Developed by Bilibili [土川虎虎虎](https://space.bilibili.com/29125536), [明昭MZhao](https://space.bilibili.com/24807452) and [Keldos](https://github.com/Keldos-Li)\n\nDownload latest code from [GitHub](https://github.com/GaiZhenbiao/ChuanhuChatGPT)", "切换亮暗色主题": "Switch light/dark theme", "您的IP区域:未知。": "Your IP region: Unknown.", "获取IP地理位置失败。原因:": "Failed to get IP location. Reason: ", "。你仍然可以使用聊天功能。": ". You can still use the chat function.", - "您的IP区域:": "Your IP region: " + "您的IP区域:": "Your IP region: ", + "总结": "Summarize", + "生成内容总结中……": "Generating content summary...", + "由于下面的原因,Google 拒绝返回 PaLM 的回答:\n\n": "Due to the following reasons, Google refuses to provide an answer to PaLM: \n\n", + "---\n⚠️ 为保证API-Key安全,请在配置文件`config.json`中修改网络设置": "---\n⚠️ To ensure the security of API-Key, please modify the network settings in the configuration file `config.json`.", + "网络参数": "Network parameter" } diff --git a/locale/ja_JP.json b/locale/ja_JP.json index 1acbe7103ef01beb81a8039a77981af8fa31e402..db8fb8441bb669848c5eec4644d5b3e8d814060a 100644 --- a/locale/ja_JP.json +++ b/locale/ja_JP.json @@ -32,24 +32,33 @@ "📝 导出为Markdown": "📝 Markdownでエクスポート", "默认保存于history文件夹": "デフォルトでhistoryフォルダに保存されます", "高级": "Advanced", - "# ⚠️ 务必谨慎更改 ⚠️\n\n如果无法使用请恢复默认设置": "# ⚠️ 変更には慎重に ⚠️\n\nもし動作しない場合は、デフォルト設定に戻してください。", + "# ⚠️ 务必谨慎更改 ⚠️": "# ⚠️ 変更には慎重に ⚠️", "参数": "パラメータ", - "在这里输入停止符,用英文逗号隔开...": "ここにストップ文字を英語のカンマで区切って入力してください...", + "停止符,用英文逗号隔开...": "ここにストップ文字を英語のカンマで区切って入力してください...", "用于定位滥用行为": "不正行為を特定するために使用されます", "用户名": "ユーザー名", - "网络设置": "ネットワーク設定", "在这里输入API-Host...": "API-Hostを入力してください...", "🔄 切换API地址": "🔄 APIアドレスを切り替え", - "在这里输入代理地址...": "プロキシアドレスを入力してください...", - "代理地址(示例:http://127.0.0.1:10809)": "プロキシアドレス(例:http://127.0.0.1:10809)", + "未设置代理...": "代理が設定されていません...", + "代理地址": "プロキシアドレス", "🔄 设置代理地址": "🔄 プロキシアドレスを設定", - "🔙 恢复默认设置": "🔙 デフォルト設定に戻す", + "🔙 恢复默认网络设置": "🔙 ネットワーク設定のリセット", + "🔄 检查更新...": "🔄 アップデートをチェック...", + "取消": "キャンセル", + "更新": "アップデート", + "详情": "詳細", + "好": "はい", + "更新成功,请重启本程序": "更新が成功しました、このプログラムを再起動してください", + "更新失败,请尝试[手动更新](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)": "更新に失敗しました、[手動での更新](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)をお試しください。", "川虎Chat 🚀": "川虎Chat 🚀", "开始实时传输回答……": "ストリーム出力開始……", "Token 计数: ": "Token数: ", ",本次对话累计消耗了 ": ", 今の会話で消費合計 ", "**获取API使用情况失败**": "**API使用状況の取得に失敗しました**", + "**获取API使用情况失败**,需在填写`config.json`中正确填写sensitive_id": "**API使用状況の取得に失敗しました**、`config.json`に正しい`sensitive_id`を入力する必要があります", + "**获取API使用情况失败**,sensitive_id错误或已过期": "**API使用状況の取得に失敗しました**、sensitive_idが間違っているか、期限切れです", "**本月使用金额** ": "**今月の使用料金** ", + "本月使用金额": "今月の使用料金", "获取API使用情况失败:": "API使用状況の取得に失敗しました:", "API密钥更改为了": "APIキーが変更されました", "JSON解析错误,收到的内容: ": "JSON解析エラー、受信内容: ", @@ -64,10 +73,15 @@ "API key为空,请检查是否输入正确。": "APIキーが入力されていません。正しく入力されているか確認してください。", "请输入对话内容。": "会話内容を入力してください。", "账单信息不适用": "課金情報は対象外です", - "由Bilibili [土川虎虎虎](https://space.bilibili.com/29125536) 和 [明昭MZhao](https://space.bilibili.com/24807452)开发
访问川虎Chat的 [GitHub项目](https://github.com/GaiZhenbiao/ChuanhuChatGPT) 下载最新版脚本": "開発:Bilibili [土川虎虎虎](https://space.bilibili.com/29125536) と [明昭MZhao](https://space.bilibili.com/24807452)\n\n最新コードは川虎Chatのサイトへ [GitHubプロジェクト](https://github.com/GaiZhenbiao/ChuanhuChatGPT)", + "由Bilibili [土川虎虎虎](https://space.bilibili.com/29125536)、[明昭MZhao](https://space.bilibili.com/24807452) 和 [Keldos](https://github.com/Keldos-Li) 开发
访问川虎Chat的 [GitHub项目](https://github.com/GaiZhenbiao/ChuanhuChatGPT) 下载最新版脚本": "開発:Bilibili [土川虎虎虎](https://space.bilibili.com/29125536) と [明昭MZhao](https://space.bilibili.com/24807452) と [Keldos](https://github.com/Keldos-Li)\n\n最新コードは川虎Chatのサイトへ [GitHubプロジェクト](https://github.com/GaiZhenbiao/ChuanhuChatGPT)", "切换亮暗色主题": "テーマの明暗切替", "您的IP区域:未知。": "あなたのIPアドレス地域:不明", "获取IP地理位置失败。原因:": "IPアドレス地域の取得に失敗しました。理由:", "。你仍然可以使用聊天功能。": "。あなたはまだチャット機能を使用できます。", - "您的IP区域:": "あなたのIPアドレス地域:" -} \ No newline at end of file + "您的IP区域:": "あなたのIPアドレス地域:", + "总结": "要約する", + "生成内容总结中……": "コンテンツ概要を生成しています...", + "由于下面的原因,Google 拒绝返回 PaLM 的回答:\n\n": "Googleは以下の理由から、PaLMの回答を返すことを拒否しています:\n\n", + "---\n⚠️ 为保证API-Key安全,请在配置文件`config.json`中修改网络设置": "---\n⚠️ APIキーの安全性を確保するために、`config.json`ファイルでネットワーク設定を変更してください。", + "网络参数": "ネットワークパラメータ" +} diff --git a/locale/ko_KR.json b/locale/ko_KR.json new file mode 100644 index 0000000000000000000000000000000000000000..a7f45732eeae5b65930a078c0b326c9659abd270 --- /dev/null +++ b/locale/ko_KR.json @@ -0,0 +1,89 @@ +{ + "未命名对话历史记录": "이름없는 대화 기록", + "在这里输入": "여기에 입력하세요", + "🧹 新的对话": "🧹 새로운 대화", + "🔄 重新生成": "🔄 재생성", + "🗑️ 删除最旧对话": "🗑️ 가장 오래된 대화 삭제", + "🗑️ 删除最新对话": "🗑️ 최신 대화 삭제", + "🗑️ 删除": "🗑️ 삭제", + "模型": "LLM 모델", + "多账号模式已开启,无需输入key,可直接开始对话": "다중 계정 모드가 활성화되어 있으므로 키를 입력할 필요가 없이 바로 대화를 시작할 수 있습니다", + "**发送消息** 或 **提交key** 以显示额度": "**메세지를 전송** 하거나 **Key를 입력**하여 크레딧 표시", + "选择模型": "모델 선택", + "选择LoRA模型": "LoRA 모델 선택", + "实时传输回答": "실시간 전송", + "单轮对话": "단일 대화", + "使用在线搜索": "온라인 검색 사용", + "选择回复语言(针对搜索&索引功能)": "답장 언어 선택 (검색 & 인덱스용)", + "上传索引文件": "업로드", + "双栏pdf": "2-column pdf", + "识别公式": "formula OCR", + "在这里输入System Prompt...": "여기에 시스템 프롬프트를 입력하세요...", + "加载Prompt模板": "프롬프트 템플릿 불러오기", + "选择Prompt模板集合文件": "프롬프트 콜렉션 파일 선택", + "🔄 刷新": "🔄 새로고침", + "从Prompt模板中加载": "프롬프트 템플릿에서 불러오기", + "保存/加载": "저장/불러오기", + "保存/加载对话历史记录": "대화 기록 저장/불러오기", + "从列表中加载对话": "리스트에서 대화 불러오기", + "设置文件名: 默认为.json,可选为.md": "파일 이름 설정: 기본값: .json, 선택: .md", + "设置保存文件名": "저장 파일명 설정", + "对话历史记录": "대화 기록", + "💾 保存对话": "💾 대화 저장", + "📝 导出为Markdown": "📝 마크다운으로 내보내기", + "默认保存于history文件夹": "히스토리 폴더에 기본 저장", + "高级": "고급", + "# ⚠️ 务必谨慎更改 ⚠️": "# ⚠️ 주의: 변경시 주의하세요. ⚠️", + "参数": "파라미터들", + "停止符,用英文逗号隔开...": "여기에 정지 토큰 입력, ','로 구분됨...", + "用于定位滥用行为": "악용 사례 파악에 활용됨", + "用户名": "사용자 이름", + "在这里输入API-Host...": "여기에 API host를 입력하세요...", + "🔄 切换API地址": "🔄 API 주소 변경", + "未设置代理...": "대리인이 설정되지 않았습니다...", + "代理地址": "프록시 주소", + "🔄 设置代理地址": "🔄 프록시 주소 설정", + "🔙 恢复默认网络设置": "🔙 네트워크 설정 초기화", + "🔄 检查更新...": "🔄 업데이트 확인...", + "取消": "취소", + "更新": "업데이트", + "详情": "상세", + "好": "예", + "更新成功,请重启本程序": "업데이트 성공, 이 프로그램을 재시작 해주세요", + "更新失败,请尝试[手动更新](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)": "업데이트 실패, [수동 업데이트](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)를 시도하십시오", + "川虎Chat 🚀": "Chuanhu Chat 🚀", + "开始实时传输回答……": "실시간 응답 출력 시작...", + "Token 计数: ": "토큰 수: ", + ",本次对话累计消耗了 ": ",이 대화의 전체 비용은 ", + "**获取API使用情况失败**": "**API 사용량 가져오기 실패**", + "**获取API使用情况失败**,需在填写`config.json`中正确填写sensitive_id": "**API 사용량 가져오기 실패**. `config.json`에 올바른 `sensitive_id`를 입력해야 합니다", + "**获取API使用情况失败**,sensitive_id错误或已过期": "**API 사용량 가져오기 실패**. sensitive_id가 잘못되었거나 만료되었습니다", + "**本月使用金额** ": "**이번 달 사용금액** ", + "本月使用金额": "이번 달 사용금액", + "获取API使用情况失败:": "API 사용량 가져오기 실패:", + "API密钥更改为了": "API 키가 변경되었습니다.", + "JSON解析错误,收到的内容: ": "JSON 파싱 에러, 응답: ", + "模型设置为了:": "설정된 모델: ", + "☹️发生了错误:": "☹️에러: ", + "获取对话时发生错误,请查看后台日志": "대화를 가져오는 중 에러가 발생했습니다. 백그라운드 로그를 확인하세요", + "请检查网络连接,或者API-Key是否有效。": "네트워크 연결 또는 API키가 유효한지 확인하세요", + "连接超时,无法获取对话。": "연결 시간 초과, 대화를 가져올 수 없습니다.", + "读取超时,无法获取对话。": "읽기 시간 초과, 대화를 가져올 수 없습니다.", + "代理错误,无法获取对话。": "프록시 에러, 대화를 가져올 수 없습니다.", + "SSL错误,无法获取对话。": "SSL 에러, 대화를 가져올 수 없습니다.", + "API key为空,请检查是否输入正确。": "API 키가 비어 있습니다. 올바르게 입력되었는지 확인하십세요.", + "请输入对话内容。": "대화 내용을 입력하세요.", + "账单信息不适用": "청구 정보를 가져올 수 없습니다", + "由Bilibili [土川虎虎虎](https://space.bilibili.com/29125536)、[明昭MZhao](https://space.bilibili.com/24807452) 和 [Keldos](https://github.com/Keldos-Li) 开发
访问川虎Chat的 [GitHub项目](https://github.com/GaiZhenbiao/ChuanhuChatGPT) 下载最新版脚本": "제작: Bilibili [土川虎虎虎](https://space.bilibili.com/29125536), [明昭MZhao](https://space.bilibili.com/24807452), [Keldos](https://github.com/Keldos-Li)\n\n최신 코드 다운로드: [GitHub](https://github.com/GaiZhenbiao/ChuanhuChatGPT)", + "切换亮暗色主题": "라이트/다크 테마 전환", + "您的IP区域:未知。": "IP 지역: 알 수 없음.", + "获取IP地理位置失败。原因:": "다음과 같은 이유로 IP 위치를 가져올 수 없습니다. 이유: ", + "。你仍然可以使用聊天功能。": ". 채팅 기능을 계속 사용할 수 있습니다.", + "您的IP区域:": "당신의 IP 지역: ", + "总结": "요약", + "生成内容总结中……": "콘텐츠 요약 생성중...", + "上传": "업로드", + "由于下面的原因,Google 拒绝返回 PaLM 的回答:\n\n": "구글은 다음과 같은 이유로 인해 PaLM의 응답을 거부합니다: \n\n", + "---\n⚠️ 为保证API-Key安全,请在配置文件`config.json`中修改网络设置": "---\n⚠️ API-Key의 안전을 보장하기 위해 네트워크 설정을 `config.json` 구성 파일에서 수정해주세요.", + "网络参数": "네트워크 매개변수" +} diff --git a/locale/sv-SE.json b/locale/sv-SE.json new file mode 100644 index 0000000000000000000000000000000000000000..4d3c9627fd967724fceac2a55aaff6b434b70c1b --- /dev/null +++ b/locale/sv-SE.json @@ -0,0 +1,87 @@ +{ + "未命名对话历史记录": "Onämnd Dialoghistorik", + "在这里输入": "Skriv in här", + "🧹 新的对话": "🧹 Ny Dialog", + "🔄 重新生成": "🔄 Regenerera", + "🗑️ 删除最旧对话": "🗑️ Ta bort äldsta dialogen", + "🗑️ 删除最新对话": "🗑️ Ta bort senaste dialogen", + "模型": "Modell", + "多账号模式已开启,无需输入key,可直接开始对话": "Flerkontoläge är aktiverat, ingen nyckel behövs, du kan starta dialogen direkt", + "**发送消息** 或 **提交key** 以显示额度": "**Skicka meddelande** eller **Skicka in nyckel** för att visa kredit", + "选择模型": "Välj Modell", + "选择LoRA模型": "Välj LoRA Modell", + "实时传输回答": "Strömmande utdata", + "单轮对话": "Enkel dialog", + "使用在线搜索": "Använd online-sökning", + "选择回复语言(针对搜索&索引功能)": "Välj svarspråk (för sök- och indexfunktion)", + "上传索引文件": "Ladda upp", + "双栏pdf": "Två-kolumns pdf", + "识别公式": "Formel OCR", + "在这里输入System Prompt...": "Skriv in System Prompt här...", + "加载Prompt模板": "Ladda Prompt-mall", + "选择Prompt模板集合文件": "Välj Prompt-mall Samlingsfil", + "🔄 刷新": "🔄 Uppdatera", + "从Prompt模板中加载": "Ladda från Prompt-mall", + "保存/加载": "Spara/Ladda", + "保存/加载对话历史记录": "Spara/Ladda Dialoghistorik", + "从列表中加载对话": "Ladda dialog från lista", + "设置文件名: 默认为.json,可选为.md": "Ställ in filnamn: standard är .json, valfritt är .md", + "设置保存文件名": "Ställ in sparfilnamn", + "对话历史记录": "Dialoghistorik", + "💾 保存对话": "💾 Spara Dialog", + "📝 导出为Markdown": "📝 Exportera som Markdown", + "默认保存于history文件夹": "Sparas som standard i mappen history", + "高级": "Avancerat", + "# ⚠️ 务必谨慎更改 ⚠️": "# ⚠️ Var försiktig med ändringar. ⚠️", + "参数": "Parametrar", + "停止符,用英文逗号隔开...": "Skriv in stopptecken här, separerade med kommatecken...", + "用于定位滥用行为": "Används för att lokalisera missbruk", + "用户名": "Användarnamn", + "在这里输入API-Host...": "Skriv in API-Host här...", + "🔄 切换API地址": "🔄 Byt API-adress", + "未设置代理...": "Inte inställd proxy...", + "代理地址": "Proxyadress", + "🔄 设置代理地址": "🔄 Ställ in Proxyadress", + "🔙 恢复网络默认设置": "🔙 Återställ Nätverksinställningar", + "🔄 检查更新...": "🔄 Sök efter uppdateringar...", + "取消": "Avbryt", + "更新": "Uppdatera", + "详情": "Detaljer", + "好": "OK", + "更新成功,请重启本程序": "Uppdaterat framgångsrikt, starta om programmet", + "更新失败,请尝试[手动更新](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)": "Uppdateringen misslyckades, prova att [uppdatera manuellt](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)", + "川虎Chat 🚀": "Chuanhu Chat 🚀", + "开始实时传输回答……": "Börjar strömma utdata...", + "Token 计数: ": "Tokenräkning: ", + ",本次对话累计消耗了 ": ", Total kostnad för denna dialog är ", + "**获取API使用情况失败**": "**Misslyckades med att hämta API-användning**", + "**获取API使用情况失败**,需在填写`config.json`中正确填写sensitive_id": "**Misslyckades med att hämta API-användning**, korrekt sensitive_id behövs i `config.json`", + "**获取API使用情况失败**,sensitive_id错误或已过期": "**Misslyckades med att hämta API-användning**, felaktig eller utgången sensitive_id", + "**本月使用金额** ": "**Månadens användning** ", + "本月使用金额": "Månadens användning", + "获取API使用情况失败:": "Misslyckades med att hämta API-användning:", + "API密钥更改为了": "API-nyckeln har ändrats till", + "JSON解析错误,收到的内容: ": "JSON-tolkningsfel, mottaget innehåll: ", + "模型设置为了:": "Modellen är inställd på: ", + "☹️发生了错误:": "☹️Fel: ", + "获取对话时发生错误,请查看后台日志": "Ett fel uppstod när dialogen hämtades, kontrollera bakgrundsloggen", + "请检查网络连接,或者API-Key是否有效。": "Kontrollera nätverksanslutningen eller om API-nyckeln är giltig.", + "连接超时,无法获取对话。": "Anslutningen tog för lång tid, kunde inte hämta dialogen.", + "读取超时,无法获取对话。": "Läsningen tog för lång tid, kunde inte hämta dialogen.", + "代理错误,无法获取对话。": "Proxyfel, kunde inte hämta dialogen.", + "SSL错误,无法获取对话。": "SSL-fel, kunde inte hämta dialogen.", + "API key为空,请检查是否输入正确。": "API-nyckeln är tom, kontrollera om den är korrekt inmatad.", + "请输入对话内容。": "Ange dialoginnehåll.", + "账单信息不适用": "Faktureringsinformation är inte tillämplig", + "由Bilibili [土川虎虎虎](https://space.bilibili.com/29125536)、[明昭MZhao](https://space.bilibili.com/24807452) 和 [Keldos](https://github.com/Keldos-Li) 开发
访问川虎Chat的 [GitHub项目](https://github.com/GaiZhenbiao/ChuanhuChatGPT) 下载最新版脚本": "Utvecklad av Bilibili [土川虎虎虎](https://space.bilibili.com/29125536), [明昭MZhao](https://space.bilibili.com/24807452) och [Keldos](https://github.com/Keldos-Li)\n\nLadda ner senaste koden från [GitHub](https://github.com/GaiZhenbiao/ChuanhuChatGPT)", + "切换亮暗色主题": "Byt ljus/mörk tema", + "您的IP区域:未知。": "Din IP-region: Okänd.", + "获取IP地理位置失败。原因:": "Misslyckades med att hämta IP-plats. Orsak: ", + "。你仍然可以使用聊天功能。": ". Du kan fortfarande använda chattfunktionen.", + "您的IP区域:": "Din IP-region: ", + "总结": "Sammanfatta", + "生成内容总结中……": "Genererar innehållssammanfattning...", + "由于下面的原因,Google 拒绝返回 PaLM 的回答:\n\n": "På grund av följande skäl vägrar Google att ge ett svar till PaLM: \n\n", + "---\n⚠️ 为保证API-Key安全,请在配置文件`config.json`中修改网络设置": "---\n⚠️ För att säkerställa säkerheten för API-nyckeln, vänligen ändra nätverksinställningarna i konfigurationsfilen `config.json`.", + "网络参数": "nätverksparametrar" +} diff --git a/modules/.DS_Store b/modules/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b568306d92f15378a29b2420f594020c5ea7d5fa Binary files /dev/null and b/modules/.DS_Store differ diff --git a/modules/__pycache__/config.cpython-311.pyc b/modules/__pycache__/config.cpython-311.pyc index 814b6c81b46088e3da8bf033612613d118bcafc3..9530da3815908ec3787cd4abe832c5a7616b51cd 100644 Binary files a/modules/__pycache__/config.cpython-311.pyc and b/modules/__pycache__/config.cpython-311.pyc differ diff --git a/modules/__pycache__/config.cpython-39.pyc b/modules/__pycache__/config.cpython-39.pyc index 74c61920214d19c533ec6eaa6d1243f91937bc7d..4e2d78243a1c2f7b4b0633bfcdcdb6379e1943f3 100644 Binary files a/modules/__pycache__/config.cpython-39.pyc and b/modules/__pycache__/config.cpython-39.pyc differ diff --git a/modules/__pycache__/index_func.cpython-311.pyc b/modules/__pycache__/index_func.cpython-311.pyc index 637f2271c8683f759fb8a253b19ce9589b50074a..a993b842af381300d650ebfe736cc5865225fead 100644 Binary files a/modules/__pycache__/index_func.cpython-311.pyc and b/modules/__pycache__/index_func.cpython-311.pyc differ diff --git a/modules/__pycache__/index_func.cpython-39.pyc b/modules/__pycache__/index_func.cpython-39.pyc index e5331c8816453dbb4fae6e6061cd2c2a4214194a..b97db89c58f233333a9eb6bf72fd871bb3cc4a29 100644 Binary files a/modules/__pycache__/index_func.cpython-39.pyc and b/modules/__pycache__/index_func.cpython-39.pyc differ diff --git a/modules/__pycache__/overwrites.cpython-311.pyc b/modules/__pycache__/overwrites.cpython-311.pyc index 4cbf7c22204cf40a1b62baf83da402791763c404..cdf5755ea2443f0f2b64f1da6ee85be078b76a7c 100644 Binary files a/modules/__pycache__/overwrites.cpython-311.pyc and b/modules/__pycache__/overwrites.cpython-311.pyc differ diff --git a/modules/__pycache__/overwrites.cpython-39.pyc b/modules/__pycache__/overwrites.cpython-39.pyc index f31912fa0b49ee69112454b36ed33d8546ff9d1b..3d54035e7c8937f1d0fae198be3a2c862468e026 100644 Binary files a/modules/__pycache__/overwrites.cpython-39.pyc and b/modules/__pycache__/overwrites.cpython-39.pyc differ diff --git a/modules/__pycache__/pdf_func.cpython-311.pyc b/modules/__pycache__/pdf_func.cpython-311.pyc index c5225f07eca638916ed5da2c5e1d248d29432300..e2b10156a9940c6f0c470fb86682fcc574e5a80c 100644 Binary files a/modules/__pycache__/pdf_func.cpython-311.pyc and b/modules/__pycache__/pdf_func.cpython-311.pyc differ diff --git a/modules/__pycache__/presets.cpython-311.pyc b/modules/__pycache__/presets.cpython-311.pyc index 417a5228acc399992d06f44baab1ecd2a0e2f393..666229ab5bb4b74e06b75326f0bf67a5990cb398 100644 Binary files a/modules/__pycache__/presets.cpython-311.pyc and b/modules/__pycache__/presets.cpython-311.pyc differ diff --git a/modules/__pycache__/presets.cpython-39.pyc b/modules/__pycache__/presets.cpython-39.pyc index d4c24f132f0874d16073a80681cab1c26631ba79..8ea74040f3b8124051ba6565d1a733dd3546cee4 100644 Binary files a/modules/__pycache__/presets.cpython-39.pyc and b/modules/__pycache__/presets.cpython-39.pyc differ diff --git a/modules/__pycache__/repo.cpython-311.pyc b/modules/__pycache__/repo.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8619438e55aa7d7af505f298ca8602d8ba21e0b2 Binary files /dev/null and b/modules/__pycache__/repo.cpython-311.pyc differ diff --git a/modules/__pycache__/shared.cpython-311.pyc b/modules/__pycache__/shared.cpython-311.pyc index 0916f22b230a897be92f6535bbca83fe7f53e86f..14621e716a8d3758c252ae7ccc4c42451b4e3d13 100644 Binary files a/modules/__pycache__/shared.cpython-311.pyc and b/modules/__pycache__/shared.cpython-311.pyc differ diff --git a/modules/__pycache__/shared.cpython-39.pyc b/modules/__pycache__/shared.cpython-39.pyc index 2c616f792e6e67d427badfd73c06edaf8796c9db..049e6cf0ee5f24ca3aa5346b9f5f810f37b0a025 100644 Binary files a/modules/__pycache__/shared.cpython-39.pyc and b/modules/__pycache__/shared.cpython-39.pyc differ diff --git a/modules/__pycache__/train_func.cpython-311.pyc b/modules/__pycache__/train_func.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b79271fc649a7635932ab60408e4aadcd143fd1f Binary files /dev/null and b/modules/__pycache__/train_func.cpython-311.pyc differ diff --git a/modules/__pycache__/utils.cpython-311.pyc b/modules/__pycache__/utils.cpython-311.pyc index 17298f3a636270dcf70303a286df9b1605f841a8..5ddb1e91c08c0aa62e5c31b3afd29c07e64008e0 100644 Binary files a/modules/__pycache__/utils.cpython-311.pyc and b/modules/__pycache__/utils.cpython-311.pyc differ diff --git a/modules/__pycache__/utils.cpython-39.pyc b/modules/__pycache__/utils.cpython-39.pyc index d81ba43da611907a5693a9a7e363c459d9191195..f4cbd0c64bbe16dd098fb92346f36a16ad64833d 100644 Binary files a/modules/__pycache__/utils.cpython-39.pyc and b/modules/__pycache__/utils.cpython-39.pyc differ diff --git a/modules/__pycache__/webui.cpython-311.pyc b/modules/__pycache__/webui.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e027ee26faf0e36cce97a84afaa5ef05a4df603 Binary files /dev/null and b/modules/__pycache__/webui.cpython-311.pyc differ diff --git a/modules/config.py b/modules/config.py index c9224996dd7056508519be8cbe906746f362abb0..77f8bc62737cb08ae38a9345ae8dc420fb643f25 100644 --- a/modules/config.py +++ b/modules/config.py @@ -11,11 +11,11 @@ from . import presets __all__ = [ "my_api_key", + "sensitive_id", "authflag", "auth_list", "dockerflag", "retrieve_proxy", - "log_level", "advance_docs", "update_doc_config", "usage_limit", @@ -23,8 +23,11 @@ __all__ = [ "server_name", "server_port", "share", + "check_update", + "latex_delimiters_set", "hide_history_when_not_logged_in", - "default_chuanhu_assistant_model" + "default_chuanhu_assistant_model", + "show_api_billing" ] # 添加一个统一的config文件,避免文件过多造成的疑惑(优先级最低) @@ -35,10 +38,22 @@ if os.path.exists("config.json"): else: config = {} + +def load_config_to_environ(key_list): + global config + for key in key_list: + if key in config: + os.environ[key.upper()] = os.environ.get(key.upper(), config[key]) + + lang_config = config.get("language", "auto") language = os.environ.get("LANGUAGE", lang_config) -hide_history_when_not_logged_in = config.get("hide_history_when_not_logged_in", False) +hide_history_when_not_logged_in = config.get( + "hide_history_when_not_logged_in", False) +check_update = config.get("check_update", True) +show_api_billing = config.get("show_api_billing", False) +show_api_billing = bool(os.environ.get("SHOW_API_BILLING", show_api_billing)) if os.path.exists("api_key.txt"): logging.info("检测到api_key.txt文件,正在进行迁移...") @@ -52,26 +67,44 @@ if os.path.exists("auth.json"): logging.info("检测到auth.json文件,正在进行迁移...") auth_list = [] with open("auth.json", "r", encoding='utf-8') as f: - auth = json.load(f) - for _ in auth: - if auth[_]["username"] and auth[_]["password"]: - auth_list.append((auth[_]["username"], auth[_]["password"])) - else: - logging.error("请检查auth.json文件中的用户名和密码!") - sys.exit(1) + auth = json.load(f) + for _ in auth: + if auth[_]["username"] and auth[_]["password"]: + auth_list.append((auth[_]["username"], auth[_]["password"])) + else: + logging.error("请检查auth.json文件中的用户名和密码!") + sys.exit(1) config["users"] = auth_list os.rename("auth.json", "auth(deprecated).json") with open("config.json", "w", encoding='utf-8') as f: json.dump(config, f, indent=4, ensure_ascii=False) -## 处理docker if we are running in Docker +# 处理docker if we are running in Docker dockerflag = config.get("dockerflag", False) if os.environ.get("dockerrun") == "yes": dockerflag = True -## 处理 api-key 以及 允许的用户列表 +# 处理 api-key 以及 允许的用户列表 my_api_key = config.get("openai_api_key", "") my_api_key = os.environ.get("OPENAI_API_KEY", my_api_key) +os.environ["OPENAI_API_KEY"] = my_api_key +os.environ["OPENAI_EMBEDDING_API_KEY"] = my_api_key + +if config.get("legacy_api_usage", False): + sensitive_id = my_api_key +else: + sensitive_id = config.get("sensitive_id", "") + sensitive_id = os.environ.get("SENSITIVE_ID", sensitive_id) + +# 模型配置 +if "extra_models" in config: + presets.MODELS.extend(config["extra_models"]) + logging.info(f"已添加额外的模型:{config['extra_models']}") + +google_palm_api_key = config.get("google_palm_api_key", "") +google_palm_api_key = os.environ.get( + "GOOGLE_PALM_API_KEY", google_palm_api_key) +os.environ["GOOGLE_PALM_API_KEY"] = google_palm_api_key xmchat_api_key = config.get("xmchat_api_key", "") os.environ["XMCHAT_API_KEY"] = xmchat_api_key @@ -81,11 +114,23 @@ os.environ["MINIMAX_API_KEY"] = minimax_api_key minimax_group_id = config.get("minimax_group_id", "") os.environ["MINIMAX_GROUP_ID"] = minimax_group_id +midjourney_proxy_api_base = config.get("midjourney_proxy_api_base", "") +os.environ["MIDJOURNEY_PROXY_API_BASE"] = midjourney_proxy_api_base +midjourney_proxy_api_secret = config.get("midjourney_proxy_api_secret", "") +os.environ["MIDJOURNEY_PROXY_API_SECRET"] = midjourney_proxy_api_secret +midjourney_discord_proxy_url = config.get("midjourney_discord_proxy_url", "") +os.environ["MIDJOURNEY_DISCORD_PROXY_URL"] = midjourney_discord_proxy_url +midjourney_temp_folder = config.get("midjourney_temp_folder", "") +os.environ["MIDJOURNEY_TEMP_FOLDER"] = midjourney_temp_folder + +load_config_to_environ(["openai_api_type", "azure_openai_api_key", "azure_openai_api_base_url", + "azure_openai_api_version", "azure_deployment_name", "azure_embedding_deployment_name", "azure_embedding_model_name"]) + usage_limit = os.environ.get("USAGE_LIMIT", config.get("usage_limit", 120)) -## 多账户机制 -multi_api_key = config.get("multi_api_key", False) # 是否开启多账户机制 +# 多账户机制 +multi_api_key = config.get("multi_api_key", False) # 是否开启多账户机制 if multi_api_key: api_key_list = config.get("api_key_list", []) if len(api_key_list) == 0: @@ -93,21 +138,26 @@ if multi_api_key: sys.exit(1) shared.state.set_api_key_queue(api_key_list) -auth_list = config.get("users", []) # 实际上是使用者的列表 +auth_list = config.get("users", []) # 实际上是使用者的列表 authflag = len(auth_list) > 0 # 是否开启认证的状态值,改为判断auth_list长度 # 处理自定义的api_host,优先读环境变量的配置,如果存在则自动装配 -api_host = os.environ.get("OPENAI_API_BASE", config.get("openai_api_base", None)) +api_host = os.environ.get( + "OPENAI_API_BASE", config.get("openai_api_base", None)) if api_host is not None: shared.state.set_api_host(api_host) + os.environ["OPENAI_API_BASE"] = f"{api_host}/v1" + logging.info(f"OpenAI API Base set to: {os.environ['OPENAI_API_BASE']}") -default_chuanhu_assistant_model = config.get("default_chuanhu_assistant_model", "gpt-3.5-turbo") +default_chuanhu_assistant_model = config.get( + "default_chuanhu_assistant_model", "gpt-3.5-turbo") for x in ["GOOGLE_CSE_ID", "GOOGLE_API_KEY", "WOLFRAM_ALPHA_APPID", "SERPAPI_API_KEY"]: if config.get(x, None) is not None: os.environ[x] = config[x] + @contextmanager -def retrieve_openai_api(api_key = None): +def retrieve_openai_api(api_key=None): old_api_key = os.environ.get("OPENAI_API_KEY", "") if api_key is None: os.environ["OPENAI_API_KEY"] = my_api_key @@ -117,24 +167,20 @@ def retrieve_openai_api(api_key = None): yield api_key os.environ["OPENAI_API_KEY"] = old_api_key -## 处理log -log_level = config.get("log_level", "INFO") -logging.basicConfig( - level=log_level, - format="%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s", -) -## 处理代理: -http_proxy = config.get("http_proxy", "") -https_proxy = config.get("https_proxy", "") -http_proxy = os.environ.get("HTTP_PROXY", http_proxy) -https_proxy = os.environ.get("HTTPS_PROXY", https_proxy) + +# 处理代理: +http_proxy = os.environ.get("HTTP_PROXY", "") +https_proxy = os.environ.get("HTTPS_PROXY", "") +http_proxy = config.get("http_proxy", http_proxy) +https_proxy = config.get("https_proxy", https_proxy) # 重置系统变量,在不需要设置的时候不设置环境变量,以免引起全局代理报错 os.environ["HTTP_PROXY"] = "" os.environ["HTTPS_PROXY"] = "" -local_embedding = config.get("local_embedding", False) # 是否使用本地embedding +local_embedding = config.get("local_embedding", False) # 是否使用本地embedding + @contextmanager def retrieve_proxy(proxy=None): @@ -151,22 +197,62 @@ def retrieve_proxy(proxy=None): old_var = os.environ["HTTP_PROXY"], os.environ["HTTPS_PROXY"] os.environ["HTTP_PROXY"] = http_proxy os.environ["HTTPS_PROXY"] = https_proxy - yield http_proxy, https_proxy # return new proxy + yield http_proxy, https_proxy # return new proxy # return old proxy os.environ["HTTP_PROXY"], os.environ["HTTPS_PROXY"] = old_var -## 处理advance docs +# 处理latex options +user_latex_option = config.get("latex_option", "default") +if user_latex_option == "default": + latex_delimiters_set = [ + {"left": "$$", "right": "$$", "display": True}, + {"left": "$", "right": "$", "display": False}, + {"left": "\\(", "right": "\\)", "display": False}, + {"left": "\\[", "right": "\\]", "display": True}, + ] +elif user_latex_option == "strict": + latex_delimiters_set = [ + {"left": "$$", "right": "$$", "display": True}, + {"left": "\\(", "right": "\\)", "display": False}, + {"left": "\\[", "right": "\\]", "display": True}, + ] +elif user_latex_option == "all": + latex_delimiters_set = [ + {"left": "$$", "right": "$$", "display": True}, + {"left": "$", "right": "$", "display": False}, + {"left": "\\(", "right": "\\)", "display": False}, + {"left": "\\[", "right": "\\]", "display": True}, + {"left": "\\begin{equation}", "right": "\\end{equation}", "display": True}, + {"left": "\\begin{align}", "right": "\\end{align}", "display": True}, + {"left": "\\begin{alignat}", "right": "\\end{alignat}", "display": True}, + {"left": "\\begin{gather}", "right": "\\end{gather}", "display": True}, + {"left": "\\begin{CD}", "right": "\\end{CD}", "display": True}, + ] +elif user_latex_option == "disabled": + latex_delimiters_set = [] +else: + latex_delimiters_set = [ + {"left": "$$", "right": "$$", "display": True}, + {"left": "$", "right": "$", "display": False}, + {"left": "\\(", "right": "\\)", "display": False}, + {"left": "\\[", "right": "\\]", "display": True}, + ] + +# 处理advance docs advance_docs = defaultdict(lambda: defaultdict(dict)) advance_docs.update(config.get("advance_docs", {})) + + def update_doc_config(two_column_pdf): global advance_docs advance_docs["pdf"]["two_column"] = two_column_pdf logging.info(f"更新后的文件参数为:{advance_docs}") -## 处理gradio.launch参数 + +# 处理gradio.launch参数 server_name = config.get("server_name", None) server_port = config.get("server_port", None) if server_name is None: @@ -188,3 +274,7 @@ except ValueError: pass share = config.get("share", False) + +# avatar +bot_avatar = config.get("bot_avatar", "default") +user_avatar = config.get("user_avatar", "default") \ No newline at end of file diff --git a/modules/index_func.py b/modules/index_func.py index 09f792eb9df4d55d8bb1c172a9d07d7c41541266..ac128668c2920b6b4b945e0de3dcd745fe141200 100644 --- a/modules/index_func.py +++ b/modules/index_func.py @@ -1,7 +1,7 @@ import os import logging -import colorama +import hashlib import PyPDF2 from tqdm import tqdm @@ -10,19 +10,6 @@ from modules.utils import * from modules.config import local_embedding -def get_index_name(file_src): - file_paths = [x.name for x in file_src] - file_paths.sort(key=lambda x: os.path.basename(x)) - - md5_hash = hashlib.md5() - for file_path in file_paths: - with open(file_path, "rb") as f: - while chunk := f.read(8192): - md5_hash.update(chunk) - - return md5_hash.hexdigest() - - def get_documents(file_src): from langchain.schema import Document from langchain.text_splitter import TokenTextSplitter @@ -47,11 +34,12 @@ def get_documents(file_src): pdftext = parse_pdf(filepath, two_column).text except: pdftext = "" - with open(filepath, "rb", encoding="utf-8") as pdfFileObj: + with open(filepath, "rb") as pdfFileObj: pdfReader = PyPDF2.PdfReader(pdfFileObj) for page in tqdm(pdfReader.pages): pdftext += page.extract_text() - texts = [Document(page_content=pdftext, metadata={"source": filepath})] + texts = [Document(page_content=pdftext, + metadata={"source": filepath})] elif file_type == ".docx": logging.debug("Loading Word...") from langchain.document_loaders import UnstructuredWordDocumentLoader @@ -72,7 +60,8 @@ def get_documents(file_src): text_list = excel_to_string(filepath) texts = [] for elem in text_list: - texts.append(Document(page_content=elem, metadata={"source": filepath})) + texts.append(Document(page_content=elem, + metadata={"source": filepath})) else: logging.debug("Loading text file...") from langchain.document_loaders import TextLoader @@ -111,14 +100,20 @@ def construct_index( embedding_limit = None if embedding_limit == 0 else embedding_limit separator = " " if separator == "" else separator - index_name = get_index_name(file_src) + index_name = get_file_hash(file_src) index_path = f"./index/{index_name}" if local_embedding: from langchain.embeddings.huggingface import HuggingFaceEmbeddings - embeddings = HuggingFaceEmbeddings(model_name = "sentence-transformers/distiluse-base-multilingual-cased-v2") + embeddings = HuggingFaceEmbeddings( + model_name="sentence-transformers/distiluse-base-multilingual-cased-v2") else: from langchain.embeddings import OpenAIEmbeddings - embeddings = OpenAIEmbeddings(openai_api_base=os.environ.get("OPENAI_API_BASE", None), openai_api_key=os.environ.get("OPENAI_EMBEDDING_API_KEY", api_key)) + if os.environ.get("OPENAI_API_TYPE", "openai") == "openai": + embeddings = OpenAIEmbeddings(openai_api_base=os.environ.get( + "OPENAI_API_BASE", None), openai_api_key=os.environ.get("OPENAI_EMBEDDING_API_KEY", api_key)) + else: + embeddings = OpenAIEmbeddings(deployment=os.environ["AZURE_EMBEDDING_DEPLOYMENT_NAME"], openai_api_key=os.environ["AZURE_OPENAI_API_KEY"], + model=os.environ["AZURE_EMBEDDING_MODEL_NAME"], openai_api_base=os.environ["AZURE_OPENAI_API_BASE_URL"], openai_api_type="azure") if os.path.exists(index_path): logging.info("找到了缓存的索引文件,加载中……") return FAISS.load_local(index_path, embeddings) diff --git a/modules/models/Google_PaLM.py b/modules/models/Google_PaLM.py new file mode 100644 index 0000000000000000000000000000000000000000..79ca042e228b25546600e4258a0b75790e25bb52 --- /dev/null +++ b/modules/models/Google_PaLM.py @@ -0,0 +1,26 @@ +from .base_model import BaseLLMModel +import google.generativeai as palm + +class Google_PaLM_Client(BaseLLMModel): + def __init__(self, model_name, api_key, user_name="") -> None: + super().__init__(model_name=model_name, user=user_name) + self.api_key = api_key + + def _get_palm_style_input(self): + new_history = [] + for item in self.history: + if item["role"] == "user": + new_history.append({'author': '1', 'content': item["content"]}) + else: + new_history.append({'author': '0', 'content': item["content"]}) + return new_history + + def get_answer_at_once(self): + palm.configure(api_key=self.api_key) + messages = self._get_palm_style_input() + response = palm.chat(context=self.system_prompt, messages=messages, temperature=self.temperature, top_p=self.top_p) + if response.last is not None: + return response.last, len(response.last) + else: + reasons = '\n\n'.join(reason['reason'].name for reason in response.filters) + return "由于下面的原因,Google 拒绝返回 PaLM 的回答:\n\n" + reasons, 0 \ No newline at end of file diff --git a/modules/models/__pycache__/ChuanhuAgent.cpython-311.pyc b/modules/models/__pycache__/ChuanhuAgent.cpython-311.pyc index d4433e7511f215299b6860e7e018885b0fb4d48f..52ddd9f380549cd59f30b5f64838cc1802ca55ec 100644 Binary files a/modules/models/__pycache__/ChuanhuAgent.cpython-311.pyc and b/modules/models/__pycache__/ChuanhuAgent.cpython-311.pyc differ diff --git a/modules/models/__pycache__/Google_PaLM.cpython-311.pyc b/modules/models/__pycache__/Google_PaLM.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed75107609d5740e72e85029fee7bf9d492fc841 Binary files /dev/null and b/modules/models/__pycache__/Google_PaLM.cpython-311.pyc differ diff --git a/modules/models/__pycache__/azure.cpython-311.pyc b/modules/models/__pycache__/azure.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e551727c93687624924c67029e32057c8ceecfc3 Binary files /dev/null and b/modules/models/__pycache__/azure.cpython-311.pyc differ diff --git a/modules/models/__pycache__/base_model.cpython-311.pyc b/modules/models/__pycache__/base_model.cpython-311.pyc index 351b91a086cfacf0d9677f176ceb54ce5668058c..e24b400d5a0b82cda92b099df8df18940afa66b4 100644 Binary files a/modules/models/__pycache__/base_model.cpython-311.pyc and b/modules/models/__pycache__/base_model.cpython-311.pyc differ diff --git a/modules/models/__pycache__/base_model.cpython-39.pyc b/modules/models/__pycache__/base_model.cpython-39.pyc index 5cd38dcb76a41f29fc2d86f586c411480b65eda2..eef2894f3d10ee39351925e8da7952ad7f462fe2 100644 Binary files a/modules/models/__pycache__/base_model.cpython-39.pyc and b/modules/models/__pycache__/base_model.cpython-39.pyc differ diff --git a/modules/models/__pycache__/models.cpython-311.pyc b/modules/models/__pycache__/models.cpython-311.pyc index a1e63fb18cb6ef430173b8d71e575b4dd7da8c5f..67918c0d0fed25631b30819c4271bb73ae63ae92 100644 Binary files a/modules/models/__pycache__/models.cpython-311.pyc and b/modules/models/__pycache__/models.cpython-311.pyc differ diff --git a/modules/models/__pycache__/models.cpython-39.pyc b/modules/models/__pycache__/models.cpython-39.pyc index 16fc4cbf158d5d39c01c2bf33a0d7f011765ed34..41fc2f8be62153f20564b31afc5d163dd1131e2d 100644 Binary files a/modules/models/__pycache__/models.cpython-39.pyc and b/modules/models/__pycache__/models.cpython-39.pyc differ diff --git a/modules/models/azure.py b/modules/models/azure.py new file mode 100644 index 0000000000000000000000000000000000000000..42cddfbda8cc74e40e114ee4bed46a2f9ff74ce9 --- /dev/null +++ b/modules/models/azure.py @@ -0,0 +1,17 @@ +from langchain.chat_models import AzureChatOpenAI +import os + +from .base_model import Base_Chat_Langchain_Client + +# load_config_to_environ(["azure_openai_api_key", "azure_api_base_url", "azure_openai_api_version", "azure_deployment_name"]) + +class Azure_OpenAI_Client(Base_Chat_Langchain_Client): + def setup_model(self): + # inplement this to setup the model then return it + return AzureChatOpenAI( + openai_api_base=os.environ["AZURE_OPENAI_API_BASE_URL"], + openai_api_version=os.environ["AZURE_OPENAI_API_VERSION"], + deployment_name=os.environ["AZURE_DEPLOYMENT_NAME"], + openai_api_key=os.environ["AZURE_OPENAI_API_KEY"], + openai_api_type="azure", + ) \ No newline at end of file diff --git a/modules/models/base_model.py b/modules/models/base_model.py index 0c703b6750cbea953bbe8e97a806473831035c0a..fa94579d725dbf9d739d58fc17b35bc2248c7fcd 100644 --- a/modules/models/base_model.py +++ b/modules/models/base_model.py @@ -29,6 +29,8 @@ from langchain.input import print_text from langchain.schema import AgentAction, AgentFinish, LLMResult from threading import Thread, Condition from collections import deque +from langchain.chat_models.base import BaseChatModel +from langchain.schema import HumanMessage, AIMessage, SystemMessage, BaseMessage from ..presets import * from ..index_func import * @@ -36,6 +38,7 @@ from ..utils import * from .. import shared from ..config import retrieve_proxy + class CallbackToIterator: def __init__(self): self.queue = deque() @@ -52,7 +55,8 @@ class CallbackToIterator: def __next__(self): with self.cond: - while not self.queue and not self.finished: # Wait for a value to be added to the queue. + # Wait for a value to be added to the queue. + while not self.queue and not self.finished: self.cond.wait() if not self.queue: raise StopIteration() @@ -63,6 +67,7 @@ class CallbackToIterator: self.finished = True self.cond.notify() # Wake up the generator if it's waiting. + def get_action_description(text): match = re.search('```(.*?)```', text, re.S) json_text = match.group(1) @@ -72,10 +77,11 @@ def get_action_description(text): action_name = json_dict['action'] action_input = json_dict['action_input'] if action_name != "Final Answer": - return f'

{action_name}: {action_input}

' + return f'

{action_name}: {action_input}\n\n

' else: return "" + class ChuanhuCallbackHandler(BaseCallbackHandler): def __init__(self, callback) -> None: @@ -117,6 +123,10 @@ class ChuanhuCallbackHandler(BaseCallbackHandler): """Run on new LLM token. Only available when streaming is enabled.""" self.callback(token) + def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], **kwargs: Any) -> Any: + """Run when a chat model starts running.""" + pass + class ModelType(Enum): Unknown = -1 @@ -129,6 +139,9 @@ class ModelType(Enum): YuanAI = 6 Minimax = 7 ChuanhuAgent = 8 + GooglePaLM = 9 + LangchainChat = 10 + Midjourney = 11 @classmethod def get_type(cls, model_name: str): @@ -152,6 +165,12 @@ class ModelType(Enum): model_type = ModelType.Minimax elif "川虎助理" in model_name_lower: model_type = ModelType.ChuanhuAgent + elif "palm" in model_name_lower: + model_type = ModelType.GooglePaLM + elif "midjourney" in model_name_lower: + model_type = ModelType.Midjourney + elif "azure" in model_name_lower or "api" in model_name_lower: + model_type = ModelType.LangchainChat else: model_type = ModelType.Unknown return model_type @@ -161,7 +180,7 @@ class BaseLLMModel: def __init__( self, model_name, - system_prompt="", + system_prompt=INITIAL_SYSTEM_PROMPT, temperature=1.0, top_p=1.0, n_choices=1, @@ -201,7 +220,8 @@ class BaseLLMModel: conversations are stored in self.history, with the most recent question, in OpenAI format should return a generator, each time give the next word (str) in the answer """ - logging.warning("stream predict not implemented, using at once predict instead") + logging.warning( + "stream predict not implemented, using at once predict instead") response, _ = self.get_answer_at_once() yield response @@ -212,7 +232,8 @@ class BaseLLMModel: the answer (str) total token count (int) """ - logging.warning("at once predict not implemented, using stream predict instead") + logging.warning( + "at once predict not implemented, using stream predict instead") response_iter = self.get_answer_stream_iter() count = 0 for response in response_iter: @@ -246,7 +267,8 @@ class BaseLLMModel: stream_iter = self.get_answer_stream_iter() if display_append: - display_append = "
" +display_append + display_append = '\n\n
' + display_append + partial_text = "" for partial_text in stream_iter: chatbot[-1] = (chatbot[-1][0], partial_text + display_append) self.all_token_counts[-1] += 1 @@ -273,9 +295,11 @@ class BaseLLMModel: self.history[-2] = construct_user(fake_input) chatbot[-1] = (chatbot[-1][0], ai_reply + display_append) if fake_input is not None: - self.all_token_counts[-1] += count_token(construct_assistant(ai_reply)) + self.all_token_counts[-1] += count_token( + construct_assistant(ai_reply)) else: - self.all_token_counts[-1] = total_token_count - sum(self.all_token_counts) + self.all_token_counts[-1] = total_token_count - \ + sum(self.all_token_counts) status_text = self.token_message() return chatbot, status_text @@ -299,10 +323,13 @@ class BaseLLMModel: from langchain.chat_models import ChatOpenAI from langchain.callbacks import StdOutCallbackHandler prompt_template = "Write a concise summary of the following:\n\n{text}\n\nCONCISE SUMMARY IN " + language + ":" - PROMPT = PromptTemplate(template=prompt_template, input_variables=["text"]) + PROMPT = PromptTemplate( + template=prompt_template, input_variables=["text"]) llm = ChatOpenAI() - chain = load_summarize_chain(llm, chain_type="map_reduce", return_intermediate_steps=True, map_prompt=PROMPT, combine_prompt=PROMPT) - summary = chain({"input_documents": list(index.docstore.__dict__["_dict"].values())}, return_only_outputs=True)["output_text"] + chain = load_summarize_chain( + llm, chain_type="map_reduce", return_intermediate_steps=True, map_prompt=PROMPT, combine_prompt=PROMPT) + summary = chain({"input_documents": list(index.docstore.__dict__[ + "_dict"].values())}, return_only_outputs=True)["output_text"] print(i18n("总结") + f": {summary}") chatbot.append([i18n("上传了")+str(len(files))+"个文件", summary]) return chatbot, status @@ -323,9 +350,12 @@ class BaseLLMModel: msg = "索引获取成功,生成回答中……" logging.info(msg) with retrieve_proxy(): - retriever = VectorStoreRetriever(vectorstore=index, search_type="similarity_score_threshold",search_kwargs={"k":6, "score_threshold": 0.5}) - relevant_documents = retriever.get_relevant_documents(real_inputs) - reference_results = [[d.page_content.strip("�"), os.path.basename(d.metadata["source"])] for d in relevant_documents] + retriever = VectorStoreRetriever(vectorstore=index, search_type="similarity_score_threshold", search_kwargs={ + "k": 6, "score_threshold": 0.5}) + relevant_documents = retriever.get_relevant_documents( + real_inputs) + reference_results = [[d.page_content.strip("�"), os.path.basename( + d.metadata["source"])] for d in relevant_documents] reference_results = add_source_numbers(reference_results) display_append = add_details(reference_results) display_append = "\n\n" + "".join(display_append) @@ -348,10 +378,12 @@ class BaseLLMModel: reference_results.append([result['body'], result['href']]) display_append.append( # f"{idx+1}. [{domain_name}]({result['href']})\n" - f"
  • {result['title']}
  • \n" + f"{idx+1}. {result['title']}" ) reference_results = add_source_numbers(reference_results) - display_append = "
      \n\n" + "".join(display_append) + "
    " + # display_append = "
      \n\n" + "".join(display_append) + "
    " + display_append = '
    ' + \ + "".join(display_append) + '
    ' real_inputs = ( replace_today(WEBSEARCH_PTOMPT_TEMPLATE) .replace("{query}", real_inputs) @@ -375,14 +407,16 @@ class BaseLLMModel: status_text = "开始生成回答……" logging.info( - "用户" + f"{self.user_identifier}" + "的输入为:" + colorama.Fore.BLUE + f"{inputs}" + colorama.Style.RESET_ALL + "用户" + f"{self.user_identifier}" + "的输入为:" + + colorama.Fore.BLUE + f"{inputs}" + colorama.Style.RESET_ALL ) if should_check_token_count: yield chatbot + [(inputs, "")], status_text if reply_language == "跟随问题语言(不稳定)": reply_language = "the same language as the question, such as English, 中文, 日本語, Español, Français, or Deutsch." - limited_context, fake_inputs, display_append, inputs, chatbot = self.prepare_inputs(real_inputs=inputs, use_websearch=use_websearch, files=files, reply_language=reply_language, chatbot=chatbot) + limited_context, fake_inputs, display_append, inputs, chatbot = self.prepare_inputs( + real_inputs=inputs, use_websearch=use_websearch, files=files, reply_language=reply_language, chatbot=chatbot) yield chatbot + [(fake_inputs, "")], status_text if ( @@ -434,7 +468,7 @@ class BaseLLMModel: yield chatbot, status_text except Exception as e: traceback.print_exc() - status_text = STANDARD_ERROR_MSG + str(e) + status_text = STANDARD_ERROR_MSG + beautify_err_msg(str(e)) yield chatbot, status_text if len(self.history) > 1 and self.history[-1]["content"] != inputs: @@ -568,10 +602,13 @@ class BaseLLMModel: self.system_prompt = new_system_prompt def set_key(self, new_access_key): - self.api_key = new_access_key.strip() - msg = i18n("API密钥更改为了") + hide_middle_chars(self.api_key) - logging.info(msg) - return self.api_key, msg + if "*" not in new_access_key: + self.api_key = new_access_key.strip() + msg = i18n("API密钥更改为了") + hide_middle_chars(self.api_key) + logging.info(msg) + return self.api_key, msg + else: + return gr.update(), gr.update() def set_single_turn(self, new_single_turn): self.single_turn = new_single_turn @@ -580,7 +617,8 @@ class BaseLLMModel: self.history = [] self.all_token_counts = [] self.interrupted = False - pathlib.Path(os.path.join(HISTORY_DIR, self.user_identifier, new_auto_history_filename(os.path.join(HISTORY_DIR, self.user_identifier)))).touch() + pathlib.Path(os.path.join(HISTORY_DIR, self.user_identifier, new_auto_history_filename( + os.path.join(HISTORY_DIR, self.user_identifier)))).touch() return [], self.token_message([0]) def delete_first_conversation(self): @@ -623,7 +661,8 @@ class BaseLLMModel: def auto_save(self, chatbot): history_file_path = get_history_filepath(self.user_identifier) - save_file(history_file_path, self.system_prompt, self.history, chatbot, self.user_identifier) + save_file(history_file_path, self.system_prompt, + self.history, chatbot, self.user_identifier) def export_markdown(self, filename, chatbot, user_name): if filename == "": @@ -639,7 +678,8 @@ class BaseLLMModel: filename = filename.name try: if "/" not in filename: - history_file_path = os.path.join(HISTORY_DIR, user_name, filename) + history_file_path = os.path.join( + HISTORY_DIR, user_name, filename) else: history_file_path = filename with open(history_file_path, "r", encoding="utf-8") as f: @@ -665,15 +705,33 @@ class BaseLLMModel: logging.info(f"没有找到对话历史记录 {filename}") return gr.update(), self.system_prompt, gr.update() + def delete_chat_history(self, filename, user_name): + if filename == "CANCELED": + return gr.update(), gr.update(), gr.update() + if filename == "": + return i18n("你没有选择任何对话历史"), gr.update(), gr.update() + if not filename.endswith(".json"): + filename += ".json" + if "/" not in filename: + history_file_path = os.path.join(HISTORY_DIR, user_name, filename) + else: + history_file_path = filename + try: + os.remove(history_file_path) + return i18n("删除对话历史成功"), get_history_names(False, user_name), [] + except: + logging.info(f"删除对话历史失败 {history_file_path}") + return i18n("对话历史")+filename+i18n("已经被删除啦"), gr.update(), gr.update() + def auto_load(self): if self.user_identifier == "": self.reset() return self.system_prompt, gr.update() history_file_path = get_history_filepath(self.user_identifier) - filename, system_prompt, chatbot = self.load_chat_history(history_file_path, self.user_identifier) + filename, system_prompt, chatbot = self.load_chat_history( + history_file_path, self.user_identifier) return system_prompt, chatbot - def like(self): """like the last response, implement if needed """ @@ -683,3 +741,47 @@ class BaseLLMModel: """dislike the last response, implement if needed """ return gr.update() + + +class Base_Chat_Langchain_Client(BaseLLMModel): + def __init__(self, model_name, user_name=""): + super().__init__(model_name, user=user_name) + self.need_api_key = False + self.model = self.setup_model() + + def setup_model(self): + # inplement this to setup the model then return it + pass + + def _get_langchain_style_history(self): + history = [SystemMessage(content=self.system_prompt)] + for i in self.history: + if i["role"] == "user": + history.append(HumanMessage(content=i["content"])) + elif i["role"] == "assistant": + history.append(AIMessage(content=i["content"])) + return history + + def get_answer_at_once(self): + assert isinstance( + self.model, BaseChatModel), "model is not instance of LangChain BaseChatModel" + history = self._get_langchain_style_history() + response = self.model.generate(history) + return response.content, sum(response.content) + + def get_answer_stream_iter(self): + it = CallbackToIterator() + assert isinstance( + self.model, BaseChatModel), "model is not instance of LangChain BaseChatModel" + history = self._get_langchain_style_history() + + def thread_func(): + self.model(messages=history, callbacks=[ + ChuanhuCallbackHandler(it.callback)]) + it.finish() + t = Thread(target=thread_func) + t.start() + partial_text = "" + for value in it: + partial_text += value + yield partial_text diff --git a/modules/models/midjourney.py b/modules/models/midjourney.py new file mode 100644 index 0000000000000000000000000000000000000000..65a560fc2427aad735d227d4d25b61b72f3ace5a --- /dev/null +++ b/modules/models/midjourney.py @@ -0,0 +1,385 @@ +import base64 +import io +import json +import logging +import pathlib +import time +import tempfile +import os + +from datetime import datetime + +import requests +import tiktoken +from PIL import Image + +from modules.config import retrieve_proxy +from modules.models.models import XMChat + +mj_proxy_api_base = os.getenv("MIDJOURNEY_PROXY_API_BASE") +mj_discord_proxy_url = os.getenv("MIDJOURNEY_DISCORD_PROXY_URL") +mj_temp_folder = os.getenv("MIDJOURNEY_TEMP_FOLDER") + + +class Midjourney_Client(XMChat): + + class FetchDataPack: + """ + A class to store data for current fetching data from Midjourney API + """ + + action: str # current action, e.g. "IMAGINE", "UPSCALE", "VARIATION" + prefix_content: str # prefix content, task description and process hint + task_id: str # task id + start_time: float # task start timestamp + timeout: int # task timeout in seconds + finished: bool # whether the task is finished + prompt: str # prompt for the task + + def __init__(self, action, prefix_content, task_id, timeout=900): + self.action = action + self.prefix_content = prefix_content + self.task_id = task_id + self.start_time = time.time() + self.timeout = timeout + self.finished = False + + def __init__(self, model_name, api_key, user_name=""): + super().__init__(api_key, user_name) + self.model_name = model_name + self.history = [] + self.api_key = api_key + self.headers = { + "Content-Type": "application/json", + "mj-api-secret": f"{api_key}" + } + self.proxy_url = mj_proxy_api_base + self.command_splitter = "::" + + if mj_temp_folder: + temp = "./tmp" + if user_name: + temp = os.path.join(temp, user_name) + if not os.path.exists(temp): + os.makedirs(temp) + self.temp_path = tempfile.mkdtemp(dir=temp) + logging.info("mj temp folder: " + self.temp_path) + else: + self.temp_path = None + + def use_mj_self_proxy_url(self, img_url): + """ + replace discord cdn url with mj self proxy url + """ + return img_url.replace( + "https://cdn.discordapp.com/", + mj_discord_proxy_url and mj_discord_proxy_url or "https://cdn.discordapp.com/" + ) + + def split_image(self, image_url): + """ + when enabling temp dir, split image into 4 parts + """ + with retrieve_proxy(): + image_bytes = requests.get(image_url).content + img = Image.open(io.BytesIO(image_bytes)) + width, height = img.size + # calculate half width and height + half_width = width // 2 + half_height = height // 2 + # create coordinates (top-left x, top-left y, bottom-right x, bottom-right y) + coordinates = [(0, 0, half_width, half_height), + (half_width, 0, width, half_height), + (0, half_height, half_width, height), + (half_width, half_height, width, height)] + + images = [img.crop(c) for c in coordinates] + return images + + def auth_mj(self): + """ + auth midjourney api + """ + # TODO: check if secret is valid + return {'status': 'ok'} + + def request_mj(self, path: str, action: str, data: str, retries=3): + """ + request midjourney api + """ + mj_proxy_url = self.proxy_url + if mj_proxy_url is None or not (mj_proxy_url.startswith("http://") or mj_proxy_url.startswith("https://")): + raise Exception('please set MIDJOURNEY_PROXY_API_BASE in ENV or in config.json') + + auth_ = self.auth_mj() + if auth_.get('error'): + raise Exception('auth not set') + + fetch_url = f"{mj_proxy_url}/{path}" + # logging.info(f"[MJ Proxy] {action} {fetch_url} params: {data}") + + for _ in range(retries): + try: + with retrieve_proxy(): + res = requests.request(method=action, url=fetch_url, headers=self.headers, data=data) + break + except Exception as e: + print(e) + + if res.status_code != 200: + raise Exception(f'{res.status_code} - {res.content}') + + return res + + def fetch_status(self, fetch_data: FetchDataPack): + """ + fetch status of current task + """ + if fetch_data.start_time + fetch_data.timeout < time.time(): + fetch_data.finished = True + return "任务超时,请检查 dc 输出。描述:" + fetch_data.prompt + + time.sleep(3) + status_res = self.request_mj(f"task/{fetch_data.task_id}/fetch", "GET", '') + status_res_json = status_res.json() + if not (200 <= status_res.status_code < 300): + raise Exception("任务状态获取失败:" + status_res_json.get( + 'error') or status_res_json.get('description') or '未知错误') + else: + fetch_data.finished = False + if status_res_json['status'] == "SUCCESS": + content = status_res_json['imageUrl'] + fetch_data.finished = True + elif status_res_json['status'] == "FAILED": + content = status_res_json['failReason'] or '未知原因' + fetch_data.finished = True + elif status_res_json['status'] == "NOT_START": + content = f'任务未开始,已等待 {time.time() - fetch_data.start_time:.2f} 秒' + elif status_res_json['status'] == "IN_PROGRESS": + content = '任务正在运行' + if status_res_json.get('progress'): + content += f",进度:{status_res_json['progress']}" + elif status_res_json['status'] == "SUBMITTED": + content = '任务已提交处理' + elif status_res_json['status'] == "FAILURE": + fetch_data.finished = True + return "任务处理失败,原因:" + status_res_json['failReason'] or '未知原因' + else: + content = status_res_json['status'] + if fetch_data.finished: + img_url = self.use_mj_self_proxy_url(status_res_json['imageUrl']) + if fetch_data.action == "DESCRIBE": + return f"\n{status_res_json['prompt']}" + time_cost_str = f"\n\n{fetch_data.action} 花费时间:{time.time() - fetch_data.start_time:.2f} 秒" + upscale_str = "" + variation_str = "" + if fetch_data.action in ["IMAGINE", "UPSCALE", "VARIATION"]: + upscale = [f'/mj UPSCALE{self.command_splitter}{i+1}{self.command_splitter}{fetch_data.task_id}' + for i in range(4)] + upscale_str = '\n放大图片:\n\n' + '\n\n'.join(upscale) + variation = [f'/mj VARIATION{self.command_splitter}{i+1}{self.command_splitter}{fetch_data.task_id}' + for i in range(4)] + variation_str = '\n图片变体:\n\n' + '\n\n'.join(variation) + if self.temp_path and fetch_data.action in ["IMAGINE", "VARIATION"]: + try: + images = self.split_image(img_url) + # save images to temp path + for i in range(4): + images[i].save(pathlib.Path(self.temp_path) / f"{fetch_data.task_id}_{i}.png") + img_str = '\n'.join( + [f"![{fetch_data.task_id}](/file={self.temp_path}/{fetch_data.task_id}_{i}.png)" + for i in range(4)]) + return fetch_data.prefix_content + f"{time_cost_str}\n\n{img_str}{upscale_str}{variation_str}" + except Exception as e: + logging.error(e) + return fetch_data.prefix_content + \ + f"{time_cost_str}[![{fetch_data.task_id}]({img_url})]({img_url}){upscale_str}{variation_str}" + else: + content = f"**任务状态:** [{(datetime.now()).strftime('%Y-%m-%d %H:%M:%S')}] - {content}" + content += f"\n\n花费时间:{time.time() - fetch_data.start_time:.2f} 秒" + if status_res_json['status'] == 'IN_PROGRESS' and status_res_json.get('imageUrl'): + img_url = status_res_json.get('imageUrl') + return f"{content}\n[![{fetch_data.task_id}]({img_url})]({img_url})" + return content + return None + + def handle_file_upload(self, files, chatbot, language): + """ + handle file upload + """ + if files: + for file in files: + if file.name: + logging.info(f"尝试读取图像: {file.name}") + self.try_read_image(file.name) + if self.image_path is not None: + chatbot = chatbot + [((self.image_path,), None)] + if self.image_bytes is not None: + logging.info("使用图片作为输入") + return None, chatbot, None + + def reset(self): + self.image_bytes = None + self.image_path = None + return [], "已重置" + + def get_answer_at_once(self): + content = self.history[-1]['content'] + answer = self.get_help() + + if not content.lower().startswith("/mj"): + return answer, len(content) + + prompt = content[3:].strip() + action = "IMAGINE" + first_split_index = prompt.find(self.command_splitter) + if first_split_index > 0: + action = prompt[:first_split_index] + if action not in ["IMAGINE", "DESCRIBE", "UPSCALE", + # "VARIATION", "BLEND", "REROLL" + ]: + raise Exception("任务提交失败:未知的任务类型") + else: + action_index = None + action_use_task_id = None + if action in ["VARIATION", "UPSCALE", "REROLL"]: + action_index = int(prompt[first_split_index + 2:first_split_index + 3]) + action_use_task_id = prompt[first_split_index + 5:] + + try: + res = None + if action == "IMAGINE": + data = { + "prompt": prompt + } + if self.image_bytes is not None: + data["base64"] = 'data:image/png;base64,' + self.image_bytes + res = self.request_mj("submit/imagine", "POST", + json.dumps(data)) + elif action == "DESCRIBE": + res = self.request_mj("submit/describe", "POST", + json.dumps({"base64": 'data:image/png;base64,' + self.image_bytes})) + elif action == "BLEND": + res = self.request_mj("submit/blend", "POST", json.dumps( + {"base64Array": [self.image_bytes, self.image_bytes]})) + elif action in ["UPSCALE", "VARIATION", "REROLL"]: + res = self.request_mj( + "submit/change", "POST", + json.dumps({"action": action, "index": action_index, "taskId": action_use_task_id})) + res_json = res.json() + if not (200 <= res.status_code < 300) or (res_json['code'] not in [1, 22]): + answer = "任务提交失败:" + res_json.get('error', res_json.get('description', '未知错误')) + else: + task_id = res_json['result'] + prefix_content = f"**画面描述:** {prompt}\n**任务ID:** {task_id}\n" + + fetch_data = Midjourney_Client.FetchDataPack( + action=action, + prefix_content=prefix_content, + task_id=task_id, + ) + fetch_data.prompt = prompt + while not fetch_data.finished: + answer = self.fetch_status(fetch_data) + except Exception as e: + logging.error("submit failed", e) + answer = "任务提交错误:" + str(e.args[0]) if e.args else '未知错误' + + return answer, tiktoken.get_encoding("cl100k_base").encode(content) + + def get_answer_stream_iter(self): + content = self.history[-1]['content'] + answer = self.get_help() + + if not content.lower().startswith("/mj"): + yield answer + return + + prompt = content[3:].strip() + action = "IMAGINE" + first_split_index = prompt.find(self.command_splitter) + if first_split_index > 0: + action = prompt[:first_split_index] + if action not in ["IMAGINE", "DESCRIBE", "UPSCALE", + "VARIATION", "BLEND", "REROLL" + ]: + yield "任务提交失败:未知的任务类型" + return + + action_index = None + action_use_task_id = None + if action in ["VARIATION", "UPSCALE", "REROLL"]: + action_index = int(prompt[first_split_index + 2:first_split_index + 3]) + action_use_task_id = prompt[first_split_index + 5:] + + try: + res = None + if action == "IMAGINE": + data = { + "prompt": prompt + } + if self.image_bytes is not None: + data["base64"] = 'data:image/png;base64,' + self.image_bytes + res = self.request_mj("submit/imagine", "POST", + json.dumps(data)) + elif action == "DESCRIBE": + res = self.request_mj("submit/describe", "POST", json.dumps( + {"base64": 'data:image/png;base64,' + self.image_bytes})) + elif action == "BLEND": + res = self.request_mj("submit/blend", "POST", json.dumps( + {"base64Array": [self.image_bytes, self.image_bytes]})) + elif action in ["UPSCALE", "VARIATION", "REROLL"]: + res = self.request_mj( + "submit/change", "POST", + json.dumps({"action": action, "index": action_index, "taskId": action_use_task_id})) + res_json = res.json() + if not (200 <= res.status_code < 300) or (res_json['code'] not in [1, 22]): + yield "任务提交失败:" + res_json.get('error', res_json.get('description', '未知错误')) + else: + task_id = res_json['result'] + prefix_content = f"**画面描述:** {prompt}\n**任务ID:** {task_id}\n" + content = f"[{(datetime.now()).strftime('%Y-%m-%d %H:%M:%S')}] - 任务提交成功:" + \ + res_json.get('description') or '请稍等片刻' + yield content + + fetch_data = Midjourney_Client.FetchDataPack( + action=action, + prefix_content=prefix_content, + task_id=task_id, + ) + while not fetch_data.finished: + yield self.fetch_status(fetch_data) + except Exception as e: + logging.error('submit failed', e) + yield "任务提交错误:" + str(e.args[0]) if e.args else '未知错误' + + def get_help(self): + return """``` +【绘图帮助】 +所有命令都需要以 /mj 开头,如:/mj a dog +IMAGINE - 绘图,可以省略该命令,后面跟上绘图内容 + /mj a dog + /mj IMAGINE::a cat +DESCRIBE - 描述图片,需要在右下角上传需要描述的图片内容 + /mj DESCRIBE:: +UPSCALE - 确认后放大图片,第一个数值为需要放大的图片(1~4),第二参数为任务ID + /mj UPSCALE::1::123456789 + 请使用SD进行UPSCALE +VARIATION - 图片变体,第一个数值为需要放大的图片(1~4),第二参数为任务ID + /mj VARIATION::1::123456789 + +【绘图参数】 +所有命令默认会带上参数--v 5.2 +其他参数参照 https://docs.midjourney.com/docs/parameter-list +长宽比 --aspect/--ar + --ar 1:2 + --ar 16:9 +负面tag --no + --no plants + --no hands +随机种子 --seed + --seed 1 +生成动漫风格(NijiJourney) --niji + --niji +``` +""" diff --git a/modules/models/models.py b/modules/models/models.py index be730033c42c1085a8c25bbd30cc4c84933f3770..fe1edbc91fc611881f6dff34affd23dc93596699 100644 --- a/modules/models/models.py +++ b/modules/models/models.py @@ -24,7 +24,7 @@ from ..presets import * from ..index_func import * from ..utils import * from .. import shared -from ..config import retrieve_proxy, usage_limit +from ..config import retrieve_proxy, usage_limit, sensitive_id from modules import config from .base_model import BaseLLMModel, ModelType @@ -87,21 +87,23 @@ class OpenAIClient(BaseLLMModel): try: usage_data = self._get_billing_data(usage_url) except Exception as e: - logging.error(f"获取API使用情况失败:" + str(e)) + # logging.error(f"获取API使用情况失败: " + str(e)) + if "Invalid authorization header" in str(e): + return i18n("**获取API使用情况失败**,需在填写`config.json`中正确填写sensitive_id") + elif "Incorrect API key provided: sess" in str(e): + return i18n("**获取API使用情况失败**,sensitive_id错误或已过期") return i18n("**获取API使用情况失败**") # rounded_usage = "{:.5f}".format(usage_data["total_usage"] / 100) rounded_usage = round(usage_data["total_usage"] / 100, 5) usage_percent = round(usage_data["total_usage"] / usage_limit, 2) + from ..webui import get_html # return i18n("**本月使用金额** ") + f"\u3000 ${rounded_usage}" - return """\ - """ + i18n("本月使用金额") + f""" -
    -
    - {usage_percent}% -
    -
    -
    ${rounded_usage}${usage_limit}
    - """ + return get_html("billing_info.html").format( + label = i18n("本月使用金额"), + usage_percent = usage_percent, + rounded_usage = rounded_usage, + usage_limit = usage_limit + ) except requests.exceptions.ConnectTimeout: status_text = ( STANDARD_ERROR_MSG + CONNECTION_TIMEOUT_MSG + ERROR_RETRIEVE_MSG @@ -161,7 +163,7 @@ class OpenAIClient(BaseLLMModel): # 如果有自定义的api-host,使用自定义host发送请求,否则使用默认设置发送请求 if shared.state.completion_url != COMPLETION_URL: - logging.info(f"使用自定义API URL: {shared.state.completion_url}") + logging.debug(f"使用自定义API URL: {shared.state.completion_url}") with retrieve_proxy(): try: @@ -179,9 +181,10 @@ class OpenAIClient(BaseLLMModel): def _refresh_header(self): self.headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}", + "Authorization": f"Bearer {sensitive_id}", } + def _get_billing_data(self, billing_url): with retrieve_proxy(): response = requests.get( @@ -206,7 +209,7 @@ class OpenAIClient(BaseLLMModel): chunk_length = len(chunk) try: chunk = json.loads(chunk[6:]) - except json.JSONDecodeError: + except: print(i18n("JSON解析错误,收到的内容: ") + f"{chunk}") error_msg += chunk continue @@ -560,6 +563,7 @@ def get_model( try: if model_type == ModelType.OpenAI: logging.info(f"正在加载OpenAI模型: {model_name}") + access_key = os.environ.get("OPENAI_API_KEY", access_key) model = OpenAIClient( model_name=model_name, api_key=access_key, @@ -610,16 +614,29 @@ def get_model( elif model_type == ModelType.ChuanhuAgent: from .ChuanhuAgent import ChuanhuAgent_Client model = ChuanhuAgent_Client(model_name, access_key, user_name=user_name) + elif model_type == ModelType.GooglePaLM: + from .Google_PaLM import Google_PaLM_Client + access_key = os.environ.get("GOOGLE_PALM_API_KEY", access_key) + model = Google_PaLM_Client(model_name, access_key, user_name=user_name) + elif model_type == ModelType.LangchainChat: + from .azure import Azure_OpenAI_Client + model = Azure_OpenAI_Client(model_name, user_name=user_name) + elif model_type == ModelType.Midjourney: + from .midjourney import Midjourney_Client + mj_proxy_api_secret = os.getenv("MIDJOURNEY_PROXY_API_SECRET") + model = Midjourney_Client(model_name, mj_proxy_api_secret, user_name=user_name) elif model_type == ModelType.Unknown: raise ValueError(f"未知模型: {model_name}") logging.info(msg) except Exception as e: - logging.error(e) + import traceback + traceback.print_exc() msg = f"{STANDARD_ERROR_MSG}: {e}" + presudo_key = hide_middle_chars(access_key) if dont_change_lora_selector: - return model, msg, chatbot + return model, msg, chatbot, gr.update(), access_key, presudo_key else: - return model, msg, chatbot, gr.Dropdown.update(choices=lora_choices, visible=lora_selector_visibility) + return model, msg, chatbot, gr.Dropdown.update(choices=lora_choices, visible=lora_selector_visibility), access_key, presudo_key if __name__ == "__main__": diff --git a/modules/overwrites.py b/modules/overwrites.py index e029f4a50285c64dcb286a34cb1c3b2680880e05..a4ef6167eb7ce75ed8b88024ad1187b24f2fc191 100644 --- a/modules/overwrites.py +++ b/modules/overwrites.py @@ -71,23 +71,36 @@ def postprocess_chat_messages( else: raise ValueError(f"Invalid message for Chatbot component: {chat_message}") -with open("./assets/custom.js", "r", encoding="utf-8") as f, \ - open("./assets/external-scripts.js", "r", encoding="utf-8") as f1: - customJS = f.read() - externalScripts = f1.read() - - -def reload_javascript(): - print("Reloading javascript...") - js = f'' - # if render_latex: - # js += """\""" - def template_response(*args, **kwargs): - res = GradioTemplateResponseOriginal(*args, **kwargs) - res.body = res.body.replace(b'', f'{js}'.encode("utf8")) - res.init_headers() - return res - - gr.routes.templates.TemplateResponse = template_response - -GradioTemplateResponseOriginal = gr.routes.templates.TemplateResponse \ No newline at end of file + + +def add_classes_to_gradio_component(comp): + """ + this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others + code from stable-diffusion-webui + """ + + comp.elem_classes = [f"gradio-{comp.get_block_name()}", *(comp.elem_classes or [])] + + if getattr(comp, 'multiselect', False): + comp.elem_classes.append('multiselect') + + +def IOComponent_init(self, *args, **kwargs): + res = original_IOComponent_init(self, *args, **kwargs) + add_classes_to_gradio_component(self) + + return res + +original_IOComponent_init = gr.components.IOComponent.__init__ +gr.components.IOComponent.__init__ = IOComponent_init + + +def BlockContext_init(self, *args, **kwargs): + res = original_BlockContext_init(self, *args, **kwargs) + add_classes_to_gradio_component(self) + + return res + +original_BlockContext_init = gr.blocks.BlockContext.__init__ +gr.blocks.BlockContext.__init__ = BlockContext_init + diff --git a/modules/presets.py b/modules/presets.py index 23a1ec3bd13fe761653a89920bd087f425e12fcb..a56d50e1c7aefae37b3252b983d445ea327471a4 100644 --- a/modules/presets.py +++ b/modules/presets.py @@ -60,19 +60,24 @@ ONLINE_MODELS = [ "gpt-4-32k-0613", "川虎助理", "川虎助理 Pro", + "GooglePaLM", "xmchat", + "Azure OpenAI", "yuanai-1.0-base_10B", "yuanai-1.0-translate", "yuanai-1.0-dialog", "yuanai-1.0-rhythm_poems", "minimax-abab4-chat", "minimax-abab5-chat", + "midjourney" ] LOCAL_MODELS = [ "chatglm-6b", "chatglm-6b-int4", - "chatglm-6b-int4-qe", + "chatglm-6b-int4-ge", + "chatglm2-6b", + "chatglm2-6b-int4", "StableLM", "MOSS", "llama-7b-hf", @@ -121,6 +126,7 @@ REPLY_LANGUAGES = [ "Español", "Français", "Deutsch", + "한국어", "跟随问题语言(不稳定)" ] @@ -170,6 +176,8 @@ SUMMARIZE_PROMPT = """Write a concise summary of the following: CONCISE SUMMARY IN 中文:""" ALREADY_CONVERTED_MARK = "" +START_OF_OUTPUT_MARK = "" +END_OF_OUTPUT_MARK = "" small_and_beautiful_theme = gr.themes.Soft( primary_hue=gr.themes.Color( @@ -222,7 +230,7 @@ small_and_beautiful_theme = gr.themes.Soft( # button_primary_background_fill_hover="*primary_400", # button_primary_border_color="*primary_500", button_primary_border_color_dark="*primary_600", - button_primary_text_color="wihte", + button_primary_text_color="white", button_primary_text_color_dark="white", button_secondary_background_fill="*neutral_100", button_secondary_background_fill_hover="*neutral_50", diff --git a/modules/repo.py b/modules/repo.py new file mode 100644 index 0000000000000000000000000000000000000000..2788de5b06a744bc436df677a973d89c26489a8a --- /dev/null +++ b/modules/repo.py @@ -0,0 +1,239 @@ +# -*- coding:utf-8 -*- +import os +import sys +import subprocess +from functools import lru_cache +import logging +import gradio as gr +import datetime + +# This file is mainly used to describe repo version info, execute the git command, python pip command, shell command, etc. +# Part of the code in this file is referenced from stable-diffusion-webui/modules/launch_utils.py + +python = sys.executable +pip = os.environ.get('PIP', "pip") +git = os.environ.get('GIT', "git") + +# Pypi index url +index_url = os.environ.get('INDEX_URL', "") + +# Whether to default to printing command output +default_command_live = True + + +def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str: + if desc is not None: + print(desc) + run_kwargs = { + "args": command, + "shell": True, + "env": os.environ if custom_env is None else custom_env, + "encoding": 'utf8', + "errors": 'ignore', + } + + if not live: + run_kwargs["stdout"] = run_kwargs["stderr"] = subprocess.PIPE + + result = subprocess.run(**run_kwargs) + if result.returncode != 0: + error_bits = [ + f"{errdesc or 'Error running command'}.", + f"Command: {command}", + f"Error code: {result.returncode}", + ] + if result.stdout: + error_bits.append(f"stdout: {result.stdout}") + if result.stderr: + error_bits.append(f"stderr: {result.stderr}") + raise RuntimeError("\n".join(error_bits)) + + return (result.stdout or "") + + +def run_pip(command, desc=None, pref=None, live=default_command_live): + # if args.skip_install: + # return + + index_url_line = f' --index-url {index_url}' if index_url != '' else '' + return run( + f'"{python}" -m pip {command} --prefer-binary{index_url_line}', + desc=f"{pref} Installing {desc}...", + errdesc=f"Couldn't install {desc}", + live=live + ) + + +@lru_cache() +def commit_hash(): + try: + return subprocess.check_output([git, "rev-parse", "HEAD"], shell=False, encoding='utf8').strip() + except Exception: + return "" + +def commit_html(): + commit = commit_hash() + if commit != "": + short_commit = commit[0:7] + commit_info = f'{short_commit}' + else: + commit_info = "unknown \U0001F615" + return commit_info + +@lru_cache() +def tag_html(): + try: + latest_tag = run(f"{git} describe --tags --abbrev=0", live=False).strip() + try: + # tag = subprocess.check_output([git, "describe", "--tags", "--exact-match"], shell=False, encoding='utf8').strip() + tag = run(f"{git} describe --tags --exact-match", live=False).strip() + except Exception: + tag = "" + except Exception: + tag = "" + + if tag == "": + tag_info = "unknown \U0001F615" + elif tag == "": + tag_info = f'{latest_tag}*' + else: + tag_info = f'{tag}' + + return tag_info + +def repo_tag_html(): + commit_version = commit_html() + tag_version = tag_html() + return tag_version if tag_version != "unknown \U0001F615" else commit_version + +def versions_html(): + python_version = ".".join([str(x) for x in sys.version_info[0:3]]) + repo_version = repo_tag_html() + return f""" + Python: {python_version} +  •  + Gradio: {gr.__version__} +  •  + ChuanhuChat: {repo_version} + """ + +def version_time(): + try: + commit_time = subprocess.check_output(f"TZ=UTC {git} log -1 --format=%cd --date='format-local:%Y-%m-%dT%H:%M:%SZ'", shell=True, encoding='utf8').strip() + # commit_time = run(f"TZ=UTC {git} log -1 --format=%cd --date='format-local:%Y-%m-%dT%H:%M:%SZ'").strip() + except Exception: + commit_time = "unknown" + return commit_time + + + +def get_current_branch(): + try: + # branch = run(f"{git} rev-parse --abbrev-ref HEAD").strip() + branch = subprocess.check_output([git, "rev-parse", "--abbrev-ref", "HEAD"], shell=False, encoding='utf8').strip() + except Exception: + branch = "" + return branch + + +def get_latest_release(): + try: + import requests + release = requests.get("https://api.github.com/repos/GaiZhenbiao/ChuanhuChatGPT/releases/latest").json() + tag = release["tag_name"] + release_note = release["body"] + need_pip = release_note.find("requirements reinstall needed") != -1 + except Exception: + tag = "" + release_note = "" + need_pip = False + return {"tag": tag, "release_note": release_note, "need_pip": need_pip} + +def get_tag_commit_hash(tag): + try: + import requests + tags = requests.get("https://api.github.com/repos/GaiZhenbiao/ChuanhuChatGPT/tags").json() + commit_hash = [x["commit"]["sha"] for x in tags if x["name"] == tag][0] + except Exception: + commit_hash = "" + return commit_hash + +def repo_need_stash(): + try: + return subprocess.check_output([git, "diff-index", "--quiet", "HEAD", "--"], shell=False, encoding='utf8').strip() != "" + except Exception: + return True + +def background_update(): + # {git} fetch --all && ({git} pull https://github.com/GaiZhenbiao/ChuanhuChatGPT.git main -f || ({git} stash && {git} pull https://github.com/GaiZhenbiao/ChuanhuChatGPT.git main -f && {git} stash pop)) && {pip} install -r requirements.txt") + try: + latest_release = get_latest_release() + latest_release_tag = latest_release["tag"] + latest_release_hash = get_tag_commit_hash(latest_release_tag) + need_pip = latest_release["need_pip"] + need_stash = repo_need_stash() + + timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + current_branch = get_current_branch() + updater_branch = f'tmp_{timestamp}' + backup_branch = f'backup_{timestamp}' + track_repo = "https://github.com/GaiZhenbiao/ChuanhuChatGPT.git" + try: + try: + run(f"{git} fetch {track_repo}", desc="[Updater] Fetching from github...", live=False) + except Exception: + logging.error(f"Update failed in fetching, check your network connection") + return "failed" + + run(f'{git} stash push --include-untracked -m "updater-{timestamp}"', + desc=f'[Updater] Restoring you local changes on stash updater-{timestamp}', live=False) if need_stash else None + + run(f"{git} checkout -b {backup_branch}", live=False) + run(f"{git} checkout -b {updater_branch}", live=False) + run(f"{git} reset --hard FETCH_HEAD", live=False) + run(f"{git} reset --hard {latest_release_hash}", desc=f'[Updater] Checking out {latest_release_tag}...', live=False) + run(f"{git} checkout {current_branch}", live=False) + + try: + run(f"{git} merge --no-edit {updater_branch} -q", desc=f"[Updater] Trying to apply latest update on version {latest_release_tag}...") + except Exception: + logging.error(f"Update failed in merging") + try: + run(f"{git} merge --abort", desc="[Updater] Conflict detected, canceling update...") + run(f"{git} reset --hard {backup_branch}", live=False) + run(f"{git} branch -D -f {updater_branch}", live=False) + run(f"{git} branch -D -f {backup_branch}", live=False) + run(f"{git} stash pop", live=False) if need_stash else None + logging.error(f"Update failed, but your file was safely reset to the state before the update.") + return "failed" + except Exception as e: + logging.error(f"!!!Update failed in resetting, try to reset your files manually. {e}") + return "failed" + + if need_stash: + try: + run(f"{git} stash apply", desc="[Updater] Trying to restore your local modifications...", live=False) + except Exception: + run(f"{git} reset --hard {backup_branch}", desc="[Updater] Conflict detected, canceling update...", live=False) + run(f"{git} branch -D -f {updater_branch}", live=False) + run(f"{git} branch -D -f {backup_branch}", live=False) + run(f"{git} stash pop", live=False) + logging.error(f"Update failed in applying your local changes, but your file was safely reset to the state before the update.") + return "failed" + run(f"{git} stash drop", live=False) + + run(f"{git} branch -D -f {updater_branch}", live=False) + run(f"{git} branch -D -f {backup_branch}", live=False) + except Exception as e: + logging.error(f"Update failed: {e}") + return "failed" + if need_pip: + try: + run_pip(f"install -r requirements.txt", pref="[Updater]", desc="requirements", live=False) + except Exception: + logging.error(f"Update failed in pip install") + return "failed" + return "success" + except Exception as e: + logging.error(f"Update failed: {e}") + return "failed" diff --git a/modules/shared.py b/modules/shared.py index 32e74665b400a56fd1b10bbd4a9566fe332e49bd..89f0779459225957c13865ef7f7448efae6d1998 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -62,3 +62,4 @@ state = State() modules_path = os.path.dirname(os.path.realpath(__file__)) chuanhu_path = os.path.dirname(modules_path) +assets_path = os.path.join(chuanhu_path, "web_assets") \ No newline at end of file diff --git a/modules/train_func.py b/modules/train_func.py new file mode 100644 index 0000000000000000000000000000000000000000..bc5e2c6aea1f3f28d4bb3f9f4fd2f6d761ba00a2 --- /dev/null +++ b/modules/train_func.py @@ -0,0 +1,161 @@ +import os +import logging +import traceback + +import openai +import gradio as gr +import ujson as json +import commentjson +import openpyxl + +import modules.presets as presets +from modules.utils import get_file_hash, count_token +from modules.presets import i18n + +def excel_to_jsonl(filepath, preview=False): + # 打开Excel文件 + workbook = openpyxl.load_workbook(filepath) + + # 获取第一个工作表 + sheet = workbook.active + + # 获取所有行数据 + data = [] + for row in sheet.iter_rows(values_only=True): + data.append(row) + + # 构建字典列表 + headers = data[0] + jsonl = [] + for row in data[1:]: + row_data = dict(zip(headers, row)) + if any(row_data.values()): + jsonl.append(row_data) + formatted_jsonl = [] + for i in jsonl: + if "提问" in i and "答案" in i: + if "系统" in i : + formatted_jsonl.append({ + "messages":[ + {"role": "system", "content": i["系统"]}, + {"role": "user", "content": i["提问"]}, + {"role": "assistant", "content": i["答案"]} + ] + }) + else: + formatted_jsonl.append({ + "messages":[ + {"role": "user", "content": i["提问"]}, + {"role": "assistant", "content": i["答案"]} + ] + }) + else: + logging.warning(f"跳过一行数据,因为没有找到提问和答案: {i}") + return formatted_jsonl + +def jsonl_save_to_disk(jsonl, filepath): + file_hash = get_file_hash(file_paths = [filepath]) + os.makedirs("files", exist_ok=True) + save_path = f"files/{file_hash}.jsonl" + with open(save_path, "w") as f: + f.write("\n".join([json.dumps(i, ensure_ascii=False) for i in jsonl])) + return save_path + +def estimate_cost(ds): + dialogues = [] + for l in ds: + for m in l["messages"]: + dialogues.append(m["content"]) + dialogues = "\n".join(dialogues) + tokens = count_token(dialogues) + return f"Token 数约为 {tokens},预估每轮(epoch)费用约为 {tokens / 1000 * 0.008} 美元。" + + +def handle_dataset_selection(file_src): + logging.info(f"Loading dataset {file_src.name}...") + preview = "" + if file_src.name.endswith(".jsonl"): + with open(file_src.name, "r") as f: + ds = [json.loads(l) for l in f.readlines()] + else: + ds = excel_to_jsonl(file_src.name) + preview = ds[0] + + return preview, gr.update(interactive=True), estimate_cost(ds) + +def upload_to_openai(file_src): + openai.api_key = os.getenv("OPENAI_API_KEY") + dspath = file_src.name + msg = "" + logging.info(f"Uploading dataset {dspath}...") + if dspath.endswith(".xlsx"): + jsonl = excel_to_jsonl(dspath) + dspath = jsonl_save_to_disk(jsonl, dspath) + try: + uploaded = openai.File.create( + file=open(dspath, "rb"), + purpose='fine-tune' + ) + return uploaded.id, f"上传成功" + except Exception as e: + traceback.print_exc() + return "", f"上传失败,原因:{ e }" + +def build_event_description(id, status, trained_tokens, name=i18n("暂时未知")): + # convert to markdown + return f""" + #### 训练任务 {id} + + 模型名称:{name} + + 状态:{status} + + 已经训练了 {trained_tokens} 个token + """ + +def start_training(file_id, suffix, epochs): + openai.api_key = os.getenv("OPENAI_API_KEY") + try: + job = openai.FineTuningJob.create(training_file=file_id, model="gpt-3.5-turbo", suffix=suffix, hyperparameters={"n_epochs": epochs}) + return build_event_description(job.id, job.status, job.trained_tokens) + except Exception as e: + traceback.print_exc() + if "is not ready" in str(e): + return "训练出错,因为文件还没准备好。OpenAI 需要一点时间准备文件,过几分钟再来试试。" + return f"训练失败,原因:{ e }" + +def get_training_status(): + openai.api_key = os.getenv("OPENAI_API_KEY") + active_jobs = [build_event_description(job["id"], job["status"], job["trained_tokens"], job["fine_tuned_model"]) for job in openai.FineTuningJob.list(limit=10)["data"] if job["status"] != "cancelled"] + return "\n\n".join(active_jobs), gr.update(interactive=True) if len(active_jobs) > 0 else gr.update(interactive=False) + +def handle_dataset_clear(): + return gr.update(value=None), gr.update(interactive=False) + +def add_to_models(): + openai.api_key = os.getenv("OPENAI_API_KEY") + succeeded_jobs = [job for job in openai.FineTuningJob.list()["data"] if job["status"] == "succeeded"] + extra_models = [job["fine_tuned_model"] for job in succeeded_jobs] + for i in extra_models: + if i not in presets.MODELS: + presets.MODELS.append(i) + + with open('config.json', 'r') as f: + data = commentjson.load(f) + if 'extra_models' in data: + for i in extra_models: + if i not in data['extra_models']: + data['extra_models'].append(i) + else: + data['extra_models'] = extra_models + with open('config.json', 'w') as f: + commentjson.dump(data, f, indent=4) + + return gr.update(choices=presets.MODELS), f"成功添加了 {len(succeeded_jobs)} 个模型。" + +def cancel_all_jobs(): + openai.api_key = os.getenv("OPENAI_API_KEY") + jobs = [job for job in openai.FineTuningJob.list()["data"] if job["status"] not in ["cancelled", "succeeded"]] + for job in jobs: + openai.FineTuningJob.cancel(job["id"]) + return f"成功取消了 {len(jobs)} 个训练任务。" diff --git a/modules/utils.py b/modules/utils.py index 6f93d1f71d41837cda6b4cbe7c3c6bbc18fa2109..fcc7d4b198a8e796d3ef5016c8eb0226ca4d6f9a 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -2,16 +2,14 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Callable, Dict, List, Tuple, Type import logging -import json +import commentjson as json import os import datetime -import hashlib import csv import requests import re import html -import sys -import subprocess +import hashlib import gradio as gr from pypinyin import lazy_pinyin @@ -47,6 +45,9 @@ def set_key(current_model, *args): def load_chat_history(current_model, *args): return current_model.load_chat_history(*args) +def delete_chat_history(current_model, *args): + return current_model.delete_chat_history(*args) + def interrupt(current_model, *args): return current_model.interrupt(*args) @@ -125,9 +126,10 @@ def dislike(current_model, *args): return current_model.dislike(*args) -def count_token(message): +def count_token(input_str): encoding = tiktoken.get_encoding("cl100k_base") - input_str = f"role: {message['role']}, content: {message['content']}" + if type(input_str) == dict: + input_str = f"role: {input_str['role']}, content: {input_str['content']}" length = len(encoding.encode(input_str)) return length @@ -202,6 +204,28 @@ def convert_mdtext(md_text): # deprecated output += ALREADY_CONVERTED_MARK return output + +def clip_rawtext(chat_message, need_escape=True): + # first, clip hr line + hr_pattern = r'\n\n
    (.*?)' + hr_match = re.search(hr_pattern, chat_message, re.DOTALL) + message_clipped = chat_message[:hr_match.start()] if hr_match else chat_message + # second, avoid agent-prefix being escaped + agent_prefix_pattern = r'

    (.*?)<\/p>' + agent_matches = re.findall(agent_prefix_pattern, message_clipped) + final_message = "" + if agent_matches: + agent_parts = re.split(agent_prefix_pattern, message_clipped) + for i, part in enumerate(agent_parts): + if i % 2 == 0: + final_message += escape_markdown(part) if need_escape else part + else: + final_message += f'

    {part}

    ' + else: + final_message = escape_markdown(message_clipped) if need_escape else message_clipped + return final_message + + def convert_bot_before_marked(chat_message): """ 注意不能给输出加缩进, 否则会被marked解析成代码块 @@ -209,12 +233,13 @@ def convert_bot_before_marked(chat_message): if '
    ' in chat_message: return chat_message else: + raw = f'
    {clip_rawtext(chat_message)}
    ' + # really_raw = f'{START_OF_OUTPUT_MARK}
    {clip_rawtext(chat_message, need_escape=False)}\n
    {END_OF_OUTPUT_MARK}' + code_block_pattern = re.compile(r"```(.*?)(?:```|$)", re.DOTALL) code_blocks = code_block_pattern.findall(chat_message) non_code_parts = code_block_pattern.split(chat_message)[::2] result = [] - - raw = f'
    {escape_markdown(chat_message)}
    ' for non_code, code in zip(non_code_parts, code_blocks + [""]): if non_code.strip(): result.append(non_code) @@ -222,7 +247,7 @@ def convert_bot_before_marked(chat_message): code = f"\n```{code}\n```" result.append(code) result = "".join(result) - md = f'
    {result}\n
    ' + md = f'
    \n\n{result}\n
    ' return raw + md def convert_user_before_marked(chat_message): @@ -236,7 +261,7 @@ def escape_markdown(text): Escape Markdown special characters to HTML-safe equivalents. """ escape_chars = { - ' ': ' ', + # ' ': ' ', '_': '_', '*': '*', '[': '[', @@ -253,8 +278,12 @@ def escape_markdown(text): '`': '`', '>': '>', '<': '<', - '|': '|' + '|': '|', + '$': '$', + ':': ':', + '\n': '
    ', } + text = text.replace(' ', '    ') return ''.join(escape_chars.get(c, c) for c in text) @@ -508,55 +537,19 @@ def transfer_input(inputs): ) +def update_chuanhu(): + from .repo import background_update -def run(command, desc=None, errdesc=None, custom_env=None, live=False): - if desc is not None: - print(desc) - if live: - result = subprocess.run(command, shell=True, env=os.environ if custom_env is None else custom_env) - if result.returncode != 0: - raise RuntimeError(f"""{errdesc or 'Error running command'}. - Command: {command} - Error code: {result.returncode}""") - - return "" - result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ if custom_env is None else custom_env) - if result.returncode != 0: - message = f"""{errdesc or 'Error running command'}. - Command: {command} - Error code: {result.returncode} - stdout: {result.stdout.decode(encoding="utf8", errors="ignore") if len(result.stdout)>0 else ''} - stderr: {result.stderr.decode(encoding="utf8", errors="ignore") if len(result.stderr)>0 else ''} - """ - raise RuntimeError(message) - return result.stdout.decode(encoding="utf8", errors="ignore") - -def versions_html(): - git = os.environ.get('GIT', "git") - python_version = ".".join([str(x) for x in sys.version_info[0:3]]) - try: - commit_hash = run(f"{git} rev-parse HEAD").strip() - except Exception: - commit_hash = "" - if commit_hash != "": - short_commit = commit_hash[0:7] - commit_info = f"{short_commit}" + print("[Updater] Trying to update...") + update_status = background_update() + if update_status == "success": + logging.info("Successfully updated, restart needed") + status = 'success' + return gr.Markdown.update(value=i18n("更新成功,请重启本程序")+status) else: - commit_info = "unknown \U0001F615" - return f""" - Python: {python_version} -  •  - Gradio: {gr.__version__} -  •  - ChuanhuChat: {commit_info} - """ - -def get_html(filename): - path = os.path.join(shared.chuanhu_path, "assets", "html", filename) - if os.path.exists(path): - with open(path, encoding="utf8") as file: - return file.read() - return "" + status = 'failure' + return gr.Markdown.update(value=i18n("更新失败,请尝试[手动更新](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/使用教程#手动更新)")+status) + def add_source_numbers(lst, source_name = "Source", use_source = True): if use_source: @@ -654,3 +647,37 @@ def get_history_filepath(username): latest_file = os.path.join(dirname, latest_file) return latest_file + +def beautify_err_msg(err_msg): + if "insufficient_quota" in err_msg: + return i18n("剩余配额不足,[进一步了解](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98#you-exceeded-your-current-quota-please-check-your-plan-and-billing-details)") + if "The model: gpt-4 does not exist" in err_msg: + return i18n("你没有权限访问 GPT4,[进一步了解](https://github.com/GaiZhenbiao/ChuanhuChatGPT/issues/843)") + if "Resource not found" in err_msg: + return i18n("请查看 config_example.json,配置 Azure OpenAI") + return err_msg + +def auth_from_conf(username, password): + try: + with open("config.json", encoding="utf-8") as f: + conf = json.load(f) + usernames, passwords = [i[0] for i in conf["users"]], [i[1] for i in conf["users"]] + if username in usernames: + if passwords[usernames.index(username)] == password: + return True + return False + except: + return False + +def get_file_hash(file_src=None, file_paths=None): + if file_src: + file_paths = [x.name for x in file_src] + file_paths.sort(key=lambda x: os.path.basename(x)) + + md5_hash = hashlib.md5() + for file_path in file_paths: + with open(file_path, "rb") as f: + while chunk := f.read(8192): + md5_hash.update(chunk) + + return md5_hash.hexdigest() diff --git a/modules/webui.py b/modules/webui.py new file mode 100644 index 0000000000000000000000000000000000000000..61f863d7ca3b8975222b90d4f66a2c6cdc9d2e0d --- /dev/null +++ b/modules/webui.py @@ -0,0 +1,70 @@ + +from collections import namedtuple +import os +import gradio as gr + +from . import shared + +# with open("./assets/ChuanhuChat.js", "r", encoding="utf-8") as f, \ +# open("./assets/external-scripts.js", "r", encoding="utf-8") as f1: +# customJS = f.read() +# externalScripts = f1.read() + + +def get_html(filename): + path = os.path.join(shared.chuanhu_path, "web_assets", "html", filename) + if os.path.exists(path): + with open(path, encoding="utf8") as file: + return file.read() + return "" + +def webpath(fn): + if fn.startswith(shared.assets_path): + web_path = os.path.relpath(fn, shared.chuanhu_path).replace('\\', '/') + else: + web_path = os.path.abspath(fn) + return f'file={web_path}?{os.path.getmtime(fn)}' + +ScriptFile = namedtuple("ScriptFile", ["basedir", "filename", "path"]) + +def javascript_html(): + head = "" + for script in list_scripts("javascript", ".js"): + head += f'\n' + for script in list_scripts("javascript", ".mjs"): + head += f'\n' + return head + +def css_html(): + head = "" + for cssfile in list_scripts("stylesheet", ".css"): + head += f'' + return head + +def list_scripts(scriptdirname, extension): + scripts_list = [] + scripts_dir = os.path.join(shared.chuanhu_path, "web_assets", scriptdirname) + if os.path.exists(scripts_dir): + for filename in sorted(os.listdir(scripts_dir)): + scripts_list.append(ScriptFile(shared.assets_path, filename, os.path.join(scripts_dir, filename))) + scripts_list = [x for x in scripts_list if os.path.splitext(x.path)[1].lower() == extension and os.path.isfile(x.path)] + return scripts_list + + +def reload_javascript(): + js = javascript_html() + js += '' + js += '' + + css = css_html() + + def template_response(*args, **kwargs): + res = GradioTemplateResponseOriginal(*args, **kwargs) + res.body = res.body.replace(b'', f'{js}'.encode("utf8")) + res.body = res.body.replace(b'', f'{css}'.encode("utf8")) + res.init_headers() + return res + + gr.routes.templates.TemplateResponse = template_response + +GradioTemplateResponseOriginal = gr.routes.templates.TemplateResponse \ No newline at end of file diff --git a/readme/README_en.md b/readme/README_en.md index a906ecb3ebc411f5cdeb33d661266a489a20c3b0..80af4fbbfba5d15e1cb6d1f4b67808ca76fa37d7 100644 --- a/readme/README_en.md +++ b/readme/README_en.md @@ -6,7 +6,7 @@

    川虎 Chat 🐯 Chuanhu Chat

    - Logo + Logo

    @@ -44,6 +44,23 @@

    +## Supported LLM Models + +**LLM models via API**: + +- [ChatGPT](https://chat.openai.com) ([GPT-4](https://openai.com/product/gpt-4)) +- [Google PaLM](https://developers.generativeai.google/products/palm) +- [Inspur Yuan 1.0](https://air.inspur.com/home) +- [MiniMax](https://api.minimax.chat/) +- [XMChat](https://github.com/MILVLG/xmchat) + +**LLM models via local deployment**: + +- [ChatGLM](https://github.com/THUDM/ChatGLM-6B) ([ChatGLM2](https://github.com/THUDM/ChatGLM2-6B)) +- [LLaMA](https://github.com/facebookresearch/llama) +- [StableLM](https://github.com/Stability-AI/StableLM) +- [MOSS](https://github.com/OpenLMLab/MOSS) + ## Usage Tips - To better control the ChatGPT, use System Prompt. @@ -51,11 +68,11 @@ - To try again if the response is unsatisfactory, use `🔄 Regenerate` button. - To start a new line in the input box, press Shift + Enter keys. - To quickly switch between input history, press and key in the input box. -- To deploy the program onto a server, change the last line of the program to `demo.launch(server_name="0.0.0.0", server_port=)`. -- To get a public shared link, change the last line of the program to `demo.launch(share=True)`. Please be noted that the program must be running in order to be accessed via a public link. +- To deploy the program onto a server, set `"server_name": "0.0.0.0", "server_port" ,` in `config.json`. +- To get a public shared link, set `"share": true,` in `config.json`. Please be noted that the program must be running in order to be accessed via a public link. - To use it in Hugging Face Spaces: It is recommended to **Duplicate Space** and run the program in your own Space for a faster and more secure experience. -## Installation +## Quickstart ```shell git clone https://github.com/GaiZhenbiao/ChuanhuChatGPT.git @@ -87,10 +104,6 @@ When you encounter problems, you should try manually pulling the latest changes ``` pip install -r requirements.txt ``` -3. Update Gradio - ``` - pip install gradio --upgrade --force-reinstall - ``` Generally, you can solve most problems by following these steps. diff --git a/readme/README_ja.md b/readme/README_ja.md index fc56eec0b81c22ff0a49e3960aa52ffd7d6dc5cb..1e0771070e0c9852f02a1024c65176f5a1ac46ba 100644 --- a/readme/README_ja.md +++ b/readme/README_ja.md @@ -6,7 +6,7 @@

    川虎 Chat 🐯 Chuanhu Chat

    - Logo + Logo

    @@ -44,17 +44,34 @@

    +## サポートされている大規模言語モデル + +**APIを通じてアクセス可能な大規模言語モデル**: + +- [ChatGPT](https://chat.openai.com) ([GPT-4](https://openai.com/product/gpt-4)) +- [Google PaLM](https://developers.generativeai.google/products/palm) +- [Inspur Yuan 1.0](https://air.inspur.com/home) +- [MiniMax](https://api.minimax.chat/) +- [XMChat](https://github.com/MILVLG/xmchat) + +**ローカルに展開された大規模言語モデル**: + +- [ChatGLM](https://github.com/THUDM/ChatGLM-6B) ([ChatGLM2](https://github.com/THUDM/ChatGLM2-6B)) +- [LLaMA](https://github.com/facebookresearch/llama) +- [StableLM](https://github.com/Stability-AI/StableLM) +- [MOSS](https://github.com/OpenLMLab/MOSS) + ## 使う上でのTips - ChatGPTをより適切に制御するために、システムプロンプトを使用できます。 - プロンプトテンプレートを使用するには、プロンプトテンプレートコレクションを選択し、ドロップダウンメニューから特定のプロンプトを選択。回答が不十分な場合は、`🔄再生成`ボタンを使って再試行します。 - 入力ボックスで改行するには、Shift + Enterキーを押してください。 - 入力履歴を素早く切り替えるには、入力ボックスで キーを押す。 -- プログラムをサーバにデプロイするには、プログラムの最終行を `demo.launch(server_name="0.0.0.0", server_port=)`に変更します。 -- 共有リンクを取得するには、プログラムの最後の行を `demo.launch(share=True)` に変更してください。なお、公開リンクでアクセスするためには、プログラムが実行されている必要があることに注意してください。 +- プログラムをサーバーに展開するには、`config.json` 内の `"server_name": "0.0.0.0", "server_port": <ポート番号>`を設定してください。 +- 共有リンクを取得するには、 `config.json` 内の `"share": true` を設定してください。なお、公開リンクでアクセスするためには、プログラムが実行されている必要があることに注意してください。 - Hugging Face Spacesで使用する場合: より速く、より安全に利用するために、**Duplicate Space**を使用し、自分のスペースでプログラムを実行することをお勧めします。 -## インストール +## クイックスタート ```shell git clone https://github.com/GaiZhenbiao/ChuanhuChatGPT.git @@ -86,10 +103,6 @@ python ChuanhuChatbot.py ``` pip install -r requirements.txt ``` -3. Gradioを更新 - ``` - pip install gradio --upgrade --force-reinstall - ``` 一般的に、以下の手順でほとんどの問題を解決することができます。 diff --git a/requirements.txt b/requirements.txt index 416b3dec5b71d0b3b4d07c82294c1d5c5d3e72ce..5dcb8cab519a78fe35591770fa3df4f5384f0dcd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -gradio==3.33.1 -gradio_client==0.2.5 +gradio==3.40.0 +gradio_client==0.4.0 pypinyin tiktoken socksio @@ -7,7 +7,7 @@ tqdm colorama googlesearch-python Pygments -langchain==0.0.173 +langchain==0.0.276 markdown PyPDF2 pdfplumber @@ -16,10 +16,13 @@ commentjson openpyxl pandoc wolframalpha -faiss-cpu +faiss-cpu==1.7.4 duckduckgo-search arxiv wikipedia google.generativeai -openai +openai>=0.27.9 unstructured +google-api-python-client +tabulate +ujson diff --git a/requirements_advanced.txt b/requirements_advanced.txt new file mode 100644 index 0000000000000000000000000000000000000000..8aaf00860e2cbbb7d778b708974c8ce50a84c01c --- /dev/null +++ b/requirements_advanced.txt @@ -0,0 +1,11 @@ +transformers +huggingface_hub +torch +icetk +protobuf==3.19.0 +git+https://github.com/OptimalScale/LMFlow.git +cpm-kernels +sentence_transformers +accelerate +sentencepiece +datasets diff --git a/run_Windows.bat b/run_Windows.bat index 4c18f9ccaeea0af972301ffdf48778641221f76d..5dd4dd065807bc83425e3876c1be14b5a234e253 100644 --- a/run_Windows.bat +++ b/run_Windows.bat @@ -1,5 +1,24 @@ @echo off echo Opening ChuanhuChatGPT... -REM Open powershell via bat -start powershell.exe -NoExit -Command "python ./ChuanhuChatbot.py" +if not exist "%~dp0\ChuanhuChat\Scripts" ( + echo Creating venv... + python -m venv ChuanhuChat + + cd /d "%~dp0\ChuanhuChat\Scripts" + call activate.bat + + cd /d "%~dp0" + pip install -r requirements.txt +) + +goto :activate_venv + +:launch +%PYTHON% ChuanhuChatbot.py %* +pause + +:activate_venv +set PYTHON="%~dp0\ChuanhuChat\Scripts\Python.exe" +echo venv %PYTHON% +goto :launch diff --git a/web_assets/chatbot.png b/web_assets/chatbot.png new file mode 100644 index 0000000000000000000000000000000000000000..03a767753b7add4f4d80d5889c0314181ac6c750 Binary files /dev/null and b/web_assets/chatbot.png differ diff --git a/web_assets/favicon.ico b/web_assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2396913aee087584fef0cad7d2846b01b051cac6 Binary files /dev/null and b/web_assets/favicon.ico differ diff --git a/web_assets/html/appearance_switcher.html b/web_assets/html/appearance_switcher.html new file mode 100644 index 0000000000000000000000000000000000000000..5fbf3b09b1c39de75c400514c9d0d81c807ea6bd --- /dev/null +++ b/web_assets/html/appearance_switcher.html @@ -0,0 +1,6 @@ +
    + +
    diff --git a/web_assets/html/billing_info.html b/web_assets/html/billing_info.html new file mode 100644 index 0000000000000000000000000000000000000000..71abcc802da3c70716919c1a4738ac077c47bf01 --- /dev/null +++ b/web_assets/html/billing_info.html @@ -0,0 +1,9 @@ +{label} +
    +
    + {usage_percent}% +
    +
    +
    + ${rounded_usage}${usage_limit} +
    \ No newline at end of file diff --git a/web_assets/html/config_info.html b/web_assets/html/config_info.html new file mode 100644 index 0000000000000000000000000000000000000000..899c6b0cbbd490e28fc36988bbbe9901d1df5729 --- /dev/null +++ b/web_assets/html/config_info.html @@ -0,0 +1,2 @@ +
    {bot_avatar}
    +
    {user_avatar}
    \ No newline at end of file diff --git a/web_assets/html/footer.html b/web_assets/html/footer.html new file mode 100644 index 0000000000000000000000000000000000000000..bca27bb8066dfab5cc0acf7be349a514de5f9a58 --- /dev/null +++ b/web_assets/html/footer.html @@ -0,0 +1 @@ +
    {versions}
    diff --git a/web_assets/html/update.html b/web_assets/html/update.html new file mode 100644 index 0000000000000000000000000000000000000000..6f005e11a0a11f441ff2c56d05b98402c640a53f --- /dev/null +++ b/web_assets/html/update.html @@ -0,0 +1,29 @@ +
    +
    +

    + {current_version} + {version_time} +

    +

    + Latest Version: getting latest version... +

    +

    + Getting update... +

    +
    +
    +
    + Getting Release Note... +
    +
    +
    + + +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/web_assets/javascript/ChuanhuChat.js b/web_assets/javascript/ChuanhuChat.js new file mode 100644 index 0000000000000000000000000000000000000000..1128b7782111381f4540282db574ba951e65f2f1 --- /dev/null +++ b/web_assets/javascript/ChuanhuChat.js @@ -0,0 +1,328 @@ + +// ChuanhuChat core javascript + +const MAX_HISTORY_LENGTH = 32; + +var key_down_history = []; +var currentIndex = -1; + +var gradioContainer = null; +var user_input_ta = null; +var user_input_tb = null; +var userInfoDiv = null; +var appTitleDiv = null; +var chatbot = null; +var chatbotIndicator = null; +var chatbotWrap = null; +var apSwitch = null; +var messageBotDivs = null; +var loginUserForm = null; +var logginUser = null; +var updateToast = null; +var sendBtn = null; +var cancelBtn = null; +var sliders = null; +var updateChuanhuBtn = null; +var statusDisplay = null; + +var isInIframe = (window.self !== window.top); +var currentTime = new Date().getTime(); +var initialized = false; + +// gradio 页面加载好了么??? 我能动你的元素了么?? +function gradioLoaded(mutations) { + for (var i = 0; i < mutations.length; i++) { + if (mutations[i].addedNodes.length) { + if (initialized) { + observer.disconnect(); // 停止监听 + return; + } + initialize(); + } + } +} + +function initialize() { + var needInit = {gradioContainer, apSwitch, user_input_tb, userInfoDiv, appTitleDiv, chatbot, chatbotIndicator, chatbotWrap, statusDisplay, sliders, updateChuanhuBtn}; + initialized = true; + + loginUserForm = gradioApp().querySelector(".gradio-container > .main > .wrap > .panel > .form") + gradioContainer = gradioApp().querySelector(".gradio-container"); + user_input_tb = gradioApp().getElementById('user-input-tb'); + userInfoDiv = gradioApp().getElementById("user-info"); + appTitleDiv = gradioApp().getElementById("app-title"); + chatbot = gradioApp().querySelector('#chuanhu-chatbot'); + chatbotIndicator = gradioApp().querySelector('#chuanhu-chatbot>div.wrap'); + chatbotWrap = gradioApp().querySelector('#chuanhu-chatbot > .wrapper > .wrap'); + apSwitch = gradioApp().querySelector('.apSwitch input[type="checkbox"]'); + updateToast = gradioApp().querySelector("#toast-update"); + sendBtn = gradioApp().getElementById("submit-btn"); + cancelBtn = gradioApp().getElementById("cancel-btn"); + sliders = gradioApp().querySelectorAll('input[type="range"]'); + updateChuanhuBtn = gradioApp().getElementById("update-chuanhu-btn"); + statusDisplay = gradioApp().querySelector('#status-display'); + + if (loginUserForm) { + localStorage.setItem("userLogged", true); + userLogged = true; + } + + for (let elem in needInit) { + if (needInit[elem] == null) { + initialized = false; + return; + } + } + + if (initialized) { + adjustDarkMode(); + selectHistory(); + setTimeout(showOrHideUserInfo(), 2000); + setChatbotHeight(); + setChatbotScroll(); + setSlider(); + setAvatar(); + if (!historyLoaded) loadHistoryHtml(); + if (!usernameGotten) getUserInfo(); + chatbotObserver.observe(chatbotIndicator, { attributes: true }); + + const lastCheckTime = localStorage.getItem('lastCheckTime') || 0; + const longTimeNoCheck = currentTime - lastCheckTime > 3 * 24 * 60 * 60 * 1000; + if (longTimeNoCheck && !updateInfoGotten && !isLatestVersion || isLatestVersion && !updateInfoGotten) { + updateLatestVersion(); + } + } +} + +function gradioApp() { + const elems = document.getElementsByTagName('gradio-app'); + const elem = elems.length == 0 ? document : elems[0]; + + if (elem !== document) { + elem.getElementById = function(id) { + return document.getElementById(id); + }; + } + return elem.shadowRoot ? elem.shadowRoot : elem; +} + +function showConfirmationDialog(a, file, c) { + if (file != "") { + var result = confirm(i18n(deleteConfirm_i18n_pref) + file + i18n(deleteConfirm_i18n_suff)); + if (result) { + return [a, file, c]; + } + } + return [a, "CANCELED", c]; +} + +function selectHistory() { + user_input_ta = user_input_tb.querySelector("textarea"); + if (user_input_ta) { + disableSendBtn(); + // 在 textarea 上监听 keydown 事件 + user_input_ta.addEventListener("keydown", function (event) { + var value = user_input_ta.value.trim(); + // 判断按下的是否为方向键 + if (event.code === 'ArrowUp' || event.code === 'ArrowDown') { + // 如果按下的是方向键,且输入框中有内容,且历史记录中没有该内容,则不执行操作 + if (value && key_down_history.indexOf(value) === -1) + return; + // 对于需要响应的动作,阻止默认行为。 + event.preventDefault(); + var length = key_down_history.length; + if (length === 0) { + currentIndex = -1; // 如果历史记录为空,直接将当前选中的记录重置 + return; + } + if (currentIndex === -1) { + currentIndex = length; + } + if (event.code === 'ArrowUp' && currentIndex > 0) { + currentIndex--; + user_input_ta.value = key_down_history[currentIndex]; + } else if (event.code === 'ArrowDown' && currentIndex < length - 1) { + currentIndex++; + user_input_ta.value = key_down_history[currentIndex]; + } + user_input_ta.selectionStart = user_input_ta.value.length; + user_input_ta.selectionEnd = user_input_ta.value.length; + const input_event = new InputEvent("input", { bubbles: true, cancelable: true }); + user_input_ta.dispatchEvent(input_event); + } else if (event.code === "Enter") { + if (value) { + currentIndex = -1; + if (key_down_history.indexOf(value) === -1) { + key_down_history.push(value); + if (key_down_history.length > MAX_HISTORY_LENGTH) { + key_down_history.shift(); + } + } + } + } + }); + } +} + +function disableSendBtn() { + sendBtn.disabled = user_input_ta.value.trim() === ''; + user_input_ta.addEventListener('input', () => { + sendBtn.disabled = user_input_ta.value.trim() === ''; + }); +} + +function adjustDarkMode() { + function toggleDarkMode(isEnabled) { + if (isEnabled) { + document.body.classList.add("dark"); + document.body.style.setProperty("background-color", "var(--neutral-950)", "important"); + } else { + document.body.classList.remove("dark"); + document.body.style.backgroundColor = ""; + } + } + + const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + apSwitch.checked = darkModeQuery.matches; + toggleDarkMode(darkModeQuery.matches); + darkModeQuery.addEventListener("change", (e) => { + apSwitch.checked = e.matches; + toggleDarkMode(e.matches); + }); + apSwitch.addEventListener("change", (e) => { + toggleDarkMode(e.target.checked); + }); +} + +function setChatbotHeight() { + const screenWidth = window.innerWidth; + const statusDisplay = document.querySelector('#status-display'); + const statusDisplayHeight = statusDisplay ? statusDisplay.offsetHeight : 0; + const vh = window.innerHeight * 0.01; + document.documentElement.style.setProperty('--vh', `${vh}px`); + if (isInIframe) { + chatbot.style.height = `700px`; + chatbotWrap.style.maxHeight = `calc(700px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))` + } else { + if (screenWidth <= 320) { + chatbot.style.height = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 150}px)`; + chatbotWrap.style.maxHeight = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 150}px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))`; + } else if (screenWidth <= 499) { + chatbot.style.height = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 100}px)`; + chatbotWrap.style.maxHeight = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 100}px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))`; + } else { + chatbot.style.height = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 160}px)`; + chatbotWrap.style.maxHeight = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 160}px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))`; + } + } +} +function setChatbotScroll() { + var scrollHeight = chatbotWrap.scrollHeight; + chatbotWrap.scrollTo(0,scrollHeight) +} + +var botAvatarUrl = ""; +var userAvatarUrl = ""; +function setAvatar() { + var botAvatar = gradioApp().getElementById("config-bot-avatar-url").innerText; + var userAvatar = gradioApp().getElementById("config-user-avatar-url").innerText; + + if (botAvatar == "none") { + botAvatarUrl = ""; + } else if (isImgUrl(botAvatar)) { + botAvatarUrl = botAvatar; + } else { + // botAvatarUrl = "https://github.com/GaiZhenbiao/ChuanhuChatGPT/assets/70903329/aca3a7ec-4f1d-4667-890c-a6f47bf08f63"; + botAvatarUrl = "/file=web_assets/chatbot.png" + } + + if (userAvatar == "none") { + userAvatarUrl = ""; + } else if (isImgUrl(userAvatar)) { + userAvatarUrl = userAvatar; + } else { + userAvatarUrl = "data:image/svg+xml,%3Csvg width='32px' height='32px' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Crect fill-opacity='0.5' fill='%23bbbbbb' x='0' y='0' width='32' height='32'%3E%3C/rect%3E%3Cg transform='translate(5, 4)' fill='%23999999' fill-opacity='0.8' fill-rule='nonzero'%3E%3Cpath d='M2.29372246,24 L19.7187739,24 C20.4277609,24 20.985212,23.8373915 21.3911272,23.5121746 C21.7970424,23.1869576 22,22.7418004 22,22.1767029 C22,21.3161536 21.7458721,20.4130827 21.2376163,19.4674902 C20.7293605,18.5218977 19.9956681,17.6371184 19.036539,16.8131524 C18.07741,15.9891863 16.9210688,15.3177115 15.5675154,14.798728 C14.2139621,14.2797445 12.6914569,14.0202527 11,14.0202527 C9.30854307,14.0202527 7.78603793,14.2797445 6.43248458,14.798728 C5.07893122,15.3177115 3.92259002,15.9891863 2.96346097,16.8131524 C2.00433193,17.6371184 1.27063951,18.5218977 0.762383704,19.4674902 C0.254127901,20.4130827 0,21.3161536 0,22.1767029 C0,22.7418004 0.202957595,23.1869576 0.608872784,23.5121746 C1.01478797,23.8373915 1.57640453,24 2.29372246,24 Z M11.0124963,11.6521659 C11.9498645,11.6521659 12.8155943,11.3906214 13.6096856,10.8675324 C14.403777,10.3444433 15.042131,9.63605539 15.5247478,8.74236856 C16.0073646,7.84868174 16.248673,6.84722464 16.248673,5.73799727 C16.248673,4.65135034 16.0071492,3.67452644 15.5241015,2.80752559 C15.0410538,1.94052474 14.4024842,1.25585359 13.6083929,0.753512156 C12.8143016,0.251170719 11.9490027,0 11.0124963,0 C10.0759899,0 9.20860836,0.255422879 8.41035158,0.766268638 C7.6120948,1.2771144 6.97352528,1.96622098 6.49464303,2.8335884 C6.01576078,3.70095582 5.77631966,4.67803631 5.77631966,5.76482987 C5.77631966,6.86452653 6.01554533,7.85912886 6.49399667,8.74863683 C6.97244801,9.63814481 7.60871935,10.3444433 8.40281069,10.8675324 C9.19690203,11.3906214 10.0667972,11.6521659 11.0124963,11.6521659 Z'%3E%3C/path%3E%3C/g%3E%3C/g%3E%3C/svg%3E"; + } +} + +function clearChatbot() { + clearHistoryHtml(); + clearMessageRows(); +} + +function chatbotContentChanged(attempt = 1) { + for (var i = 0; i < attempt; i++) { + setTimeout(() => { + // clearMessageRows(); + saveHistoryHtml(); + disableSendBtn(); + gradioApp().querySelectorAll('#chuanhu-chatbot .message-wrap .message.user').forEach((userElement) => {addAvatars(userElement, 'user')}); + gradioApp().querySelectorAll('#chuanhu-chatbot .message-wrap .message.bot').forEach((botElement) => {addAvatars(botElement, 'bot'); addChuanhuButton(botElement)}); + }, i === 0 ? 0 : 500); + } + // 理论上是不需要多次尝试执行的,可惜gradio的bug导致message可能没有渲染完毕,所以尝试500ms后再次执行 +} + +var chatbotObserver = new MutationObserver(() => { + clearMessageRows(); + chatbotContentChanged(1); + if (chatbotIndicator.classList.contains('hide')) { + chatbotContentChanged(2); + } +}); + +// 监视页面内部 DOM 变动 +var observer = new MutationObserver(function (mutations) { + gradioLoaded(mutations); +}); + +// 监视页面变化 +window.addEventListener("DOMContentLoaded", function () { + const ga = document.getElementsByTagName("gradio-app"); + observer.observe(ga[0], { childList: true, subtree: true }); + isInIframe = (window.self !== window.top); + historyLoaded = false; +}); +window.addEventListener('resize', setChatbotHeight); +window.addEventListener('scroll', function(){setChatbotHeight(); setUpdateWindowHeight();}); +window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", adjustDarkMode); + +// console suprise +var styleTitle1 = ` +font-size: 16px; +font-family: ui-monospace, monospace; +color: #06AE56; +` +var styleDesc1 = ` +font-size: 12px; +font-family: ui-monospace, monospace; +` +function makeML(str) { + let l = new String(str) + l = l.substring(l.indexOf("/*") + 3, l.lastIndexOf("*/")) + return l +} +let ChuanhuInfo = function () { + /* + ________ __ ________ __ + / ____/ /_ __ ______ _____ / /_ __ __ / ____/ /_ ____ _/ /_ + / / / __ \/ / / / __ `/ __ \/ __ \/ / / / / / / __ \/ __ `/ __/ +/ /___/ / / / /_/ / /_/ / / / / / / / /_/ / / /___/ / / / /_/ / /_ +\____/_/ /_/\__,_/\__,_/_/ /_/_/ /_/\__,_/ \____/_/ /_/\__,_/\__/ + + 川虎Chat (Chuanhu Chat) - GUI for ChatGPT API and many LLMs + */ +} +let description = ` +© 2023 Chuanhu, MZhao, Keldos +GitHub repository: [https://github.com/GaiZhenbiao/ChuanhuChatGPT]\n +Enjoy our project!\n +` +console.log(`%c${makeML(ChuanhuInfo)}`,styleTitle1) +console.log(`%c${description}`, styleDesc1) + +// button svg code +const copyIcon = ''; +const copiedIcon = ''; +const mdIcon = ''; +const rawIcon = ''; diff --git a/web_assets/javascript/avatar.js b/web_assets/javascript/avatar.js new file mode 100644 index 0000000000000000000000000000000000000000..14da1d3ba174320f8b52b6ceb18799909dff0c6e --- /dev/null +++ b/web_assets/javascript/avatar.js @@ -0,0 +1,53 @@ + +function addAvatars(messageElement, role='user'||'bot') { + if(messageElement.innerHTML === '') { + return; + } + if (messageElement.classList.contains('avatar-added') || messageElement.classList.contains('hide')) { + return; + } + if (role === 'bot' && botAvatarUrl === "" || role === 'user' && userAvatarUrl === "") { + messageElement.classList.add('avatar-added'); + return; + } + + + const messageRow = document.createElement('div'); + messageRow.classList.add('message-row'); + messageElement.classList.add('avatar-added'); + + if (role === 'bot') { + messageRow.classList.add('bot-message-row'); + } else if (role === 'user') { + messageRow.classList.add('user-message-row'); + } + + const avatarDiv = document.createElement('div'); + avatarDiv.classList.add('chatbot-avatar'); + if (role === 'bot') { + avatarDiv.classList.add('bot-avatar'); + avatarDiv.innerHTML = `bot-avatar`; + } else if (role === 'user') { + avatarDiv.classList.add('user-avatar'); + avatarDiv.innerHTML = `user-avatar`; + } + + messageElement.parentNode.replaceChild(messageRow, messageElement); + + if (role === 'bot') { + messageRow.appendChild(avatarDiv); + messageRow.appendChild(messageElement); + } else if (role === 'user') { + messageRow.appendChild(messageElement); + messageRow.appendChild(avatarDiv); + } +} + +function clearMessageRows() { + const messageRows = chatbotWrap.querySelectorAll('.message-row'); + messageRows.forEach((messageRow) => { + if (messageRow.innerText === '') { + messageRow.parentNode.removeChild(messageRow); + } + }); +} \ No newline at end of file diff --git a/web_assets/javascript/chat-history.js b/web_assets/javascript/chat-history.js new file mode 100644 index 0000000000000000000000000000000000000000..1bc05355025a1a9abf83f51ff94b6815c604a6d6 --- /dev/null +++ b/web_assets/javascript/chat-history.js @@ -0,0 +1,54 @@ + +var historyLoaded = false; +var loadhistorytime = 0; // for debugging + + +function saveHistoryHtml() { + var historyHtml = document.querySelector('#chuanhu-chatbot>.wrapper>.wrap'); + if (!historyHtml) return; // no history, do nothing + localStorage.setItem('chatHistory', historyHtml.innerHTML); + // console.log("History Saved") + historyLoaded = false; +} + +function loadHistoryHtml() { + var historyHtml = localStorage.getItem('chatHistory'); + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = historyHtml; + if (!historyHtml || tempDiv.innerText.trim() === "") { + historyLoaded = true; + return; // no history, do nothing + } + userLogged = localStorage.getItem('userLogged'); + if (userLogged){ + historyLoaded = true; + return; // logged in, do nothing + } + if (!historyLoaded) { + var fakeHistory = document.createElement('div'); + fakeHistory.classList.add('history-message'); + fakeHistory.innerHTML = tempDiv.innerHTML; + const forViewStyle = document.createElement('style'); + forViewStyle.innerHTML = '.wrapper>.wrap>.history-message>:last-child::after { content: "' + i18n(forView_i18n) + '"!important; }'; + document.head.appendChild(forViewStyle); + chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild); + // var fakeHistory = document.createElement('div'); + // fakeHistory.classList.add('history-message'); + // fakeHistory.innerHTML = historyHtml; + // chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild); + historyLoaded = true; + console.log("History Loaded"); + loadhistorytime += 1; // for debugging + } else { + historyLoaded = false; + } +} + +function clearHistoryHtml() { + localStorage.removeItem("chatHistory"); + historyMessages = chatbotWrap.querySelector('.history-message'); + if (historyMessages) { + chatbotWrap.removeChild(historyMessages); + console.log("History Cleared"); + } +} diff --git a/web_assets/javascript/external-scripts.js b/web_assets/javascript/external-scripts.js new file mode 100644 index 0000000000000000000000000000000000000000..8d0352669045537af5698b1824dbc1dba21df478 --- /dev/null +++ b/web_assets/javascript/external-scripts.js @@ -0,0 +1,2 @@ + +// external javascript here diff --git a/web_assets/javascript/localization.js b/web_assets/javascript/localization.js new file mode 100644 index 0000000000000000000000000000000000000000..2e9997ac154d0fee66c0e8e28a780a3bc54ef8b1 --- /dev/null +++ b/web_assets/javascript/localization.js @@ -0,0 +1,67 @@ + +// i18n + +const language = navigator.language.slice(0,2); + +const forView_i18n = { + 'zh': "仅供查看", + 'en': "For viewing only", + 'ja': "閲覧専用", + 'ko': "읽기 전용", + 'fr': "Pour consultation seulement", + 'es': "Solo para visualización", + 'sv': "Endast för visning", +}; + +const deleteConfirm_i18n_pref = { + 'zh': "你真的要删除 ", + 'en': "Are you sure you want to delete ", + 'ja': "本当に ", + 'ko': "정말로 ", + 'sv': "Är du säker på att du vill ta bort " +}; + +const deleteConfirm_i18n_suff = { + 'zh': " 吗?", + 'en': " ?", + 'ja': " を削除してもよろしいですか?", + 'ko': " 을(를) 삭제하시겠습니까?", + 'sv': " ?" +}; + +const usingLatest_i18n = { + 'zh': "您使用的就是最新版!", + 'en': "You are using the latest version!", + 'ja': "最新バージョンを使用しています!", + 'ko': "최신 버전을 사용하고 있습니다!", + 'sv': "Du använder den senaste versionen!" +}; + +const updatingMsg_i18n = { + 'zh': "正在尝试更新...", + 'en': "Trying to update...", + 'ja': "更新を試みています...", + 'ko': "업데이트를 시도 중...", + 'sv': "Försöker uppdatera..." +} + +const updateSuccess_i18n = { + 'zh': "更新成功,请重启本程序。", + 'en': "Updated successfully, please restart this program.", + 'ja': "更新が成功しました、このプログラムを再起動してください。", + 'ko': "업데이트 성공, 이 프로그램을 재시작 해주세요.", + 'sv': "Uppdaterat framgångsrikt, starta om programmet." +} + +const updateFailure_i18n = { + 'zh': '更新失败,请尝试手动更新。', + 'en': 'Update failed, please try manually updating.', + 'ja': '更新に失敗しました、手動での更新をお試しください。', + 'ko': '업데이트 실패, 수동 업데이트를 시도하십시오.', + 'sv': 'Uppdateringen misslyckades, prova att uppdatera manuellt.' +} + + +function i18n(msg) { + return msg.hasOwnProperty(language) ? msg[language] : msg['en']; +} diff --git a/web_assets/javascript/message-button.js b/web_assets/javascript/message-button.js new file mode 100644 index 0000000000000000000000000000000000000000..e16b065c8c0ea84b927ebbb46b7ff336d085b8d9 --- /dev/null +++ b/web_assets/javascript/message-button.js @@ -0,0 +1,92 @@ + +// 为 bot 消息添加复制与切换显示按钮 + +function addChuanhuButton(botElement) { + var rawMessage = botElement.querySelector('.raw-message'); + var mdMessage = botElement.querySelector('.md-message'); + + if (!rawMessage) { // 如果没有 raw message,说明是早期历史记录,去除按钮 + var buttons = botElement.querySelectorAll('button.chuanhu-btn'); + for (var i = 0; i < buttons.length; i++) { + buttons[i].parentNode.removeChild(buttons[i]); + } + return; + } + botElement.querySelectorAll('button.copy-bot-btn, button.toggle-md-btn').forEach(btn => btn.remove()); // 就算原先有了,也必须重新添加,而不是跳过 + + // Copy bot button + var copyButton = document.createElement('button'); + copyButton.classList.add('chuanhu-btn'); + copyButton.classList.add('copy-bot-btn'); + copyButton.setAttribute('aria-label', 'Copy'); + copyButton.innerHTML = copyIcon; + + copyButton.addEventListener('click', async () => { + const textToCopy = rawMessage.innerText; + try { + if ("clipboard" in navigator) { + await navigator.clipboard.writeText(textToCopy); + copyButton.innerHTML = copiedIcon; + setTimeout(() => { + copyButton.innerHTML = copyIcon; + }, 1500); + } else { + const textArea = document.createElement("textarea"); + textArea.value = textToCopy; + document.body.appendChild(textArea); + textArea.select(); + try { + document.execCommand('copy'); + copyButton.innerHTML = copiedIcon; + setTimeout(() => { + copyButton.innerHTML = copyIcon; + }, 1500); + } catch (error) { + console.error("Copy failed: ", error); + } + document.body.removeChild(textArea); + } + } catch (error) { + console.error("Copy failed: ", error); + } + }); + botElement.appendChild(copyButton); + + // Toggle button + var toggleButton = document.createElement('button'); + toggleButton.classList.add('chuanhu-btn'); + toggleButton.classList.add('toggle-md-btn'); + toggleButton.setAttribute('aria-label', 'Toggle'); + var renderMarkdown = mdMessage.classList.contains('hideM'); + toggleButton.innerHTML = renderMarkdown ? mdIcon : rawIcon; + toggleButton.addEventListener('click', () => { + renderMarkdown = mdMessage.classList.contains('hideM'); + if (renderMarkdown) { + renderMarkdownText(botElement); + toggleButton.innerHTML=rawIcon; + } else { + removeMarkdownText(botElement); + toggleButton.innerHTML=mdIcon; + } + chatbotContentChanged(1); // to set md or raw in read-only history html + }); + botElement.insertBefore(toggleButton, copyButton); + + function renderMarkdownText(message) { + var mdDiv = message.querySelector('.md-message'); + if (mdDiv) mdDiv.classList.remove('hideM'); + var rawDiv = message.querySelector('.raw-message'); + if (rawDiv) rawDiv.classList.add('hideM'); + } + function removeMarkdownText(message) { + var rawDiv = message.querySelector('.raw-message'); + if (rawDiv) { + rawDiv.innerHTML = rawDiv.querySelector('pre')?.innerHTML || rawDiv.innerHTML; + rawDiv.classList.remove('hideM'); + } + var mdDiv = message.querySelector('.md-message'); + if (mdDiv) mdDiv.classList.add('hideM'); + } +} + + diff --git a/web_assets/javascript/sliders.js b/web_assets/javascript/sliders.js new file mode 100644 index 0000000000000000000000000000000000000000..1351f3ae3902c374b3f5f73b2787c5ec1989bafd --- /dev/null +++ b/web_assets/javascript/sliders.js @@ -0,0 +1,22 @@ + +var rangeInputs = null; +var numberInputs = null; + + +function setSlider() { + function setSliderRange() { + var range = document.querySelectorAll('input[type="range"]'); + range.forEach(range => { + range.style.backgroundSize = (range.value - range.min) / (range.max - range.min) * 100 + '% 100%'; + }); + } + rangeInputs = document.querySelectorAll('input[type="range"]'); + numberInputs = document.querySelectorAll('input[type="number"]') + setSliderRange(); + rangeInputs.forEach(rangeInput => { + rangeInput.addEventListener('input', setSliderRange); + }); + numberInputs.forEach(numberInput => { + numberInput.addEventListener('input', setSliderRange); + }) +} diff --git a/web_assets/javascript/updater.js b/web_assets/javascript/updater.js new file mode 100644 index 0000000000000000000000000000000000000000..68c0ff21aeb92bc538036c42264c41e3787097a7 --- /dev/null +++ b/web_assets/javascript/updater.js @@ -0,0 +1,202 @@ + +var updateInfoGotten = false; +var isLatestVersion = localStorage.getItem('isLatestVersion') || false; + + +var statusObserver = new MutationObserver(function (mutationsList) { + for (const mutation of mutationsList) { + if (mutation.type === 'attributes' || mutation.type === 'childList') { + if (statusDisplay.innerHTML.includes(']*>([^<]*)<\/a>/g; + const versionMatch = reVersion.exec(currentVersionElement.innerHTML); + const currentVersion = (versionMatch && versionMatch[1].length == 8) ? versionMatch[1] : null; + const latestVersionElement = document.getElementById('latest-version-title'); + const versionInfoElement = document.getElementById('version-info-title'); + releaseNoteElement = document.getElementById('release-note-content'); + updatingInfoElement = document.getElementById('updating-info'); + + const versionTime = document.getElementById('version-time').innerText; + const localVersionTime = versionTime !== "unknown" ? (new Date(versionTime)).getTime() : 0; + updateInfoGotten = true; //无论成功与否都只执行一次,否则容易api超限... + try { + const data = await getLatestRelease(); + const releaseNote = data.body; + if (releaseNote) { + releaseNoteElement.innerHTML = marked.parse(releaseNote, {mangle: false, headerIds: false}); + } + const latestVersion = data.tag_name; + if (currentVersion) { + if (latestVersion <= currentVersion) { + noUpdate(); + } else { + latestVersionElement.textContent = latestVersion; + console.log(`New version ${latestVersion} found!`); + if (!isInIframe) openUpdateToast(); + } + } else { //如果当前版本号获取失败,使用时间比较 + const latestVersionTime = (new Date(data.created_at)).getTime(); + if (latestVersionTime) { + const latestVersionInfo = `${latestVersion}` + const manualUpdateInfo = `manual update` + if (localVersionTime == 0) { + const infoMessage = `Local version check failed. \nBut latest revision is ${latestVersionInfo}. \n\nWhen Update needed, \n- If you are using Docker, try to update package. \n- If you didn't use git, try ${manualUpdateInfo}.` + versionInfoElement.innerHTML = marked.parse(infoMessage, {mangle: false, headerIds: false}); + console.log(`New version ${latestVersion} found!`); + disableUpdateBtn_enableCancelBtn(); + } else if (localVersionTime < latestVersionTime) { + const infoMessage = `Local version check failed, it seems to be a local rivision. \n\nBut latest revision is ${latestVersionInfo}. Try ${manualUpdateInfo}.` + versionInfoElement.innerHTML = marked.parse(infoMessage, {mangle: false, headerIds: false}); + console.log(`New version ${latestVersion} found!`); + disableUpdateBtn_enableCancelBtn(); + // if (!isInIframe) openUpdateToast(); + } else { + noUpdate("Local version check failed, it seems to be a local rivision.
    But your revision is newer than the latest release."); + } + } + } + currentTime = new Date().getTime(); + localStorage.setItem('lastCheckTime', currentTime); + } catch (error) { + console.error(error); + } +} + +function getUpdateInfo() { + window.open('https://github.com/gaizhenbiao/chuanhuchatgpt/releases/latest', '_blank'); + closeUpdateToast(); +} + +var updateSpinner = null; + +function bgUpdateChuanhu() { + updateChuanhuBtn.click(); + updatingInfoElement.innerText = i18n(updatingMsg_i18n); + var updatingSpinner = document.getElementById('updating-spinner'); + try { + updateSpinner = new Spin.Spinner({color:'#06AE56',top:'45%',lines:9}).spin(updatingSpinner); + } catch (error) { + console.error("Can't create spinner") + } + updatingInfoElement.classList.remove('hideK'); + disableUpdateBtns(); + const releaseNoteWrap = document.getElementById('release-note-wrap'); + releaseNoteWrap.style.setProperty('display', 'none'); + statusObserver.observe(statusDisplay, { childList: true, subtree: true, characterData: true}); +} +function cancelUpdate() { + closeUpdateToast(); +} +function openUpdateToast() { + showingUpdateInfo = true; + setUpdateWindowHeight(); +} +function closeUpdateToast() { + updateToast.style.setProperty('top', '-500px'); + showingUpdateInfo = false; + if (updatingInfoElement.classList.contains('hideK') === false) { + updatingInfoElement.classList.add('hideK'); + } +} +function manualCheckUpdate() { + openUpdateToast(); + updateLatestVersion(); + currentTime = new Date().getTime(); + localStorage.setItem('lastCheckTime', currentTime); +} +function noUpdate(message="") { + localStorage.setItem('isLatestVersion', 'true'); + isLatestVersion = true; + noUpdateHtml(message); +} +function noUpdateHtml(message="") { + const versionInfoElement = document.getElementById('version-info-title'); + const gotoUpdateBtn = document.getElementById('goto-update-btn'); + const closeUpdateBtn = document.getElementById('close-update-btn'); + const releaseNoteWrap = document.getElementById('release-note-wrap'); + releaseNoteWrap.style.setProperty('display', 'none'); + if (message === "") { + versionInfoElement.textContent = i18n(usingLatest_i18n) + } else { + versionInfoElement.innerHTML = message; + } + gotoUpdateBtn.classList.add('hideK'); + closeUpdateBtn.classList.remove('hideK'); +} + +var updateStatus = null; +function getUpdateStatus() { + updateStatus = statusDisplay.querySelector("#update-status"); + if (updateStatus) { + return updateStatus.innerText; + } else { + return "unknown"; + } +} + +function disableUpdateBtns() { + const updatesButtons = document.querySelectorAll('.btn-update'); + updatesButtons.forEach( function (btn) { + btn.disabled = true; + }); +} +function enableUpdateBtns() { + const updatesButtons = document.querySelectorAll('.btn-update'); + updatesButtons.forEach( function (btn) { + btn.disabled = false; + }); +} +function disableUpdateBtn_enableCancelBtn() { + document.querySelector('#update-button.btn-update').disabled = true; + document.querySelector('#cancel-button.btn-update').disabled = false; +} + +function setUpdateWindowHeight() { + if (!showingUpdateInfo) {return;} + const scrollPosition = window.scrollY; + // const originalTop = updateToast.style.getPropertyValue('top'); + const resultTop = scrollPosition - 20 + 'px'; + updateToast.style.setProperty('top', resultTop); +} diff --git a/web_assets/javascript/user-info.js b/web_assets/javascript/user-info.js new file mode 100644 index 0000000000000000000000000000000000000000..690346ad24121db1b81bab5f90a633862dd7a849 --- /dev/null +++ b/web_assets/javascript/user-info.js @@ -0,0 +1,67 @@ + +var userLogged = false; +var usernameGotten = false; +var username = null; + + +function getUserInfo() { + if (usernameGotten) { + return; + } + userLogged = localStorage.getItem('userLogged'); + if (userLogged) { + username = userInfoDiv.innerText; + if (username) { + if (username.includes("getting user info…")) { + setTimeout(getUserInfo, 500); + return; + } else if (username === " ") { + localStorage.removeItem("username"); + localStorage.removeItem("userLogged") + userLogged = false; + usernameGotten = true; + return; + } else { + username = username.match(/User:\s*(.*)/)[1] || username; + localStorage.setItem("username", username); + usernameGotten = true; + clearHistoryHtml(); + } + } + } +} + +function showOrHideUserInfo() { + function toggleUserInfoVisibility(shouldHide) { + if (userInfoDiv) { + if (shouldHide) { + userInfoDiv.classList.add("info-transparent"); + } else { + userInfoDiv.classList.remove("info-transparent"); + } + } + } + + // When webpage loaded, hide user info after 2 second + setTimeout(function () { + toggleUserInfoVisibility(true); + }, 2000); + + let triggerElements = {appTitleDiv, userInfoDiv, sendBtn}; + for (let elem in triggerElements) { + triggerElements[elem].addEventListener("mouseenter", function () { + toggleUserInfoVisibility(false); + }); + triggerElements[elem].addEventListener("mouseleave", function () { + toggleUserInfoVisibility(true); + }); + triggerElements[elem].ontouchstart = function () { + toggleUserInfoVisibility(false); + }; + triggerElements[elem].ontouchend = function () { + setTimeout(function () { + toggleUserInfoVisibility(true); + }, 3000); + }; + } +} diff --git a/web_assets/javascript/utils.js b/web_assets/javascript/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..cda208a085be790cca1cf1a18bba27550caeca30 --- /dev/null +++ b/web_assets/javascript/utils.js @@ -0,0 +1,83 @@ + +var gradioUploader = null; + +function testUpload(target) { + gradioUploader = gradioApp().querySelector("#upload-index-file > .center.flex"); + let uploaderEvents = ["click", "drag", "dragend", "dragenter", "dragleave", "dragover", "dragstart", "drop"]; + transEventListeners(target, gradioUploader, uploaderEvents); +} + + +function transEventListeners(target, source, events) { + events.forEach((sourceEvent) => { + target.addEventListener(sourceEvent, function (targetEvent) { + if(targetEvent.preventDefault) targetEvent.preventDefault(); + if(targetEvent.stopPropagation) targetEvent.stopPropagation(); + + source.dispatchEvent(new Event(sourceEvent, {detail: targetEvent.detail})); + console.log(targetEvent.detail); + }); + }); +} + + +function isImgUrl(url) { + const imageExtensions = /\.(jpg|jpeg|png|gif|bmp|webp)$/i; + if (url.startsWith('data:image/')) { + return true; + } + if (url.match(imageExtensions)) { + return true; + } + if (url.startsWith('http://') || url.startsWith('https://')) { + return true; + } + + return false; +} + + +/* NOTE: These reload functions are not used in the current version of the code. + * From stable-diffusion-webui + */ +function restart_reload() { + document.body.innerHTML = '

    Reloading...

    '; + + var requestPing = function () { + requestGet("./internal/ping", {}, function (data) { + location.reload(); + }, function () { + setTimeout(requestPing, 500); + }); + }; + + setTimeout(requestPing, 2000); + + return []; +} + +function requestGet(url, data, handler, errorHandler) { + var xhr = new XMLHttpRequest(); + var args = Object.keys(data).map(function (k) { + return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]); + }).join('&'); + xhr.open("GET", url + "?" + args, true); + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + try { + var js = JSON.parse(xhr.responseText); + handler(js); + } catch (error) { + console.error(error); + errorHandler(); + } + } else { + errorHandler(); + } + } + }; + var js = JSON.stringify(data); + xhr.send(js); +} diff --git a/web_assets/stylesheet/ChuanhuChat.css b/web_assets/stylesheet/ChuanhuChat.css new file mode 100644 index 0000000000000000000000000000000000000000..62d41dbd061d200ba5a6841b318aea22950d1791 --- /dev/null +++ b/web_assets/stylesheet/ChuanhuChat.css @@ -0,0 +1,112 @@ +:root { + --chatbot-color-light: #000000; + --chatbot-color-dark: #FFFFFF; + --chatbot-background-color-light: #F3F3F3; + --chatbot-background-color-dark: #121111; + --message-user-background-color-light: #95EC69; + --message-user-background-color-dark: #26B561; + --message-bot-background-color-light: #FFFFFF; + --message-bot-background-color-dark: #2C2C2C; + --switch-checkbox-color-light: #e5e7eb; + --switch-checkbox-color-dark: #515151; +} + +.hideK { + display: none; +} + +#app-title { + font-weight: var(--prose-header-text-weight); + font-size: var(--text-xxl); + line-height: 1.3; + text-align: left; + margin-top: 6px; + white-space: nowrap; +} +#description { + text-align: center; + margin: 32px 0 4px 0; +} + +/* 高级页面 */ +#advanced-warning { + display: flex; + flex-wrap: wrap; + flex-direction: column; + align-content: center; +} + +#netsetting-warning hr { + margin-bottom: 1em; +} + +.view-only-textbox textarea { + -webkit-text-fill-color: darkgray !important; + cursor: not-allowed !important; +} + +#footer { + text-align: center; +} +#footer div { + display: inline-block; +} +#footer .versions{ + font-size: 85%; + opacity: 0.60; +} + + +#float-display { + position: absolute; + max-height: 30px; +} + +.insert-block { + position: relative; + margin: 0; + padding: 8px 12px; + box-shadow: var(--block-shadow); + border-width: var(--block-border-width); + border-color: var(--block-border-color); + border-radius: var(--block-radius); + background: var(--block-background-fill); + width: 100%; + line-height: var(--line-sm); + min-height: 2em; +} + +/* status-display */ +#status-display { + display: flex; + min-height: 2em; + align-items: flex-end; + justify-content: flex-end; + transition: all 0.6s; +} +#status-display p { + font-size: .85em; + font-family: ui-monospace, "SF Mono", "SFMono-Regular", "Menlo", "Consolas", "Liberation Mono", "Microsoft Yahei UI", "Microsoft Yahei", monospace; + /* Windows下中文的monospace会fallback为新宋体,实在太丑,这里折中使用微软雅黑 */ + color: var(--body-text-color-subdued); +} + + +#submit-btn, #cancel-btn { + height: 40px !important; +} +#submit-btn::before { + content: url("data:image/svg+xml, %3Csvg width='21px' height='20px' viewBox='0 0 21 20' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg id='page' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E %3Cg id='send' transform='translate(0.435849, 0.088463)' fill='%23FFFFFF' fill-rule='nonzero'%3E %3Cpath d='M0.579148261,0.0428666046 C0.301105539,-0.0961547561 -0.036517765,0.122307382 0.0032026237,0.420210298 L1.4927172,18.1553639 C1.5125774,18.4334066 1.79062012,18.5922882 2.04880264,18.4929872 L8.24518329,15.8913017 L11.6412765,19.7441794 C11.8597387,19.9825018 12.2370824,19.8832008 12.3165231,19.5852979 L13.9450591,13.4882182 L19.7839562,11.0255541 C20.0619989,10.8865327 20.0818591,10.4694687 19.7839562,10.3105871 L0.579148261,0.0428666046 Z M11.6138902,17.0883151 L9.85385903,14.7195502 L0.718169621,0.618812241 L12.69945,12.9346347 L11.6138902,17.0883151 Z' id='shape'%3E%3C/path%3E %3C/g%3E %3C/g%3E %3C/svg%3E"); + height: 21px; +} +#cancel-btn::before { + content: url("data:image/svg+xml,%3Csvg width='21px' height='21px' viewBox='0 0 21 21' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg id='pg' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E %3Cpath d='M10.2072007,20.088463 C11.5727865,20.088463 12.8594566,19.8259823 14.067211,19.3010209 C15.2749653,18.7760595 16.3386126,18.0538087 17.2581528,17.1342685 C18.177693,16.2147282 18.8982283,15.1527965 19.4197586,13.9484733 C19.9412889,12.7441501 20.202054,11.4557644 20.202054,10.0833163 C20.202054,8.71773046 19.9395733,7.43106036 19.4146119,6.22330603 C18.8896505,5.01555169 18.1673997,3.95018885 17.2478595,3.0272175 C16.3283192,2.10424615 15.2646719,1.3837109 14.0569176,0.865611739 C12.8491633,0.34751258 11.5624932,0.088463 10.1969073,0.088463 C8.83132146,0.088463 7.54636692,0.34751258 6.34204371,0.865611739 C5.1377205,1.3837109 4.07407321,2.10424615 3.15110186,3.0272175 C2.22813051,3.95018885 1.5058797,5.01555169 0.984349419,6.22330603 C0.46281914,7.43106036 0.202054,8.71773046 0.202054,10.0833163 C0.202054,11.4557644 0.4645347,12.7441501 0.9894961,13.9484733 C1.5144575,15.1527965 2.23670831,16.2147282 3.15624854,17.1342685 C4.07578877,18.0538087 5.1377205,18.7760595 6.34204371,19.3010209 C7.54636692,19.8259823 8.83475258,20.088463 10.2072007,20.088463 Z M10.2072007,18.2562448 C9.07493099,18.2562448 8.01471483,18.0452309 7.0265522,17.6232031 C6.03838956,17.2011753 5.17031614,16.6161693 4.42233192,15.8681851 C3.6743477,15.1202009 3.09105726,14.2521274 2.67246059,13.2639648 C2.25386392,12.2758022 2.04456558,11.215586 2.04456558,10.0833163 C2.04456558,8.95104663 2.25386392,7.89083047 2.67246059,6.90266784 C3.09105726,5.9145052 3.6743477,5.04643178 4.42233192,4.29844756 C5.17031614,3.55046334 6.036674,2.9671729 7.02140552,2.54857623 C8.00613703,2.12997956 9.06463763,1.92068122 10.1969073,1.92068122 C11.329177,1.92068122 12.3911087,2.12997956 13.3827025,2.54857623 C14.3742962,2.9671729 15.2440852,3.55046334 15.9920694,4.29844756 C16.7400537,5.04643178 17.3233441,5.9145052 17.7419408,6.90266784 C18.1605374,7.89083047 18.3698358,8.95104663 18.3698358,10.0833163 C18.3698358,11.215586 18.1605374,12.2758022 17.7419408,13.2639648 C17.3233441,14.2521274 16.7400537,15.1202009 15.9920694,15.8681851 C15.2440852,16.6161693 14.3760118,17.2011753 13.3878492,17.6232031 C12.3996865,18.0452309 11.3394704,18.2562448 10.2072007,18.2562448 Z M7.65444721,13.6242324 L12.7496608,13.6242324 C13.0584616,13.6242324 13.3003556,13.5384544 13.4753427,13.3668984 C13.6503299,13.1953424 13.7378234,12.9585951 13.7378234,12.6566565 L13.7378234,7.49968276 C13.7378234,7.19774418 13.6503299,6.96099688 13.4753427,6.78944087 C13.3003556,6.61788486 13.0584616,6.53210685 12.7496608,6.53210685 L7.65444721,6.53210685 C7.33878414,6.53210685 7.09345904,6.61788486 6.91847191,6.78944087 C6.74348478,6.96099688 6.65599121,7.19774418 6.65599121,7.49968276 L6.65599121,12.6566565 C6.65599121,12.9585951 6.74348478,13.1953424 6.91847191,13.3668984 C7.09345904,13.5384544 7.33878414,13.6242324 7.65444721,13.6242324 Z' id='shape' fill='%23FF3B30' fill-rule='nonzero'%3E%3C/path%3E %3C/g%3E %3C/svg%3E"); + height: 21px; +} + +#chatbot-buttons button { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} \ No newline at end of file diff --git a/web_assets/stylesheet/chatbot.css b/web_assets/stylesheet/chatbot.css new file mode 100644 index 0000000000000000000000000000000000000000..d99584282c052861e5e401add62c3b94eb48ec65 --- /dev/null +++ b/web_assets/stylesheet/chatbot.css @@ -0,0 +1,278 @@ + +hr.append-display { + margin: 8px 0; + border: none; + height: 1px; + border-top-width: 0; + background-image: linear-gradient(to right, rgba(50,50,50, 0.1), rgba(150, 150, 150, 0.8), rgba(50,50,50, 0.1)); +} +.source-a { + font-size: 0.8em; + max-width: 100%; + margin: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + /* background-color: #dddddd88; */ + border-radius: 1.5rem; + padding: 0.2em; +} +.source-a a { + display: inline-block; + background-color: #aaaaaa50; + border-radius: 1rem; + padding: 0.5em; + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + min-width: 20%; + white-space: nowrap; + margin: 0.2rem 0.1rem; + text-decoration: none !important; + flex: 1; + transition: flex 0.5s; +} +.source-a a:hover { + background-color: #aaaaaa20; + flex: 2; +} + +/* 川虎助理 */ +.agent-prefix { + font-size: smaller; + opacity: 0.6; + padding: 6px 0 4px; +} +.agent-prefix::before { + content: '🐯'; + filter: grayscale(); + padding: 0 4px; +} + +/* 亮色(默认) */ +#chuanhu-chatbot { + background-color: var(--chatbot-background-color-light) !important; + color: var(--chatbot-color-light) !important; +} +[data-testid = "bot"] { + background-color: var(--message-bot-background-color-light) !important; +} +[data-testid = "user"] { + background-color: var(--message-user-background-color-light) !important; +} +/* 暗色 */ +.dark #chuanhu-chatbot { + background-color: var(--chatbot-background-color-dark) !important; + color: var(--chatbot-color-dark) !important; +} +.dark [data-testid = "bot"] { + background-color: var(--message-bot-background-color-dark) !important; +} +.dark [data-testid = "user"] { + background-color: var(--message-user-background-color-dark) !important; +} + +/* 对话气泡 */ +.message { + border-radius: var(--radius-xl) !important; + border: none; + padding: var(--spacing-xl) !important; + font-size: var(--text-md) !important; + line-height: var(--line-md) !important; + min-height: calc(var(--text-md)*var(--line-md) + 2*var(--spacing-xl)); + min-width: calc(var(--text-md)*var(--line-md) + 2*var(--spacing-xl)); +} +[data-testid = "bot"] { + max-width: calc(85% - 38px); + border-top-left-radius: 0 !important; +} +[data-testid = "user"] { + max-width: calc(85% - 38px); + width: auto !important; + border-top-right-radius: 0 !important; +} + +/* 屏幕宽度大于等于500px的设备 */ +/* update on 2023.4.8: 高度的细致调整已写入JavaScript */ +@media screen and (min-width: 500px) { + #chuanhu-chatbot { + height: calc(100vh - 200px); + } + #chuanhu-chatbot>.wrapper>.wrap { + max-height: calc(100vh - 200px - var(--line-sm)*1rem - 2*var(--block-label-margin) ); + } +} +/* 屏幕宽度小于500px的设备 */ +@media screen and (max-width: 499px) { + #chuanhu-chatbot { + height: calc(100vh - 140px); + } + #chuanhu-chatbot>.wrapper>.wrap { + max-height: calc(100vh - 140px - var(--line-sm)*1rem - 2*var(--block-label-margin) ); + } + [data-testid = "bot"] { + max-width: calc(98% - 20px) !important; + } + .chatbot-avatar { + display: none; + } + #app-title h1{ + letter-spacing: -1px; font-size: 22px; + } +} + +#chuanhu-chatbot>.wrapper>.wrap { + overflow-x: hidden; +} + +.message.user p { + white-space: pre-wrap; +} +.message .user-message { + display: block; + padding: 0 !important; + white-space: pre-wrap; +} + +.message .md-message p { + margin-top: 0.6em !important; + margin-bottom: 0.6em !important; +} +.message .md-message p:first-child { margin-top: 0 !important; } +.message .md-message p:last-of-type { margin-bottom: 0 !important; } + +.message .md-message { + display: block; + padding: 0 !important; +} +.message .raw-message p { + margin:0 !important; +} +.message .raw-message { + display: block; + padding: 0 !important; + white-space: pre-wrap; +} +.message .hideM { + display: none; +} + +/* custom buttons */ +.chuanhu-btn { + border-radius: 5px; + /* background-color: #E6E6E6 !important; */ + color: rgba(120, 120, 120, 0.64) !important; + padding: 4px !important; + position: absolute; + right: -22px; + cursor: pointer !important; + transition: color .2s ease, background-color .2s ease; +} +.chuanhu-btn:hover { + background-color: rgba(167, 167, 167, 0.25) !important; + color: unset !important; +} +.chuanhu-btn:active { + background-color: rgba(167, 167, 167, 0.5) !important; +} +.chuanhu-btn:focus { + outline: none; +} + +.copy-bot-btn { + /* top: 18px; */ + bottom: 0; +} +.toggle-md-btn { + /* top: 0; */ + bottom: 20px; +} + +/* note: this is deprecated */ +.copy-code-btn { + position: relative; + float: right; + font-size: 1em; + cursor: pointer; +} +/* note: the button below disabled in chatbot.py */ +.message div.icon-button > button[title="copy"] { + display: none; +} + + +/* history message */ +.wrapper>.wrap>.history-message { + padding-bottom: 10px !important; +} +.history-message { + /* padding: 0 !important; */ + opacity: 80%; + display: flex; + flex-direction: column; +} +.history-message>.history-message { + padding: 0 !important; +} +.history-message>.message-wrap { + padding: 0 !important; + margin-bottom: 16px; +} +.history-message>.message { + margin-bottom: 16px; +} +.wrapper>.wrap>.history-message::after { + content: ""; + display: block; + height: 2px; + background-color: var(--body-text-color-subdued); + margin-bottom: 10px; + margin-top: -10px; + clear: both; +} +.wrapper>.wrap>.history-message>:last-child::after { + content: "仅供查看"; + display: block; + text-align: center; + color: var(--body-text-color-subdued); + font-size: 0.8em; +} + +/* #chuanhu-chatbot { + transition: height 0.3s ease; + note: find it better without transition animation...; +} */ + + +.message-row { + flex-direction: row; + display: flex; + gap: 8px; + width: 100%; +} +.bot-message-row { + justify-content: flex-start; +} +.user-message-row { + justify-content: flex-end; +} +.chatbot-avatar { + width: 32px; + height: 32px; + background-color: transparent; + background-size: cover; + border-radius: 5px !important; +} +.chatbot-avatar.bot-avatar { + margin-left: 5px; +} +.chatbot-avatar.user-avatar { + margin-right: 10px; +} +.chatbot-avatar img { + border-radius: 5px !important; + object-fit: cover; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/web_assets/stylesheet/custom-components.css b/web_assets/stylesheet/custom-components.css new file mode 100644 index 0000000000000000000000000000000000000000..633c4cd958b8f45d6f185aa81adcf26f07043ea8 --- /dev/null +++ b/web_assets/stylesheet/custom-components.css @@ -0,0 +1,240 @@ + +/* user-info */ +#user-info.block { + white-space: nowrap; + position: absolute; left: 8em; top: .8em; + z-index: var(--layer-2); + box-shadow: var(--block-shadow); + border: none!important; border-radius: var(--block-label-radius); + background: var(--color-accent); + padding: var(--block-label-padding); + font-size: var(--block-label-text-size); line-height: var(--line-sm); + width: auto; max-height: 30px!important; + opacity: 1; + transition: opacity 0.3s ease-in-out; +} +#user-info.block .wrap { + opacity: 0; +} +#user-info p { + color: white; + font-weight: var(--block-label-text-weight); +} +#user-info.info-transparent { + opacity: 0; + transition: opacity 1s ease-in-out; +} + + +/* updater */ +#toast-update { + position: absolute; + display: flex; + top: -500px; + width: 100%; + justify-content: center; + z-index: var(--layer-top); + transition: top 0.3s ease-out; +} +#check-chuanhu-update { + position: absolute; + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + margin: var(--size-6) var(--size-4); + box-shadow: var(--shadow-drop-lg); + border: 1px solid var(--block-label-border-color); + border-radius: var(--container-radius); + background: var(--background-fill-primary); + padding: var(--size-4) var(--size-6); + min-width: 360px; + max-width: 480px; + overflow: hidden; + pointer-events: auto; +} +#version-info-title { + font-size: 1.2em; + font-weight: bold; + text-align: start; + width: 100%; +} +#release-note-wrap { + width: 100%; + max-width: 400px; + height: 120px; + border: solid 1px var(--border-color-primary); + overflow: auto; + padding: 0 8px; +} +#release-note-wrap.hideK { + display: none; +} +.btn-update-group { + display: flex; + justify-content: space-evenly; + align-items: center; + width: 100%; + padding-top: 10px; +} +.btn-update-group.hideK { + display: none; +} +#updating-info { + margin: 16px 0px 24px; + text-align: start; + width: 100%; +} + + +#usage-display p, #usage-display span { + margin: 0; + font-size: .85em; + color: var(--body-text-color-subdued); +} +.progress-bar { + background-color: var(--input-background-fill);; + margin: .5em 0 !important; + height: 20px; + border-radius: 10px; + overflow: hidden; +} +.progress { + background-color: var(--block-title-background-fill); + height: 100%; + border-radius: 10px; + text-align: right; + transition: width 0.5s ease-in-out; +} +.progress-text { + /* color: white; */ + color: var(--color-accent) !important; + font-size: 1em !important; + font-weight: bold; + padding-right: 10px; + line-height: 20px; +} + + +/* 亮暗色模式切换 */ +#apSwitch input[type="checkbox"] { + margin: 0 !important; +} +#apSwitch label.apSwitch { + display: flex; + align-items: center; + cursor: pointer; + color: var(--body-text-color); + font-weight: var(--checkbox-label-text-weight); + font-size: var(--checkbox-label-text-size); + line-height: var(--line-md); + margin: 2px 0 !important; +} +input[type="checkbox"]#apSwitch-checkbox::before { + background: none !important; + content: '🌞'; + border: none !important; + box-shadow: none !important; + font-size: 22px; + top: -4.4px; + left: -1px; +} +input:checked[type="checkbox"]#apSwitch-checkbox::before { + content: '🌚'; + left: 16px; +} + +/* .apSwitch { + top: 2px; + display: inline-block; + height: 22px; + position: relative; + width: 40px; + border-radius: 11px; + box-shadow: inset 0 0 1px 0 rgba(0,0,0,0.05), inset 0 0 2px 0 rgba(0,0,0,0.08) !important; +} +.apSwitch input { + display: none !important; +} +.apSlider { + background-color: var(--neutral-200); + bottom: 0; + cursor: pointer; + left: 0; + position: absolute; + right: 0; + top: 0; + transition: .4s; + font-size: 22px; + border-radius: 11px; +} +.apSlider::before { + transform: scale(0.9); + position: absolute; + transition: .4s; + content: "🌞"; +} +input:checked + .apSlider { + background-color: var(--primary-600); +} +input:checked + .apSlider::before { + transform: translateX(18px); + content:"🌚"; +} */ + +/* switch-checkbox */ +.switch-checkbox label { + flex-direction: row-reverse; + justify-content: space-between; +} +.switch-checkbox input[type="checkbox"] + span { + margin-left: 0 !important; +} + +.switch-checkbox input[type="checkbox"] { + -moz-appearance: none; + appearance: none; + -webkit-appearance: none; + outline: none; +} + +.switch-checkbox input[type="checkbox"] { + display: inline-block !important; + position: relative !important; + border: none !important; + outline: none; + width: 40px !important; + height: 22px !important; + border-radius: 11px !important; + background-image: none !important; + box-shadow: inset 0 0 1px 0 rgba(0,0,0,0.05), inset 0 0 2px 0 rgba(0,0,0,0.08) !important; + background-image: none !important; + background-color: var(--switch-checkbox-color-light) !important; + transition: .2s ease background-color; +} +.dark .switch-checkbox input[type="checkbox"] { + background-color: var(--switch-checkbox-color-dark) !important; +} +.switch-checkbox input[type="checkbox"]::before { + content: ""; + position: absolute; + width: 22px; + height: 22px; + top: 0; + left: 0; + background: #FFFFFF; + border: 0.5px solid rgba(0,0,0,0.02); + box-shadow: 0 0 0 0 rgba(0,0,0,0.15), 0 1px 0 0 rgba(0,0,0,0.05); + transform: scale(0.9); + border-radius: 11px !important; + transition: .4s ease all; + box-shadow: var(--input-shadow); +} +.switch-checkbox input:checked[type="checkbox"] { + background-color: var(--primary-600) !important; +} +.switch-checkbox input:checked[type="checkbox"]::before { + background-color: #fff; + left: 18px; +} + diff --git a/web_assets/stylesheet/markdown.css b/web_assets/stylesheet/markdown.css new file mode 100644 index 0000000000000000000000000000000000000000..6b2215ad0d9284192a8cad21aa79e904aa5e8b16 --- /dev/null +++ b/web_assets/stylesheet/markdown.css @@ -0,0 +1,61 @@ + +.md-message img{ + border-radius: 10px !important; +} + +/* 表格 */ +.message table { + margin: 1em 0; + border-collapse: collapse; + empty-cells: show; +} +.message td, .message th { + border: 1.2px solid var(--border-color-primary) !important; + padding: 0.2em; +} +.message thead { + background-color: rgba(175,184,193,0.2); +} +.message thead th { + padding: .5em .2em; +} + +/* 行内代码 */ +.message :not(pre) code { + display: inline; + white-space: break-spaces; + font-family: var(--font-mono); + border-radius: 6px; + margin: 0 2px 0 2px; + padding: .2em .4em .1em .4em; + background-color: rgba(175,184,193,0.2); +} +/* 代码块 */ +.message pre, +.message pre[class*=language-] { + color: #fff; + overflow-x: auto; + overflow-y: hidden; + margin: .8em 1em 1em 0em !important; + padding: var(--spacing-xl) 1.2em !important; + border-radius: var(--radius-lg) !important; +} +.message pre code, +.message pre code[class*=language-] { + color: #fff; + padding: 0; + margin: 0; + background-color: unset; + text-shadow: none; + font-family: var(--font-mono); +} + + +/* 覆盖prism.css */ +.language-css .token.string, +.style .token.string, +.token.entity, +.token.operator, +.token.url { + background: none !important; +} diff --git a/web_assets/stylesheet/override-gradio.css b/web_assets/stylesheet/override-gradio.css new file mode 100644 index 0000000000000000000000000000000000000000..4705139f87d3625c43bb688c9e62d200a956f57a --- /dev/null +++ b/web_assets/stylesheet/override-gradio.css @@ -0,0 +1,67 @@ + +/* 解决container=False时的错误填充 */ +div.form { + background: none !important; +} +div.no-container { + padding: 10px 0 0 0 !important; +} + +/* gradio的页脚信息 */ +footer { + /* display: none !important; */ + margin-top: .2em !important; + font-size: 85%; +} + +/* 覆盖 gradio 丑陋的复制按钮样式 */ +.message pre button[title="copy"] { + border-radius: 5px; + transition: background-color .2s ease; +} +.message pre button[title="copy"]:hover { + background-color: #333232; +} +.message pre button .check { + color: #fff !important; + background: var(--neutral-950) !important; +} + + + + +/* Override Slider Styles (for webkit browsers like Safari and Chrome) + * 好希望这份提案能早日实现 https://github.com/w3c/csswg-drafts/issues/4410 + * 进度滑块在各个平台还是太不统一了 +**/ + +input[type="range"] { + /* -webkit-appearance: none; */ + appearance: none; + height: 4px; + background: var(--input-background-fill); + border-radius: 5px; + background-image: linear-gradient(var(--primary-500),var(--primary-500)); + background-size: 0% 100%; + background-repeat: no-repeat; +} +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + height: 20px; + width: 20px; + border-radius: 50%; + border: solid 0.5px #ddd; + background-color: white; + cursor: ew-resize; + box-shadow: var(--input-shadow); + transition: background-color .1s ease; +} +input[type="range"]::-webkit-slider-thumb:hover { + background: var(--neutral-50); +} +input[type=range]::-webkit-slider-runnable-track { + -webkit-appearance: none; + box-shadow: none; + border: none; + background: transparent; +}