UI — NiceGUI

The NiceGUI web frontend is composed per app (klea_agent.ui.web / klea_rag.ui.web, ADR-0031): each app builds its page from the shared components below. klea_utils provides only the reusable helpers and components, never a full page.

Shared frontend helpers

In-memory chat state for the NiceGUI frontend.

Keyed by {user_id}:{chat_id} so that colliding chat_ids across different users do not interfere.

File: klea_utils/ui/web/nicegui/state.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.state.ensure_chat(user_id: str, chat_id: str) dict[source]

Return the chat session dict for user_id / chat_id, creating it if missing.

Each chat session dict has the following keys:

name                Human-readable display name (auto-generated)
created             ``datetime.timestamp()`` of creation (float).
pinned              Whether the chat session is pinned to the top of the list.
messages            List of ``(text, stamp, is_user)`` tuples where
                    *is_user* is ``True`` for user messages and
                    ``False`` for bot / system messages.
inspector_entries   List of dicts with info/debug events for the most
                    recent query in this chat session.
inspector_expanded  Set of indices into *inspector_entries* that are
                    currently expanded in the UI.
state_sections      Dict of ``{node_label: section_data}`` for the status
                    pane, ordered by first insertion (per node label).
model_info          Dict of active model config per role
                    (from ``fetch_active_models``).
token_usage         Numeric token totals accumulated for this in-memory
                    chat session.
klea_utils.ui.web.nicegui.state.get_chats_sorted(user_id: str) list[tuple[str, dict]][source]

Return (chat_id, data) pairs for user_id, pinned first, then by creation desc.

Filters by the user_id prefix so that in a multi-browser scenario (same NiceGUI process) each user only sees their own chats.

Server API client for the NiceGUI frontend.

All functions accept the server_url as their first argument so the caller can point them at any running Klea backend without shared state.

File: klea_utils/ui/web/nicegui/client.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

async klea_utils.ui.web.nicegui.client.clear_model_override(server_url: str, user_id: str, chat_id: str, role: str) bool[source]

DELETE the model override for a chat role.

async klea_utils.ui.web.nicegui.client.create_chat_on_server(server_url: str, user_id: str, chat_id: str) None[source]

POST a new chat to the server so it persists.

async klea_utils.ui.web.nicegui.client.delete_chat_on_server(server_url: str, user_id: str, chat_id: str) None[source]

DELETE the chat on the server.

async klea_utils.ui.web.nicegui.client.hydrate_chats(server_url: str, user_id: str) None[source]

Fetch all chats and their messages from the server into the local state.

Populates the in-memory chats dict with every conversation belonging to user_id, including the full message history for each chat.

After this call the frontend can switch between any chat without additional server round-trips. If the server has no data for this user yet the local store stays empty.

async klea_utils.ui.web.nicegui.client.rename_chat_on_server(server_url: str, user_id: str, chat_id: str, title: str) None[source]

PATCH the chat title on the server.

async klea_utils.ui.web.nicegui.client.set_model_override(server_url: str, user_id: str, chat_id: str, role: str, payload: dict) bool[source]

POST a model override for a chat role.

Argparse parser for the Klea NiceGUI frontend entry point.

Provides a standard argparse parser for the arguments passed by the Typer web command when it launches app.py. The entry point simply calls make_parser().parse_args() and picks only the arguments it needs.

Usage:

from klea_utils.ui.web.nicegui.parser import make_parser

args = make_parser("My frontend").parse_args()
run_app(
    args.title, args.url,
    subtitle=args.subtitle,
    disclaimer=args.disclaimer,
    reload=args.reload,
)

File: klea_utils/ui/web/nicegui/parser.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.parser.make_parser(description: str = 'Klea web interface') ArgumentParser[source]

Return a preconfigured argparse.ArgumentParser.

Parameters:

description – Description shown in --help.

Shared NiceGUI web-entry helpers for Klea apps.

