add tooltip,max_tokens
This commit is contained in:
@@ -69,7 +69,6 @@ class OllamaEmbeddingAdapter(BaseEmbeddingAdapter):
|
|||||||
"""
|
"""
|
||||||
调用 Ollama 本地服务 /api/embeddings 接口,获取文本 embedding
|
调用 Ollama 本地服务 /api/embeddings 接口,获取文本 embedding
|
||||||
"""
|
"""
|
||||||
# 如果 base_url 中已含 /api/embeddings,可直接用;否则拼上 /api/embeddings
|
|
||||||
url = self.base_url
|
url = self.base_url
|
||||||
if "api/embeddings" not in url:
|
if "api/embeddings" not in url:
|
||||||
url = f"{url}/api/embeddings"
|
url = f"{url}/api/embeddings"
|
||||||
|
|||||||
+10
-9
@@ -28,7 +28,7 @@ class DeepSeekAdapter(BaseLLMAdapter):
|
|||||||
"""
|
"""
|
||||||
适配官方/OpenAI兼容接口(使用 langchain.ChatOpenAI)
|
适配官方/OpenAI兼容接口(使用 langchain.ChatOpenAI)
|
||||||
"""
|
"""
|
||||||
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens:int, temperature: float = 0.7):
|
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
|
||||||
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
@@ -54,7 +54,7 @@ class OpenAIAdapter(BaseLLMAdapter):
|
|||||||
"""
|
"""
|
||||||
适配官方/OpenAI兼容接口(使用 langchain.ChatOpenAI)
|
适配官方/OpenAI兼容接口(使用 langchain.ChatOpenAI)
|
||||||
"""
|
"""
|
||||||
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens:int, temperature: float = 0.7):
|
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
|
||||||
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
@@ -81,7 +81,7 @@ class OllamaAdapter(BaseLLMAdapter):
|
|||||||
Ollama 同样有一个 OpenAI-like /v1/chat 接口,可直接使用 ChatOpenAI。
|
Ollama 同样有一个 OpenAI-like /v1/chat 接口,可直接使用 ChatOpenAI。
|
||||||
但是通常 Ollama 默认本地服务在 http://localhost:11434,如果符合OpenAI风格即可直接传参。
|
但是通常 Ollama 默认本地服务在 http://localhost:11434,如果符合OpenAI风格即可直接传参。
|
||||||
"""
|
"""
|
||||||
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens:int, temperature: float = 0.7):
|
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
|
||||||
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
@@ -104,7 +104,7 @@ class OllamaAdapter(BaseLLMAdapter):
|
|||||||
return response.content
|
return response.content
|
||||||
|
|
||||||
class MLStudioAdapter(BaseLLMAdapter):
|
class MLStudioAdapter(BaseLLMAdapter):
|
||||||
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens:int, temperature: float = 0.7):
|
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
|
||||||
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
self.base_url = ensure_openai_base_url_has_v1(base_url)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
@@ -131,18 +131,19 @@ def create_llm_adapter(
|
|||||||
base_url: str,
|
base_url: str,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
temperature: float
|
temperature: float,
|
||||||
|
max_tokens: int
|
||||||
) -> BaseLLMAdapter:
|
) -> BaseLLMAdapter:
|
||||||
"""
|
"""
|
||||||
工厂函数:根据 interface_format 返回不同的适配器实例。
|
工厂函数:根据 interface_format 返回不同的适配器实例。
|
||||||
"""
|
"""
|
||||||
if interface_format.lower() == "deepseek":
|
if interface_format.lower() == "deepseek":
|
||||||
return DeepSeekAdapter(api_key, base_url, model_name, temperature)
|
return DeepSeekAdapter(api_key, base_url, model_name, max_tokens, temperature)
|
||||||
elif interface_format.lower() == "openai":
|
elif interface_format.lower() == "openai":
|
||||||
return OpenAIAdapter(api_key, base_url, model_name, temperature)
|
return OpenAIAdapter(api_key, base_url, model_name, max_tokens, temperature)
|
||||||
elif interface_format.lower() == "ollama":
|
elif interface_format.lower() == "ollama":
|
||||||
return OllamaAdapter(api_key, base_url, model_name, temperature)
|
return OllamaAdapter(api_key, base_url, model_name, max_tokens, temperature)
|
||||||
elif interface_format.lower() == "ml studio":
|
elif interface_format.lower() == "ml studio":
|
||||||
return MLStudioAdapter(api_key, base_url, model_name, temperature)
|
return MLStudioAdapter(api_key, base_url, model_name, max_tokens, temperature)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown interface_format: {interface_format}")
|
raise ValueError(f"Unknown interface_format: {interface_format}")
|
||||||
|
|||||||
+43
-38
@@ -43,6 +43,7 @@ from embedding_adapters import create_embedding_adapter
|
|||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
|
|
||||||
|
|
||||||
# ============ 工具函数 ============
|
# ============ 工具函数 ============
|
||||||
|
|
||||||
def remove_think_tags(text: str) -> str:
|
def remove_think_tags(text: str) -> str:
|
||||||
@@ -67,6 +68,7 @@ def invoke_with_cleaning(llm_adapter, prompt: str) -> str:
|
|||||||
debug_log(prompt, cleaned_text)
|
debug_log(prompt, cleaned_text)
|
||||||
return cleaned_text.strip()
|
return cleaned_text.strip()
|
||||||
|
|
||||||
|
|
||||||
# ============ 获取 vectorstore 路径 ============
|
# ============ 获取 vectorstore 路径 ============
|
||||||
|
|
||||||
def get_vectorstore_dir(filepath: str) -> str:
|
def get_vectorstore_dir(filepath: str) -> str:
|
||||||
@@ -89,6 +91,7 @@ def clear_vector_store(filepath: str) -> bool:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ============ 根据 embedding 接口创建/加载 Chroma ============
|
# ============ 根据 embedding 接口创建/加载 Chroma ============
|
||||||
|
|
||||||
def init_vector_store(
|
def init_vector_store(
|
||||||
@@ -103,11 +106,8 @@ def init_vector_store(
|
|||||||
store_dir = get_vectorstore_dir(filepath)
|
store_dir = get_vectorstore_dir(filepath)
|
||||||
os.makedirs(store_dir, exist_ok=True)
|
os.makedirs(store_dir, exist_ok=True)
|
||||||
|
|
||||||
# 将文本封装为 Document
|
|
||||||
documents = [Document(page_content=str(t)) for t in texts]
|
documents = [Document(page_content=str(t)) for t in texts]
|
||||||
|
|
||||||
# 因为我们是自定义的 embeddings,对接Chroma时需包装一个“langchain兼容对象”
|
|
||||||
# 这里示例:写一个包装函数
|
|
||||||
from langchain.embeddings.base import Embeddings as LCEmbeddings
|
from langchain.embeddings.base import Embeddings as LCEmbeddings
|
||||||
|
|
||||||
class LCEmbeddingWrapper(LCEmbeddings):
|
class LCEmbeddingWrapper(LCEmbeddings):
|
||||||
@@ -140,7 +140,6 @@ def load_vector_store(
|
|||||||
logging.info("Vector store not found. Will return None.")
|
logging.info("Vector store not found. Will return None.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 同样要包装embedding_adapter
|
|
||||||
from langchain.embeddings.base import Embeddings as LCEmbeddings
|
from langchain.embeddings.base import Embeddings as LCEmbeddings
|
||||||
|
|
||||||
class LCEmbeddingWrapper(LCEmbeddings):
|
class LCEmbeddingWrapper(LCEmbeddings):
|
||||||
@@ -159,6 +158,7 @@ def load_vector_store(
|
|||||||
collection_name="novel_collection"
|
collection_name="novel_collection"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============ 文本分段工具 ============
|
# ============ 文本分段工具 ============
|
||||||
|
|
||||||
def split_by_length(text: str, max_length: int = 500) -> List[str]:
|
def split_by_length(text: str, max_length: int = 500) -> List[str]:
|
||||||
@@ -287,6 +287,7 @@ def summarize_recent_chapters(
|
|||||||
base_url: str,
|
base_url: str,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
temperature: float,
|
temperature: float,
|
||||||
|
max_tokens: int,
|
||||||
chapters_text_list: List[str]
|
chapters_text_list: List[str]
|
||||||
) -> Tuple[str, str]:
|
) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
@@ -297,13 +298,13 @@ def summarize_recent_chapters(
|
|||||||
if not combined_text:
|
if not combined_text:
|
||||||
return ("", "")
|
return ("", "")
|
||||||
|
|
||||||
# 1) 构造 llm_adapter
|
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format=interface_format,
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
|
|
||||||
prompt = summarize_recent_chapters_prompt.format(combined_text=combined_text)
|
prompt = summarize_recent_chapters_prompt.format(combined_text=combined_text)
|
||||||
@@ -328,6 +329,7 @@ def summarize_recent_chapters(
|
|||||||
# ============ 1) 生成总体架构 ============
|
# ============ 1) 生成总体架构 ============
|
||||||
|
|
||||||
def Novel_architecture_generate(
|
def Novel_architecture_generate(
|
||||||
|
interface_format: str,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
base_url: str,
|
base_url: str,
|
||||||
llm_model: str,
|
llm_model: str,
|
||||||
@@ -336,7 +338,8 @@ def Novel_architecture_generate(
|
|||||||
number_of_chapters: int,
|
number_of_chapters: int,
|
||||||
word_number: int,
|
word_number: int,
|
||||||
filepath: str,
|
filepath: str,
|
||||||
temperature: float = 0.7
|
temperature: float = 0.7,
|
||||||
|
max_tokens: int = 2048
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
依次调用:
|
依次调用:
|
||||||
@@ -348,13 +351,13 @@ def Novel_architecture_generate(
|
|||||||
"""
|
"""
|
||||||
os.makedirs(filepath, exist_ok=True)
|
os.makedirs(filepath, exist_ok=True)
|
||||||
|
|
||||||
# 通过工厂函数创建 LLM 适配器
|
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format="openai", # 或根据你的实际:若你在UI中就是 "OpenAI" 就传递过来
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=llm_model,
|
model_name=llm_model,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step1: 核心种子
|
# Step1: 核心种子
|
||||||
@@ -382,7 +385,6 @@ def Novel_architecture_generate(
|
|||||||
)
|
)
|
||||||
plot_arch_result = invoke_with_cleaning(llm_adapter, prompt_plot)
|
plot_arch_result = invoke_with_cleaning(llm_adapter, prompt_plot)
|
||||||
|
|
||||||
# 合并
|
|
||||||
final_content = (
|
final_content = (
|
||||||
"#=== 1) 核心种子 ===\n"
|
"#=== 1) 核心种子 ===\n"
|
||||||
f"{core_seed_result}\n\n"
|
f"{core_seed_result}\n\n"
|
||||||
@@ -399,14 +401,17 @@ def Novel_architecture_generate(
|
|||||||
save_string_to_txt(final_content, arch_file)
|
save_string_to_txt(final_content, arch_file)
|
||||||
logging.info("Novel_architecture.txt has been generated successfully.")
|
logging.info("Novel_architecture.txt has been generated successfully.")
|
||||||
|
|
||||||
|
|
||||||
# ============ 2) 生成章节蓝图 ============
|
# ============ 2) 生成章节蓝图 ============
|
||||||
|
|
||||||
def Chapter_blueprint_generate(
|
def Chapter_blueprint_generate(
|
||||||
|
interface_format: str,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
base_url: str,
|
base_url: str,
|
||||||
llm_model: str,
|
llm_model: str,
|
||||||
filepath: str,
|
filepath: str,
|
||||||
temperature: float = 0.7
|
temperature: float = 0.7,
|
||||||
|
max_tokens: int = 2048
|
||||||
) -> None:
|
) -> None:
|
||||||
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
||||||
if not os.path.exists(arch_file):
|
if not os.path.exists(arch_file):
|
||||||
@@ -432,11 +437,12 @@ def Chapter_blueprint_generate(
|
|||||||
plot_arch_text = m.group(1).strip()
|
plot_arch_text = m.group(1).strip()
|
||||||
|
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format="openai", # 或实际由UI传入
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=llm_model,
|
model_name=llm_model,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
|
|
||||||
prompt = chapter_blueprint_prompt.format(
|
prompt = chapter_blueprint_prompt.format(
|
||||||
@@ -454,6 +460,7 @@ def Chapter_blueprint_generate(
|
|||||||
|
|
||||||
logging.info("Novel_directory.txt (chapter blueprint) has been generated successfully.")
|
logging.info("Novel_directory.txt (chapter blueprint) has been generated successfully.")
|
||||||
|
|
||||||
|
|
||||||
# ============ 3) 生成章节草稿 ============
|
# ============ 3) 生成章节草稿 ============
|
||||||
|
|
||||||
def generate_chapter_draft(
|
def generate_chapter_draft(
|
||||||
@@ -473,7 +480,9 @@ def generate_chapter_draft(
|
|||||||
embedding_url: str,
|
embedding_url: str,
|
||||||
embedding_interface_format: str,
|
embedding_interface_format: str,
|
||||||
embedding_model_name: str,
|
embedding_model_name: str,
|
||||||
embedding_retrieval_k: int = 2
|
embedding_retrieval_k: int = 2,
|
||||||
|
interface_format: str = "openai",
|
||||||
|
max_tokens: int = 2048
|
||||||
) -> str:
|
) -> str:
|
||||||
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
||||||
novel_architecture_text = read_file(arch_file)
|
novel_architecture_text = read_file(arch_file)
|
||||||
@@ -487,7 +496,6 @@ def generate_chapter_draft(
|
|||||||
character_state_file = os.path.join(filepath, "character_state.txt")
|
character_state_file = os.path.join(filepath, "character_state.txt")
|
||||||
character_state_text = read_file(character_state_file)
|
character_state_text = read_file(character_state_file)
|
||||||
|
|
||||||
# 解析本章信息
|
|
||||||
chapter_info = get_chapter_info_from_blueprint(blueprint_text, novel_number)
|
chapter_info = get_chapter_info_from_blueprint(blueprint_text, novel_number)
|
||||||
chapter_title = chapter_info["chapter_title"]
|
chapter_title = chapter_info["chapter_title"]
|
||||||
chapter_role = chapter_info["chapter_role"]
|
chapter_role = chapter_info["chapter_role"]
|
||||||
@@ -500,18 +508,17 @@ def generate_chapter_draft(
|
|||||||
chapters_dir = os.path.join(filepath, "chapters")
|
chapters_dir = os.path.join(filepath, "chapters")
|
||||||
os.makedirs(chapters_dir, exist_ok=True)
|
os.makedirs(chapters_dir, exist_ok=True)
|
||||||
|
|
||||||
# 获取最近3章 => (短期摘要, 下一章关键字)
|
|
||||||
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
||||||
short_summary, next_chapter_keywords = summarize_recent_chapters(
|
short_summary, next_chapter_keywords = summarize_recent_chapters(
|
||||||
interface_format="openai", # 或由UI传进
|
interface_format=interface_format,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens,
|
||||||
chapters_text_list=recent_3_texts
|
chapters_text_list=recent_3_texts
|
||||||
)
|
)
|
||||||
|
|
||||||
# 上一章片段(末尾1500字)
|
|
||||||
previous_chapter_excerpt = ""
|
previous_chapter_excerpt = ""
|
||||||
for text_block in reversed(recent_3_texts):
|
for text_block in reversed(recent_3_texts):
|
||||||
if text_block.strip():
|
if text_block.strip():
|
||||||
@@ -521,7 +528,6 @@ def generate_chapter_draft(
|
|||||||
previous_chapter_excerpt = text_block
|
previous_chapter_excerpt = text_block
|
||||||
break
|
break
|
||||||
|
|
||||||
# 使用embedding检索上下文
|
|
||||||
embedding_adapter = create_embedding_adapter(
|
embedding_adapter = create_embedding_adapter(
|
||||||
embedding_interface_format,
|
embedding_interface_format,
|
||||||
embedding_api_key,
|
embedding_api_key,
|
||||||
@@ -538,7 +544,6 @@ def generate_chapter_draft(
|
|||||||
if not relevant_context.strip():
|
if not relevant_context.strip():
|
||||||
relevant_context = "(无检索到的上下文)"
|
relevant_context = "(无检索到的上下文)"
|
||||||
|
|
||||||
# 组装 Prompt
|
|
||||||
prompt_text = chapter_draft_prompt.format(
|
prompt_text = chapter_draft_prompt.format(
|
||||||
novel_number=novel_number,
|
novel_number=novel_number,
|
||||||
chapter_title=chapter_title,
|
chapter_title=chapter_title,
|
||||||
@@ -562,19 +567,18 @@ def generate_chapter_draft(
|
|||||||
context_excerpt=relevant_context
|
context_excerpt=relevant_context
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用 LLM 生成
|
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format="openai", # 或由UI传进
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
chapter_content = invoke_with_cleaning(llm_adapter, prompt_text)
|
chapter_content = invoke_with_cleaning(llm_adapter, prompt_text)
|
||||||
if not chapter_content.strip():
|
if not chapter_content.strip():
|
||||||
logging.warning("Generated chapter draft is empty.")
|
logging.warning("Generated chapter draft is empty.")
|
||||||
|
|
||||||
# 写入 chapter_X.txt
|
|
||||||
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
||||||
clear_file_content(chapter_file)
|
clear_file_content(chapter_file)
|
||||||
save_string_to_txt(chapter_content, chapter_file)
|
save_string_to_txt(chapter_content, chapter_file)
|
||||||
@@ -595,7 +599,9 @@ def finalize_chapter(
|
|||||||
embedding_api_key: str,
|
embedding_api_key: str,
|
||||||
embedding_url: str,
|
embedding_url: str,
|
||||||
embedding_interface_format: str,
|
embedding_interface_format: str,
|
||||||
embedding_model_name: str
|
embedding_model_name: str,
|
||||||
|
interface_format: str,
|
||||||
|
max_tokens: int
|
||||||
):
|
):
|
||||||
chapters_dir = os.path.join(filepath, "chapters")
|
chapters_dir = os.path.join(filepath, "chapters")
|
||||||
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
||||||
@@ -604,25 +610,23 @@ def finalize_chapter(
|
|||||||
logging.warning(f"Chapter {novel_number} is empty, cannot finalize.")
|
logging.warning(f"Chapter {novel_number} is empty, cannot finalize.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 如果篇幅过短,可以扩写
|
|
||||||
if len(chapter_text) < 0.6 * word_number:
|
if len(chapter_text) < 0.6 * word_number:
|
||||||
chapter_text = enrich_chapter_text(chapter_text, word_number, api_key, base_url, model_name, temperature)
|
chapter_text = enrich_chapter_text(chapter_text, word_number, api_key, base_url, model_name, temperature, interface_format, max_tokens)
|
||||||
clear_file_content(chapter_file)
|
clear_file_content(chapter_file)
|
||||||
save_string_to_txt(chapter_text, chapter_file)
|
save_string_to_txt(chapter_text, chapter_file)
|
||||||
|
|
||||||
# 读取全局摘要、角色状态
|
|
||||||
global_summary_file = os.path.join(filepath, "global_summary.txt")
|
global_summary_file = os.path.join(filepath, "global_summary.txt")
|
||||||
old_global_summary = read_file(global_summary_file)
|
old_global_summary = read_file(global_summary_file)
|
||||||
character_state_file = os.path.join(filepath, "character_state.txt")
|
character_state_file = os.path.join(filepath, "character_state.txt")
|
||||||
old_character_state = read_file(character_state_file)
|
old_character_state = read_file(character_state_file)
|
||||||
|
|
||||||
# 调用 LLM 更新全局摘要
|
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format="openai",
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
prompt_summary = summary_prompt.format(
|
prompt_summary = summary_prompt.format(
|
||||||
chapter_text=chapter_text,
|
chapter_text=chapter_text,
|
||||||
@@ -632,7 +636,6 @@ def finalize_chapter(
|
|||||||
if not new_global_summary.strip():
|
if not new_global_summary.strip():
|
||||||
new_global_summary = old_global_summary
|
new_global_summary = old_global_summary
|
||||||
|
|
||||||
# 更新角色状态
|
|
||||||
prompt_char_state = update_character_state_prompt.format(
|
prompt_char_state = update_character_state_prompt.format(
|
||||||
chapter_text=chapter_text,
|
chapter_text=chapter_text,
|
||||||
old_state=old_character_state
|
old_state=old_character_state
|
||||||
@@ -641,14 +644,12 @@ def finalize_chapter(
|
|||||||
if not new_char_state.strip():
|
if not new_char_state.strip():
|
||||||
new_char_state = old_character_state
|
new_char_state = old_character_state
|
||||||
|
|
||||||
# 写回
|
|
||||||
clear_file_content(global_summary_file)
|
clear_file_content(global_summary_file)
|
||||||
save_string_to_txt(new_global_summary, global_summary_file)
|
save_string_to_txt(new_global_summary, global_summary_file)
|
||||||
|
|
||||||
clear_file_content(character_state_file)
|
clear_file_content(character_state_file)
|
||||||
save_string_to_txt(new_char_state, character_state_file)
|
save_string_to_txt(new_char_state, character_state_file)
|
||||||
|
|
||||||
# 更新向量库
|
|
||||||
embedding_adapter = create_embedding_adapter(
|
embedding_adapter = create_embedding_adapter(
|
||||||
embedding_interface_format,
|
embedding_interface_format,
|
||||||
embedding_api_key,
|
embedding_api_key,
|
||||||
@@ -665,14 +666,17 @@ def enrich_chapter_text(
|
|||||||
api_key: str,
|
api_key: str,
|
||||||
base_url: str,
|
base_url: str,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
temperature: float
|
temperature: float,
|
||||||
|
interface_format: str,
|
||||||
|
max_tokens: int
|
||||||
) -> str:
|
) -> str:
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format="openai",
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
prompt = f"""以下章节文本较短,请在保持剧情连贯的前提下进行扩写,使其更充实,接近 {word_number} 字左右:
|
prompt = f"""以下章节文本较短,请在保持剧情连贯的前提下进行扩写,使其更充实,接近 {word_number} 字左右:
|
||||||
原内容:
|
原内容:
|
||||||
@@ -681,6 +685,7 @@ def enrich_chapter_text(
|
|||||||
enriched_text = invoke_with_cleaning(llm_adapter, prompt)
|
enriched_text = invoke_with_cleaning(llm_adapter, prompt)
|
||||||
return enriched_text if enriched_text else chapter_text
|
return enriched_text if enriched_text else chapter_text
|
||||||
|
|
||||||
|
|
||||||
# ============ 导入知识文件到向量库 ============
|
# ============ 导入知识文件到向量库 ============
|
||||||
|
|
||||||
def advanced_split_content(content: str,
|
def advanced_split_content(content: str,
|
||||||
|
|||||||
+13
-3
@@ -4,10 +4,20 @@
|
|||||||
tooltips = {
|
tooltips = {
|
||||||
"api_key": "在这里填写你的API Key。如果使用OpenAI官方接口,请在 https://platform.openai.com/account/api-keys 获取。",
|
"api_key": "在这里填写你的API Key。如果使用OpenAI官方接口,请在 https://platform.openai.com/account/api-keys 获取。",
|
||||||
"base_url": "模型的接口地址。若使用OpenAI官方:https://api.openai.com/v1。若使用Ollama本地部署,则类似 http://localhost:11434/v1。",
|
"base_url": "模型的接口地址。若使用OpenAI官方:https://api.openai.com/v1。若使用Ollama本地部署,则类似 http://localhost:11434/v1。",
|
||||||
"interface_format": "指定LLM接口兼容格式,可选OpenAI、Ollama、ML Studio等。",
|
"interface_format": "指定LLM接口兼容格式,可选DeepSeek\OpenAI\Ollama\ML Studio等。\n\n注意:"+
|
||||||
"model_name": "要使用的模型名称,例如gpt-3.5-turbo、llama2等。如果是Ollama,请填写你下载好的本地模型名。",
|
"OpenAI 兼容是指的可以通过该标准请求的任何接口,不是只允许使用api.openai.com接口\n"+
|
||||||
|
"例如Ollama接口格式也兼容OpenAI,可以无需修改直接使用\n"+
|
||||||
|
"ML Studio接口格式与OpenAI接口格式也一致。",
|
||||||
|
"model_name": "要使用的模型名称,例如deepseek-reasoner、gpt-4o等。如果是Ollama等,请填写你下载好的本地模型名。",
|
||||||
"temperature": "生成文本的随机度。数值越大越具有发散性,越小越严谨。",
|
"temperature": "生成文本的随机度。数值越大越具有发散性,越小越严谨。",
|
||||||
"max_tokens": "限制单次生成的最大Token数。范围1~100000,请根据模型上下文及需求填写合适值。",
|
"max_tokens": "限制单次生成的最大Token数。范围1~100000,请根据模型上下文及需求填写合适值。\n"+
|
||||||
|
"以下是一些常见模型的最大值:\n"+
|
||||||
|
"o1:100,000\n"+
|
||||||
|
"o1-mini:65,536\n"+
|
||||||
|
"gpt-4o:16384\n"+
|
||||||
|
"gpt-4o-mini:16384\n"+
|
||||||
|
"deepseek-reasoner:8192\n"+
|
||||||
|
"deepseek-chat:4096\n",
|
||||||
"embedding_api_key": "调用Embedding模型时所需的API Key。",
|
"embedding_api_key": "调用Embedding模型时所需的API Key。",
|
||||||
"embedding_interface_format": "Embedding模型接口风格,比如OpenAI或Ollama。",
|
"embedding_interface_format": "Embedding模型接口风格,比如OpenAI或Ollama。",
|
||||||
"embedding_url": "Embedding模型接口地址。",
|
"embedding_url": "Embedding模型接口地址。",
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ from novel_generator import (
|
|||||||
clear_vector_store,
|
clear_vector_store,
|
||||||
get_last_n_chapters_text
|
get_last_n_chapters_text
|
||||||
)
|
)
|
||||||
|
|
||||||
from consistency_checker import check_consistency
|
from consistency_checker import check_consistency
|
||||||
|
|
||||||
|
# ---- Import the tooltip texts ----
|
||||||
|
from tooltips import tooltips
|
||||||
|
|
||||||
def log_error(message: str):
|
def log_error(message: str):
|
||||||
logging.error(f"{message}\n{traceback.format_exc()}")
|
logging.error(f"{message}\n{traceback.format_exc()}")
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ class NovelGeneratorGUI:
|
|||||||
self.interface_format_var = ctk.StringVar(value=self.loaded_config.get("interface_format", "OpenAI"))
|
self.interface_format_var = ctk.StringVar(value=self.loaded_config.get("interface_format", "OpenAI"))
|
||||||
self.model_name_var = ctk.StringVar(value=self.loaded_config.get("model_name", "gpt-4o-mini"))
|
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.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))
|
||||||
|
|
||||||
# Embedding相关
|
# Embedding相关
|
||||||
self.embedding_interface_format_var = ctk.StringVar(value=self.loaded_config.get("embedding_interface_format", "OpenAI"))
|
self.embedding_interface_format_var = ctk.StringVar(value=self.loaded_config.get("embedding_interface_format", "OpenAI"))
|
||||||
@@ -92,6 +95,11 @@ class NovelGeneratorGUI:
|
|||||||
self.build_summary_tab()
|
self.build_summary_tab()
|
||||||
self.build_chapters_tab()
|
self.build_chapters_tab()
|
||||||
|
|
||||||
|
def show_tooltip(self, key: str):
|
||||||
|
"""Display a popup with tooltip text."""
|
||||||
|
info_text = tooltips.get(key, "暂无说明")
|
||||||
|
messagebox.showinfo("参数说明", info_text)
|
||||||
|
|
||||||
def safe_get_int(self, var, default=1):
|
def safe_get_int(self, var, default=1):
|
||||||
try:
|
try:
|
||||||
val_str = str(var.get()).strip()
|
val_str = str(var.get()).strip()
|
||||||
@@ -203,6 +211,27 @@ class NovelGeneratorGUI:
|
|||||||
self.build_ai_config_tab()
|
self.build_ai_config_tab()
|
||||||
self.build_embeddings_config_tab()
|
self.build_embeddings_config_tab()
|
||||||
|
|
||||||
|
# 封装一个小工具函数,用来创建「标签 + 问号按钮」的组合
|
||||||
|
def create_label_with_help(self, parent, label_text, tooltip_key, row, column, font=None, sticky="e", padx=5, pady=5):
|
||||||
|
# frame容器:同一格子里存放 label + "?"按钮
|
||||||
|
frame = ctk.CTkFrame(parent)
|
||||||
|
frame.grid(row=row, column=column, padx=padx, pady=pady, sticky=sticky)
|
||||||
|
frame.columnconfigure(0, weight=0)
|
||||||
|
# 先放 label
|
||||||
|
label = ctk.CTkLabel(frame, text=label_text, font=font)
|
||||||
|
label.pack(side="left")
|
||||||
|
# 再放问号按钮
|
||||||
|
btn = ctk.CTkButton(
|
||||||
|
frame,
|
||||||
|
text="?",
|
||||||
|
width=22,
|
||||||
|
height=22,
|
||||||
|
font=("Microsoft YaHei", 10),
|
||||||
|
command=lambda: self.show_tooltip(tooltip_key)
|
||||||
|
)
|
||||||
|
btn.pack(side="left", padx=3)
|
||||||
|
return frame
|
||||||
|
|
||||||
def build_ai_config_tab(self):
|
def build_ai_config_tab(self):
|
||||||
def on_interface_format_changed(new_value):
|
def on_interface_format_changed(new_value):
|
||||||
if new_value == "Ollama":
|
if new_value == "Ollama":
|
||||||
@@ -211,26 +240,49 @@ class NovelGeneratorGUI:
|
|||||||
self.base_url_var.set("http://localhost:1234/v1")
|
self.base_url_var.set("http://localhost:1234/v1")
|
||||||
elif new_value == "OpenAI":
|
elif new_value == "OpenAI":
|
||||||
self.base_url_var.set("https://api.openai.com/v1")
|
self.base_url_var.set("https://api.openai.com/v1")
|
||||||
|
elif new_value == "DeepSeek":
|
||||||
|
self.base_url_var.set("https://api.deepseek.com/v1")
|
||||||
|
|
||||||
for i in range(5):
|
for i in range(6):
|
||||||
self.ai_config_tab.grid_rowconfigure(i, weight=0)
|
self.ai_config_tab.grid_rowconfigure(i, weight=0)
|
||||||
self.ai_config_tab.grid_columnconfigure(0, weight=0)
|
self.ai_config_tab.grid_columnconfigure(0, weight=0)
|
||||||
self.ai_config_tab.grid_columnconfigure(1, weight=1)
|
self.ai_config_tab.grid_columnconfigure(1, weight=1)
|
||||||
self.ai_config_tab.grid_columnconfigure(2, weight=0)
|
self.ai_config_tab.grid_columnconfigure(2, weight=0)
|
||||||
|
|
||||||
api_key_label = ctk.CTkLabel(self.ai_config_tab, text="LLM API Key:", font=("Microsoft YaHei", 12))
|
# 1) API Key
|
||||||
api_key_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.ai_config_tab,
|
||||||
|
label_text="LLM API Key:",
|
||||||
|
tooltip_key="api_key",
|
||||||
|
row=0,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
api_key_entry = ctk.CTkEntry(self.ai_config_tab, textvariable=self.api_key_var, font=("Microsoft YaHei", 12))
|
api_key_entry = ctk.CTkEntry(self.ai_config_tab, textvariable=self.api_key_var, font=("Microsoft YaHei", 12))
|
||||||
api_key_entry.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
api_key_entry.grid(row=0, column=1, padx=5, pady=5, columnspan=2, sticky="nsew")
|
||||||
|
|
||||||
base_url_label = ctk.CTkLabel(self.ai_config_tab, text="LLM Base URL:", font=("Microsoft YaHei", 12))
|
# 2) Base URL
|
||||||
base_url_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.ai_config_tab,
|
||||||
|
label_text="LLM Base URL:",
|
||||||
|
tooltip_key="base_url",
|
||||||
|
row=1,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
base_url_entry = ctk.CTkEntry(self.ai_config_tab, textvariable=self.base_url_var, font=("Microsoft YaHei", 12))
|
base_url_entry = ctk.CTkEntry(self.ai_config_tab, textvariable=self.base_url_var, font=("Microsoft YaHei", 12))
|
||||||
base_url_entry.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")
|
base_url_entry.grid(row=1, column=1, padx=5, pady=5, columnspan=2, sticky="nsew")
|
||||||
|
|
||||||
interface_label = ctk.CTkLabel(self.ai_config_tab, text="LLM 接口格式:", font=("Microsoft YaHei", 12))
|
# 3) 接口格式
|
||||||
interface_label.grid(row=2, column=0, padx=5, pady=5, sticky="e")
|
label_frame = self.create_label_with_help(
|
||||||
interface_options = ["OpenAI", "Ollama", "ML Studio"]
|
parent=self.ai_config_tab,
|
||||||
|
label_text="LLM 接口格式:",
|
||||||
|
tooltip_key="interface_format",
|
||||||
|
row=2,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
interface_options = ["DeepSeek", "OpenAI", "Ollama", "ML Studio"]
|
||||||
interface_dropdown = ctk.CTkOptionMenu(
|
interface_dropdown = ctk.CTkOptionMenu(
|
||||||
self.ai_config_tab,
|
self.ai_config_tab,
|
||||||
values=interface_options,
|
values=interface_options,
|
||||||
@@ -238,15 +290,29 @@ class NovelGeneratorGUI:
|
|||||||
command=on_interface_format_changed,
|
command=on_interface_format_changed,
|
||||||
font=("Microsoft YaHei", 12)
|
font=("Microsoft YaHei", 12)
|
||||||
)
|
)
|
||||||
interface_dropdown.grid(row=2, column=1, padx=5, pady=5, sticky="nsew")
|
interface_dropdown.grid(row=2, column=1, padx=5, pady=5, columnspan=2, sticky="nsew")
|
||||||
|
|
||||||
model_name_label = ctk.CTkLabel(self.ai_config_tab, text="Model Name:", font=("Microsoft YaHei", 12))
|
# 4) Model Name
|
||||||
model_name_label.grid(row=3, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.ai_config_tab,
|
||||||
|
label_text="Model Name:",
|
||||||
|
tooltip_key="model_name",
|
||||||
|
row=3,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
model_name_entry = ctk.CTkEntry(self.ai_config_tab, textvariable=self.model_name_var, font=("Microsoft YaHei", 12))
|
model_name_entry = ctk.CTkEntry(self.ai_config_tab, textvariable=self.model_name_var, font=("Microsoft YaHei", 12))
|
||||||
model_name_entry.grid(row=3, column=1, padx=5, pady=5, sticky="nsew")
|
model_name_entry.grid(row=3, column=1, padx=5, pady=5, columnspan=2, sticky="nsew")
|
||||||
|
|
||||||
temp_label = ctk.CTkLabel(self.ai_config_tab, text="Temperature:", font=("Microsoft YaHei", 12))
|
# 5) Temperature
|
||||||
temp_label.grid(row=4, column=0, padx=5, pady=5, sticky="e")
|
temp_frame = self.create_label_with_help(
|
||||||
|
parent=self.ai_config_tab,
|
||||||
|
label_text="Temperature:",
|
||||||
|
tooltip_key="temperature",
|
||||||
|
row=4,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
|
||||||
def update_temp_label(value):
|
def update_temp_label(value):
|
||||||
self.temp_value_label.configure(text=f"{float(value):.2f}")
|
self.temp_value_label.configure(text=f"{float(value):.2f}")
|
||||||
@@ -265,7 +331,37 @@ class NovelGeneratorGUI:
|
|||||||
text=f"{self.temperature_var.get():.2f}",
|
text=f"{self.temperature_var.get():.2f}",
|
||||||
font=("Microsoft YaHei", 12)
|
font=("Microsoft YaHei", 12)
|
||||||
)
|
)
|
||||||
self.temp_value_label.grid(row=4, column=2, padx=1, pady=1, sticky="w")
|
self.temp_value_label.grid(row=4, column=2, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 6) Max Tokens
|
||||||
|
self.create_label_with_help(
|
||||||
|
parent=self.ai_config_tab,
|
||||||
|
label_text="Max Tokens:",
|
||||||
|
tooltip_key="max_tokens",
|
||||||
|
row=5,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_max_tokens_label(value):
|
||||||
|
self.max_tokens_value_label.configure(text=str(int(float(value))))
|
||||||
|
|
||||||
|
max_tokens_slider = ctk.CTkSlider(
|
||||||
|
self.ai_config_tab,
|
||||||
|
from_=0,
|
||||||
|
to=102400,
|
||||||
|
number_of_steps=100,
|
||||||
|
command=update_max_tokens_label,
|
||||||
|
variable=self.max_tokens_var
|
||||||
|
)
|
||||||
|
max_tokens_slider.grid(row=5, column=1, padx=5, pady=5, sticky="we")
|
||||||
|
|
||||||
|
self.max_tokens_value_label = ctk.CTkLabel(
|
||||||
|
self.ai_config_tab,
|
||||||
|
text=str(self.max_tokens_var.get()),
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
self.max_tokens_value_label.grid(row=5, column=2, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
def build_embeddings_config_tab(self):
|
def build_embeddings_config_tab(self):
|
||||||
def on_embedding_interface_changed(new_value):
|
def on_embedding_interface_changed(new_value):
|
||||||
@@ -275,20 +371,37 @@ class NovelGeneratorGUI:
|
|||||||
self.embedding_url_var.set("http://localhost:1234/v1")
|
self.embedding_url_var.set("http://localhost:1234/v1")
|
||||||
elif new_value == "OpenAI":
|
elif new_value == "OpenAI":
|
||||||
self.embedding_url_var.set("https://api.openai.com/v1")
|
self.embedding_url_var.set("https://api.openai.com/v1")
|
||||||
|
elif new_value == "DeepSeek":
|
||||||
|
self.embedding_url_var.set("https://api.deepseek.com/v1")
|
||||||
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
self.embeddings_config_tab.grid_rowconfigure(i, weight=0)
|
self.embeddings_config_tab.grid_rowconfigure(i, weight=0)
|
||||||
self.embeddings_config_tab.grid_columnconfigure(0, weight=0)
|
self.embeddings_config_tab.grid_columnconfigure(0, weight=0)
|
||||||
self.embeddings_config_tab.grid_columnconfigure(1, weight=1)
|
self.embeddings_config_tab.grid_columnconfigure(1, weight=1)
|
||||||
|
self.embeddings_config_tab.grid_columnconfigure(2, weight=0)
|
||||||
|
|
||||||
emb_api_key_label = ctk.CTkLabel(self.embeddings_config_tab, text="Embedding API Key:", font=("Microsoft YaHei", 12))
|
# 1) Embedding API Key
|
||||||
emb_api_key_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.embeddings_config_tab,
|
||||||
|
label_text="Embedding API Key:",
|
||||||
|
tooltip_key="embedding_api_key",
|
||||||
|
row=0,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
emb_api_key_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_api_key_var, font=("Microsoft YaHei", 12))
|
emb_api_key_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_api_key_var, font=("Microsoft YaHei", 12))
|
||||||
emb_api_key_entry.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
emb_api_key_entry.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
emb_interface_label = ctk.CTkLabel(self.embeddings_config_tab, text="Embedding 接口格式:", font=("Microsoft YaHei", 12))
|
# 2) Embedding 接口格式
|
||||||
emb_interface_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
emb_interface_options = ["OpenAI", "Ollama", "ML Studio"]
|
parent=self.embeddings_config_tab,
|
||||||
|
label_text="Embedding 接口格式:",
|
||||||
|
tooltip_key="embedding_interface_format",
|
||||||
|
row=1,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
emb_interface_options = ["DeepSeek", "OpenAI", "Ollama", "ML Studio"]
|
||||||
emb_interface_dropdown = ctk.CTkOptionMenu(
|
emb_interface_dropdown = ctk.CTkOptionMenu(
|
||||||
self.embeddings_config_tab,
|
self.embeddings_config_tab,
|
||||||
values=emb_interface_options,
|
values=emb_interface_options,
|
||||||
@@ -298,18 +411,39 @@ class NovelGeneratorGUI:
|
|||||||
)
|
)
|
||||||
emb_interface_dropdown.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")
|
emb_interface_dropdown.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
emb_url_label = ctk.CTkLabel(self.embeddings_config_tab, text="Embedding Base URL:", font=("Microsoft YaHei", 12))
|
# 3) Embedding Base URL
|
||||||
emb_url_label.grid(row=2, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.embeddings_config_tab,
|
||||||
|
label_text="Embedding Base URL:",
|
||||||
|
tooltip_key="embedding_url",
|
||||||
|
row=2,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
emb_url_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_url_var, font=("Microsoft YaHei", 12))
|
emb_url_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_url_var, font=("Microsoft YaHei", 12))
|
||||||
emb_url_entry.grid(row=2, column=1, padx=5, pady=5, sticky="nsew")
|
emb_url_entry.grid(row=2, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
emb_model_name_label = ctk.CTkLabel(self.embeddings_config_tab, text="Embedding Model Name:", font=("Microsoft YaHei", 12))
|
# 4) Embedding Model Name
|
||||||
emb_model_name_label.grid(row=3, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.embeddings_config_tab,
|
||||||
|
label_text="Embedding Model Name:",
|
||||||
|
tooltip_key="embedding_model_name",
|
||||||
|
row=3,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
emb_model_name_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_model_name_var, font=("Microsoft YaHei", 12))
|
emb_model_name_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_model_name_var, font=("Microsoft YaHei", 12))
|
||||||
emb_model_name_entry.grid(row=3, column=1, padx=5, pady=5, sticky="nsew")
|
emb_model_name_entry.grid(row=3, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
emb_retrieval_k_label = ctk.CTkLabel(self.embeddings_config_tab, text="Retrieval Top-K:", font=("Microsoft YaHei", 12))
|
# 5) Retrieval Top-K
|
||||||
emb_retrieval_k_label.grid(row=4, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.embeddings_config_tab,
|
||||||
|
label_text="Retrieval Top-K:",
|
||||||
|
tooltip_key="embedding_retrieval_k",
|
||||||
|
row=4,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
emb_retrieval_k_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_retrieval_k_var, font=("Microsoft YaHei", 12))
|
emb_retrieval_k_entry = ctk.CTkEntry(self.embeddings_config_tab, textvariable=self.embedding_retrieval_k_var, font=("Microsoft YaHei", 12))
|
||||||
emb_retrieval_k_entry.grid(row=4, column=1, padx=5, pady=5, sticky="nsew")
|
emb_retrieval_k_entry.grid(row=4, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
@@ -330,41 +464,72 @@ class NovelGeneratorGUI:
|
|||||||
self.params_frame.grid(row=start_row, column=0, sticky="nsew", padx=5, pady=5)
|
self.params_frame.grid(row=start_row, column=0, sticky="nsew", padx=5, pady=5)
|
||||||
self.params_frame.columnconfigure(1, weight=1)
|
self.params_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
topic_label = ctk.CTkLabel(self.params_frame, text="主题(Topic):", font=("Microsoft YaHei", 12))
|
# 1) 主题(Topic)
|
||||||
topic_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
topic_label_frame = self.create_label_with_help(
|
||||||
self.topic_text = ctk.CTkTextbox(self.params_frame,height=80, wrap="word", font=("Microsoft YaHei", 12))
|
parent=self.params_frame,
|
||||||
|
label_text="主题(Topic):",
|
||||||
|
tooltip_key="topic",
|
||||||
|
row=0,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12),
|
||||||
|
sticky="ne"
|
||||||
|
)
|
||||||
|
self.topic_text = ctk.CTkTextbox(self.params_frame, height=80, wrap="word", font=("Microsoft YaHei", 12))
|
||||||
self.topic_text.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
self.topic_text.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
if self.topic_default:
|
if self.topic_default:
|
||||||
self.topic_text.insert("0.0", self.topic_default)
|
self.topic_text.insert("0.0", self.topic_default)
|
||||||
|
|
||||||
genre_label = ctk.CTkLabel(self.params_frame, text="类型(Genre):", font=("Microsoft YaHei", 12))
|
# 2) 类型(Genre)
|
||||||
genre_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.params_frame,
|
||||||
|
label_text="类型(Genre):",
|
||||||
|
tooltip_key="genre",
|
||||||
|
row=1,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
genre_entry = ctk.CTkEntry(self.params_frame, textvariable=self.genre_var, font=("Microsoft YaHei", 12))
|
genre_entry = ctk.CTkEntry(self.params_frame, textvariable=self.genre_var, font=("Microsoft YaHei", 12))
|
||||||
genre_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
|
genre_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
|
# 3) 章节数 & 每章字数
|
||||||
row_for_chapter_and_word = 2
|
row_for_chapter_and_word = 2
|
||||||
num_chapters_label = ctk.CTkLabel(self.params_frame, text="章节数:", font=("Microsoft YaHei", 12))
|
chapter_word_frame = ctk.CTkFrame(self.params_frame)
|
||||||
num_chapters_label.grid(row=row_for_chapter_and_word, column=0, padx=5, pady=5, sticky="e")
|
chapter_word_frame.grid(row=row_for_chapter_and_word, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
chapter_word_frame.columnconfigure((0, 1, 2, 3), weight=0)
|
||||||
|
|
||||||
ch_word_frame = ctk.CTkFrame(self.params_frame)
|
# 左边标签
|
||||||
ch_word_frame.grid(row=row_for_chapter_and_word, column=1, padx=5, pady=5, sticky="ew")
|
label_frame = self.create_label_with_help(
|
||||||
ch_word_frame.columnconfigure((0, 1, 2, 3), weight=0)
|
parent=self.params_frame,
|
||||||
|
label_text="章节数 & 每章字数:",
|
||||||
|
tooltip_key="num_chapters",
|
||||||
|
row=row_for_chapter_and_word,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
|
|
||||||
num_chapters_entry = ctk.CTkEntry(ch_word_frame, textvariable=self.num_chapters_var, width=60, font=("Microsoft YaHei", 12))
|
# 输入框
|
||||||
num_chapters_entry.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
num_chapters_label = ctk.CTkLabel(chapter_word_frame, text="章节数:", font=("Microsoft YaHei", 12))
|
||||||
|
num_chapters_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
num_chapters_entry = ctk.CTkEntry(chapter_word_frame, textvariable=self.num_chapters_var, width=60, font=("Microsoft YaHei", 12))
|
||||||
|
num_chapters_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
word_number_label = ctk.CTkLabel(ch_word_frame, text="每章字数:", font=("Microsoft YaHei", 12))
|
word_number_label = ctk.CTkLabel(chapter_word_frame, text="每章字数:", font=("Microsoft YaHei", 12))
|
||||||
word_number_label.grid(row=0, column=1, padx=(15, 5), pady=5, sticky="e")
|
word_number_label.grid(row=0, column=2, padx=(15, 5), pady=5, sticky="e")
|
||||||
|
word_number_entry = ctk.CTkEntry(chapter_word_frame, textvariable=self.word_number_var, width=60, font=("Microsoft YaHei", 12))
|
||||||
word_number_entry = ctk.CTkEntry(ch_word_frame, textvariable=self.word_number_var, width=60, font=("Microsoft YaHei", 12))
|
word_number_entry.grid(row=0, column=3, padx=5, pady=5, sticky="w")
|
||||||
word_number_entry.grid(row=0, column=2, padx=5, pady=5, sticky="w")
|
|
||||||
|
|
||||||
# 保存路径
|
|
||||||
filepath_label = ctk.CTkLabel(self.params_frame, text="保存路径:", font=("Microsoft YaHei", 12))
|
|
||||||
filepath_label.grid(row=3, column=0, padx=5, pady=5, sticky="e")
|
|
||||||
|
|
||||||
|
# 4) 保存路径
|
||||||
|
row_fp = 3
|
||||||
|
self.create_label_with_help(
|
||||||
|
parent=self.params_frame,
|
||||||
|
label_text="保存路径:",
|
||||||
|
tooltip_key="filepath",
|
||||||
|
row=row_fp,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
self.filepath_frame = ctk.CTkFrame(self.params_frame)
|
self.filepath_frame = ctk.CTkFrame(self.params_frame)
|
||||||
self.filepath_frame.grid(row=3, column=1, padx=5, pady=5, sticky="nsew")
|
self.filepath_frame.grid(row=row_fp, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
self.filepath_frame.columnconfigure(0, weight=1)
|
self.filepath_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
filepath_entry = ctk.CTkEntry(self.filepath_frame, textvariable=self.filepath_var, font=("Microsoft YaHei", 12))
|
filepath_entry = ctk.CTkEntry(self.filepath_frame, textvariable=self.filepath_var, font=("Microsoft YaHei", 12))
|
||||||
@@ -372,43 +537,85 @@ class NovelGeneratorGUI:
|
|||||||
browse_btn = ctk.CTkButton(self.filepath_frame, text="浏览...", command=self.browse_folder, width=60, font=("Microsoft YaHei", 12))
|
browse_btn = ctk.CTkButton(self.filepath_frame, text="浏览...", command=self.browse_folder, width=60, font=("Microsoft YaHei", 12))
|
||||||
browse_btn.grid(row=0, column=1, padx=5, pady=5, sticky="e")
|
browse_btn.grid(row=0, column=1, padx=5, pady=5, sticky="e")
|
||||||
|
|
||||||
# 章节号
|
# 5) 章节号
|
||||||
chapter_num_label = ctk.CTkLabel(self.params_frame, text="章节号:", font=("Microsoft YaHei", 12))
|
row_chap_num = 4
|
||||||
chapter_num_label.grid(row=4, column=0, padx=5, pady=5, sticky="e")
|
self.create_label_with_help(
|
||||||
|
parent=self.params_frame,
|
||||||
|
label_text="章节号:",
|
||||||
|
tooltip_key="chapter_num",
|
||||||
|
row=row_chap_num,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
chapter_num_entry = ctk.CTkEntry(self.params_frame, textvariable=self.chapter_num_var, width=80, font=("Microsoft YaHei", 12))
|
chapter_num_entry = ctk.CTkEntry(self.params_frame, textvariable=self.chapter_num_var, width=80, font=("Microsoft YaHei", 12))
|
||||||
chapter_num_entry.grid(row=4, column=1, padx=5, pady=5, sticky="w")
|
chapter_num_entry.grid(row=row_chap_num, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
# 用户指导
|
# 6) 本章指导
|
||||||
guide_label = ctk.CTkLabel(self.params_frame, text="本章指导:", font=("Microsoft YaHei", 12))
|
row_user_guide = 5
|
||||||
guide_label.grid(row=5, column=0, padx=5, pady=5, sticky="ne")
|
guide_label_frame = self.create_label_with_help(
|
||||||
self.user_guide_text = ctk.CTkTextbox(self.params_frame,height=80, wrap="word", font=("Microsoft YaHei", 12))
|
parent=self.params_frame,
|
||||||
self.user_guide_text.grid(row=5, column=1, padx=5, pady=5, sticky="nsew")
|
label_text="本章指导:",
|
||||||
|
tooltip_key="user_guidance",
|
||||||
|
row=row_user_guide,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12),
|
||||||
|
sticky="ne"
|
||||||
|
)
|
||||||
|
self.user_guide_text = ctk.CTkTextbox(self.params_frame, height=80, wrap="word", font=("Microsoft YaHei", 12))
|
||||||
|
self.user_guide_text.grid(row=row_user_guide, column=1, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
# 新增:四个可选元素
|
# 7) 可选元素:核心人物/关键道具/空间坐标/时间压力
|
||||||
row_index = 6
|
row_idx = 6
|
||||||
|
# 核心人物
|
||||||
char_inv_label = ctk.CTkLabel(self.params_frame, text="核心人物:", font=("Microsoft YaHei", 12))
|
self.create_label_with_help(
|
||||||
char_inv_label.grid(row=row_index, column=0, padx=5, pady=5, sticky="e")
|
parent=self.params_frame,
|
||||||
|
label_text="核心人物:",
|
||||||
|
tooltip_key="characters_involved",
|
||||||
|
row=row_idx,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
char_inv_entry = ctk.CTkEntry(self.params_frame, textvariable=self.characters_involved_var, font=("Microsoft YaHei", 12))
|
char_inv_entry = ctk.CTkEntry(self.params_frame, textvariable=self.characters_involved_var, font=("Microsoft YaHei", 12))
|
||||||
char_inv_entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="ew")
|
char_inv_entry.grid(row=row_idx, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
row_idx += 1
|
||||||
|
|
||||||
row_index += 1
|
# 关键道具
|
||||||
key_items_label = ctk.CTkLabel(self.params_frame, text="关键道具:", font=("Microsoft YaHei", 12))
|
self.create_label_with_help(
|
||||||
key_items_label.grid(row=row_index, column=0, padx=5, pady=5, sticky="e")
|
parent=self.params_frame,
|
||||||
|
label_text="关键道具:",
|
||||||
|
tooltip_key="key_items",
|
||||||
|
row=row_idx,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
key_items_entry = ctk.CTkEntry(self.params_frame, textvariable=self.key_items_var, font=("Microsoft YaHei", 12))
|
key_items_entry = ctk.CTkEntry(self.params_frame, textvariable=self.key_items_var, font=("Microsoft YaHei", 12))
|
||||||
key_items_entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="ew")
|
key_items_entry.grid(row=row_idx, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
row_idx += 1
|
||||||
|
|
||||||
row_index += 1
|
# 空间坐标
|
||||||
scene_loc_label = ctk.CTkLabel(self.params_frame, text="空间坐标:", font=("Microsoft YaHei", 12))
|
self.create_label_with_help(
|
||||||
scene_loc_label.grid(row=row_index, column=0, padx=5, pady=5, sticky="e")
|
parent=self.params_frame,
|
||||||
|
label_text="空间坐标:",
|
||||||
|
tooltip_key="scene_location",
|
||||||
|
row=row_idx,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
scene_loc_entry = ctk.CTkEntry(self.params_frame, textvariable=self.scene_location_var, font=("Microsoft YaHei", 12))
|
scene_loc_entry = ctk.CTkEntry(self.params_frame, textvariable=self.scene_location_var, font=("Microsoft YaHei", 12))
|
||||||
scene_loc_entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="ew")
|
scene_loc_entry.grid(row=row_idx, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
row_idx += 1
|
||||||
|
|
||||||
row_index += 1
|
# 时间压力
|
||||||
time_const_label = ctk.CTkLabel(self.params_frame, text="时间压力:", font=("Microsoft YaHei", 12))
|
self.create_label_with_help(
|
||||||
time_const_label.grid(row=row_index, column=0, padx=5, pady=5, sticky="e")
|
parent=self.params_frame,
|
||||||
|
label_text="时间压力:",
|
||||||
|
tooltip_key="time_constraint",
|
||||||
|
row=row_idx,
|
||||||
|
column=0,
|
||||||
|
font=("Microsoft YaHei", 12)
|
||||||
|
)
|
||||||
time_const_entry = ctk.CTkEntry(self.params_frame, textvariable=self.time_constraint_var, font=("Microsoft YaHei", 12))
|
time_const_entry = ctk.CTkEntry(self.params_frame, textvariable=self.time_constraint_var, font=("Microsoft YaHei", 12))
|
||||||
time_const_entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="ew")
|
time_const_entry.grid(row=row_idx, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
def build_optional_buttons_area(self, start_row=2):
|
def build_optional_buttons_area(self, start_row=2):
|
||||||
self.optional_btn_frame = ctk.CTkFrame(self.right_frame)
|
self.optional_btn_frame = ctk.CTkFrame(self.right_frame)
|
||||||
@@ -456,11 +663,14 @@ class NovelGeneratorGUI:
|
|||||||
self.interface_format_var.set(cfg.get("interface_format", "OpenAI"))
|
self.interface_format_var.set(cfg.get("interface_format", "OpenAI"))
|
||||||
self.model_name_var.set(cfg.get("model_name", ""))
|
self.model_name_var.set(cfg.get("model_name", ""))
|
||||||
self.temperature_var.set(cfg.get("temperature", 0.7))
|
self.temperature_var.set(cfg.get("temperature", 0.7))
|
||||||
|
self.max_tokens_var.set(cfg.get("max_tokens", 2048))
|
||||||
|
|
||||||
self.embedding_api_key_var.set(cfg.get("embedding_api_key", ""))
|
self.embedding_api_key_var.set(cfg.get("embedding_api_key", ""))
|
||||||
self.embedding_interface_format_var.set(cfg.get("embedding_interface_format", "OpenAI"))
|
self.embedding_interface_format_var.set(cfg.get("embedding_interface_format", "OpenAI"))
|
||||||
self.embedding_url_var.set(cfg.get("embedding_url", ""))
|
self.embedding_url_var.set(cfg.get("embedding_url", ""))
|
||||||
self.embedding_model_name_var.set(cfg.get("embedding_model_name", ""))
|
self.embedding_model_name_var.set(cfg.get("embedding_model_name", ""))
|
||||||
self.embedding_retrieval_k_var.set(str(cfg.get("embedding_retrieval_k", 4)))
|
self.embedding_retrieval_k_var.set(str(cfg.get("embedding_retrieval_k", 4)))
|
||||||
|
|
||||||
self.genre_var.set(cfg.get("genre", ""))
|
self.genre_var.set(cfg.get("genre", ""))
|
||||||
self.num_chapters_var.set(str(cfg.get("num_chapters", 10)))
|
self.num_chapters_var.set(str(cfg.get("num_chapters", 10)))
|
||||||
self.word_number_var.set(str(cfg.get("word_number", 3000)))
|
self.word_number_var.set(str(cfg.get("word_number", 3000)))
|
||||||
@@ -481,6 +691,7 @@ class NovelGeneratorGUI:
|
|||||||
"interface_format": self.interface_format_var.get(),
|
"interface_format": self.interface_format_var.get(),
|
||||||
"model_name": self.model_name_var.get(),
|
"model_name": self.model_name_var.get(),
|
||||||
"temperature": self.temperature_var.get(),
|
"temperature": self.temperature_var.get(),
|
||||||
|
"max_tokens": self.max_tokens_var.get(),
|
||||||
|
|
||||||
"embedding_api_key": self.embedding_api_key_var.get(),
|
"embedding_api_key": self.embedding_api_key_var.get(),
|
||||||
"embedding_interface_format": self.embedding_interface_format_var.get(),
|
"embedding_interface_format": self.embedding_interface_format_var.get(),
|
||||||
@@ -535,10 +746,12 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_generate_architecture)
|
self.disable_button_safe(self.btn_generate_architecture)
|
||||||
try:
|
try:
|
||||||
|
interface_format = self.interface_format_var.get().strip()
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
base_url = self.base_url_var.get().strip()
|
base_url = self.base_url_var.get().strip()
|
||||||
model_name = self.model_name_var.get().strip()
|
model_name = self.model_name_var.get().strip()
|
||||||
temperature = self.temperature_var.get()
|
temperature = self.temperature_var.get()
|
||||||
|
max_tokens = self.max_tokens_var.get()
|
||||||
|
|
||||||
topic = self.topic_text.get("0.0", "end").strip()
|
topic = self.topic_text.get("0.0", "end").strip()
|
||||||
genre = self.genre_var.get().strip()
|
genre = self.genre_var.get().strip()
|
||||||
@@ -547,6 +760,7 @@ class NovelGeneratorGUI:
|
|||||||
|
|
||||||
self.safe_log("开始生成小说架构...")
|
self.safe_log("开始生成小说架构...")
|
||||||
Novel_architecture_generate(
|
Novel_architecture_generate(
|
||||||
|
interface_format=interface_format,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
llm_model=model_name,
|
llm_model=model_name,
|
||||||
@@ -555,7 +769,8 @@ class NovelGeneratorGUI:
|
|||||||
number_of_chapters=num_chapters,
|
number_of_chapters=num_chapters,
|
||||||
word_number=word_number,
|
word_number=word_number,
|
||||||
filepath=filepath,
|
filepath=filepath,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
self.safe_log("✅ 小说架构生成完成。请在 'Novel Architecture' 标签页查看或编辑。")
|
self.safe_log("✅ 小说架构生成完成。请在 'Novel Architecture' 标签页查看或编辑。")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -575,18 +790,22 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_generate_directory)
|
self.disable_button_safe(self.btn_generate_directory)
|
||||||
try:
|
try:
|
||||||
|
interface_format = self.interface_format_var.get().strip()
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
base_url = self.base_url_var.get().strip()
|
base_url = self.base_url_var.get().strip()
|
||||||
model_name = self.model_name_var.get().strip()
|
model_name = self.model_name_var.get().strip()
|
||||||
temperature = self.temperature_var.get()
|
temperature = self.temperature_var.get()
|
||||||
|
max_tokens = self.max_tokens_var.get()
|
||||||
|
|
||||||
self.safe_log("开始生成章节蓝图...")
|
self.safe_log("开始生成章节蓝图...")
|
||||||
Chapter_blueprint_generate(
|
Chapter_blueprint_generate(
|
||||||
|
interface_format=interface_format,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
llm_model=model_name,
|
llm_model=model_name,
|
||||||
filepath=filepath,
|
filepath=filepath,
|
||||||
temperature=temperature
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
self.safe_log("✅ 章节蓝图生成完成。请在 'Chapter Blueprint' 标签页查看或编辑。")
|
self.safe_log("✅ 章节蓝图生成完成。请在 'Chapter Blueprint' 标签页查看或编辑。")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -606,24 +825,22 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_generate_chapter)
|
self.disable_button_safe(self.btn_generate_chapter)
|
||||||
try:
|
try:
|
||||||
# LLM相关
|
interface_format = self.interface_format_var.get().strip()
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
base_url = self.base_url_var.get().strip()
|
base_url = self.base_url_var.get().strip()
|
||||||
model_name = self.model_name_var.get().strip()
|
model_name = self.model_name_var.get().strip()
|
||||||
temperature = self.temperature_var.get()
|
temperature = self.temperature_var.get()
|
||||||
|
max_tokens = self.max_tokens_var.get()
|
||||||
|
|
||||||
# 章节信息
|
|
||||||
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
||||||
word_number = self.safe_get_int(self.word_number_var, 3000)
|
word_number = self.safe_get_int(self.word_number_var, 3000)
|
||||||
user_guidance = self.user_guide_text.get("0.0", "end").strip()
|
user_guidance = self.user_guide_text.get("0.0", "end").strip()
|
||||||
|
|
||||||
# 新增四个可选要素
|
|
||||||
char_inv = self.characters_involved_var.get().strip()
|
char_inv = self.characters_involved_var.get().strip()
|
||||||
key_items = self.key_items_var.get().strip()
|
key_items = self.key_items_var.get().strip()
|
||||||
scene_loc = self.scene_location_var.get().strip()
|
scene_loc = self.scene_location_var.get().strip()
|
||||||
time_constr = self.time_constraint_var.get().strip()
|
time_constr = self.time_constraint_var.get().strip()
|
||||||
|
|
||||||
# Embedding相关
|
|
||||||
embedding_api_key = self.embedding_api_key_var.get().strip()
|
embedding_api_key = self.embedding_api_key_var.get().strip()
|
||||||
embedding_url = self.embedding_url_var.get().strip()
|
embedding_url = self.embedding_url_var.get().strip()
|
||||||
embedding_interface_format = self.embedding_interface_format_var.get().strip()
|
embedding_interface_format = self.embedding_interface_format_var.get().strip()
|
||||||
@@ -648,7 +865,9 @@ class NovelGeneratorGUI:
|
|||||||
embedding_url=embedding_url,
|
embedding_url=embedding_url,
|
||||||
embedding_interface_format=embedding_interface_format,
|
embedding_interface_format=embedding_interface_format,
|
||||||
embedding_model_name=embedding_model_name,
|
embedding_model_name=embedding_model_name,
|
||||||
embedding_retrieval_k=embedding_k
|
embedding_retrieval_k=embedding_k,
|
||||||
|
interface_format=interface_format,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
if draft_text:
|
if draft_text:
|
||||||
self.safe_log(f"✅ 第{chap_num}章草稿生成完成。请在左侧查看或编辑。")
|
self.safe_log(f"✅ 第{chap_num}章草稿生成完成。请在左侧查看或编辑。")
|
||||||
@@ -678,24 +897,22 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_finalize_chapter)
|
self.disable_button_safe(self.btn_finalize_chapter)
|
||||||
try:
|
try:
|
||||||
# LLM相关
|
interface_format = self.interface_format_var.get().strip()
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
base_url = self.base_url_var.get().strip()
|
base_url = self.base_url_var.get().strip()
|
||||||
model_name = self.model_name_var.get().strip()
|
model_name = self.model_name_var.get().strip()
|
||||||
temperature = self.temperature_var.get()
|
temperature = self.temperature_var.get()
|
||||||
|
max_tokens = self.max_tokens_var.get()
|
||||||
|
|
||||||
# Embedding相关
|
|
||||||
embedding_api_key = self.embedding_api_key_var.get().strip()
|
embedding_api_key = self.embedding_api_key_var.get().strip()
|
||||||
embedding_url = self.embedding_url_var.get().strip()
|
embedding_url = self.embedding_url_var.get().strip()
|
||||||
embedding_interface_format = self.embedding_interface_format_var.get().strip()
|
embedding_interface_format = self.embedding_interface_format_var.get().strip()
|
||||||
embedding_model_name = self.embedding_model_name_var.get().strip()
|
embedding_model_name = self.embedding_model_name_var.get().strip()
|
||||||
|
|
||||||
# 章节参数
|
|
||||||
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
||||||
word_number = self.safe_get_int(self.word_number_var, 3000)
|
word_number = self.safe_get_int(self.word_number_var, 3000)
|
||||||
|
|
||||||
self.safe_log(f"开始定稿第{chap_num}章...")
|
self.safe_log(f"开始定稿第{chap_num}章...")
|
||||||
# 先保存用户在左侧编辑框中的修改
|
|
||||||
chapters_dir = os.path.join(filepath, "chapters")
|
chapters_dir = os.path.join(filepath, "chapters")
|
||||||
os.makedirs(chapters_dir, exist_ok=True)
|
os.makedirs(chapters_dir, exist_ok=True)
|
||||||
chapter_file = os.path.join(chapters_dir, f"chapter_{chap_num}.txt")
|
chapter_file = os.path.join(chapters_dir, f"chapter_{chap_num}.txt")
|
||||||
@@ -714,7 +931,9 @@ class NovelGeneratorGUI:
|
|||||||
embedding_api_key=embedding_api_key,
|
embedding_api_key=embedding_api_key,
|
||||||
embedding_url=embedding_url,
|
embedding_url=embedding_url,
|
||||||
embedding_interface_format=embedding_interface_format,
|
embedding_interface_format=embedding_interface_format,
|
||||||
embedding_model_name=embedding_model_name
|
embedding_model_name=embedding_model_name,
|
||||||
|
interface_format=interface_format,
|
||||||
|
max_tokens=max_tokens
|
||||||
)
|
)
|
||||||
self.safe_log(f"✅ 第{chap_num}章定稿完成(已更新全局摘要、角色状态、向量库)。")
|
self.safe_log(f"✅ 第{chap_num}章定稿完成(已更新全局摘要、角色状态、向量库)。")
|
||||||
|
|
||||||
@@ -846,7 +1065,6 @@ class NovelGeneratorGUI:
|
|||||||
text_area.configure(state="disabled")
|
text_area.configure(state="disabled")
|
||||||
|
|
||||||
# ============ 其余标签页: Novel Architecture, Chapter Blueprint, Character State, Summary ============
|
# ============ 其余标签页: Novel Architecture, Chapter Blueprint, Character State, Summary ============
|
||||||
|
|
||||||
def build_setting_tab(self):
|
def build_setting_tab(self):
|
||||||
self.setting_tab.rowconfigure(0, weight=0)
|
self.setting_tab.rowconfigure(0, weight=0)
|
||||||
self.setting_tab.rowconfigure(1, weight=1)
|
self.setting_tab.rowconfigure(1, weight=1)
|
||||||
@@ -1164,6 +1382,7 @@ class NovelGeneratorGUI:
|
|||||||
else:
|
else:
|
||||||
messagebox.showinfo("提示", "已经是最后一章了。")
|
messagebox.showinfo("提示", "已经是最后一章了。")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = ctk.CTk()
|
app = ctk.CTk()
|
||||||
gui = NovelGeneratorGUI(app)
|
gui = NovelGeneratorGUI(app)
|
||||||
|
|||||||
Reference in New Issue
Block a user