pywry.chat_manager¶
High-level orchestration layer for the PyWry chat component.
Manager¶
pywry.chat_manager.ChatManager
¶
ChatManager(handler: HandlerFunc, *, system_prompt: str = '', model: str = '', temperature: float = 0.7, welcome_message: str = '', settings: Sequence[SettingsItem] | None = None, slash_commands: Sequence[SlashCommandDef] | None = None, on_slash_command: Callable[..., Any] | None = None, on_settings_change: Callable[..., Any] | None = None, show_sidebar: bool = True, show_settings: bool = True, toolbar_width: str = '380px', toolbar_min_width: str = '280px', collapsible: bool = True, resizable: bool = True, include_plotly: bool = False, include_aggrid: bool = False, aggrid_theme: str = 'alpine', enable_context: bool = False, enable_file_attach: bool = False, file_accept_types: list[str] | None = None, context_allowed_roots: list[str] | None = None)
Zero-boilerplate orchestrator for the PyWry chat component.
Handles all event wiring, thread management, streaming, cancellation, and state synchronization. The developer provides a handler function and optional configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
callable
|
Function that receives |
required |
system_prompt
|
str
|
System prompt prepended to every request. |
''
|
model
|
str
|
Model identifier (passed to handler via context). |
''
|
temperature
|
float
|
Temperature (passed to handler via context). |
0.7
|
welcome_message
|
str
|
Markdown message sent when the chat initializes. |
''
|
settings
|
list[SettingsItem]
|
Settings items rendered in the gear dropdown. |
None
|
slash_commands
|
list[SlashCommandDef]
|
Slash commands registered in the chat input. |
None
|
on_slash_command
|
callable
|
Callback |
None
|
on_settings_change
|
callable
|
Callback |
None
|
show_sidebar
|
bool
|
Show the conversation picker in the header. |
True
|
show_settings
|
bool
|
Show the gear icon in the header. |
True
|
toolbar_width
|
str
|
CSS width for the chat toolbar. |
'380px'
|
toolbar_min_width
|
str
|
CSS min-width for the chat toolbar. |
'280px'
|
collapsible
|
bool
|
Whether the chat toolbar is collapsible. |
True
|
resizable
|
bool
|
Whether the chat toolbar is resizable. |
True
|
include_plotly
|
bool
|
Include Plotly.js library eagerly on initialization. |
False
|
include_aggrid
|
bool
|
Include AG Grid library eagerly on initialization. |
False
|
aggrid_theme
|
str
|
AG Grid theme name (e.g. |
'alpine'
|
enable_context
|
bool
|
Enable the context attachment system (@ mentions for live widgets). |
False
|
enable_file_attach
|
bool
|
Enable the file attachment button (📎), drag-and-drop, and the
hidden file input. Independent of |
False
|
file_accept_types
|
list[str] | None
|
Required when |
None
|
context_allowed_roots
|
list[str] | None
|
Restrict file attachments to these directories. |
None
|
Examples:
Simplest usage — echo bot::
def echo(messages, ctx):
return f"You said: {messages[-1]['text']}"
chat = ChatManager(handler=echo)
Streaming generator::
def stream_handler(messages, ctx):
for word in call_my_api(messages):
if ctx.cancel_event.is_set():
return
yield word
chat = ChatManager(handler=stream_handler)
Rich responses with tool calls::
def agent_handler(messages, ctx):
yield StatusResponse(text="Searching...")
results = search(messages[-1]["text"])
yield ToolCallResponse(name="search", arguments={"q": messages[-1]["text"]})
yield ToolResultResponse(tool_id="...", result=str(results))
for chunk in summarize(results):
yield chunk
chat = ChatManager(handler=agent_handler)
Attributes¶
CONTEXT_TOOL
class-attribute
¶
CONTEXT_TOOL: dict[str, Any] = {'type': 'function', 'function': {'name': 'get_context', 'description': 'Retrieve the full content of an attached file or widget. Call this when you need to read, analyze, or reference an attachment the user has provided.', 'parameters': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'The attachment name (from the list of attached items).'}}, 'required': ['name']}}}
_slash_commands
instance-attribute
¶
_context_allowed_roots
instance-attribute
¶
_context_allowed_roots = [(str(resolve())) for r in context_allowed_roots] if context_allowed_roots else None
_settings_values
instance-attribute
¶
_settings_values: dict[str, Any] = {(id): (value) for s in (_settings_items) if type != 'separator'}
Functions¶
register_context_source
¶
Register a live dashboard component as an @-mentionable context source.
When the user types @ in the chat, registered sources appear
in the autocomplete popup. When selected and sent, the frontend
extracts live data from the component and includes it in the
message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
component_id
|
str
|
The unique ID of the component (e.g. the |
required |
name
|
str
|
Human-readable label shown in the popup (e.g. |
required |
toolbar
¶
toolbar(*, position: Literal['header', 'footer', 'top', 'bottom', 'left', 'right', 'inside'] = 'right') -> Any
Build a Toolbar containing the chat panel.
Returns a Toolbar instance ready to pass to app.show(toolbars=...).
callbacks
¶
Return the callbacks dict to pass to app.show(callbacks=...).
This wires up ALL chat events automatically.
_emit_fire
¶
Fire-and-forget emit — non-blocking, for high-frequency streaming.
_inject_aggrid_assets
¶
Lazy-inject AG Grid JS/CSS on first table artifact.
_inject_plotly_assets
¶
Lazy-inject Plotly JS on first plotly artifact.
_is_accepted_file
¶
Check if the file extension is in the developer's allowed list.
_resolve_widget_attachment
¶
_resolve_widget_attachment(widget_id: str, content: str | None = None, name: str | None = None) -> Attachment | None
Create an Attachment for a widget/component reference.
When content is provided (extracted by the frontend from the live component), it is used directly. Otherwise falls back to auto-discovered inline widgets.
_resolve_attachments
¶
Resolve raw attachment dicts from the frontend into Attachments.
Desktop (Tauri): file attachments carry a path — a full
filesystem path. The handler is responsible for reading file
content.
Browser (inline / iframe): the File API cannot expose
filesystem paths, so file attachments carry content instead
(read by the frontend via FileReader).
Widget attachments always carry content extracted by the
frontend from the live component.
_get_context_sources
¶
List available context sources for the @ mention popup.
_dispatch_artifact
¶
Dispatch an artifact to the frontend with type-specific payloads.
For TableArtifact / PlotlyArtifact, assets are lazy-injected on first use and data is normalized before sending.
_on_user_message
¶
Handle incoming user message — run handler in background thread.
_inject_context
¶
_inject_context(messages: list[MessageDict], ctx: ChatContext, message_id: str, thread_id: str) -> list[MessageDict]
Inject attachment context into messages and emit tool-call events.
_dispatch_handler_result
¶
_dispatch_handler_result(result: Any, message_id: str, thread_id: str, cancel: Event, ctx: ChatContext) -> None
Route handler return value to the appropriate processing path.
_run_handler
¶
_run_handler(messages: list[MessageDict], ctx: ChatContext, message_id: str, thread_id: str, cancel: Event) -> None
Execute the handler in a background thread.
_handle_complete
¶
Send a complete (non-streamed) assistant message.
_buffer_text
¶
Add text to the stream buffer and auto-flush if threshold reached.
_handle_input_required
¶
_handle_input_required(item: InputRequiredResponse, state: _StreamState, thread_id: str, ctx: ChatContext | None) -> None
Handle an InputRequiredResponse during streaming.
_process_stream_item
¶
_process_stream_item(item: Any, state: _StreamState, thread_id: str, ctx: ChatContext | None) -> None
Dispatch a single stream item to the appropriate handler.
_finalize_stream
¶
Flush remaining buffer and send stream-done events.
_handle_cancel
¶
Handle stream cancellation.
_handle_stream
¶
_handle_stream(gen: Any, message_id: str, thread_id: str, cancel: Event, *, ctx: ChatContext | None = None) -> None
Stream chunks from a generator, handling rich response types.
_handle_async_stream
async
¶
_handle_async_stream(agen: Any, message_id: str, thread_id: str, cancel: Event, *, ctx: ChatContext | None = None) -> None
Stream chunks from an async generator, handling rich response types.
_on_stop_generation
¶
Cancel active generation.
_on_todo_clear
¶
Handle user clearing the todo list.
_on_input_response
¶
Handle user's response to an InputRequiredResponse.
_on_slash_command_event
¶
Handle slash command from frontend.
_on_thread_create
¶
Create a new thread.
_on_thread_switch
¶
Switch to an existing thread.
_on_thread_delete
¶
Delete a thread.
_on_thread_rename
¶
Rename a thread.
_on_settings_change_event
¶
Handle settings change.
_on_request_state
¶
Respond to initialization request from frontend JS.
update_todos
¶
update_todos(items: list[TodoItem]) -> None
Push a todo list update to the chat UI.
Call from handlers, slash commands, or any callback.
send_message
¶
Send a programmatic assistant message to the chat.
Useful from slash-command handlers or other callbacks that want to inject a message directly (without going through the handler).
_build_thread_list
¶
Build thread list dicts for the frontend.
pywry.chat_manager.ChatContext
dataclass
¶
ChatContext(thread_id: str = '', message_id: str = '', settings: dict[str, Any] = dict(), cancel_event: Event = Event(), system_prompt: str = '', model: str = '', temperature: float = 0.7, attachments: list[Attachment] = list())
Context object passed to handler functions.
Attributes:
| Name | Type | Description |
|---|---|---|
thread_id |
str
|
Active thread ID. |
message_id |
str
|
The assistant message ID being generated. |
settings |
dict
|
Current settings values (from the settings dropdown). |
cancel_event |
Event
|
Set when the user clicks Stop. Check this between chunks. |
system_prompt |
str
|
System prompt configured for the chat. |
model |
str
|
Model name configured for the chat. |
temperature |
float
|
Temperature configured for the chat. |
attachments |
list[Attachment]
|
Resolved context attachments for the current message. |
Attributes¶
settings
class-attribute
instance-attribute
¶
cancel_event
class-attribute
instance-attribute
¶
attachments
class-attribute
instance-attribute
¶
attachment_summary
property
¶
One-line summary of attached context for system/user prompts.
Returns an empty string when there are no attachments. Example output::
Attached context: report.csv (file: C:/data/report.csv), @Sales Data (widget)
Returns:
| Type | Description |
|---|---|
str
|
Summary line suitable for prompt construction. |
context_text
property
¶
Pre-formatted attachment content ready to inject into prompts.
For file attachments with a path (desktop/Tauri), includes only
the path — the handler is responsible for reading file content.
For file attachments with content (browser/inline), includes
the content directly.
For widget attachments, includes the extracted content.
Empty string when there are no attachments.
Returns:
| Type | Description |
|---|---|
str
|
Multi-block attachment context ready to insert into prompts. |
_input_event
class-attribute
instance-attribute
¶
_input_response
class-attribute
instance-attribute
¶
Functions¶
get_attachment
¶
Retrieve attachment content or path by name.
For file attachments with a path (desktop/Tauri), returns the
file path as a string — the handler should read the file itself.
For file attachments with content (browser/inline), or widget
attachments, returns the content directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Attachment name, with or without a leading |
required |
Returns:
| Type | Description |
|---|---|
str
|
Attachment path or content, or a not-found message. |
wait_for_input
¶
Block until the user provides input via InputRequiredResponse.
Call this after yielding InputRequiredResponse to pause the
handler until the user responds. Returns the user's text.
Returns empty string on cancellation or timeout.
Compatible with both the OpenAI API pattern (tool calls requiring
user confirmation) and MCP A2A input_required task status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
Maximum seconds to wait. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
User-supplied input, or an empty string on cancellation or timeout. |
Streaming Responses¶
pywry.chat_manager.TextChunkResponse
¶
pywry.chat_manager.StatusResponse
¶
pywry.chat_manager.ThinkingResponse
¶
Bases: BaseModel
Streaming thinking or reasoning chunk.
Rendered as a collapsible inline block in the UI. Thinking tokens are streamed in real-time and are NOT stored in conversation history. The block auto-collapses when the handler finishes.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['thinking']
|
Response discriminator for thinking chunks. |
text |
str
|
Incremental reasoning text to render. |
pywry.chat_manager.CitationResponse
¶
Bases: BaseModel
Citation or source reference attached to a response.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['citation']
|
Response discriminator for citation UI events. |
url |
str
|
Citation URL shown to the user. |
title |
str
|
Human-readable citation title. |
snippet |
str
|
Supporting excerpt associated with the citation. |
Attributes¶
Functions¶
_block_dangerous_schemes
classmethod
¶
Reject unsafe citation URL schemes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
v
|
str
|
Candidate citation URL. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The original URL when it is safe to render. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Raised when the URL uses the |
pywry.chat_manager.ToolCallResponse
¶
Bases: BaseModel
Handler requests a tool invocation to be shown in the UI.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['tool_call']
|
Response discriminator for tool-call UI events. |
tool_id |
str
|
Stable identifier used to correlate a later tool result. |
name |
str
|
Tool name displayed to the user and routed by the handler. |
arguments |
dict[str, Any]
|
Structured tool arguments associated with the invocation. |
pywry.chat_manager.ToolResultResponse
¶
Bases: BaseModel
Result of a tool invocation shown in the UI.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['tool_result']
|
Response discriminator for tool-result UI events. |
tool_id |
str
|
Identifier of the tool call this result satisfies. |
result |
str
|
Human-readable tool output rendered in the transcript. |
is_error |
bool
|
Indicates the tool invocation failed. |
Artifacts¶
pywry.chat_manager.CodeArtifact
¶
Bases: _ArtifactBase
Code snippet artifact rendered with syntax highlighting.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['code']
|
Artifact subtype discriminator. |
content |
str
|
Source code or text snippet to render. |
language |
str
|
Optional language hint for syntax highlighting. |
Attributes¶
pywry.chat_manager.MarkdownArtifact
¶
Bases: _ArtifactBase
Markdown artifact rendered as formatted HTML.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['markdown']
|
Artifact subtype discriminator. |
content |
str
|
Markdown source to render. |
Attributes¶
pywry.chat_manager.HtmlArtifact
¶
Bases: _ArtifactBase
Raw HTML artifact rendered in a sandboxed container.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['html']
|
Artifact subtype discriminator. |
content |
str
|
Raw HTML content to render. |
Attributes¶
pywry.chat_manager.TableArtifact
¶
Bases: _ArtifactBase
Tabular data rendered as an AG Grid widget.
Accepts the same data formats as normalize_data() in grid.py:
pandas DataFrame, list of dicts, dict of lists, or single dict.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['table']
|
Artifact subtype discriminator. |
data |
list[dict[str, Any]] | dict[str, Any]
|
Table rows or source object to normalize into rows. |
column_defs |
list[dict[str, Any]] | None
|
Optional AG Grid column definitions. |
grid_options |
dict[str, Any] | None
|
Optional AG Grid configuration overrides. |
height |
str
|
CSS height used by the table container. |
pywry.chat_manager.PlotlyArtifact
¶
Bases: _ArtifactBase
Plotly chart artifact rendered as an interactive widget.
figure accepts a standard Plotly figure dict:
{"data": [...traces], "layout": {...}, "config": {...}}.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['plotly']
|
Artifact subtype discriminator. |
figure |
dict[str, Any]
|
Plotly figure payload passed to the frontend renderer. |
height |
str
|
CSS height used by the chart container. |
Attributes¶
pywry.chat_manager.ImageArtifact
¶
Bases: _ArtifactBase
Image artifact rendered as an <img> element.
url can be a data URI (data:image/png;base64,...) or an
HTTP(S) URL.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['image']
|
Artifact subtype discriminator. |
url |
str
|
Image URL or data URI. |
alt |
str
|
Alternate text for the rendered image. |
Attributes¶
Functions¶
_block_dangerous_schemes
classmethod
¶
Reject unsafe image URL schemes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
v
|
str
|
Candidate image URL. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The original URL when it is safe to render. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Raised when the URL uses the |
pywry.chat_manager.JsonArtifact
¶
Bases: _ArtifactBase
Structured data rendered as a collapsible JSON tree.
Attributes:
| Name | Type | Description |
|---|---|---|
artifact_type |
Literal['json']
|
Artifact subtype discriminator. |
data |
Any
|
Arbitrary JSON-serializable payload to display. |
Attributes¶
UI State Models¶
pywry.chat_manager.SettingsItem
¶
Bases: BaseModel
Settings menu item shown in the gear dropdown.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable identifier for the setting. |
label |
str
|
User-visible label. |
type |
Literal['action', 'toggle', 'select', 'range', 'separator']
|
Control type rendered in the settings menu. |
value |
Any
|
Current value or payload associated with the setting. |
options |
list[str] | None
|
Allowed values for |
min |
float | None
|
Minimum value for |
max |
float | None
|
Maximum value for |
step |
float | None
|
Increment for |
pywry.chat_manager.SlashCommandDef
¶
pywry.chat_manager.TodoItem
¶
Bases: BaseModel
Single todo item in the agent's task list.
Rendered as a collapsible list above the chat input bar. The agent manages items; the user can clear the list.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int | str
|
Stable todo-item identifier. |
title |
str
|
User-visible todo label. |
status |
Literal['not-started', 'in-progress', 'completed']
|
Current progress state for the item. |
pywry.chat_manager.TodoUpdateResponse
¶
Bases: BaseModel
Push the full todo list to the UI.
Yielded from a handler to update the todo list above the input bar. The list is NOT stored in conversation history.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['todo']
|
Response discriminator for todo-list updates. |
items |
list[TodoItem]
|
Full replacement set of todo items to display. |
pywry.chat_manager.InputRequiredResponse
¶
Bases: BaseModel
Pause generation to request user input mid-stream.
When yielded from a handler, the current streaming batch is finalized
and the chat input is re-enabled so the user can respond. The handler
then calls ctx.wait_for_input() to block until the response arrives.
Compatible with:
- OpenAI API: maps to the pattern where the assistant asks a clarifying question and the conversation continues with the user's reply.
- MCP A2A: maps to Task status
input_required— the agent pauses execution until the client supplies additional input.
Example::
def handler(messages, ctx):
yield "Which file should I modify?"
yield InputRequiredResponse(placeholder="Enter filename...")
filename = ctx.wait_for_input()
if not filename:
return # Cancelled
yield f"Modifying **{filename}**..."
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['input_required']
|
Response discriminator for interactive pauses. |
prompt |
str
|
Optional prompt shown above the resumed input control. |
placeholder |
str
|
Placeholder text for the temporary input control. |
request_id |
str
|
Stable identifier for correlating the user response. |
input_type |
Literal['text', 'buttons', 'radio']
|
Input control variant to render. |
options |
list[str] | None
|
Choices for button or radio-style prompts. |