Skip to content

Commit

Permalink
Merge pull request #22 from akashjss/main
Browse files Browse the repository at this point in the history
UI update
  • Loading branch information
AmberSahdev authored Nov 10, 2024
2 parents e2b1ed6 + 227bfb4 commit eb5a40b
Showing 1 changed file with 92 additions and 51 deletions.
143 changes: 92 additions & 51 deletions app/ui.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import threading
import tkinter as tk
import webbrowser
from multiprocessing import Queue
from pathlib import Path
from tkinter import ttk

import speech_recognition as sr
import ttkbootstrap as ttk
from PIL import Image, ImageTk

from llm import DEFAULT_MODEL_NAME
Expand All @@ -27,7 +26,7 @@ def run(self) -> None:
def display_current_status(self, text: str):
self.main_window.update_message(text)

class AdvancedSettingsWindow(tk.Toplevel):
class AdvancedSettingsWindow(ttk.Toplevel):
"""
Self-contained settings sub-window for the UI
"""
Expand All @@ -36,8 +35,8 @@ def __init__(self, parent):
super().__init__(parent)
self.title('Advanced Settings')
self.minsize(300, 300)
self.create_widgets()
self.settings = Settings()
self.create_widgets()

# Populate UI
settings_dict = self.settings.get_dict()
Expand All @@ -53,8 +52,8 @@ def __init__(self, parent):

def create_widgets(self) -> None:
# Radio buttons for model selection
tk.Label(self, text='Select Model:').pack(pady=10, padx=10)
self.model_var = tk.StringVar(value='custom') # default selection
ttk.Label(self, text='Select Model:', bootstyle="primary").pack(pady=10, padx=10)
self.model_var = ttk.StringVar(value='custom') # default selection

# Create a frame to hold the radio buttons
radio_frame = ttk.Frame(self)
Expand All @@ -67,25 +66,26 @@ def create_widgets(self) -> None:
('Custom (Specify Settings Below)', 'custom')
]
for text, value in models:
ttk.Radiobutton(radio_frame, text=text, value=value, variable=self.model_var).pack(anchor=tk.W)
ttk.Radiobutton(radio_frame, text=text, value=value, variable=self.model_var, bootstyle="info").pack(
anchor=ttk.W)

label_base_url = tk.Label(self, text='Custom OpenAI-Like API Model Base URL')
label_base_url = ttk.Label(self, text='Custom OpenAI-Like API Model Base URL', bootstyle="secondary")
label_base_url.pack(pady=10)

# Entry for Base URL
self.base_url_entry = ttk.Entry(self, width=30)
self.base_url_entry.pack()

# Model Label
label_model = tk.Label(self, text='Custom Model Name:')
label_model = ttk.Label(self, text='Custom Model Name:', bootstyle="secondary")
label_model.pack(pady=10)

# Entry for Model
self.model_entry = ttk.Entry(self, width=30)
self.model_entry.pack()

# Save Button
save_button = ttk.Button(self, text='Save Settings', command=self.save_button)
save_button = ttk.Button(self, text='Save Settings', bootstyle="success", command=self.save_button)
save_button.pack(pady=20)

def save_button(self) -> None:
Expand All @@ -99,7 +99,7 @@ def save_button(self) -> None:
self.settings.save_settings_to_file(settings_dict)
self.destroy()

class SettingsWindow(tk.Toplevel):
class SettingsWindow(ttk.Toplevel):
"""
Self-contained settings sub-window for the UI
"""
Expand All @@ -108,6 +108,7 @@ def __init__(self, parent):
super().__init__(parent)
self.title('Settings')
self.minsize(300, 450)
self.available_themes = ['darkly', 'cyborg', 'journal', 'solar', 'superhero']
self.create_widgets()

self.settings = Settings()
Expand All @@ -121,88 +122,124 @@ def __init__(self, parent):
self.browser_combobox.set(settings_dict['default_browser'])
if 'play_ding_on_completion' in settings_dict:
self.play_ding.set(1 if settings_dict['play_ding_on_completion'] else 0)
if 'custom_llm_instructions' in settings_dict:
if 'custom_llm_instructions':
self.llm_instructions_text.insert('1.0', settings_dict['custom_llm_instructions'])
self.theme_combobox.set(settings_dict.get('theme', 'superhero'))

def create_widgets(self) -> None:
# Label for API Key
label_api = tk.Label(self, text='OpenAI API Key:')
# API Key Widgets
label_api = ttk.Label(self, text='OpenAI API Key:', bootstyle="info")
label_api.pack(pady=10)

# Entry for API Key
self.api_key_entry = ttk.Entry(self, width=30)
self.api_key_entry.pack()

# Label for Browser Choice
label_browser = tk.Label(self, text='Choose Default Browser:')
label_browser = ttk.Label(self, text='Choose Default Browser:', bootstyle="info")
label_browser.pack(pady=10)

