ArticleReplace/ArticleReplace.py.bak

1610 lines
75 KiB
Python
Raw Normal View History

2025-11-23 22:52:25 +08:00
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("<<ComboboxSelected>>", 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("<<ComboboxSelected>>", 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("<<ListboxSelect>>", 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("<<ComboboxSelected>>", 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()