Commit Graph

53 Commits

Author SHA1 Message Date
Levi Neely 07232b94df Add PlantUML and Mermaid diagram rendering
- 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
2026-08-07 14:59:20 +02:00
Levi Neely 1ca5563fdb gui: stream partial content to UI before newlines
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.
2026-08-07 10:11:14 +02:00
Levi Neely 28de16dbf1 fix(gui): rewrite chat block rendering for proper syntax highlighting
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.
2026-08-03 16:16:43 +02:00
Levi Neely 34d4a97b3d gui: complete KF5 build with full feature parity
- 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)
2026-08-03 09:03:12 +02:00
Levi Neely 7cb809fad2 revert all system-user-interruption changes 2026-08-03 00:41:49 +02:00
Levi Neely 64883cf9b8 Revert "render system-user-interruption as styled yellow box in prose"
This reverts commit 4e455e2dd3.
2026-08-03 00:39:34 +02:00
Levi Neely 4e455e2dd3 render system-user-interruption as styled yellow box in prose 2026-08-03 00:38:01 +02:00
Levi Neely e08b69c152 fix build: replace-with-lambda requires Qt 6.5+, use manual global-match loop 2026-08-03 00:32:26 +02:00
Levi Neely 1fdae741c3 render system-user-interruption as a styled yellow box with warning icon 2026-08-03 00:31:08 +02:00
Levi Neely cf4313165b fix fence close regex: only match backtick on its own line 2026-08-03 00:27:25 +02:00
Levi Neely b841f556d4 gui: preserve newlines in plaintext blocks 2026-08-03 00:24:52 +02:00
Levi Neely b50a2e01b2 fix source block rendering 2026-08-02 23:48:00 +02:00
Levi Neely c983e2bb6a simplify rendering 2026-08-02 22:43:11 +02:00
Levi Neely 753467db5b fix: preserve pre-fence text and post-fence content in rendered blocks
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.
2026-08-02 11:13:22 +02:00
Levi Neely 67b33f95d2 refactor: StreamFsm lifecycle + chatblock model FSM fix
- 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
2026-08-02 11:11:05 +02:00
Levi Neely 8617aa5293 gui: render user-interrupt with yellow background and warning icon
- 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
2026-08-01 17:11:52 +02:00
Levi Neely 998efc15c5 chat log: switch to [[[type]]]/[[[end]]] block format
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
2026-08-01 16:48:57 +02:00
Levi Neely 3a33c4e42a add retry block support: Retry type, header color, expanded default 2026-08-01 12:45:33 +02:00
Levi Neely 7d75ba8524 gui: preserve original indentation in fenced code/diff blocks
- 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
2026-08-01 12:00:14 +02:00
Levi Neely 24edd5941a gui: syntax-highlight fenced code/diff blocks at stream end
- 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
2026-08-01 11:36:43 +02:00
Levi Neely df41aad5cb trim chat to last 30 blocks on initial load and session switch 2026-08-01 10:19:22 +02:00
Levi Neely 857a919b64 streaming INI parser: ChatBlockModel with per-block decorations 2026-08-01 09:37:48 +02:00
Levi Neely e7b3291826 cleanup: delete all dead D-Bus code and unused UI components
Removed:
- plasmoid/ (entire directory — QML plugin, D-Bus client, metadata)
- tray/ (entire directory — KStatusNotifierItem, D-Bus watcher)
- gui/chatblockmodel.* (replaced by plain text streaming)
- gui/thememanager.* (unused — Basic style handles palette)
- gui/ChatPane.qml, SessionGraph.qml (complex delegate system, gone)
- gui/emojis.js (emoji shortcode completion, can re-add later)
- gui/main-kf5.qml (duplicate of main.qml)

