diff --git a/Dockerfile b/Dockerfile
index 27755a35..b5ae77c1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
# - 如何构建: 先修改 `config.py`, 然后 `docker build -t gpt-academic . `
# - 如何运行(Linux下): `docker run --rm -it --net=host gpt-academic `
# - 如何运行(其他操作系统,选择任意一个固定端口50923): `docker run --rm -it -e WEB_PORT=50923 -p 50923:50923 gpt-academic `
-FROM python:3.11
+FROM ghcr.io/astral-sh/uv:python3.12-bookworm
# 非必要步骤,更换pip源 (以下三行,可以删除)
@@ -12,8 +12,15 @@ RUN echo '[global]' > /etc/pip.conf && \
echo 'trusted-host = mirrors.aliyun.com' >> /etc/pip.conf
-# 语音输出功能(以下两行,第一行更换阿里源,第二行安装ffmpeg,都可以删除)
-RUN UBUNTU_VERSION=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release); echo "deb https://mirrors.aliyun.com/debian/ $UBUNTU_VERSION main non-free contrib" > /etc/apt/sources.list; apt-get update
+# 语音输出功能(以下1,2行更换阿里源,第3,4行安装ffmpeg,都可以删除)
+# RUN echo "deb https://mirrors.aliyun.com/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list
+# RUN echo "deb https://mirrors.aliyun.com/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list
+# RUN echo "deb https://mirrors.aliyun.com/debian/ bookworm-backports main contrib non-free non-free-firmware" >> /etc/apt/sources.list
+# RUN echo "deb https://mirrors.aliyun.com/debian-security/ bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list
+# COPY /usr/share/doc/apt/examples/sources.list /etc/apt/sources.list
+RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
+ sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
+ apt-get update
RUN apt-get install ffmpeg -y
RUN apt-get clean
@@ -24,13 +31,15 @@ WORKDIR /gpt
# 安装大部分依赖,利用Docker缓存加速以后的构建 (以下两行,可以删除)
COPY requirements.txt ./
-RUN pip3 install -r requirements.txt
+RUN uv venv --python=3.12 && uv pip install --verbose -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
# 装载项目文件,安装剩余依赖(必要)
COPY . .
-RUN pip3 install -r requirements.txt
+RUN uv venv --python=3.12 && uv pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
+RUN ln -sf .venv/bin/python3.12 /usr/local/bin/python3 && \
+ ln -sf .venv/bin/python3.12 /usr/local/bin/python
# 非必要步骤,用于预热模块(可以删除)
RUN python3 -c 'from check_proxy import warm_up_modules; warm_up_modules()'
@@ -38,4 +47,4 @@ RUN python3 -m pip cache purge
# 启动(必要)
-CMD ["python3", "-u", "main.py"]
+CMD ["bash", "-c", "source .venv/bin/activate && python main.py"]
diff --git a/crazy_functions/Conversation_To_File.py b/crazy_functions/Conversation_To_File.py
index b8408748..2e1bf47f 100644
--- a/crazy_functions/Conversation_To_File.py
+++ b/crazy_functions/Conversation_To_File.py
@@ -1,10 +1,11 @@
-from toolbox import CatchException, update_ui, promote_file_to_downloadzone, get_log_folder, get_user
-from crazy_functions.plugin_template.plugin_class_template import GptAcademicPluginTemplate, ArgProperty
import re
+from toolbox import CatchException, update_ui, promote_file_to_downloadzone, get_log_folder, get_user, update_ui_latest_msg
+from crazy_functions.plugin_template.plugin_class_template import GptAcademicPluginTemplate, ArgProperty
+from loguru import logger
f_prefix = 'GPT-Academic对话存档'
-def write_chat_to_file(chatbot, history=None, file_name=None):
+def write_chat_to_file_legacy(chatbot, history=None, file_name=None):
"""
将对话记录history以Markdown格式写入文件中。如果没有指定文件名,则使用当前时间生成文件名。
"""
@@ -12,6 +13,9 @@ def write_chat_to_file(chatbot, history=None, file_name=None):
import time
from themes.theme import advanced_css
+ if (file_name is not None) and (file_name != "") and (not file_name.endswith('.html')): file_name += '.html'
+ else: file_name = None
+
if file_name is None:
file_name = f_prefix + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + '.html'
fp = os.path.join(get_log_folder(get_user(chatbot), plugin_name='chat_history'), file_name)
@@ -68,6 +72,147 @@ def write_chat_to_file(chatbot, history=None, file_name=None):
promote_file_to_downloadzone(fp, rename_file=file_name, chatbot=chatbot)
return '对话历史写入:' + fp
+def write_chat_to_file(chatbot, history=None, file_name=None):
+ """
+ 将对话记录history以多种格式(HTML、Word、Markdown)写入文件中。如果没有指定文件名,则使用当前时间生成文件名。
+
+ Args:
+ chatbot: 聊天机器人对象,包含对话内容
+ history: 对话历史记录
+ file_name: 指定的文件名,如果为None则使用时间戳
+
+ Returns:
+ str: 提示信息,包含文件保存路径
+ """
+ import os
+ import time
+ import asyncio
+ import aiofiles
+ from toolbox import promote_file_to_downloadzone
+ from crazy_functions.doc_fns.conversation_doc.excel_doc import save_chat_tables
+ from crazy_functions.doc_fns.conversation_doc.html_doc import HtmlFormatter
+ from crazy_functions.doc_fns.conversation_doc.markdown_doc import MarkdownFormatter
+ from crazy_functions.doc_fns.conversation_doc.word_doc import WordFormatter
+ from crazy_functions.doc_fns.conversation_doc.txt_doc import TxtFormatter
+ from crazy_functions.doc_fns.conversation_doc.word2pdf import WordToPdfConverter
+
+ async def save_html():
+ try:
+ html_formatter = HtmlFormatter(chatbot, history)
+ html_content = html_formatter.create_document()
+ html_file = os.path.join(save_dir, base_name + '.html')
+ async with aiofiles.open(html_file, 'w', encoding='utf8') as f:
+ await f.write(html_content)
+ return html_file
+ except Exception as e:
+ print(f"保存HTML格式失败: {str(e)}")
+ return None
+
+ async def save_word():
+ try:
+ word_formatter = WordFormatter()
+ doc = word_formatter.create_document(history)
+ docx_file = os.path.join(save_dir, base_name + '.docx')
+ # 由于python-docx不支持异步,使用线程池执行
+ loop = asyncio.get_event_loop()
+ await loop.run_in_executor(None, doc.save, docx_file)
+ return docx_file
+ except Exception as e:
+ print(f"保存Word格式失败: {str(e)}")
+ return None
+ async def save_pdf(docx_file):
+ try:
+ if docx_file:
+ # 获取文件名和保存路径
+ pdf_file = os.path.join(save_dir, base_name + '.pdf')
+
+ # 在线程池中执行转换
+ loop = asyncio.get_event_loop()
+ pdf_file = await loop.run_in_executor(
+ None,
+ WordToPdfConverter.convert_to_pdf,
+ docx_file
+ # save_dir
+ )
+
+ return pdf_file
+
+ except Exception as e:
+ print(f"保存PDF格式失败: {str(e)}")
+ return None
+
+ async def save_markdown():
+ try:
+ md_formatter = MarkdownFormatter()
+ md_content = md_formatter.create_document(history)
+ md_file = os.path.join(save_dir, base_name + '.md')
+ async with aiofiles.open(md_file, 'w', encoding='utf8') as f:
+ await f.write(md_content)
+ return md_file
+ except Exception as e:
+ print(f"保存Markdown格式失败: {str(e)}")
+ return None
+
+ async def save_txt():
+ try:
+ txt_formatter = TxtFormatter()
+ txt_content = txt_formatter.create_document(history)
+ txt_file = os.path.join(save_dir, base_name + '.txt')
+ async with aiofiles.open(txt_file, 'w', encoding='utf8') as f:
+ await f.write(txt_content)
+ return txt_file
+ except Exception as e:
+ print(f"保存TXT格式失败: {str(e)}")
+ return None
+
+ async def main():
+ # 并发执行所有保存任务
+ html_task = asyncio.create_task(save_html())
+ word_task = asyncio.create_task(save_word())
+ md_task = asyncio.create_task(save_markdown())
+ txt_task = asyncio.create_task(save_txt())
+
+ # 等待所有任务完成
+ html_file = await html_task
+ docx_file = await word_task
+ md_file = await md_task
+ txt_file = await txt_task
+
+ # PDF转换需要等待word文件生成完成
+ pdf_file = await save_pdf(docx_file)
+ # 收集所有成功生成的文件
+ result_files = [f for f in [html_file, docx_file, md_file, txt_file, pdf_file] if f]
+
+ # 保存Excel表格
+ excel_files = save_chat_tables(history, save_dir, base_name)
+ result_files.extend(excel_files)
+
+ return result_files
+
+ # 生成时间戳
+ timestamp = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
+
+ # 获取保存目录
+ save_dir = get_log_folder(get_user(chatbot), plugin_name='chat_history')
+
+ # 处理文件名
+ base_name = file_name if file_name else f"聊天记录_{timestamp}"
+
+ # 运行异步任务
+ result_files = asyncio.run(main())
+
+ # 将生成的文件添加到下载区
+ for file in result_files:
+ promote_file_to_downloadzone(file, rename_file=os.path.basename(file), chatbot=chatbot)
+
+ # 如果没有成功保存任何文件,返回错误信息
+ if not result_files:
+ return "保存对话记录失败,请检查错误日志"
+
+ ext_list = [os.path.splitext(f)[1] for f in result_files]
+ # 返回成功信息和文件路径
+ return f"对话历史已保存至以下格式文件:" + "、".join(ext_list)
+
def gen_file_preview(file_name):
try:
with open(file_name, 'r', encoding='utf8') as f:
@@ -119,12 +264,21 @@ def 对话历史存档(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_
user_request 当前用户的请求信息(IP地址等)
"""
file_name = plugin_kwargs.get("file_name", None)
- if (file_name is not None) and (file_name != "") and (not file_name.endswith('.html')): file_name += '.html'
- else: file_name = None
-
- chatbot.append((None, f"[Local Message] {write_chat_to_file(chatbot, history, file_name)},您可以调用下拉菜单中的“载入对话历史存档”还原当下的对话。"))
- yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 # 由于请求gpt需要一段时间,我们先及时地做一次界面更新
+ chatbot.append((None, f"[Local Message] {write_chat_to_file_legacy(chatbot, history, file_name)},您可以调用下拉菜单中的“载入对话历史存档”还原当下的对话。"))
+ try:
+ chatbot.append((None, f"[Local Message] 正在尝试生成pdf以及word格式的对话存档,请稍等..."))
+ yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 # 由于请求需要一段时间,我们先及时地做一次界面更新
+ lastmsg = f"[Local Message] {write_chat_to_file(chatbot, history, file_name)}。" \
+ f"您可以调用下拉菜单中的“载入对话历史会话”还原当下的对话,请注意,目前只支持html格式载入历史。" \
+ f"当模型回答中存在表格,将提取表格内容存储为Excel的xlsx格式,如果你提供一些数据,然后输入指令要求模型帮你整理为表格" \
+ f"(如“请帮我将下面的数据整理为表格:”),再利用此插件就可以获取到Excel表格。"
+ yield from update_ui_latest_msg(lastmsg, chatbot, history) # 刷新界面 # 由于请求需要一段时间,我们先及时地做一次界面更新
+ except Exception as e:
+ logger.exception(f"已完成对话存档(pdf和word格式的对话存档生成未成功)。{str(e)}")
+ lastmsg = "已完成对话存档(pdf和word格式的对话存档生成未成功)。"
+ yield from update_ui_latest_msg(lastmsg, chatbot, history) # 刷新界面 # 由于请求需要一段时间,我们先及时地做一次界面更新
+ return
class Conversation_To_File_Wrap(GptAcademicPluginTemplate):
def __init__(self):
diff --git a/toolbox.py b/toolbox.py
index 3f0ca5cf..d2583905 100644
--- a/toolbox.py
+++ b/toolbox.py
@@ -594,7 +594,7 @@ def on_report_generated(cookies:dict, files:List[str], chatbot:ChatBotWithCookie
file_links += (
f'
{f}'
)
- chatbot.append(["报告如何远程获取?", f"报告已经添加到右侧“文件下载区”(可能处于折叠状态),请查收。{file_links}"])
+ chatbot.append([None, f"已经添加到右侧“文件下载区”(可能处于折叠状态),请查收。您也可以点击以下链接直接下载:{file_links}"])
return cookies, report_files, chatbot