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 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 = '', stream_tasks: dict[str, ~typing.Any] = <factory>, retry_cbs: dict[str, ~typing.Any] = <factory>, mini_state: bool = True, status_models_expanded: bool = False, query_extra: dict[str, ~typing.Any] = <factory>, command_catalogue: ~typing.Any = None, 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, turn_status_container: ~typing.Any = None, interrupt_container: ~typing.Any = None, input_row: ~typing.Any = None, turn_status_label: ~typing.Any = None, text: ~typing.Any = None, loading_row: ~typing.Any = None, inspector_container: ~typing.Any = None, inspector_scroll: ~typing.Any = None, dialog_container: ~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>, begin_inspector_section: ~collections.abc.Callable[[...], None] = <function _noop_args>, append_inspector: ~collections.abc.Callable[[...], None] = <function _noop_args>, reset_center_tab: ~collections.abc.Callable[[], None] = <function _noop>, refresh_send_state: ~collections.abc.Callable[[], None] = <function _noop>, refresh_turn_status: ~collections.abc.Callable[[], None] = <function _noop>, refresh_stream_button: ~collections.abc.Callable[[], None] = <function _noop>, submit_interrupt: ~collections.abc.Callable[[...], ~typing.Any] = <function _noop_args>, fetch_model_info: ~collections.abc.Callable[[], ~typing.Any] | None = None, fetch_session_model_info: ~collections.abc.Callable[[], ~typing.Any] | None = None, fetch_credentials: ~collections.abc.Callable[[], ~typing.Any] | None = None, model_config_dialog: ~collections.abc.Callable[[], ~typing.Any] | None = None, session_model_info: dict[str, ~typing.Any] = <factory>, credentials: list[dict[str, ~typing.Any]] = <factory>, status_extras: list[~collections.abc.Callable[[], ~typing.Any]] = <factory>, chat_created_hooks: list[~collections.abc.Callable[[~typing.Any], None]] = <factory>, switch_chat: ~collections.abc.Callable[[str], None] = <function _noop_arg>, new_chat: ~collections.abc.Callable[[], None] = <function _noop>, rename_chat: ~collections.abc.Callable[[str, str], None] = <function _noop_args>, delete_chat: ~collections.abc.Callable[[str], None] = <function _noop_arg>, show_model_dialog: ~collections.abc.Callable[[], None] = <function _noop>, context_hooks: list[~collections.abc.Callable[[~typing.Any], None]] = <factory>, ensure_active_chat: ~collections.abc.Callable[[], str] = <function _noop_str>, exit_app: ~collections.abc.Callable[[], None] = <function _noop>)[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.Warning
Do not create NiceGUI elements from a ``nicegui.background_tasks`` task. NiceGUI resolves a new element’s parent from a per-task slot stack; a background task has an empty stack and raises “The current slot cannot be determined because the slot stack for this task is empty”. Either wire the work as an
asyncevent handler (NiceGUI awaits it in the client/slot context, soui.dialog()etc. work), or enter a slot explicitly before creating elements, e.g.with ctx.turn_status_container:(the turn status region) orwith ctx.dialog_container:(dialogs opened from a background task). Updating existing elements andapp.storagefrom a background task is fine.- 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 in the footer.
footer_text – HTML content for the footer bar.
chat_id – Active chat conversation identifier.
- begin_inspector_section(**_kwargs: Any) None¶
Incremental inspector updates: open a new query section (chat_id, marker) and append one entry (chat_id, entry) without a full rebuild.
- chat_created_hooks: list[Callable[[Any], None]]¶
Hooks invoked with a chat’s data dict when it is first created, so an app context control can carry a selection made before the first message (e.g. operating mode / access level in
query_extra) onto that chat.
- chat_is_streaming(chat_id: str) bool[source]¶
Return whether chat_id has an active run on this page.
Reads the per-chat registry (the
chat_idis scoped by this page’suser_id), not the display state in thechatsstore.- Parameters:
chat_id – Chat conversation identifier.
- Returns:
True when a live run is registered for the chat.
- command_catalogue: Any = None¶
Merged command catalogue (the frontend’s client commands plus the server catalogue fetched from
GET /commands);Noneuntil the initial load fetches it (ADR-0047).
- context_hooks: list[Callable[[Any], None]]¶
Hooks invoked with a chat’s data dict whenever a graph
contextevent updates its session context, so a context control (operating mode / tool access) can reconcile its per-chat preference with the checkpointed value the server just reported (e.g. after a/modecommand).
- delete_chat() None¶
Request deletion of a chat session (
chat_id): opens a confirmation dialog, then deletes on confirm. Registered by the chat-list component and used by the client/sessions deletecommand (ADR-0047).
- dialog_container: Any = None¶
Hidden container used as an explicit slot when building dialogs from a context that has no ambient slot (e.g. the first-run prompt runs in a background task). See the slot warning in the class docstring.
- ensure_active_chat() str¶
Return the active chat id, creating and registering one (server-side creation, chat-list refresh, created-hooks) when none is active. Registered by the input area; lets client-command output (e.g.
/help) land in a chat even when the first input is a command (ADR-0047).
- exit_app() None¶
Stop the frontend and the server it spawned: stops any active runs, then shuts the NiceGUI server down. Registered by the header component and used by the header’s exit button and the
/exitclient command (ADR-0047).
- inspector_container: Any = None¶
Stable inspector pane content column / scroll area (incremental append).
- interrupt_container: Any = None¶
Pinned container for the HITL ask form, between the transcript and the composer: while an ask is active it replaces the input row (ADR-0046).
- new_chat() None¶
Create and switch to a new chat session (registered by the chat-list component; used by the client
/newcommand, ADR-0047).
- refresh_stream_button() None¶
Flip the send control between Send and Stop based on the current chat’s run state (registered by the input area; called by the stream component).
- refresh_turn_status() None¶
Re-render the turn status region from the active chat’s turn status. Registered by the chat-area component; the stream component calls it on each status transition.
- rename_chat(**_kwargs: Any) None¶
Rename a chat session (
chat_id,name); registered by the chat-list component and used by the client/renamecommand (ADR-0047).
- show_model_dialog() None¶
Open the model/credential dialog from a synchronous caller; registered by the model-dialog component and used by the client
/modelcommand (ADR-0047).
- status_models_expanded: bool = False¶
Whether the status pane’s Models group is expanded; kept here so the state survives the pane’s refreshes (which rebuild the element).
- stream_tasks: dict[str, Any]¶
the asyncio task driving each chat’s run and its Retry callback. Page-scoped (the tasks are bound to this page’s event loop) and independent of the process-global
chatsstore, so several chats can run at once.- Type:
Per-chat run registry, keyed by
"{user_id}:{chat_id}"
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.default_favicon_path() Path | None[source]¶
Return the bundled Klea favicon path, or
Nonewhen absent.The caller passes the result to NiceGUI’s
favicon;Noneleaves NiceGUI’s own default icon in place.
- async klea_utils.ui.web.nicegui.components.bootstrap.resolve_initial_chat(*, resume: str = '', continue_session: bool = False, server_url: str = '', user_id: str = '') str[source]¶
Return the chat id the page should open on load.
--resume <chat_id>wins; else--continueopens the most recently updated chat for the user (GET /chat/{user_id}is ordered most-recent first); else""so the frontend starts a new chat.- Parameters:
resume – The
--resumechat id, or"".continue_session – Whether
--continuewas given.server_url – Base URL of the backend API server.
user_id – The resolved user id.
- Returns:
The chat id to open, or
""for a fresh chat.
- 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-agent-web', favicon: str | Path | None = None, user_id: str = '', identity_app_name: str = 'klea-agent', resume: str = '', continue_session: bool = False, open_browser: bool = True) 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.
favicon – Browser-tab favicon (a file path, URL, raw SVG or a single character). Defaults to the bundled Klea icon.
user_id – Explicit
--user-idoverride for the page identity.identity_app_name – Canonical identity key (the server app, e.g.
"klea-agent") for the shared host-local identity file.resume – Chat id to open on load (
--resume), or"".continue_session – Open the most recently updated chat on load (
--continue).open_browser – When
True, open the UI in the user’s default browser once the port is ready (NiceGUI’sshow; uses the portablewebbrowsermodule). PassFalsefor headless/remote use.
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.browser_user_id() str[source]¶
Return the persistent per-browser
user_id, creating it if missing.The multi-user fallback: identity comes from the browser’s signed session cookie (
app.storage.user), so each browser/profile is a distinct user. Must be called before anyawaitsoapp.storage.useris still in the request context.- Returns:
The stored (or freshly generated)
user_idstring.
- klea_utils.ui.web.nicegui.components.storage.client_is_loopback() bool[source]¶
Return whether the current request’s client is on a loopback host.
Guards the shared local identity file: a remote browser must never be handed the machine-local
user_id(it would expose the local user’s chats). ANoneclient (no request context) is treated as not loopback.- Returns:
True when the request client host is loopback.
- 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_page_user_id(*, explicit: str = '', server_url: str = '', identity_app_name: str = 'klea-agent') str[source]¶
Resolve the page’s
user_idthrough the shared identity seam.Order: an explicit
--user-id/KLEA_USER_ID> the canonical host-local identity file (only when both the backend URL and the requesting browser are loopback, so the TUI and web share one identity for a local single-user setup) > the per-browser cookie (remote / multi-user).- Parameters:
explicit – The
--user-idvalue, or"".server_url – Base URL of the backend API server.
identity_app_name – Canonical identity key (the server app, e.g.
"klea-agent").
- Returns:
The resolved user id.
- 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.
See devdocs/system/web-theming.md.
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.
The pure event-to-state reducer moved to
klea_utils.ui.stream_events (shared with the terminal client); this
module keeps the UI-driving coroutines (run_stream(),
stop_stream()) that consume the backend’s /query/stream events and
update the chat panel, status pane and inspector.
See devdocs/system/streams.md and devdocs/system/api-sse-sequence.md.
File: klea_utils/ui/web/nicegui/components/stream.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- async klea_utils.ui.web.nicegui.components.stream.run_stream(ctx: PageContext, query: str, chat_id: str, resume: bool = False, interrupt_response: dict[str, Any] | None = None, interrupt_id: str | None = None, interrupt_cancel: bool = False) 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 inline (with a Retry action when the run can be resumed from its checkpoint). When the run pauses for human input it renders the ask (the status region builds the form) and stops streaming until the user answers or cancels.
- Parameters:
ctx – The shared page context.
query – The user’s query text (unused when resuming/answering).
chat_id – Chat conversation identifier.
resume – Resume the chat’s last failed run instead of starting a new turn (the query is not resent).
interrupt_response – Answer a pending HITL interrupt (ADR-0046).
interrupt_id – Id of the interrupt being answered.
interrupt_cancel – Cancel the pending interrupt instead of answering.
- klea_utils.ui.web.nicegui.components.stream.stop_all_streams(ctx: PageContext) None[source]¶
Stop every active run on this page (used when exiting the frontend).
Cancels each chat’s
run_streamtask and fires the best-effort cancel request, so a reused/remote server does not keep running after the page goes away.- Parameters:
ctx – The shared page context.
- klea_utils.ui.web.nicegui.components.stream.stop_stream(ctx: PageContext, chat_id: str) None[source]¶
Stop chat_id’s active run: cancel locally and ask the server to cancel.
Cancels the chat’s
run_streamtask (closing the SSE connection) and fires a best-effort/query/cancelso the server stops the graph run even if the disconnect alone would not. Idempotent and safe to call when the chat is not streaming.- Parameters:
ctx – The shared page context.
chat_id – Chat whose run should stop.
Compact single-choice control for the NiceGUI status pane.
A q-btn-group (connected buttons on one line) is used rather than
ui.toggle because each option can carry its own active colour (for
example destructive = red, scientific = green), which a q-btn-toggle
cannot express. Labels and button groups use fixed widths so that stacked
controls line up regardless of their text lengths.
File: klea_utils/ui/web/nicegui/components/choice.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.ui.web.nicegui.components.choice.DEFAULT_GROUP_WIDTH = '10rem'¶
Fixed width for the button group, so controls with different option lengths (e.g.
General | ScientificvsFull | Read-only) match.
- klea_utils.ui.web.nicegui.components.choice.DEFAULT_LABEL_WIDTH = '13ch'¶
Fixed width for the row label (fits the longest expected label, e.g.
"Project root:"), so labels of different lengths stay aligned.
- klea_utils.ui.web.nicegui.components.choice.choice_buttons(label: str, options: dict[str, str], selected: str, on_select: Callable[[str], None], *, colors: dict[str, str] | None = None, tooltips: dict[str, str] | None = None, info: str = '', label_width: str = '13ch', group_width: str = '10rem') None[source]¶
Render a compact, aligned segmented single-choice control.
The selected option is filled with its colour (or
primary) and white text; the others are drawn as outlines. Buttons aredense/size=smand share the group width equally, so the control stays close to the surrounding label text and two stacked controls align.The
choice-row/choice-label/choice-btnclasses are styled inklea_utils.ui.web.nicegui.components.themeso the inactive option stays legible in dark mode (its outline follows its text colour).- Parameters:
label – Row label (e.g.
"Mode:").options –
{value: label}mapping; iteration order is display order.selected – The currently selected value.
on_select – Callback invoked with the newly selected value.
colors – Optional
{value: quasar-colour}for the active state (e.g.{"full": "red-5", "read_only": "green-5"}).tooltips – Optional
{value: text}tooltips per option.info – Optional explanatory tooltip attached to the whole group (e.g. an operating-mode downgrade note).
label_width – CSS width for the label (default
DEFAULT_LABEL_WIDTH); pass the same value for stacked controls to align them.group_width – CSS width for the button group (default
DEFAULT_GROUP_WIDTH); pass the same value for stacked controls to align them.
Session-command handling for the web frontend (ADR-0047).
The web frontend owns its client-side commands and merges them with the
server catalogue fetched from GET /commands. An input beginning with
/ is validated against the merge: a client command runs locally and its
output is rendered as a system chat bubble; a server command is forwarded
as the query (the graph’s command node runs it); an unknown command is
rejected locally and never sent.
File: klea_utils/ui/web/nicegui/components/commands.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.ui.web.nicegui.components.commands.WEB_CAPABILITIES: frozenset[str] = frozenset({'file_picker'})¶
it cannot read the user’s filesystem or run a shell, but it can show a file picker / upload.
- Type:
Capabilities the browser frontend has (ADR-0047)
- klea_utils.ui.web.nicegui.components.commands.attach_autocomplete(ctx: PageContext, text: Any, suggestions: Any) None[source]¶
Attach a
/-command autocomplete list to the chat input (ADR-0047).As the user types
/prefix, suggestions (a container above the input) is populated with the matching commands from the merged catalogue; clicking one inserts/namefollowed by a space into the input.- Parameters:
ctx – The page context (its
command_cataloguebacks the list).text – The chat
ui.textareaelement.suggestions – The container element the suggestions render into.
- klea_utils.ui.web.nicegui.components.commands.build_catalogue(server_commands: list[dict[str, Any]]) CommandRegistry[source]¶
Merge the client commands with the server catalogue (ADR-0047).
- Parameters:
server_commands – Command metadata dicts from
GET /commands.- Returns:
The merged registry (client + server).
- klea_utils.ui.web.nicegui.components.commands.client_commands() CommandRegistry[source]¶
Return the web frontend’s own client-side commands (ADR-0047).
- Returns:
A registry holding the commands the browser handles itself.
- klea_utils.ui.web.nicegui.components.commands.handle_command(ctx: PageContext, text: str) bool[source]¶
Handle text as a command if it is one; return
Truewhen consumed.A client command (or an unknown / parse-error command) is handled locally and rendered as a
systembubble. A server command returnsFalseso the caller forwards it as the query; a non-command also returnsFalse.- Parameters:
ctx – The page context.
text – The raw input.
- Returns:
Truewhen the input was consumed as a client command.
Chat message block 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, role: str, collapsed: bool, idx: int, header: str = '', mime: str = '', data: str = '', meta: dict | None = None, is_error: bool = False, on_expand=None, on_copy=None)[source]¶
Bases:
ElementA full-width chat transcript block with built-in actions.
All roles span the full chat width (opencode-style); the surface colour distinguishes them (semantic
chat-bubble--<role>classes intheme.py), so user blocks read as tinted prompts, agent blocks as plain document text, and tool blocks as neutral blocks. Each block has an optional header, collapsible text content, a timestamp, a copy button, and an expand / collapse toggle – all flowing naturally inside the block (no CSS hacks).Usage inside a
@ui.refreshable:ChatBubble( text="Hello", stamp="12:00", role="user", collapsed=False, idx=0, on_expand=lambda: print("toggle"), on_copy=lambda: print("copy"), )
- klea_utils.ui.web.nicegui.components.chat_bubble.GLYPHS = GlyphSet(success='✔', failure='✘', pending='⋯', agent='◆', prompt='❯', separator='│', overflow='…')¶
Browser output is always Unicode-capable, so the web reuses the TUI’s glyph table directly (the terminal detection / ASCII fallback does not apply); one shared source keeps the role markers identical in both frontends.
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_areaandturn_status_containerare filled in here.
- klea_utils.ui.web.nicegui.components.chat_area.render_turn_status(ctx: PageContext, retry_cb=None) None[source]¶
Render the turn status region for the active chat from its state.
The region is a single slot under the transcript, owned by the chat’s
statusdict (state.ChatData), so it is restored on chat switch and replaced wholesale on the next turn.kindselects the content:progress- a spinner plus the liveheading.error- the message and, whenresumable, a Retry action.stopped- a plain “Stopped” line.idle/missing - nothing.
- Parameters:
ctx – The shared page context (uses
turn_status_container).retry_cb – Callable invoked by the Retry button; the stream component passes it when rendering a live error.
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, dark-mode toggle and exit button.
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, subtitle, dark-mode toggle, exit button.
Relies on
ctx.darkbeing set bytheme.install_theme()before this is called. Registersctx.exit_app: it stops this page’s active runs, then shuts the NiceGUI server down (which, for a client-spawned server, ends the subprocess via the CLI’s process-group teardown).ctx.exit_appis the immediate action used by the/exitclient command; the header’s exit button confirms first, since an accidental click would stop the server for every open tab.- Parameters:
ctx – The shared page context.
File: klea_utils/ui/web/nicegui/components/footer.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
Build the page footer: disclaimer and footer text on one row.
Both share the footer band’s single line, centred as
disclaimer | footer_text, so the disclaimer no longer needs a line below the chat input and the input can sit flush at the bottom of the scroll area. The row wraps on narrow screens.- Parameters:
ctx – The shared page context (supplies
disclaimerandfooter_text).
Inspector panel component: streamed inspect entries for the active chat.
Entries arrive as the graph runs. The pane appends them incrementally (no full rebuild per node) and groups them into a collapsible section per query, marked by the query text and time. Sections are kept for the browser session so a chat accumulates its inspection history.
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. Builds the scroll area and content column once (so entries can be appended incrementally) and registers the refresh/append callbacks on the context.
- Parameters:
ctx – The shared page context.
Inspector code block: JSON highlighting and copy button for inspect details.
ui.code renders its content through a markdown2 fenced block, which
breaks when the content itself contains a code fence (the fence is closed
early and the rest is rendered as markdown). This module renders the
formatted details directly with Pygments’ JSON lexer, keeping ui.code’s
code box and copy button without the markdown indirection.
File: klea_utils/ui/web/nicegui/components/inspector_code.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.ui.web.nicegui.components.inspector_code.CODE_HL_CLASS = 'inspector-code-hl'¶
CSS class of the Pygments wrapper;
theme.pyserves its token colours.
- klea_utils.ui.web.nicegui.components.inspector_code.highlight_details(text: str) str[source]¶
Return text highlighted as JSON (HTML).
Uses Pygments’ stock JSON lexer (object keys as
Name.Tag, string values asString.Double); it already tolerates the real newlines inside string values thatformat_details()produces.- Parameters:
text – The formatted details string.
- Returns:
HTML with Pygments token spans;
theme.pyserves the matching.{CODE_HL_CLASS}colours.
- klea_utils.ui.web.nicegui.components.inspector_code.render_inspector_code(text: str) None[source]¶
Render a code box with highlighted text and a copy button.
Mirrors
ui.code(box, JSON highlighting, copy button) without its markdown2 fenced block, so content containing code fences or multi-line values renders intact.- Parameters:
text – The formatted details string.
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. Everything up to (and including) the token usage stays visible: it is a sticky header, so the state sections below scroll under it (mirroring the TUI status panel).
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-session / per-chat model overrides and the provider credentials editor.
Models are selected per role (chat/plan/guard/…). The dialog edits the per-session defaults when no chat is active and the current chat’s overrides otherwise; saving in a chat also promotes the changed role into the session defaults (“last used”). API keys are not part of the model override — they are provider-scoped and edited in a separate, masked (write-only) credentials dialog.
Each role’s model is picked with a provider field, a model field and an optional custom URL, all filled from the models.dev catalogue and joined back into a single model string on save.
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 fetches and the model-config dialog.
- Parameters:
ctx – The shared page context;
fetch_model_info,fetch_session_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¶
Human-readable formatting for streamed inspection details.
NodeStreamData.details is structured JSON (dict/list/scalar) carried
over the inspect stream event. json.dumps escapes embedded
newlines, so multi-line values such as prompts collapse into a single
unreadable line. format_details() keeps the JSON shape but renders
string values faithfully: embedded newlines become real line breaks, and
a value that is itself a JSON object/array is inlined and pretty-printed.
The output is plain text, for any frontend (the NiceGUI inspector, the
TUI).
The structure, indentation and quote/backslash escaping are all delegated
to json.dumps. Only the two value transformations are custom (see
_prepare()), because json.dumps always escapes newlines.
File: klea_utils/ui/inspect_format.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.ui.inspect_format.format_details(details: Any) str[source]¶
Render structured inspect
detailsas readable indented JSON.The structure matches
json.dumps(..., indent=2); only string values are treated differently:embedded newlines are kept as real line breaks (at their original column, so the text is not altered), so multi-line prompts read as text;
a value that is itself a JSON object/array string is parsed and inlined as nested, pretty-printed JSON;
all other values (numbers, booleans,
null, single-line strings such as a Pydanticrepr) are rendered as normal JSON.
- Parameters:
details – The
detailspayload of aninspectevent.- Returns:
A human-readable, indented multi-line string.
Bare-URL linkification for rendered text
Two variants: linkify_md() wraps bare URLs as Markdown links for a
Markdown renderer, and linkify_html() escapes the text and wraps bare
URLs in safe anchors for plain (non-Markdown) rendering.
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_html(text: str) str[source]¶
HTML-escape text and wrap bare URLs in safe
<a>anchors.For plain-text (non-markdown) rendering, e.g. user input: the text is escaped so it is shown verbatim (no Markdown decoration), while bare URLs become clickable links. The result is safe to pass to
ui.html(sanitize=True).- Parameters:
text – The literal text to render.
- Returns:
HTML with the text escaped and bare URLs wrapped in anchors.
- 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, and fenced code blocks (triple-backtick or tilde fences) are left untouched: a URL in code would otherwise be wrapped and the literal[url](url)shown inside the code span (fenced blocks are not parsed as markdown links). 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)