diff --git a/embedding_adapters.py b/embedding_adapters.py index 2294de9..869dc1d 100644 --- a/embedding_adapters.py +++ b/embedding_adapters.py @@ -120,18 +120,59 @@ class OllamaEmbeddingAdapter(BaseEmbeddingAdapter): return [] class MLStudioEmbeddingAdapter(BaseEmbeddingAdapter): + """ + 基于 LM Studio 的 embedding 适配器 + """ def __init__(self, api_key: str, base_url: str, model_name: str): - self._embedding = OpenAIEmbeddings( - openai_api_key=api_key, - openai_api_base=ensure_openai_base_url_has_v1(base_url), - model=model_name - ) + self.url = ensure_openai_base_url_has_v1(base_url) + if not self.url.endswith('/embeddings'): + self.url = f"{self.url}/embeddings" + + self.headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + self.model_name = model_name def embed_documents(self, texts: List[str]) -> List[List[float]]: - return self._embedding.embed_documents(texts) + try: + payload = { + "input": texts, + "model": self.model_name + } + response = requests.post(self.url, json=payload, headers=self.headers) + response.raise_for_status() + result = response.json() + if "data" not in result: + logging.error(f"Invalid response format from LM Studio API: {result}") + return [[]] * len(texts) + return [item.get("embedding", []) for item in result["data"]] + except requests.exceptions.RequestException as e: + logging.error(f"LM Studio API request failed: {str(e)}") + return [[]] * len(texts) + except (KeyError, IndexError, ValueError, TypeError) as e: + logging.error(f"Error parsing LM Studio API response: {str(e)}") + return [[]] * len(texts) def embed_query(self, query: str) -> List[float]: - return self._embedding.embed_query(query) + try: + payload = { + "input": query, + "model": self.model_name + } + response = requests.post(self.url, json=payload, headers=self.headers) + response.raise_for_status() + result = response.json() + if "data" not in result or not result["data"]: + logging.error(f"Invalid response format from LM Studio API: {result}") + return [] + return result["data"][0].get("embedding", []) + except requests.exceptions.RequestException as e: + logging.error(f"LM Studio API request failed: {str(e)}") + return [] + except (KeyError, IndexError, ValueError, TypeError) as e: + logging.error(f"Error parsing LM Studio API response: {str(e)}") + return [] class GeminiEmbeddingAdapter(BaseEmbeddingAdapter): """ diff --git a/prompt_definitions.py b/prompt_definitions.py index c7173d5..54dac6c 100644 --- a/prompt_definitions.py +++ b/prompt_definitions.py @@ -152,6 +152,9 @@ knowledge_filter_prompt = """\ [情节燃料]→可用于时间压力类悬念 ❗ 地下氧气系统剩余23%储量(可制造生存危机) ▲ 与第三章提到的"永久生态循环系统"存在设定冲突 + +仅给出最终文本,不要解释任何内容。 +提示词:内容 """ # =============== 1. 核心种子设定(雪花第1层)=================== diff --git a/ui/role_library.py b/ui/role_library.py index 0f53d58..f441028 100644 --- a/ui/role_library.py +++ b/ui/role_library.py @@ -11,6 +11,8 @@ from utils import read_file, save_string_to_txt # 导入 utils 中的函数 from novel_generator.common import invoke_with_cleaning # 新增导入 from prompt_definitions import Character_Import_Prompt +DEFAULT_FONT = ("Microsoft YaHei", 12) + class RoleLibrary: def __init__(self, master, save_path, llm_adapter): # 新增llm_adapter参数 self.master = master @@ -85,13 +87,14 @@ class RoleLibrary: category_frame.pack(fill="x", padx=5, pady=5) # 分类选择标签 - ctk.CTkLabel(category_frame, text="分类选择").pack(side="left", padx=(0, 5)) + ctk.CTkLabel(category_frame, text="分类选择", font=DEFAULT_FONT).pack(side="left", padx=(0, 5)) # 分类选择框 self.category_combobox = ctk.CTkComboBox( category_frame, values=self._get_all_categories(), - width=200 + width=200, + font=DEFAULT_FONT ) self.category_combobox.pack(side="left", padx=0) @@ -100,7 +103,8 @@ class RoleLibrary: category_frame, text="保存分类", width=80, - command=self._move_to_category + command=self._move_to_category, + font=DEFAULT_FONT ) self.save_category_btn.pack(side="left", padx=(0, 5)) @@ -110,7 +114,8 @@ class RoleLibrary: text="打开文件夹", width=80, command=lambda: os.startfile( - os.path.join(self.save_path, self.category_combobox.get())) + os.path.join(self.save_path, self.category_combobox.get())), + font=DEFAULT_FONT ).pack(side="left", padx=0) # 角色名编辑行 @@ -118,14 +123,15 @@ class RoleLibrary: name_frame.pack(fill="x", padx=5, pady=5) # 角色名称标签 - ctk.CTkLabel(name_frame, text="角色名称").pack(side="left", padx=(0, 5)) + ctk.CTkLabel(name_frame, text="角色名称", font=DEFAULT_FONT).pack(side="left", padx=(0, 5)) self.role_name_var = tk.StringVar() self.role_name_entry = ctk.CTkEntry( name_frame, textvariable=self.role_name_var, placeholder_text="角色名称", - width=200 + width=200, + font=DEFAULT_FONT ) self.role_name_entry.pack(side="left", padx=0) @@ -133,14 +139,16 @@ class RoleLibrary: name_frame, text="修改", width=60, - command=self._rename_role_file + command=self._rename_role_file, + font=DEFAULT_FONT ).pack(side="left", padx=(0, 5)) ctk.CTkButton( name_frame, text="新增", width=60, - command=lambda: self._create_new_role("全部") + command=lambda: self._create_new_role("全部"), + font=DEFAULT_FONT ).pack(side="left", padx=0) # 属性编辑区(基础框架) @@ -153,11 +161,11 @@ class RoleLibrary: button_frame.pack(fill="x", padx=5, pady=5) ctk.CTkButton(button_frame, text="导入角色", - command=self.import_roles).pack(side="left", padx=5) + command=self.import_roles, font=DEFAULT_FONT).pack(side="left", padx=5) ctk.CTkButton(button_frame, text="删除", - command=self.delete_current_role).pack(side="left", padx=5) + command=self.delete_current_role, font=DEFAULT_FONT).pack(side="left", padx=5) ctk.CTkButton(button_frame, text="保存", - command=self.save_current_role).pack(side="left", padx=5) + command=self.save_current_role, font=DEFAULT_FONT).pack(side="left", padx=5) def _get_all_categories(self): """获取所有有效分类(包括动态更新)""" @@ -289,7 +297,7 @@ class RoleLibrary: right_panel.grid_columnconfigure(0, weight=1) # 创建初始可编辑文本框 - text_box = ctk.CTkTextbox(right_panel, wrap="word") + text_box = ctk.CTkTextbox(right_panel, wrap="word", font=DEFAULT_FONT) text_box.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) text_box.configure(state="normal") # 保持可编辑状态 @@ -305,7 +313,8 @@ class RoleLibrary: btn_frame, text="导入临时角色库", width=120, - command=lambda: self.confirm_import(import_window) + command=lambda: self.confirm_import(import_window), + font=DEFAULT_FONT ).pack(side="left", padx=10) # 分析文件按钮 @@ -313,7 +322,8 @@ class RoleLibrary: btn_frame, text="分析文件", width=100, - command=lambda: self.analyze_character_state(right_panel, left_panel) + command=lambda: self.analyze_character_state(right_panel, left_panel), + font=DEFAULT_FONT ).pack(side="left", padx=10) # 加载character_state.txt按钮 @@ -321,7 +331,8 @@ class RoleLibrary: btn_frame, text="加载character_state.txt", width=160, - command=lambda: self.load_default_character_state(right_panel) + command=lambda: self.load_default_character_state(right_panel), + font=DEFAULT_FONT ).pack(side="right", padx=10) # 从文件导入按钮 @@ -329,7 +340,8 @@ class RoleLibrary: btn_frame, text="从文件导入", width=100, - command=lambda: self.import_from_file(right_panel) + command=lambda: self.import_from_file(right_panel), + font=DEFAULT_FONT ).pack(side="right", padx=10) # 设置内容区权重 @@ -407,17 +419,18 @@ class RoleLibrary: # 勾选框 var = BooleanVar(value=True) - cb = ctk.CTkCheckBox(frame, text="", variable=var, width=20) + cb = ctk.CTkCheckBox(frame, text="", variable=var, width=20, font=DEFAULT_FONT) cb.pack(side="left", padx=5) # 角色名称 lbl = ctk.CTkLabel(frame, text=role_name, - font=("Microsoft YaHei", 11, "bold")) + font=("Microsoft YaHei", 12)) lbl.pack(side="left", padx=5) # 属性摘要 attrs = [f"{k}({len(v)})" for k,v in attributes.items()] summary = ctk.CTkLabel(frame, text=" | ".join(attrs), + font=("Microsoft YaHei", 12), text_color="gray") summary.pack(side="right", padx=10) @@ -430,9 +443,9 @@ class RoleLibrary: btn_frame = ctk.CTkFrame(scroll_frame) btn_frame.pack(fill="x", pady=5) ctk.CTkButton(btn_frame, text="全选", - command=lambda: self._toggle_all(True)).pack(side="left") + command=lambda: self._toggle_all(True), font=DEFAULT_FONT).pack(side="left") ctk.CTkButton(btn_frame, text="取消选择", - command=lambda: self._toggle_all(False)).pack(side="left") + command=lambda: self._toggle_all(False), font=DEFAULT_FONT).pack(side="left") def _parse_temp_role_file(self, file_path): """解析临时角色文件""" @@ -532,17 +545,18 @@ class RoleLibrary: # 勾选框 var = BooleanVar(value=True) - cb = ctk.CTkCheckBox(frame, text="", variable=var, width=20) + cb = ctk.CTkCheckBox(frame, text="", variable=var, width=20, font=DEFAULT_FONT) cb.pack(side="left", padx=5) # 角色名称标签 lbl = ctk.CTkLabel(frame, text=role['name'], - font=("Microsoft YaHei", 11, "bold")) + font=("Microsoft YaHei", 12)) lbl.pack(side="left", padx=5) # 属性摘要 attrs = [f"{k}({len(v)})" for k,v in role['attributes'].items()] summary = ctk.CTkLabel(frame, text=" | ".join(attrs), + font=("Microsoft YaHei", 12), text_color="gray") summary.pack(side="right", padx=10) @@ -556,9 +570,9 @@ class RoleLibrary: btn_frame.pack(fill="x", pady=5) ctk.CTkButton(btn_frame, text="全选", - command=lambda: self._toggle_all(True)).pack(side="left") + command=lambda: self._toggle_all(True), font=DEFAULT_FONT).pack(side="left") ctk.CTkButton(btn_frame, text="反选", - command=lambda: self._toggle_all(False)).pack(side="left") + command=lambda: self._toggle_all(False), font=DEFAULT_FONT).pack(side="left") def _toggle_all(self, select): """全选/反选操作""" @@ -636,7 +650,7 @@ class RoleLibrary: break if not text_box: - text_box = ctk.CTkTextbox(right_panel, wrap="word") + text_box = ctk.CTkTextbox(right_panel, wrap="word", font=DEFAULT_FONT) text_box.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) text_box.configure(state="normal") @@ -669,8 +683,8 @@ class RoleLibrary: error_window.grab_set() # 窗口内容 - ctk.CTkLabel(error_window, text="请至少选择一个角色").pack(padx=20, pady=10) - ctk.CTkButton(error_window, text="确定", command=error_window.destroy).pack(pady=10) + ctk.CTkLabel(error_window, text="请至少选择一个角色", font=DEFAULT_FONT).pack(padx=20, pady=10) + ctk.CTkButton(error_window, text="确定", command=error_window.destroy, font=DEFAULT_FONT).pack(pady=10) # 窗口居中 error_window.update_idletasks() @@ -990,11 +1004,12 @@ class RoleLibrary: # 操作提示 ctk.CTkLabel(category_frame, text="右键分类名即可重命名", - font=("Microsoft YaHei", 10), + font=DEFAULT_FONT, text_color="gray").pack(side="top", anchor="w", padx=5) # 固定按钮 ctk.CTkButton(category_frame, text="全部", width=50, + font=("Microsoft YaHei", 12), command=lambda: self.show_category("全部")).pack(side="left", padx=2) # 滚动分类区 @@ -1004,9 +1019,9 @@ class RoleLibrary: # 操作按钮 ctk.CTkButton(category_frame, text="新增", width=50, - command=self.add_category).pack(side="right", padx=2) + command=self.add_category, font=DEFAULT_FONT).pack(side="right", padx=2) ctk.CTkButton(category_frame, text="删除", width=50, - command=self.delete_category).pack(side="right", padx=2) + command=self.delete_category, font=DEFAULT_FONT).pack(side="right", padx=2) self.load_categories() @@ -1032,7 +1047,7 @@ class RoleLibrary: if os.path.isdir(os.path.join(self.save_path, d)) and d != "全部"] for category in categories: - btn = ctk.CTkButton(self.scroll_frame, text=category, width=80) + btn = ctk.CTkButton(self.scroll_frame, text=category, width=80, font=DEFAULT_FONT) btn.bind("", lambda e, c=category: self.show_category(c)) btn.bind("", lambda e, c=category: self.rename_category(c)) btn.pack(side="left", padx=2) @@ -1079,7 +1094,7 @@ class RoleLibrary: for cat in categories: var = tk.BooleanVar() - chk = ctk.CTkCheckBox(scroll_frame, text=cat, variable=var) + chk = ctk.CTkCheckBox(scroll_frame, text=cat, variable=var, font=DEFAULT_FONT) chk.pack(anchor="w") self.selected_del.append((cat, var)) @@ -1088,9 +1103,9 @@ class RoleLibrary: btn_frame.pack(fill="x", pady=5) ctk.CTkButton(btn_frame, text="删除选中", - command=lambda: self.confirm_delete(del_window)).pack(side="left", padx=5) + command=lambda: self.confirm_delete(del_window), font=DEFAULT_FONT).pack(side="left", padx=5) ctk.CTkButton(btn_frame, text="取消", - command=del_window.destroy).pack(side="right", padx=5) + command=del_window.destroy, font=DEFAULT_FONT).pack(side="right", padx=5) self.category_combobox.configure(values=self._get_all_categories()) self.category_combobox.set("全部") @@ -1119,7 +1134,7 @@ class RoleLibrary: y = self.window.winfo_y() + (self.window.winfo_height() - c_height) // 2 choice_window.geometry(f"+{x}+{y}") - ctk.CTkLabel(choice_window, text="请选择删除方式:").pack(pady=10) + ctk.CTkLabel(choice_window, text="请选择删除方式:", font=DEFAULT_FONT).pack(pady=10) btn_frame = ctk.CTkFrame(choice_window) btn_frame.pack(pady=10) @@ -1145,9 +1160,9 @@ class RoleLibrary: choice_window.destroy() ctk.CTkButton(btn_frame, text="全部删除", - command=lambda: perform_delete("all")).pack(side="left", padx=5) + command=lambda: perform_delete("all"), font=DEFAULT_FONT).pack(side="left", padx=5) ctk.CTkButton(btn_frame, text="移动角色", - command=lambda: perform_delete("move")).pack(side="left", padx=5) + command=lambda: perform_delete("move"), font=DEFAULT_FONT).pack(side="left", padx=5) def count_roles(self, categories): """统计角色数量""" @@ -1185,7 +1200,8 @@ class RoleLibrary: btn = ctk.CTkButton( self.role_list_frame, text=role_name, - command=lambda r=role_name: self.show_role(r) + command=lambda r=role_name: self.show_role(r), + font=DEFAULT_FONT ) btn.pack(fill="x", pady=2) except FileNotFoundError: @@ -1200,7 +1216,8 @@ class RoleLibrary: btn = ctk.CTkButton( self.role_list_frame, text=role_name, - command=lambda r=role_name: self.show_role(r) + command=lambda r=role_name: self.show_role(r), + font=DEFAULT_FONT ) btn.pack(fill="x", pady=2) except FileNotFoundError: @@ -1294,9 +1311,9 @@ class RoleLibrary: self._create_attribute_section(attr_name, items) except FileNotFoundError as e: - messagebox.showerror("错误", f"文件不存在:{str(e)}", parent=self.window) + messagebox.showerror("错误", f"文件不存在:{e}", parent=self.window) except Exception as e: - messagebox.showerror("错误", f"读取文件失败:{str(e)}", parent=self.window) + messagebox.showerror("错误", f"读取文件失败:{e}", parent=self.window) def _create_attribute_section(self, attr_name, items): """创建单个属性的编辑区域""" @@ -1309,7 +1326,7 @@ class RoleLibrary: attribute_block.grid_columnconfigure(1, weight=1) # 设置第二列权重 # 属性名称标签 - label = ctk.CTkLabel(attribute_block, text=attr_name) + label = ctk.CTkLabel(attribute_block, text=attr_name, font=DEFAULT_FONT) label.grid(row=0, column=0, sticky="w", padx=(5, 10), pady=2) # 第一个条目和“增加”按钮的容器 @@ -1318,7 +1335,7 @@ class RoleLibrary: first_item_frame.grid_columnconfigure(0, weight=1) # 第一个条目输入框 - first_entry = ctk.CTkEntry(first_item_frame) + first_entry = ctk.CTkEntry(first_item_frame, font=DEFAULT_FONT) first_entry.grid(row=0, column=0, sticky="ew", padx=(0, 5), ipadx=5, ipady=3) if items: first_entry.insert(0, items[0]) # 填充第一个条目的内容 @@ -1332,7 +1349,8 @@ class RoleLibrary: add_button_frame, text="+", width=30, - command=lambda: self._add_item(attr_name) + command=lambda: self._add_item(attr_name), + font=DEFAULT_FONT ) add_button.grid(row=0, column=0) @@ -1365,7 +1383,7 @@ class RoleLibrary: item_frame.grid_columnconfigure(0, weight=1) # 条目输入框 - new_entry = ctk.CTkEntry(item_frame) + new_entry = ctk.CTkEntry(item_frame, font=DEFAULT_FONT) new_entry.grid(row=0, column=0, sticky="ew", padx=(0, 5), ipadx=5, ipady=3) new_entry.insert(0, initial_text) # 设置初始文本 @@ -1377,7 +1395,8 @@ class RoleLibrary: del_button_frame, text="-", width=30, - command=lambda f=item_frame: self._remove_item(f, attr_name) + command=lambda f=item_frame: self._remove_item(f, attr_name), + font=DEFAULT_FONT ) del_button.grid(row=0, column=0) @@ -1468,14 +1487,16 @@ class RoleLibrary: content_frame.pack(fill="both", expand=True, padx=10, pady=10) # 顶部提示 - ctk.CTkLabel(content_frame, text=f"当前分类:{old_name}").pack(pady=(10, 5)) + ctk.CTkLabel(content_frame, text=f"当前分类:{old_name}", + font=DEFAULT_FONT).pack(pady=(10, 5)) # 输入框 input_frame = ctk.CTkFrame(content_frame) input_frame.pack(fill="x", pady=5) - ctk.CTkLabel(input_frame, text="新名称:").pack(side="left", padx=5) + ctk.CTkLabel(input_frame, text="新名称:", + font=DEFAULT_FONT).pack(side="left", padx=5) name_var = tk.StringVar() - name_entry = ctk.CTkEntry(input_frame, textvariable=name_var, width=150) + name_entry = ctk.CTkEntry(input_frame, textvariable=name_var, width=150, font=DEFAULT_FONT) name_entry.pack(side="left", padx=5) # 按钮区 @@ -1508,9 +1529,9 @@ class RoleLibrary: messagebox.showerror("错误", f"重命名失败:{str(e)}", parent=self.window) ctk.CTkButton(button_frame, text="确认", - command=confirm_rename).pack(side="left", padx=10) + command=confirm_rename, font=DEFAULT_FONT).pack(side="left", padx=10) ctk.CTkButton(button_frame, text="取消", - command=dialog.destroy).pack(side="right", padx=10) + command=dialog.destroy, font=DEFAULT_FONT).pack(side="right", padx=10) # 窗口居中 dialog.update_idletasks()