Skip to content

Commit f77c4ad

Browse files
committed
OGC 1.6.0: Release Linux/Windows
Added Stop button Added more error logs Added Threading that prevents freezing on msg Reorganized some lines of code Refresh models on new Host URL Update Windows Version
1 parent 75d6b2d commit f77c4ad

File tree

3 files changed

+159
-69
lines changed

3 files changed

+159
-69
lines changed

App/Linux/OllamaGUIChat.py

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import customtkinter as ctk
55
import os
6+
import threading as thr
67

78
APP_PATH: str = os.path.dirname(os.path.realpath(__file__))
89
THEME_PATH: str = os.path.join(APP_PATH, "theme", "custom-theme.json")
@@ -119,7 +120,6 @@ def setup_ui(self):
119120
self.chat_font_var.set(value="14")
120121
self.chat_output = ctk.CTkTextbox(self, height=600, cursor="arrow")
121122
self.chat_output.grid(row=1, columnspan=2, padx=5, pady=(5, 2), sticky="nsew",)
122-
#self.chat_output.insert("1.0", error_log)
123123
self.chat_output.configure(wrap="word", state="disabled", font=("", int(self.chat_font_var.get())))
124124

125125
mid_frame = ctk.CTkFrame(self)
@@ -159,19 +159,19 @@ def setup_ui(self):
159159
self.progress_bar = ctk.CTkProgressBar(mid_frame2, mode="determinate",)
160160
self.progress_bar.configure(width=150,)
161161

162-
self.stop_button = ctk.CTkButton(mid_frame2, text="🛑", command=self.stop_response)
162+
self.stop_button = ctk.CTkButton(mid_frame2, text="Stop", command=self.stop_response)
163163

164164
# lower panel
165165
self.input_field = ctk.CTkTextbox(self, height=100)
166166
self.input_field.grid(row=3, column=0, padx=5, pady=(5,5), sticky="nswe",)
167167
self.input_field.configure(wrap="word",)
168168
self.input_field.focus_set()
169169

170-
self.send_button = ctk.CTkButton(self, text="📤", command=self.send_message)
170+
self.send_button = ctk.CTkButton(self, text="📤", command=self.send_message_thread)
171171
self.send_button.grid(row=3, column=1, sticky="we", padx=(0,5), pady=(5,5))
172172
self.send_button.configure(height=100, width=10, corner_radius=5, border_width=2)
173173

174-
self.copyright_label = ctk.CTkLabel(self, text="Copyright © 2025 by Kamil Wiśniewski | Ver. 1.5")
174+
self.copyright_label = ctk.CTkLabel(self, text="Copyright © 2025 by Kamil Wiśniewski | Ver. 1.6.0")
175175
self.copyright_label.grid(sticky="se", row=4, column=0, columnspan=2, padx=5)
176176
self.copyright_label.configure(font=("", 10))
177177

@@ -181,15 +181,15 @@ def setup_ui(self):
181181
self.custom_model_name.bind("<Return>", self.custom_model)
182182
self.input_field.bind("<Control-a>", self.keybinds)
183183
self.input_field.bind("<Return>", self.keybinds)
184-
184+
self.host_url.bind("<Return>", self.check_existing_models)
185185

186186
self.check_existing_models()
187187

188188
# functions
189189
def keybinds(self, event):
190190
if event.keysym == "Return" and not (event.state & 0x1): # Enter
191191
if self.send_button.cget("state") == "normal":
192-
self.send_message()
192+
self.send_message_thread()
193193
return "break"
194194

195195
elif event.keysym == "Return" and (event.state & 0x1): # Shift+Enter
@@ -220,7 +220,7 @@ def insert_text(self, content):
220220
self.chat_output.see("end")
221221

222222
except Exception as e:
223-
error_log.append(f"Error: {e}")
223+
error_log.append(f"Autoscroll error: {e}")
224224
self.open_error_logs()
225225

226226
def on_model_change(self, *args):
@@ -234,6 +234,7 @@ def custom_model(self, event):
234234
return
235235

236236
self.custom_model_name.delete("0", "end")
237+
self.check_existing_models()
237238
model_list.append(cModel)
238239
self.refresh_model_list()
239240
self.model_menu_var.set(cModel)
@@ -301,10 +302,9 @@ def stop_response(self):
301302
last_key = len(self.messages) - 1
302303

