Open Floor Protocol   ›   Tutorials   ›   Building a Client   ›   with the Python SDK - GUI

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
Open Floor GUI Client with the Python SDK
Open Floor GUI Client with the Python SDK

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 toolkit
  • ttk - Themed widgets for better appearance
  • scrolledtext - Text areas with built-in scrollbars
  • threading - For non-blocking operations
  • Our reused OpenFloorClient from 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 users
  • append_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() and format_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:

  1. Validate inputs
  2. Clear the input field immediately
  3. Show user message in chat
  4. Send to agent in background thread using our reused client's send_message() method
  5. Display response when received
  6. 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:

  1. Load the window - Should open with agent URL pre-filled
  2. Get manifest - Click "Get Manifest" to see agent capabilities
  3. Start chatting - Type a message and press Enter
  4. 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