镜像自地址
https://github.com/binary-husky/gpt_academic.git
已同步 2025-12-06 14:36:48 +00:00
Merge branch 'master' into frontier
这个提交包含在:
@@ -1,9 +1,9 @@
|
||||
> [!IMPORTANT]
|
||||
> `master主分支`最新动态(2025.1.28): 增加字体自定义功能
|
||||
> `frontier开发分支`最新动态(2024.12.9): 更新对话时间线功能,优化xelatex论文翻译
|
||||
> `wiki文档`最新动态(2024.12.5): 更新ollama接入指南
|
||||
> `master主分支`最新动态(2024.12.19): 更新3.91版本,更新release页一键安装脚本
|
||||
>
|
||||
> 2024.10.10: 突发停电,紧急恢复了提供[whl包](https://drive.google.com/file/d/19U_hsLoMrjOlQSzYS3pzWX9fTzyusArP/view?usp=sharing)的文件服务器
|
||||
> 2024.10.10: 突发停电,紧急恢复了提供[whl包](https://drive.google.com/drive/folders/14kR-3V-lIbvGxri4AHc8TpiA1fqsw7SK?usp=sharing)的文件服务器
|
||||
> 2024.10.8: 版本3.90加入对llama-index的初步支持,版本3.80加入插件二级菜单功能(详见wiki)
|
||||
> 2024.5.1: 加入Doc2x翻译PDF论文的功能,[查看详情](https://github.com/binary-husky/gpt_academic/wiki/Doc2x)
|
||||
> 2024.3.11: 全力支持Qwen、GLM、DeepseekCoder等中文大语言模型! SoVits语音克隆模块,[查看详情](https://www.bilibili.com/video/BV1Rp421S7tF/)
|
||||
|
||||
24
config.py
24
config.py
@@ -85,6 +85,30 @@ DEFAULT_WORKER_NUM = 3
|
||||
THEME = "Default"
|
||||
AVAIL_THEMES = ["Default", "Chuanhu-Small-and-Beautiful", "High-Contrast", "Gstaff/Xkcd", "NoCrypt/Miku"]
|
||||
|
||||
FONT = "Theme-Default-Font"
|
||||
AVAIL_FONTS = [
|
||||
"默认值(Theme-Default-Font)",
|
||||
"宋体(SimSun)",
|
||||
"黑体(SimHei)",
|
||||
"楷体(KaiTi)",
|
||||
"仿宋(FangSong)",
|
||||
"华文细黑(STHeiti Light)",
|
||||
"华文楷体(STKaiti)",
|
||||
"华文仿宋(STFangsong)",
|
||||
"华文宋体(STSong)",
|
||||
"华文中宋(STZhongsong)",
|
||||
"华文新魏(STXinwei)",
|
||||
"华文隶书(STLiti)",
|
||||
"思源宋体(Source Han Serif CN VF@https://chinese-fonts-cdn.deno.dev/packages/syst/dist/SourceHanSerifCN/result.css)",
|
||||
"月星楷(Moon Stars Kai HW@https://chinese-fonts-cdn.deno.dev/packages/moon-stars-kai/dist/MoonStarsKaiHW-Regular/result.css)",
|
||||
"珠圆体(MaokenZhuyuanTi@https://chinese-fonts-cdn.deno.dev/packages/mkzyt/dist/猫啃珠圆体/result.css)",
|
||||
"平方萌萌哒(PING FANG MENG MNEG DA@https://chinese-fonts-cdn.deno.dev/packages/pfmmd/dist/平方萌萌哒/result.css)",
|
||||
"Helvetica",
|
||||
"ui-sans-serif",
|
||||
"sans-serif",
|
||||
"system-ui"
|
||||
]
|
||||
|
||||
|
||||
# 默认的系统提示词(system prompt)
|
||||
INIT_SYS_PROMPT = "Serve me as a writing and programming assistant."
|
||||
|
||||
@@ -373,7 +373,7 @@ def 编译Latex(chatbot, history, main_file_original, main_file_modified, work_f
|
||||
# 根据编译器类型返回编译命令
|
||||
def get_compile_command(compiler, filename):
|
||||
compile_command = f'{compiler} -interaction=batchmode -file-line-error {filename}.tex'
|
||||
logger.info('Latex 编译指令: ', compile_command)
|
||||
logger.info('Latex 编译指令: ' + compile_command)
|
||||
return compile_command
|
||||
|
||||
# 确定使用的编译器
|
||||
|
||||
@@ -5,6 +5,9 @@ FROM fuqingxu/11.3.1-runtime-ubuntu20.04-with-texlive:latest
|
||||
|
||||
# edge-tts需要的依赖,某些pip包所需的依赖
|
||||
RUN apt update && apt install ffmpeg build-essential -y
|
||||
RUN apt-get install -y fontconfig
|
||||
RUN ln -s /usr/local/texlive/2023/texmf-dist/fonts/truetype /usr/share/fonts/truetype/texlive
|
||||
RUN fc-cache -fv
|
||||
RUN apt-get clean
|
||||
|
||||
# use python3 as the system default python
|
||||
|
||||
7
main.py
7
main.py
@@ -49,7 +49,7 @@ def main():
|
||||
# 读取配置
|
||||
proxies, WEB_PORT, LLM_MODEL, CONCURRENT_COUNT, AUTHENTICATION = get_conf('proxies', 'WEB_PORT', 'LLM_MODEL', 'CONCURRENT_COUNT', 'AUTHENTICATION')
|
||||
CHATBOT_HEIGHT, LAYOUT, AVAIL_LLM_MODELS, AUTO_CLEAR_TXT = get_conf('CHATBOT_HEIGHT', 'LAYOUT', 'AVAIL_LLM_MODELS', 'AUTO_CLEAR_TXT')
|
||||
ENABLE_AUDIO, AUTO_CLEAR_TXT, PATH_LOGGING, AVAIL_THEMES, THEME, ADD_WAIFU = get_conf('ENABLE_AUDIO', 'AUTO_CLEAR_TXT', 'PATH_LOGGING', 'AVAIL_THEMES', 'THEME', 'ADD_WAIFU')
|
||||
ENABLE_AUDIO, AUTO_CLEAR_TXT, AVAIL_FONTS, AVAIL_THEMES, THEME, ADD_WAIFU = get_conf('ENABLE_AUDIO', 'AUTO_CLEAR_TXT', 'AVAIL_FONTS', 'AVAIL_THEMES', 'THEME', 'ADD_WAIFU')
|
||||
NUM_CUSTOM_BASIC_BTN, SSL_KEYFILE, SSL_CERTFILE = get_conf('NUM_CUSTOM_BASIC_BTN', 'SSL_KEYFILE', 'SSL_CERTFILE')
|
||||
DARK_MODE, INIT_SYS_PROMPT, ADD_WAIFU, TTS_TYPE = get_conf('DARK_MODE', 'INIT_SYS_PROMPT', 'ADD_WAIFU', 'TTS_TYPE')
|
||||
if LLM_MODEL not in AVAIL_LLM_MODELS: AVAIL_LLM_MODELS += [LLM_MODEL]
|
||||
@@ -178,7 +178,7 @@ def main():
|
||||
# 左上角工具栏定义
|
||||
from themes.gui_toolbar import define_gui_toolbar
|
||||
checkboxes, checkboxes_2, max_length_sl, theme_dropdown, system_prompt, file_upload_2, md_dropdown, top_p, temperature = \
|
||||
define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAIL_THEMES, ADD_WAIFU, help_menu_description, js_code_for_toggle_darkmode)
|
||||
define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAIL_THEMES, AVAIL_FONTS, ADD_WAIFU, help_menu_description, js_code_for_toggle_darkmode)
|
||||
|
||||
# 浮动菜单定义
|
||||
from themes.gui_floating_menu import define_gui_floating_menu
|
||||
@@ -228,9 +228,6 @@ def main():
|
||||
cancel_handles.append(submit_btn.click(**predict_args))
|
||||
resetBtn.click(None, None, [chatbot, history, status], _js= """clear_conversation""") # 先在前端快速清除chatbot&status
|
||||
resetBtn2.click(None, None, [chatbot, history, status], _js="""clear_conversation""") # 先在前端快速清除chatbot&status
|
||||
# reset_server_side_args = (lambda history: ([], [], "已重置"), [history], [chatbot, history, status])
|
||||
# resetBtn.click(*reset_server_side_args) # 再在后端清除history
|
||||
# resetBtn2.click(*reset_server_side_args) # 再在后端清除history
|
||||
clearBtn.click(None, None, [txt, txt2], _js=js_code_clear)
|
||||
clearBtn2.click(None, None, [txt, txt2], _js=js_code_clear)
|
||||
if AUTO_CLEAR_TXT:
|
||||
|
||||
@@ -13,7 +13,7 @@ scipdf_parser>=0.52
|
||||
spacy==3.7.4
|
||||
anthropic>=0.18.1
|
||||
python-markdown-math
|
||||
pymdown-extensions
|
||||
pymdown-extensions>=10.14
|
||||
websocket-client
|
||||
beautifulsoup4
|
||||
prompt_toolkit
|
||||
|
||||
@@ -2,6 +2,7 @@ import markdown
|
||||
import re
|
||||
import os
|
||||
import math
|
||||
import html
|
||||
|
||||
from loguru import logger
|
||||
from textwrap import dedent
|
||||
@@ -384,6 +385,24 @@ def markdown_convertion(txt):
|
||||
)
|
||||
|
||||
|
||||
def code_block_title_replace_format(match):
|
||||
lang = match.group(1)
|
||||
filename = match.group(2)
|
||||
return f"```{lang} {{title=\"{filename}\"}}\n"
|
||||
|
||||
|
||||
def get_last_backticks_indent(text):
|
||||
# 从后向前查找最后一个 ```
|
||||
lines = text.splitlines()
|
||||
for line in reversed(lines):
|
||||
if '```' in line:
|
||||
# 计算前面的空格数量
|
||||
indent = len(line) - len(line.lstrip())
|
||||
return indent
|
||||
return 0 # 如果没找到返回0
|
||||
|
||||
|
||||
@lru_cache(maxsize=16) # 使用lru缓存
|
||||
def close_up_code_segment_during_stream(gpt_reply):
|
||||
"""
|
||||
在gpt输出代码的中途(输出了前面的```,但还没输出完后面的```),补上后面的```
|
||||
@@ -397,6 +416,12 @@ def close_up_code_segment_during_stream(gpt_reply):
|
||||
"""
|
||||
if "```" not in gpt_reply:
|
||||
return gpt_reply
|
||||
|
||||
# replace [```python:warp.py] to [```python {title="warp.py"}]
|
||||
pattern = re.compile(r"```([a-z]{1,12}):([^:\n]{1,35}\.([a-zA-Z^:\n]{1,3}))\n")
|
||||
if pattern.search(gpt_reply):
|
||||
gpt_reply = pattern.sub(code_block_title_replace_format, gpt_reply)
|
||||
|
||||
if gpt_reply.endswith("```"):
|
||||
return gpt_reply
|
||||
|
||||
@@ -404,7 +429,11 @@ def close_up_code_segment_during_stream(gpt_reply):
|
||||
segments = gpt_reply.split("```")
|
||||
n_mark = len(segments) - 1
|
||||
if n_mark % 2 == 1:
|
||||
return gpt_reply + "\n```" # 输出代码片段中!
|
||||
try:
|
||||
num_padding = get_last_backticks_indent(gpt_reply)
|
||||
except:
|
||||
num_padding = 0
|
||||
return gpt_reply + "\n" + " "*num_padding + "```" # 输出代码片段中!
|
||||
else:
|
||||
return gpt_reply
|
||||
|
||||
@@ -421,6 +450,19 @@ def special_render_issues_for_mermaid(text):
|
||||
return text
|
||||
|
||||
|
||||
def contain_html_tag(text):
|
||||
"""
|
||||
判断文本中是否包含HTML标签。
|
||||
"""
|
||||
pattern = r'</?([a-zA-Z0-9_]{3,16})>|<script\s+[^>]*src=["\']([^"\']+)["\'][^>]*>'
|
||||
return re.search(pattern, text) is not None
|
||||
|
||||
|
||||
def contain_image(text):
|
||||
pattern = r'<br/><br/><div align="center"><img src="file=(.*?)" base64="(.*?)"></div>'
|
||||
return re.search(pattern, text) is not None
|
||||
|
||||
|
||||
def compat_non_markdown_input(text):
|
||||
"""
|
||||
改善非markdown输入的显示效果,例如将空格转换为 ,将换行符转换为</br>等。
|
||||
@@ -429,9 +471,13 @@ def compat_non_markdown_input(text):
|
||||
# careful input:markdown输入
|
||||
text = special_render_issues_for_mermaid(text) # 处理特殊的渲染问题
|
||||
return text
|
||||
elif "</div>" in text:
|
||||
elif ("<" in text) and (">" in text) and contain_html_tag(text):
|
||||
# careful input:html输入
|
||||
return text
|
||||
if contain_image(text):
|
||||
return text
|
||||
else:
|
||||
escaped_text = html.escape(text)
|
||||
return escaped_text
|
||||
else:
|
||||
# whatever input:非markdown输入
|
||||
lines = text.split("\n")
|
||||
|
||||
@@ -20,7 +20,7 @@ Replace 'Tex/' with the actual directory path where your files are located befor
|
||||
md = """
|
||||
Following code including wrapper
|
||||
|
||||
```mermaid
|
||||
```python:wrapper.py
|
||||
graph TD
|
||||
A[Enter Chart Definition] --> B(Preview)
|
||||
B --> C{decide}
|
||||
@@ -41,6 +41,33 @@ Any folded content here. It requires an empty line just above it.
|
||||
|
||||
</details>
|
||||
|
||||
"""
|
||||
|
||||
md ="""
|
||||
|
||||
在这种场景中,您希望机器 B 能够通过轮询机制来间接地“请求”机器 A,而实际上机器 A 只能主动向机器 B 发出请求。这是一种典型的客户端-服务器轮询模式。下面是如何实现这种机制的详细步骤:
|
||||
|
||||
### 机器 B 的实现
|
||||
|
||||
1. **安装 FastAPI 和必要的依赖库**:
|
||||
```bash
|
||||
pip install fastapi uvicorn
|
||||
```
|
||||
|
||||
2. **创建 FastAPI 服务**:
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import JSONResponse
|
||||
from uuid import uuid4
|
||||
from threading import Lock
|
||||
import time
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# 字典用于存储请求和状态
|
||||
requests = {}
|
||||
process_lock = Lock()
|
||||
|
||||
"""
|
||||
def validate_path():
|
||||
import os, sys
|
||||
@@ -53,10 +80,12 @@ def validate_path():
|
||||
|
||||
validate_path() # validate path so you can run from base directory
|
||||
from toolbox import markdown_convertion
|
||||
from shared_utils.advanced_markdown_format import markdown_convertion_for_file
|
||||
# from shared_utils.advanced_markdown_format import markdown_convertion_for_file
|
||||
from shared_utils.advanced_markdown_format import close_up_code_segment_during_stream
|
||||
# with open("gpt_log/default_user/shared/2024-04-22-01-27-43.zip.extract/translated_markdown.md", "r", encoding="utf-8") as f:
|
||||
# md = f.read()
|
||||
html = markdown_convertion_for_file(md)
|
||||
md = close_up_code_segment_during_stream(md)
|
||||
html = markdown_convertion(md)
|
||||
# print(html)
|
||||
with open("test.html", "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
:root {
|
||||
--gpt-academic-message-font-size: 15px;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: var(--gpt-academic-message-font-size) !important;
|
||||
}
|
||||
|
||||
#plugin_arg_menu {
|
||||
transform: translate(-50%, -50%);
|
||||
border: dashed;
|
||||
}
|
||||
|
||||
|
||||
/* hide remove all button */
|
||||
.remove-all.svelte-aqlk7e.svelte-aqlk7e.svelte-aqlk7e {
|
||||
visibility: hidden;
|
||||
@@ -25,7 +32,6 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* height of the upload box */
|
||||
.wrap.svelte-xwlu1w {
|
||||
min-height: var(--size-32);
|
||||
@@ -97,13 +103,9 @@
|
||||
min-width: min(80px, 100%);
|
||||
}
|
||||
|
||||
|
||||
#cbs {
|
||||
background-color: var(--block-background-fill) !important;
|
||||
}
|
||||
|
||||
#cbs,
|
||||
#cbsc {
|
||||
background-color: var(--block-background-fill) !important;
|
||||
background-color: rgba(var(--block-background-fill), 0.5) !important;
|
||||
}
|
||||
|
||||
#interact-panel .form {
|
||||
@@ -207,6 +209,7 @@
|
||||
.welcome-content {
|
||||
text-wrap: balance;
|
||||
height: 55px;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -276,3 +279,35 @@
|
||||
box-shadow: 10px 10px 15px rgba(0, 0, 0, 0.5);
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
#tooltip .hidden {
|
||||
/* display: none; */
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
#tooltip .visible {
|
||||
/* display: block; */
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
#elem_fontsize,
|
||||
#elem_top_p,
|
||||
#elem_temperature,
|
||||
#elem_max_length_sl,
|
||||
#elem_prompt {
|
||||
/* 左右为0;顶部为0,底部为2px */
|
||||
padding: 0 0 4px 0;
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--block-background-fill), 0.5);
|
||||
}
|
||||
|
||||
|
||||
#tooltip #cbs,
|
||||
#tooltip #cbsc,
|
||||
#tooltip .svelte-b6y5bg,
|
||||
#tooltip .tabitem {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(var(--block-background-fill), 0.5);
|
||||
}
|
||||
|
||||
@@ -750,10 +750,24 @@ function minor_ui_adjustment() {
|
||||
var bar_btn_width = [];
|
||||
// 自动隐藏超出范围的toolbar按钮
|
||||
function auto_hide_toolbar() {
|
||||
var qq = document.getElementById('tooltip');
|
||||
var tab_nav = qq.getElementsByClassName('tab-nav');
|
||||
// if chatbot hit upper page boarder, hide all
|
||||
const elem_chatbot = document.getElementById('gpt-chatbot');
|
||||
const chatbot_top = elem_chatbot.getBoundingClientRect().top;
|
||||
var tooltip = document.getElementById('tooltip');
|
||||
var tab_nav = tooltip.getElementsByClassName('tab-nav')[0];
|
||||
|
||||
// 20 px 大概是一个字的高度
|
||||
if (chatbot_top < 20) {
|
||||
// tab_nav.style.display = 'none';
|
||||
if (tab_nav.classList.contains('visible')) {tab_nav.classList.remove('visible');}
|
||||
if (!tab_nav.classList.contains('hidden')) {tab_nav.classList.add('hidden');}
|
||||
return;
|
||||
}
|
||||
if (tab_nav.classList.contains('hidden')) {tab_nav.classList.remove('hidden');}
|
||||
if (!tab_nav.classList.contains('visible')) {tab_nav.classList.add('visible');}
|
||||
// tab_nav.style.display = '';
|
||||
if (tab_nav.length == 0) { return; }
|
||||
var btn_list = tab_nav[0].getElementsByTagName('button')
|
||||
var btn_list = tab_nav.getElementsByTagName('button')
|
||||
if (btn_list.length == 0) { return; }
|
||||
// 获取页面宽度
|
||||
var page_width = document.documentElement.clientWidth;
|
||||
@@ -1034,7 +1048,7 @@ async function save_conversation_history() {
|
||||
return timeB - timeA;
|
||||
});
|
||||
|
||||
const max_chat_preserve = 4;
|
||||
const max_chat_preserve = 10;
|
||||
|
||||
if (conversation_history.length >= max_chat_preserve + 1) {
|
||||
toast_push('对话时间线记录已满,正在移除最早的对话记录。您也可以点击左侧的记录点进行手动清理。', 3000);
|
||||
|
||||
@@ -567,7 +567,6 @@ ul:not(.options) {
|
||||
border-radius: var(--radius-xl) !important;
|
||||
border: none;
|
||||
padding: var(--spacing-xl) !important;
|
||||
font-size: 15px !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));
|
||||
|
||||
@@ -10,6 +10,14 @@ theme_dir = os.path.dirname(__file__)
|
||||
def adjust_theme():
|
||||
try:
|
||||
set_theme = gr.themes.Soft(
|
||||
font=[
|
||||
"Helvetica",
|
||||
"Microsoft YaHei",
|
||||
"ui-sans-serif",
|
||||
"sans-serif",
|
||||
"system-ui",
|
||||
],
|
||||
font_mono=["ui-monospace", "Consolas", "monospace"],
|
||||
primary_hue=gr.themes.Color(
|
||||
c50="#EBFAF2",
|
||||
c100="#CFF3E1",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import gradio as gr
|
||||
from toolbox import get_conf
|
||||
|
||||
def define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAIL_THEMES, ADD_WAIFU, help_menu_description, js_code_for_toggle_darkmode):
|
||||
def define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAIL_THEMES, AVAIL_FONTS, ADD_WAIFU, help_menu_description, js_code_for_toggle_darkmode):
|
||||
with gr.Floating(init_x="0%", init_y="0%", visible=True, width=None, drag="forbidden", elem_id="tooltip"):
|
||||
with gr.Row():
|
||||
with gr.Tab("上传文件", elem_id="interact-panel"):
|
||||
@@ -9,12 +10,12 @@ def define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAI
|
||||
|
||||
with gr.Tab("更换模型", elem_id="interact-panel"):
|
||||
md_dropdown = gr.Dropdown(AVAIL_LLM_MODELS, value=LLM_MODEL, elem_id="elem_model_sel", label="更换LLM模型/请求源").style(container=False)
|
||||
top_p = gr.Slider(minimum=-0, maximum=1.0, value=1.0, step=0.01,interactive=True, label="Top-p (nucleus sampling)",)
|
||||
top_p = gr.Slider(minimum=-0, maximum=1.0, value=1.0, step=0.01,interactive=True, label="Top-p (nucleus sampling)", elem_id="elem_top_p")
|
||||
temperature = gr.Slider(minimum=-0, maximum=2.0, value=1.0, step=0.01, interactive=True, label="Temperature", elem_id="elem_temperature")
|
||||
max_length_sl = gr.Slider(minimum=256, maximum=1024*32, value=4096, step=128, interactive=True, label="Local LLM MaxLength",)
|
||||
max_length_sl = gr.Slider(minimum=256, maximum=1024*32, value=4096, step=128, interactive=True, label="Local LLM MaxLength", elem_id="elem_max_length_sl")
|
||||
system_prompt = gr.Textbox(show_label=True, lines=2, placeholder=f"System Prompt", label="System prompt", value=INIT_SYS_PROMPT, elem_id="elem_prompt")
|
||||
temperature.change(None, inputs=[temperature], outputs=None,
|
||||
_js="""(temperature)=>gpt_academic_gradio_saveload("save", "elem_prompt", "js_temperature_cookie", temperature)""")
|
||||
_js="""(temperature)=>gpt_academic_gradio_saveload("save", "elem_temperature", "js_temperature_cookie", temperature)""")
|
||||
system_prompt.change(None, inputs=[system_prompt], outputs=None,
|
||||
_js="""(system_prompt)=>gpt_academic_gradio_saveload("save", "elem_prompt", "js_system_prompt_cookie", system_prompt)""")
|
||||
md_dropdown.change(None, inputs=[md_dropdown], outputs=None,
|
||||
@@ -22,6 +23,8 @@ def define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAI
|
||||
|
||||
with gr.Tab("界面外观", elem_id="interact-panel"):
|
||||
theme_dropdown = gr.Dropdown(AVAIL_THEMES, value=THEME, label="更换UI主题").style(container=False)
|
||||
fontfamily_dropdown = gr.Dropdown(AVAIL_FONTS, value=get_conf("FONT"), elem_id="elem_fontfamily", label="更换字体类型").style(container=False)
|
||||
fontsize_slider = gr.Slider(minimum=5, maximum=25, value=15, step=1, interactive=True, label="字体大小(默认15)", elem_id="elem_fontsize")
|
||||
checkboxes = gr.CheckboxGroup(["基础功能区", "函数插件区", "浮动输入区", "输入清除键", "插件参数区"], value=["基础功能区", "函数插件区"], label="显示/隐藏功能区", elem_id='cbs').style(container=False)
|
||||
opt = ["自定义菜单"]
|
||||
value=[]
|
||||
@@ -31,7 +34,10 @@ def define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAI
|
||||
dark_mode_btn.click(None, None, None, _js=js_code_for_toggle_darkmode)
|
||||
open_new_tab = gr.Button("打开新对话", variant="secondary").style(size="sm")
|
||||
open_new_tab.click(None, None, None, _js=f"""()=>duplicate_in_new_window()""")
|
||||
|
||||
fontfamily_dropdown.select(None, inputs=[fontfamily_dropdown], outputs=None,
|
||||
_js="""(fontfamily)=>{gpt_academic_gradio_saveload("save", "elem_fontfamily", "js_fontfamily", fontfamily); gpt_academic_change_chatbot_font(fontfamily, null, null);}""")
|
||||
fontsize_slider.change(None, inputs=[fontsize_slider], outputs=None,
|
||||
_js="""(fontsize)=>{gpt_academic_gradio_saveload("save", "elem_fontsize", "js_fontsize", fontsize); gpt_academic_change_chatbot_font(null, fontsize, null);}""")
|
||||
|
||||
with gr.Tab("帮助", elem_id="interact-panel"):
|
||||
gr.Markdown(help_menu_description)
|
||||
|
||||
133
themes/init.js
133
themes/init.js
@@ -5,6 +5,129 @@ function remove_legacy_cookie() {
|
||||
}
|
||||
|
||||
|
||||
function processFontFamily(fontfamily) {
|
||||
// 检查是否包含括号
|
||||
if (fontfamily.includes('(')) {
|
||||
// 分割字符串
|
||||
const parts = fontfamily.split('(');
|
||||
const fontNamePart = parts[1].split(')')[0].trim(); // 获取括号内的部分
|
||||
|
||||
// 检查是否包含 @
|
||||
if (fontNamePart.includes('@')) {
|
||||
const [fontName, fontUrl] = fontNamePart.split('@').map(part => part.trim());
|
||||
return { fontName, fontUrl };
|
||||
} else {
|
||||
return { fontName: fontNamePart, fontUrl: null };
|
||||
}
|
||||
} else {
|
||||
return { fontName: fontfamily, fontUrl: null };
|
||||
}
|
||||
}
|
||||
|
||||
// 检查字体是否存在
|
||||
function checkFontAvailability(fontfamily) {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
// 设置两个不同的字体进行比较
|
||||
const testText = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
context.font = `16px ${fontfamily}, sans-serif`;
|
||||
const widthWithFont = context.measureText(testText).width;
|
||||
|
||||
context.font = '16px sans-serif';
|
||||
const widthWithFallback = context.measureText(testText).width;
|
||||
|
||||
// 如果宽度相同,说明字体不存在
|
||||
resolve(widthWithFont !== widthWithFallback);
|
||||
});
|
||||
}
|
||||
async function checkFontAvailabilityV2(fontfamily) {
|
||||
fontName = fontfamily;
|
||||
console.log('Checking font availability:', fontName);
|
||||
if ('queryLocalFonts' in window) {
|
||||
try {
|
||||
const fonts = await window.queryLocalFonts();
|
||||
const fontExists = fonts.some(font => font.family === fontName);
|
||||
console.log(`Local Font "${fontName}" exists:`, fontExists);
|
||||
return fontExists;
|
||||
} catch (error) {
|
||||
console.error('Error querying local fonts:', error);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
console.error('queryLocalFonts is not supported in this browser.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 动态加载字体
|
||||
function loadFont(fontfamily, fontUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 使用 Google Fonts 或其他字体来源
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = fontUrl;
|
||||
link.onload = () => {
|
||||
toast_push(`字体 "${fontfamily}" 已成功加载`, 3000);
|
||||
resolve();
|
||||
};
|
||||
link.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
}
|
||||
function gpt_academic_change_chatbot_font(fontfamily, fontsize, fontcolor) {
|
||||
const chatbot = document.querySelector('#gpt-chatbot');
|
||||
// 检查元素是否存在
|
||||
if (chatbot) {
|
||||
if (fontfamily != null) {
|
||||
// 更改字体
|
||||
const result = processFontFamily(fontfamily);
|
||||
if (result.fontName == "Theme-Default-Font") {
|
||||
chatbot.style.fontFamily = result.fontName;
|
||||
return;
|
||||
}
|
||||
// 检查字体是否存在
|
||||
checkFontAvailability(result.fontName).then((isAvailable) => {
|
||||
if (isAvailable) {
|
||||
// 如果字体存在,直接应用
|
||||
chatbot.style.fontFamily = result.fontName;
|
||||
} else {
|
||||
if (result.fontUrl == null) {
|
||||
// toast_push('无法加载字体,本地字体不存在,且URL未提供', 3000);
|
||||
// 直接把失效的字体放上去,让系统自动fallback
|
||||
chatbot.style.fontFamily = result.fontName;
|
||||
return;
|
||||
} else {
|
||||
toast_push('正在下载字体', 3000);
|
||||
// 如果字体不存在,尝试加载字体
|
||||
loadFont(result.fontName, result.fontUrl).then(() => {
|
||||
chatbot.style.fontFamily = result.fontName;
|
||||
}).catch((error) => {
|
||||
console.error(`无法加载字体 "${result.fontName}":`, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
if (fontsize != null) {
|
||||
// 修改字体大小
|
||||
document.documentElement.style.setProperty(
|
||||
'--gpt-academic-message-font-size',
|
||||
`${fontsize}px`
|
||||
);
|
||||
}
|
||||
if (fontcolor != null) {
|
||||
// 更改字体颜色
|
||||
chatbot.style.color = fontcolor;
|
||||
}
|
||||
} else {
|
||||
console.error('#gpt-chatbot is missing');
|
||||
}
|
||||
}
|
||||
|
||||
async function GptAcademicJavaScriptInit(dark, prompt, live2d, layout, tts) {
|
||||
// 第一部分,布局初始化
|
||||
remove_legacy_cookie();
|
||||
@@ -46,7 +169,7 @@ async function GptAcademicJavaScriptInit(dark, prompt, live2d, layout, tts) {
|
||||
}
|
||||
|
||||
// 自动朗读
|
||||
if (tts != "DISABLE"){
|
||||
if (tts != "DISABLE") {
|
||||
enable_tts = true;
|
||||
if (getCookie("js_auto_read_cookie")) {
|
||||
auto_read_tts = getCookie("js_auto_read_cookie")
|
||||
@@ -56,7 +179,11 @@ async function GptAcademicJavaScriptInit(dark, prompt, live2d, layout, tts) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 字体
|
||||
gpt_academic_gradio_saveload("load", "elem_fontfamily", "js_fontfamily", null, "str");
|
||||
gpt_academic_change_chatbot_font(getCookie("js_fontfamily"), null, null);
|
||||
gpt_academic_gradio_saveload("load", "elem_fontsize", "js_fontsize", null, "str");
|
||||
gpt_academic_change_chatbot_font(null, getCookie("js_fontsize"), null);
|
||||
// SysPrompt 系统静默提示词
|
||||
gpt_academic_gradio_saveload("load", "elem_prompt", "js_system_prompt_cookie", null, "str");
|
||||
// Temperature 大模型温度参数
|
||||
@@ -66,7 +193,7 @@ async function GptAcademicJavaScriptInit(dark, prompt, live2d, layout, tts) {
|
||||
const cached_model = getCookie("js_md_dropdown_cookie");
|
||||
var model_sel = await get_gradio_component("elem_model_sel");
|
||||
// determine whether the cached model is in the choices
|
||||
if (model_sel.props.choices.includes(cached_model)){
|
||||
if (model_sel.props.choices.includes(cached_model)) {
|
||||
// change dropdown
|
||||
gpt_academic_gradio_saveload("load", "elem_model_sel", "js_md_dropdown_cookie", null, "str");
|
||||
// 连锁修改chatbot的label
|
||||
|
||||
@@ -85,7 +85,7 @@ class WelcomeMessage {
|
||||
this.card_array = [];
|
||||
this.static_welcome_message_previous = [];
|
||||
this.reflesh_time_interval = 15 * 1000;
|
||||
|
||||
this.major_title = "欢迎使用GPT-Academic";
|
||||
|
||||
const reflesh_render_status = () => {
|
||||
for (let index = 0; index < this.card_array.length; index++) {
|
||||
@@ -99,6 +99,8 @@ class WelcomeMessage {
|
||||
|
||||
// call update when page size change, call this.update when page size change
|
||||
window.addEventListener('resize', this.update.bind(this));
|
||||
// add a loop to reflesh cards
|
||||
this.startRefleshCards();
|
||||
}
|
||||
|
||||
begin_render() {
|
||||
@@ -106,9 +108,12 @@ class WelcomeMessage {
|
||||
}
|
||||
|
||||
async startRefleshCards() {
|
||||
// sleep certain time
|
||||
await new Promise(r => setTimeout(r, this.reflesh_time_interval));
|
||||
await this.reflesh_cards();
|
||||
// checkout visible status
|
||||
if (this.visible) {
|
||||
// if visible, then reflesh cards
|
||||
await this.reflesh_cards();
|
||||
setTimeout(() => {
|
||||
this.startRefleshCards.call(this);
|
||||
}, 1);
|
||||
@@ -194,35 +199,38 @@ class WelcomeMessage {
|
||||
}
|
||||
|
||||
async update() {
|
||||
// console.log('update')
|
||||
// update the card visibility
|
||||
const elem_chatbot = document.getElementById('gpt-chatbot');
|
||||
const chatbot_top = elem_chatbot.getBoundingClientRect().top;
|
||||
const welcome_card_container = document.getElementsByClassName('welcome-card-container')[0];
|
||||
|
||||
// detect if welcome card overflow
|
||||
let welcome_card_overflow = false;
|
||||
if (welcome_card_container) {
|
||||
const welcome_card_top = welcome_card_container.getBoundingClientRect().top;
|
||||
if (welcome_card_top < chatbot_top) {
|
||||
welcome_card_overflow = true;
|
||||
// console.log("welcome_card_overflow");
|
||||
}
|
||||
}
|
||||
var page_width = document.documentElement.clientWidth;
|
||||
const width_to_hide_welcome = 1200;
|
||||
if (!await this.isChatbotEmpty() || page_width < width_to_hide_welcome || welcome_card_overflow) {
|
||||
// overflow !
|
||||
if (this.visible) {
|
||||
console.log("remove welcome");
|
||||
this.removeWelcome(); this.visible = false; // this two lines must always be together
|
||||
// console.log("remove welcome");
|
||||
this.removeWelcome();
|
||||
this.card_array = [];
|
||||
this.static_welcome_message_previous = [];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.visible) {
|
||||
// console.log("already visible");
|
||||
return;
|
||||
}
|
||||
console.log("show welcome");
|
||||
this.showWelcome(); this.visible = true; // this two lines must always be together
|
||||
this.startRefleshCards();
|
||||
// not overflow, not yet shown, then create and display welcome card
|
||||
// console.log("show welcome");
|
||||
this.showWelcome();
|
||||
}
|
||||
|
||||
showCard(message) {
|
||||
@@ -263,7 +271,7 @@ class WelcomeMessage {
|
||||
}
|
||||
|
||||
async showWelcome() {
|
||||
|
||||
this.visible = true;
|
||||
// 首先,找到想要添加子元素的父元素
|
||||
const elem_chatbot = document.getElementById('gpt-chatbot');
|
||||
|
||||
@@ -274,7 +282,7 @@ class WelcomeMessage {
|
||||
// 创建主标题
|
||||
const major_title = document.createElement('div');
|
||||
major_title.classList.add('welcome-title');
|
||||
major_title.textContent = "欢迎使用GPT-Academic";
|
||||
major_title.textContent = this.major_title;
|
||||
welcome_card_container.appendChild(major_title)
|
||||
|
||||
// 创建卡片
|
||||
@@ -297,16 +305,17 @@ class WelcomeMessage {
|
||||
}
|
||||
|
||||
async removeWelcome() {
|
||||
this.visible = false;
|
||||
// remove welcome-card-container
|
||||
const elem_chatbot = document.getElementById('gpt-chatbot');
|
||||
const welcome_card_container = document.getElementsByClassName('welcome-card-container')[0];
|
||||
// 添加隐藏动画
|
||||
// begin hide animation
|
||||
welcome_card_container.classList.add('hide');
|
||||
// 等待动画结束后再移除元素
|
||||
welcome_card_container.addEventListener('transitionend', () => {
|
||||
elem_chatbot.removeChild(welcome_card_container);
|
||||
}, { once: true });
|
||||
const timeout = 600; // 与CSS中transition的时间保持一致(1s)
|
||||
// add a fail safe timeout
|
||||
const timeout = 600; // 与 CSS 中 transition 的时间保持一致(1s)
|
||||
setTimeout(() => {
|
||||
if (welcome_card_container.parentNode) {
|
||||
elem_chatbot.removeChild(welcome_card_container);
|
||||
|
||||
在新工单中引用
屏蔽一个用户