import tkinter as tk from tkinter import ttk, messagebox, filedialog import sqlite3 import os import datetime import json import importlib.util from typing import List, Dict import pyperclip class ScriptManager: def __init__(self, db_path="scripts.db", scripts_dir="scripts"): self.db_path = db_path self.scripts_dir = scripts_dir self._init_db() self._init_scripts_dir() self.tag_library = { "文本": ["string", "text", "format", "generate"], "转换": ["convert", "transform", "format"], "处理": ["process", "handle", "manipulate"], "生成": ["create", "generate", "make"], "解析": ["parse", "extract", "analyze"] } def _init_db(self): with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS scripts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, path TEXT NOT NULL, ui_path TEXT NOT NULL, created_at TEXT NOT NULL, tags TEXT, abbreviation TEXT, usage_count INTEGER DEFAULT 0 ) """) conn.commit() def _init_scripts_dir(self): os.makedirs(self.scripts_dir, exist_ok=True) def _suggest_tags(self, script_name: str) -> List[str]: name = script_name.lower() suggested_tags = [] for category, keywords in self.tag_library.items(): if any(keyword in name for keyword in keywords): suggested_tags.append(category) return suggested_tags def add_script(self, script_name: str, script_content: str = None, script_path: str = None, ui_content: str = None) -> Dict: if not script_path: script_name_clean = script_name.replace(" ", "_").replace("/", "_").replace("\\", "_") script_path = os.path.join(self.scripts_dir, f"{script_name_clean}.py") ui_path = os.path.join(self.scripts_dir, f"{script_name_clean}.ui.py") if not script_content: script_content = '''def process(input_strings: list) -> list: """处理输入字符串并返回结果""" return [s.upper() for s in input_strings] ''' if not ui_content: ui_content = '''import tkinter as tk from tkinter import ttk import pyperclip def create_ui(parent, run_callback): """创建前端界面""" frame = ttk.Frame(parent) frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) frame.columnconfigure(1, weight=1) ttk.Label(frame, text="输入字符串 1:").grid(row=0, column=0, sticky=tk.W, pady=2) input_entry = ttk.Entry(frame) input_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=2) ttk.Label(frame, text="输出结果 1:").grid(row=1, column=0, sticky=tk.W, pady=2) output_entry = ttk.Entry(frame, state="readonly") output_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=2) def copy_output(event): output = output_entry.get() if output: pyperclip.copy(output) tk.messagebox.showinfo("提示", "输出已复制到剪贴板") output_entry.bind("", copy_output) def run(): input_text = input_entry.get().strip() if not input_text: tk.messagebox.showerror("错误", "请输入字符串") return result = run_callback([input_text]) output_entry.configure(state="normal") output_entry.delete(0, tk.END) output_entry.insert(0, result[0] if result else "") output_entry.configure(state="readonly") ttk.Button(frame, text="运行脚本", command=run).grid(row=2, column=0, columnspan=2, pady=5) return frame ''' with open(script_path, 'w', encoding='utf-8') as f: f.write(script_content) with open(ui_path, 'w', encoding='utf-8') as f: f.write(ui_content) abbr = ''.join(word[0].upper() for word in script_name.split('_') if word) suggested_tags = self._suggest_tags(script_name) with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() cursor.execute(""" INSERT INTO scripts (name, path, ui_path, created_at, tags, abbreviation, usage_count) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( script_name, script_path, ui_path, datetime.datetime.now().isoformat(), json.dumps(suggested_tags), abbr, 0 )) conn.commit() return { "id": cursor.lastrowid, "name": script_name, "path": script_path, "ui_path": ui_path, "suggested_tags": suggested_tags, "abbreviation": abbr } def list_scripts(self, tag: str = None, name: str = None) -> List[Dict]: with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() query = "SELECT * FROM scripts" params = [] conditions = [] if tag: conditions.append("tags LIKE ?") params.append(f'%{tag}%') if name: conditions.append("name LIKE ?") params.append(f'%{name}%') if conditions: query += " WHERE " + " AND ".join(conditions) cursor.execute(query, params) scripts = [] for row in cursor.fetchall(): try: tags = json.loads(row[5]) if row[5] and row[5].strip() else [] except json.JSONDecodeError: tags = [] # Fallback to empty list if JSON is invalid scripts.append({ "id": row[0], "name": row[1], "path": row[2], "created_at": row[3], "tags": tags, "abbreviation": row[5], "usage_count": row[6], "ui_path":row[7] }) return scripts def run_script(self, script_id: int, input_strings: List[str]) -> List[str]: with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() cursor.execute("SELECT path FROM scripts WHERE id = ?", (script_id,)) result = cursor.fetchone() if not result: raise ValueError("脚本未找到") script_path = result[0] cursor.execute("UPDATE scripts SET usage_count = usage_count + 1 WHERE id = ?", (script_id,)) conn.commit() spec = importlib.util.spec_from_file_location("script_module", script_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module.process(input_strings) def load_ui_module(self, ui_path: str): """加载前端界面模块""" if not ui_path or not os.path.isfile(ui_path): raise ValueError(f"前端界面文件不存在或路径无效:{ui_path}") try: spec = importlib.util.spec_from_file_location("ui_module", ui_path) if spec is None: raise ValueError(f"无法加载前端界面模块:{ui_path} (模块规格无效)") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module except Exception as e: raise ValueError(f"加载前端界面失败:{str(e)}") class ScriptManagerGUI: def __init__(self, root): self.root = root self.root.title("脚本管理器") self.manager = ScriptManager() self.selected_script_id = None self.current_ui_frame = None self.create_widgets() self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) def create_widgets(self): # 使用 Notebook 创建选项卡 self.notebook = ttk.Notebook(self.root) self.notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=10, pady=5) self.notebook.columnconfigure(0, weight=1) self.notebook.rowconfigure(0, weight=1) # 添加脚本选项卡 add_frame = ttk.Frame(self.notebook, padding="10") self.notebook.add(add_frame, text="添加脚本") add_frame.columnconfigure(1, weight=1) add_frame.rowconfigure(3, weight=1) # 脚本名称 ttk.Label(add_frame, text="脚本名称:").grid(row=0, column=0, sticky=tk.W, pady=5) self.script_name_var = tk.StringVar() ttk.Entry(add_frame, textvariable=self.script_name_var).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5) # 脚本路径 ttk.Label(add_frame, text="脚本路径(可选):").grid(row=1, column=0, sticky=tk.W, pady=5) self.script_path_var = tk.StringVar() ttk.Entry(add_frame, textvariable=self.script_path_var).grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5) ttk.Button(add_frame, text="选择文件", command=self.browse_file).grid(row=1, column=2, padx=5) # 标签 ttk.Label(add_frame, text="标签(逗号分隔,可选):").grid(row=2, column=0, sticky=tk.W, pady=5) self.tags_var = tk.StringVar() ttk.Entry(add_frame, textvariable=self.tags_var).grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5) # 脚本内容 ttk.Label(add_frame, text="脚本内容(可选):").grid(row=3, column=0, sticky=tk.W, pady=5) self.script_content_text = tk.Text(add_frame, height=5) self.script_content_text.grid(row=3, column=1, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) # 前端界面内容 ttk.Label(add_frame, text="前端界面内容(可选):").grid(row=4, column=0, sticky=tk.W, pady=5) self.ui_content_text = tk.Text(add_frame, height=5) self.ui_content_text.grid(row=4, column=1, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) ttk.Button(add_frame, text="添加脚本", command=self.add_script).grid(row=5, column=0, columnspan=3, pady=5) # 索引运行脚本选项卡 run_frame = ttk.Frame(self.notebook, padding="10") self.notebook.add(run_frame, text="索引运行脚本") run_frame.columnconfigure(0, weight=1) run_frame.columnconfigure(2, weight=1) run_frame.rowconfigure(0, weight=1) # 左侧:搜索和脚本列表 left_frame = ttk.Frame(run_frame) left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5)) left_frame.columnconfigure(0, weight=1) left_frame.rowconfigure(1, weight=1) ttk.Label(left_frame, text="搜索(名称或标签):").grid(row=0, column=0, sticky=tk.W, pady=5) self.search_var = tk.StringVar() ttk.Entry(left_frame, textvariable=self.search_var).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5) ttk.Button(left_frame, text="搜索", command=self.search_scripts).grid(row=0, column=2, padx=5) self.tree = ttk.Treeview(left_frame, columns=("ID", "名称", "标签", "缩写", "使用次数", "创建时间"), show="headings") # Set column headings self.tree.heading("ID", text="ID") self.tree.heading("名称", text="名称") self.tree.heading("标签", text="标签") self.tree.heading("缩写", text="缩写") self.tree.heading("使用次数", text="使用次数") self.tree.heading("创建时间", text="创建时间") # Set column widths (in pixels) self.tree.column("ID", width=25, minwidth=25, stretch=False) # Narrow for ID self.tree.column("名称", width=150, minwidth=100, stretch=True) # Wider for name self.tree.column("标签", width=100, minwidth=80, stretch=True) # Medium for tags self.tree.column("缩写", width=60, minwidth=50, stretch=False) # Narrow for abbreviation self.tree.column("使用次数", width=80, minwidth=60, stretch=False) # Narrow for usage count self.tree.column("创建时间", width=120, minwidth=100, stretch=True) # Medium for date # Place the Treeview in the grid self.tree.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S)) self.tree.bind("<>", self.on_script_select) # 分割线 ttk.Separator(run_frame, orient=tk.VERTICAL).grid(row=0, column=1, sticky=(tk.N, tk.S), padx=5) # 右侧:动态前端界面 self.io_frame = ttk.Frame(run_frame) self.io_frame.grid(row=0, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0)) self.io_frame.columnconfigure(0, weight=1) self.io_frame.rowconfigure(0, weight=1) # 加载脚本列表 self.refresh_script_list() def browse_file(self): file_path = filedialog.askopenfilename(filetypes=[("Python files", "*.py")]) if file_path: self.script_path_var.set(file_path) def refresh_script_list(self, search_term: str = None): for item in self.tree.get_children(): self.tree.delete(item) scripts = self.manager.list_scripts(tag=search_term, name=search_term) for script in scripts: self.tree.insert("", tk.END, values=( script["id"], script["name"], ", ".join(script["tags"]), script["abbreviation"], script["usage_count"], script["created_at"] )) def add_script(self): script_name = self.script_name_var.get().strip() script_path = self.script_path_var.get().strip() script_content = self.script_content_text.get("1.0", tk.END).strip() ui_content = self.ui_content_text.get("1.0", tk.END).strip() tags = [t.strip() for t in self.tags_var.get().split(",") if t.strip()] if not script_name and not script_path: messagebox.showerror("错误", "脚本名称和脚本路径不能同时为空") return if not script_path and not script_content: messagebox.showerror("错误", "脚本路径和脚本内容不能同时为空") return if not script_name and script_path: script_name = os.path.splitext(os.path.basename(script_path))[0] try: result = self.manager.add_script(script_name, script_content or None, script_path or None, ui_content or None) if tags: self.manager.update_tags(result["id"], tags) messagebox.showinfo("成功", f"脚本 '{script_name}' 已添加!\n建议标签:{', '.join(result['suggested_tags'])}") self.script_name_var.set("") self.script_path_var.set("") self.script_content_text.delete("1.0", tk.END) self.ui_content_text.delete("1.0", tk.END) self.tags_var.set(", ".join(result["suggested_tags"])) self.refresh_script_list() except Exception as e: messagebox.showerror("错误", f"添加脚本失败:{str(e)}") def on_script_select(self, event): selection = self.tree.selection() if selection: item = self.tree.item(selection[0]) self.selected_script_id = int(item["values"][0]) script = next(s for s in self.manager.list_scripts() if s["id"] == self.selected_script_id) self.refresh_io_frame(script["ui_path"], script["path"]) def refresh_io_frame(self, ui_path: str = None, script_path: str = None): # 清空现有界面 if self.current_ui_frame: self.current_ui_frame.destroy() if not ui_path or not script_path: self.current_ui_frame = ttk.Label( self.io_frame, text="请选择一个脚本", wraplength=int(self.io_frame.winfo_width() * 0.9) or 300 ) self.current_ui_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) return try: # 加载前端界面模块 ui_module = self.manager.load_ui_module(ui_path) # 创建运行回调 def run_callback(input_strings): return self.manager.run_script(self.selected_script_id, input_strings) # 调用前端界面的 create_ui 函数 self.current_ui_frame = ui_module.create_ui(self.io_frame, run_callback) self.current_ui_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) except Exception as e: frame_width = self.io_frame.winfo_width() or 300 self.current_ui_frame = ttk.Label( self.io_frame, text=f"加载前端界面失败:{str(e)}", wraplength=int(frame_width * 0.9), padding=(5, 5), foreground="red" ) self.current_ui_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) def search_scripts(self): search_term = self.search_var.get().strip() self.refresh_script_list(search_term) if __name__ == "__main__": root = tk.Tk() root.geometry("800x600") app = ScriptManagerGUI(root) root.mainloop()