These helpers are intentionally free of any nicegui import so an app’s ui/web/app.py can call them before the NiceGUI machinery is imported (NiceGUI reads NICEGUI_STORAGE_PATH at import time).

File: klea_utils/ui/web/nicegui/entry.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.entry.app_name_from_argv(default: str) str[source]

Return the --app-name flag value from sys.argv, else default.

Parameters:

default – App name to fall back to.

Returns:

The parsed --app-name value, or default.

klea_utils.ui.web.nicegui.entry.default_storage_env(app_name: str) str[source]

Point NICEGUI_STORAGE_PATH at the per-app data dir when unset.

Called at the very top of each app’s ui/web/app.py, before anything imports NiceGUI: nicegui/storage.py honours NICEGUI_STORAGE_PATH at import time, so setting it early gives the storage subsystem the correct per-app directory from the start.

Parameters:

app_name – App name (overridable via --app-name) used for the platformdirs data directory.

Returns:

The resolved NICEGUI_STORAGE_PATH value.

Reusable custom NiceGUI widgets for Klea web interfaces.

The widgets now live in klea_utils.ui.web.nicegui.components (ADR-0031); this module re-exports them so existing imports keep working.

File: klea_utils/ui/web/nicegui/widgets.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

class klea_utils.ui.web.nicegui.widgets.ChatBubble(text: str, stamp: str, is_user: bool, collapsed: bool, idx: int, on_expand=None, on_copy=None)[source]

Bases: Element

A custom chat message bubble with built-in actions.

Replaces ui.chat_message with full control over the layout. Each bubble has collapsible text content, a timestamp, a copy button, and an expand / collapse toggle – all flowing naturally inside the bubble (no CSS hacks).

Usage inside a @ui.refreshable:

ChatBubble(
    text="Hello", stamp="12:00", is_user=True,
    collapsed=False, idx=0,
    on_expand=lambda: print("toggle"),
    on_copy=lambda: print("copy"),
)

Shared components

Shared page context for Klea NiceGUI components.

A PageContext carries everything the reusable components need to coordinate without being assembled into a single closure: the page configuration, the mutable runtime state (active chat, expand state, streaming flag), the NiceGUI element references, and the cross-component callbacks. Components write into the context at attach time; handlers read it at event time, after the whole page has been assembled.

File: klea_utils/ui/web/nicegui/components/context.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

class klea_utils.ui.web.nicegui.components.context.PageContext(server_url: str, user_id: str, title: str = 'Klea', subtitle: str = '', disclaimer: str = '', footer_text: str = 'Powered by <a href="https://github.com/neuroml/klea">Klea</a>', chat_id: str = '', expanded: set[int] = <factory>, is_streaming: bool = False, mini_state: bool = True, query_extra: dict[str, ~typing.Any] = <factory>, dark: ~typing.Any = None, left_drawer: ~typing.Any = None, toggle_icon: ~typing.Any = None, center_panels: ~typing.Any = None, chat_area: ~typing.Any = None, scroll_area: ~typing.Any = None, stream_container: ~typing.Any = None, text: ~typing.Any = None, loading_row: ~typing.Any = None, render_chat_area: ~collections.abc.Callable[[], None] = <function _noop>, scroll_chat_bottom: ~collections.abc.Callable[[], None] = <function _noop>, refresh_chat_list: ~collections.abc.Callable[[...], ~typing.Any] = <function _noop>, refresh_status_pane: ~collections.abc.Callable[[...], ~typing.Any] = <function _noop>, refresh_inspector: ~collections.abc.Callable[[...], ~typing.Any] = <function _noop>, reset_center_tab: ~collections.abc.Callable[[], None] = <function _noop>, fetch_model_info: ~collections.abc.Callable[[], ~typing.Any] | None = None, model_config_dialog: ~collections.abc.Callable[[], ~typing.Any] | None = None, status_extra: ~collections.abc.Callable[[], ~typing.Any] | None = None, switch_chat: ~collections.abc.Callable[[str], None] = <function _noop_arg>)[source]