# Dropdown for Browser Choice
self.browser_var = tk.StringVar()
self.browser_var = ttk.StringVar()
self.browser_combobox = ttk.Combobox(self, textvariable=self.browser_var,
values=['Safari', 'Firefox', 'Chrome'])
self.browser_combobox.pack(pady=5)
self.browser_combobox.set('Choose Browser')

# Label for Custom LLM Instructions
label_llm = tk.Label(self, text='Custom LLM Instructions:')
label_llm = ttk.Label(self, text='Custom LLM Instructions:', bootstyle="info")
label_llm.pack(pady=10)

# Text Box for Custom LLM Instructions
self.llm_instructions_text = tk.Text(self, height=5, width=40)
self.llm_instructions_text.pack(pady=5)
self.llm_instructions_text = ttk.Text(self, height=10, width=50)
self.llm_instructions_text.pack(padx=(10, 10), pady=(0, 10))

# Checkbox for "Play Ding" option
self.play_ding = tk.IntVar()
play_ding_checkbox = ttk.Checkbutton(self, text="Play Ding on Completion", variable=self.play_ding)
self.play_ding = ttk.IntVar()
play_ding_checkbox = ttk.Checkbutton(self, text="Play Ding on Completion", variable=self.play_ding,
bootstyle="round-toggle")
play_ding_checkbox.pack(pady=10)

# Theme Selection Widgets
label_theme = ttk.Label(self, text='UI Theme:', bootstyle="info")
label_theme.pack()
self.theme_var = ttk.StringVar()
self.theme_combobox = ttk.Combobox(self, textvariable=self.theme_var, values=self.available_themes,
state="readonly")
self.theme_combobox.pack(pady=5)
self.theme_combobox.set('superhero')
# Add binding for immediate theme change
self.theme_combobox.bind('<<ComboboxSelected>>', self.on_theme_change)

# Save Button
save_button = ttk.Button(self, text='Save Settings', command=self.save_button)
save_button.pack(pady=(10, 0))
save_button = ttk.Button(self, text='Save Settings', bootstyle="success", command=self.save_button)
save_button.pack(pady=(10, 5))

# Button to open Advanced Settings
advanced_settings_button = ttk.Button(self, text='Advanced Settings', command=self.open_advanced_settings)
advanced_settings_button = ttk.Button(self, text='Advanced Settings', bootstyle="info",
command=self.open_advanced_settings)
advanced_settings_button.pack(pady=(0, 10))

# Hyperlink Label
link_label = tk.Label(self, text='Instructions', fg='#499CE4')
link_label = ttk.Label(self, text='Instructions', bootstyle="primary")
link_label.pack()
link_label.bind('<Button-1>', lambda e: open_link(
'https://github.com/AmberSahdev/Open-Interface?tab=readme-ov-file#setup-%EF%B8%8F'))

# Check for updates Label
update_label = tk.Label(self, text='Check for Updates', fg='#499CE4', font=('Helvetica', 10))
update_label = ttk.Label(self, text='Check for Updates', bootstyle="primary")
update_label.pack()
update_label.bind('<Button-1>', lambda e: open_link(
'https://github.com/AmberSahdev/Open-Interface/releases/latest'))

# Version Label
version_label = tk.Label(self, text=f'Version: {str(version)}', font=('Helvetica', 10))
version_label = ttk.Label(self, text=f'Version: {str(version)}', font=('Helvetica', 10))
version_label.pack(side="bottom", pady=10)

def on_theme_change(self, event=None) -> None:
# Apply theme immediately when selected
theme = self.theme_var.get()
self.master.change_theme(theme)

def save_button(self) -> None:
theme = self.theme_var.get()
api_key = self.api_key_entry.get().strip()
default_browser = self.browser_var.get()
settings_dict = {
'api_key': api_key,
'default_browser': default_browser,
'play_ding_on_completion': bool(self.play_ding.get()),
'custom_llm_instructions': self.llm_instructions_text.get("1.0", "end-1c").strip()
'custom_llm_instructions': self.llm_instructions_text.get("1.0", "end-1c").strip(),
'theme': theme
}

# Remove redundant theme change since it's already applied
self.settings.save_settings_to_file(settings_dict)
self.destroy()

def open_advanced_settings(self):
# Open the advanced settings window
UI.AdvancedSettingsWindow(self)

class MainWindow(tk.Tk):
class MainWindow(ttk.Window):
def change_theme(self, theme_name: str) -> None:
self.style.theme_use(theme_name)

def __init__(self):
super().__init__()
settings = Settings()
settings_dict = settings.get_dict()
theme = settings_dict.get('theme', 'superhero')

super().__init__(themename=theme)
self.title('Open Interface')
self.minsize(420, 250)
window_width = 420
window_height = 250
self.minsize(window_width, window_height)

