44import customtkinter as ctk
55import os
66
7- # TODO: Add `progressbar` to all jsons.
8- # Fill README.md
9-
10-
117APP_PATH : str = os .path .dirname (os .path .realpath (__file__ ))
128THEME_PATH : str = os .path .join (APP_PATH , "theme" , "custom-theme.json" )
139
10+ error_log = []
11+ model_list = []
12+
1413class GPLLicense (ctk .CTkToplevel ):
1514 def __init__ (self ):
1615 super ().__init__ ()
@@ -38,9 +37,19 @@ def __init__(self):
3837 """ ,)
3938 self .label .grid (row = 0 , column = 0 , sticky = "nsew" , padx = 10 )
4039
40+ class ErrorWindow (ctk .CTkToplevel ):
41+ def __init__ (self ):
42+ super ().__init__ ()
4143
42- error_log = []
43- model_list = []
44+ self .title ("Error logs" )
45+ self .geometry ("600x400" )
46+
47+ self .error_output = ctk .CTkTextbox (self , height = 390 , width = 590 , cursor = "arrow" )
48+ self .error_output .grid (row = 0 , column = 0 , padx = 5 , pady = 5 , sticky = "nsew" ,)
49+
50+ error_lines = "\n \n " .join (error_log )
51+ self .error_output .insert ("1.0" , error_lines )
52+ self .error_output .configure (wrap = "word" , state = "disabled" , font = ("" , 14 ))
4453
4554class OllamaGUIChat (ctk .CTk ):
4655 def __init__ (self ):
@@ -59,6 +68,8 @@ def __init__(self):
5968 self .messages = []
6069 self .full_reply = ""
6170 self .gpl_opened = None
71+ self .error_opened = None
72+ self .response = None
6273
6374 self .title ("Ollama GUI Chat" )
6475 self .geometry ("800x800" )
@@ -70,7 +81,6 @@ def __init__(self):
7081
7182 def setup_ui (self ):
7283
73-
7484 # top panel buttons
7585 top_frame = ctk .CTkFrame (self )
7686 top_frame .grid (row = 0 , padx = 5 ,pady = 2 , sticky = "nswe" )
@@ -109,7 +119,7 @@ def setup_ui(self):
109119 self .chat_font_var .set (value = "14" )
110120 self .chat_output = ctk .CTkTextbox (self , height = 600 , cursor = "arrow" )
111121 self .chat_output .grid (row = 1 , columnspan = 2 , padx = 5 , pady = (5 , 2 ), sticky = "nsew" ,)
112- self .chat_output .insert ("1.0" , error_log )
122+ # self.chat_output.insert("1.0", error_log)
113123 self .chat_output .configure (wrap = "word" , state = "disabled" , font = ("" , int (self .chat_font_var .get ())))
114124
115125 mid_frame = ctk .CTkFrame (self )
@@ -149,6 +159,8 @@ def setup_ui(self):
149159 self .progress_bar = ctk .CTkProgressBar (mid_frame2 , mode = "determinate" ,)
150160 self .progress_bar .configure (width = 150 ,)
151161
162+ self .stop_button = ctk .CTkButton (mid_frame2 , text = "🛑" , command = self .stop_response )
163+
152164 # lower panel
153165 self .input_field = ctk .CTkTextbox (self , height = 100 )
154166 self .input_field .grid (row = 3 , column = 0 , padx = 5 , pady = (5 ,5 ), sticky = "nswe" ,)
@@ -208,7 +220,8 @@ def insert_text(self, content):
208220 self .chat_output .see ("end" )
209221
210222 except Exception as e :
211- print (f"Error: { e } " )
223+ error_log .append (f"Error: { e } " )
224+ self .open_error_logs ()
212225
213226 def on_model_change (self , * args ):
214227 self .messages = []
@@ -242,6 +255,14 @@ def open_gpl(self):
242255 else :
243256 self .gpl_opened .focus ()
244257
258+ def open_error_logs (self ):
259+ if self .error_opened is None or not self .error_opened .winfo_exists ():
260+ self .error_opened = ErrorWindow ()
261+
262+ else :
263+ self .error_opened .destroy ()
264+ self .error_opened = ErrorWindow ()
265+
245266 def clear_chat (self ):
246267 self .chat_output .configure (state = "normal" )
247268 self .messages = []
@@ -251,25 +272,57 @@ def clear_chat(self):
251272 def save_chat (self ):
252273 file_path = filedialog .asksaveasfilename (defaultextension = ".txt" , filetypes = [("Text files" , "*.txt" ), ("All files" , "*.*" )])
253274
254- if file_path :
255- with open (file_path , "w" , encoding = "utf-8" ) as f :
256- for message in self .messages :
257- f .write (f"{ message ['role' ]} : { message ['content' ]} \n " )
275+ try :
276+ if file_path :
277+ with open (file_path , "w" , encoding = "utf-8" ) as f :
278+ for message in self .messages :
279+ f .write (f"{ message ['role' ]} : { message ['content' ]} \n " )
280+
281+ except Exception as e :
282+ error_log .append (f"def save_chat: { e } " )
283+ self .open_error_logs ()
258284
259285 def load_chat (self ):
260286 file_path = filedialog .askopenfilename (filetypes = [("Text files" , "*.txt" ), ("All files" , "*.*" )])
261- with open (file_path , "r" , encoding = "utf-8" ) as f :
262- for line in f .readlines ():
263- self .messages .append ({"role" : "assistant" , "content" : line })
287+
288+ if not file_path : # if you dont want to see an error each time you cancel loading - keep this intact
289+ return
290+
291+ try :
292+ with open (file_path , "r" , encoding = "utf-8" ) as f :
293+ for line in f .readlines ():
294+ self .messages .append ({"role" : "assistant" , "content" : line })
295+
296+ except Exception as e :
297+ error_log .append (f"def load_chat: { e } " )
298+ self .open_error_logs ()
299+
300+ def stop_response (self ):
301+ last_key = len (self .messages ) - 1
302+
303+ if hasattr (self , "response" ) and self .response :
304+ self .response .close ()
305+ del self .response
306+ self .messages .pop (last_key )
307+ self .insert_text ("\n \n [AI] : Chat stopped.\n \n " )
308+
309+ self .hide_progress ()
310+ self .send_button .configure (state = "normal" )
264311
265312 def show_progress (self ):
266313 self .progress_bar .grid (row = 2 , column = 3 , sticky = "we" )
267314 self .progress_bar .start ()
268315
316+ self .stop_button .grid (row = 2 , column = 4 , sticky = "we" , padx = 5 )
317+ self .stop_button .configure (height = 10 , width = 10 , corner_radius = 5 , border_width = 2 , state = "normal" )
318+
269319 def hide_progress (self ):
270320 self .progress_bar .stop ()
271321 self .progress_bar .grid_forget ()
272322
323+ self .stop_button .grid_forget ()
324+ self .stop_button .configure (state = "disabled" )
325+
273326 # checks for installed LLMs.
274327 def check_existing_models (self ) -> None :
275328 try :
@@ -284,8 +337,8 @@ def check_existing_models(self) -> None:
284337 self .refresh_model_list ()
285338
286339 except requests .exceptions .RequestException as e :
287- print (f"Error while checking models : { e } " )
288- self .insert_text ( f"Error while checking models: { e } \n \n " )
340+ error_log . append (f"def check_existing_models : { e } " )
341+ self .open_error_logs ( )
289342
290343 def send_message (self ):
291344 question = self .input_field .get ("1.0" , "end" ).strip ()
@@ -311,35 +364,46 @@ def send_message(self):
311364
312365 try :
313366 response = requests .post (url , json = payload , stream = True )
367+ self .response = response
314368
315369 if response .status_code == 200 :
316370 self .insert_text ("[AI] :\n " )
371+ self .show_progress ()
317372 #self.update()
318373
319374 # full_reply "", needed for AI to remember the context of messages.
320375 # Without this, every new message is considered as new chat or whatever.
321376 full_reply = ""
322- for line in response .iter_lines (decode_unicode = True ):
323- if line .strip ():
324- try :
325- data = json .loads (line )
326- content = data .get ("message" , {}).get ("content" , "" )
327- full_reply += content
328- self .show_progress ()
329- self .insert_text (content )
330- self .update ()
331-
332- except json .JSONDecodeError :
333- continue
334-
335- self .messages .append ({"role" : "assistant" , "content" : full_reply })
336- self .insert_text ("\n \n " )
377+ try :
378+ for line in response .iter_lines (decode_unicode = True ):
379+ if line .strip ():
380+ try :
381+ data = json .loads (line )
382+ content = data .get ("message" , {}).get ("content" , "" )
383+ full_reply += content
384+ self .insert_text (content )
385+ self .update ()
386+
387+ except json .JSONDecodeError :
388+ error_log .append (f"def send_message: { line } " )
389+ self .open_error_logs ()
390+ continue
391+
392+ self .messages .append ({"role" : "assistant" , "content" : full_reply })
393+ self .insert_text ("\n \n " )
394+
395+ # YES, IT WILL SCREAM AN ERROR EACH TIME YOU CANCEL THE CHAT.
396+ except Exception as e :
397+ error_log .append (f"def send_message: { e } " )
398+ #self.open_error_logs() # Yes, thats why this is commented out. How would you feel if someone rips your tongue off mid-sentence?
337399
338400 else :
339- self .insert_text (f"\n Error: { response .status_code } \n { response .text } \n \n " )
401+ error_log .append (f"def send_message: { response .status_code } " )
402+ self .open_error_logs ()
340403
341404 except requests .exceptions .RequestException as e :
342- self .insert_text (f"\n Error sending request: { e } \n \n " )
405+ error_log .append (f"def send_message: { e } " )
406+ self .open_error_logs ()
343407
344408 self .send_button .configure (state = "normal" )
345409 self .hide_progress ()
0 commit comments