303304
if hasattr(self, "response") and self.response:
304-
self.response.close()
305-
del self.response
305+
self.response = None
306306
self.messages.pop(last_key)
307-
self.insert_text("\n\n[AI] : Chat stopped.\n\n")
307+
self.insert_text("\n\n[AI] : Response canceled.")
308308

309309
self.hide_progress()
310310
self.send_button.configure(state="normal")
@@ -324,7 +324,8 @@ def hide_progress(self):
324324
self.stop_button.configure(state="disabled")
325325

326326
# checks for installed LLMs.
327-
def check_existing_models(self) -> None:
327+
def check_existing_models(self, *args) -> None:
328+
model_list.clear()
328329
try:
329330
api_url = self.host_url.get().rstrip('/api/chat')
330331
response = requests.get(
@@ -337,22 +338,31 @@ def check_existing_models(self) -> None:
337338
self.refresh_model_list()
338339

339340
except requests.exceptions.RequestException as e:
340-
error_log.append(f"def check_existing_models: {e}")
341+
model_list.clear()
342+
self.model_menu_var.set("")
343+
error_log.append(f"Failed to fetch models: {e}")
341344
self.open_error_logs()
342345

343-
def send_message(self):
346+
# threading for app to not freeze on first message.
347+
# It used to freeze for a while when Ollama was setting up a server
348+
def send_message_thread(self):
344349
question = self.input_field.get("1.0", "end").strip()
345350
if not question:
346351
return
347352

348-
model_name = self.model_menu_var.get()
349-
350353
self.insert_text(f"[You] :\n{question}\n\n")
351354
self.input_field.delete("1.0", "end") # clear input field
352355

353356
self.messages.append({"role": "user", "content": question})
354357
self.send_button.configure(state="disabled") # disable send button
355358

359+
t1 = thr.Thread(target=self.send_message)
360+
t1.start()
361+
362+
self.show_progress()
363+
364+
def send_message(self):
365+
model_name = self.model_menu_var.get()
356366

357367
payload = {
358368
"model": model_name,
@@ -368,15 +378,16 @@ def send_message(self):
368378

369379
if response.status_code == 200:
370380
self.insert_text("[AI] :\n")
371-
self.show_progress()
372-
#self.update()
373381

374382
# full_reply "", needed for AI to remember the context of messages.
375383
# Without this, every new message is considered as new chat or whatever.
376384
full_reply= ""
377385
try:
378386
for line in response.iter_lines(decode_unicode=True):
379-
if line.strip():
387+
if self.response is None: # stop response
388+
break
389+
390+
elif line.strip():
380391
try:
381392
data = json.loads(line)
382393
content = data.get("message", {}).get("content", "")
@@ -385,28 +396,28 @@ def send_message(self):
385396
self.update()
386397

387398
except json.JSONDecodeError:
388-
error_log.append(f"def send_message: {line}")
399+
error_log.append(f"Failed to Decode line: {line}")
389400
self.open_error_logs()
390401
continue
391402

392403
self.messages.append({"role": "assistant", "content": full_reply})
393404
self.insert_text("\n\n")
394405

395-
# YES, IT WILL SCREAM AN ERROR EACH TIME YOU CANCEL THE CHAT.
396406
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?
407+
error_log.append(f"Failed to get response: {e}")
408+
self.open_error_logs()
399409

400410
else:
401-
error_log.append(f"def send_message: {response.status_code}")
411+
error_log.append(f"Failed to send message, Status Code: {response.status_code}")
402412
self.open_error_logs()
403413

404414
except requests.exceptions.RequestException as e:
405-
error_log.append(f"def send_message: {e}")
415+
error_log.append(f"Request error: {e}")
406416
self.open_error_logs()
407417

408-
self.send_button.configure(state="normal")
409-
self.hide_progress()
418+
finally:
419+
self.send_button.configure(state="normal")
420+
self.hide_progress()
410421

411422
ollama_gui = OllamaGUIChat()
412423
ollama_gui.mainloop()

0 commit comments

Comments
 (0)