Building a GUI Client with the Python SDK
In this guide, we'll build a graphical user interface for interacting with Open Floor Protocol agents using Python and tkinter. The GUI client will provide a user-friendly window for discovering agent capabilities and having conversations, perfect for extended use and non-technical users.
TL;DR: We'll create a desktop application with a clean interface for chatting with Open Floor agents by reusing our CLI client logic. The GitHub repository of the GUI client.
What We're Building
Our GUI client will have these main features:
- Agent URL Input: Easy way to enter and change agent URLs
- Manifest Display: Scrollable area showing agent capabilities
- Chat Interface: Real-time conversation window with message history
- Status Updates: Visual feedback on what the client is doing

Initial Setup
First, let's set up our project by creating the project folder and installing the required packages:
mkdir openfloor-gui-client
cd openfloor-gui-client
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
Create a requirements.txt file with the following content:
--extra-index-url https://test.pypi.org/simple/
events==0.5
jsonpath-ng>=1.5.0
openfloor
requests
Then install the dependencies:
pip install -r requirements.txt
Note: We're using tkinter which comes built-in with Python, so no additional GUI packages needed!
Step 1: Reusing the CLI Client Logic
Instead of rebuilding the Open Floor communication logic, we'll reuse the client from our CLI tutorial. Copy the openfloor_client.py file from the CLI tutorial into this directory.
Step 2: Building the GUI Interface
Now let's create the graphical interface. Create a new file gui_client.py.
Step 2.1: Add imports and setup
#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import threading
from openfloor_client import OpenFloorClient
Why these imports?
tkinter- Python's built-in GUI toolkitttk- Themed widgets for better appearancescrolledtext- Text areas with built-in scrollbarsthreading- For non-blocking operations- Our reused
OpenFloorClientfrom the CLI tutorial
Step 2.2: Create the main GUI class
class OpenFloorGUI:
"""GUI interface for Open Floor agents using tkinter"""
def __init__(self, root):
self.root = root
self.client = OpenFloorClient()
self.setup_ui()
What this sets up:
- Takes the main tkinter window as input
- Creates an instance of our reused Open Floor client
- Calls the UI setup method
Step 2.3: Build the main window layout
def setup_ui(self):
"""Set up the user interface"""
self.root.title("🤖 Open Floor Protocol Client")
self.root.geometry("800x600")
# Create main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights for resizing
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(2, weight=1)
main_frame.rowconfigure(4, weight=1)
Layout setup:
- Sets window title and size
- Creates a main frame with padding
- Configures grid weights so the window resizes properly
Step 2.4: Add the agent URL section
# Agent URL section
ttk.Label(main_frame, text="Agent URL:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
url_frame = ttk.Frame(main_frame)
url_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=(0, 5))
url_frame.columnconfigure(0, weight=1)
self.url_var = tk.StringVar(value="https://secondassistant.pythonanywhere.com/verity")
self.url_entry = ttk.Entry(url_frame, textvariable=self.url_var, width=50)
self.url_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
self.manifest_btn = ttk.Button(url_frame, text="Get Manifest", command=self.get_manifest)
self.manifest_btn.grid(row=0, column=1)
URL input features:
- Label to describe the field
- Button to trigger manifest fetching
- Pre-filled with a working agent URL for testing
Step 2.5: Add the manifest display area
# Manifest display section
ttk.Label(main_frame, text="Agent Capabilities:").grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=(10, 5))
self.manifest_text = scrolledtext.ScrolledText(main_frame, height=8, wrap=tk.WORD)
self.manifest_text.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
self.manifest_text.insert('1.0', 'Click "Get Manifest" to see agent capabilities...')
Manifest display features:
- ScrolledText widget for large manifests
- Word wrapping for readability
- Placeholder text to guide users
Step 2.6: Add the chat interface
# Chat section
ttk.Label(main_frame, text="Chat with Agent:").grid(row=3, column=0, columnspan=2, sticky=tk.W, pady=(0, 5))
self.chat_text = scrolledtext.ScrolledText(main_frame, height=12, wrap=tk.WORD)
self.chat_text.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
self.chat_text.insert('1.0', 'Chat messages will appear here...\n\n')
self.chat_text.config(state=tk.DISABLED)
Chat display setup:
- Larger text area for conversation history
- Disabled by default (we'll enable it when adding messages)
- Placeholder text to show what it's for
Step 2.7: Add the message input section
# Chat input section
input_frame = ttk.Frame(main_frame)
input_frame.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(5, 0))
input_frame.columnconfigure(0, weight=1)
self.message_var = tk.StringVar()
self.message_entry = ttk.Entry(input_frame, textvariable=self.message_var, width=60)
self.message_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
self.message_entry.bind('<Return>', self.send_message_event)
self.send_btn = ttk.Button(input_frame, text="Send", command=self.send_message)
self.send_btn.grid(row=0, column=1)
Input section features:
- Entry field for typing messages
- Send button
- Enter key binding for quick sending
- Proper grid layout with resizing
Step 2.8: Add status bar and focus
# Status bar
self.status_var = tk.StringVar(value="Ready")
status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, padding="5")
status_bar.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
# Focus on message entry
self.message_entry.focus()
Finishing touches:
- Status bar to show what's happening
- Focus on message entry so users can start typing immediately
Step 3: Implementing GUI Functionality
Now let's add the methods that make the interface work.
Step 3.1: Add helper methods
def update_status(self, message: str):
"""Update the status bar"""
self.status_var.set(message)
self.root.update_idletasks()
def append_to_chat(self, text: str):
"""Append text to the chat display"""
self.chat_text.config(state=tk.NORMAL)
self.chat_text.insert(tk.END, text + '\n')
self.chat_text.config(state=tk.DISABLED)
self.chat_text.see(tk.END)
Helper method purposes:
update_status: Provides visual feedback to usersappend_to_chat: Adds messages to the chat area and scrolls to the bottom
Step 3.2: Implement manifest fetching
def get_manifest(self):
"""Fetch and display the agent manifest"""
url = self.url_var.get().strip()
if not url:
messagebox.showerror("Error", "Please enter an agent URL")
return
def fetch_manifest():
self.update_status("Fetching manifest...")
self.manifest_btn.config(state='disabled')
try:
manifests = self.client.get_manifest(url)
if manifests is None:
self.manifest_text.delete('1.0', tk.END)
self.manifest_text.insert('1.0', 'Failed to fetch manifest. Check the URL and try again.')
self.update_status("Failed to fetch manifest")
else:
formatted_manifest = self.client.format_manifest(manifests)
self.manifest_text.delete('1.0', tk.END)
self.manifest_text.insert('1.0', formatted_manifest)
self.update_status(f"Manifest loaded for {url}")
except Exception as e:
self.manifest_text.delete('1.0', tk.END)
self.manifest_text.insert('1.0', f'Error: {e}')
self.update_status("Error fetching manifest")
finally:
self.manifest_btn.config(state='normal')
# Run in background thread to avoid freezing UI
threading.Thread(target=fetch_manifest, daemon=True).start()
Threading for responsiveness:
- Network requests can take time
- Threading prevents UI freezing
- Status updates show progress
- Button is disabled during operation to prevent double-clicks
- Uses our reused client's
get_manifest()andformat_manifest()methods
Step 3.3: Implement message sending
def send_message_event(self, event):
"""Handle Enter key press in message entry"""
self.send_message()
def send_message(self):
"""Send a message to the agent"""
url = self.url_var.get().strip()
message = self.message_var.get().strip()
if not url:
messagebox.showerror("Error", "Please enter an agent URL")
return
if not message:
return
# Clear the input
self.message_var.set("")
# Add user message to chat
self.append_to_chat(f"You: {message}")
def send_and_receive():
self.update_status("Sending message...")
self.send_btn.config(state='disabled')
self.message_entry.config(state='disabled')
try:
response = self.client.send_message(url, message)
if response:
self.append_to_chat(f"Agent: {response}")
self.update_status("Message sent")
else:
self.append_to_chat("Agent: [No response received]")
self.update_status("No response from agent")
except Exception as e:
self.append_to_chat(f"Error: {e}")
self.update_status("Error sending message")
finally:
self.send_btn.config(state='normal')
self.message_entry.config(state='normal')
self.message_entry.focus()
# Run in background thread
threading.Thread(target=send_and_receive, daemon=True).start()
Message sending flow:
- Validate inputs
- Clear the input field immediately
- Show user message in chat
- Send to agent in background thread using our reused client's
send_message()method - Display response when received
- Re-enable controls and focus input
Step 4: Creating the Entry Point
Add the main function to create and run the GUI:
def main():
root = tk.Tk()
app = OpenFloorGUI(root)
root.mainloop()
if __name__ == "__main__":
main()
What this does:
- Creates the main tkinter window
- Initializes our GUI class
- Starts the event loop
Step 5: Testing Your GUI Client
Run your GUI client:
python gui_client.py
Testing steps:
- Load the window - Should open with agent URL pre-filled
- Get manifest - Click "Get Manifest" to see agent capabilities
- Start chatting - Type a message and press Enter
- Try different agents - Change the URL and test other agents
Final Project Structure
Your project should look like this:
openfloor-gui-client/
├── requirements.txt
├── openfloor_client.py # Shared client logic (from CLI tutorial)
└── gui_client.py # GUI interface only
Testing with Sample Agents
Try these example Open Floor agents:
Parrot Agent:
https://kmhhywpw32.us-east-1.awsapprunner.com/
Verification Agent:
https://secondassistant.pythonanywhere.com/verity