Bases: object

Shared mutable state and element/callback registry for a Klea page.

Components (components/*.py) attach into this object: they read the configuration, keep their mutable state here, and register the element references and cross-component callbacks they need. The page assembly creates one context, attaches all components, then lets handlers resolve references at event time.

Parameters:
  • server_url – Base URL of the backend API server.

  • user_id – Opaque persistent user identifier.

  • title – Bold application title in the header bar.

  • subtitle – Optional smaller text shown next to title.

  • disclaimer – Optional text shown below the chat input.

  • footer_text – HTML content for the footer bar.

  • chat_id – Active chat conversation identifier.

refresh_chat_list() None

No-op default for callbacks that are not yet/never registered.

refresh_inspector() None

No-op default for callbacks that are not yet/never registered.

refresh_status_pane() None

No-op default for callbacks that are not yet/never registered.

render_chat_area() None

No-op default for callbacks that are not yet/never registered.

reset_center_tab() None

No-op default for callbacks that are not yet/never registered.

scroll_chat_bottom() None

No-op default for callbacks that are not yet/never registered.

switch_chat() None

No-op default for one-argument callbacks (e.g. chat switching).

NiceGUI server bootstrap for Klea pages.

Shared, app-agnostic boilerplate: process logging, per-app NiceGUI storage path, the / page handler (which resolves the per-browser identity and delegates the actual composition to a page builder supplied by the caller), and nicegui.ui.run().

The page layout itself is not defined here – the caller passes the page_builder callable so each app composes the page from klea_utils.ui.web.nicegui.components (ADR-0031).

File: klea_utils/ui/web/nicegui/components/bootstrap.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.bootstrap.run_nicegui_server(title: str, server_url: str, *, page_builder: Callable[[...], Any], subtitle: str = '', disclaimer: str = '', footer_text: str = 'Powered by <a href="https://github.com/neuroml/klea">Klea</a>', reload: bool = False, nicegui_url: str = '0.0.0.0:7860', storage_secret: str = 'klea-nicegui-secret-change-me', app_name: str = 'klea-web') None[source]

Start the NiceGUI web server with a Klea page.

This is the process-level entry point for a Klea frontend. It registers a @ui.page("/") handler that resolves the per-browser user_id and delegates page composition to page_builder (signature (chat_id, user_id, server_url, title, subtitle, disclaimer, footer_text)), then starts the NiceGUI server.

Backend readiness is handled by the page itself: the layout is delivered immediately and the health probe + chat hydration run as a background task, so main_page returns within response_timeout even on a cold start.

Parameters:
  • title – Application title (displayed in the header and browser tab).

  • server_url – Base URL of the backend API server (e.g. http://127.0.0.1:8005).

  • page_builder – Callable that composes the page layout.

  • subtitle – Optional smaller text shown next to title.

  • disclaimer – Optional text shown below the chat input.

  • footer_text – HTML content for the footer bar.

  • reload – When True, enable NiceGUI’s file-watch hot reload.

  • nicegui_urlhost:port to bind the NiceGUI web server to.

  • storage_secret – Secret used by NiceGUI for browser session persistence.

  • app_name – Log identity for this frontend process, used as the log file name so each app keeps its own logs.

NiceGUI user-storage helpers for Klea pages.

Covers the persistent per-browser identity (app.storage.user) and defends against the stale-session case where NiceGUI raises AssertionError because the backing storage-user-*.json file was lost while the browser still sends the old session cookie.

File: klea_utils/ui/web/nicegui/components/storage.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

async klea_utils.ui.web.nicegui.components.storage.ensure_user_storage()[source]

Return app.storage.user, recreating it if stale.

When the .nicegui/storage-user-*.json file is missing but the browser still sends the old session cookie, app.storage.user raises AssertionError. We warn and recreate the backing FilePersistentDict for that session_id so the page can continue with a fresh user_id instead of 500.

async klea_utils.ui.web.nicegui.components.storage.resolve_user_id() str[source]

Return the persistent per-browser user_id, creating it if missing.

Must be called at the top of the page builder before any await so app.storage.user is still in the request context.

Returns:

The stored (or freshly generated) user_id string.

klea_utils.ui.web.nicegui.components.storage.safe_set_user(key: str, value: Any) None[source]

Set app.storage.user[key] if storage is available, else warn.

klea_utils.ui.web.nicegui.components.storage.user_storage_or_none()[source]

Return app.storage.user or None if stale (no await).

Page theme component: CSS overrides and persistent dark mode.

File: klea_utils/ui/web/nicegui/components/theme.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.theme.install_theme(ctx: PageContext) None[source]

Install the page CSS overrides and persistent dark mode.

Binds the dark-mode flag to app.storage.user["dark_mode"] when storage is available, and stores the resulting ui.dark_mode element on ctx.dark for the header toggle.

Parameters:

ctx – The shared page context.

SSE stream handling component for Klea pages.

Contains the pure state-mutation logic (apply_stream_event(), unit-testable without NiceGUI) and the UI-driving coroutine (run_stream()) that consumes the backend’s /query/stream events and updates the chat panel, status pane and inspector.

File: klea_utils/ui/web/nicegui/components/stream.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.stream.apply_stream_event(chat: dict[str, Any], event: dict[str, Any]) str | None[source]

Apply one stream event’s pure state mutations to the chat dict.

Mutates chat in place (token usage, status sections, inspector buffer, and, on completion, the final message) and returns the action the UI layer reacts to:

return value

meaning

"usage"

token usage totals were incremented

"state"

a status-pane section was stored

"debug"

an inspector entry was buffered

"context"

session context (e.g. the operating mode) was stored

"complete"

the final assistant message was appended

"error"

the backend signalled an error

None

no state change (progress / info / token events)

Inspector entries are buffered under INSPECTOR_BUFFER_KEY; the caller clears the buffer at stream start and commits it to inspector_entries when the complete event arrives.

Parameters:
  • chat – Chat session dict (see state.ensure_chat).

  • event – Parsed SSE event dict from stream_events.

Returns:

Action string described above, or None.

async klea_utils.ui.web.nicegui.components.stream.run_stream(ctx: PageContext, query: str, chat_id: str) None[source]

Stream a query’s events into the UI for chat_id.

Shows a progress row while streaming, commits the final answer and inspector data on completion, and surfaces errors as nicegui notifications.

Parameters:
  • ctx – The shared page context.

  • query – The user’s query text.

  • chat_id – Chat conversation identifier.

Chat message bubble widget.

File: klea_utils/ui/web/nicegui/components/chat_bubble.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

class klea_utils.ui.web.nicegui.components.chat_bubble.ChatBubble(text: str, stamp: str, is_user: bool, collapsed: bool, idx: int, on_expand=None, on_copy=None)[source]

Bases: Element

A custom chat message bubble with built-in actions.

Replaces ui.chat_message with full control over the layout. Each bubble has collapsible text content, a timestamp, a copy button, and an expand / collapse toggle – all flowing naturally inside the bubble (no CSS hacks).

Usage inside a @ui.refreshable:

ChatBubble(
    text="Hello", stamp="12:00", is_user=True,
    collapsed=False, idx=0,
    on_expand=lambda: print("toggle"),
    on_copy=lambda: print("copy"),
)

Chat area component: message list, scroll, and empty-state.

Builds the scroll area that holds the message bubbles and the stream progress container, and registers the render/scroll callbacks on the page context.

File: klea_utils/ui/web/nicegui/components/chat_area.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.chat_area.attach_chat_area(ctx: PageContext) None[source]

Build the chat scroll area and register its render callbacks.

Must be called with the chat tab panel as the ambient NiceGUI context (the surrounding layout decides where the tab panels go).

Parameters:

ctx – The shared page context; chat_area, scroll_area and stream_container are filled in here.

Chat list component: left drawer with sessions and session management.

Holds the session list, chat switching / renaming / pinning / deletion, the delete-user-session flow, and the drawer rail toggle. All identity and active-chat reads go through the shared PageContext, so a user-identity reset (delete all data) takes effect immediately for every handler without a closure rebind.

File: klea_utils/ui/web/nicegui/components/chat_list.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.chat_list.attach_chat_list(ctx: PageContext) None[source]

Build the left drawer (session rail) and register its handlers.

Registers refresh_chat_list and switch_chat on the context.

Parameters:

ctx – The shared page context; left_drawer and toggle_icon are filled in here.

Header component: title bar and dark-mode toggle.

File: klea_utils/ui/web/nicegui/components/header.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.header.attach_header(ctx: PageContext) None[source]

Build the page header: title, optional subtitle, dark-mode toggle.

Relies on ctx.dark being set by theme.install_theme() before this is called.

Parameters:

ctx – The shared page context.

Inspector panel component: streamed info/debug entries for the active chat.

File: klea_utils/ui/web/nicegui/components/inspector.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.inspector.attach_inspector_panel(ctx: PageContext) None[source]

Build the inspector panel for the active chat in the inspect tab.

Must be called with the inspect tab panel as the ambient NiceGUI context. Registers refresh_inspector and reset_center_tab on the context and renders the (empty) initial state.

Parameters:

ctx – The shared page context.

Status pane component: the right drawer with chat state details.

Shows the chat name, per-role model summary (with a settings button opening the model-config dialog), token usage totals, and the per-node status sections streamed by the graph.

File: klea_utils/ui/web/nicegui/components/status_pane.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.status_pane.attach_status_pane(ctx: PageContext) None[source]

Build the right drawer (status pane) and register its refresh.

Must be called with ctx.model_config_dialog already registered (by model_dialog.attach_model_info()), since the pane’s settings button invokes it.

Parameters:

ctx – The shared page context; refresh_status_pane is set here.

Model configuration component: per-chat model overrides dialog.

File: klea_utils/ui/web/nicegui/components/model_dialog.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.model_dialog.attach_model_info(ctx: PageContext) None[source]

Register the model-info fetch and the model-config dialog.

Parameters:

ctx – The shared page context; fetch_model_info and model_config_dialog are set here.

Chat input component: text area, send handling, and stream kick-off.

File: klea_utils/ui/web/nicegui/components/input_area.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.input_area.attach_input(ctx: PageContext) None[source]

Build the chat input row and wire send / Enter handling.

Must be called with the chat tab panel (below the chat area) as the ambient NiceGUI context. The input is disabled until initial_load.attach_initial_load() enables it after the backend health probe succeeds.

Parameters:

ctx – The shared page context; text is filled in here.

Initial-load component: backend readiness probe and chat hydration.

Runs in a background task after the layout is delivered so the page frame renders immediately even when the backend needs 30-60s to become ready (e.g. a cold HuggingFace container).

File: klea_utils/ui/web/nicegui/components/initial_load.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.web.nicegui.components.initial_load.attach_initial_load(ctx: PageContext) None[source]

Schedule the backend probe + hydration in a background task.

Requires ctx.loading_row (the backend banner) and ctx.text (the chat input) to already be built.

Parameters:

ctx – The shared page context.

Text helpers

Bare-URL linkification for markdown text

File: klea_utils/ui/linkify.py

Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>

klea_utils.ui.linkify.linkify_md(text: str) str[source]

Convert bare URLs in markdown text to [url](url) links.

Existing [text](url) links are returned verbatim so they are not re-wrapped. Callers pass the result to a markdown renderer, which turns the wrapped URLs into clickable anchors.

Parameters:

text – Markdown source text

Returns:

Text with bare URLs wrapped as [url](url)