|
|
|
|
|
""" |
|
|
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") |
|
|
|
|
|
|
|
|
self._q = queue.Queue() |
|
|
|
|
|
|
|
|
self.history = ctk.CTkTextbox(master=root, width=760, height=420) |
|
|
self.history.configure(state="disabled") |
|
|
self.history.pack(padx=20, pady=(20, 6)) |
|
|
|
|
|
|
|
|
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 = 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) |
|
|
|
|
|
|
|
|
self.agent = None |
|
|
self.agent_lock = threading.Lock() |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
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...") |
|
|
|
|
|
|
|
|
t = threading.Thread(target=self._background_agent_call, args=(user_text,), daemon=True) |
|
|
t.start() |
|
|
|
|
|
def _init_agent(self): |
|
|
|
|
|
if self.agent is not None: |
|
|
return |
|
|
try: |
|
|
|
|
|
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() |
|
|
|
|
|
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): |
|
|
|
|
|
with self.agent_lock: |
|
|
if self.agent is None: |
|
|
self._init_agent() |
|
|
|
|
|
if self.agent is None: |
|
|
|
|
|
self._q.put(("error", "Agent not available")) |
|
|
return |
|
|
|
|
|
try: |
|
|
start = time.time() |
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
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() |
|
|
|