# Thread history

> Past conversations, searchable and grouped by day. A native Pyzed component for Python desktop GUIs, with a live preview, code examples and its API.

Source: https://albakore.github.io/pyzed/library/thread-history/

:::tip[In plain words]
The list of your earlier chats, sorted by day, with a box to search them.
:::

`ui.ThreadHistory` is a component for past conversations: a search box and the threads grouped by when they were last updated (Today, Yesterday, This week, This month, Older). Each row shows the title, an optional `detail` (the model or the agent) and a delete button. `threads` are dicts or objects with `id`, `title`, `updated` (a `time.time()` timestamp) and `detail`; `selected` marks the open one. Picking a thread reports `on_open` and the trash button `on_delete`, both with the thread's id in `event.value`. The component keeps the search text itself.

`ui.ThreadHistory` · Category: [AI agents](https://albakore.github.io/pyzed/library/#ai-agents) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#threadhistory)

**Code:**

```python
import time

now = time.time()
pz.container(ui.ThreadHistory(threads=[
    {"id": "1", "title": "Add a login form", "updated": now - 60, "detail": "Claude"},
    {"id": "2", "title": "Fix the failing tests", "updated": now - 900, "detail": "Claude"},
    {"id": "3", "title": "Dark theme for the settings", "updated": now - 86400, "detail": "Gemini CLI"},
    {"id": "4", "title": "Export the table to CSV", "updated": now - 4 * 86400},
    {"id": "5", "title": "First steps", "updated": now - 60 * 86400},
], selected="1", on_open=on_open, on_delete=on_delete), width=380, height=320)
```

:::note[New]
This component was added recently. Tell us how it works for you in the [issue tracker](https://github.com/albakore/pyzed/issues).
:::

## When to use

- In a side panel or a popover of a chat app, to reopen an earlier conversation.

## Usage

```python
import pyzed as pz
from pyzed import ui

class Chats(pz.App):
    def __init__(self):
        self.threads = load_threads()  # [{"id", "title", "updated", "detail"}, ...]
        self.open_id = ""

    def delete(self, ident):
        self.threads = [t for t in self.threads if t["id"] != ident]

    def view(self):
        return ui.ThreadHistory(threads=self.threads, selected=self.open_id,
                                on_open=lambda e: setattr(self, "open_id", e.value),
                                on_delete=lambda e: self.delete(e.value))
```

## Variants

:::note[About these captures]
Each capture is recorded by pyzed itself, off-screen at 2x, in One Dark or One Light to match this site's theme, and cropped to the component so it shows at its real size. The pointer hovers, presses or types into it; a component that does not react to the pointer cycles through a few themes. With reduced motion you see the first frame. Open **Code** under a capture to see the snippet that draws it; free names such as `on_save` stand for your own callbacks.
:::

### State

The search ones are drawn with text typed in the box (the component keeps it).

```python
import time

now = time.time()
threads = [
    {"id": "1", "title": "Add a login form", "updated": now - 60, "detail": "Claude"},
    {"id": "2", "title": "Fix the failing tests", "updated": now - 86400},
    {"id": "3", "title": "Export the table to CSV", "updated": now - 4 * 86400},
]
pz.container(ui.ThreadHistory(threads=threads), width=320, height=260)
```

```python
import time

now = time.time()
threads = [
    {"id": "1", "title": "Add a login form", "updated": now - 60, "detail": "Claude"},
    {"id": "2", "title": "Fix the failing tests", "updated": now - 86400},
    {"id": "3", "title": "Export the table to CSV", "updated": now - 4 * 86400},
]
pz.container(ui.ThreadHistory(threads=threads, selected="2"), width=320, height=260)
```

```python
pz.container(ui.ThreadHistory(threads=[]), width=320, height=110)
```

```python
import time

now = time.time()
threads = [
    {"id": "1", "title": "Add a login form", "updated": now - 60, "detail": "Claude"},
    {"id": "2", "title": "Fix the failing tests", "updated": now - 86400},
    {"id": "3", "title": "Export the table to CSV", "updated": now - 4 * 86400},
]
pz.container(ui.ThreadHistory(threads=threads), width=320, height=160)
```

```python
import time

now = time.time()
threads = [
    {"id": "1", "title": "Add a login form", "updated": now - 60, "detail": "Claude"},
    {"id": "2", "title": "Fix the failing tests", "updated": now - 86400},
    {"id": "3", "title": "Export the table to CSV", "updated": now - 4 * 86400},
]
pz.container(ui.ThreadHistory(threads=threads), width=320, height=110)
```

## Events

Thread history has **2 events of its own**, listed below. Like every widget it also has the **18 common events** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), attached with `.on(...)`: see [Events](https://albakore.github.io/pyzed/guides/events/) and [Events of every component](https://albakore.github.io/pyzed/library/events/).

It is a `pz.Component`, so it also raises `on_mount` and `on_unmount`, and its handlers get a `pz.Event` whose `value` holds the data.

| Event | Handler gets | When |
| --- | --- | --- |
| `on_open` | `(event)` | A thread was picked (`event.value` is its id). |
| `on_delete` | `(event)` | Delete was pressed on a thread. |

**Handler gets** is what Pyzed passes to your callable: `()` means nothing. Any handler can also be a message (a value that is not callable), which goes to `update()`.

### Handling the events

The same events written three ways. **Imperative**: methods of the app change `self`. **Declarative**: the handlers produce messages and one `update()` decides. **Functional**: a pure function draws an immutable state and asks for changes with `set_state(...)`. See [Imperative, declarative and functional](https://albakore.github.io/pyzed/guides/code-styles/).

**Imperative:**

```python
import time

class Chats(pz.App):
    def __init__(self):
        now = time.time()
        self.threads = [{"id": "1", "title": "Add a login form", "updated": now - 60},
                        {"id": "2", "title": "Fix the tests", "updated": now - 900}]
        self.open_id = "1"

    def view(self):                      # event.value is the thread's id
        return ui.ThreadHistory(threads=self.threads, selected=self.open_id,
                                on_open=lambda e: setattr(self, "open_id", e.value),
                                on_delete=lambda e: self.delete(e.value))

    def delete(self, ident):
        self.threads = [t for t in self.threads if t["id"] != ident]
```

**Declarative:**

```python
from dataclasses import dataclass

@dataclass
class OpenThread:
    ident: str

@dataclass
class DeleteThread:
    ident: str

class Chats(pz.App):
    threads = [{"id": "1", "title": "Add a login form", "updated": 0}]
    open_id = ""

    def update(self, message):
        match message:
            case OpenThread(ident):
                self.open_id = ident
            case DeleteThread(ident):
                self.threads = [t for t in self.threads if t["id"] != ident]

    def view(self):
        return ui.ThreadHistory(threads=self.threads, selected=self.open_id,
                                on_open=lambda e: OpenThread(e.value), on_delete=lambda e: DeleteThread(e.value))
```

**Functional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    threads: tuple = ({"id": "1", "title": "Add a login form", "updated": 0},)
    open_id: str = ""

def view(state, set_state):
    return ui.ThreadHistory(
        threads=list(state.threads), selected=state.open_id,
        on_open=lambda e: set_state(open_id=e.value),
        on_delete=lambda e: set_state(threads=tuple(t for t in state.threads if t["id"] != e.value)))
```

## Properties and API

### `ui.ThreadHistory`

```python
class ThreadHistory
```

A component for past conversations: a search box and the threads grouped by day (Today, Yesterday, This week, This month, Older), each with a delete button.

| Property | Default | Description |
| --- | --- | --- |
| `threads` | `None` | Dicts or objects with `id`, `title`, `updated` (a timestamp) and an optional `detail`. |
| `selected` | `""` | The id of the open thread. |
| `theme` | `None` | Theme. |

Events: `on_open`, `on_delete` ([events](#events)).

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#threadhistory)

## Related

- [List item](https://albakore.github.io/pyzed/library/list-item/): A list row with icon, text and an end slot.
- [Search input](https://albakore.github.io/pyzed/library/search-input/): An input with a magnifying glass, for filtering.
- [Virtual list](https://albakore.github.io/pyzed/library/virtual-list/): A list that only builds the rows in view, for millions of items.