# Set the geometry of the window
# Calculate position for bottom right corner
screen_width = self.winfo_screenwidth()
x_position = screen_width - window_width - 10 # 10px margin from the right edge
y_position = 50 # 50px margin from the bottom edge
self.geometry(f'{window_width}x{window_height}+{x_position}+{y_position}')

# PhotoImage object needs to persist as long as the app does, hence it's a class object.
path_to_icon_png = Path(__file__).resolve().parent.joinpath('resources', 'icon.png')
Expand All @@ -224,45 +261,49 @@ def create_widgets(self) -> None:
# Creates and arranges the UI elements
# Frame
frame = ttk.Frame(self, padding='10 10 10 10')
frame.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N, tk.S))
frame.grid(column=0, row=0, sticky=(ttk.W, ttk.E, ttk.N, ttk.S))
frame.columnconfigure(0, weight=1)

logo_label = tk.Label(frame, image=self.logo_img)
logo_label.grid(column=0, row=0, sticky=tk.W, pady=(10, 20))
logo_label = ttk.Label(frame, image=self.logo_img)
logo_label.grid(column=0, row=0, sticky=ttk.W, pady=(10, 20))

# Heading Label
heading_label = tk.Label(frame, text='What would you like me to do?', font=('Helvetica', 16),
wraplength=300)
heading_label.grid(column=0, row=1, columnspan=3, sticky=tk.W)
heading_label = ttk.Label(frame, text='What would you like me to do?', font=('Helvetica', 16),
bootstyle="primary",
wraplength=300)
heading_label.grid(column=0, row=1, columnspan=3, sticky=ttk.W)

# Entry widget
self.entry = ttk.Entry(frame, width=30)
self.entry.grid(column=0, row=2, sticky=(tk.W, tk.E))
self.entry.grid(column=0, row=2, sticky=(ttk.W, ttk.E))

# Bind the Enter key to the submit function
self.entry.bind("<Return>", lambda event: self.execute_user_request())
self.entry.bind("<KP_Enter>", lambda event: self.execute_user_request())

# Submit Button
button = ttk.Button(frame, text='Submit', command=self.execute_user_request)
button = ttk.Button(frame, text='Submit', bootstyle="success", command=self.execute_user_request)
button.grid(column=2, row=2)

# Mic Button
mic_button = tk.Button(frame, image=self.mic_icon, command=self.start_voice_input_thread, borderwidth=0,
highlightthickness=0)
mic_button = ttk.Button(frame, image=self.mic_icon, bootstyle="link", command=self.start_voice_input_thread)
mic_button.grid(column=1, row=2, padx=(0, 5))

# Settings Button
settings_button = ttk.Button(self, text='Settings', command=self.open_settings)
settings_button = ttk.Button(self, text='Settings', bootstyle="info-outline", command=self.open_settings)
settings_button.place(relx=1.0, rely=0.0, anchor='ne', x=-5, y=5)

# Stop Button
stop_button = ttk.Button(self, text='Stop', command=self.stop_previous_request)
stop_button = ttk.Button(self, text='Stop', bootstyle="danger-outline", command=self.stop_previous_request)
stop_button.place(relx=1.0, rely=1.0, anchor='se', x=-10, y=-10)

# Text display for echoed input
self.input_display = tk.Label(frame, text='', font=('Helvetica', 16), wraplength=400)
self.input_display.grid(column=0, row=3, columnspan=3, sticky=tk.W)
self.input_display = ttk.Label(frame, text='', font=('Helvetica', 16), wraplength=400)
self.input_display.grid(column=0, row=3, columnspan=3, sticky=ttk.W)

# Text display for additional messages
self.message_display = tk.Label(frame, text='', font=('Helvetica', 14), wraplength=400)
self.message_display.grid(column=0, row=6, columnspan=3, sticky=tk.W)
self.message_display = ttk.Label(frame, text='', font=('Helvetica', 14), wraplength=400)
self.message_display.grid(column=0, row=6, columnspan=3, sticky=ttk.W)

def open_settings(self) -> None:
UI.SettingsWindow(self)
Expand All @@ -277,7 +318,7 @@ def display_input(self) -> str:
self.input_display['text'] = f'{user_input}'

# Clear the entry widget
self.entry.delete(0, tk.END)
self.entry.delete(0, ttk.END)

return user_input.strip()

Expand Down Expand Up @@ -307,7 +348,7 @@ def voice_input(self) -> None:
audio = recognizer.listen(source, timeout=4)
try:
text = recognizer.recognize_google(audio)
self.entry.delete(0, tk.END)
self.entry.delete(0, ttk.END)
self.entry.insert(0, text)
self.update_message('')
except sr.UnknownValueError:
Expand Down

0 comments on commit eb5a40b

Please sign in to comment.