Remove unused modules and dialogs to streamline the application

This commit is contained in:
Christopher Clendening
2026-07-27 17:40:57 -04:00
parent b78cd43464
commit ce0e022f11
7 changed files with 0 additions and 978 deletions
-42
View File
@@ -1,42 +0,0 @@
import tkinter as tk
from tkinter import ttk
import difflib
class DiffViewerDialog(tk.Toplevel):
def __init__(self, parent, expected: str, actual: str, title: str = "Output Diff"):
super().__init__(parent)
self.title(title)
self.geometry("900x600")
self.transient(parent)
self.grab_set()
paned = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
paned.pack(fill="both", expand=True, padx=10, pady=10)
left_frame = ttk.Frame(paned)
right_frame = ttk.Frame(paned)
paned.add(left_frame, weight=1)
paned.add(right_frame, weight=1)
ttk.Label(left_frame, text="Expected Output", font=("Helvetica", 12, "bold")).pack(anchor="w")
self.expected_text = tk.Text(left_frame, wrap="word", bg="#f8f8f8")
self.expected_text.pack(fill="both", expand=True)
self.expected_text.insert("1.0", expected)
self.expected_text.configure(state="disabled")
ttk.Label(right_frame, text="Actual Output", font=("Helvetica", 12, "bold")).pack(anchor="w")
self.actual_text = tk.Text(right_frame, wrap="word", bg="#f8f8f8")
self.actual_text.pack(fill="both", expand=True)
self.actual_text.insert("1.0", actual)
self.actual_text.configure(state="disabled")
# Diff summary at the bottom
diff = list(difflib.unified_diff(
expected.splitlines(), actual.splitlines(), lineterm='', n=3,
fromfile='expected', tofile='actual'))
diff_str = '\n'.join(diff) if diff else 'No differences.'
ttk.Label(self, text="Diff Summary", font=("Helvetica", 11, "bold")).pack(anchor="w", padx=10)
diff_box = tk.Text(self, height=8, wrap="none", bg="#f0f0f0")
diff_box.pack(fill="x", padx=10, pady=(0,10))
diff_box.insert("1.0", diff_str)
diff_box.configure(state="disabled")
-489
View File
@@ -1,489 +0,0 @@
from __future__ import annotations
import os
import subprocess
import sys
import threading
import time
from pathlib import Path
from typing import Dict, List, Tuple
from tkinter import filedialog, messagebox, scrolledtext, ttk
import tkinter as tk
CORE_DIR = Path(__file__).resolve().parent / ".core"
if str(CORE_DIR) not in sys.path:
sys.path.insert(0, str(CORE_DIR))
from config import RESULTS_DIR, TESTS_DIR # type: ignore # noqa: E402
from providers import ( # type: ignore # noqa: E402
LocalEngineProvider,
ProviderError,
get_provider,
list_provider_names,
)
from reporting import TemplateNotFoundError # type: ignore # noqa: E402
from runner import TestRunError, run_suite # type: ignore # noqa: E402
from settings import get_local_model_paths, set_local_model_paths # type: ignore # noqa: E402
def open_path(path: Path) -> None:
if sys.platform.startswith("darwin"):
subprocess.run(["open", str(path)], check=False)
elif os.name == "nt": # Windows
os.startfile(str(path)) # type: ignore[attr-defined]
else:
subprocess.run(["xdg-open", str(path)], check=False)
class App(tk.Tk):
def open_settings_dialog(self):
try:
from settings_dialog import SettingsDialog
except ImportError:
messagebox.showerror("Error", "Settings dialog module not found.")
return
SettingsDialog(self)
def open_prompt_playground(self):
try:
from prompt_playground import PlaygroundDialog
except ImportError:
messagebox.showerror("Error", "Prompt playground module not found.")
return
def send_callback(prompt):
provider_name = self.provider_var.get()
model_display = self.model_var.get()
if not provider_name or not model_display:
return "Please select a provider and model."
try:
provider = get_provider(provider_name, **self._provider_kwargs(provider_name))
model_id = self._model_id_for_display(model_display)
if not model_id:
return "Model not found."
# Use temperature from the GUI
response = provider.run_inference(model_id, prompt, temperature=self.temperature_var.get())
return response
except Exception as e:
return f"Error: {e}"
PlaygroundDialog(self, send_callback)
def load_plugins(self):
try:
from plugin_system import PluginManager
self.plugin_manager = PluginManager()
self.plugin_manager.run_hook('on_app_start', self)
except Exception as e:
print(f"Plugin system error: {e}")
def open_results_chart(self):
try:
from results_chart import ResultsChartDialog
except ImportError:
messagebox.showerror("Error", "Results chart module not found.")
return
# Find the latest report
if not RESULTS_DIR.exists():
messagebox.showinfo("No Reports", "The results directory does not exist yet.")
return
reports = list(RESULTS_DIR.glob("*.md"))
if not reports:
messagebox.showinfo("No Reports", "No markdown reports found yet.")
return
latest_report = max(reports, key=lambda p: p.stat().st_mtime)
ResultsChartDialog(self, latest_report)
def open_diff_viewer(self):
try:
from diff_viewer import DiffViewerDialog
except ImportError:
messagebox.showerror("Error", "Diff viewer module not found.")
return
# Try to get the last test's expected and actual output from the log or a result file
# For now, just prompt the user for both (can be improved to auto-load from last run)
expected = ""
actual = ""
def get_text(title):
d = tk.Toplevel(self)
d.title(f"{title} - LM-Gambit")
d.geometry("600x400")
t = tk.Text(d, wrap="word")
t.pack(fill="both", expand=True)
result = {}
def ok():
result['text'] = t.get("1.0", "end-1c")
d.destroy()
ttk.Button(d, text="OK", command=ok).pack()
self.wait_window(d)
return result.get('text', "")
expected = get_text("Paste Expected Output")
actual = get_text("Paste Actual Output")
if expected and actual:
DiffViewerDialog(self, expected, actual)
else:
messagebox.showinfo("Diff Viewer", "Both expected and actual output are required.")
def configure_model_paths(self) -> None:
dialog = tk.Toplevel(self)
dialog.title("Configure Model Paths - LM-Gambit")
dialog.geometry("520x320")
dialog.transient(self)
dialog.grab_set()
paths: List[str] = get_local_model_paths()
listbox = tk.Listbox(dialog, selectmode=tk.SINGLE, width=60, height=10)
for entry in paths:
listbox.insert("end", entry)
listbox.pack(fill="both", expand=True, padx=12, pady=(12, 6))
button_frame = ttk.Frame(dialog)
button_frame.pack(fill="x", padx=12, pady=6)
def add_path() -> None:
selection = filedialog.askdirectory(parent=dialog)
if selection and selection not in paths:
paths.append(selection)
listbox.insert("end", selection)
def remove_path() -> None:
selection = listbox.curselection()
if not selection:
return
index = selection[0]
listbox.delete(index)
paths.pop(index)
ttk.Button(button_frame, text="Add Path", command=add_path).pack(side="left", padx=(0, 8))
ttk.Button(button_frame, text="Remove Selected", command=remove_path).pack(side="left")
ttk.Button(button_frame, text="Save", command=lambda: (set_local_model_paths(paths), dialog.destroy())).pack(side="right")
def open_test_editor(self):
try:
from test_editor import TestEditorDialog
except ImportError:
messagebox.showerror("Error", "Test editor module not found.")
return
TestEditorDialog(self, TESTS_DIR)
def open_latest_report(self) -> None:
if not RESULTS_DIR.exists():
messagebox.showinfo("No Reports", "The results directory does not exist yet.")
return
reports = list(RESULTS_DIR.glob("*.md"))
if not reports:
messagebox.showinfo("No Reports", "No markdown reports found yet.")
return
latest_report = max(reports, key=lambda p: p.stat().st_mtime)
open_path(latest_report)
self._append_log(f"Opened latest report: {latest_report.name}")
def open_model_downloader(self):
try:
from model_downloader import DownloadModelDialog
except ImportError:
messagebox.showerror("Error", "Model downloader module not found.")
return
paths = get_local_model_paths()
import os
model_dir = Path(paths[0]).expanduser() if paths else (Path(os.getcwd()) / "models")
model_dir.mkdir(parents=True, exist_ok=True)
DownloadModelDialog(self, model_dir)
def __init__(self) -> None:
super().__init__()
self.title("LM-Gambit v1.0.0")
self.geometry("880x620")
self.minsize(760, 520)
self.provider_names = list_provider_names()
# Always default to Local Engine if available
default_provider = LocalEngineProvider.name if LocalEngineProvider.name in self.provider_names else (self.provider_names[0] if self.provider_names else "")
self.provider_var = tk.StringVar(value=default_provider)
self.model_var = tk.StringVar()
self.temperature_var = tk.DoubleVar(value=0.1)
self.status_var = tk.StringVar(value="Idle")
self.available_models: List[Tuple[str, str]] = [] # (id, display name)
self.running = False
self.load_plugins()
self._build_ui()
self._build_menu()
if self.provider_var.get():
self.fetch_models_async()
def _build_ui(self) -> None:
padding = {"padx": 12, "pady": 8}
header = ttk.Label(self, text="Automated Diagnostic Test Runner", font=("Helvetica", 18, "bold"))
header.pack(anchor="w", **padding)
form_frame = ttk.Frame(self)
form_frame.pack(fill="x", **padding)
ttk.Label(form_frame, text="Provider:").grid(row=0, column=0, sticky="w")
self.provider_combo = ttk.Combobox(
form_frame,
textvariable=self.provider_var,
values=self.provider_names,
state="readonly",
)
self.provider_combo.grid(row=0, column=1, sticky="ew", padx=(6, 18))
self.provider_combo.bind("<<ComboboxSelected>>", lambda event: self.fetch_models_async())
ttk.Label(form_frame, text="Model:").grid(row=0, column=2, sticky="w")
self.model_combo = ttk.Combobox(form_frame, textvariable=self.model_var, state="readonly")
self.model_combo.grid(row=0, column=3, sticky="ew", padx=(6, 18))
ttk.Label(form_frame, text="Temperature:").grid(row=0, column=4, sticky="w")
self.temperature_spin = ttk.Spinbox(
form_frame,
textvariable=self.temperature_var,
from_=0.0,
to=1.0,
increment=0.05,
width=6,
)
self.temperature_spin.grid(row=0, column=5, sticky="w")
form_frame.columnconfigure(1, weight=1)
form_frame.columnconfigure(3, weight=1)
button_frame = ttk.Frame(self)
button_frame.pack(fill="x", **padding)
self.run_button = ttk.Button(button_frame, text="Run Tests", command=self.start_run)
self.run_button.pack(side="left")
ttk.Button(button_frame, text="Refresh Models", command=self.fetch_models_async).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="New Test", command=self.open_test_editor).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Compare Outputs", command=self.open_diff_viewer).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="View Results Chart", command=self.open_results_chart).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Prompt Playground", command=self.open_prompt_playground).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Settings", command=self.open_settings_dialog).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Download Model", command=self.open_model_downloader).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Edit Tests", command=lambda: open_path(TESTS_DIR)).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Open Results", command=lambda: open_path(RESULTS_DIR)).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="View Latest Report", command=self.open_latest_report).pack(side="left", padx=(12, 0))
ttk.Label(self, textvariable=self.status_var).pack(anchor="w", **padding)
self.log = scrolledtext.ScrolledText(self, wrap="word", height=22)
self.log.pack(fill="both", expand=True, padx=12, pady=(0, 12))
self.log.configure(state="disabled")
def _build_menu(self) -> None:
menubar = tk.Menu(self)
settings_menu = tk.Menu(menubar, tearoff=0)
settings_menu.add_command(label="Settings…", command=self.open_settings_dialog)
menubar.add_cascade(label="Settings", menu=settings_menu)
self.config(menu=menubar)
self._settings_menu = settings_menu
def fetch_models_async(self) -> None:
if self.running:
return
provider_name = self.provider_var.get()
if not provider_name:
return
self._set_status(f"Fetching models for {provider_name}")
self.model_combo.configure(values=())
self.model_var.set("")
thread = threading.Thread(target=self._fetch_models, daemon=True)
thread.start()
def _fetch_models(self) -> None:
provider_name = self.provider_var.get()
try:
provider = get_provider(provider_name, **self._provider_kwargs(provider_name))
models = provider.list_models()
except ProviderError as exc:
message = str(exc)
self.after(0, lambda m=message: self._on_model_fetch_error(m))
return
model_pairs = [(model.id, model.display_name) for model in models]
self.after(0, lambda: self._on_models_loaded(model_pairs))
def open_test_editor(self):
try:
from test_editor import TestEditorDialog
except ImportError:
messagebox.showerror("Error", "Test editor module not found.")
return
TestEditorDialog(self, TESTS_DIR)
def _on_model_fetch_error(self, message: str) -> None:
self._set_status("Model fetch failed")
messagebox.showerror("Model Load Error", message)
def _on_models_loaded(self, model_pairs: List[Tuple[str, str]]) -> None:
self.available_models = model_pairs
options = [display for _, display in model_pairs]
self.model_combo.configure(values=options)
if options:
self.model_var.set(options[0])
self._set_status(f"Loaded {len(options)} models.")
else:
self.model_var.set("")
self._set_status("No models found.")
def start_run(self) -> None:
if self.running:
return
provider_name = self.provider_var.get()
model_display = self.model_var.get()
if not provider_name:
messagebox.showwarning("Missing Provider", "Please select a provider before running tests.")
return
if not model_display:
messagebox.showwarning("Missing Model", "Please select a model before running tests.")
return
model_id = self._model_id_for_display(model_display)
if not model_id:
messagebox.showerror("Model Error", "Unable to resolve the selected model.")
return
temperature = float(self.temperature_var.get())
self.running = True
self._set_status("Running tests…")
self._append_log(f"Starting run with provider '{provider_name}' and model '{model_display}'.")
self._toggle_controls(state="disabled")
thread = threading.Thread(
target=self._run_tests_thread,
args=(provider_name, model_id, temperature),
daemon=True,
)
thread.start()
def _run_tests_thread(self, provider_name: str, model_id: str, temperature: float) -> None:
start_time = time.time()
def progress_callback(index: int, total: int, prompt: Dict[str, str], result: Dict[str, object]) -> None:
status = "FAILED" if "error" in result else "DONE"
title = prompt.get("title", f"Test {index}")
filename = prompt.get("filename", f"test{index}")
message = f"[{index}/{total}] {title} ({filename}) -> {status}"
self.after(0, lambda: self._append_log(message))
try:
report_path = run_suite(
provider_name=provider_name,
model_id=model_id,
temperature=temperature,
progress_callback=progress_callback,
)
except (TestRunError, TemplateNotFoundError) as exc:
message = str(exc)
self.after(0, lambda m=message: self._handle_run_error(m))
return
elapsed = time.time() - start_time
self.after(0, lambda: self._handle_run_success(report_path, elapsed))
def _handle_run_error(self, message: str) -> None:
self.running = False
self._toggle_controls(state="normal")
self._set_status("Run failed")
self._append_log(f"Error: {message}")
messagebox.showerror("Run Failed", message)
def _handle_run_success(self, report_path: Path, elapsed: float) -> None:
self.running = False
self._toggle_controls(state="normal")
self._set_status("Run complete")
self._append_log(f"Run complete in {elapsed:.2f}s. Report saved to {report_path.name}.")
messagebox.showinfo("Run Complete", f"Report saved to {report_path}")
def _toggle_controls(self, *, state: str) -> None:
widgets = [
self.run_button,
self.provider_combo,
self.model_combo,
self.temperature_spin,
]
for widget in widgets:
widget.configure(state=state)
def _append_log(self, message: str) -> None:
timestamp = time.strftime("%H:%M:%S")
self.log.configure(state="normal")
self.log.insert("end", f"[{timestamp}] {message}\n")
self.log.see("end")
self.log.configure(state="disabled")
def _set_status(self, message: str) -> None:
self.status_var.set(message)
def _model_id_for_display(self, display_name: str) -> str | None:
for model_id, display in self.available_models:
if display == display_name:
return model_id
return None
def _provider_kwargs(self, provider_name: str) -> Dict[str, object]:
if provider_name == LocalEngineProvider.name:
custom_paths = [Path(p).expanduser() for p in get_local_model_paths()]
return {"search_paths": [path for path in custom_paths if path]}
return {}
def _build_ui(self) -> None:
padding = {"padx": 12, "pady": 8}
header = ttk.Label(self, text="Automated Diagnostic Test Runner", font=("Helvetica", 18, "bold"))
header.pack(anchor="w", **padding)
form_frame = ttk.Frame(self)
form_frame.pack(fill="x", **padding)
ttk.Label(form_frame, text="Provider:").grid(row=0, column=0, sticky="w")
self.provider_combo = ttk.Combobox(
form_frame,
textvariable=self.provider_var,
values=self.provider_names,
state="readonly",
)
self.provider_combo.grid(row=0, column=1, sticky="ew", padx=(6, 18))
self.provider_combo.bind("<<ComboboxSelected>>", lambda event: self.fetch_models_async())
ttk.Label(form_frame, text="Model:").grid(row=0, column=2, sticky="w")
self.model_combo = ttk.Combobox(form_frame, textvariable=self.model_var, state="readonly")
self.model_combo.grid(row=0, column=3, sticky="ew", padx=(6, 18))
ttk.Label(form_frame, text="Temperature:").grid(row=0, column=4, sticky="w")
self.temperature_spin = ttk.Spinbox(
form_frame,
textvariable=self.temperature_var,
from_=0.0,
to=1.0,
increment=0.05,
width=6,
)
self.temperature_spin.grid(row=0, column=5, sticky="w")
form_frame.columnconfigure(1, weight=1)
form_frame.columnconfigure(3, weight=1)
button_frame = ttk.Frame(self)
button_frame.pack(fill="x", **padding)
self.run_button = ttk.Button(button_frame, text="Run Tests", command=self.start_run)
self.run_button.pack(side="left")
ttk.Button(button_frame, text="Refresh Models", command=self.fetch_models_async).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="New Test", command=self.open_test_editor).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Download Model", command=self.open_model_downloader).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Edit Tests", command=lambda: open_path(TESTS_DIR)).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="Open Results", command=lambda: open_path(RESULTS_DIR)).pack(side="left", padx=(12, 0))
ttk.Button(button_frame, text="View Latest Report", command=self.open_latest_report).pack(side="left", padx=(12, 0))
ttk.Label(self, textvariable=self.status_var).pack(anchor="w", **padding)
self.log = scrolledtext.ScrolledText(self, wrap="word", height=22)
self.log.pack(fill="both", expand=True, padx=12, pady=(0, 12))
self.log.configure(state="disabled")
if __name__ == "__main__":
app = App()
app.mainloop()
-193
View File
@@ -1,193 +0,0 @@
import tkinter as tk
from tkinter import simpledialog, messagebox
from pathlib import Path
import urllib.request
import shutil
import threading
class DownloadModelDialog(simpledialog.Dialog):
def __init__(self, parent, model_dir: Path):
self.model_dir = model_dir
self.active_downloads = {} # filename -> {progress, thread, cancel_flag}
super().__init__(parent, title="Download Model")
def body(self, master):
tk.Label(master, text="Model URL:").grid(row=0, column=0, sticky="w")
self.url_entry = tk.Entry(master, width=60)
self.url_entry.grid(row=0, column=1, padx=6, pady=4)
# Browse Hugging Face button
browse_btn = tk.Button(master, text="Browse Hugging Face...", command=self.open_hf_browser, bg="#e0e0e0", fg="#111", width=22)
browse_btn.grid(row=1, column=0, columnspan=2, sticky="w", padx=(0, 0), pady=(2, 2))
# Comprehensive caption/instruction at the bottom
caption_text = (
"Paste a direct download URL for a model file. "
"\n\n"
"Where to find models:\n"
"- Hugging Face: https://huggingface.co (search for GGUF or compatible models)\n"
"- Official model provider pages\n"
"\n"
"The URL should point directly to a downloadable file (e.g., .gguf, .bin).\n"
"Example: https://huggingface.co/ORG/MODEL/resolve/main/model-file.gguf\n"
"\n"
"Downloaded files will be saved to your selected model directory and will appear in the model selection list."
)
caption = tk.Label(
master,
text=caption_text,
justify="left",
anchor="w",
wraplength=440,
fg="#fff",
bg="#444",
font=(None, 10, "italic")
)
caption.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(16, 2))
# Download cards frame (below caption)
self.cards_frame = tk.Frame(master, bg="#222")
self.cards_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(8, 2))
self.refresh_cards()
return self.url_entry
def open_hf_browser(self):
# Popup window with embedded browser to Hugging Face
try:
from tkhtmlview import HtmlFrame
except ImportError:
messagebox.showerror("Missing Dependency", "tkhtmlview is required for the integrated browser. Please install it with 'pip install tkhtmlview'.")
return
popup = tk.Toplevel(self)
popup.title("Hugging Face Model Catalogue")
popup.geometry("1000x700")
# Info label
tk.Label(popup, text="Browse models. Right-click a download link and select 'Copy Link', then paste it in the URL field.", bg="#222", fg="#fff").pack(fill="x")
# Embedded browser (read-only, not full browser)
frame = HtmlFrame(popup, horizontal_scrollbar="auto", vertical_scrollbar="auto")
frame.pack(fill="both", expand=True)
frame.load_website("https://huggingface.co/models")
# Comprehensive caption/instruction at the bottom
caption_text = (
"Paste a direct download URL for a model file. "
"\n\n"
"Where to find models:\n"
"- Hugging Face: https://huggingface.co (search for GGUF or compatible models)\n"
"- Official model provider pages\n"
"\n"
"The URL should point directly to a downloadable file (e.g., .gguf, .bin).\n"
"Example: https://huggingface.co/ORG/MODEL/resolve/main/model-file.gguf\n"
"\n"
"Downloaded files will be saved to your selected model directory and will appear in the model selection list."
)
caption = tk.Label(
master,
text=caption_text,
justify="left",
anchor="w",
wraplength=440,
fg="#fff",
bg="#444",
font=(None, 10, "italic")
)
caption.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(16, 2))
# Download cards frame (below caption)
self.cards_frame = tk.Frame(master, bg="#222")
self.cards_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(8, 2))
self.refresh_cards()
return self.url_entry
def refresh_cards(self):
# Clear previous cards
for widget in self.cards_frame.winfo_children():
widget.destroy()
if not self.active_downloads:
placeholder = tk.Label(
self.cards_frame,
text="No downloads in progress.",
bg="#222",
fg="#aaa",
font=(None, 10, "italic")
)
placeholder.pack(fill="x", pady=12)
else:
row = 0
for fname, info in self.active_downloads.items():
card = tk.Frame(self.cards_frame, bg="#333", bd=2, relief="groove")
card.grid(row=row, column=0, sticky="ew", pady=4, padx=2)
# File name
tk.Label(card, text=fname, bg="#333", fg="#fff", font=(None, 10, "bold")).pack(anchor="w", padx=8, pady=(4,0))
bar_frame = tk.Frame(card, bg="#333")
bar_frame.pack(fill="x", padx=8, pady=(2,6))
# Percentage label
percent_var = info.get('percent_var')
if not percent_var:
percent_var = tk.StringVar(value="0%")
info['percent_var'] = percent_var
percent_label = tk.Label(bar_frame, textvariable=percent_var, width=5, anchor="e", bg="#333", fg="#fff")
percent_label.pack(side="left")
# Progress bar
progress_var = info.get('progress_var')
if not progress_var:
progress_var = tk.DoubleVar(value=0)
info['progress_var'] = progress_var
bar = tk.ttk.Progressbar(bar_frame, variable=progress_var, maximum=100, length=220)
bar.pack(side="left", fill="x", expand=True, padx=(6,6))
# Cancel button
cancel_btn = tk.Button(bar_frame, text="Cancel", command=lambda f=fname: self.cancel_download(f), bg="#a33", fg="#fff")
cancel_btn.pack(side="right", padx=(6,0))
row += 1
def apply(self):
url = self.url_entry.get().strip()
if not url:
messagebox.showerror("Error", "Model URL is required.")
return
filename = url.split("/")[-1]
dest_path = self.model_dir / filename
cancel_flag = threading.Event()
info = {
'progress_var': tk.DoubleVar(value=0),
'percent_var': tk.StringVar(value="0%"),
'cancel_flag': cancel_flag,
}
self.active_downloads[filename] = info
self.refresh_cards()
def download():
try:
with urllib.request.urlopen(url) as response, open(dest_path, 'wb') as out_file:
total = int(response.getheader('Content-Length', 0))
downloaded = 0
chunk = 8192
while True:
if cancel_flag.is_set():
break
data = response.read(chunk)
if not data:
break
out_file.write(data)
downloaded += len(data)
percent = int((downloaded / total) * 100) if total else 0
info['progress_var'].set(percent)
info['percent_var'].set(f"{percent}%")
self.cards_frame.after(50, self.refresh_cards)
if not cancel_flag.is_set():
messagebox.showinfo("Success", f"Model downloaded to {dest_path}")
except Exception as e:
if not cancel_flag.is_set():
messagebox.showerror("Download Failed", str(e))
finally:
del self.active_downloads[filename]
self.cards_frame.after(100, self.refresh_cards)
t = threading.Thread(target=download, daemon=True)
info['thread'] = t
t.start()
def cancel_download(self, filename):
info = self.active_downloads.get(filename)
if info:
info['cancel_flag'].set()
del self.active_downloads[filename]
self.refresh_cards()
-39
View File
@@ -1,39 +0,0 @@
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
class PlaygroundDialog(tk.Toplevel):
def __init__(self, parent, send_callback):
super().__init__(parent)
self.title("Prompt Playground")
self.geometry("700x600")
self.transient(parent)
self.grab_set()
self.send_callback = send_callback
ttk.Label(self, text="Prompt:").pack(anchor="w", padx=10, pady=(10,0))
self.prompt_text = scrolledtext.ScrolledText(self, height=8, wrap="word")
self.prompt_text.pack(fill="x", padx=10, pady=(0,10))
ttk.Button(self, text="Send", command=self.send_prompt).pack(pady=(0,10))
ttk.Label(self, text="Response:").pack(anchor="w", padx=10)
self.response_text = scrolledtext.ScrolledText(self, height=18, wrap="word")
self.response_text.pack(fill="both", expand=True, padx=10, pady=(0,10))
self.response_text.configure(state="disabled")
def send_prompt(self):
prompt = self.prompt_text.get("1.0", "end-1c").strip()
if not prompt:
messagebox.showwarning("Prompt Required", "Please enter a prompt.")
return
self.response_text.configure(state="normal")
self.response_text.delete("1.0", "end")
self.response_text.insert("1.0", "Running...")
self.response_text.update()
try:
response = self.send_callback(prompt)
except Exception as e:
response = f"Error: {e}"
self.response_text.delete("1.0", "end")
self.response_text.insert("1.0", response)
self.response_text.configure(state="disabled")
-46
View File
@@ -1,46 +0,0 @@
import tkinter as tk
from tkinter import ttk
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import re
def parse_report(report_path: Path):
# Simple parser for markdown report: looks for lines like 'Pass: 8/10', 'Fail: 2/10', etc.
pass_count = 0
fail_count = 0
total = 0
with open(report_path, 'r', encoding='utf-8') as f:
for line in f:
m = re.match(r'Pass: (\d+)/(\d+)', line)
if m:
pass_count = int(m.group(1))
total = int(m.group(2))
m = re.match(r'Fail: (\d+)/(\d+)', line)
if m:
fail_count = int(m.group(1))
return pass_count, fail_count, total
class ResultsChartDialog(tk.Toplevel):
def __init__(self, parent, report_path: Path):
super().__init__(parent)
self.title(f"Test Results Chart: {report_path.name}")
self.geometry("600x500")
self.transient(parent)
self.grab_set()
pass_count, fail_count, total = parse_report(report_path)
fig, ax = plt.subplots(figsize=(5,4))
ax.bar(['Pass', 'Fail'], [pass_count, fail_count], color=['#4caf50', '#f44336'])
ax.set_ylabel('Count')
ax.set_title(f"Test Results ({pass_count+fail_count}/{total} completed)")
for i, v in enumerate([pass_count, fail_count]):
ax.text(i, v + 0.1, str(v), ha='center', va='bottom', fontweight='bold')
fig.tight_layout()
canvas = FigureCanvasTkAgg(fig, master=self)
canvas.draw()
canvas.get_tk_widget().pack(fill="both", expand=True)
ttk.Button(self, text="Close", command=self.destroy).pack(pady=8)
-135
View File
@@ -1,135 +0,0 @@
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '.core'))
from settings import load_settings, save_settings
from config import DEFAULT_TEMPERATURE, DEFAULT_PROVIDER_NAME
from providers import list_provider_names
class SettingsDialog(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Settings")
self.geometry("500x400")
self.transient(parent)
self.grab_set()
self.settings = load_settings()
notebook = ttk.Notebook(self)
notebook.pack(fill="both", expand=True, padx=10, pady=10)
# General tab
general_frame = ttk.Frame(notebook)
notebook.add(general_frame, text="General")
ttk.Label(general_frame, text="Default Provider:").grid(row=0, column=0, sticky="w", pady=6)
self.provider_var = tk.StringVar(value=self.settings.get("default_provider", DEFAULT_PROVIDER_NAME))
self.provider_combo = ttk.Combobox(general_frame, textvariable=self.provider_var, values=list_provider_names(), state="readonly", width=32)
self.provider_combo.grid(row=0, column=1, sticky="ew", padx=8)
ttk.Label(general_frame, text="Default Temperature:").grid(row=1, column=0, sticky="w", pady=6)
self.temp_var = tk.DoubleVar(value=self.settings.get("default_temperature", DEFAULT_TEMPERATURE))
self.temp_entry = ttk.Entry(general_frame, textvariable=self.temp_var, width=8)
self.temp_entry.grid(row=1, column=1, sticky="w", padx=8)
# Model paths tab
paths_frame = ttk.Frame(notebook)
notebook.add(paths_frame, text="Model Paths")
# Model paths now as list of dicts: {"nickname": str, "path": str}
self.paths = self.settings.get("local_model_paths", [])
if self.paths and isinstance(self.paths[0], str):
# Migrate from old format
self.paths = [{"nickname": os.path.basename(p.rstrip("/")), "path": p} for p in self.paths]
self.paths_list = tk.Listbox(paths_frame, selectmode=tk.SINGLE, width=60, height=8)
self.paths_list.pack(fill="x", padx=8, pady=(8,4))
self.refresh_paths_list()
btn_frame = ttk.Frame(paths_frame)
btn_frame.pack(fill="x", padx=8, pady=4)
ttk.Button(btn_frame, text="Add Path", command=self.add_path).pack(side="left")
ttk.Button(btn_frame, text="Edit Selected", command=self.edit_path).pack(side="left", padx=(8,0))
ttk.Button(btn_frame, text="Remove Selected", command=self.remove_path).pack(side="left", padx=(8,0))
# Key/legend for nicknames
key_frame = ttk.Frame(paths_frame)
key_frame.pack(fill="x", padx=8, pady=(8, 4))
key_text = (
"Nickname: A short label for this model path. "
"You can use nicknames to quickly identify or group model folders. "
"For example, use 'Main', 'Backup', or 'Experimental'. "
"Nicknames are for your reference only and do not affect model loading."
)
ttk.Label(key_frame, text=key_text, wraplength=420, foreground="#878787").pack(anchor="w")
def refresh_paths_list(self):
self.paths_list.delete(0, "end")
for entry in self.paths:
display = f"{entry.get('nickname','')}{entry.get('path','')}"
self.paths_list.insert("end", display)
def add_path(self):
self.edit_path(new=True)
def edit_path(self, new=False):
idx = None
if not new:
sel = self.paths_list.curselection()
if not sel:
return
idx = sel[0]
entry = self.paths[idx]
else:
entry = {"nickname": "", "path": ""}
popup = tk.Toplevel(self)
popup.title("Edit Model Path")
popup.geometry("400x140")
tk.Label(popup, text="Nickname:").pack(anchor="w", padx=12, pady=(12,0))
nick_var = tk.StringVar(value=entry.get("nickname", ""))
nick_entry = ttk.Entry(popup, textvariable=nick_var, width=40)
nick_entry.pack(fill="x", padx=12)
tk.Label(popup, text="Path:").pack(anchor="w", padx=12, pady=(8,0))
path_var = tk.StringVar(value=entry.get("path", ""))
path_entry = ttk.Entry(popup, textvariable=path_var, width=40)
path_entry.pack(fill="x", padx=12)
def save_edit():
new_entry = {"nickname": nick_var.get().strip(), "path": path_var.get().strip()}
if not new_entry["path"]:
messagebox.showerror("Error", "Path cannot be empty.", parent=popup)
return
if new:
self.paths.append(new_entry)
else:
self.paths[idx] = new_entry
self.refresh_paths_list()
popup.destroy()
btns = ttk.Frame(popup)
btns.pack(fill="x", pady=10)
ttk.Button(btns, text="Save", command=save_edit).pack(side="right", padx=12)
ttk.Button(btns, text="Cancel", command=popup.destroy).pack(side="right")
# Save/Cancel
save_btn = ttk.Button(self, text="Save", command=self.save)
save_btn.pack(side="right", padx=10, pady=10)
cancel_btn = ttk.Button(self, text="Cancel", command=self.destroy)
cancel_btn.pack(side="right", pady=10)
def add_path(self):
selection = filedialog.askdirectory(parent=self)
if selection and selection not in self.paths_list.get(0, "end"):
self.paths_list.insert("end", selection)
def remove_path(self):
sel = self.paths_list.curselection()
if sel:
idx = sel[0]
del self.paths[idx]
self.refresh_paths_list()
def save(self):
self.settings["default_provider"] = self.provider_var.get().strip()
self.settings["default_temperature"] = self.temp_var.get()
self.settings["local_model_paths"] = self.paths
save_settings(self.settings)
messagebox.showinfo("Settings", "Settings saved.")
self.destroy()
-34
View File
@@ -1,34 +0,0 @@
import tkinter as tk
from tkinter import simpledialog, messagebox
from pathlib import Path
class TestEditorDialog(simpledialog.Dialog):
def __init__(self, parent, test_dir: Path):
self.test_dir = test_dir
super().__init__(parent, title="New/Edit Test")
def body(self, master):
tk.Label(master, text="Test Filename:").grid(row=0, column=0, sticky="w")
self.filename_entry = tk.Entry(master, width=40)
self.filename_entry.grid(row=0, column=1, padx=6, pady=4)
tk.Label(master, text="Prompt:").grid(row=1, column=0, sticky="nw")
self.prompt_text = tk.Text(master, width=60, height=10)
self.prompt_text.grid(row=1, column=1, padx=6, pady=4)
tk.Label(master, text="Expected Output:").grid(row=2, column=0, sticky="nw")
self.output_text = tk.Text(master, width=60, height=10)
self.output_text.grid(row=2, column=1, padx=6, pady=4)
return self.filename_entry
def apply(self):
filename = self.filename_entry.get().strip()
prompt = self.prompt_text.get("1.0", "end").strip()
output = self.output_text.get("1.0", "end").strip()
if not filename or not prompt:
messagebox.showerror("Error", "Filename and prompt are required.")
return
file_path = self.test_dir / filename
with open(file_path, "w", encoding="utf-8") as f:
f.write(f"PROMPT:\n{prompt}\n\nEXPECTED:\n{output}\n")
messagebox.showinfo("Saved", f"Test saved as {file_path.name}")