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
chatsdict 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-nameflag value fromsys.argv, else default.- Parameters:
default – App name to fall back to.
- Returns:
The parsed
--app-namevalue, or default.
- klea_utils.ui.web.nicegui.entry.default_storage_env(app_name: str) str[source]¶
Point
NICEGUI_STORAGE_PATHat 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.pyhonoursNICEGUI_STORAGE_PATHat 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_PATHvalue.
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:
ElementA custom chat message bubble with built-in actions.
Replaces
ui.chat_messagewith 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:
objectShared 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.
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-browseruser_idand 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_pagereturns withinresponse_timeouteven 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_url –
host:portto 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-*.jsonfile is missing but the browser still sends the old session cookie,app.storage.userraisesAssertionError. We warn and recreate the backingFilePersistentDictfor thatsession_idso the page can continue with a freshuser_idinstead 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
awaitsoapp.storage.useris still in the request context.- Returns:
The stored (or freshly generated)
user_idstring.
- 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.userorNoneif 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 resultingui.dark_modeelement onctx.darkfor 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
Noneno 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 toinspector_entrieswhen thecompleteevent 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:
ElementA custom chat message bubble with built-in actions.
Replaces
ui.chat_messagewith 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_areaandstream_containerare 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_listandswitch_chaton the context.- Parameters:
ctx – The shared page context;
left_drawerandtoggle_iconare 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.darkbeing set bytheme.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_inspectorandreset_center_tabon 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_dialogalready registered (bymodel_dialog.attach_model_info()), since the pane’s settings button invokes it.- Parameters:
ctx – The shared page context;
refresh_status_paneis 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_infoandmodel_config_dialogare 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;
textis 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) andctx.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)