Skip to content

Commit 76d7c53

Browse files
committed
Update Windows version to v1.5
1 parent 0bbe48c commit 76d7c53

File tree

2 files changed

+157
-55
lines changed

2 files changed

+157
-55
lines changed

App/Windows/OllamaGUIChat.py

Lines changed: 140 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,9 @@
44
import customtkinter as ctk
55
import os
66

7+
78
APP_PATH: str = os.path.dirname(os.path.realpath(__file__))
89
THEME_PATH: str = os.path.join(APP_PATH, "theme", "custom-theme.json")
9-
ctk.set_default_color_theme(THEME_PATH)
10-
11-
url = "http://localhost:11434/api/chat"
12-
13-
model_list = [
14-
"llama3.1",
15-
"llama3.2",
16-
"llama3.3",
17-
"gemma3",
18-
"qwq",
19-
"deepseek-r1",
20-
"phi4",
21-
"phi4-mini",
22-
"mistral",
23-
"moondream",
24-
"starling-lm",
25-
"codellama",
26-
"llama2-uncensored",
27-
"llava",
28-
"granite3.2",
29-
]
3010

3111
class GPLLicense(ctk.CTkToplevel):
3212
def __init__(self):
@@ -53,16 +33,28 @@ def __init__(self):
5333
You should have received a copy of the GNU General Public License
5434
along with this program; if not, see <https://www.gnu.org/licenses/gpl-3.0.en.html>
5535
""",)
56-
self.label.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
36+
self.label.grid(row=0, column=0, sticky="nsew", padx=10)
37+
5738

39+
error_log = []
40+
model_list = []
5841

5942
class OllamaGUIChat(ctk.CTk):
6043
def __init__(self):
44+
45+
try:
46+
ctk.set_default_color_theme(THEME_PATH)
47+
except Exception as e:
48+
ctk.set_default_color_theme("blue")
49+
print(f"Error: {e}")
50+
error_log.append(f"Error: {e}")
51+
6152
super().__init__()
6253

6354
ctk.set_appearance_mode("light")
6455

6556
self.messages = []
57+
self.full_reply = ""
6658
self.gpl_opened = None
6759

6860
self.title("Ollama GUI Chat")
@@ -75,60 +67,96 @@ def __init__(self):
7567

7668
def setup_ui(self):
7769

70+
7871
# top panel buttons
7972
top_frame = ctk.CTkFrame(self)
80-
top_frame.grid(padx=5,pady=2, sticky="nswe")
73+
top_frame.grid(row=0, padx=5,pady=2, sticky="nswe")
8174
top_frame.configure(fg_color="transparent")
8275

83-
#self.theme_button = ctk.CTkButton(top_frame, text="🌗Toogle theme",)
84-
#self.theme_button.grid(row=0, column=1, padx=(0,3))
85-
#self.theme_button.configure(height=20, font=("", 12), width=10)
86-
8776
self.gpl_button = ctk.CTkButton(top_frame, text="GPL License", command=self.open_gpl)
88-
self.gpl_button.grid(row=0, column=2, padx=(0,3))
89-
self.gpl_button.configure(height=20, font=("", 13), width=10,)
77+
self.gpl_button.grid(row=0, column=0, padx=(0,3))
78+
self.gpl_button.configure(height=20, font=("", 13), width=10, border_width=0)
9079

9180
self.model_menu_var = ctk.StringVar()
92-
self.model_menu_var.set(model_list[0])
81+
#self.model_menu_var.set(model_list[0])
9382
self.model_menu_var.trace_add("write", self.on_model_change)
9483
self.model_menu = ctk.CTkOptionMenu(top_frame, variable=self.model_menu_var, values=model_list)
95-
self.model_menu.grid(row=0, column=3, padx=(0,3), pady=(1,0))
84+
self.model_menu.grid(row=0, column=1, padx=(0,3), pady=(1,0))
9685
self.model_menu.configure(height=23, font=("", 12), dynamic_resizing=True,)
9786

9887
self.custom_model_name = ctk.CTkEntry(top_frame,)
99-
self.custom_model_name.grid(row=0, column=4, padx=(0,3),)
88+
self.custom_model_name.grid(row=0, column=2, padx=(0,3),)
10089
self.custom_model_name.configure(height=20, font=("", 12), placeholder_text="Custom model...")
10190

91+
top_frame2 = ctk.CTkFrame(self)
92+
top_frame2.grid(row=0, columnspan=2, padx=5, pady=2, sticky="e")
93+
top_frame2.configure(fg_color="transparent")
94+
95+
self.host_url = ctk.CTkEntry(top_frame2)
96+
self.host_url.grid(row=0, column=0, padx=(20,3), sticky="e")
97+
self.host_url.configure(height=20, width=230, font=("", 12), placeholder_text="Host URL...")
98+
self.host_url.insert(0 ,"http://localhost:11434")
99+
102100
self.theme_var = ctk.StringVar(value="off")
103-
self.theme_switch = ctk.CTkSwitch(self, text="light/dark mode", variable=self.theme_var, onvalue="on", offvalue="off", command=self.theme_modes)
104-
self.theme_switch.grid(row=0, columnspan=2, padx=(0,5), sticky="e")
101+
self.theme_switch = ctk.CTkSwitch(top_frame2, text="light/dark mode", variable=self.theme_var, onvalue="on", offvalue="off", command=self.theme_modes,)
102+
self.theme_switch.grid(row=0, column=1, padx=(0,5), sticky="e")
105103

106104
# mid panel
107-
self.chat_output = ctk.CTkTextbox(self, height=600)
108-
self.chat_output.grid(row=1, columnspan=2, padx=5, pady=(5,2), sticky="nsew",)
109-
self.chat_output.configure(wrap="word", state="disabled",
110-
font=("", 14)
111-
)
105+
self.chat_font_var = ctk.StringVar()
106+
self.chat_font_var.set(value="14")
107+
self.chat_output = ctk.CTkTextbox(self, height=600, cursor="arrow")
108+
self.chat_output.grid(row=1, columnspan=2, padx=5, pady=(5, 2), sticky="nsew",)
109+
self.chat_output.insert("1.0", error_log)
110+
self.chat_output.configure(wrap="word", state="disabled", font=("", int(self.chat_font_var.get())))
111+
112+
mid_frame = ctk.CTkFrame(self)
113+
mid_frame.grid(row=2, columnspan=2, sticky="e")
114+
mid_frame.configure(fg_color="transparent")
115+
116+
mid_frame2 = ctk.CTkFrame(self)
117+
mid_frame2.grid(row=2, sticky="w")
118+
mid_frame2.configure(fg_color="transparent")
119+
120+
self.save_button = ctk.CTkButton(mid_frame, text="Save", command=self.save_chat)
121+
self.save_button.grid(row=2, column=0, padx=5,)
122+
self.save_button.configure(height=10, width=15, corner_radius=5, hover_color="#024f04", border_width=2)
123+
124+
self.load_button = ctk.CTkButton(mid_frame, text="Load", command=self.load_chat)
125+
self.load_button.grid(row=2, column=1, padx=(0,5),)
126+
self.load_button.configure(height=10, width=15, corner_radius=5, hover_color="#1f538d", border_width=2)
127+
128+
self.clear_button = ctk.CTkButton(mid_frame, text="Clear", command=self.clear_chat)
129+
self.clear_button.grid(row=2, column=2, padx=(0,5),)
130+
self.clear_button.configure(height=10, width=10, corner_radius=5, hover_color="#3b0103", border_width=2,)
131+
132+
self.autoscroll_var = ctk.StringVar(value="on")
133+
self.autoscroll_box = ctk.CTkCheckBox(mid_frame2, text="Autoscroll", variable=self.autoscroll_var, onvalue="on", offvalue="off",)
134+
self.autoscroll_box.grid(row=2, column=0, padx=(5,0),)
135+
self.autoscroll_box.configure(font=("", 12), checkbox_width=18, checkbox_height=18)
136+
137+
self.increase_font = ctk.CTkButton(mid_frame2, text=" + ", command=lambda: self.change_font_size(1))
138+
self.increase_font.grid(row=2,column=1,)
139+
self.increase_font.configure(width=10, height=10, corner_radius=5, border_width=2)
140+
141+
self.decrease_font = ctk.CTkButton(mid_frame2, text=" - ", command=lambda: self.change_font_size(-1))
142+
self.decrease_font.grid(row=2,column=2, padx=(0,5))
143+
self.decrease_font.configure(width=10, height=10, corner_radius=5, border_width=2)
112144

113-
self.save_button = ctk.CTkButton(self, text="Save", command=self.save_chat)
114-
self.save_button.grid(row=2, column=0, sticky="e", padx=5)
115-
self.save_button.configure(height=10, width=10, corner_radius=5, hover_color="#024f04", anchor="center", border_width=2)
116145

117-
self.clear_button = ctk.CTkButton(self, text="Clear", command=self.clear_chat)
118-
self.clear_button.grid(row=2, column=1, sticky="e", padx=(0,5))
119-
self.clear_button.configure(height=10, width=10, corner_radius=5, hover_color="#3b0103", anchor="center", border_width=2)
146+
self.progress_bar = ctk.CTkProgressBar(mid_frame2, mode="determinate",)
147+
self.progress_bar.configure(width=150,)
120148

121149
# lower panel
122150
self.input_field = ctk.CTkTextbox(self, height=100)
123151
self.input_field.grid(row=3, column=0, padx=5, pady=(5,5), sticky="nswe",)
124152
self.input_field.configure(wrap="word",)
125153
self.input_field.focus_set()
126154

127-
self.send_button = ctk.CTkButton(self, text="📤", command=self.send_message)
155+
self.send_button = ctk.CTkButton(self, text="Send", command=self.send_message)
128156
self.send_button.grid(row=3, column=1, sticky="we", padx=(0,5), pady=(5,5))
129-
self.send_button.configure(height=100, width=10, corner_radius=5, anchor="center", border_width=2)
157+
self.send_button.configure(height=100, width=10, corner_radius=5, border_width=2)
130158

131-
self.copyright_label = ctk.CTkLabel(self, text="Copyright (c) 2025 by Kamil Wiśniewski")
159+
self.copyright_label = ctk.CTkLabel(self, text="Copyright © 2025 by Kamil Wiśniewski | Ver. 1.5")
132160
self.copyright_label.grid(sticky="se", row=4, column=0, columnspan=2, padx=5)
133161
self.copyright_label.configure(font=("", 10))
134162

@@ -140,11 +168,14 @@ def setup_ui(self):
140168
self.input_field.bind("<Return>", self.keybinds)
141169

142170

171+
self.check_existing_models()
172+
143173
# functions
144174
def keybinds(self, event):
145175
if event.keysym == "Return" and not (event.state & 0x1): # Enter
146-
self.send_message()
147-
return "break"
176+
if self.send_button.cget("state") == "normal":
177+
self.send_message()
178+
return "break"
148179

149180
elif event.keysym == "Return" and (event.state & 0x1): # Shift+Enter
150181
self.input_field.insert("end", "\n")
@@ -154,11 +185,28 @@ def keybinds(self, event):
154185
self.input_field.tag_add("sel", "1.0", "end")
155186
return "break"
156187

188+
def change_font_size(self, increment):
189+
font_size = int(self.chat_font_var.get()) + increment
190+
if font_size < 6:
191+
font_size = 6
192+
elif font_size > 48:
193+
font_size = 48
194+
195+
self.chat_font_var.set(value=f"{font_size}")
196+
self.chat_output.configure(font=("", font_size))
197+
157198
def insert_text(self, content):
158199
self.chat_output.configure(state="normal")
159200
self.chat_output.insert("end", content)
160201
self.chat_output.configure(state="disabled")
161202

203+
try:
204+
if self.autoscroll_var.get() == "on":
205+
self.chat_output.see("end")
206+
207+
except Exception as e:
208+
print(f"Error: {e}")
209+
162210
def on_model_change(self, *args):
163211
self.messages = []
164212
self.insert_text(f"\nModel changed to: {self.model_menu.get()}\n\n")
@@ -202,7 +250,39 @@ def save_chat(self):
202250

203251
if file_path:
204252
with open(file_path, "w") as f:
205-
f.write(self.chat_output.get("1.0", "end"))
253+
for message in self.messages:
254+
f.write(f"{message['role']} : {message['content']}\n")
255+
256+
def load_chat(self):
257+
file_path = filedialog.askopenfilename(filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
258+
with open(file_path, "r") as f:
259+
for line in f.readlines():
260+
self.messages.append({"role": "assistant", "content": line})
261+
262+
def show_progress(self):
263+
self.progress_bar.grid(row=2, column=3, sticky="we")
264+
self.progress_bar.start()
265+
266+
def hide_progress(self):
267+
self.progress_bar.stop()
268+
self.progress_bar.grid_forget()
269+
270+
# checks for installed LLMs.
271+
def check_existing_models(self) -> None:
272+
try:
273+
api_url = self.host_url.get().rstrip('/api/chat')
274+
response = requests.get(
275+
url=api_url + "/api/tags"
276+
)
277+
response.raise_for_status()
278+
data = response.json()
279+
model_list.extend([model["name"] for model in data["models"]])
280+
self.model_menu_var.set(model_list[0])
281+
self.refresh_model_list()
282+
283+
except requests.exceptions.RequestException as e:
284+
print(f"Error while checking models: {e}")
285+
self.insert_text(f"Error while checking models: {e}\n\n")
206286

207287
def send_message(self):
208288
question = self.input_field.get("1.0", "end").strip()
@@ -211,34 +291,38 @@ def send_message(self):
211291

212292
model_name = self.model_menu_var.get()
213293

214-
self.insert_text(f"You: {question}\n\n")
294+
self.insert_text(f"[You] :\n{question}\n\n")
215295
self.input_field.delete("1.0", "end") # clear input field
216296

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

300+
220301
payload = {
221302
"model": model_name,
222303
"messages": self.messages,
223304
"stream": True
224305
}
225306

307+
url = f"{self.host_url.get().rstrip('/api/chat')}/api/chat"
308+
226309
try:
227310
response = requests.post(url, json=payload, stream=True)
228311

229312
if response.status_code == 200:
230-
self.insert_text("AI: ")
231-
self.update()
313+
self.insert_text("[AI] :\n")
314+
#self.update()
232315

233316
# full_reply "", needed for AI to remember the context of messages.
234317
# Without this, every new message is considered as new chat or whatever.
235-
full_reply = ""
318+
full_reply= ""
236319
for line in response.iter_lines(decode_unicode=True):
237320
if line.strip():
238321
try:
239322
data = json.loads(line)
240323
content = data.get("message", {}).get("content", "")
241324
full_reply += content
325+
self.show_progress()
242326
self.insert_text(content)
243327
self.update()
244328

@@ -255,6 +339,7 @@ def send_message(self):
255339
self.insert_text(f"\nError sending request: {e}\n\n")
256340

257341
self.send_button.configure(state="normal")
342+
self.hide_progress()
258343

259344
ollama_gui = OllamaGUIChat()
260345
ollama_gui.mainloop()

App/Windows/theme/custom-theme.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@
3434
"text_color": ["#000", "#00ff04"],
3535
"placeholder_text_color": ["#cdccd9", "gray62"]
3636
},
37+
"CTkCheckBox": {
38+
"corner_radius": 6,
39+
"border_width": 3,
40+
"fg_color": ["#3a7ebf", "#1f538d"],
41+
"border_color": ["#3a3666", "#949A9F"],
42+
"hover_color": ["#6f68bd", "#14375e"],
43+
"checkmark_color": ["#DCE4EE", "gray90"],
44+
"text_color": ["#FFFFFF", "#00ff04"],
45+
"text_color_disabled": ["gray60", "gray45"]
46+
},
3747
"CTkSwitch": {
3848
"corner_radius": 1000,
3949
"border_width": 3,
@@ -45,6 +55,13 @@
4555
"text_color": ["#FFFFFF", "#00ff04"],
4656
"text_color_disabled": ["gray60", "gray45"]
4757
},
58+
"CTkProgressBar": {
59+
"corner_radius": 1000,
60+
"border_width": 0,
61+
"fg_color": ["#939BA2", "#4A4D50"],
62+
"progress_color": ["#3a7ebf", "#00ff04"],
63+
"border_color": ["gray", "gray"]
64+
},
4865
"CTkOptionMenu": {
4966
"corner_radius": 6,
5067
"fg_color": ["#978dfd", "#191a19"],

0 commit comments

Comments
 (0)