Overview
TheChatWidget displays the conversation history as a scrollable feed of message cards. It handles auto-scrolling, empty states, and per-message actions like copy and replay.
Module: klaus.ui.chat_widget
Inherits: QWidget (PyQt6)
Class Definition
Signals
Emitted when the user clicks the replay button on an assistant message. Argument is
exchange_id.Constructor
- A
QScrollAreacontaining a vertical layout aligned to the bottom - An empty state label: “Place a page under the camera and ask a question”
- Auto-scroll behavior: scrolls to bottom when new messages arrive and user is near the bottom
Layout Structure
The widget uses aQVBoxLayout with:
QScrollArea(#chat-scrollobject name)QWidgetcontainer withQVBoxLayout(aligned bottom)- Empty state
QLabel(#chat-empty) - Message cards (
MessageCardinstances) - Status messages (
QLabelwith#chat-status-msg)
- Empty state
Methods
Adding Messages
role:"user"or"assistant"text: Message bodytimestamp: Unix timestamp (optional, displayed as relative time)thumbnail_bytes: JPEG/PNG image bytes for user messages (optional)exchange_id: Unique ID for replay functionality
- Hides the empty state label
- Preserves auto-scroll state if user was near the bottom
- Appends
MessageCardto the layout - Connects the card’s
replay_requestedsignal to the widget’s signal
Clearing
Scrolling
QTimer.singleShot calls at 0ms and 100ms to ensure the scroll happens after Qt finishes layout updates.
Auto-Scroll Behavior
The widget automatically scrolls to the bottom when:- New messages are added and the user is within
_SCROLL_THRESHOLD(30px) of the bottom - The scroll range changes (e.g. after adding a widget)
_is_near_bottom(): Checks if scroll value is within 30px of maximum_on_scroll_value_changed(): Updates_auto_scrollflag when user scrolls_on_range_changed(): Scrolls to bottom if_auto_scrollis true_do_scroll_to_bottom(): Sets scroll value to maximum
MessageCard Class
Signals
Emitted when the user clicks the replay button. Argument is
exchange_id.Constructor
role:"user"or"assistant"text: Message bodytimestamp: Unix timestamp (optional)thumbnail_bytes: Image bytes for user messages (optional, scaled to max 500x180)exchange_id: ID for replay functionalityparent: Parent widget
Layout Structure
Each card contains:-
Header row (
QHBoxLayout)- Role label (“You” or “Klaus”)
- Relative timestamp with tooltip
- Copy button (assistant messages only)
- Replay button (assistant messages only)
-
Thumbnail (user messages only)
- Scaled to max 500x180 with aspect ratio preserved
- Max height: 180px
-
Body text
- Word-wrapped, selectable text
Styling
Cards use:- Object name:
#message-card - Role property:
role="user"orrole="assistant"(for QSS selectors) - Padding:
theme.CARD_PADDING_Hxtheme.CARD_PADDING_V
Copy Button Behavior
Usage Example
Notes
- Relative timestamps: Formatted using
format_relative_time_with_tooltip()fromklaus.ui.shared.relative_time - Thumbnails: Only shown for user messages; loaded from bytes using
QPixmap.loadFromData() - Scroll threshold: 30px (
_SCROLL_THRESHOLD) - Deferred scrolling: Uses two timer shots (0ms + 100ms) to ensure layout is complete
- Empty state: Shown when no messages are present