Add files via upload
增加代理设置选项; 优化新版本google-generativeai 不支持 Client 类问题; 优化批量生成章节界面,使用customtkinter统一设计风格;
This commit is contained in:
@@ -58,5 +58,15 @@
|
|||||||
"architecture_llm": "Gemini 2.5 Pro",
|
"architecture_llm": "Gemini 2.5 Pro",
|
||||||
"final_chapter_llm": "GPT 5",
|
"final_chapter_llm": "GPT 5",
|
||||||
"consistency_review_llm": "DeepSeek V3"
|
"consistency_review_llm": "DeepSeek V3"
|
||||||
|
},
|
||||||
|
"proxy_setting": {
|
||||||
|
"proxy_url": "127.0.0.1",
|
||||||
|
"proxy_port": "",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"webdav_config": {
|
||||||
|
"webdav_url": "",
|
||||||
|
"webdav_username": "",
|
||||||
|
"webdav_password": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+91
-8
@@ -8,14 +8,97 @@ from embedding_adapters import create_embedding_adapter
|
|||||||
|
|
||||||
|
|
||||||
def load_config(config_file: str) -> dict:
|
def load_config(config_file: str) -> dict:
|
||||||
"""从指定的 config_file 加载配置,若不存在则返回空字典。"""
|
"""从指定的 config_file 加载配置,若不存在则创建一个默认配置文件。"""
|
||||||
if os.path.exists(config_file):
|
|
||||||
try:
|
# PenBo 修改代码,增加配置文件不存在则创建一个默认配置文件
|
||||||
with open(config_file, 'r', encoding='utf-8') as f:
|
if not os.path.exists(config_file):
|
||||||
return json.load(f)
|
create_config(config_file)
|
||||||
except:
|
|
||||||
pass
|
try:
|
||||||
return {}
|
with open(config_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# PenBo 增加了创建默认配置文件函数
|
||||||
|
def create_config(config_file: str) -> dict:
|
||||||
|
"""创建一个创建默认配置文件。"""
|
||||||
|
config = {
|
||||||
|
"last_interface_format": "OpenAI",
|
||||||
|
"last_embedding_interface_format": "OpenAI",
|
||||||
|
"llm_configs": {
|
||||||
|
"DeepSeek V3": {
|
||||||
|
"api_key": "",
|
||||||
|
"base_url": "https://api.deepseek.com/v1",
|
||||||
|
"model_name": "deepseek-chat",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 8192,
|
||||||
|
"timeout": 600,
|
||||||
|
"interface_format": "OpenAI"
|
||||||
|
},
|
||||||
|
"GPT 5": {
|
||||||
|
"api_key": "",
|
||||||
|
"base_url": "https://api.openai.com/v1",
|
||||||
|
"model_name": "gpt-5",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 32768,
|
||||||
|
"timeout": 600,
|
||||||
|
"interface_format": "OpenAI"
|
||||||
|
},
|
||||||
|
"Gemini 2.5 Pro": {
|
||||||
|
"api_key": "",
|
||||||
|
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
|
||||||
|
"model_name": "gemini-2.5-pro",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 32768,
|
||||||
|
"timeout": 600,
|
||||||
|
"interface_format": "OpenAI"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"embedding_configs": {
|
||||||
|
"OpenAI": {
|
||||||
|
"api_key": "",
|
||||||
|
"base_url": "https://api.openai.com/v1",
|
||||||
|
"model_name": "text-embedding-ada-002",
|
||||||
|
"retrieval_k": 4,
|
||||||
|
"interface_format": "OpenAI"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"other_params": {
|
||||||
|
"topic": "",
|
||||||
|
"genre": "",
|
||||||
|
"num_chapters": 0,
|
||||||
|
"word_number": 0,
|
||||||
|
"filepath": "",
|
||||||
|
"chapter_num": "120",
|
||||||
|
"user_guidance": "",
|
||||||
|
"characters_involved": "",
|
||||||
|
"key_items": "",
|
||||||
|
"scene_location": "",
|
||||||
|
"time_constraint": ""
|
||||||
|
},
|
||||||
|
"choose_configs": {
|
||||||
|
"prompt_draft_llm": "DeepSeek V3",
|
||||||
|
"chapter_outline_llm": "DeepSeek V3",
|
||||||
|
"architecture_llm": "Gemini 2.5 Pro",
|
||||||
|
"final_chapter_llm": "GPT 5",
|
||||||
|
"consistency_review_llm": "DeepSeek V3"
|
||||||
|
},
|
||||||
|
"proxy_setting": {
|
||||||
|
"proxy_url": "127.0.0.1",
|
||||||
|
"proxy_port": "",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"webdav_config": {
|
||||||
|
"webdav_url": "",
|
||||||
|
"webdav_username": "",
|
||||||
|
"webdav_password": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
save_config(config, config_file)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(config_data: dict, config_file: str) -> bool:
|
def save_config(config_data: dict, config_file: str) -> bool:
|
||||||
"""将 config_data 保存到 config_file 中,返回 True/False 表示是否成功。"""
|
"""将 config_data 保存到 config_file 中,返回 True/False 表示是否成功。"""
|
||||||
|
|||||||
+19
-10
@@ -100,26 +100,35 @@ class GeminiAdapter(BaseLLMAdapter):
|
|||||||
"""
|
"""
|
||||||
适配 Google Gemini (Google Generative AI) 接口
|
适配 Google Gemini (Google Generative AI) 接口
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# PenBo 修复新版本google-generativeai 不支持 Client 类问题;而是使用 GenerativeModel 类来访问API
|
||||||
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7, timeout: Optional[int] = 600):
|
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7, timeout: Optional[int] = 600):
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
self.max_tokens = max_tokens
|
self.max_tokens = max_tokens
|
||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
# gemini超时时间是毫秒
|
self.timeout = timeout
|
||||||
self.timeout = timeout * 1000
|
|
||||||
|
|
||||||
self._client = genai.Client(api_key=self.api_key,http_options=types.HttpOptions(base_url=base_url,timeout=self.timeout))
|
# 配置API密钥
|
||||||
|
genai.configure(api_key=self.api_key)
|
||||||
|
|
||||||
|
# 创建生成模型实例
|
||||||
|
self._model = genai.GenerativeModel(model_name=self.model_name)
|
||||||
|
|
||||||
def invoke(self, prompt: str) -> str:
|
def invoke(self, prompt: str) -> str:
|
||||||
try:
|
try:
|
||||||
response = self._client.models.generate_content(
|
# 设置生成配置
|
||||||
model = self.model_name,
|
generation_config = genai.types.GenerationConfig(
|
||||||
contents = prompt,
|
max_output_tokens=self.max_tokens,
|
||||||
config = types.GenerateContentConfig(
|
temperature=self.temperature,
|
||||||
max_output_tokens=self.max_tokens,
|
|
||||||
temperature=self.temperature,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 生成内容
|
||||||
|
response = self._model.generate_content(
|
||||||
|
prompt,
|
||||||
|
generation_config=generation_config
|
||||||
|
)
|
||||||
|
|
||||||
if response and response.text:
|
if response and response.text:
|
||||||
return response.text
|
return response.text
|
||||||
else:
|
else:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+85
-1
@@ -9,6 +9,8 @@ import customtkinter as ctk
|
|||||||
from config_manager import load_config, save_config
|
from config_manager import load_config, save_config
|
||||||
from tooltips import tooltips
|
from tooltips import tooltips
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def create_label_with_help(self, parent, label_text, tooltip_key, row, column,
|
def create_label_with_help(self, parent, label_text, tooltip_key, row, column,
|
||||||
font=None, sticky="e", padx=5, pady=5):
|
font=None, sticky="e", padx=5, pady=5):
|
||||||
@@ -45,11 +47,17 @@ def build_config_tabview(self):
|
|||||||
self.embeddings_config_tab = self.config_tabview.add("Embedding settings")
|
self.embeddings_config_tab = self.config_tabview.add("Embedding settings")
|
||||||
self.config_choose = self.config_tabview.add("Config choose")
|
self.config_choose = self.config_tabview.add("Config choose")
|
||||||
|
|
||||||
|
# PenBo 增加代理功能支持
|
||||||
|
self.proxy_setting_tab = self.config_tabview.add("Proxy setting")
|
||||||
|
|
||||||
|
|
||||||
build_ai_config_tab(self)
|
build_ai_config_tab(self)
|
||||||
build_embeddings_config_tab(self)
|
build_embeddings_config_tab(self)
|
||||||
build_config_choose_tab(self)
|
build_config_choose_tab(self)
|
||||||
|
|
||||||
|
# PenBo 增加代理功能支持
|
||||||
|
build_proxy_setting_tab(self)
|
||||||
|
|
||||||
def build_ai_config_tab(self):
|
def build_ai_config_tab(self):
|
||||||
def refresh_config_dropdown():
|
def refresh_config_dropdown():
|
||||||
"""刷新配置下拉菜单"""
|
"""刷新配置下拉菜单"""
|
||||||
@@ -607,11 +615,87 @@ def build_config_choose_tab(self):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# PenBo 增加代理功能支持
|
||||||
|
def build_proxy_setting_tab(self):
|
||||||
|
# 代理设置标签页布局
|
||||||
|
for i in range(5):
|
||||||
|
self.proxy_setting_tab.grid_rowconfigure(i, weight=0)
|
||||||
|
self.proxy_setting_tab.grid_columnconfigure(0, weight=0)
|
||||||
|
self.proxy_setting_tab.grid_columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# 从配置文件加载代理设置
|
||||||
|
config_data = load_config(self.config_file)
|
||||||
|
proxy_setting = config_data.get("proxy_setting", {})
|
||||||
|
|
||||||
|
# 代理启用开关
|
||||||
|
create_label_with_help(self, self.proxy_setting_tab, "启用代理:", "proxy_enabled", 0, 0)
|
||||||
|
self.proxy_enabled_var = ctk.BooleanVar(value=proxy_setting.get("enabled", False))
|
||||||
|
proxy_enabled_switch = ctk.CTkSwitch(
|
||||||
|
self.proxy_setting_tab,
|
||||||
|
text="",
|
||||||
|
variable=self.proxy_enabled_var,
|
||||||
|
onvalue=True,
|
||||||
|
offvalue=False,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
proxy_enabled_switch.grid(row=0, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 地址输入框
|
||||||
|
create_label_with_help(self, self.proxy_setting_tab, "地址:", "proxy_address", 1, 0)
|
||||||
|
self.proxy_address_var = ctk.StringVar(value=proxy_setting.get("proxy_url", "127.0.0.1"))
|
||||||
|
proxy_address_entry = ctk.CTkEntry(
|
||||||
|
self.proxy_setting_tab,
|
||||||
|
textvariable=self.proxy_address_var,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
proxy_address_entry.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
|
# 端口输入框
|
||||||
|
create_label_with_help(self, self.proxy_setting_tab, "端口:", "proxy_port", 2, 0)
|
||||||
|
self.proxy_port_var = ctk.StringVar(value=proxy_setting.get("proxy_port", "10809"))
|
||||||
|
proxy_port_entry = ctk.CTkEntry(
|
||||||
|
self.proxy_setting_tab,
|
||||||
|
textvariable=self.proxy_port_var,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
proxy_port_entry.grid(row=2, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
|
|
||||||
|
def open_proxy(address, port):
|
||||||
|
"""启动代理"""
|
||||||
|
# 设置环境变量
|
||||||
|
os.environ['HTTP_PROXY'] = f"http://{address}:{port}"
|
||||||
|
os.environ['HTTPS_PROXY'] = f"http://{address}:{port}"
|
||||||
|
|
||||||
|
def save_proxy_setting():
|
||||||
|
config_data = load_config(self.config_file)
|
||||||
|
if "proxy_setting" not in config_data:
|
||||||
|
config_data["proxy_setting"] = {}
|
||||||
|
|
||||||
|
config_data["proxy_setting"]["enabled"] = self.proxy_enabled_var.get()
|
||||||
|
config_data["proxy_setting"]["proxy_url"] = self.proxy_address_var.get()
|
||||||
|
config_data["proxy_setting"]["proxy_port"] = self.proxy_port_var.get()
|
||||||
|
|
||||||
|
save_config(config_data, self.config_file)
|
||||||
|
messagebox.showinfo("提示", "代理配置已保存。")
|
||||||
|
|
||||||
|
if self.proxy_enabled_var.get():
|
||||||
|
open_proxy(self.proxy_address_var.get(), self.proxy_port_var.get())
|
||||||
|
else:
|
||||||
|
os.environ.pop('HTTP_PROXY', None)
|
||||||
|
os.environ.pop('HTTPS_PROXY', None)
|
||||||
|
|
||||||
|
# 添加保存按钮
|
||||||
|
save_btn = ctk.CTkButton(
|
||||||
|
self.proxy_setting_tab,
|
||||||
|
text="保存代理设置",
|
||||||
|
command=save_proxy_setting,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
save_btn.grid(row=3, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_config_btn(self):
|
def load_config_btn(self):
|
||||||
cfg = load_config(self.config_file)
|
cfg = load_config(self.config_file)
|
||||||
|
|||||||
+48
-19
@@ -445,38 +445,58 @@ def do_consistency_check(self):
|
|||||||
self.enable_button_safe(self.btn_check_consistency)
|
self.enable_button_safe(self.btn_check_consistency)
|
||||||
threading.Thread(target=task, daemon=True).start()
|
threading.Thread(target=task, daemon=True).start()
|
||||||
def generate_batch_ui(self):
|
def generate_batch_ui(self):
|
||||||
|
|
||||||
|
# PenBo 优化界面,使用customtkinter进行批量生成章节界面
|
||||||
def open_batch_dialog():
|
def open_batch_dialog():
|
||||||
dialog = tk.Toplevel()
|
dialog = ctk.CTkToplevel()
|
||||||
|
dialog.title("批量生成章节")
|
||||||
|
|
||||||
chapter_file = os.path.join(self.filepath_var.get().strip(), "chapters")
|
chapter_file = os.path.join(self.filepath_var.get().strip(), "chapters")
|
||||||
files = glob.glob(os.path.join(chapter_file, "chapter_*.txt"))
|
files = glob.glob(os.path.join(chapter_file, "chapter_*.txt"))
|
||||||
if not files:
|
if not files:
|
||||||
num = 1
|
num = 1
|
||||||
else:
|
else:
|
||||||
num = max(int(os.path.basename(f).split('_')[1].split('.')[0]) for f in files) + 1
|
num = max(int(os.path.basename(f).split('_')[1].split('.')[0]) for f in files) + 1
|
||||||
dialog.geometry("+500+400")
|
|
||||||
tk.Label(dialog, text="起始章节").grid(row=0, column=0)
|
dialog.geometry("400x200")
|
||||||
entry_start = tk.Entry(dialog)
|
dialog.resizable(False, False)
|
||||||
entry_start.grid(row=0, column=1)
|
|
||||||
|
# 创建网格布局
|
||||||
|
dialog.grid_columnconfigure(0, weight=0)
|
||||||
|
dialog.grid_columnconfigure(1, weight=1)
|
||||||
|
dialog.grid_columnconfigure(2, weight=0)
|
||||||
|
dialog.grid_columnconfigure(3, weight=1)
|
||||||
|
|
||||||
|
# 起始章节
|
||||||
|
ctk.CTkLabel(dialog, text="起始章节:").grid(row=0, column=0, padx=10, pady=10, sticky="w")
|
||||||
|
entry_start = ctk.CTkEntry(dialog)
|
||||||
|
entry_start.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
|
||||||
entry_start.insert(0, str(num))
|
entry_start.insert(0, str(num))
|
||||||
tk.Label(dialog, text="结束章节").grid(row=0, column=2)
|
|
||||||
entry_end = tk.Entry(dialog)
|
# 结束章节
|
||||||
entry_end.grid(row=0, column=3)
|
ctk.CTkLabel(dialog, text="结束章节:").grid(row=0, column=2, padx=10, pady=10, sticky="w")
|
||||||
tk.Label(dialog, text="期望字数").grid(row=1, column=0)
|
entry_end = ctk.CTkEntry(dialog)
|
||||||
entry_word = tk.Entry(dialog)
|
entry_end.grid(row=0, column=3, padx=10, pady=10, sticky="ew")
|
||||||
entry_word.grid(row=1, column=1)
|
|
||||||
|
# 期望字数
|
||||||
|
ctk.CTkLabel(dialog, text="期望字数:").grid(row=1, column=0, padx=10, pady=10, sticky="w")
|
||||||
|
entry_word = ctk.CTkEntry(dialog)
|
||||||
|
entry_word.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
|
||||||
entry_word.insert(0, self.word_number_var.get())
|
entry_word.insert(0, self.word_number_var.get())
|
||||||
tk.Label(dialog, text="最低字数").grid(row=1, column=2)
|
|
||||||
entry_min = tk.Entry(dialog)
|
# 最低字数
|
||||||
entry_min.grid(row=1, column=3)
|
ctk.CTkLabel(dialog, text="最低字数:").grid(row=1, column=2, padx=10, pady=10, sticky="w")
|
||||||
|
entry_min = ctk.CTkEntry(dialog)
|
||||||
|
entry_min.grid(row=1, column=3, padx=10, pady=10, sticky="ew")
|
||||||
entry_min.insert(0, self.word_number_var.get())
|
entry_min.insert(0, self.word_number_var.get())
|
||||||
|
|
||||||
auto_enrich_bool = tk.BooleanVar()
|
# 自动扩写选项
|
||||||
auto_enrich_bool_ck = tk.Checkbutton(dialog, text="低于最低字数时自动扩写", variable=auto_enrich_bool)
|
auto_enrich_bool = ctk.BooleanVar()
|
||||||
auto_enrich_bool_ck.grid(row=2, column=0)
|
auto_enrich_bool_ck = ctk.CTkCheckBox(dialog, text="低于最低字数时自动扩写", variable=auto_enrich_bool)
|
||||||
|
auto_enrich_bool_ck.grid(row=2, column=0, columnspan=2, padx=10, pady=10, sticky="w")
|
||||||
|
|
||||||
result = {"start": None, "end": None, "word": None, "min": None, "auto_enrich": None, "close": False}
|
result = {"start": None, "end": None, "word": None, "min": None, "auto_enrich": None, "close": False}
|
||||||
|
|
||||||
|
|
||||||
def on_confirm():
|
def on_confirm():
|
||||||
nonlocal result
|
nonlocal result
|
||||||
if not entry_start.get() or not entry_end.get() or not entry_word.get() or not entry_min.get():
|
if not entry_start.get() or not entry_end.get() or not entry_word.get() or not entry_min.get():
|
||||||
@@ -497,7 +517,16 @@ def generate_batch_ui(self):
|
|||||||
nonlocal result
|
nonlocal result
|
||||||
result["close"] = True
|
result["close"] = True
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
tk.Button(dialog, text="确认", command=on_confirm).grid(row=2, column=1)
|
|
||||||
|
# 按钮框架
|
||||||
|
button_frame = ctk.CTkFrame(dialog)
|
||||||
|
button_frame.grid(row=3, column=0, columnspan=4, padx=10, pady=10, sticky="ew")
|
||||||
|
button_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
button_frame.grid_columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
ctk.CTkButton(button_frame, text="确认", command=on_confirm).grid(row=0, column=0, padx=10, pady=10, sticky="e")
|
||||||
|
ctk.CTkButton(button_frame, text="取消", command=on_cancel).grid(row=0, column=1, padx=10, pady=10, sticky="w")
|
||||||
|
|
||||||
dialog.protocol("WM_DELETE_WINDOW", on_cancel)
|
dialog.protocol("WM_DELETE_WINDOW", on_cancel)
|
||||||
dialog.transient(self.master)
|
dialog.transient(self.master)
|
||||||
dialog.grab_set()
|
dialog.grab_set()
|
||||||
|
|||||||
@@ -88,6 +88,18 @@ class NovelGeneratorGUI:
|
|||||||
"retrieval_k": 4
|
"retrieval_k": 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# PenBo 增加代理功能支持
|
||||||
|
proxy_url = self.loaded_config["proxy_setting"]["proxy_url"]
|
||||||
|
proxy_port = self.loaded_config["proxy_setting"]["proxy_port"]
|
||||||
|
if self.loaded_config["proxy_setting"]["enabled"]:
|
||||||
|
os.environ['HTTP_PROXY'] = f"http://{proxy_url}:{proxy_port}"
|
||||||
|
os.environ['HTTPS_PROXY'] = f"http://{proxy_url}:{proxy_port}"
|
||||||
|
else:
|
||||||
|
os.environ.pop('HTTP_PROXY', None)
|
||||||
|
os.environ.pop('HTTPS_PROXY', None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -- LLM通用参数 --
|
# -- LLM通用参数 --
|
||||||
# self.llm_conf_name = next(iter(self.loaded_config["llm_configs"]))
|
# self.llm_conf_name = next(iter(self.loaded_config["llm_configs"]))
|
||||||
self.api_key_var = ctk.StringVar(value=llm_conf.get("api_key", ""))
|
self.api_key_var = ctk.StringVar(value=llm_conf.get("api_key", ""))
|
||||||
|
|||||||
Reference in New Issue
Block a user