Merge pull request #185 from PenBo1/main

Add files via upload
This commit is contained in:
xianyun
2025-09-08 19:57:37 +08:00
committed by GitHub
29 changed files with 265 additions and 38 deletions
+10
View File
@@ -58,5 +58,15 @@
"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": ""
}
}
+91 -8
View File
@@ -8,14 +8,97 @@ from embedding_adapters import create_embedding_adapter
def load_config(config_file: str) -> dict:
"""从指定的 config_file 加载配置,若不存在则返回空字典"""
if os.path.exists(config_file):
try:
with open(config_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {}
"""从指定的 config_file 加载配置,若不存在则创建一个默认配置文件"""
# PenBo 修改代码,增加配置文件不存在则创建一个默认配置文件
if not os.path.exists(config_file):
create_config(config_file)
try:
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:
"""将 config_data 保存到 config_file 中,返回 True/False 表示是否成功。"""
+19 -10
View File
@@ -100,26 +100,35 @@ class GeminiAdapter(BaseLLMAdapter):
"""
适配 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):
self.api_key = api_key
self.model_name = model_name
self.max_tokens = max_tokens
self.temperature = temperature
# gemini超时时间是毫秒
self.timeout = timeout * 1000
self.timeout = timeout
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:
try:
response = self._client.models.generate_content(
model = self.model_name,
contents = prompt,
config = types.GenerateContentConfig(
max_output_tokens=self.max_tokens,
temperature=self.temperature,
),
# 设置生成配置
generation_config = genai.types.GenerationConfig(
max_output_tokens=self.max_tokens,
temperature=self.temperature,
)
# 生成内容
response = self._model.generate_content(
prompt,
generation_config=generation_config
)
if response and response.text:
return response.text
else:
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
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
View File
@@ -9,6 +9,8 @@ import customtkinter as ctk
from config_manager import load_config, save_config
from tooltips import tooltips
import os
def create_label_with_help(self, parent, label_text, tooltip_key, row, column,
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.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_embeddings_config_tab(self)
build_config_choose_tab(self)
# PenBo 增加代理功能支持
build_proxy_setting_tab(self)
def build_ai_config_tab(self):
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):
cfg = load_config(self.config_file)
+48 -19
View File
@@ -445,38 +445,58 @@ def do_consistency_check(self):
self.enable_button_safe(self.btn_check_consistency)
threading.Thread(target=task, daemon=True).start()
def generate_batch_ui(self):
# PenBo 优化界面,使用customtkinter进行批量生成章节界面
def open_batch_dialog():
dialog = tk.Toplevel()
dialog = ctk.CTkToplevel()
dialog.title("批量生成章节")
chapter_file = os.path.join(self.filepath_var.get().strip(), "chapters")
files = glob.glob(os.path.join(chapter_file, "chapter_*.txt"))
if not files:
num = 1
else:
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)
entry_start = tk.Entry(dialog)
entry_start.grid(row=0, column=1)
dialog.geometry("400x200")
dialog.resizable(False, False)
# 创建网格布局
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))
tk.Label(dialog, text="结束章节").grid(row=0, column=2)
entry_end = tk.Entry(dialog)
entry_end.grid(row=0, column=3)
tk.Label(dialog, text="期望字数").grid(row=1, column=0)
entry_word = tk.Entry(dialog)
entry_word.grid(row=1, column=1)
# 结束章节
ctk.CTkLabel(dialog, text="结束章节:").grid(row=0, column=2, padx=10, pady=10, sticky="w")
entry_end = ctk.CTkEntry(dialog)
entry_end.grid(row=0, column=3, padx=10, pady=10, sticky="ew")
# 期望字数
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())
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())
auto_enrich_bool = tk.BooleanVar()
auto_enrich_bool_ck = tk.Checkbutton(dialog, text="低于最低字数时自动扩写", variable=auto_enrich_bool)
auto_enrich_bool_ck.grid(row=2, column=0)
# 自动扩写选项
auto_enrich_bool = ctk.BooleanVar()
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}
def on_confirm():
nonlocal result
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
result["close"] = True
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.transient(self.master)
dialog.grab_set()
+12
View File
@@ -88,6 +88,18 @@ class NovelGeneratorGUI:
"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通用参数 --
# self.llm_conf_name = next(iter(self.loaded_config["llm_configs"]))
self.api_key_var = ctk.StringVar(value=llm_conf.get("api_key", ""))