diff --git a/ArticleReplace.py b/ArticleReplace.py index ddc5f47..7c0d049 100644 --- a/ArticleReplace.py +++ b/ArticleReplace.py @@ -1,16 +1,15 @@ import json -import sys # 导入sys模块 +import sys from PIL import Image, ImageDraw, ImageFont, ImageEnhance import time import random import threading -import tkinter as tk +import customtkinter as ctk from config import * -from tkinter import ttk, messagebox, filedialog, simpledialog -from tkinter.scrolledtext import ScrolledText +from tkinter import messagebox, filedialog, simpledialog import pandas as pd import pymysql @@ -21,28 +20,29 @@ from auth_validator import AuthValidator sys.setrecursionlimit(5000) -class ArticleReplaceApp(tk.Tk): +class ArticleReplaceApp(ctk.CTk): def __init__(self): super().__init__() self.title("文章工作流调用工具(软件仅供交流使用)") self.geometry("900x600") + # 设置CustomTkinter外观模式 + ctk.set_appearance_mode("System") + ctk.set_default_color_theme("blue") + # 创建标签页控件 - self.notebook = ttk.Notebook(self) - self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + self.notebook = ctk.CTkTabview(self) + self.notebook.pack(fill=ctk.BOTH, expand=True, padx=10, pady=10) # 创建主页面 - self.main_frame = ttk.Frame(self.notebook) - self.notebook.add(self.main_frame, text="主页面") + self.main_frame = self.notebook.add("主页面") # 创建配置页面 - self.config_frame = ttk.Frame(self.notebook) - self.notebook.add(self.config_frame, text="配置") + self.config_frame = self.notebook.add("配置") # 创建免责声明页面 - self.disclaimer_frame = ttk.Frame(self.notebook) - self.notebook.add(self.disclaimer_frame, text="免责声明") + self.disclaimer_frame = self.notebook.add("免责声明") # 初始化变量 self.running = False @@ -51,11 +51,10 @@ class ArticleReplaceApp(tk.Tk): self.processed_links = 0 # 初始化Coze配置变量 - self.template_name_var = tk.StringVar() - self.coze_workflow_id_var = tk.StringVar(value=CONFIG['Coze']['workflow_id']) - self.coze_access_token_var = tk.StringVar(value=CONFIG['Coze']['access_token']) - self.coze_is_async_var = tk.StringVar(value=CONFIG['Coze'].get('is_async', 'true')) - # self.coze_input_data_template_var = tk.StringVar(value=CONFIG['Coze'].get('input_data_template', '{"article": "{article_text}", "link":"{link}", "weijin":"{weijin}"}')) + self.template_name_var = ctk.StringVar() + self.coze_workflow_id_var = ctk.StringVar(value=CONFIG['Coze']['workflow_id']) + self.coze_access_token_var = ctk.StringVar(value=CONFIG['Coze']['access_token']) + self.coze_is_async_var = ctk.StringVar(value=CONFIG['Coze'].get('is_async', 'true')) # 初始化模板数据结构 self.templates = { @@ -75,57 +74,55 @@ class ArticleReplaceApp(tk.Tk): def init_main_frame(self): # 创建左侧控制面板 - control_frame = ttk.LabelFrame(self.main_frame, text="控制面板") - control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10) + control_frame = ctk.CTkFrame(self.main_frame, fg_color="transparent") + control_frame.pack(side=ctk.LEFT, fill=ctk.Y, padx=10, pady=10) # Excel文件选择 - ttk.Label(control_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.excel_path_var = tk.StringVar(value=TITLE_BASE_PATH) - ttk.Entry(control_frame, textvariable=self.excel_path_var, width=30).grid(row=0, column=1, padx=5, pady=5) - ttk.Button(control_frame, text="浏览", command=self.browse_excel).grid(row=0, column=2, padx=5, pady=5) + ctk.CTkLabel(control_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.excel_path_var = ctk.StringVar(value=TITLE_BASE_PATH) + ctk.CTkEntry(control_frame, textvariable=self.excel_path_var, width=150).grid(row=0, column=1, padx=5, pady=5) + ctk.CTkButton(control_frame, text="浏览", command=self.browse_excel, width=60).grid(row=0, column=2, padx=5, + pady=5) # 线程数设置 - ttk.Label(control_frame, text="线程数:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - self.thread_count_var = tk.StringVar(value="1") - ttk.Spinbox(control_frame, from_=1, to=MAX_THREADS, textvariable=self.thread_count_var, width=5).grid(row=1, - column=1, - padx=5, - pady=5, - sticky=tk.W) + ctk.CTkLabel(control_frame, text="线程数:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + self.thread_count_var = ctk.StringVar(value="1") + ctk.CTkEntry(control_frame, textvariable=self.thread_count_var, width=50).grid(row=1, column=1, padx=5, pady=5, + sticky=ctk.W) # AI服务提供商选择 - ttk.Label(control_frame, text="工作流选择:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - self.ai_service_var = tk.StringVar(value="coze") - ai_service_combo = ttk.Combobox(control_frame, textvariable=self.ai_service_var, values=["dify", "coze"], - width=10, state="readonly") - ai_service_combo.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W) + ctk.CTkLabel(control_frame, text="工作流选择:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + self.ai_service_var = ctk.StringVar(value="coze") + ai_service_combo = ctk.CTkComboBox(control_frame, variable=self.ai_service_var, values=["dify", "coze"], + width=120, state="readonly") + ai_service_combo.grid(row=2, column=1, padx=5, pady=5, sticky=ctk.W) # 生成类型选择 - ttk.Label(control_frame, text="生成类型:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) - self.generation_type_var = tk.StringVar(value="文章") - self.generation_type_combo = ttk.Combobox(control_frame, textvariable=self.generation_type_var, - values=["短篇", "文章"], width=10, state="readonly") - self.generation_type_combo.grid(row=3, column=1, padx=5, pady=5, sticky=tk.W) - self.generation_type_combo.bind("<>", self.on_generation_type_changed) + ctk.CTkLabel(control_frame, text="生成类型:").grid(row=3, column=0, padx=5, pady=5, sticky=ctk.W) + self.generation_type_var = ctk.StringVar(value="文章") + self.generation_type_combo = ctk.CTkComboBox(control_frame, variable=self.generation_type_var, + values=["短篇", "文章"], width=120, state="readonly") + self.generation_type_combo.grid(row=3, column=1, padx=5, pady=5, sticky=ctk.W) + self.generation_type_combo.configure(command=self.on_generation_type_changed) # 开始按钮 - self.start_button = ttk.Button(control_frame, text="开始处理", command=self.start_processing) + self.start_button = ctk.CTkButton(control_frame, text="开始处理", command=self.start_processing) self.start_button.grid(row=4, column=0, columnspan=3, padx=5, pady=20) # 进度条 - ttk.Label(control_frame, text="处理进度:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W) - self.progress_var = tk.DoubleVar() - ttk.Progressbar(control_frame, variable=self.progress_var, maximum=100).grid(row=5, column=1, columnspan=2, - padx=5, pady=5, sticky=tk.EW) + ctk.CTkLabel(control_frame, text="处理进度:").grid(row=5, column=0, padx=5, pady=5, sticky=ctk.W) + self.progress_var = ctk.DoubleVar() + self.progress_bar = ctk.CTkProgressBar(control_frame, variable=self.progress_var) + self.progress_bar.grid(row=5, column=1, columnspan=2, padx=5, pady=5, sticky=ctk.EW) + self.progress_bar.set(0) # 创建右侧日志面板 - log_frame = ttk.LabelFrame(self.main_frame, text="日志") - log_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10) + log_frame = ctk.CTkFrame(self.main_frame, fg_color="transparent") + log_frame.pack(side=ctk.RIGHT, fill=ctk.BOTH, expand=True, padx=10, pady=10) - # 日志文本框 - self.log_text = ScrolledText(log_frame, width=70, height=30) - self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - self.log_text.config(state=tk.DISABLED) + # 日志文本框 - 使用CTkTextbox替代ScrolledText + self.log_text = ctk.CTkTextbox(log_frame, width=500, height=400) + self.log_text.pack(fill=ctk.BOTH, expand=True, padx=5, pady=5) # 添加日志处理器 self.log_handler = LogTextHandler(self.log_text) @@ -136,26 +133,17 @@ class ArticleReplaceApp(tk.Tk): def init_config_frame(self): # 创建配置标签页 - config_notebook = ttk.Notebook(self.config_frame) - config_notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + config_notebook = ctk.CTkTabview(self.config_frame) + config_notebook.pack(fill=ctk.BOTH, expand=True, padx=5, pady=5) # 创建各个配置页面 - general_frame = ttk.Frame(config_notebook) - database_frame = ttk.Frame(config_notebook) - dify_frame = ttk.Frame(config_notebook) - coze_frame = ttk.Frame(config_notebook) - baidu_frame = ttk.Frame(config_notebook) - image_frame = ttk.Frame(config_notebook) - keywords_frame = ttk.Frame(config_notebook) - - # 添加到标签页 - config_notebook.add(general_frame, text="常规设置") - config_notebook.add(database_frame, text="数据库设置") - config_notebook.add(dify_frame, text="Dify设置") - config_notebook.add(coze_frame, text="Coze设置") - config_notebook.add(baidu_frame, text="百度API设置") - config_notebook.add(image_frame, text="图片处理设置") - config_notebook.add(keywords_frame, text="违禁词设置") + general_frame = config_notebook.add("常规设置") + database_frame = config_notebook.add("数据库设置") + dify_frame = config_notebook.add("Dify设置") + coze_frame = config_notebook.add("Coze设置") + baidu_frame = config_notebook.add("百度API设置") + image_frame = config_notebook.add("图片处理设置") + keywords_frame = config_notebook.add("违禁词设置") # 初始化各个配置页面 self.init_general_config(general_frame) @@ -167,133 +155,127 @@ class ArticleReplaceApp(tk.Tk): self.init_keywords_config(keywords_frame) # 保存按钮 - save_button = ttk.Button(self.config_frame, text="保存所有配置", command=self.save_all_configs) - save_button.pack(side=tk.RIGHT, padx=10, pady=10) + save_button = ctk.CTkButton(self.config_frame, text="保存所有配置", command=self.save_all_configs) + save_button.pack(side=ctk.RIGHT, padx=10, pady=10) def init_general_config(self, parent): # Chrome用户目录 - ttk.Label(parent, text="Chrome用户目录:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.chrome_dir_var = tk.StringVar(value=CONFIG['General']['chrome_user_dir']) - ttk.Entry(parent, textvariable=self.chrome_dir_var, width=50).grid(row=0, column=1, padx=5, pady=5) - ttk.Button(parent, text="浏览", command=lambda: self.browse_directory(self.chrome_dir_var)).grid(row=0, - column=2, - padx=5, pady=5) + ctk.CTkLabel(parent, text="Chrome用户目录:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.chrome_dir_var = ctk.StringVar(value=CONFIG['General']['chrome_user_dir']) + ctk.CTkEntry(parent, textvariable=self.chrome_dir_var, width=200).grid(row=0, column=1, padx=5, pady=5) + ctk.CTkButton(parent, text="浏览", command=lambda: self.browse_directory(self.chrome_dir_var), width=60).grid( + row=0, column=2, padx=5, pady=5) # 文章保存路径 - ttk.Label(parent, text="文章保存路径:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - self.articles_path_var = tk.StringVar(value=CONFIG['General']['articles_path']) - ttk.Entry(parent, textvariable=self.articles_path_var, width=50).grid(row=1, column=1, padx=5, pady=5) - ttk.Button(parent, text="浏览", command=lambda: self.browse_directory(self.articles_path_var)).grid(row=1, - column=2, - padx=5, - pady=5) + ctk.CTkLabel(parent, text="文章保存路径:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + self.articles_path_var = ctk.StringVar(value=CONFIG['General']['articles_path']) + ctk.CTkEntry(parent, textvariable=self.articles_path_var, width=200).grid(row=1, column=1, padx=5, pady=5) + ctk.CTkButton(parent, text="浏览", command=lambda: self.browse_directory(self.articles_path_var), + width=60).grid(row=1, column=2, padx=5, pady=5) # 图片保存路径 - ttk.Label(parent, text="图片保存路径:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - self.images_path_var = tk.StringVar(value=CONFIG['General']['images_path']) - self.grid = ttk.Entry(parent, textvariable=self.images_path_var, width=50).grid(row=2, column=1, padx=5, pady=5) - ttk.Button(parent, text="浏览", command=lambda: self.browse_directory(self.images_path_var)).grid(row=2, - column=2, - padx=5, - pady=5) + ctk.CTkLabel(parent, text="图片保存路径:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + self.images_path_var = ctk.StringVar(value=CONFIG['General']['images_path']) + ctk.CTkEntry(parent, textvariable=self.images_path_var, width=200).grid(row=2, column=1, padx=5, pady=5) + ctk.CTkButton(parent, text="浏览", command=lambda: self.browse_directory(self.images_path_var), width=60).grid( + row=2, column=2, padx=5, pady=5) # Excel文件路径 - ttk.Label(parent, text="默认Excel文件:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) - self.excel_file_var = tk.StringVar(value=CONFIG['General']['title_file']) - ttk.Entry(parent, textvariable=self.excel_file_var, width=50).grid(row=3, column=1, padx=5, pady=5) - ttk.Button(parent, text="浏览", command=lambda: self.browse_file(self.excel_file_var, [("Excel文件", "*.xlsx"), - ("所有文件", - "*.*")])).grid(row=3, - column=2, - padx=5, - pady=5) + ctk.CTkLabel(parent, text="默认Excel文件:").grid(row=3, column=0, padx=5, pady=5, sticky=ctk.W) + self.excel_file_var = ctk.StringVar(value=CONFIG['General']['title_file']) + ctk.CTkEntry(parent, textvariable=self.excel_file_var, width=200).grid(row=3, column=1, padx=5, pady=5) + ctk.CTkButton(parent, text="浏览", command=lambda: self.browse_file(self.excel_file_var, + [("Excel文件", "*.xlsx"), + ("所有文件", "*.*")]), width=60).grid( + row=3, column=2, padx=5, pady=5) # 最大线程数 - ttk.Label(parent, text="最大线程数:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W) - self.max_threads_var = tk.StringVar(value=CONFIG['General']['max_threads']) - ttk.Spinbox(parent, from_=1, to=10, textvariable=self.max_threads_var, width=5).grid(row=4, column=1, padx=5, - pady=5, sticky=tk.W) + ctk.CTkLabel(parent, text="最大线程数:").grid(row=4, column=0, padx=5, pady=5, sticky=ctk.W) + self.max_threads_var = ctk.StringVar(value=CONFIG['General']['max_threads']) + ctk.CTkEntry(parent, textvariable=self.max_threads_var, width=50).grid(row=4, column=1, padx=5, pady=5, + sticky=ctk.W) # 最小文章字数 - ttk.Label(parent, text="最小文章字数:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W) - self.min_article_length_var = tk.StringVar(value=CONFIG['General'].get('min_article_length', '100')) - ttk.Spinbox(parent, from_=0, to=10000, textvariable=self.min_article_length_var, width=5).grid(row=5, column=1, padx=5, - pady=5, sticky=tk.W) + ctk.CTkLabel(parent, text="最小文章字数:").grid(row=5, column=0, padx=5, pady=5, sticky=ctk.W) + self.min_article_length_var = ctk.StringVar(value=CONFIG['General'].get('min_article_length', '100')) + ctk.CTkEntry(parent, textvariable=self.min_article_length_var, width=50).grid(row=5, column=1, padx=5, pady=5, + sticky=ctk.W) # 保存按钮 - ttk.Button(parent, text="保存配置", command=self.save_general_config).grid(row=6, column=1, padx=5, pady=10, - sticky=tk.E) + ctk.CTkButton(parent, text="保存配置", command=self.save_general_config).grid(row=6, column=1, padx=5, pady=10, + sticky=ctk.E) def init_database_config(self, parent): # 数据库主机 - ttk.Label(parent, text="数据库主机:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.db_host_var = tk.StringVar(value=CONFIG['Database']['host']) - ttk.Entry(parent, textvariable=self.db_host_var, width=30).grid(row=0, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="数据库主机:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.db_host_var = ctk.StringVar(value=CONFIG['Database']['host']) + ctk.CTkEntry(parent, textvariable=self.db_host_var, width=150).grid(row=0, column=1, padx=5, pady=5) # 数据库用户名 - ttk.Label(parent, text="数据库用户名:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - self.db_user_var = tk.StringVar(value=CONFIG['Database']['user']) - ttk.Entry(parent, textvariable=self.db_user_var, width=30).grid(row=1, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="数据库用户名:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + self.db_user_var = ctk.StringVar(value=CONFIG['Database']['user']) + ctk.CTkEntry(parent, textvariable=self.db_user_var, width=150).grid(row=1, column=1, padx=5, pady=5) # 数据库密码 - ttk.Label(parent, text="数据库密码:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - self.db_password_var = tk.StringVar(value=CONFIG['Database']['password']) - ttk.Entry(parent, textvariable=self.db_password_var, width=30, show="*").grid(row=2, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="数据库密码:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + self.db_password_var = ctk.StringVar(value=CONFIG['Database']['password']) + ctk.CTkEntry(parent, textvariable=self.db_password_var, width=150, show="*").grid(row=2, column=1, padx=5, + pady=5) # 数据库名称 - ttk.Label(parent, text="数据库名称:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) - self.db_name_var = tk.StringVar(value=CONFIG['Database']['database']) - ttk.Entry(parent, textvariable=self.db_name_var, width=30).grid(row=3, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="数据库名称:").grid(row=3, column=0, padx=5, pady=5, sticky=ctk.W) + self.db_name_var = ctk.StringVar(value=CONFIG['Database']['database']) + ctk.CTkEntry(parent, textvariable=self.db_name_var, width=150).grid(row=3, column=1, padx=5, pady=5) # 测试连接按钮 - ttk.Button(parent, text="测试连接", command=self.test_db_connection).grid(row=4, column=1, padx=5, pady=5, - sticky=tk.E) + ctk.CTkButton(parent, text="测试连接", command=self.test_db_connection).grid(row=4, column=1, padx=5, pady=5, + sticky=ctk.E) # 保存按钮 - ttk.Button(parent, text="保存配置", command=self.save_database_config).grid(row=5, column=1, padx=5, pady=10, - sticky=tk.E) + ctk.CTkButton(parent, text="保存配置", command=self.save_database_config).grid(row=5, column=1, padx=5, pady=10, + sticky=ctk.E) def init_dify_config(self, parent): # Dify API Key - ttk.Label(parent, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.dify_api_key_var = tk.StringVar(value=CONFIG['Dify']['api_key']) - ttk.Entry(parent, textvariable=self.dify_api_key_var, width=50).grid(row=0, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.dify_api_key_var = ctk.StringVar(value=CONFIG['Dify']['api_key']) + ctk.CTkEntry(parent, textvariable=self.dify_api_key_var, width=200).grid(row=0, column=1, padx=5, pady=5) # Dify User ID - ttk.Label(parent, text="User ID:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - self.dify_user_id_var = tk.StringVar(value=CONFIG['Dify']['user_id']) - ttk.Entry(parent, textvariable=self.dify_user_id_var, width=30).grid(row=1, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="User ID:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + self.dify_user_id_var = ctk.StringVar(value=CONFIG['Dify']['user_id']) + ctk.CTkEntry(parent, textvariable=self.dify_user_id_var, width=150).grid(row=1, column=1, padx=5, pady=5) # Dify URL - ttk.Label(parent, text="URL:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - self.dify_url_var = tk.StringVar(value=CONFIG['Dify']['url']) - ttk.Entry(parent, textvariable=self.dify_url_var, width=50).grid(row=2, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="URL:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + self.dify_url_var = ctk.StringVar(value=CONFIG['Dify']['url']) + ctk.CTkEntry(parent, textvariable=self.dify_url_var, width=200).grid(row=2, column=1, padx=5, pady=5) # Dify Input Data Template - ttk.Label(parent, text="Input Data模板:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) - self.dify_input_data_template_var = tk.StringVar( - value=CONFIG['Dify'].get('input_data_template', '{"old_article": "{article_text}"}')) # 添加默认值 - ttk.Entry(parent, textvariable=self.dify_input_data_template_var, width=50).grid(row=3, column=1, padx=5, - pady=5) + ctk.CTkLabel(parent, text="Input Data模板:").grid(row=3, column=0, padx=5, pady=5, sticky=ctk.W) + self.dify_input_data_template_var = ctk.StringVar( + value=CONFIG['Dify'].get('input_data_template', '{"old_article": "{article_text"}')) + ctk.CTkEntry(parent, textvariable=self.dify_input_data_template_var, width=200).grid(row=3, column=1, padx=5, + pady=5) # 保存按钮 - ttk.Button(parent, text="保存配置", command=self.save_dify_config).grid(row=4, column=1, padx=5, pady=10, - sticky=tk.E) + ctk.CTkButton(parent, text="保存配置", command=self.save_dify_config).grid(row=4, column=1, padx=5, pady=10, + sticky=ctk.E) def init_coze_config(self, parent): # 生成类型选择(与主页面联动) - type_frame = ttk.Frame(parent) - type_frame.grid(row=0, column=0, columnspan=3, padx=5, pady=5, sticky=tk.EW) - ttk.Label(type_frame, text="生成类型:").pack(side=tk.LEFT, padx=5) - self.coze_generation_type_var = tk.StringVar(value="短篇") - self.coze_generation_type_combo = ttk.Combobox(type_frame, textvariable=self.coze_generation_type_var, - values=["短篇", "文章"], width=10, state="readonly") - self.coze_generation_type_combo.pack(side=tk.LEFT, padx=5) - self.coze_generation_type_combo.bind("<>", self.on_coze_generation_type_changed) + type_frame = ctk.CTkFrame(parent, fg_color="transparent") + type_frame.grid(row=0, column=0, columnspan=3, padx=5, pady=5, sticky=ctk.EW) + ctk.CTkLabel(type_frame, text="生成类型:").pack(side=ctk.LEFT, padx=5) + self.coze_generation_type_var = ctk.StringVar(value="短篇") + self.coze_generation_type_combo = ctk.CTkComboBox(type_frame, variable=self.coze_generation_type_var, + values=["短篇", "文章"], width=120, state="readonly") + self.coze_generation_type_combo.pack(side=ctk.LEFT, padx=5) + self.coze_generation_type_combo.configure(command=self.on_coze_generation_type_changed) # 编辑状态标签 - self.edit_status_label = ttk.Label(type_frame, text="", foreground="blue") - self.edit_status_label.pack(side=tk.LEFT, padx=20) + self.edit_status_label = ctk.CTkLabel(type_frame, text="", text_color="blue") + self.edit_status_label.pack(side=ctk.LEFT, padx=20) # 加载已保存的模板 self.load_templates() @@ -302,59 +284,62 @@ class ArticleReplaceApp(tk.Tk): self._setup_var_trace() # 模板管理框架 - template_frame = ttk.LabelFrame(parent, text="模板管理") - template_frame.grid(row=1, column=0, columnspan=3, padx=5, pady=10, sticky=tk.EW) + template_frame = ctk.CTkFrame(parent) + template_frame.grid(row=1, column=0, columnspan=3, padx=5, pady=10, sticky=ctk.EW) - # 模板列表和滚动条 - list_frame = ttk.Frame(template_frame) - list_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - ttk.Label(template_frame, text="模板列表:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.template_listbox = tk.Listbox(list_frame, height=5, width=30) - scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.template_listbox.yview) - self.template_listbox.configure(yscrollcommand=scrollbar.set) - self.template_listbox.pack(side=tk.LEFT, fill=tk.Y) - scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - self.template_listbox.bind("<>", self.on_template_selected) + # 模板列表标题 + ctk.CTkLabel(template_frame, text="模板管理", font=ctk.CTkFont(size=14, weight="bold")).grid(row=0, column=0, + columnspan=2, + padx=5, pady=5) + + # 创建包含列表框和滚动条的容器 + list_container = ctk.CTkFrame(template_frame, fg_color="transparent") + list_container.grid(row=1, column=0, padx=5, pady=5, sticky=ctk.NSEW) + + # 使用CTkScrollableFrame作为可滚动列表的替代方案 + self.template_list_frame = ctk.CTkScrollableFrame(list_container, width=250, height=150) + self.template_list_frame.pack(fill=ctk.BOTH, expand=True) + + # 存储模板按钮的列表 + self.template_buttons = [] + self.selected_template_index = None # 模板操作按钮 - button_frame = ttk.Frame(template_frame) - button_frame.grid(row=1, column=1, padx=10, pady=5, sticky=tk.N) - ttk.Button(button_frame, text="新增模板", command=self.add_template).pack(pady=2) - ttk.Button(button_frame, text="删除模板", command=self.delete_template).pack(pady=2) - ttk.Button(button_frame, text="重命名模板", command=self.rename_template).pack(pady=2) - ttk.Button(button_frame, text="保存模板", command=self.save_template).pack(pady=2) - ttk.Button(button_frame, text="复制模板", command=self.duplicate_template).pack(pady=2) - ttk.Button(button_frame, text="使用模板", command=self.use_template).pack(pady=2) + button_frame = ctk.CTkFrame(template_frame, fg_color="transparent") + button_frame.grid(row=1, column=1, padx=10, pady=5, sticky=ctk.N) + ctk.CTkButton(button_frame, text="新增模板", command=self.add_template, width=100).pack(pady=2) + ctk.CTkButton(button_frame, text="删除模板", command=self.delete_template, width=100).pack(pady=2) + ctk.CTkButton(button_frame, text="重命名模板", command=self.rename_template, width=100).pack(pady=2) + ctk.CTkButton(button_frame, text="保存模板", command=self.save_template, width=100).pack(pady=2) + ctk.CTkButton(button_frame, text="复制模板", command=self.duplicate_template, width=100).pack(pady=2) + ctk.CTkButton(button_frame, text="使用模板", command=self.use_template, width=100).pack(pady=2) # 当前模板配置 - config_frame = ttk.LabelFrame(parent, text="当前模板配置") - config_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=10, sticky=tk.EW) + config_frame = ctk.CTkFrame(parent, fg_color="transparent") + config_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=10, sticky=ctk.EW) # 模板名称 - ttk.Label(config_frame, text="模板名称:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - ttk.Entry(config_frame, textvariable=self.template_name_var, width=30).grid(row=0, column=1, padx=5, pady=5) + ctk.CTkLabel(config_frame, text="模板名称:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + ctk.CTkEntry(config_frame, textvariable=self.template_name_var, width=150).grid(row=0, column=1, padx=5, pady=5) # Coze Workflow ID - ttk.Label(config_frame, text="Workflow ID:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - ttk.Entry(config_frame, textvariable=self.coze_workflow_id_var, width=50).grid(row=1, column=1, padx=5, pady=5) + ctk.CTkLabel(config_frame, text="Workflow ID:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + ctk.CTkEntry(config_frame, textvariable=self.coze_workflow_id_var, width=200).grid(row=1, column=1, padx=5, + pady=5) # Coze Access Token - ttk.Label(config_frame, text="Access Token:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - ttk.Entry(config_frame, textvariable=self.coze_access_token_var, width=50).grid(row=2, column=1, padx=5, pady=5) + ctk.CTkLabel(config_frame, text="Access Token:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + ctk.CTkEntry(config_frame, textvariable=self.coze_access_token_var, width=200).grid(row=2, column=1, padx=5, + pady=5) # Coze Is Async - ttk.Label(config_frame, text="Is Async:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) - ttk.Combobox(config_frame, textvariable=self.coze_is_async_var, values=["true", "false"], width=10, - state="readonly").grid(row=3, column=1, padx=5, pady=5, sticky=tk.W) - - # # Coze Input Data Template - # ttk.Label(config_frame, text="Input Data模板:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W) - # # Variable already initialized in __init__ - # ttk.Entry(config_frame, textvariable=self.coze_input_data_template_var, width=50).grid(row=4, column=1, padx=5, pady=5) + ctk.CTkLabel(config_frame, text="Is Async:").grid(row=3, column=0, padx=5, pady=5, sticky=ctk.W) + ctk.CTkComboBox(config_frame, variable=self.coze_is_async_var, values=["true", "false"], width=120, + state="readonly").grid(row=3, column=1, padx=5, pady=5, sticky=ctk.W) # 保存按钮 - ttk.Button(config_frame, text="保存配置", command=self.save_coze_config).grid(row=5, column=1, padx=5, pady=10, - sticky=tk.E) + ctk.CTkButton(config_frame, text="保存配置", command=self.save_coze_config).grid(row=5, column=1, padx=5, + pady=10, sticky=ctk.E) # 更新模板列表 self.update_template_list() @@ -364,110 +349,110 @@ class ArticleReplaceApp(tk.Tk): def init_baidu_config(self, parent): # 百度 API Key - ttk.Label(parent, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.baidu_api_key_var = tk.StringVar(value=CONFIG['Baidu']['api_key']) - ttk.Entry(parent, textvariable=self.baidu_api_key_var, width=50).grid(row=0, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.baidu_api_key_var = ctk.StringVar(value=CONFIG['Baidu']['api_key']) + ctk.CTkEntry(parent, textvariable=self.baidu_api_key_var, width=200).grid(row=0, column=1, padx=5, pady=5) # 百度 Secret Key - ttk.Label(parent, text="Secret Key:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - self.baidu_secret_key_var = tk.StringVar(value=CONFIG['Baidu']['secret_key']) - ttk.Entry(parent, textvariable=self.baidu_secret_key_var, width=50).grid(row=1, column=1, padx=5, pady=5) + ctk.CTkLabel(parent, text="Secret Key:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + self.baidu_secret_key_var = ctk.StringVar(value=CONFIG['Baidu']['secret_key']) + ctk.CTkEntry(parent, textvariable=self.baidu_secret_key_var, width=200).grid(row=1, column=1, padx=5, pady=5) # 是否启用违规检测 - ttk.Label(parent, text="启用违规检测:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - self.baidu_enable_detection_var = tk.StringVar(value=CONFIG['Baidu'].get('enable_detection', 'false')) - enable_detection_combo = ttk.Combobox(parent, textvariable=self.baidu_enable_detection_var, - values=["true", "false"], width=10, state="readonly") - enable_detection_combo.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W) + ctk.CTkLabel(parent, text="启用违规检测:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + self.baidu_enable_detection_var = ctk.StringVar(value=CONFIG['Baidu'].get('enable_detection', 'false')) + enable_detection_combo = ctk.CTkComboBox(parent, variable=self.baidu_enable_detection_var, + values=["true", "false"], width=120, state="readonly") + enable_detection_combo.grid(row=2, column=1, padx=5, pady=5, sticky=ctk.W) # 保存按钮 - ttk.Button(parent, text="保存配置", command=self.save_baidu_config).grid(row=3, column=1, padx=5, pady=10, - sticky=tk.E) + ctk.CTkButton(parent, text="保存配置", command=self.save_baidu_config).grid(row=3, column=1, padx=5, pady=10, + sticky=ctk.E) def init_image_config(self, parent): # 裁剪百分比 - ttk.Label(parent, text="裁剪百分比:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.crop_percent_var = tk.StringVar(value=CONFIG['ImageModify']['crop_percent']) - ttk.Entry(parent, textvariable=self.crop_percent_var, width=10).grid(row=0, column=1, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="裁剪百分比:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.crop_percent_var = ctk.StringVar(value=CONFIG['ImageModify']['crop_percent']) + ctk.CTkEntry(parent, textvariable=self.crop_percent_var, width=50).grid(row=0, column=1, padx=5, pady=5, + sticky=ctk.W) # 最小旋转角度 - ttk.Label(parent, text="最小旋转角度:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) - self.min_rotation_var = tk.StringVar(value=CONFIG['ImageModify']['min_rotation']) - ttk.Entry(parent, textvariable=self.min_rotation_var, width=10).grid(row=1, column=1, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="最小旋转角度:").grid(row=1, column=0, padx=5, pady=5, sticky=ctk.W) + self.min_rotation_var = ctk.StringVar(value=CONFIG['ImageModify']['min_rotation']) + ctk.CTkEntry(parent, textvariable=self.min_rotation_var, width=50).grid(row=1, column=1, padx=5, pady=5, + sticky=ctk.W) # 最大旋转角度 - ttk.Label(parent, text="最大旋转角度:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) - self.max_rotation_var = tk.StringVar(value=CONFIG['ImageModify']['max_rotation']) - ttk.Entry(parent, textvariable=self.max_rotation_var, width=10).grid(row=2, column=1, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="最大旋转角度:").grid(row=2, column=0, padx=5, pady=5, sticky=ctk.W) + self.max_rotation_var = ctk.StringVar(value=CONFIG['ImageModify']['max_rotation']) + ctk.CTkEntry(parent, textvariable=self.max_rotation_var, width=50).grid(row=2, column=1, padx=5, pady=5, + sticky=ctk.W) # 最小亮度 - ttk.Label(parent, text="最小亮度:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) - self.min_brightness_var = tk.StringVar(value=CONFIG['ImageModify']['min_brightness']) - ttk.Entry(parent, textvariable=self.min_brightness_var, width=10).grid(row=3, column=1, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="最小亮度:").grid(row=3, column=0, padx=5, pady=5, sticky=ctk.W) + self.min_brightness_var = ctk.StringVar(value=CONFIG['ImageModify']['min_brightness']) + ctk.CTkEntry(parent, textvariable=self.min_brightness_var, width=50).grid(row=3, column=1, padx=5, pady=5, + sticky=ctk.W) # 最大亮度 - ttk.Label(parent, text="最大亮度:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W) - self.max_brightness_var = tk.StringVar(value=CONFIG['ImageModify']['max_brightness']) - ttk.Entry(parent, textvariable=self.max_brightness_var, width=10).grid(row=4, column=1, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="最大亮度:").grid(row=4, column=0, padx=5, pady=5, sticky=ctk.W) + self.max_brightness_var = ctk.StringVar(value=CONFIG['ImageModify']['max_brightness']) + ctk.CTkEntry(parent, textvariable=self.max_brightness_var, width=50).grid(row=4, column=1, padx=5, pady=5, + sticky=ctk.W) # 水印文字 - ttk.Label(parent, text="水印文字:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W) - self.watermark_text_var = tk.StringVar(value=CONFIG['ImageModify']['watermark_text']) - ttk.Entry(parent, textvariable=self.watermark_text_var, width=30).grid(row=0, column=3, padx=5, pady=5) + ctk.CTkLabel(parent, text="水印文字:").grid(row=0, column=2, padx=5, pady=5, sticky=ctk.W) + self.watermark_text_var = ctk.StringVar(value=CONFIG['ImageModify']['watermark_text']) + ctk.CTkEntry(parent, textvariable=self.watermark_text_var, width=150).grid(row=0, column=3, padx=5, pady=5) # 水印透明度 - ttk.Label(parent, text="水印透明度:").grid(row=1, column=2, padx=5, pady=5, sticky=tk.W) - self.watermark_opacity_var = tk.StringVar(value=CONFIG['ImageModify']['watermark_opacity']) - ttk.Entry(parent, textvariable=self.watermark_opacity_var, width=10).grid(row=1, column=3, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="水印透明度:").grid(row=1, column=2, padx=5, pady=5, sticky=ctk.W) + self.watermark_opacity_var = ctk.StringVar(value=CONFIG['ImageModify']['watermark_opacity']) + ctk.CTkEntry(parent, textvariable=self.watermark_opacity_var, width=50).grid(row=1, column=3, padx=5, pady=5, + sticky=ctk.W) # 蒙版透明度 - ttk.Label(parent, text="蒙版透明度:").grid(row=2, column=2, padx=5, pady=5, sticky=tk.W) - self.overlay_opacity_var = tk.StringVar(value=CONFIG['ImageModify']['overlay_opacity']) - ttk.Entry(parent, textvariable=self.overlay_opacity_var, width=10).grid(row=2, column=3, padx=5, pady=5, - sticky=tk.W) + ctk.CTkLabel(parent, text="蒙版透明度:").grid(row=2, column=2, padx=5, pady=5, sticky=ctk.W) + self.overlay_opacity_var = ctk.StringVar(value=CONFIG['ImageModify']['overlay_opacity']) + ctk.CTkEntry(parent, textvariable=self.overlay_opacity_var, width=50).grid(row=2, column=3, padx=5, pady=5, + sticky=ctk.W) # 预览按钮 - ttk.Button(parent, text="预览效果", command=self.preview_image_effect).grid(row=4, column=3, padx=5, pady=5, - sticky=tk.E) + ctk.CTkButton(parent, text="预览效果", command=self.preview_image_effect).grid(row=4, column=3, padx=5, pady=5, + sticky=ctk.E) # 保存按钮 - ttk.Button(parent, text="保存配置", command=self.save_image_config).grid(row=5, column=3, padx=5, pady=10, - sticky=tk.E) + ctk.CTkButton(parent, text="保存配置", command=self.save_image_config).grid(row=5, column=3, padx=5, pady=10, + sticky=ctk.E) def init_keywords_config(self, parent): # 违禁词列表 - ttk.Label(parent, text="违禁词列表:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - self.banned_words_text = ScrolledText(parent, width=60, height=15) - self.banned_words_text.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky=tk.NSEW) - self.banned_words_text.insert(tk.END, CONFIG['Keywords']['banned_words'].replace(',', '\n')) + ctk.CTkLabel(parent, text="违禁词列表:").grid(row=0, column=0, padx=5, pady=5, sticky=ctk.W) + self.banned_words_text = ctk.CTkTextbox(parent, width=500, height=300) + self.banned_words_text.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky=ctk.NSEW) + self.banned_words_text.insert("1.0", CONFIG['Keywords']['banned_words'].replace(',', '\n')) # 保存按钮 - ttk.Button(parent, text="保存违禁词", command=self.save_banned_words).grid(row=2, column=1, padx=5, pady=5, - sticky=tk.E) + ctk.CTkButton(parent, text="保存违禁词", command=self.save_banned_words).grid(row=2, column=1, padx=5, pady=5, + sticky=ctk.E) # 配置行列权重 parent.columnconfigure(0, weight=1) parent.rowconfigure(1, weight=1) - + def init_disclaimer_frame(self): # 创建免责声明内容框架 - disclaimer_content = ttk.Frame(self.disclaimer_frame) - disclaimer_content.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + disclaimer_content = ctk.CTkFrame(self.disclaimer_frame, fg_color="transparent") + disclaimer_content.pack(fill=ctk.BOTH, expand=True, padx=20, pady=20) # 标题 - title_label = ttk.Label(disclaimer_content, text="免责声明", font=("Arial", 16, "bold")) + title_label = ctk.CTkLabel(disclaimer_content, text="免责声明", font=ctk.CTkFont(size=16, weight="bold")) title_label.pack(pady=10) # 免责声明文本 - disclaimer_text = ScrolledText(disclaimer_content, width=80, height=20, wrap=tk.WORD) - disclaimer_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - disclaimer_text.insert(tk.END, """ + disclaimer_text = ctk.CTkTextbox(disclaimer_content, width=700, height=400, wrap="word") + disclaimer_text.pack(fill=ctk.BOTH, expand=True, padx=10, pady=10) + disclaimer_text.insert("1.0", """ 软件使用免责声明 1. 合法使用声明 @@ -496,12 +481,14 @@ class ArticleReplaceApp(tk.Tk): 7. 最终解释 本免责声明的最终解释权归本软件开发者所有。 """) - disclaimer_text.config(state=tk.DISABLED) # 设置为只读 + + # 设置为只读 - CTkTextbox使用configure方法 + disclaimer_text.configure(state="disabled") # 确认按钮 - confirm_frame = ttk.Frame(disclaimer_content) + confirm_frame = ctk.CTkFrame(disclaimer_content, fg_color="transparent") confirm_frame.pack(pady=10) - ttk.Button(confirm_frame, text="我已阅读并同意以上声明", command=lambda: self.notebook.select(0)).pack() + ctk.CTkButton(confirm_frame, text="我已阅读并同意以上声明", command=lambda: self.notebook.set("主页面")).pack() def save_all_configs(self): """保存所有配置到配置文件""" @@ -514,18 +501,15 @@ class ArticleReplaceApp(tk.Tk): self.save_baidu_config() self.save_image_config() self.save_banned_words() - + messagebox.showinfo("保存成功", "所有配置已保存") except Exception as e: messagebox.showerror("保存失败", f"保存配置时出错:{e}") - # 保存按钮 - save_button = ttk.Button(self.config_frame, text="保存所有配置", command=self.save_all_configs) - save_button.pack(side=tk.RIGHT, padx=10, pady=10) - - def on_coze_generation_type_changed(self, event=None): + def on_coze_generation_type_changed(self, choice=None): """coze页面生成类型改变时的处理""" - + self.update_template_list() + def save_general_config(self): # 保存常规配置 try: @@ -586,8 +570,7 @@ class ArticleReplaceApp(tk.Tk): # 保存当前Coze模板配置 try: # 获取当前选中的模板 - selection = self.template_listbox.curselection() - if not selection: + if self.selected_template_index is None: # 如果没有选中模板,只保存全局Coze配置 CONFIG['Coze']['workflow_id'] = self.coze_workflow_id_var.get() CONFIG['Coze']['access_token'] = self.coze_access_token_var.get() @@ -596,9 +579,8 @@ class ArticleReplaceApp(tk.Tk): messagebox.showinfo("保存成功", "Coze全局配置已保存") return - # 获取当前选中的模板索引 - index = selection[0] current_type = self.coze_generation_type_var.get() + index = self.selected_template_index if current_type not in self.templates or index >= len(self.templates[current_type]): messagebox.showerror("错误", "无效的模板选择") @@ -620,8 +602,8 @@ class ArticleReplaceApp(tk.Tk): CONFIG['Coze']['is_async'] = self.coze_is_async_var.get() save_config(CONFIG) - self.edit_status_label.config(text="已保存", foreground="green") - self.after(2000, lambda: self.edit_status_label.config(text="")) + self.edit_status_label.configure(text="已保存", text_color="green") + self.after(2000, lambda: self.edit_status_label.configure(text="")) messagebox.showinfo("保存成功", f"模板 '{template['name']}' 配置已保存") except Exception as e: messagebox.showerror("保存失败", f"保存Coze配置时出错:{e}") @@ -657,7 +639,7 @@ class ArticleReplaceApp(tk.Tk): def save_banned_words(self): # 处理文本,将换行符替换为逗号 - words = self.banned_words_text.get(1.0, tk.END).strip().replace('\n', ',') + words = self.banned_words_text.get("1.0", "end-1c").strip().replace('\n', ',') CONFIG['Keywords']['banned_words'] = words save_config(CONFIG) messagebox.showinfo("保存成功", "违禁词列表已更新") @@ -666,7 +648,7 @@ class ArticleReplaceApp(tk.Tk): self.generation_type_var.set(self.coze_generation_type_var.get()) self.update_template_list() - def on_generation_type_changed(self, event=None): + def on_generation_type_changed(self, choice=None): """主页面生成类型改变时的处理""" # 同步到coze页面 self.coze_generation_type_var.set(self.generation_type_var.get()) @@ -675,26 +657,49 @@ class ArticleReplaceApp(tk.Tk): def update_template_list(self): """更新模板列表显示""" current_type = self.coze_generation_type_var.get() - self.template_listbox.delete(0, tk.END) + # 清空现有按钮 + for btn in self.template_buttons: + btn.destroy() + self.template_buttons.clear() + + # 创建新的模板按钮 if current_type in self.templates: - for template in self.templates[current_type]: - self.template_listbox.insert(tk.END, template['name']) + for i, template in enumerate(self.templates[current_type]): + btn = ctk.CTkButton( + self.template_list_frame, + text=template['name'], + command=lambda idx=i: self.on_template_button_click(idx), + fg_color="transparent", + border_width=2, + anchor="w" + ) + btn.pack(fill=ctk.X, padx=5, pady=2) + self.template_buttons.append(btn) - def on_template_selected(self, event=None): - """模板选择时的处理""" - selection = self.template_listbox.curselection() - if selection: - index = selection[0] - current_type = self.coze_generation_type_var.get() - if current_type in self.templates and index < len(self.templates[current_type]): - template = self.templates[current_type][index] - self.load_template_config(template) + def on_template_button_click(self, index): + """模板按钮点击事件""" + # 重置所有按钮颜色 + for btn in self.template_buttons: + btn.configure(fg_color="transparent") - # 更新上次使用的模板信息 - CONFIG['Coze']['last_used_template'] = template['name'] - CONFIG['Coze']['last_used_template_type'] = current_type - save_config(CONFIG) # 保存配置文件 + # 高亮选中的按钮 + if index < len(self.template_buttons): + self.template_buttons[index].configure(fg_color=("gray75", "gray25")) + + # 更新选中索引 + self.selected_template_index = index + + # 加载模板配置 + current_type = self.coze_generation_type_var.get() + if current_type in self.templates and index < len(self.templates[current_type]): + template = self.templates[current_type][index] + self.load_template_config(template) + + # 更新上次使用的模板信息 + CONFIG['Coze']['last_used_template'] = template['name'] + CONFIG['Coze']['last_used_template_type'] = current_type + save_config(CONFIG) def load_template_config(self, template): """加载模板配置到界面""" @@ -705,10 +710,9 @@ class ArticleReplaceApp(tk.Tk): self.coze_workflow_id_var.set(template.get('workflow_id', '')) self.coze_access_token_var.set(template.get('access_token', '')) self.coze_is_async_var.set(template.get('is_async', 'true')) - # self.coze_input_data_template_var.set(template.get('input_data_template', '')) - self.edit_status_label.config(text="已加载", foreground="blue") - self.after(2000, lambda: self.edit_status_label.config(text="")) + self.edit_status_label.configure(text="已加载", text_color="blue") + self.after(2000, lambda: self.edit_status_label.configure(text="")) # 重新绑定变量跟踪 self._setup_var_trace() @@ -718,7 +722,6 @@ class ArticleReplaceApp(tk.Tk): self.var_traces = [] for var in [self.template_name_var, self.coze_workflow_id_var, self.coze_access_token_var, self.coze_is_async_var]: - # self.coze_input_data_template_var]: trace_id = var.trace_add('write', lambda *args: self._show_edit_status()) self.var_traces.append((var, trace_id)) @@ -734,7 +737,7 @@ class ArticleReplaceApp(tk.Tk): def _show_edit_status(self): """显示编辑状态""" - self.edit_status_label.config(text="未保存", foreground="red") + self.edit_status_label.configure(text="未保存", text_color="red") def add_template(self): """添加新模板""" @@ -760,7 +763,6 @@ class ArticleReplaceApp(tk.Tk): 'workflow_id': '', 'access_token': '', 'is_async': 'true', - # 'input_data_template': '{"article": "{article_text}", "link":"{link}", "weijin":"{weijin}"}' } self.templates[current_type].append(new_template) self.update_template_list() @@ -768,24 +770,21 @@ class ArticleReplaceApp(tk.Tk): # 选中新添加的模板 new_index = len(self.templates[current_type]) - 1 - self.template_listbox.selection_clear(0, tk.END) - self.template_listbox.selection_set(new_index) - # selection_set会触发on_template_selected事件,自动加载模板配置 + self.on_template_button_click(new_index) - # 延迟设置状态,确保覆盖on_template_selected中设置的状态 - self.after(100, lambda: self.edit_status_label.config(text="已添加", foreground="green")) - self.after(2100, lambda: self.edit_status_label.config(text="")) + # 延迟设置状态,确保覆盖on_template_button_click中设置的状态 + self.after(100, lambda: self.edit_status_label.configure(text="已添加", text_color="green")) + self.after(2100, lambda: self.edit_status_label.configure(text="")) else: messagebox.showinfo("取消操作", "已取消新增模板。") def delete_template(self): """删除选中的模板""" - selection = self.template_listbox.curselection() - if not selection: + if self.selected_template_index is None: messagebox.showwarning("提示", "请先选择要删除的模板") return - index = selection[0] + index = self.selected_template_index current_type = self.coze_generation_type_var.get() if current_type not in self.templates or index >= len(self.templates[current_type]): return @@ -798,21 +797,22 @@ class ArticleReplaceApp(tk.Tk): # 清除配置并更新状态 self.clear_template_config() - self.edit_status_label.config(text=f"已删除 '{template_name}'", foreground="red") - self.after(2000, lambda: self.edit_status_label.config(text="")) + self.edit_status_label.configure(text=f"已删除 '{template_name}'", text_color="red") + self.after(2000, lambda: self.edit_status_label.configure(text="")) + + # 重置选中索引 + self.selected_template_index = None # 如果还有模板,选中最后一个 if self.templates[current_type]: last_index = len(self.templates[current_type]) - 1 - self.template_listbox.selection_set(last_index) - self.load_template_config(self.templates[current_type][last_index]) + self.on_template_button_click(last_index) def validate_template(self): """验证模板配置""" name = self.template_name_var.get().strip() workflow_id = self.coze_workflow_id_var.get().strip() access_token = self.coze_access_token_var.get().strip() - # input_template = self.coze_input_data_template_var.get().strip() if not name: messagebox.showerror("错误", "模板名称不能为空") @@ -826,22 +826,6 @@ class ArticleReplaceApp(tk.Tk): messagebox.showerror("错误", "Access Token不能为空") return False - # if not input_template: - # messagebox.showerror("错误", "输入数据模板不能为空") - # return False - # - # # 验证输入数据模板的JSON格式 - # try: - # # 替换占位符以便验证JSON格式 - # test_template = input_template.replace('{article_text}', '""')\ - # .replace('{link}', '""')\ - # .replace('{weijin}', '""')\ - # .replace('{title_text}', '""') - # json.loads(test_template) - # except json.JSONDecodeError as e: - # messagebox.showerror("错误", f"输入数据模板不是有效的JSON格式:\n{str(e)}") - # return False - return True def save_template(self): @@ -849,9 +833,8 @@ class ArticleReplaceApp(tk.Tk): if not self.validate_template(): return - selection = self.template_listbox.curselection() - if selection: - index = selection[0] + if self.selected_template_index is not None: + index = self.selected_template_index current_type = self.coze_generation_type_var.get() if current_type in self.templates and index < len(self.templates[current_type]): template = self.templates[current_type][index] @@ -869,7 +852,6 @@ class ArticleReplaceApp(tk.Tk): template['workflow_id'] = self.coze_workflow_id_var.get().strip() template['access_token'] = self.coze_access_token_var.get().strip() template['is_async'] = self.coze_is_async_var.get() - # template['input_data_template'] = self.coze_input_data_template_var.get().strip() # 更新上次使用的模板信息 CONFIG['Coze']['last_used_template'] = template['name'] @@ -877,21 +859,20 @@ class ArticleReplaceApp(tk.Tk): self.update_template_list() self.save_templates() - save_config(CONFIG) # 保存配置文件 + save_config(CONFIG) - self.edit_status_label.config(text="已保存", foreground="green") - self.after(2000, lambda: self.edit_status_label.config(text="")) + self.edit_status_label.configure(text="已保存", text_color="green") + self.after(2000, lambda: self.edit_status_label.configure(text="")) else: messagebox.showwarning("未选择模板", "请先选择要保存的模板") def rename_template(self): """重命名当前选中的模板""" - selection = self.template_listbox.curselection() - if not selection: + if self.selected_template_index is None: messagebox.showwarning("未选择模板", "请先选择要重命名的模板") return - index = selection[0] + index = self.selected_template_index current_type = self.coze_generation_type_var.get() if current_type not in self.templates or index >= len(self.templates[current_type]): return @@ -918,20 +899,19 @@ class ArticleReplaceApp(tk.Tk): self.update_template_list() self.save_templates() # 重新选中重命名后的模板 - self.template_listbox.selection_set(index) - self.edit_status_label.config(text="已重命名", foreground="green") - self.after(2000, lambda: self.edit_status_label.config(text="")) + self.on_template_button_click(index) + self.edit_status_label.configure(text="已重命名", text_color="green") + self.after(2000, lambda: self.edit_status_label.configure(text="")) else: messagebox.showinfo("取消操作", "已取消重命名模板。") def duplicate_template(self): """复制当前选中的模板""" - selection = self.template_listbox.curselection() - if not selection: + if self.selected_template_index is None: messagebox.showwarning("提示", "请先选择要复制的模板") return - index = selection[0] + index = self.selected_template_index current_type = self.coze_generation_type_var.get() if current_type not in self.templates or index >= len(self.templates[current_type]): return @@ -954,76 +934,93 @@ class ArticleReplaceApp(tk.Tk): # 选中新复制的模板 new_index = len(self.templates[current_type]) - 1 - self.template_listbox.selection_clear(0, tk.END) - self.template_listbox.selection_set(new_index) - # selection_set会触发on_template_selected事件,自动加载模板配置 + self.on_template_button_click(new_index) - # 延迟设置状态,确保覆盖on_template_selected中设置的状态 - self.after(100, lambda: self.edit_status_label.config(text="已复制", foreground="green")) - self.after(2100, lambda: self.edit_status_label.config(text="")) + # 延迟设置状态,确保覆盖on_template_button_click中设置的状态 + self.after(100, lambda: self.edit_status_label.configure(text="已复制", text_color="green")) + self.after(2100, lambda: self.edit_status_label.configure(text="")) def use_template(self): """使用模板功能 - 弹出模板选择对话框并应用所选模板配置""" # 创建模板选择对话框 - dialog = tk.Toplevel(self) + dialog = ctk.CTkToplevel(self) dialog.title("选择模板") dialog.geometry("400x400") - dialog.transient(self) # 设置为应用程序的子窗口 - dialog.grab_set() # 模态对话框 + dialog.transient(self) + dialog.grab_set() dialog.resizable(False, False) # 创建说明标签 - ttk.Label(dialog, text="请选择要使用的模板:", font=("Arial", 10)).pack(pady=10) + ctk.CTkLabel(dialog, text="请选择要使用的模板:", font=ctk.CTkFont(size=12)).pack(pady=10) # 创建模板类型选择框架 - type_frame = ttk.Frame(dialog) - type_frame.pack(fill=tk.X, padx=10, pady=5) + type_frame = ctk.CTkFrame(dialog, fg_color="transparent") + type_frame.pack(fill=ctk.X, padx=10, pady=5) - ttk.Label(type_frame, text="模板类型:").pack(side=tk.LEFT, padx=5) - dialog_type_var = tk.StringVar(value=self.coze_generation_type_var.get()) - type_combo = ttk.Combobox(type_frame, textvariable=dialog_type_var, values=["短篇", "文章"], width=10, - state="readonly") - type_combo.pack(side=tk.LEFT, padx=5) + ctk.CTkLabel(type_frame, text="模板类型:").pack(side=ctk.LEFT, padx=5) + dialog_type_var = ctk.StringVar(value=self.coze_generation_type_var.get()) + type_combo = ctk.CTkComboBox(type_frame, variable=dialog_type_var, values=["短篇", "文章"], width=120, + state="readonly") + type_combo.pack(side=ctk.LEFT, padx=5) - # 创建模板列表框架 - list_frame = ttk.Frame(dialog) - list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # 创建模板列表框架 - 使用CTkScrollableFrame + list_frame = ctk.CTkScrollableFrame(dialog, width=350, height=200) + list_frame.pack(fill=ctk.BOTH, expand=True, padx=10, pady=10) - # 创建模板列表和滚动条 - template_listbox = tk.Listbox(list_frame, height=10, width=40) - scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=template_listbox.yview) - template_listbox.configure(yscrollcommand=scrollbar.set) - template_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + # 存储模板按钮 + dialog_template_buttons = [] + dialog_selected_index = [None] # 使用列表存储以便在嵌套函数中修改 # 填充模板列表 def update_dialog_template_list(): - template_listbox.delete(0, tk.END) + # 清空现有按钮 + for btn in dialog_template_buttons: + btn.destroy() + dialog_template_buttons.clear() + dialog_selected_index[0] = None + current_type = dialog_type_var.get() if current_type in self.templates: - for template in self.templates[current_type]: - template_listbox.insert(tk.END, template['name']) + for i, template in enumerate(self.templates[current_type]): + btn = ctk.CTkButton( + list_frame, + text=template['name'], + command=lambda idx=i: select_dialog_template(idx), + fg_color="transparent", + border_width=2, + anchor="w" + ) + btn.pack(fill=ctk.X, padx=5, pady=2) + dialog_template_buttons.append(btn) + + def select_dialog_template(index): + """对话框中的模板选择""" + # 重置所有按钮颜色 + for btn in dialog_template_buttons: + btn.configure(fg_color="transparent") + + # 高亮选中的按钮 + if index < len(dialog_template_buttons): + dialog_template_buttons[index].configure(fg_color=("gray75", "gray25")) + + dialog_selected_index[0] = index update_dialog_template_list() # 绑定类型选择变更事件 - def on_dialog_type_changed(event=None): - update_dialog_template_list() - - type_combo.bind("<>", on_dialog_type_changed) + type_combo.configure(command=lambda choice: update_dialog_template_list()) # 创建按钮框架 - button_frame = ttk.Frame(dialog) - button_frame.pack(fill=tk.X, padx=10, pady=10) + button_frame = ctk.CTkFrame(dialog, fg_color="transparent") + button_frame.pack(fill=ctk.X, padx=10, pady=10) # 定义确定按钮功能 def on_confirm(): - selection = template_listbox.curselection() - if not selection: + if dialog_selected_index[0] is None: messagebox.showwarning("未选择模板", "请先选择要使用的模板", parent=dialog) return - index = selection[0] + index = dialog_selected_index[0] current_type = dialog_type_var.get() if current_type not in self.templates or index >= len(self.templates[current_type]): return @@ -1031,20 +1028,18 @@ class ArticleReplaceApp(tk.Tk): selected_template = self.templates[current_type][index] # 应用所选模板的配置 - self.coze_generation_type_var.set(current_type) # 更新生成类型 - self.generation_type_var.set(current_type) # 同步到主页面 + self.coze_generation_type_var.set(current_type) + self.generation_type_var.set(current_type) # 更新工作流配置 self.coze_workflow_id_var.set(selected_template.get('workflow_id', '')) self.coze_access_token_var.set(selected_template.get('access_token', '')) self.coze_is_async_var.set(selected_template.get('is_async', 'true')) - # self.coze_input_data_template_var.set(selected_template.get('input_data_template', '')) # 更新CONFIG配置 CONFIG['Coze']['workflow_id'] = selected_template.get('workflow_id', '') CONFIG['Coze']['access_token'] = selected_template.get('access_token', '') CONFIG['Coze']['is_async'] = selected_template.get('is_async', 'true') - # CONFIG['Coze']['input_data_template'] = selected_template.get('input_data_template', '') # 保存上次使用的模板信息 CONFIG['Coze']['last_used_template'] = selected_template['name'] @@ -1055,29 +1050,24 @@ class ArticleReplaceApp(tk.Tk): # 更新模板列表和选中状态 self.update_template_list() - for i in range(self.template_listbox.size()): - if self.template_listbox.get(i) == selected_template['name']: - self.template_listbox.selection_set(i) + + # 在主界面中选中该模板 + for i, template in enumerate(self.templates[current_type]): + if template['name'] == selected_template['name']: + self.on_template_button_click(i) break - # 加载模板配置到界面 - self.load_template_config(selected_template) - - # 延迟设置状态,确保覆盖load_template_config设置的状态 - self.after(100, lambda: self.edit_status_label.config(text=f"已应用模板 '{selected_template['name']}'", - foreground="green")) - self.after(2100, lambda: self.edit_status_label.config(text="")) + # 延迟设置状态 + self.after(100, lambda: self.edit_status_label.configure(text=f"已应用模板 '{selected_template['name']}'", + text_color="green")) + self.after(2100, lambda: self.edit_status_label.configure(text="")) # 关闭对话框 dialog.destroy() # 添加确定和取消按钮 - ttk.Button(button_frame, text="确定", command=on_confirm).pack(side=tk.RIGHT, padx=5) - ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=5) - - # 设置默认选中第一个模板 - if template_listbox.size() > 0: - template_listbox.selection_set(0) + ctk.CTkButton(button_frame, text="确定", command=on_confirm).pack(side=ctk.RIGHT, padx=5) + ctk.CTkButton(button_frame, text="取消", command=dialog.destroy).pack(side=ctk.RIGHT, padx=5) # 等待对话框关闭 self.wait_window(dialog) @@ -1092,11 +1082,10 @@ class ArticleReplaceApp(tk.Tk): self.coze_workflow_id_var.set('') self.coze_access_token_var.set('') self.coze_is_async_var.set('true') - # self.coze_input_data_template_var.set('') # 清空状态提示 - self.edit_status_label.config(text="已清空", foreground="gray") - self.after(2000, lambda: self.edit_status_label.config(text="")) + self.edit_status_label.configure(text="已清空", text_color="gray") + self.after(2000, lambda: self.edit_status_label.configure(text="")) # 重新绑定变量跟踪 self._setup_var_trace() @@ -1129,7 +1118,6 @@ class ArticleReplaceApp(tk.Tk): self.templates[template_type] = [] except Exception as e: logger.error(f"加载模板配置失败: {e}") - # 确保模板字典已初始化 self.templates = {"短篇": [], "文章": []} def save_templates(self): @@ -1158,43 +1146,29 @@ class ArticleReplaceApp(tk.Tk): if last_template and last_template_type in self.templates: # 设置模板类型 self.coze_generation_type_var.set(last_template_type) - self.generation_type_var.set(last_template_type) # 同步到主页面 + self.generation_type_var.set(last_template_type) # 更新模板列表 self.update_template_list() # 查找并选中上次使用的模板 - found = False for i, template in enumerate(self.templates[last_template_type]): if template['name'] == last_template: - self.template_listbox.selection_clear(0, tk.END) - self.template_listbox.selection_set(i) - self.template_listbox.see(i) # 确保可见 - - # 加载模板配置 - self.load_template_config(template) + self.on_template_button_click(i) # 显示状态信息 - self.edit_status_label.config(text=f"已加载上次使用的模板 '{last_template}'") - self.after(3000, lambda: self.edit_status_label.config(text="")) - - found = True + self.edit_status_label.configure(text=f"已加载上次使用的模板 '{last_template}'") + self.after(3000, lambda: self.edit_status_label.configure(text="")) break - - if not found: - logger.warning(f"未找到上次使用的模板: {last_template}") except Exception as e: logger.error(f"加载上次使用的模板失败: {e}") - # 出错时不显示错误消息,静默失败 def get_current_template(self): """获取当前选中的模板配置""" - selection = self.template_listbox.curselection() - if selection: - index = selection[0] + if self.selected_template_index is not None: current_type = self.coze_generation_type_var.get() - if current_type in self.templates and index < len(self.templates[current_type]): - return self.templates[current_type][index] + if current_type in self.templates and self.selected_template_index < len(self.templates[current_type]): + return self.templates[current_type][self.selected_template_index] # 如果没有选中模板,返回当前界面的配置 return { @@ -1203,7 +1177,6 @@ class ArticleReplaceApp(tk.Tk): 'workflow_id': self.coze_workflow_id_var.get(), 'access_token': self.coze_access_token_var.get(), 'is_async': self.coze_is_async_var.get(), - # 'input_data_template': self.coze_input_data_template_var.get() } def browse_directory(self, var): @@ -1251,7 +1224,7 @@ class ArticleReplaceApp(tk.Tk): modified_img = self.apply_image_modifications(img) # 显示修改后的图片 - self.show_preview_image(modified_img) + modified_img.show() except Exception as e: messagebox.showerror("预览失败", f"生成预览图片时出错:{e}") @@ -1266,10 +1239,6 @@ class ArticleReplaceApp(tk.Tk): max_rotation = float(self.max_rotation_var.get()) min_brightness = float(self.min_brightness_var.get()) max_brightness = float(self.max_brightness_var.get()) - min_contrast = float(self.min_contrast_var.get()) - max_contrast = float(self.max_contrast_var.get()) - min_saturation = float(self.min_saturation_var.get()) - max_saturation = float(self.max_saturation_var.get()) # 裁剪 crop_size = (int(width * crop_percent), int(height * crop_percent)) @@ -1288,23 +1257,11 @@ class ArticleReplaceApp(tk.Tk): brightness_factor = random.uniform(min_brightness, max_brightness) img = enhancer.enhance(brightness_factor) - # 调整对比度 - enhancer = ImageEnhance.Contrast(img) - contrast_factor = random.uniform(min_contrast, max_contrast) - img = enhancer.enhance(contrast_factor) - - # 调整饱和度 - enhancer = ImageEnhance.Color(img) - saturation_factor = random.uniform(min_saturation, max_saturation) - img = enhancer.enhance(saturation_factor) - return img except Exception as e: messagebox.showerror("应用效果失败", f"应用图片修改效果时出错:{e}") return img - - def start_processing(self): """开始处理链接""" if self.running: @@ -1329,13 +1286,11 @@ class ArticleReplaceApp(tk.Tk): num_threads = 1 # 禁用开始按钮 - self.start_button.config(state=tk.DISABLED) + self.start_button.configure(state="disabled") self.running = True # 清空日志 - self.log_text.config(state=tk.NORMAL) - self.log_text.delete(1.0, tk.END) - self.log_text.config(state=tk.DISABLED) + self.log_text.delete("1.0", "end") # 获取AI服务提供商选择 ai_service = self.ai_service_var.get() @@ -1357,86 +1312,7 @@ class ArticleReplaceApp(tk.Tk): self.after(100, self.update_progress) except Exception as e: messagebox.showerror("启动失败", f"启动处理任务时出错:{e}") - self.start_button.config(state=tk.NORMAL) - self.running = False - - def run_processing(self, excel_path, num_threads, ai_service, generation_type=None, current_template=None): - """运行处理任务""" - try: - # 读取Excel文件 - df = pd.read_excel(excel_path) - - # 获取关键词列表 - keywords = self.keywords_var.get().split(",") - - # 获取图片处理参数 - crop_percent = float(self.crop_percent_var.get()) - min_rotation = float(self.min_rotation_var.get()) - max_rotation = float(self.max_rotation_var.get()) - min_brightness = float(self.min_brightness_var.get()) - max_brightness = float(self.max_brightness_var.get()) - min_saturation = float(self.min_saturation_var.get()) - max_saturation = float(self.max_saturation_var.get()) - watermark_text = self.watermark_text_var.get() - watermark_opacity = int(self.watermark_opacity_var.get()) - overlay_opacity = int(self.overlay_opacity_var.get()) - - # 1. 裁剪边缘 - crop_px_w = int(width * crop_percent) - crop_px_h = int(height * crop_percent) - img = img.crop((crop_px_w, crop_px_h, width - crop_px_w, height - crop_px_h)) - - # 2. 随机旋转 - angle = random.uniform(min_rotation, max_rotation) * random.choice([-1, 1]) - img = img.rotate(angle, expand=True) - - # 3. 调整亮度 - enhancer = ImageEnhance.Brightness(img) - factor = random.uniform(min_brightness, max_brightness) - img = enhancer.enhance(factor) - - # 4. 添加文字水印 - draw = ImageDraw.Draw(img) - font_size = max(20, int(min(img.size) * 0.05)) - try: - num_threads = int(self.thread_count_var.get()) - if num_threads < 1: - num_threads = 1 - elif num_threads > MAX_THREADS: - num_threads = MAX_THREADS - except: - num_threads = 1 - - # 禁用开始按钮 - self.start_button.config(state=tk.DISABLED) - self.running = True - - # 清空日志 - self.log_text.config(state=tk.NORMAL) - self.log_text.delete(1.0, tk.END) - self.log_text.config(state=tk.DISABLED) - - # 获取AI服务提供商选择 - ai_service = self.ai_service_var.get() - - # 获取生成类型 - generation_type = self.generation_type_var.get() - - # 获取当前选中的模板配置 - current_template = self.get_current_template() - - # 在新线程中运行处理任务 - self.thread = threading.Thread(target=self.run_processing, - args=(excel_path, num_threads, ai_service, generation_type, - current_template)) - self.thread.daemon = True - self.thread.start() - - # 启动进度更新 - self.after(100, self.update_progress) - except Exception as e: - messagebox.showerror("启动失败", f"启动处理任务时出错:{e}") - self.start_button.config(state=tk.NORMAL) + self.start_button.configure(state="normal") self.running = False def run_processing(self, excel_path, num_threads, ai_service, generation_type=None, current_template=None): @@ -1456,19 +1332,16 @@ class ArticleReplaceApp(tk.Tk): 'workflow_id': CONFIG['Coze']['workflow_id'], 'access_token': CONFIG['Coze']['access_token'], 'is_async': CONFIG['Coze']['is_async'], - # 'input_data_template': CONFIG['Coze'].get('input_data_template', '') } CONFIG['Coze']['workflow_id'] = current_template.get('workflow_id', '') CONFIG['Coze']['access_token'] = current_template.get('access_token', '') CONFIG['Coze']['is_async'] = current_template.get('is_async', 'true') - # CONFIG['Coze']['input_data_template'] = current_template.get('input_data_template', '') logger.info(f"应用模板配置: {current_template.get('name')}") logger.info(f"Workflow ID: {CONFIG['Coze']['workflow_id']}") logger.info(f"Access Token: {'*' * len(CONFIG['Coze']['access_token'])}") logger.info(f"Is Async: {CONFIG['Coze']['is_async']}") - # logger.info(f"Input Template: {CONFIG['Coze']['input_data_template']}") # 读取链接并处理 logger.info(f"开始处理链接,使用 {num_threads} 个线程,生成类型: {generation_type}") @@ -1497,17 +1370,15 @@ class ArticleReplaceApp(tk.Tk): logger.error(f"处理任务出错: {e}") error_msg = str(e) self.after(0, lambda: messagebox.showerror("处理错误", f"处理任务出错: {error_msg}")) - # self.after(0, lambda e=e: messagebox.showerror("处理错误", f"处理任务出错: {e}")) finally: # 恢复原始配置(如果有的话) if original_config is not None: CONFIG['Coze']['workflow_id'] = original_config['workflow_id'] CONFIG['Coze']['access_token'] = original_config['access_token'] CONFIG['Coze']['is_async'] = original_config['is_async'] - # CONFIG['Coze']['input_data_template'] = original_config['input_data_template'] # 恢复开始按钮状态 - self.after(0, lambda: self.start_button.config(state=tk.NORMAL)) + self.after(0, lambda: self.start_button.configure(state="normal")) self.running = False def update_progress(self): @@ -1522,11 +1393,11 @@ class ArticleReplaceApp(tk.Tk): if total > 0: # 更新进度条 - progress = (done / total) * 100 - self.progress_var.set(progress) + progress = done / total + self.progress_bar.set(progress) # 更新标题显示进度 - self.title(f"文章采集与处理工具 - 进度: {progress:.1f}%") + self.title(f"文章采集与处理工具 - 进度: {progress * 100:.1f}%") # 继续更新 self.after(500, self.update_progress) @@ -1552,10 +1423,8 @@ class LogTextHandler(logging.Handler): msg = self.format(record) def append(): - self.text_widget.configure(state=tk.NORMAL) - self.text_widget.insert(tk.END, msg + '\n') - self.text_widget.see(tk.END) # 自动滚动到底部 - self.text_widget.configure(state=tk.DISABLED) + self.text_widget.insert("end", msg + '\n') + self.text_widget.see("end") # 在主线程中更新UI self.text_widget.after(0, append) @@ -1563,22 +1432,24 @@ class LogTextHandler(logging.Handler): # 主函数 def main(): - validator = AuthValidator(software_id="ArticleReplace", - api_url="http://km.taisan.online/api/v1", - gui_mode=True, - secret_key="taiyi1224" + # 设置CustomTkinter外观模式 + ctk.set_appearance_mode("System") + ctk.set_default_color_theme("blue") - ) + validator = AuthValidator( + software_id="ArticleReplace", + api_url="http://km.taisan.online/api/v1", + gui_mode=True, + secret_key="taiyi1224", + restore_tkinter=True + ) # 执行验证 if not validator.validate(): print("授权验证失败,程序退出") return - # y验证成功,继续执行程序逻辑 - - print("授权验证成功,启动程序。。。") - + print("授权验证成功,启动程序...") # 初始化日志 logging.basicConfig( @@ -1602,6 +1473,4 @@ def main(): if __name__ == "__main__": - - - main() + main() \ No newline at end of file diff --git a/ArticleReplace.py.bak b/ArticleReplace.py.bak new file mode 100644 index 0000000..ee61e76 --- /dev/null +++ b/ArticleReplace.py.bak @@ -0,0 +1,1609 @@ +import json +import sys # 导入sys模块 + +from PIL import Image, ImageDraw, ImageFont, ImageEnhance +import time +import random + +import threading +import tkinter as tk + +from config import * +from tkinter import ttk, messagebox, filedialog, simpledialog +from tkinter.scrolledtext import ScrolledText +import pandas as pd +import pymysql + +from main_process import link_to_text, task_queue, result_queue + +from auth_validator import AuthValidator + +sys.setrecursionlimit(5000) + + +class ArticleReplaceApp(tk.Tk): + def __init__(self): + super().__init__() + + self.title("文章工作流调用工具(软件仅供交流使用)") + self.geometry("900x600") + + # 创建标签页控件 + self.notebook = ttk.Notebook(self) + self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # 创建主页面 + self.main_frame = ttk.Frame(self.notebook) + self.notebook.add(self.main_frame, text="主页面") + + # 创建配置页面 + self.config_frame = ttk.Frame(self.notebook) + self.notebook.add(self.config_frame, text="配置") + + # 创建免责声明页面 + self.disclaimer_frame = ttk.Frame(self.notebook) + self.notebook.add(self.disclaimer_frame, text="免责声明") + + # 初始化变量 + self.running = False + self.thread = None + self.total_links = 0 + self.processed_links = 0 + + # 初始化Coze配置变量 + self.template_name_var = tk.StringVar() + self.coze_workflow_id_var = tk.StringVar(value=CONFIG['Coze']['workflow_id']) + self.coze_access_token_var = tk.StringVar(value=CONFIG['Coze']['access_token']) + self.coze_is_async_var = tk.StringVar(value=CONFIG['Coze'].get('is_async', 'true')) + # self.coze_input_data_template_var = tk.StringVar(value=CONFIG['Coze'].get('input_data_template', '{"article": "{article_text}", "link":"{link}", "weijin":"{weijin}"}')) + + # 初始化模板数据结构 + self.templates = { + "短篇": [], + "文章": [] + } + + # 初始化主页面 + self.init_main_frame() + # 初始化配置页面 + self.init_config_frame() + # 初始化免责声明页面 + self.init_disclaimer_frame() + + # 设置关闭窗口事件 + self.protocol("WM_DELETE_WINDOW", self.on_close) + + def init_main_frame(self): + # 创建左侧控制面板 + control_frame = ttk.LabelFrame(self.main_frame, text="控制面板") + control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10) + + # Excel文件选择 + ttk.Label(control_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.excel_path_var = tk.StringVar(value=TITLE_BASE_PATH) + ttk.Entry(control_frame, textvariable=self.excel_path_var, width=30).grid(row=0, column=1, padx=5, pady=5) + ttk.Button(control_frame, text="浏览", command=self.browse_excel).grid(row=0, column=2, padx=5, pady=5) + + # 线程数设置 + ttk.Label(control_frame, text="线程数:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + self.thread_count_var = tk.StringVar(value="1") + ttk.Spinbox(control_frame, from_=1, to=MAX_THREADS, textvariable=self.thread_count_var, width=5).grid(row=1, + column=1, + padx=5, + pady=5, + sticky=tk.W) + + # AI服务提供商选择 + ttk.Label(control_frame, text="工作流选择:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + self.ai_service_var = tk.StringVar(value="coze") + ai_service_combo = ttk.Combobox(control_frame, textvariable=self.ai_service_var, values=["dify", "coze"], + width=10, state="readonly") + ai_service_combo.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W) + + # 生成类型选择 + ttk.Label(control_frame, text="生成类型:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) + self.generation_type_var = tk.StringVar(value="文章") + self.generation_type_combo = ttk.Combobox(control_frame, textvariable=self.generation_type_var, + values=["短篇", "文章"], width=10, state="readonly") + self.generation_type_combo.grid(row=3, column=1, padx=5, pady=5, sticky=tk.W) + self.generation_type_combo.bind("<>", self.on_generation_type_changed) + + # 开始按钮 + self.start_button = ttk.Button(control_frame, text="开始处理", command=self.start_processing) + self.start_button.grid(row=4, column=0, columnspan=3, padx=5, pady=20) + + # 进度条 + ttk.Label(control_frame, text="处理进度:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W) + self.progress_var = tk.DoubleVar() + ttk.Progressbar(control_frame, variable=self.progress_var, maximum=100).grid(row=5, column=1, columnspan=2, + padx=5, pady=5, sticky=tk.EW) + + # 创建右侧日志面板 + log_frame = ttk.LabelFrame(self.main_frame, text="日志") + log_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10) + + # 日志文本框 + self.log_text = ScrolledText(log_frame, width=70, height=30) + self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + self.log_text.config(state=tk.DISABLED) + + # 添加日志处理器 + self.log_handler = LogTextHandler(self.log_text) + self.log_handler.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + self.log_handler.setFormatter(formatter) + logger.addHandler(self.log_handler) + + def init_config_frame(self): + # 创建配置标签页 + config_notebook = ttk.Notebook(self.config_frame) + config_notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # 创建各个配置页面 + general_frame = ttk.Frame(config_notebook) + database_frame = ttk.Frame(config_notebook) + dify_frame = ttk.Frame(config_notebook) + coze_frame = ttk.Frame(config_notebook) + baidu_frame = ttk.Frame(config_notebook) + image_frame = ttk.Frame(config_notebook) + keywords_frame = ttk.Frame(config_notebook) + + # 添加到标签页 + config_notebook.add(general_frame, text="常规设置") + config_notebook.add(database_frame, text="数据库设置") + config_notebook.add(dify_frame, text="Dify设置") + config_notebook.add(coze_frame, text="Coze设置") + config_notebook.add(baidu_frame, text="百度API设置") + config_notebook.add(image_frame, text="图片处理设置") + config_notebook.add(keywords_frame, text="违禁词设置") + + # 初始化各个配置页面 + self.init_general_config(general_frame) + self.init_database_config(database_frame) + self.init_dify_config(dify_frame) + self.init_coze_config(coze_frame) + self.init_baidu_config(baidu_frame) + self.init_image_config(image_frame) + self.init_keywords_config(keywords_frame) + + # 保存按钮 + save_button = ttk.Button(self.config_frame, text="保存所有配置", command=self.save_all_configs) + save_button.pack(side=tk.RIGHT, padx=10, pady=10) + + def init_general_config(self, parent): + # Chrome用户目录 + ttk.Label(parent, text="Chrome用户目录:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.chrome_dir_var = tk.StringVar(value=CONFIG['General']['chrome_user_dir']) + ttk.Entry(parent, textvariable=self.chrome_dir_var, width=50).grid(row=0, column=1, padx=5, pady=5) + ttk.Button(parent, text="浏览", command=lambda: self.browse_directory(self.chrome_dir_var)).grid(row=0, + column=2, + padx=5, pady=5) + + # 文章保存路径 + ttk.Label(parent, text="文章保存路径:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + self.articles_path_var = tk.StringVar(value=CONFIG['General']['articles_path']) + ttk.Entry(parent, textvariable=self.articles_path_var, width=50).grid(row=1, column=1, padx=5, pady=5) + ttk.Button(parent, text="浏览", command=lambda: self.browse_directory(self.articles_path_var)).grid(row=1, + column=2, + padx=5, + pady=5) + + # 图片保存路径 + ttk.Label(parent, text="图片保存路径:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + self.images_path_var = tk.StringVar(value=CONFIG['General']['images_path']) + self.grid = ttk.Entry(parent, textvariable=self.images_path_var, width=50).grid(row=2, column=1, padx=5, pady=5) + ttk.Button(parent, text="浏览", command=lambda: self.browse_directory(self.images_path_var)).grid(row=2, + column=2, + padx=5, + pady=5) + + # Excel文件路径 + ttk.Label(parent, text="默认Excel文件:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) + self.excel_file_var = tk.StringVar(value=CONFIG['General']['title_file']) + ttk.Entry(parent, textvariable=self.excel_file_var, width=50).grid(row=3, column=1, padx=5, pady=5) + ttk.Button(parent, text="浏览", command=lambda: self.browse_file(self.excel_file_var, [("Excel文件", "*.xlsx"), + ("所有文件", + "*.*")])).grid(row=3, + column=2, + padx=5, + pady=5) + + # 最大线程数 + ttk.Label(parent, text="最大线程数:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W) + self.max_threads_var = tk.StringVar(value=CONFIG['General']['max_threads']) + ttk.Spinbox(parent, from_=1, to=10, textvariable=self.max_threads_var, width=5).grid(row=4, column=1, padx=5, + pady=5, sticky=tk.W) + + # 最小文章字数 + ttk.Label(parent, text="最小文章字数:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W) + self.min_article_length_var = tk.StringVar(value=CONFIG['General'].get('min_article_length', '100')) + ttk.Spinbox(parent, from_=0, to=10000, textvariable=self.min_article_length_var, width=5).grid(row=5, column=1, padx=5, + pady=5, sticky=tk.W) + + # 保存按钮 + ttk.Button(parent, text="保存配置", command=self.save_general_config).grid(row=6, column=1, padx=5, pady=10, + sticky=tk.E) + + def init_database_config(self, parent): + # 数据库主机 + ttk.Label(parent, text="数据库主机:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.db_host_var = tk.StringVar(value=CONFIG['Database']['host']) + ttk.Entry(parent, textvariable=self.db_host_var, width=30).grid(row=0, column=1, padx=5, pady=5) + + # 数据库用户名 + ttk.Label(parent, text="数据库用户名:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + self.db_user_var = tk.StringVar(value=CONFIG['Database']['user']) + ttk.Entry(parent, textvariable=self.db_user_var, width=30).grid(row=1, column=1, padx=5, pady=5) + + # 数据库密码 + ttk.Label(parent, text="数据库密码:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + self.db_password_var = tk.StringVar(value=CONFIG['Database']['password']) + ttk.Entry(parent, textvariable=self.db_password_var, width=30, show="*").grid(row=2, column=1, padx=5, pady=5) + + # 数据库名称 + ttk.Label(parent, text="数据库名称:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) + self.db_name_var = tk.StringVar(value=CONFIG['Database']['database']) + ttk.Entry(parent, textvariable=self.db_name_var, width=30).grid(row=3, column=1, padx=5, pady=5) + + # 测试连接按钮 + ttk.Button(parent, text="测试连接", command=self.test_db_connection).grid(row=4, column=1, padx=5, pady=5, + sticky=tk.E) + + # 保存按钮 + ttk.Button(parent, text="保存配置", command=self.save_database_config).grid(row=5, column=1, padx=5, pady=10, + sticky=tk.E) + + def init_dify_config(self, parent): + # Dify API Key + ttk.Label(parent, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.dify_api_key_var = tk.StringVar(value=CONFIG['Dify']['api_key']) + ttk.Entry(parent, textvariable=self.dify_api_key_var, width=50).grid(row=0, column=1, padx=5, pady=5) + + # Dify User ID + ttk.Label(parent, text="User ID:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + self.dify_user_id_var = tk.StringVar(value=CONFIG['Dify']['user_id']) + ttk.Entry(parent, textvariable=self.dify_user_id_var, width=30).grid(row=1, column=1, padx=5, pady=5) + + # Dify URL + ttk.Label(parent, text="URL:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + self.dify_url_var = tk.StringVar(value=CONFIG['Dify']['url']) + ttk.Entry(parent, textvariable=self.dify_url_var, width=50).grid(row=2, column=1, padx=5, pady=5) + + # Dify Input Data Template + ttk.Label(parent, text="Input Data模板:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) + self.dify_input_data_template_var = tk.StringVar( + value=CONFIG['Dify'].get('input_data_template', '{"old_article": "{article_text}"}')) # 添加默认值 + ttk.Entry(parent, textvariable=self.dify_input_data_template_var, width=50).grid(row=3, column=1, padx=5, + pady=5) + + # 保存按钮 + ttk.Button(parent, text="保存配置", command=self.save_dify_config).grid(row=4, column=1, padx=5, pady=10, + sticky=tk.E) + + def init_coze_config(self, parent): + # 生成类型选择(与主页面联动) + type_frame = ttk.Frame(parent) + type_frame.grid(row=0, column=0, columnspan=3, padx=5, pady=5, sticky=tk.EW) + ttk.Label(type_frame, text="生成类型:").pack(side=tk.LEFT, padx=5) + self.coze_generation_type_var = tk.StringVar(value="短篇") + self.coze_generation_type_combo = ttk.Combobox(type_frame, textvariable=self.coze_generation_type_var, + values=["短篇", "文章"], width=10, state="readonly") + self.coze_generation_type_combo.pack(side=tk.LEFT, padx=5) + self.coze_generation_type_combo.bind("<>", self.on_coze_generation_type_changed) + + # 编辑状态标签 + self.edit_status_label = ttk.Label(type_frame, text="", foreground="blue") + self.edit_status_label.pack(side=tk.LEFT, padx=20) + + # 加载已保存的模板 + self.load_templates() + + # 初始化变量跟踪 + self._setup_var_trace() + + # 模板管理框架 + template_frame = ttk.LabelFrame(parent, text="模板管理") + template_frame.grid(row=1, column=0, columnspan=3, padx=5, pady=10, sticky=tk.EW) + + # 模板列表和滚动条 + list_frame = ttk.Frame(template_frame) + list_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + ttk.Label(template_frame, text="模板列表:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.template_listbox = tk.Listbox(list_frame, height=5, width=30) + scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.template_listbox.yview) + self.template_listbox.configure(yscrollcommand=scrollbar.set) + self.template_listbox.pack(side=tk.LEFT, fill=tk.Y) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + self.template_listbox.bind("<>", self.on_template_selected) + + # 模板操作按钮 + button_frame = ttk.Frame(template_frame) + button_frame.grid(row=1, column=1, padx=10, pady=5, sticky=tk.N) + ttk.Button(button_frame, text="新增模板", command=self.add_template).pack(pady=2) + ttk.Button(button_frame, text="删除模板", command=self.delete_template).pack(pady=2) + ttk.Button(button_frame, text="重命名模板", command=self.rename_template).pack(pady=2) + ttk.Button(button_frame, text="保存模板", command=self.save_template).pack(pady=2) + ttk.Button(button_frame, text="复制模板", command=self.duplicate_template).pack(pady=2) + ttk.Button(button_frame, text="使用模板", command=self.use_template).pack(pady=2) + + # 当前模板配置 + config_frame = ttk.LabelFrame(parent, text="当前模板配置") + config_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=10, sticky=tk.EW) + + # 模板名称 + ttk.Label(config_frame, text="模板名称:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + ttk.Entry(config_frame, textvariable=self.template_name_var, width=30).grid(row=0, column=1, padx=5, pady=5) + + # Coze Workflow ID + ttk.Label(config_frame, text="Workflow ID:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + ttk.Entry(config_frame, textvariable=self.coze_workflow_id_var, width=50).grid(row=1, column=1, padx=5, pady=5) + + # Coze Access Token + ttk.Label(config_frame, text="Access Token:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + ttk.Entry(config_frame, textvariable=self.coze_access_token_var, width=50).grid(row=2, column=1, padx=5, pady=5) + + # Coze Is Async + ttk.Label(config_frame, text="Is Async:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) + ttk.Combobox(config_frame, textvariable=self.coze_is_async_var, values=["true", "false"], width=10, + state="readonly").grid(row=3, column=1, padx=5, pady=5, sticky=tk.W) + + # # Coze Input Data Template + # ttk.Label(config_frame, text="Input Data模板:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W) + # # Variable already initialized in __init__ + # ttk.Entry(config_frame, textvariable=self.coze_input_data_template_var, width=50).grid(row=4, column=1, padx=5, pady=5) + + # 保存按钮 + ttk.Button(config_frame, text="保存配置", command=self.save_coze_config).grid(row=5, column=1, padx=5, pady=10, + sticky=tk.E) + + # 更新模板列表 + self.update_template_list() + + # 自动加载上次使用的模板 + self.load_last_used_template() + + def init_baidu_config(self, parent): + # 百度 API Key + ttk.Label(parent, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.baidu_api_key_var = tk.StringVar(value=CONFIG['Baidu']['api_key']) + ttk.Entry(parent, textvariable=self.baidu_api_key_var, width=50).grid(row=0, column=1, padx=5, pady=5) + + # 百度 Secret Key + ttk.Label(parent, text="Secret Key:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + self.baidu_secret_key_var = tk.StringVar(value=CONFIG['Baidu']['secret_key']) + ttk.Entry(parent, textvariable=self.baidu_secret_key_var, width=50).grid(row=1, column=1, padx=5, pady=5) + + # 是否启用违规检测 + ttk.Label(parent, text="启用违规检测:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + self.baidu_enable_detection_var = tk.StringVar(value=CONFIG['Baidu'].get('enable_detection', 'false')) + enable_detection_combo = ttk.Combobox(parent, textvariable=self.baidu_enable_detection_var, + values=["true", "false"], width=10, state="readonly") + enable_detection_combo.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W) + + # 保存按钮 + ttk.Button(parent, text="保存配置", command=self.save_baidu_config).grid(row=3, column=1, padx=5, pady=10, + sticky=tk.E) + + def init_image_config(self, parent): + # 裁剪百分比 + ttk.Label(parent, text="裁剪百分比:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.crop_percent_var = tk.StringVar(value=CONFIG['ImageModify']['crop_percent']) + ttk.Entry(parent, textvariable=self.crop_percent_var, width=10).grid(row=0, column=1, padx=5, pady=5, + sticky=tk.W) + + # 最小旋转角度 + ttk.Label(parent, text="最小旋转角度:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) + self.min_rotation_var = tk.StringVar(value=CONFIG['ImageModify']['min_rotation']) + ttk.Entry(parent, textvariable=self.min_rotation_var, width=10).grid(row=1, column=1, padx=5, pady=5, + sticky=tk.W) + + # 最大旋转角度 + ttk.Label(parent, text="最大旋转角度:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) + self.max_rotation_var = tk.StringVar(value=CONFIG['ImageModify']['max_rotation']) + ttk.Entry(parent, textvariable=self.max_rotation_var, width=10).grid(row=2, column=1, padx=5, pady=5, + sticky=tk.W) + + # 最小亮度 + ttk.Label(parent, text="最小亮度:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) + self.min_brightness_var = tk.StringVar(value=CONFIG['ImageModify']['min_brightness']) + ttk.Entry(parent, textvariable=self.min_brightness_var, width=10).grid(row=3, column=1, padx=5, pady=5, + sticky=tk.W) + + # 最大亮度 + ttk.Label(parent, text="最大亮度:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W) + self.max_brightness_var = tk.StringVar(value=CONFIG['ImageModify']['max_brightness']) + ttk.Entry(parent, textvariable=self.max_brightness_var, width=10).grid(row=4, column=1, padx=5, pady=5, + sticky=tk.W) + + # 水印文字 + ttk.Label(parent, text="水印文字:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W) + self.watermark_text_var = tk.StringVar(value=CONFIG['ImageModify']['watermark_text']) + ttk.Entry(parent, textvariable=self.watermark_text_var, width=30).grid(row=0, column=3, padx=5, pady=5) + + # 水印透明度 + ttk.Label(parent, text="水印透明度:").grid(row=1, column=2, padx=5, pady=5, sticky=tk.W) + self.watermark_opacity_var = tk.StringVar(value=CONFIG['ImageModify']['watermark_opacity']) + ttk.Entry(parent, textvariable=self.watermark_opacity_var, width=10).grid(row=1, column=3, padx=5, pady=5, + sticky=tk.W) + + # 蒙版透明度 + ttk.Label(parent, text="蒙版透明度:").grid(row=2, column=2, padx=5, pady=5, sticky=tk.W) + self.overlay_opacity_var = tk.StringVar(value=CONFIG['ImageModify']['overlay_opacity']) + ttk.Entry(parent, textvariable=self.overlay_opacity_var, width=10).grid(row=2, column=3, padx=5, pady=5, + sticky=tk.W) + + # 预览按钮 + ttk.Button(parent, text="预览效果", command=self.preview_image_effect).grid(row=4, column=3, padx=5, pady=5, + sticky=tk.E) + + # 保存按钮 + ttk.Button(parent, text="保存配置", command=self.save_image_config).grid(row=5, column=3, padx=5, pady=10, + sticky=tk.E) + + def init_keywords_config(self, parent): + # 违禁词列表 + ttk.Label(parent, text="违禁词列表:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) + self.banned_words_text = ScrolledText(parent, width=60, height=15) + self.banned_words_text.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky=tk.NSEW) + self.banned_words_text.insert(tk.END, CONFIG['Keywords']['banned_words'].replace(',', '\n')) + + # 保存按钮 + ttk.Button(parent, text="保存违禁词", command=self.save_banned_words).grid(row=2, column=1, padx=5, pady=5, + sticky=tk.E) + + # 配置行列权重 + parent.columnconfigure(0, weight=1) + parent.rowconfigure(1, weight=1) + + def init_disclaimer_frame(self): + # 创建免责声明内容框架 + disclaimer_content = ttk.Frame(self.disclaimer_frame) + disclaimer_content.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + + # 标题 + title_label = ttk.Label(disclaimer_content, text="免责声明", font=("Arial", 16, "bold")) + title_label.pack(pady=10) + + # 免责声明文本 + disclaimer_text = ScrolledText(disclaimer_content, width=80, height=20, wrap=tk.WORD) + disclaimer_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + disclaimer_text.insert(tk.END, """ +软件使用免责声明 + +1. 合法使用声明 + 本软件仅供合法、正当用途使用。用户应当遵守中华人民共和国相关法律法规,不得将本软件用于任何违法犯罪活动。 + +2. 内容责任声明 + 用户通过本软件生成、处理或发布的所有内容,其版权归属、合法性及内容真实性由用户自行负责。本软件开发者不对用户使用本软件处理的内容承担任何法律责任。 + +3. 使用风险声明 + 用户应自行承担使用本软件的风险。本软件按"现状"提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权性的保证。 + +4. 禁止用途 + 严禁将本软件用于以下活动: + - 违反国家法律法规的活动 + - 侵犯他人知识产权或其他合法权益的活动 + - 传播虚假、欺诈或误导性信息的活动 + - 从事任何可能危害国家安全、社会稳定的活动 + - 其他违背社会公德、商业道德的活动 + +5. 责任限制 + 在法律允许的最大范围内,对于因使用或无法使用本软件而导致的任何直接、间接、偶然、特殊、惩罚性或后果性损害,本软件开发者不承担任何责任。 + +6. 协议更新 + 本免责声明可能会不定期更新,更新后的内容将在软件中公布,不再另行通知。用户继续使用本软件即表示接受修改后的免责声明。 + +7. 最终解释 + 本免责声明的最终解释权归本软件开发者所有。 + """) + disclaimer_text.config(state=tk.DISABLED) # 设置为只读 + + # 确认按钮 + confirm_frame = ttk.Frame(disclaimer_content) + confirm_frame.pack(pady=10) + ttk.Button(confirm_frame, text="我已阅读并同意以上声明", command=lambda: self.notebook.select(0)).pack() + + def save_all_configs(self): + """保存所有配置到配置文件""" + try: + # 保存所有单独的配置 + self.save_general_config() + self.save_database_config() + self.save_dify_config() + self.save_coze_config() + self.save_baidu_config() + self.save_image_config() + self.save_banned_words() + + messagebox.showinfo("保存成功", "所有配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存配置时出错:{e}") + + # 保存按钮 + save_button = ttk.Button(self.config_frame, text="保存所有配置", command=self.save_all_configs) + save_button.pack(side=tk.RIGHT, padx=10, pady=10) + + def on_coze_generation_type_changed(self, event=None): + """coze页面生成类型改变时的处理""" + + def save_general_config(self): + # 保存常规配置 + try: + CONFIG['General']['chrome_user_dir'] = self.chrome_dir_var.get() + CONFIG['General']['articles_path'] = self.articles_path_var.get() + CONFIG['General']['images_path'] = self.images_path_var.get() + CONFIG['General']['title_file'] = self.excel_file_var.get() + CONFIG['General']['max_threads'] = self.max_threads_var.get() + CONFIG['General']['min_article_length'] = self.min_article_length_var.get() + + save_config(CONFIG) + + # 更新全局变量 + global USER_DIR_PATH, ARTICLES_BASE_PATH, IMGS_BASE_PATH, TITLE_BASE_PATH, MAX_THREADS + USER_DIR_PATH = CONFIG['General']['chrome_user_dir'] + ARTICLES_BASE_PATH = CONFIG['General']['articles_path'] + IMGS_BASE_PATH = CONFIG['General']['images_path'] + TITLE_BASE_PATH = CONFIG['General']['title_file'] + MAX_THREADS = int(CONFIG['General']['max_threads']) + + # 创建必要的目录 + if not os.path.exists(ARTICLES_BASE_PATH): + os.makedirs(ARTICLES_BASE_PATH) + if not os.path.exists(IMGS_BASE_PATH): + os.makedirs(IMGS_BASE_PATH) + + messagebox.showinfo("保存成功", "常规配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存常规配置时出错:{e}") + + def save_database_config(self): + # 保存数据库配置 + try: + CONFIG['Database']['host'] = self.db_host_var.get() + CONFIG['Database']['user'] = self.db_user_var.get() + CONFIG['Database']['password'] = self.db_password_var.get() + CONFIG['Database']['database'] = self.db_name_var.get() + + save_config(CONFIG) + messagebox.showinfo("保存成功", "数据库配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存数据库配置时出错:{e}") + + def save_dify_config(self): + # 保存Dify配置 + try: + CONFIG['Dify']['api_key'] = self.dify_api_key_var.get() + CONFIG['Dify']['user_id'] = self.dify_user_id_var.get() + CONFIG['Dify']['url'] = self.dify_url_var.get() + CONFIG['Dify']['input_data_template'] = self.dify_input_data_template_var.get() + + save_config(CONFIG) + messagebox.showinfo("保存成功", "Dify配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存Dify配置时出错:{e}") + + def save_coze_config(self): + # 保存当前Coze模板配置 + try: + # 获取当前选中的模板 + selection = self.template_listbox.curselection() + if not selection: + # 如果没有选中模板,只保存全局Coze配置 + CONFIG['Coze']['workflow_id'] = self.coze_workflow_id_var.get() + CONFIG['Coze']['access_token'] = self.coze_access_token_var.get() + CONFIG['Coze']['is_async'] = self.coze_is_async_var.get() + save_config(CONFIG) + messagebox.showinfo("保存成功", "Coze全局配置已保存") + return + + # 获取当前选中的模板索引 + index = selection[0] + current_type = self.coze_generation_type_var.get() + + if current_type not in self.templates or index >= len(self.templates[current_type]): + messagebox.showerror("错误", "无效的模板选择") + return + + # 更新模板配置 + template = self.templates[current_type][index] + template['name'] = self.template_name_var.get() + template['workflow_id'] = self.coze_workflow_id_var.get() + template['access_token'] = self.coze_access_token_var.get() + template['is_async'] = self.coze_is_async_var.get() + + # 保存模板到配置文件 + self.save_templates() + + # 同时更新全局Coze配置(如果需要的话) + CONFIG['Coze']['workflow_id'] = self.coze_workflow_id_var.get() + CONFIG['Coze']['access_token'] = self.coze_access_token_var.get() + CONFIG['Coze']['is_async'] = self.coze_is_async_var.get() + save_config(CONFIG) + + self.edit_status_label.config(text="已保存", foreground="green") + self.after(2000, lambda: self.edit_status_label.config(text="")) + messagebox.showinfo("保存成功", f"模板 '{template['name']}' 配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存Coze配置时出错:{e}") + + def save_baidu_config(self): + # 保存百度API配置 + try: + CONFIG['Baidu']['api_key'] = self.baidu_api_key_var.get() + CONFIG['Baidu']['secret_key'] = self.baidu_secret_key_var.get() + CONFIG['Baidu']['enable_detection'] = self.baidu_enable_detection_var.get() + + save_config(CONFIG) + messagebox.showinfo("保存成功", "百度API配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存百度API配置时出错:{e}") + + def save_image_config(self): + # 保存图片处理配置 + try: + CONFIG['ImageModify']['crop_percent'] = self.crop_percent_var.get() + CONFIG['ImageModify']['min_rotation'] = self.min_rotation_var.get() + CONFIG['ImageModify']['max_rotation'] = self.max_rotation_var.get() + CONFIG['ImageModify']['min_brightness'] = self.min_brightness_var.get() + CONFIG['ImageModify']['max_brightness'] = self.max_brightness_var.get() + CONFIG['ImageModify']['watermark_text'] = self.watermark_text_var.get() + CONFIG['ImageModify']['watermark_opacity'] = self.watermark_opacity_var.get() + CONFIG['ImageModify']['overlay_opacity'] = self.overlay_opacity_var.get() + + save_config(CONFIG) + messagebox.showinfo("保存成功", "图片处理配置已保存") + except Exception as e: + messagebox.showerror("保存失败", f"保存图片处理配置时出错:{e}") + + def save_banned_words(self): + # 处理文本,将换行符替换为逗号 + words = self.banned_words_text.get(1.0, tk.END).strip().replace('\n', ',') + CONFIG['Keywords']['banned_words'] = words + save_config(CONFIG) + messagebox.showinfo("保存成功", "违禁词列表已更新") + + # 同步到主页面 + self.generation_type_var.set(self.coze_generation_type_var.get()) + self.update_template_list() + + def on_generation_type_changed(self, event=None): + """主页面生成类型改变时的处理""" + # 同步到coze页面 + self.coze_generation_type_var.set(self.generation_type_var.get()) + self.update_template_list() + + def update_template_list(self): + """更新模板列表显示""" + current_type = self.coze_generation_type_var.get() + self.template_listbox.delete(0, tk.END) + + if current_type in self.templates: + for template in self.templates[current_type]: + self.template_listbox.insert(tk.END, template['name']) + + def on_template_selected(self, event=None): + """模板选择时的处理""" + selection = self.template_listbox.curselection() + if selection: + index = selection[0] + current_type = self.coze_generation_type_var.get() + if current_type in self.templates and index < len(self.templates[current_type]): + template = self.templates[current_type][index] + self.load_template_config(template) + + # 更新上次使用的模板信息 + CONFIG['Coze']['last_used_template'] = template['name'] + CONFIG['Coze']['last_used_template_type'] = current_type + save_config(CONFIG) # 保存配置文件 + + def load_template_config(self, template): + """加载模板配置到界面""" + # 解绑之前的变量跟踪 + self._unbind_var_trace() + + self.template_name_var.set(template['name']) + self.coze_workflow_id_var.set(template.get('workflow_id', '')) + self.coze_access_token_var.set(template.get('access_token', '')) + self.coze_is_async_var.set(template.get('is_async', 'true')) + # self.coze_input_data_template_var.set(template.get('input_data_template', '')) + + self.edit_status_label.config(text="已加载", foreground="blue") + self.after(2000, lambda: self.edit_status_label.config(text="")) + + # 重新绑定变量跟踪 + self._setup_var_trace() + + def _setup_var_trace(self): + """设置变量跟踪以显示编辑状态""" + self.var_traces = [] + for var in [self.template_name_var, self.coze_workflow_id_var, + self.coze_access_token_var, self.coze_is_async_var]: + # self.coze_input_data_template_var]: + trace_id = var.trace_add('write', lambda *args: self._show_edit_status()) + self.var_traces.append((var, trace_id)) + + def _unbind_var_trace(self): + """解绑变量跟踪""" + if hasattr(self, 'var_traces'): + for var, trace_id in self.var_traces: + try: + var.trace_remove('write', trace_id) + except Exception: + pass + self.var_traces = [] + + def _show_edit_status(self): + """显示编辑状态""" + self.edit_status_label.config(text="未保存", foreground="red") + + def add_template(self): + """添加新模板""" + current_type = self.coze_generation_type_var.get() + if current_type not in self.templates: + self.templates[current_type] = [] + + # 弹出对话框让用户输入模板名称 + new_template_name = simpledialog.askstring("新增模板", "请输入新模板的名称:") + if new_template_name: + new_template_name = new_template_name.strip() + if not new_template_name: + messagebox.showwarning("输入无效", "模板名称不能为空。") + return + + # 检查模板名称是否重复 + if any(t['name'] == new_template_name for t in self.templates[current_type]): + messagebox.showwarning("名称重复", f"模板名称 '{new_template_name}' 已存在,请使用其他名称。") + return + + new_template = { + 'name': new_template_name, + 'workflow_id': '', + 'access_token': '', + 'is_async': 'true', + # 'input_data_template': '{"article": "{article_text}", "link":"{link}", "weijin":"{weijin}"}' + } + self.templates[current_type].append(new_template) + self.update_template_list() + self.save_templates() + + # 选中新添加的模板 + new_index = len(self.templates[current_type]) - 1 + self.template_listbox.selection_clear(0, tk.END) + self.template_listbox.selection_set(new_index) + # selection_set会触发on_template_selected事件,自动加载模板配置 + + # 延迟设置状态,确保覆盖on_template_selected中设置的状态 + self.after(100, lambda: self.edit_status_label.config(text="已添加", foreground="green")) + self.after(2100, lambda: self.edit_status_label.config(text="")) + else: + messagebox.showinfo("取消操作", "已取消新增模板。") + + def delete_template(self): + """删除选中的模板""" + selection = self.template_listbox.curselection() + if not selection: + messagebox.showwarning("提示", "请先选择要删除的模板") + return + + index = selection[0] + current_type = self.coze_generation_type_var.get() + if current_type not in self.templates or index >= len(self.templates[current_type]): + return + + template_name = self.templates[current_type][index]['name'] + if messagebox.askyesno("确认删除", f"确定要删除模板 '{template_name}' 吗?"): + del self.templates[current_type][index] + self.update_template_list() + self.save_templates() + + # 清除配置并更新状态 + self.clear_template_config() + self.edit_status_label.config(text=f"已删除 '{template_name}'", foreground="red") + self.after(2000, lambda: self.edit_status_label.config(text="")) + + # 如果还有模板,选中最后一个 + if self.templates[current_type]: + last_index = len(self.templates[current_type]) - 1 + self.template_listbox.selection_set(last_index) + self.load_template_config(self.templates[current_type][last_index]) + + def validate_template(self): + """验证模板配置""" + name = self.template_name_var.get().strip() + workflow_id = self.coze_workflow_id_var.get().strip() + access_token = self.coze_access_token_var.get().strip() + # input_template = self.coze_input_data_template_var.get().strip() + + if not name: + messagebox.showerror("错误", "模板名称不能为空") + return False + + if not workflow_id: + messagebox.showerror("错误", "Workflow ID不能为空") + return False + + if not access_token: + messagebox.showerror("错误", "Access Token不能为空") + return False + + # if not input_template: + # messagebox.showerror("错误", "输入数据模板不能为空") + # return False + # + # # 验证输入数据模板的JSON格式 + # try: + # # 替换占位符以便验证JSON格式 + # test_template = input_template.replace('{article_text}', '""')\ + # .replace('{link}', '""')\ + # .replace('{weijin}', '""')\ + # .replace('{title_text}', '""') + # json.loads(test_template) + # except json.JSONDecodeError as e: + # messagebox.showerror("错误", f"输入数据模板不是有效的JSON格式:\n{str(e)}") + # return False + + return True + + def save_template(self): + """保存当前模板配置""" + if not self.validate_template(): + return + + selection = self.template_listbox.curselection() + if selection: + index = selection[0] + current_type = self.coze_generation_type_var.get() + if current_type in self.templates and index < len(self.templates[current_type]): + template = self.templates[current_type][index] + new_name = self.template_name_var.get().strip() + if not new_name: + messagebox.showwarning("输入无效", "模板名称不能为空。") + return + + # 检查新名称是否重复,排除当前模板自身 + if new_name != template['name'] and any(t['name'] == new_name for t in self.templates[current_type]): + messagebox.showwarning("名称重复", f"模板名称 '{new_name}' 已存在,请使用其他名称。") + return + + template['name'] = new_name + template['workflow_id'] = self.coze_workflow_id_var.get().strip() + template['access_token'] = self.coze_access_token_var.get().strip() + template['is_async'] = self.coze_is_async_var.get() + # template['input_data_template'] = self.coze_input_data_template_var.get().strip() + + # 更新上次使用的模板信息 + CONFIG['Coze']['last_used_template'] = template['name'] + CONFIG['Coze']['last_used_template_type'] = current_type + + self.update_template_list() + self.save_templates() + save_config(CONFIG) # 保存配置文件 + + self.edit_status_label.config(text="已保存", foreground="green") + self.after(2000, lambda: self.edit_status_label.config(text="")) + else: + messagebox.showwarning("未选择模板", "请先选择要保存的模板") + + def rename_template(self): + """重命名当前选中的模板""" + selection = self.template_listbox.curselection() + if not selection: + messagebox.showwarning("未选择模板", "请先选择要重命名的模板") + return + + index = selection[0] + current_type = self.coze_generation_type_var.get() + if current_type not in self.templates or index >= len(self.templates[current_type]): + return + + template = self.templates[current_type][index] + old_name = template['name'] + + # 弹出重命名对话框 + new_name = simpledialog.askstring("重命名模板", "请输入新的模板名称:", initialvalue=old_name) + if new_name: + new_name = new_name.strip() + if not new_name: + messagebox.showwarning("输入无效", "模板名称不能为空。") + return + if new_name == old_name: + messagebox.showinfo("未修改", "新名称与旧名称相同,无需重命名。") + return + # 检查新名称是否重复 + if any(t['name'] == new_name for t in self.templates[current_type] if t != template): + messagebox.showwarning("名称重复", f"模板名称 '{new_name}' 已存在,请使用其他名称。") + return + + template['name'] = new_name + self.update_template_list() + self.save_templates() + # 重新选中重命名后的模板 + self.template_listbox.selection_set(index) + self.edit_status_label.config(text="已重命名", foreground="green") + self.after(2000, lambda: self.edit_status_label.config(text="")) + else: + messagebox.showinfo("取消操作", "已取消重命名模板。") + + def duplicate_template(self): + """复制当前选中的模板""" + selection = self.template_listbox.curselection() + if not selection: + messagebox.showwarning("提示", "请先选择要复制的模板") + return + + index = selection[0] + current_type = self.coze_generation_type_var.get() + if current_type not in self.templates or index >= len(self.templates[current_type]): + return + + template = self.templates[current_type][index] + new_template = template.copy() + + # 生成新的模板名称,确保唯一性 + base_name = template['name'] + copy_num = 1 + new_name = f"{base_name}_副本" + while any(t['name'] == new_name for t in self.templates[current_type]): + copy_num += 1 + new_name = f"{base_name}_副本{copy_num}" + new_template['name'] = new_name + + self.templates[current_type].append(new_template) + self.update_template_list() + self.save_templates() + + # 选中新复制的模板 + new_index = len(self.templates[current_type]) - 1 + self.template_listbox.selection_clear(0, tk.END) + self.template_listbox.selection_set(new_index) + # selection_set会触发on_template_selected事件,自动加载模板配置 + + # 延迟设置状态,确保覆盖on_template_selected中设置的状态 + self.after(100, lambda: self.edit_status_label.config(text="已复制", foreground="green")) + self.after(2100, lambda: self.edit_status_label.config(text="")) + + def use_template(self): + """使用模板功能 - 弹出模板选择对话框并应用所选模板配置""" + # 创建模板选择对话框 + dialog = tk.Toplevel(self) + dialog.title("选择模板") + dialog.geometry("400x400") + dialog.transient(self) # 设置为应用程序的子窗口 + dialog.grab_set() # 模态对话框 + dialog.resizable(False, False) + + # 创建说明标签 + ttk.Label(dialog, text="请选择要使用的模板:", font=("Arial", 10)).pack(pady=10) + + # 创建模板类型选择框架 + type_frame = ttk.Frame(dialog) + type_frame.pack(fill=tk.X, padx=10, pady=5) + + ttk.Label(type_frame, text="模板类型:").pack(side=tk.LEFT, padx=5) + dialog_type_var = tk.StringVar(value=self.coze_generation_type_var.get()) + type_combo = ttk.Combobox(type_frame, textvariable=dialog_type_var, values=["短篇", "文章"], width=10, + state="readonly") + type_combo.pack(side=tk.LEFT, padx=5) + + # 创建模板列表框架 + list_frame = ttk.Frame(dialog) + list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # 创建模板列表和滚动条 + template_listbox = tk.Listbox(list_frame, height=10, width=40) + scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=template_listbox.yview) + template_listbox.configure(yscrollcommand=scrollbar.set) + template_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # 填充模板列表 + def update_dialog_template_list(): + template_listbox.delete(0, tk.END) + current_type = dialog_type_var.get() + if current_type in self.templates: + for template in self.templates[current_type]: + template_listbox.insert(tk.END, template['name']) + + update_dialog_template_list() + + # 绑定类型选择变更事件 + def on_dialog_type_changed(event=None): + update_dialog_template_list() + + type_combo.bind("<>", on_dialog_type_changed) + + # 创建按钮框架 + button_frame = ttk.Frame(dialog) + button_frame.pack(fill=tk.X, padx=10, pady=10) + + # 定义确定按钮功能 + def on_confirm(): + selection = template_listbox.curselection() + if not selection: + messagebox.showwarning("未选择模板", "请先选择要使用的模板", parent=dialog) + return + + index = selection[0] + current_type = dialog_type_var.get() + if current_type not in self.templates or index >= len(self.templates[current_type]): + return + + selected_template = self.templates[current_type][index] + + # 应用所选模板的配置 + self.coze_generation_type_var.set(current_type) # 更新生成类型 + self.generation_type_var.set(current_type) # 同步到主页面 + + # 更新工作流配置 + self.coze_workflow_id_var.set(selected_template.get('workflow_id', '')) + self.coze_access_token_var.set(selected_template.get('access_token', '')) + self.coze_is_async_var.set(selected_template.get('is_async', 'true')) + # self.coze_input_data_template_var.set(selected_template.get('input_data_template', '')) + + # 更新CONFIG配置 + CONFIG['Coze']['workflow_id'] = selected_template.get('workflow_id', '') + CONFIG['Coze']['access_token'] = selected_template.get('access_token', '') + CONFIG['Coze']['is_async'] = selected_template.get('is_async', 'true') + # CONFIG['Coze']['input_data_template'] = selected_template.get('input_data_template', '') + + # 保存上次使用的模板信息 + CONFIG['Coze']['last_used_template'] = selected_template['name'] + CONFIG['Coze']['last_used_template_type'] = current_type + + # 保存配置 + save_config(CONFIG) + + # 更新模板列表和选中状态 + self.update_template_list() + for i in range(self.template_listbox.size()): + if self.template_listbox.get(i) == selected_template['name']: + self.template_listbox.selection_set(i) + break + + # 加载模板配置到界面 + self.load_template_config(selected_template) + + # 延迟设置状态,确保覆盖load_template_config设置的状态 + self.after(100, lambda: self.edit_status_label.config(text=f"已应用模板 '{selected_template['name']}'", + foreground="green")) + self.after(2100, lambda: self.edit_status_label.config(text="")) + + # 关闭对话框 + dialog.destroy() + + # 添加确定和取消按钮 + ttk.Button(button_frame, text="确定", command=on_confirm).pack(side=tk.RIGHT, padx=5) + ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=5) + + # 设置默认选中第一个模板 + if template_listbox.size() > 0: + template_listbox.selection_set(0) + + # 等待对话框关闭 + self.wait_window(dialog) + + def clear_template_config(self): + """清空模板配置界面""" + # 解绑变量跟踪 + self._unbind_var_trace() + + # 清空所有配置字段 + self.template_name_var.set('') + self.coze_workflow_id_var.set('') + self.coze_access_token_var.set('') + self.coze_is_async_var.set('true') + # self.coze_input_data_template_var.set('') + + # 清空状态提示 + self.edit_status_label.config(text="已清空", foreground="gray") + self.after(2000, lambda: self.edit_status_label.config(text="")) + + # 重新绑定变量跟踪 + self._setup_var_trace() + + def load_templates(self): + """从配置文件加载模板""" + try: + import json + # 检查Templates节是否存在 + if 'Templates' in CONFIG: + templates_section = CONFIG['Templates'] + for key in templates_section: + if key.startswith('templates_'): + template_type = key.replace('templates_', '') + if template_type in self.templates: + value = templates_section[key] + # 确保value是字符串类型 + if isinstance(value, str): + try: + self.templates[template_type] = json.loads(value) + except json.JSONDecodeError as e: + logger.warning(f"解析模板配置{key}失败: {e}") + self.templates[template_type] = [] + else: + logger.warning(f"模板配置{key}的值不是字符串类型: {type(value)}") + self.templates[template_type] = [] + # 确保每个类型都有列表 + for template_type in ["短篇", "文章"]: + if template_type not in self.templates: + self.templates[template_type] = [] + except Exception as e: + logger.error(f"加载模板配置失败: {e}") + # 确保模板字典已初始化 + self.templates = {"短篇": [], "文章": []} + + def save_templates(self): + """保存模板到配置文件""" + try: + import json + # 确保Templates节存在 + if 'Templates' not in CONFIG: + CONFIG.add_section('Templates') + + for template_type, templates in self.templates.items(): + CONFIG['Templates'][f'templates_{template_type}'] = json.dumps(templates, ensure_ascii=False) + + save_config(CONFIG) + except Exception as e: + logger.error(f"保存模板配置失败: {e}") + messagebox.showerror("保存失败", f"保存模板配置时出错:{e}") + + def load_last_used_template(self): + """加载上次使用的模板""" + try: + # 检查是否有上次使用的模板信息 + last_template = CONFIG['Coze'].get('last_used_template', '') + last_template_type = CONFIG['Coze'].get('last_used_template_type', '文章') + + if last_template and last_template_type in self.templates: + # 设置模板类型 + self.coze_generation_type_var.set(last_template_type) + self.generation_type_var.set(last_template_type) # 同步到主页面 + + # 更新模板列表 + self.update_template_list() + + # 查找并选中上次使用的模板 + found = False + for i, template in enumerate(self.templates[last_template_type]): + if template['name'] == last_template: + self.template_listbox.selection_clear(0, tk.END) + self.template_listbox.selection_set(i) + self.template_listbox.see(i) # 确保可见 + + # 加载模板配置 + self.load_template_config(template) + + # 显示状态信息 + self.edit_status_label.config(text=f"已加载上次使用的模板 '{last_template}'") + self.after(3000, lambda: self.edit_status_label.config(text="")) + + found = True + break + + if not found: + logger.warning(f"未找到上次使用的模板: {last_template}") + except Exception as e: + logger.error(f"加载上次使用的模板失败: {e}") + # 出错时不显示错误消息,静默失败 + + def get_current_template(self): + """获取当前选中的模板配置""" + selection = self.template_listbox.curselection() + if selection: + index = selection[0] + current_type = self.coze_generation_type_var.get() + if current_type in self.templates and index < len(self.templates[current_type]): + return self.templates[current_type][index] + + # 如果没有选中模板,返回当前界面的配置 + return { + 'name': self.template_name_var.get() or '默认模板', + 'type': self.coze_generation_type_var.get(), + 'workflow_id': self.coze_workflow_id_var.get(), + 'access_token': self.coze_access_token_var.get(), + 'is_async': self.coze_is_async_var.get(), + # 'input_data_template': self.coze_input_data_template_var.get() + } + + def browse_directory(self, var): + directory = filedialog.askdirectory() + if directory: + var.set(directory) + + def browse_file(self, var, filetypes): + file_path = filedialog.askopenfilename(filetypes=filetypes) + if file_path: + var.set(file_path) + + def browse_excel(self): + file_path = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]) + if file_path: + self.excel_path_var.set(file_path) + + def test_db_connection(self): + try: + host = self.db_host_var.get() + user = self.db_user_var.get() + password = self.db_password_var.get() + database = self.db_name_var.get() + + connection = pymysql.connect( + host=host, + user=user, + password=password, + database=database + ) + connection.close() + messagebox.showinfo("连接成功", "数据库连接测试成功!") + except Exception as e: + messagebox.showerror("连接失败", f"数据库连接测试失败:{e}") + + def preview_image_effect(self): + try: + # 创建一个示例图片 + img = Image.new('RGB', (400, 300), color=(240, 240, 240)) + draw = ImageDraw.Draw(img) + draw.rectangle([50, 50, 350, 250], fill=(200, 200, 200)) + draw.text((150, 140), "示例图片", fill=(0, 0, 0)) + + # 应用图片修改效果 + modified_img = self.apply_image_modifications(img) + + # 显示修改后的图片 + self.show_preview_image(modified_img) + except Exception as e: + messagebox.showerror("预览失败", f"生成预览图片时出错:{e}") + + def apply_image_modifications(self, img): + """应用当前配置的图片修改效果""" + width, height = img.size + + try: + # 从界面获取参数 + crop_percent = float(self.crop_percent_var.get()) + min_rotation = float(self.min_rotation_var.get()) + max_rotation = float(self.max_rotation_var.get()) + min_brightness = float(self.min_brightness_var.get()) + max_brightness = float(self.max_brightness_var.get()) + min_contrast = float(self.min_contrast_var.get()) + max_contrast = float(self.max_contrast_var.get()) + min_saturation = float(self.min_saturation_var.get()) + max_saturation = float(self.max_saturation_var.get()) + + # 裁剪 + crop_size = (int(width * crop_percent), int(height * crop_percent)) + left = (width - crop_size[0]) // 2 + top = (height - crop_size[1]) // 2 + right = left + crop_size[0] + bottom = top + crop_size[1] + img = img.crop((left, top, right, bottom)) + + # 旋转 + rotation_angle = random.uniform(min_rotation, max_rotation) + img = img.rotate(rotation_angle) + + # 调整亮度 + enhancer = ImageEnhance.Brightness(img) + brightness_factor = random.uniform(min_brightness, max_brightness) + img = enhancer.enhance(brightness_factor) + + # 调整对比度 + enhancer = ImageEnhance.Contrast(img) + contrast_factor = random.uniform(min_contrast, max_contrast) + img = enhancer.enhance(contrast_factor) + + # 调整饱和度 + enhancer = ImageEnhance.Color(img) + saturation_factor = random.uniform(min_saturation, max_saturation) + img = enhancer.enhance(saturation_factor) + + return img + except Exception as e: + messagebox.showerror("应用效果失败", f"应用图片修改效果时出错:{e}") + return img + + + + def start_processing(self): + """开始处理链接""" + if self.running: + messagebox.showinfo("处理中", "已有任务正在处理中,请等待完成") + return + + try: + # 更新Excel文件路径 + excel_path = self.excel_path_var.get() + if not os.path.exists(excel_path): + messagebox.showerror("文件错误", f"Excel文件不存在:{excel_path}") + return + + # 获取线程数 + try: + num_threads = int(self.thread_count_var.get()) + if num_threads < 1: + num_threads = 1 + elif num_threads > MAX_THREADS: + num_threads = MAX_THREADS + except: + num_threads = 1 + + # 禁用开始按钮 + self.start_button.config(state=tk.DISABLED) + self.running = True + + # 清空日志 + self.log_text.config(state=tk.NORMAL) + self.log_text.delete(1.0, tk.END) + self.log_text.config(state=tk.DISABLED) + + # 获取AI服务提供商选择 + ai_service = self.ai_service_var.get() + + # 获取生成类型 + generation_type = self.generation_type_var.get() + + # 获取当前选中的模板配置 + current_template = self.get_current_template() + + # 在新线程中运行处理任务 + self.thread = threading.Thread(target=self.run_processing, + args=(excel_path, num_threads, ai_service, generation_type, + current_template)) + self.thread.daemon = True + self.thread.start() + + # 启动进度更新 + self.after(100, self.update_progress) + except Exception as e: + messagebox.showerror("启动失败", f"启动处理任务时出错:{e}") + self.start_button.config(state=tk.NORMAL) + self.running = False + + def run_processing(self, excel_path, num_threads, ai_service, generation_type=None, current_template=None): + """运行处理任务""" + try: + # 读取Excel文件 + df = pd.read_excel(excel_path) + + # 获取关键词列表 + keywords = self.keywords_var.get().split(",") + + # 获取图片处理参数 + crop_percent = float(self.crop_percent_var.get()) + min_rotation = float(self.min_rotation_var.get()) + max_rotation = float(self.max_rotation_var.get()) + min_brightness = float(self.min_brightness_var.get()) + max_brightness = float(self.max_brightness_var.get()) + min_saturation = float(self.min_saturation_var.get()) + max_saturation = float(self.max_saturation_var.get()) + watermark_text = self.watermark_text_var.get() + watermark_opacity = int(self.watermark_opacity_var.get()) + overlay_opacity = int(self.overlay_opacity_var.get()) + + # 1. 裁剪边缘 + crop_px_w = int(width * crop_percent) + crop_px_h = int(height * crop_percent) + img = img.crop((crop_px_w, crop_px_h, width - crop_px_w, height - crop_px_h)) + + # 2. 随机旋转 + angle = random.uniform(min_rotation, max_rotation) * random.choice([-1, 1]) + img = img.rotate(angle, expand=True) + + # 3. 调整亮度 + enhancer = ImageEnhance.Brightness(img) + factor = random.uniform(min_brightness, max_brightness) + img = enhancer.enhance(factor) + + # 4. 添加文字水印 + draw = ImageDraw.Draw(img) + font_size = max(20, int(min(img.size) * 0.05)) + try: + num_threads = int(self.thread_count_var.get()) + if num_threads < 1: + num_threads = 1 + elif num_threads > MAX_THREADS: + num_threads = MAX_THREADS + except: + num_threads = 1 + + # 禁用开始按钮 + self.start_button.config(state=tk.DISABLED) + self.running = True + + # 清空日志 + self.log_text.config(state=tk.NORMAL) + self.log_text.delete(1.0, tk.END) + self.log_text.config(state=tk.DISABLED) + + # 获取AI服务提供商选择 + ai_service = self.ai_service_var.get() + + # 获取生成类型 + generation_type = self.generation_type_var.get() + + # 获取当前选中的模板配置 + current_template = self.get_current_template() + + # 在新线程中运行处理任务 + self.thread = threading.Thread(target=self.run_processing, + args=(excel_path, num_threads, ai_service, generation_type, + current_template)) + self.thread.daemon = True + self.thread.start() + + # 启动进度更新 + self.after(100, self.update_progress) + except Exception as e: + messagebox.showerror("启动失败", f"启动处理任务时出错:{e}") + self.start_button.config(state=tk.NORMAL) + self.running = False + + def run_processing(self, excel_path, num_threads, ai_service, generation_type=None, current_template=None): + """在后台线程中运行处理任务""" + try: + # 更新全局变量 + global TITLE_BASE_PATH + TITLE_BASE_PATH = excel_path + + # 记录开始时间 + start_time = time.time() + + # 如果有模板配置,临时更新CONFIG + original_config = None + if current_template and ai_service == 'coze': + original_config = { + 'workflow_id': CONFIG['Coze']['workflow_id'], + 'access_token': CONFIG['Coze']['access_token'], + 'is_async': CONFIG['Coze']['is_async'], + # 'input_data_template': CONFIG['Coze'].get('input_data_template', '') + } + + CONFIG['Coze']['workflow_id'] = current_template.get('workflow_id', '') + CONFIG['Coze']['access_token'] = current_template.get('access_token', '') + CONFIG['Coze']['is_async'] = current_template.get('is_async', 'true') + # CONFIG['Coze']['input_data_template'] = current_template.get('input_data_template', '') + + logger.info(f"应用模板配置: {current_template.get('name')}") + logger.info(f"Workflow ID: {CONFIG['Coze']['workflow_id']}") + logger.info(f"Access Token: {'*' * len(CONFIG['Coze']['access_token'])}") + logger.info(f"Is Async: {CONFIG['Coze']['is_async']}") + # logger.info(f"Input Template: {CONFIG['Coze']['input_data_template']}") + + # 读取链接并处理 + logger.info(f"开始处理链接,使用 {num_threads} 个线程,生成类型: {generation_type}") + if current_template: + logger.info(f"使用模板: {current_template.get('name', '未命名')}") + results = link_to_text(num_threads=num_threads, ai_service=ai_service, current_template=current_template, + generation_type=generation_type) + + # 计算处理结果 + total_links = len(results) + success_links = sum(1 for _, success, _ in results if success) + + # 记录结束时间和总耗时 + end_time = time.time() + elapsed_time = end_time - start_time + + # 记录处理结果 + logger.info( + f"处理完成,共处理 {total_links} 个链接,成功 {success_links} 个,失败 {total_links - success_links} 个") + logger.info(f"总耗时: {elapsed_time:.2f} 秒") + + # 在主线程中显示处理结果 + self.after(0, lambda: messagebox.showinfo("处理完成", + f"共处理 {total_links} 个链接\n成功: {success_links} 个\n失败: {total_links - success_links} 个\n总耗时: {elapsed_time:.2f} 秒")) + except Exception as e: + logger.error(f"处理任务出错: {e}") + error_msg = str(e) + self.after(0, lambda: messagebox.showerror("处理错误", f"处理任务出错: {error_msg}")) + # self.after(0, lambda e=e: messagebox.showerror("处理错误", f"处理任务出错: {e}")) + finally: + # 恢复原始配置(如果有的话) + if original_config is not None: + CONFIG['Coze']['workflow_id'] = original_config['workflow_id'] + CONFIG['Coze']['access_token'] = original_config['access_token'] + CONFIG['Coze']['is_async'] = original_config['is_async'] + # CONFIG['Coze']['input_data_template'] = original_config['input_data_template'] + + # 恢复开始按钮状态 + self.after(0, lambda: self.start_button.config(state=tk.NORMAL)) + self.running = False + + def update_progress(self): + """更新进度条和状态""" + if not self.running: + return + + try: + # 获取当前进度 + total = task_queue.qsize() + result_queue.qsize() + done = result_queue.qsize() + + if total > 0: + # 更新进度条 + progress = (done / total) * 100 + self.progress_var.set(progress) + + # 更新标题显示进度 + self.title(f"文章采集与处理工具 - 进度: {progress:.1f}%") + + # 继续更新 + self.after(500, self.update_progress) + except Exception as e: + logger.error(f"更新进度出错: {e}") + + def on_close(self): + """关闭窗口时的处理""" + if self.running: + if messagebox.askyesno("确认退出", "任务正在处理中,确定要退出吗?"): + self.destroy() + else: + self.destroy() + + +# 日志处理器类,用于将日志输出到文本框 +class LogTextHandler(logging.Handler): + def __init__(self, text_widget): + logging.Handler.__init__(self) + self.text_widget = text_widget + + def emit(self, record): + msg = self.format(record) + + def append(): + self.text_widget.configure(state=tk.NORMAL) + self.text_widget.insert(tk.END, msg + '\n') + self.text_widget.see(tk.END) # 自动滚动到底部 + self.text_widget.configure(state=tk.DISABLED) + + # 在主线程中更新UI + self.text_widget.after(0, append) + + +# 主函数 +def main(): + validator = AuthValidator(software_id="ArticleReplace", + api_url="http://km.taisan.online/api/v1", + gui_mode=True, + secret_key="taiyi1224", + + restore_tkinter=True # 启用验证成功后恢复tkinter窗口 + + ) + + # 执行验证 + if not validator.validate(): + print("授权验证失败,程序退出") + return + + # y验证成功,继续执行程序逻辑 + + print("授权验证成功,启动程序。。。") + + + # 初始化日志 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler("article_replace.log", encoding='utf-8'), + logging.StreamHandler() + ] + ) + + # 创建必要的目录 + if not os.path.exists(ARTICLES_BASE_PATH): + os.makedirs(ARTICLES_BASE_PATH) + if not os.path.exists(IMGS_BASE_PATH): + os.makedirs(IMGS_BASE_PATH) + + # 启动GUI应用 + app = ArticleReplaceApp() + app.mainloop() + + +if __name__ == "__main__": + + + main() diff --git a/ArticleReplace.spec b/ArticleReplace.spec new file mode 100644 index 0000000..2864ae7 --- /dev/null +++ b/ArticleReplace.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['ArticleReplace.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='ArticleReplace', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/auth_config.json b/auth_config.json new file mode 100644 index 0000000..9264173 --- /dev/null +++ b/auth_config.json @@ -0,0 +1,3 @@ +{ + "last_license_key": "LGCXVKAH-665RMFLI-WFDH6OJT-CZBCTATA" +} \ No newline at end of file diff --git a/auth_validator.py b/auth_validator.py index 3afe6a9..6bc6c06 100644 --- a/auth_validator.py +++ b/auth_validator.py @@ -7,6 +7,7 @@ Python软件授权验证器 (现代化UI版) 3. 现代化深色主题 UI 4. 机器码一键复制 5. 防止后台禁用卡密后仍能使用 +6. 验证通过后自动恢复tkinter原始缩放比例 使用方法 (完全兼容旧版): from auth_validator import AuthValidator @@ -17,9 +18,11 @@ Python软件授权验证器 (现代化UI版) secret_key="your_secret_key" ) if not validator.validate(): - exit() + sys.exit() """ +import sys + import os import json import time @@ -38,7 +41,7 @@ try: from tkinter import messagebox except ImportError: print("错误: 请先安装UI库 -> pip install customtkinter") - exit(1) + sys.exit(1) # ========================================== @@ -294,7 +297,7 @@ class AuthWindow(ctk.CTk): self.verifying = False # 是否正在验证中 # 窗口基础设置 - self.title("软件授权验证") + self.title("软件授权验证(有问题联系V:taiyi1224)") self.geometry("300x350") # 增加高度以容纳服务器地址显示 self.resizable(False, False) ctk.set_appearance_mode("Dark") @@ -566,6 +569,7 @@ class AuthValidator: 1. 读取保存的卡密(如果有) 2. 弹出现代化UI窗口并自动验证 3. 验证失败则要求用户重新输入 + 4. 验证成功后恢复tkinter的原始缩放比例 Returns: bool: 是否验证成功 """ @@ -575,7 +579,32 @@ class AuthValidator: # 运行主循环 (这会阻塞代码执行,直到窗口关闭) app.mainloop() + # 窗口关闭后,如果验证成功,重置 tkinter 的缩放比例 + if app.is_verified: + self._reset_tk_scaling() + # 窗口关闭后,检查是否验证成功 return app.is_verified + def _reset_tk_scaling(self): + """ + 重置 tkinter 的 DPI 缩放,恢复到系统默认值 + 因为 customtkinter 会修改全局的 tkinter 缩放设置 + 这样可以确保后续创建的 tkinter 窗口使用正常的尺寸 + """ + try: + # 创建一个临时的 tkinter 根窗口来重置缩放 + temp_root = tk.Tk() + temp_root.withdraw() # 隐藏窗口 + + # 重置缩放因子为系统默认值(通常是 1.0) + # 这会影响后续所有 tkinter 窗口的尺寸计算 + temp_root.tk.call('tk', 'scaling', 1.0) + + # 销毁临时窗口 + temp_root.destroy() + except Exception as e: + # 如果重置失败,静默处理,不影响主流程 + pass + # --- END OF FILE --- \ No newline at end of file