Remove unused modules and dialogs to streamline the application
This commit is contained in:
@@ -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")
|
|
||||||
@@ -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()
|
|
||||||
@@ -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()
|
|
||||||
@@ -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")
|
|
||||||
@@ -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)
|
|
||||||
@@ -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()
|
|
||||||
@@ -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}")
|
|
||||||
Reference in New Issue
Block a user