diff --git a/novel_generator.py b/novel_generator.py index ed4f4b7..6385e59 100644 --- a/novel_generator.py +++ b/novel_generator.py @@ -501,11 +501,9 @@ def summarize_recent_chapters( ) combined_text = "\n".join(chapters_text_list) - prompt = f"""你是一名资深长篇小说写作辅助AI,下面是最近几章的合并文本: -{combined_text} + prompt = f"""你是一名资深长篇小说编辑,分析以下合并文本:\n\n {combined_text} \n\n -请用中文输出不超过1000字的摘要,只包含主要剧情进展、角色变化、冲突焦点等要点: -重点强调最后一章的重要内容,以及最后一幕场景细节。 +从中提取并预测下一章节的关键字[关键物品/人物/地点/事件/情节] """ summary_text = invoke_with_cleaning(model, prompt) @@ -622,16 +620,7 @@ def generate_chapter_draft( if not relevant_context.strip(): relevant_context = "(无检索到的上下文)" - # 5) 构造prompt - # 拆分 world_building_text, novel_architecture_text 等等 - world_building_text = "" - match_world = re.search(r'#=== 3\) 世界观 ===\n([\s\S]+?)\n#===', novel_architecture_text) - if match_world: - world_building_text = match_world.group(1).strip() - else: - world_building_text = "暂无世界观信息" - - novel_setting_text = novel_architecture_text # 整份当做“小说设定”参考 + novel_setting_text = novel_architecture_text prompt_text = scene_dynamics_prompt.format( novel_number=novel_number, @@ -648,7 +637,6 @@ def generate_chapter_draft( scene_location=scene_location, time_constraint=time_constraint, - world_building=world_building_text, novel_setting=novel_setting_text, global_summary=global_summary_text, character_state=character_state_text @@ -656,7 +644,7 @@ def generate_chapter_draft( # 合并检索到的上下文和用户指导 prompt_text += f"\n\n【检索到的上下文】\n{relevant_context}" - prompt_text += f"\n\n【用户指导】\n{user_guidance}\n" + prompt_text += f"\n\n【章节额外指导】\n{user_guidance}\n" model = ChatOpenAI( model=model_name, @@ -847,7 +835,7 @@ def import_knowledge_file( # 尝试加载已有的向量库 store = load_vector_store( api_key=embedding_api_key, - base_url=embedding_url if embedding_url else "http://localhost:11434/v1", + base_url=embedding_url if embedding_url else "http://localhost:11434/api", interface_format=embedding_interface_format, embedding_model_name=embedding_model_name, filepath=filepath @@ -856,7 +844,7 @@ def import_knowledge_file( logging.info("Vector store does not exist. Initializing a new one for knowledge import...") init_vector_store( api_key=embedding_api_key, - base_url=embedding_url if embedding_url else "http://localhost:11434/v1", + base_url=embedding_url if embedding_url else "http://localhost:11434/api", interface_format=embedding_interface_format, embedding_model_name=embedding_model_name, texts=paragraphs, diff --git a/prompt_definitions.py b/prompt_definitions.py index d23bc04..2410ce0 100644 --- a/prompt_definitions.py +++ b/prompt_definitions.py @@ -83,19 +83,19 @@ plot_architecture_prompt = """\ 世界观:{world_building} 要求按以下结构设计: -▶ 第一幕(触发) +第一幕(触发) - 日常状态中的异常征兆(3处铺垫) - 引出故事:展示主线、暗线、副线的开端 - 关键事件:打破平衡的催化剂(需改变至少3个角色的关系) - 错误抉择:主角的认知局限导致的错误反应 -▶ 第二幕(对抗) +第二幕(对抗) - 剧情升级:主线+副线的交叉点 - 双重压力:外部障碍升级+内部挫折 - 虚假胜利:看似解决实则深化危机的转折点 - 灵魂黑夜:世界观认知颠覆时刻 -▶ 第三幕(解决) +第三幕(解决) - 代价显现:解决危机必须牺牲的核心价值 - 嵌套转折:至少包含三层认知颠覆(表面解→新危机→终极抉择) - 余波:留下2个开放式悬念因子 @@ -169,12 +169,43 @@ update_character_state_prompt = """\ {old_state} 请更新角色状态,内容包括: -角色属性: -- 角色物品 -- 能力 -- 状态 -- 主要角色间关系网 -- 触发或加深的事件 +角色A属性: +├──物品: + ├──道具1:描述 + ├──道具2:描述 + ... +├──能力 + ├──技能1:描述 + ├──技能2:描述 + ... +├──状态 + ├──身体状态: + ├──Buff/Debuff + ├──心理状态:描述 + +├──主要角色间关系网 + ├──角色B:描述 + ├──角色C:描述 + ... +├──触发或加深的事件 + ├──事件1:描述 + ├──事件2:描述 + ... + +角色B属性: +├──物品 + ├──... +├──能力 + ├──... +├──状态 + ├──... +├──主要角色间关系网 + ├──... +├──触发或加深的事件 + ├──... + +角色C属性: +...... 新出场角色: - 任何新增角色或临时出场人物的基本信息 @@ -203,10 +234,13 @@ scene_dynamics_prompt = """\ - 时间压力(可能未指定):{time_constraint} 参考文档: -- 世界观:{world_building} -- 小说设定:{novel_setting} -- 全局摘要:{global_summary} -- 角色状态:{character_state} +- 小说设定:\n{novel_setting}\n +- 全局摘要:\n{global_summary}\n +- 角色状态:\n{character_state}\n + +前章片段(可能为空):\n{previous_chapter_excerpt}\n + +本地向量库检索到的上下文片段(可能为空):\n{context_excerpt}\n 请完成第 {novel_number} 章的正文,至少设计下方2个具有动态张力的场景: 1. 对话场景: @@ -224,7 +258,7 @@ scene_dynamics_prompt = """\ - 隐喻系统的运用(连接世界观符号) - 决策前的价值天平描写 -最后设置一个"钩链转折":结尾同时回收旧悬念/创造新悬念/抛出新危机/颠覆某个认知预设/神转折等。 +文末设置一个"钩链转折":结尾时回收旧悬念/创造新悬念/抛出新危机/颠覆某个认知预设/神转折等。 格式要求: - 仅返回章节正文文本; diff --git a/ui.py b/ui.py index 425dd40..63f6360 100644 --- a/ui.py +++ b/ui.py @@ -208,9 +208,12 @@ class NovelGeneratorGUI: def build_ai_config_tab(self): def on_interface_format_changed(new_value): - if new_value == "OpenAI": + if new_value == "Ollama": + self.base_url_var.set("http://localhost:11434/v1") + elif new_value == "ML Studio": + self.base_url_var.set("http://localhost:1234/v1") + elif new_value == "OpenAI": self.base_url_var.set("https://api.openai.com/v1") - # 可根据需要扩展 Ollama / ML Studio 等 for i in range(5): self.ai_config_tab.grid_rowconfigure(i, weight=0) @@ -230,7 +233,7 @@ class NovelGeneratorGUI: interface_label = ctk.CTkLabel(self.ai_config_tab, text="LLM 接口格式:", font=("Microsoft YaHei", 12)) interface_label.grid(row=2, column=0, padx=5, pady=5, sticky="e") - interface_options = ["OpenAI"] + interface_options = ["OpenAI", "Ollama", "ML Studio"] interface_dropdown = ctk.CTkOptionMenu( self.ai_config_tab, values=interface_options, @@ -268,6 +271,14 @@ class NovelGeneratorGUI: self.temp_value_label.grid(row=4, column=2, padx=1, pady=1, sticky="w") def build_embeddings_config_tab(self): + def on_embedding_interface_changed(new_value): + if new_value == "Ollama": + self.embedding_url_var.set("http://localhost:11434/api") + elif new_value == "ML Studio": + self.embedding_url_var.set("http://localhost:1234/v1") + elif new_value == "OpenAI": + self.embedding_url_var.set("https://api.openai.com/v1") + for i in range(5): self.embeddings_config_tab.grid_rowconfigure(i, weight=0) self.embeddings_config_tab.grid_columnconfigure(0, weight=0) @@ -280,11 +291,12 @@ class NovelGeneratorGUI: emb_interface_label = ctk.CTkLabel(self.embeddings_config_tab, text="Embedding 接口格式:", font=("Microsoft YaHei", 12)) emb_interface_label.grid(row=1, column=0, padx=5, pady=5, sticky="e") - emb_interface_options = ["OpenAI"] # 可扩展 + emb_interface_options = ["OpenAI", "Ollama", "ML Studio"] emb_interface_dropdown = ctk.CTkOptionMenu( self.embeddings_config_tab, values=emb_interface_options, variable=self.embedding_interface_format_var, + command=on_embedding_interface_changed, font=("Microsoft YaHei", 12) ) emb_interface_dropdown.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")