- Detect plantuml/mermaid code fences and render as diagrams
- PlantUML: POST to localhost:8080/svg for SVG output
- Mermaid: GET to localhost:8081/img/ with base64 for JPEG output
(SVG not usable - Qt doesn't support foreignObject)
- Cache rendered diagrams by content hash
- Toggle between source code and rendered image view
- Copy button for source text or image data
- Fall back to source display on render errors
- Add ClipboardHelper for image clipboard support
- Clear diagram cache on session reset
appendData() now emits dataChanged with partial line buffer content
so the UI updates as tokens arrive, not just after complete lines.
This fixes the laggy streaming where text would appear in chunks.
Reworked ChatBlockModel to fix syntax highlighting issues:
- Replace regex-based renderMarkdown() with line-by-line fence parsing
- Code fences detected by ```lang (open) and ``` alone (close)
- Prose wrapped in <div> with explicit palette text color
- Code blocks syntax highlighted with KSyntaxHighlighting
HtmlHighlighter improvements:
- Add defaultColor for unhighlighted text spans
- Track offset to emit gaps between highlighted tokens
- Add endLine() to flush trailing unhighlighted content
Remove TextEdit color property that was overriding inline styles in Qt5.
- Add chatblockmodel.cpp and thememanager.cpp to KF5 GUI target
- Link KF5::ConfigCore and KF5::SyntaxHighlighting to GUI
- Strip Qt6-only grouped palette syntax from QML at configure time
(ThemeManager::applyPalette handles palette via QGuiApplication)
- Fix QStringList brace-init ambiguity in Qt5
- Fix QList::remove(pos,count) not available in Qt5 (use erase)
When a fence () closes inside a block, prepend any content
that came before the fence to renderedHtml so both text and the
highlighted fence show up. When content is appended after the fence,
keep renderedHtml in sync by appending the escaped text. This prevents
the fence from 'eating' surrounding content.
- Introduce StreamFsm class wrapping QProcess lifecycle with
Oneshot/Looping/Guarded restart policies
- Replace raw QProcess management in Ollie9pClient with StreamFsm
- Fix chatblockmodel FSM to properly handle [[[end]]] and fence
transitions with a combined State enum (Outside/InBlock/InFence)
- Add Q_INVOKABLE to setActiveSessionId for direct QML access
- Add streamfsm.cpp to CMakeLists.txt for both KF5 and KF6 builds
- Add Interrupt block type to ChatBlock enum
- Detect <system-user-interruption> and </system-user-interruption>
tags as block openers/closers in the parser
- Render interrupt blocks with bright yellow header
(#3d2e00 dark, #fff8e1 light)
- Show ⚠ User Interrupt header label
Replace the single-bracket INI-like format with triple-bracket blocks
with explicit [[[end]]] terminators. The parser tracks m_inBlock to
ignore content between blocks, making the format robust against
accidental header-like content in tool output or LLM responses.
- Go sender: FormatEvent + NewEventHandler + replayMessagesToLog
- C++ parser: new regex for [[[type]]], new [[[end]]] handler, m_inBlock gate
- QML: update hardcoded [info] string
- C++: Use <pre style="white-space:pre-wrap;"> instead of <div> with <br>
so raw newlines are preserved through to the HTML renderer
- QML: Use TextEdit (preserves whitespace) instead of Label (collapses it)
for rendering blockRenderedHtml
- Add fence state machine (NoFence → InFence) to ChatBlockModel
- On closing ```, render via KSyntaxHighlighting or inline diff colorizer
- Store rendered HTML in ChatBlock.renderedHtml, emit dataChanged on RenderedHtmlRole
- QML shows RichText Label when renderedHtml is populated, falls back to PlainText
- Add KF6::SyntaxHighlighting dependency for ollie-gui target
Kill the entire D-Bus rendering pipeline. The GUI now uses plan9port's
9p command as a subprocess for all server communication:
- Chat streaming: persistent 'read chat' process, readyReadStandardOutput
delivers tokens directly to QML (no polling, no signals, no offsets)
- State: 'read statewait' process, restarts on each state change
- Session list: 'read session/idx' one-shot
- Submit/stop/kill: 'write prompt/ctl' one-shot
The QML is stripped to fundamentals: session list + monospace streaming
text (last 8KB tail) + input field. No ChatBlockModel, no ThemeManager,
no OllieDBusClient, no syntax highlighting, no markdown rendering.
This eliminates the multi-second GUI freezes caused by:
- QTextDocument relayout on 200KB+ accumulated text
- loadEarlier cascade creating hundreds of delegates
- KSyntaxHighlighting::Repository blocking init
- Offset mismatch triggering synchronous D-Bus calls
Features will be re-added incrementally on this proven foundation.
D-Bus retained only for plasmoid/tray/KRunner (desktop integration).
The mermaid container has been replaced with a proper mermaid.ink
server. Use /img/ endpoint (returns JPEG) instead of /svg/ since
Qt's SVG renderer can't handle CSS class-based styles. Falls back
to mmdc CLI with PNG output if server is unavailable.
Qt's SVG renderer doesn't support CSS class-based styling that mermaid
generates (<style> blocks with class selectors). This caused text and
connectors to be invisible — they had no fill/stroke applied.
Fix: output PNG from mmdc for mermaid diagrams. Qt renders PNG perfectly.
PlantUML and Draw.io continue using SVG (they use inline styles).
Text and connectors were invisible because mmdc rendered with a dark
theme (white text/edges) on the white diagram background. Explicitly
pass -t default to force dark text on light background.
- Detect mermaid/plantuml/drawio/mxgraph code fences as 'diagram' sub-blocks
- Render via local servers (PlantUML :8080, Mermaid :8081, draw.io :8082)
- Fall back to CLI tools (plantuml, mmdc, drawio) if servers unavailable
- Cache rendered SVGs in ~/.cache/*/diagrams/ keyed by content hash
- Show rendered image with white background (dark theme friendly)
- Collapsible source toggle (▶/▼) below rendered diagram
- Middle-click to invalidate cache and re-render
- Left-click opens fullscreen pannable/zoomable viewer
- Fullscreen: scroll-wheel zoom, drag to pan, Esc/right-click/dbl-click to close
- Both kf5 and kf6 builds
Adds a filterText property to ChatBlockModel that filters all
parsed blocks by case-insensitive substring match on content
or tool/call name. Toggle the filter bar with Ctrl+F; Escape
or the close button clears the filter and hides the bar.
Shows match count while filtering.
Detect video URLs (YouTube, Vimeo, Dailymotion, Twitch, direct media)
in chat responses. Direct .mp4/.webm/.ogv files play inline via
QtMultimedia Video. Other video sites show a card with play button
that opens in the system browser.
Adds Qt6::Multimedia dependency.
Incremental parsing caused phantom blocks and disappearing content.
The 150ms coalescing on the daemon side is sufficient for perf.
updateVisibleWindow() extraction kept for clean separation.
When the tail contains only one header (common case during streaming),
just update the last block content directly. Fall back to full reparse
only when a genuinely new block boundary appears.
reparseIncremental() only re-parses from the last block boundary
instead of the entire chat text. Fixes UI lag and typing blockage
during long streaming responses.
Also extracted updateVisibleWindow() for reuse.
- Replace Qt.callLater scroll debounce with Timer (16ms) to coalesce
rapid contentHeight changes and eliminate scroll flickering
- Add execute button (play icon) on code blocks for scripted languages
(bash, python, perl, ruby, etc.) — launches in terminal
- Fix assistant bubble width: always use 95% of available width
- Add minimum width (280px) to code fence blocks
- Move emoji popup into prompt RowLayout so it appears near input
- Disable wheel scroll inertia (direct contentY) while keeping flick
inertia intact
- Apply all fixes to both KF5 and KF6 QML files
HTML RichText collapses multiple spaces, destroying JSON indentation.
Wrap all highlighted output in <pre> tags and use real newlines instead
of <br>. pre-wrap allows wrapping at container width.
QSyntaxHighlighter applies colors to the layout only (not accessible
via QTextBlock iteration). Replace with a custom AbstractHighlighter
subclass (HtmlBuilder) that builds HTML directly in applyFormat().
This actually produces colored output.
QML scope issues made JS-based formatting unreliable. New approach:
- FormattedContentRole in the model computes pretty-printed HTML in C++
- Call blocks: always prettyJson()
- Tool blocks: detect JSON/diff and format, otherwise escaped HTML
- QML just binds text: block.formattedContent || ""
The onVisibleChanged approach failed because block.blockContent could
not resolve through the Loader scope. Now using direct binding:
- call blocks: text bound to chatModel.prettyJson(block.blockContent)
- tool blocks: detect JSON/diff, format accordingly
- prettyJson() uses QJsonDocument for proper indentation + KSyntaxHighlighting
- Only evaluated when visible (lazy)
- Code blocks show plain text immediately, highlight via Timer after
150ms (prevents blocking the render thread during initial load)
- Reduced initial blocks from 40 to 20
- Detect append vs full reset in setRawText for better session switch
- Model shows last 40 blocks initially, positioned at end
- Scrolling near top triggers loadEarlier() which prepends 30 blocks
- Scroll position maintained after prepend via contentY adjustment
- All blocks still parsed (for searching later) but only tail rendered
- Reduced initial render cost significantly
With 300KB+ chat logs producing hundreds of blocks, instantiating all
delegates was causing unresponsiveness. Cap the model at the last 80
blocks and set cacheBuffer: 200 to limit off-screen rendering.