File size: 5,791 Bytes
49dbd67 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
"""
Simple chat GUI for the project using customtkinter.
Features:
- Chat history area
- Input field + Send button
- Clear history button
- Status label to show when the agent is thinking
- Uses a background thread to call agent.run so the UI stays responsive
Usage: python agi_gui.py
This file expects the project-local Agent and AgentConfig to exist at
`agi.sophos` and `agi.agent_config`, and `all_tools` at `agi.sophos_tools`.
If `customtkinter` is not installed, the UI will show a helpful error.
"""
import threading
import queue
import time
import traceback
try:
import customtkinter as ctk
except Exception:
ctk = None
from datetime import datetime
if ctk:
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
def format_message(role: str, text: str) -> str:
ts = datetime.now().strftime("%H:%M:%S")
return f"[{ts}] {role}: {text}\n\n"
class AGIChatApp:
def __init__(self, root):
self.root = root
self.root.title("AGI Chat")
self.root.geometry("800x600")
# message queue for thread -> UI communication
self._q = queue.Queue()
# top frame: history
self.history = ctk.CTkTextbox(master=root, width=760, height=420)
self.history.configure(state="disabled")
self.history.pack(padx=20, pady=(20, 6))
# status and controls
ctrl_frame = ctk.CTkFrame(master=root)
ctrl_frame.pack(fill="x", padx=20, pady=(0, 6))
self.status_label = ctk.CTkLabel(master=ctrl_frame, text="Ready")
self.status_label.pack(side="left", padx=(8, 6))
clear_btn = ctk.CTkButton(master=ctrl_frame, text="Clear", width=80, command=self.clear_history)
clear_btn.pack(side="right", padx=(6, 8))
# bottom frame: entry and send
bottom = ctk.CTkFrame(master=root)
bottom.pack(fill="x", padx=20, pady=(0, 20))
self.entry = ctk.CTkEntry(master=bottom, placeholder_text="Type your message here...")
self.entry.pack(side="left", fill="x", expand=True, padx=(6, 6), pady=6)
self.entry.bind("<Return>", self._on_enter)
send_btn = ctk.CTkButton(master=bottom, text="Send", width=120, command=self.on_send)
send_btn.pack(side="right", padx=(6, 6), pady=6)
# Agent will be lazy-initialized on first send to avoid long startup at UI open
self.agent = None
self.agent_lock = threading.Lock()
# periodically poll the queue for results from worker threads
self.root.after(100, self._poll_queue)
def _append_history(self, text: str):
self.history.configure(state="normal")
self.history.insert("end", text)
self.history.see("end")
self.history.configure(state="disabled")
def clear_history(self):
self.history.configure(state="normal")
self.history.delete("0.0", "end")
self.history.configure(state="disabled")
self.status_label.configure(text="Cleared")
def _on_enter(self, event):
# prevent the default newline insertion
self.on_send()
return "break"
def on_send(self):
user_text = self.entry.get().strip()
if not user_text:
return
self.entry.delete(0, "end")
self._append_history(format_message("User", user_text))
self.status_label.configure(text="Thinking...")
# start background thread to call agent
t = threading.Thread(target=self._background_agent_call, args=(user_text,), daemon=True)
t.start()
def _init_agent(self):
# only run import and initialization once, in guarded block
if self.agent is not None:
return
try:
# local project imports
from agi.sophos import Agent
from agi.agent_config import AgentConfig
from agi.sophos_tools import all_tools
config = AgentConfig(
name="Sophos-GUI",
instructions="You are an AI agent.",
max_iterations=30,
max_tokens=1500,
temperature=0.7,
verbose=False,
enable_memory=True,
memory_limit=5,
)
toolbox = all_tools()
# instantiate Agent
self.agent = Agent(config=config, tools=toolbox)
self._append_history(format_message("System", "Agent initialized."))
except Exception as e:
tb = traceback.format_exc()
self._append_history(format_message("Error", f"Failed to initialize agent: {e}\n{tb}"))
self.status_label.configure(text="Agent init failed")
def _background_agent_call(self, prompt: str):
# ensure agent is ready
with self.agent_lock:
if self.agent is None:
self._init_agent()
if self.agent is None:
# initialization failed
self._q.put(("error", "Agent not available"))
return
try:
start = time.time()
# call agent.run which may be blocking
resp = self.agent.run(prompt)
elapsed = time.time() - start
self._q.put(("response", resp, elapsed))
except Exception as e:
tb = traceback.format_exc()
self._q.put(("error", f"{e}\n{tb}"))
def _poll_queue(self):
try:
while True:
item = self._q.get_nowait()
if not item:
continue
kind = item[0]
if kind == "response":
_, resp, elapsed = item
self._append_history(format_message("Agent", resp))
self.status_label.configure(text=f"Done ({elapsed:.1f}s)")
elif kind == "error":
_, err = item
self._append_history(format_message("Error", str(err)))
self.status_label.configure(text="Error")
else:
# unknown message
self._append_history(format_message("Debug", str(item)))
except queue.Empty:
pass
finally:
self.root.after(100, self._poll_queue)
def main():
if ctk is None:
print("customtkinter is not installed. Install it with: pip install customtkinter")
# Fallback to a basic tkinter popup that informs the user
try:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
root.withdraw()
messagebox.showerror("Missing dependency", "customtkinter is not installed.\nRun: pip install customtkinter")
except Exception:
pass
return
root = ctk.CTk()
app = AGIChatApp(root)
root.mainloop()
if __name__ == "__main__":
main()
|