> ## Documentation Index
> Fetch the complete documentation index at: https://simplellmfunc.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# TUI

> Build terminal chat interfaces with the @tui decorator

# TUI

The `@tui` decorator wraps a `@llm_chat` agent into a full Textual terminal UI with streaming output, tool visualization, and input handling.

## Basic Usage

```python theme={null}
from SimpleLLMFunc import llm_chat, OpenAICompatible
from SimpleLLMFunc.utils.tui import tui

models = OpenAICompatible.load_from_json_file("provider.json")
llm = models["openrouter"]["openai/gpt-4o"]


@tui
@llm_chat(llm_interface=llm, toolkit=[...], stream=True)
async def agent(message: str, history: list | None = None, _abort_signal=None):
    """A helpful assistant."""
    pass


if __name__ == "__main__":
    agent()
```

Run it and you get a full chat interface in the terminal.

## Features

* **Streaming display** — Model responses appear character-by-character
* **Tool call cards** — Each tool call gets a visual card showing name, arguments, and result
* **Specialized cards** — `execute_code`, `read_file`, `grep`, `sed`, `echo_into` have custom rendering
* **Abort** — `Ctrl+C` cancels the current response
* **Copy** — `Ctrl+Y` copies the full transcript to clipboard
* **Multi-column forks** — When using SelfRef forks, child agents get their own columns

## Slash Commands

| Command                | Action                                    |
| ---------------------- | ----------------------------------------- |
| `/exit`, `/quit`, `/q` | Quit the TUI                              |
| `/copy`, `/copyall`    | Copy transcript to clipboard              |
| `/chat <msg>`          | Send a message (bypasses tool-input mode) |

## Custom Event Hooks

Handle custom events (e.g., PyRepl streaming output) with hooks:

```python theme={null}
from SimpleLLMFunc.hooks.events import CustomEvent
from SimpleLLMFunc.utils.tui import ToolRenderSnapshot


def my_event_hook(event: CustomEvent, snapshot: ToolRenderSnapshot):
    """Process custom events for display."""
    if event.event_name == "kernel_stdout":
        # Handle PyRepl stdout streaming
        pass
    return None


@tui(custom_event_hook=[my_event_hook])
@llm_chat(llm_interface=llm, toolkit=repl.toolset, stream=True)
async def agent(message: str, history: list | None = None, _abort_signal=None):
    """My agent."""
    pass
```

## Decorator Parameters

| Parameter           | Type             | Description                    |
| ------------------- | ---------------- | ------------------------------ |
| `custom_event_hook` | `List[Callable]` | Custom event handler functions |
| `title`             | `str`            | Window title                   |

## ToolRenderSnapshot

Passed to event hooks with context about the current tool card:

```python theme={null}
@dataclass
class ToolRenderSnapshot:
    tool_name: str
    tool_call_id: str
    status: str          # "running", "completed", "error"
    arguments: dict
```

## Keyboard Shortcuts

| Key      | Action                 |
| -------- | ---------------------- |
| `Ctrl+C` | Abort current response |
| `Ctrl+Q` | Quit                   |
| `Ctrl+Y` | Copy transcript        |
| `Enter`  | Send message           |

## When to Use @tui vs Custom UI

Use `@tui` when:

* You want a working chat interface quickly
* The built-in tool cards are sufficient
* You don't need custom widgets or layouts

Build custom when:

* You need confirmation modals for dangerous actions
* You want a status bar with custom metrics
* You need non-chat UI elements (file trees, progress bars)
* You're building a product, not a tool

For custom UIs, consume the `ReactOutput` stream directly and render with Textual or any other framework.
