Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jaypopat/cf_ai_duet/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The ui package implements the Bubble Tea model for Duet’s terminal user interface, managing screens, room interactions, terminal display, and AI sidebar.

Types

Model

Main UI model managing all application state.
type Model struct {
    screen   Screen
    width    int
    height   int
    username string
    clientID string
    
    selected int
    input    textinput.Model
    
    roomID       string
    currentRoom  *room.Room
    terminal     *terminal.Terminal
    termUpdateCh chan struct{}
    termContent  string
    users        []string
    toasts       []toast
    inputMode    InputMode
    cmdInput     textinput.Model
    typingUser   string
    typingTime   time.Time
    
    showAISidebar    bool
    aiViewport       viewport.Model
    aiLoading        bool
    aiSpinner        spinner.Model
    lastPromptOffset int
    
    eventChan chan room.RoomEvent
    
    roomManager *room.Manager
    aiClient    *ai.Client
    renderer    *lipgloss.Renderer
    styles      *Styles
}

Screen

Enumeration of UI screens:
  • ScreenLaunch: Main menu
  • ScreenCreate: Create room form
  • ScreenJoin: Join room form
  • ScreenRoomCreated: Room created confirmation
  • ScreenRoom: Active room with terminal

InputMode

Terminal input modes:
  • ModeNormal: Direct terminal input
  • ModeAI: AI prompt input (Ctrl+G)
  • ModeSandbox: Sandbox command input (Ctrl+R)

Functions

New

Creates a new UI model instance.
func New(renderer *lipgloss.Renderer, roomManager *room.Manager, username string) *Model
renderer
*lipgloss.Renderer
required
Lipgloss renderer for styled output
roomManager
*room.Manager
required
Room manager instance
username
string
required
SSH username (defaults to “guest” if empty)
Returns: Initialized *Model ready for Bubble Tea Example:
renderer := bubbletea.MakeRenderer(sess)
model := ui.New(renderer, roomManager, sess.User())

Init

Bubble Tea initialization command.
func (m *Model) Init() tea.Cmd
Returns: Tick command for toast expiration

Update

Bubble Tea update function handling messages.
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)
Key Messages:
  • tea.WindowSizeMsg: Resize terminal and viewports
  • tea.KeyMsg: Keyboard input routing
  • terminalUpdateMsg: Terminal content changed
  • roomEventMsg: Room events (join/leave/typing/ai_sync)
  • AIResponseMsg: AI response received
  • SandboxResultMsg: Sandbox command completed

View

Bubble Tea view function rendering the UI.
func (m *Model) View() string
Returns: Rendered screen as string

Message Types

GotoScreenMsg

Navigate to a different screen.
type GotoScreenMsg struct {
    Screen Screen
}

RoomCreatedMsg

Room successfully created.
type RoomCreatedMsg struct {
    RoomID string
    Room   *room.Room
}

RoomJoinedMsg

Successfully joined a room.
type RoomJoinedMsg struct {
    RoomID string
    Room   *room.Room
}

AIResponseMsg

AI response received.
type AIResponseMsg struct {
    Reply    string
    Messages []AIMessage
}

SandboxResultMsg

Sandbox command output.
type SandboxResultMsg struct {
    Output string
    Cmd    string
}

ToastMsg

Display a toast notification.
type ToastMsg struct {
    Text string
}

ErrorMsg

Error occurred.
type ErrorMsg struct {
    Err error
}

Key Bindings

Launch Screen

  • c / C: Create room
  • J: Join room
  • / k: Move selection up
  • / j: Move selection down
  • Enter: Confirm selection
  • q / Esc: Quit

Room Screen (Normal Mode)

  • Ctrl+G: Open AI prompt
  • Ctrl+R: Open sandbox command
  • Ctrl+A: Toggle AI sidebar
  • Ctrl+J: Scroll AI sidebar down
  • Ctrl+K: Scroll AI sidebar up
  • Ctrl+L: Leave room
  • All other keys: Sent to terminal

AI/Sandbox Input Mode

  • Enter: Submit prompt/command
  • Esc: Cancel and return to normal mode

Layout Configuration

const (
    MinWidthForSidebar  = 120
    MinHeightForSidebar = 24
)
Sidebar visibility and layout adapt to terminal size.

Internal Methods

roomLayout

Calculates layout dimensions.
func (m *Model) roomLayout() (sidebarW, terminalW, aiSidebarW, mainH int)
Returns:
  • sidebarW: User list width (1/6 of screen)
  • terminalW: Terminal width (remaining space)
  • aiSidebarW: AI sidebar width (1/4 if visible, 0 otherwise)
  • mainH: Main content height

startTerminal

Initializes or connects to room terminal.
func (m *Model) startTerminal() tea.Cmd
Behavior:
  • Reuses existing room terminal if available
  • Creates new terminal with room workspace directory
  • Subscribes to terminal updates
  • Stores terminal reference in room for other clients

sendAIMessage

Sends AI prompt to worker.
func (m *Model) sendAIMessage(text string) tea.Cmd
text
string
required
User prompt
Returns: Command that yields AIResponseMsg or ErrorMsg

execSandboxCmd

Executes command in isolated sandbox.
func (m *Model) execSandboxCmd(cmd string) tea.Cmd
cmd
string
required
Shell command to execute
Returns: Command that yields SandboxResultMsg or ErrorMsg

syncAIViewportContent

Rebuild AI viewport from room’s message history.
func (m *Model) syncAIViewportContent()
Use Cases:
  • Late joiners syncing chat history
  • Refreshing after another client sends AI message
  • Responding to “ai_sync” room events

addToast

Displays a temporary notification.
func (m *Model) addToast(text string)
text
string
required
Toast message (visible for 1 second)
Limit: Maximum 3 toasts displayed at once

cleanup

Cleans up resources when leaving room.
func (m *Model) cleanup()
Cleanup:
  • Unsubscribes from terminal updates
  • Leaves room via manager
  • Clears terminal reference
  • Resets room state

Example Usage

// In SSH session handler
func teaHandler(sess ssh.Session) (tea.Model, []tea.ProgramOption) {
    username := sess.User()
    if username == "" {
        username = "guest"
    }
    
    renderer := bubbletea.MakeRenderer(sess)
    model := ui.New(renderer, roomManager, username)
    
    return model, []tea.ProgramOption{
        tea.WithAltScreen(),
    }
}

Build docs developers (and LLMs) love