What remains in gui/:
- main.cpp, main.qml — 9P-based minimal GUI
- ollie9pclient.cpp/h — QProcess-based 9P transport
- qml.qrc, ollie.svg, desktop file
2026-07-31 20:44:22 +02:00
Levi Neely f4161fcabc gui: replace D-Bus with native 9P streaming
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).
2026-07-31 20:06:05 +02:00
Levi Neely 5bdab3c616 remove emoji reaction feature (KDE GUI) 2026-07-31 09:09:30 +02:00
Levi Neely c76a144de2 diagrams: re-enable mermaid server via /img/ (raster) endpoint
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.
2026-07-29 16:40:18 +02:00
Levi Neely a598d0c5cc diagrams: render mermaid as PNG instead of SVG
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).
2026-07-29 16:38:34 +02:00
Levi Neely 3b27bccdda diagrams: force light theme for mermaid rendering
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.
2026-07-29 16:25:36 +02:00
Levi Neely 29c74b2837 feat(gui): render PlantUML, Mermaid, and draw.io diagrams as images
- 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
2026-07-29 15:23:29 +02:00
Levi Neely 1cce42f2d6 fix: setReactions updates blocks in-place with dataChanged signal 2026-07-27 19:50:53 +02:00
Levi Neely d5f0770b0a structured reactions via getReactions/setReactions, remove broken chat-text parsing 2026-07-27 19:47:21 +02:00
Levi Neely 6e02ea4ca3 fix: update assistant block reaction badge on incremental parse 2026-07-27 19:33:15 +02:00
Levi Neely 042acf347e fix: parse reaction markers from [user] block content 2026-07-27 19:30:10 +02:00
Levi Neely cf1f0f75d9 pin reaction badge on assistant blocks, hide reaction buttons after reacting 2026-07-27 19:27:20 +02:00
Levi Neely e2592d821f propagate response IDs through chat models to reactTo 2026-07-27 19:12:12 +02:00
Levi Neely dc18436bae gui: Ctrl+F filter bar to search chat messages
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.
2026-07-27 09:37:26 +02:00
Levi Neely b9e02a972a gui: inline video embeds in chat
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.
2026-07-18 18:46:35 +02:00
Levi Neely 7703d06430 gui: revert incremental parsing, use full reparse always
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.
2026-07-18 18:12:10 +02:00
Levi Neely c46f38fc93 gui: fix incremental parser producing phantom tool_call blocks
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.
2026-07-18 18:04:42 +02:00
Levi Neely 7ccce95445 gui: incremental chat parsing during streaming
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.
2026-07-18 17:36:36 +02:00
Levi Neely 9e0e5cb0f4 Improve KDE GUI file and command handling 2026-07-16 19:42:21 +02:00
Levi Neely 1d646f7fab gui: fix flickering, add code exec button, fix bubble width, improve scroll
- 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
2026-07-16 13:20:08 +02:00
Levi Neely 2929bb6b04 kde: visual enhancements, emoji completion, scroll/layout fixes
ollie-gui:
- Structured chat blocks (user/assistant bubbles, tool/call with left borders)
- Tool/call blocks always visible (not collapsible), whitespace preserved
- User interrupt blocks (amber warning style)
- Copy buttons on bubbles (32x32, hover-visible)
- Emoji completion with popup selector + quick emoticons
- Backend/model display in header bar
- Session state updates on switch
- Scroll flicker fix (debounced, guarded)
- Null guards on delegate block properties
- Binding loop fixes

Kate plugin:
- Diff viewer integration via Kate DiffWidget (libkateprivate)
- Diff mode config (unified/side-by-side/raw)
- Emoji processing in prompt input (quick emoticons + :shortcode:)
- Increased chat font size
- Scroll position preservation

Dbus client:
- activeModel/activeBackend properties from GetConfig
2026-07-16 11:19:22 +02:00
Levi Neely cc24400ba7 fix indentation: wrap highlighted HTML in <pre> with white-space:pre-wrap
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.
2026-07-16 07:20:55 +02:00
Levi Neely 5c093a3bb2 fix syntax highlighting: use AbstractHighlighter directly
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.
2026-07-16 07:19:09 +02:00
Levi Neely 93c9bd995e move call/tool formatting entirely to C++ via formattedContent role
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 || ""
2026-07-16 07:07:51 +02:00
Levi Neely c39e0f2723 fix call/tool pretty-print: use C++ prettyJson() with QJsonDocument
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)
2026-07-16 07:00:40 +02:00
Levi Neely 79656596e4 fix startup freeze: async highlighting + reduce initial blocks to 20
- 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
2026-07-16 06:49:20 +02:00
Levi Neely f122a69bce reverse-scroll chat: start at bottom, lazy-load on scroll up
- 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
2026-07-16 06:46:47 +02:00
Levi Neely f1a045077b limit visible blocks to 80 + reduce cacheBuffer for startup perf
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.
2026-07-16 06:44:11 +02:00