diff --git a/novel_generator.py b/novel_generator.py index 381055d..d3d322e 100644 --- a/novel_generator.py +++ b/novel_generator.py @@ -414,7 +414,8 @@ def summarize_recent_chapters( model_name: str, temperature: float, max_tokens: int, - chapters_text_list: List[str] + chapters_text_list: List[str], + timeout: int = 600 ) -> Tuple[str, str]: """ 生成 (short_summary, next_chapter_keywords) @@ -430,7 +431,8 @@ def summarize_recent_chapters( model_name=model_name, api_key=api_key, temperature=temperature, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout ) prompt = summarize_recent_chapters_prompt.format(combined_text=combined_text) @@ -572,7 +574,8 @@ def Chapter_blueprint_generate( filepath: str, number_of_chapters: int, temperature: float = 0.7, - max_tokens: int = 2048 + max_tokens: int = 4096, + timeout: int = 600 ) -> None: """ 如果章节数小于等于 chunk_size,则直接使用 chapter_blueprint_prompt 一次性生成。 @@ -605,7 +608,8 @@ def Chapter_blueprint_generate( model_name=llm_model, api_key=api_key, temperature=temperature, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout ) # 计算分块大小 @@ -702,7 +706,8 @@ def generate_chapter_draft( embedding_model_name: str, embedding_retrieval_k: int = 2, interface_format: str = "openai", - max_tokens: int = 2048 + max_tokens: int = 2048, + timeout: int = 600 ) -> str: """ 根据 novel_number 判断是否为第一章。 @@ -775,7 +780,8 @@ def generate_chapter_draft( model_name=model_name, temperature=temperature, max_tokens=max_tokens, - chapters_text_list=recent_3_texts + chapters_text_list=recent_3_texts, + timeout=timeout ) # 从最近章节中获取最后一段内容作为前章结尾 @@ -836,7 +842,8 @@ def generate_chapter_draft( model_name=model_name, api_key=api_key, temperature=temperature, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout ) chapter_content = invoke_with_cleaning(llm_adapter, prompt_text) if not chapter_content.strip(): @@ -871,7 +878,8 @@ def finalize_chapter( embedding_interface_format: str, embedding_model_name: str, interface_format: str, - max_tokens: int + max_tokens: int, + timeout: int = 600 ): progress = load_progress() if novel_number in progress.get("chapters_finalized", []): @@ -902,7 +910,8 @@ def finalize_chapter( model_name=model_name, api_key=api_key, temperature=temperature, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout ) prompt_summary = summary_prompt.format( chapter_text=chapter_text, diff --git a/ui.py b/ui.py index cce0570..35bdd16 100644 --- a/ui.py +++ b/ui.py @@ -105,6 +105,8 @@ class NovelGeneratorGUI: self.model_name_var = ctk.StringVar(value=self.loaded_config.get("model_name", "gpt-4o-mini")) self.temperature_var = ctk.DoubleVar(value=self.loaded_config.get("temperature", 0.7)) self.max_tokens_var = ctk.IntVar(value=self.loaded_config.get("max_tokens", 8192)) + # === New: Timeout === + self.timeout_var = ctk.IntVar(value=self.loaded_config.get("timeout", 600)) # Embedding相关 self.embedding_interface_format_var = ctk.StringVar(value=self.loaded_config.get("embedding_interface_format", "OpenAI")) @@ -295,7 +297,7 @@ class NovelGeneratorGUI: elif new_value == "DeepSeek": self.base_url_var.set("https://api.deepseek.com/v1") - for i in range(6): + for i in range(7): self.ai_config_tab.grid_rowconfigure(i, weight=0) self.ai_config_tab.grid_columnconfigure(0, weight=0) self.ai_config_tab.grid_columnconfigure(1, weight=1) @@ -415,6 +417,39 @@ class NovelGeneratorGUI: ) self.max_tokens_value_label.grid(row=5, column=2, padx=5, pady=5, sticky="w") + # 7) Timeout (sec) + # === MODIFIED: 使用Slider替换Entry === + self.create_label_with_help( + parent=self.ai_config_tab, + label_text="Timeout (sec):", + tooltip_key="timeout", + row=6, + column=0, + font=("Microsoft YaHei", 12) + ) + + def update_timeout_label(value): + integer_val = int(float(value)) + self.timeout_value_label.configure(text=str(integer_val)) + + timeout_slider = ctk.CTkSlider( + self.ai_config_tab, + from_=0, + to=3600, # 设定一个合理上限,例如1小时 + number_of_steps=3600, + command=update_timeout_label, + variable=self.timeout_var + ) + timeout_slider.grid(row=6, column=1, padx=5, pady=5, sticky="we") + + self.timeout_value_label = ctk.CTkLabel( + self.ai_config_tab, + text=str(self.timeout_var.get()), + font=("Microsoft YaHei", 12) + ) + self.timeout_value_label.grid(row=6, column=2, padx=5, pady=5, sticky="w") + # === MODIFIED END === + def build_embeddings_config_tab(self): def on_embedding_interface_changed(new_value): if new_value == "Ollama": @@ -718,6 +753,7 @@ class NovelGeneratorGUI: self.model_name_var.set(cfg.get("model_name", "")) self.temperature_var.set(cfg.get("temperature", 0.7)) self.max_tokens_var.set(cfg.get("max_tokens", 2048)) + self.timeout_var.set(cfg.get("timeout", 600)) self.embedding_api_key_var.set(cfg.get("embedding_api_key", "")) self.embedding_interface_format_var.set(cfg.get("embedding_interface_format", "OpenAI")) @@ -746,13 +782,12 @@ class NovelGeneratorGUI: "model_name": self.model_name_var.get(), "temperature": self.temperature_var.get(), "max_tokens": self.max_tokens_var.get(), - + "timeout": self.safe_get_int(self.timeout_var, 600), "embedding_api_key": self.embedding_api_key_var.get(), "embedding_interface_format": self.embedding_interface_format_var.get(), "embedding_url": self.embedding_url_var.get(), "embedding_model_name": self.embedding_model_name_var.get(), "embedding_retrieval_k": self.safe_get_int(self.embedding_retrieval_k_var, 4), - "topic": self.topic_text.get("0.0", "end").strip(), "genre": self.genre_var.get(), "num_chapters": self.safe_get_int(self.num_chapters_var, 10), @@ -806,6 +841,7 @@ class NovelGeneratorGUI: model_name = self.model_name_var.get().strip() temperature = self.temperature_var.get() max_tokens = self.max_tokens_var.get() + timeout_val = self.safe_get_int(self.timeout_var, 600) topic = self.topic_text.get("0.0", "end").strip() genre = self.genre_var.get().strip() @@ -824,7 +860,8 @@ class NovelGeneratorGUI: word_number=word_number, filepath=filepath, temperature=temperature, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout_val ) self.safe_log("✅ 小说架构生成完成。请在 'Novel Architecture' 标签页查看或编辑。") except Exception: @@ -851,6 +888,7 @@ class NovelGeneratorGUI: number_of_chapters = self.safe_get_int(self.num_chapters_var, 10) temperature = self.temperature_var.get() max_tokens = self.max_tokens_var.get() + timeout_val = self.safe_get_int(self.timeout_var, 600) self.safe_log("开始生成章节蓝图...") Chapter_blueprint_generate( @@ -861,7 +899,8 @@ class NovelGeneratorGUI: number_of_chapters=number_of_chapters, filepath=filepath, temperature=temperature, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout_val ) self.safe_log("✅ 章节蓝图生成完成。请在 'Chapter Blueprint' 标签页查看或编辑。") except Exception: @@ -887,6 +926,7 @@ class NovelGeneratorGUI: model_name = self.model_name_var.get().strip() temperature = self.temperature_var.get() max_tokens = self.max_tokens_var.get() + timeout_val = self.safe_get_int(self.timeout_var, 600) chap_num = self.safe_get_int(self.chapter_num_var, 1) word_number = self.safe_get_int(self.word_number_var, 3000) @@ -923,7 +963,8 @@ class NovelGeneratorGUI: embedding_model_name=embedding_model_name, embedding_retrieval_k=embedding_k, interface_format=interface_format, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout_val ) if draft_text: self.safe_log(f"✅ 第{chap_num}章草稿生成完成。请在左侧查看或编辑。") @@ -959,6 +1000,7 @@ class NovelGeneratorGUI: model_name = self.model_name_var.get().strip() temperature = self.temperature_var.get() max_tokens = self.max_tokens_var.get() + timeout_val = self.safe_get_int(self.timeout_var, 600) embedding_api_key = self.embedding_api_key_var.get().strip() embedding_url = self.embedding_url_var.get().strip() @@ -989,7 +1031,8 @@ class NovelGeneratorGUI: embedding_interface_format=embedding_interface_format, embedding_model_name=embedding_model_name, interface_format=interface_format, - max_tokens=max_tokens + max_tokens=max_tokens, + timeout=timeout_val ) self.safe_log(f"✅ 第{chap_num}章定稿完成(已更新全局摘要、角色状态、向量库)。")