Zentk integration (Zenity/yad support) (#1475)

* Zentk integration (Zenity/yad support)

* Escape incompatible dependencies in zentk

* Properly clean env
This commit is contained in:
henk717 2025-04-11 12:23:23 +02:00 committed by GitHub
parent e2fefc373f
commit 8fd70f37bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -29,6 +29,7 @@ import html
import urllib.parse as urlparse import urllib.parse as urlparse
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Tuple
# constants # constants
sampler_order_max = 7 sampler_order_max = 7
@ -615,9 +616,8 @@ def unpack_to_dir(destpath = ""):
print("Attempt to unpack KoboldCpp into directory...") print("Attempt to unpack KoboldCpp into directory...")
if not cliunpack: if not cliunpack:
from tkinter.filedialog import askdirectory
from tkinter import messagebox from tkinter import messagebox
destpath = askdirectory(title='Select an empty folder to unpack KoboldCpp') destpath = zentk_askdirectory(title='Select an empty folder to unpack KoboldCpp')
if not destpath: if not destpath:
return return
@ -3455,19 +3455,123 @@ def RunServerMultiThreaded(addr, port, server_handler):
threadArr[i].stop() threadArr[i].stop()
sys.exit(0) sys.exit(0)
# Based on https://github.com/mathgeniuszach/xdialog/blob/main/xdialog/zenity_dialogs.py - MIT license | - Expanded version by Henk717
def zenity_clean(txt: str):
return txt\
.replace("\\", "\\\\")\
.replace("$", "\\$")\
.replace("!", "\\!")\
.replace("*", "\\*")\
.replace("?", "\\?")\
.replace("&", "&")\
.replace("|", "|")\
.replace("<", "&lt;")\
.replace(">", "&gt;")\
.replace("(", "\\(")\
.replace(")", "\\)")\
.replace("[", "\\[")\
.replace("]", "\\]")\
.replace("{", "\\{")\
.replace("}", "\\}")\
def zenity(typ, filetypes=None, initialdir="", initialfile="", **kwargs) -> Tuple[int, str]:
import shutil, subprocess, os, platform
if not platform.system() == "Linux":
raise Exception("This feature should only be used on Linux, if you see this error there is no TK fallback implemented in the code.")
zenity_bin = shutil.which("zenity")
if not zenity_bin:
zenity_bin = shutil.which("yad")
if not zenity_bin:
raise Exception("Zenity not present")
# Build args based on keywords
args = ['/usr/bin/env', zenity_bin, '--'+typ]
for k, v in kwargs.items():
if v is True:
args.append(f'--{k.replace("_", "-").strip("-")}')
elif isinstance(v, str):
cv = zenity_clean(v) if k != "title" else v
args.append(f'--{k.replace("_", "-").strip("-")}={cv}')
# Build filetypes specially if specified
if filetypes:
for name, globs in filetypes:
if name:
globlist = globs.split()
args.append(f'--file-filter={name.replace("|", "")} ({", ".join(t for t in globlist)})|{globs}')
# Default filename and folder
if initialdir is None:
initialdir=os.getcwd()
if initialfile is None:
initialfile=""
initialpath = os.path.join(initialdir, initialfile)
args.append(f'--filename={initialpath}')
clean_env = os.environ.copy()
clean_env.pop("LD_LIBRARY_PATH", None)
clean_env["PATH"] = "/usr/bin:/bin"
proc = subprocess.Popen(
args,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
shell=False,
env=clean_env
)
stdout, _ = proc.communicate()
return (proc.returncode, stdout.decode('utf-8').strip())
# note: In this section we wrap around file dialogues to allow for zenity
def zentk_askopenfilename(**options):
try:
result = zenity('file-selection', filetypes=options.get("filetypes"), initialdir=options.get("initialdir"), title=options.get("title"))[1]
except:
from tkinter.filedialog import askopenfilename
result = askopenfilename(**options)
return result
def zentk_askopenmultiplefilenames(**options):
try:
from os.path import isfile
files = zenity('file-selection', filetypes=options.get("filetypes"), initialdir=options.get("initialdir"), title=options.get("title"), multiple=True, separator="\n")[1].splitlines()
result = tuple(filter(isfile, files))
except:
from tkinter.filedialog import askopenfilenames
result = askopenfilenames(**options)
return result
def zentk_askdirectory(**options):
try:
result = zenity('file-selection', initialdir=options.get("initialdir"), title=options.get("title"), directory=True)[1]
except:
from tkinter.filedialog import askdirectory
result = askdirectory(**options)
return result
def zentk_asksaveasfilename(**options):
try:
result = zenity('file-selection', filetypes=options.get("filetypes"), initialdir=options.get("initialdir"), initialfile=options.get("initialfile"), title=options.get("title"), save=True)[1]
except:
from tkinter.filedialog import asksaveasfilename
result = asksaveasfilename(**options)
return result
### End of MIT license
# note: customtkinter-5.2.0 # note: customtkinter-5.2.0
def show_gui(): def show_gui():
global using_gui_launcher global using_gui_launcher
using_gui_launcher = True using_gui_launcher = True
from tkinter.filedialog import askopenfilename, askdirectory
from tkinter.filedialog import asksaveasfilename
# if args received, launch # if args received, launch
if len(sys.argv) != 1 and not args.showgui: if len(sys.argv) != 1 and not args.showgui:
import tkinter as tk import tkinter as tk
root = tk.Tk() #we dont want the useless window to be visible, but we want it in taskbar root = tk.Tk() #we dont want the useless window to be visible, but we want it in taskbar
root.attributes("-alpha", 0) root.attributes("-alpha", 0)
args.model_param = askopenfilename(title="Select ggml model .bin or .gguf file or .kcpps config") args.model_param = zentk_askopenfilename(title="Select ggml model .bin or .gguf file or .kcpps config")
root.withdraw() root.withdraw()
root.quit() root.quit()
if args.model_param and args.model_param!="" and (args.model_param.lower().endswith('.kcpps') or args.model_param.lower().endswith('.kcppt') or args.model_param.lower().endswith('.kcpps?download=true') or args.model_param.lower().endswith('.kcppt?download=true')): if args.model_param and args.model_param!="" and (args.model_param.lower().endswith('.kcpps') or args.model_param.lower().endswith('.kcppt') or args.model_param.lower().endswith('.kcpps?download=true') or args.model_param.lower().endswith('.kcppt?download=true')):
@ -3770,16 +3874,16 @@ def show_gui():
initialDir = initialDir if os.path.isdir(initialDir) else None initialDir = initialDir if os.path.isdir(initialDir) else None
fnam = None fnam = None
if dialog_type==2: if dialog_type==2:
fnam = askdirectory(title=text, mustexist=True, initialdir=initialDir) fnam = zentk_askdirectory(title=text, mustexist=True, initialdir=initialDir)
elif dialog_type==1: elif dialog_type==1:
fnam = asksaveasfilename(title=text, filetypes=filetypes, defaultextension=filetypes, initialdir=initialDir) fnam = zentk_asksaveasfilename(title=text, filetypes=filetypes, defaultextension=filetypes, initialdir=initialDir)
if not fnam: if not fnam:
fnam = "" fnam = ""
else: else:
fnam = str(fnam).strip() fnam = str(fnam).strip()
fnam = f"{fnam}.jsondb" if ".jsondb" not in fnam.lower() else fnam fnam = f"{fnam}.jsondb" if ".jsondb" not in fnam.lower() else fnam
else: else:
fnam = askopenfilename(title=text,filetypes=filetypes, initialdir=initialDir) fnam = zentk_askopenfilename(title=text,filetypes=filetypes, initialdir=initialDir)
if fnam: if fnam:
var.set(fnam) var.set(fnam)
if onchoosefile: if onchoosefile:
@ -4181,7 +4285,7 @@ def show_gui():
def pickpremadetemplate(): def pickpremadetemplate():
initialDir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'kcpp_adapters') initialDir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'kcpp_adapters')
initialDir = initialDir if os.path.isdir(initialDir) else None initialDir = initialDir if os.path.isdir(initialDir) else None
fnam = askopenfilename(title="Pick Premade ChatCompletions Adapter",filetypes=[("JSON Adapter", "*.json")], initialdir=initialDir) fnam = zentk_askopenfilename(title="Pick Premade ChatCompletions Adapter",filetypes=[("JSON Adapter", "*.json")], initialdir=initialDir)
if fnam: if fnam:
chatcompletionsadapter_var.set(fnam) chatcompletionsadapter_var.set(fnam)
ctk.CTkButton(model_tab, 64, text="Pick Premade", command=pickpremadetemplate).grid(row=25, column=0, padx=322, stick="nw") ctk.CTkButton(model_tab, 64, text="Pick Premade", command=pickpremadetemplate).grid(row=25, column=0, padx=322, stick="nw")
@ -4311,7 +4415,7 @@ def show_gui():
file_type = [("KoboldCpp LaunchTemplate", "*.kcppt")] file_type = [("KoboldCpp LaunchTemplate", "*.kcppt")]
#remove blacklisted fields #remove blacklisted fields
savdict = convert_args_to_template(savdict) savdict = convert_args_to_template(savdict)
filename = asksaveasfilename(filetypes=file_type, defaultextension=".kcppt") filename = zentk_asksaveasfilename(filetypes=file_type, defaultextension=".kcppt")
if not filename: if not filename:
return return
filenamestr = str(filename).strip() filenamestr = str(filename).strip()
@ -4335,7 +4439,7 @@ def show_gui():
# launch # launch
def guilaunch(): def guilaunch():
if model_var.get() == "" and sd_model_var.get() == "" and whisper_model_var.get() == "" and tts_model_var.get() == "" and embeddings_model_var.get() == "" and nomodel.get()!=1: if model_var.get() == "" and sd_model_var.get() == "" and whisper_model_var.get() == "" and tts_model_var.get() == "" and embeddings_model_var.get() == "" and nomodel.get()!=1:
tmp = askopenfilename(title="Select ggml model .bin or .gguf file") tmp = zentk_askopenfilename(title="Select ggml model .bin or .gguf file")
model_var.set(tmp) model_var.set(tmp)
nonlocal nextstate nonlocal nextstate
nextstate = 1 nextstate = 1
@ -4713,7 +4817,7 @@ def show_gui():
export_vars() export_vars()
savdict = json.loads(json.dumps(args.__dict__)) savdict = json.loads(json.dumps(args.__dict__))
file_type = [("KoboldCpp Settings", "*.kcpps")] file_type = [("KoboldCpp Settings", "*.kcpps")]
filename = asksaveasfilename(filetypes=file_type, defaultextension=".kcpps") filename = zentk_asksaveasfilename(filetypes=file_type, defaultextension=".kcpps")
if not filename: if not filename:
return return
filenamestr = str(filename).strip() filenamestr = str(filename).strip()
@ -4727,7 +4831,7 @@ def show_gui():
def load_config_gui(): #this is used to populate the GUI with a config file, whereas load_config_cli simply overwrites cli args def load_config_gui(): #this is used to populate the GUI with a config file, whereas load_config_cli simply overwrites cli args
file_type = [("KoboldCpp Settings", "*.kcpps *.kcppt")] file_type = [("KoboldCpp Settings", "*.kcpps *.kcppt")]
global runmode_untouched global runmode_untouched
filename = askopenfilename(filetypes=file_type, defaultextension=".kcppt", initialdir=None) filename = zentk_askopenfilename(filetypes=file_type, defaultextension=".kcppt", initialdir=None)
if not filename or filename=="": if not filename or filename=="":
return return
runmode_untouched = False runmode_untouched = False
@ -5371,8 +5475,7 @@ def analyze_gguf_model(args,filename):
def analyze_gguf_model_wrapper(filename=""): def analyze_gguf_model_wrapper(filename=""):
if not filename or filename=="": if not filename or filename=="":
try: try:
from tkinter.filedialog import askopenfilename filename = zentk_askopenfilename(title="Select GGUF to analyze")
filename = askopenfilename(title="Select GGUF to analyze")
except Exception as e: except Exception as e:
print(f"Cannot select file to analyze: {e}") print(f"Cannot select file to analyze: {e}")
if not filename or filename=="" or not os.path.exists(filename): if not filename or filename=="" or not os.path.exists(filename):