Commit Graph

171 Commits

Author SHA1 Message Date
Levi Neely 541ef48bb7 Refactor GUI into session tree and chat pane components 2026-08-16 12:56:49 +02:00
Levi Neely 0e163f6ee6 gui: fix focus stealing from dialogs during session tree rebuild
buildSessionTree() and updateSessionTree() now save/restore
Window.activeFocusItem instead of only promptInput.activeFocus.
This prevents sessionTreeModel.clear() from pulling focus out of
open modal dialogs (NewSessionDialog, NewAgentDialog).

Also add forceActiveFocus() on dialog open so the first field is
immediately focused.
2026-08-14 14:27:37 +02:00
Levi Neely f136c3e534 revert: scroll fix caused issues, restore positionViewAtEnd 2026-08-13 22:37:12 +02:00
Levi Neely 5057b1a3dd gui: fix scroll jitter — use contentY instead of positionViewAtEnd, increase timer interval 2026-08-13 22:30:59 +02:00
Levi Neely c6da563109 gui: update slash command hints to match ctl surface
The agent now routes all /commands to ctl. Update the autocomplete
hint list to reflect current ctl commands.
2026-08-09 03:17:21 +02:00
Levi Neely eed4a1089e gui: restore scroll-follow with debounced Timer
Use a 16ms Timer restarted from onContentHeightChanged and
onCountChanged. The timer fires after the polish pass completes,
avoiding the recursive layout loop that the old synchronous
positionViewAtEnd() caused.
2026-08-09 00:16:46 +02:00
Levi Neely 022980bc40 gui: fix infinite layout loop in chat ListView
The delegate used a ColumnLayout root with parent-dependent width
fallbacks (: 0, : 100) causing two-pass layouts. Combined with
onContentHeightChanged -> positionViewAtEnd(), this created a
feedback loop: height change -> scroll -> refill -> new delegates ->
height change -> ... burning 100% CPU on the main thread.

Fixes:
- Replace onContentHeightChanged with onCountChanged + Qt.callLater
- Replace ColumnLayout root with plain Column
- Bind all child widths to root.width (top-down, no fallbacks)
- Create context Menu lazily on first right-click
- Add reuseItems: true to the ListView
- Standardize bin path to ~/.local/bin
2026-08-09 00:03:37 +02:00
Levi Neely 66aecc8315 Revert "gui: fix freeze on agent switch — bulk load chat blocks"
This reverts commit a3826f2ce8.
2026-08-08 23:15:48 +02:00
Levi Neely a3826f2ce8 gui: fix freeze on agent switch — bulk load chat blocks
appendContent was calling rebuildBlockHtml (full syntax highlighting)
on every single line during initial log load. For a 1000-line chat,
that's O(n²) highlighting passes.

Add beginBulkLoad()/endBulkLoad() — skips per-line rebuilds during
bulk, then rebuilds all blocks once at the end. QML load sites now
wrap appendData(log) with bulk mode.
2026-08-08 23:14:55 +02:00
Levi Neely a4c6802430 gui: add scroll-to-bottom button
Shows when user has scrolled up (followTail=false). Clicking scrolls
to bottom and re-enables follow mode.
2026-08-07 18:53:32 +02:00
Levi Neely 4b21deec14 gui: fix scrolling to follow content
Use onContentHeightChanged with a re-entry guard to scroll as content
renders. Works for both initial log load and streaming responses.

- followTail tracks user preference (updated on scroll end)
- scrollingNow prevents recursive positionViewAtEnd calls
2026-08-07 18:51:20 +02:00
Levi Neely cde0284bad gui: fix scrolling - use contentHeight not timer
Replace timer-based scroll hack with event-driven approach:
- onContentHeightChanged triggers scroll when content grows
- onMovementEnded detects when user scrolled away from bottom

Scrolling is now driven by actual content changes, not arbitrary delays.
2026-08-07 18:14:09 +02:00
Levi Neely 073cc2158b qml: fix binding loop in ChatBlockDelegate
Rename 'chatView' property to 'parentListView' to avoid name collision
with the ListView id, which caused QML binding loop warnings.
2026-08-07 17:49:26 +02:00
Levi Neely 2d6e0ed787 gui: extract dialogs and chat delegate into components
Phase 1 of GUI cleanup - component extraction:
- SettingsDialog.qml: theme and font settings (118 lines)
- NewSessionDialog.qml: session creation form (79 lines)
- NewAgentDialog.qml: agent creation form (174 lines)
- ChatBlockDelegate.qml: chat block with text/diagram segments (322 lines)

main.qml reduced from 1685 to 1030 lines (39% reduction).
Updated CMakeLists.txt for both Qt6 and KF5 builds.
2026-08-07 17:40:09 +02:00
Levi Neely bdad2b740f remove scroll-to-bottom button 2026-08-07 17:17:14 +02:00
Levi Neely b77667fbfb Fix QML performance and stability issues
- Fix TypeError by using explicit id instead of parent.parent chains
- Remove onContentHeightChanged handler that caused layout thrashing
- Add active guard on Loader to prevent null modelData access
- Simplify blockSegments null checks
2026-08-07 15:25:16 +02:00
Levi Neely 0ac03a9931 Fix chat rendering and improve scroll behavior
- Emit SegmentsRole in dataChanged signals so QML updates properly
- Add onContentHeightChanged to scroll when content loads after session switch
- Add floating scroll-to-bottom button that appears when scrolled up
- Wrap chat ScrollView in Item to allow button overlay
2026-08-07 15:15:05 +02:00
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 2ea6ec1ba7 gui: fix scrolling in chat and message input
- Restore ScrollView for message input to enable overflow scrolling
- Remove manual wheel handlers that broke ListView virtualization
- Let wheel events propagate naturally to ListView
2026-08-07 11:20:13 +02:00
Levi Neely df8e69aeb8 gui: I-beam cursor over block content context menu area 2026-08-07 11:01:09 +02:00
Levi Neely f086400bf3 gui: add text selection and context menus
Chat blocks:
- Enable selectByMouse and persistentSelection for text selection
- Add context menu with Copy and Select All
- Disable ListView drag during text selection (preserves wheel scroll)
- I-beam cursor over text content

Prompt input:
- Enable selectByMouse and persistentSelection
- Add context menu with Cut, Copy, Paste, Select All
- Remove ScrollView wrapper (TextArea handles overflow natively)
- I-beam cursor
2026-08-07 11:00:15 +02:00
Levi Neely 624b7ad66d gui: smart auto-scroll follows streaming content
- Add shouldAutoScroll property to track user intent
- Auto-scroll when near bottom (within 50px threshold)
- Disable auto-scroll when user scrolls up to read history
- Re-enable when user scrolls back to bottom
- Reset to auto-scroll on session/agent switch
- Increase timer interval to 16ms for smoother layout settling
2026-08-07 10:21:53 +02:00
Levi Neely 4acda0f7de gui: rename 'Human' to 'You' in chat block headers 2026-08-06 14:15:58 +02:00
Levi Neely 9887751690 gui: add sidebar toggle (Ctrl+B or click button in header)
Adds ability to hide/show the session list sidebar:
- Ctrl+B keyboard shortcut
- Toggle button (◀/▶) in the chat header
- Sidebar collapses to zero width when hidden
2026-08-06 13:42:36 +02:00
Levi Neely d01d7c8ad8 gui: add session connection status indicator
- Add colored dot to session rows showing tool server RPC status:
  green=connected, yellow=paused+connected, gray=paused, red=broken
- Remove vestigial agent.connected from model (was never displayed)
- Session-level connected status is the correct granularity
2026-08-05 08:35:43 +02:00
Levi Neely 9f3f962567 Remove sidebar state dot indicators 2026-08-04 16:33:21 +02:00
Levi Neely dbc1eabf55 gui: revert to working approach - activeState for selected agent only
Non-selected agents show idle (green). This matches the working
top indicator approach until we find a proper solution for tracking
all agent states reactively in QML.
2026-08-04 16:26:32 +02:00
Levi Neely 9929abc73b gui: use Connections to update agent state on revision change 2026-08-04 16:24:57 +02:00
Levi Neely 670d759769 gui: use revision inside binding expression for proper dependency tracking 2026-08-04 16:24:10 +02:00
Levi Neely 22980e376a gui: track all agent states via revision counter for proper QML binding
- Add agentStateRevision property that increments on any state change
- QML binds to revision to trigger re-evaluation of agentState() calls
- All agent dots now show correct live state, not just active agent
2026-08-04 16:23:04 +02:00
Levi Neely 1d79d0ee89 gui: agent tree dot uses ollie.activeState directly for active agent 2026-08-04 16:19:33 +02:00
Levi Neely dbd2606ed2 gui: agent tree dot uses ollie.agentState() like top indicator 2026-08-04 16:18:42 +02:00
Levi Neely 457a6a55dc gui: use explicit properties for agent state dot binding
Create agentState and isPaused properties to ensure proper binding
updates when model roles change via setProperty
2026-08-04 16:17:11 +02:00
Levi Neely 9e5352f700 gui: fix agent state dot colors and parentPaused propagation
- Update parentPaused on agents during incremental session updates
- Set parentPaused and parentExpanded when inserting new agents
- Update session paused property during updates
- Use explicit === true check for parentPaused
2026-08-04 16:15:40 +02:00
Levi Neely 3d739a9560 gui: gray indicators and 'inactive' label for paused sessions
- Agent dots in tree are gray when parent session is paused
- Top state indicator is gray when viewing agent in paused session
- Top state label shows 'inactive' instead of agent state when paused
2026-08-04 16:13:04 +02:00
Levi Neely 2b19cafc50 gui: show disconnected state visually, remove session connected/ended messages
- Session list shows '(not connected)' when daemon disconnected
- Chat area shows 'Not connected' placeholder when disconnected
- Message input disabled and shows 'Not connected' when disconnected
- Chat ListView hidden when disconnected
- Remove sessionConnected/streamingDone signals (visual feedback is sufficient)
2026-08-04 16:11:39 +02:00
Levi Neely 22302ce9f1 gui: session tree with Active/Paused groups, arbitrary-depth expand/collapse 2026-08-04 16:05:08 +02:00
Levi Neely 265a1fdda9 refactor: promote remote to session level, remove agent connectivity, formalize NinePConnection state machine
- Add createSession(name, remote) with New Session dialog
- Remove agent connectivity code (redundant with session connectivity)
- Refactor NinePConnection as formal state machine with explicit events
- Fix silent restart to not cycle through Connecting state
2026-08-04 15:22:08 +02:00
Levi Neely ec89eaae18 fix: always send stop on ESC regardless of cached state
Removes the activeState !== 'idle' check before sending the stop
command. The server-side Interrupt() is safe to call on an idle
agent (returns false, no-op).

This fixes an intermittent issue where pressing ESC wouldn't stop
the agent because the GUI's cached activeState was stale/out-of-sync
with the server's actual state.
2026-08-04 11:51:01 +02:00
Levi Neely 9d059943f2 cleanup: remove debug logging 2026-08-03 20:43:16 +02:00
Levi Neely 3fd13c6d38 fix: use state role directly for binding updates
model.state doesn't trigger binding re-evaluation in Qt5 delegates.
Using the implicit role 'state' directly does.
2026-08-03 20:31:17 +02:00
Levi Neely 1d0fb99501 targeted state dot updates in render path
- onAgentStateChanged: update specific agent in model instead of version bump
- State dot: use model.state directly instead of ollie.agentState() callback
- Remove ollie.isSessionPaused() call (was O(n) iteration per agent)
- Remove unused agentStateVersion property

Before: 1 state change → re-evaluate ALL agent dots → N * ollie.agentState() calls
After:  1 state change → setProperty on 1 model item → 1 binding update
2026-08-03 20:29:38 +02:00
Levi Neely 57b3ce04e3 eliminate C++ calls in QML update loop
- computeConnectionColor() in QML instead of ollie.sessionConnectionColor()
  (avoids O(n²) iteration through sessions list)
- Use agent.state from session/idx data directly instead of ollie.agentState()
  (avoids hash lookup per agent per update)

All data now flows from session/idx through m_sessions to QML with no
callbacks into C++ during the update loop.
2026-08-03 20:13:32 +02:00
Levi Neely 24921e7a2b incremental session tree updates
Instead of clearing and rebuilding the entire session tree model on
every sessionsChanged signal, diff old vs new and:
- Update existing session/agent properties in place
- Remove deleted sessions/agents
- Append new sessions/agents

This avoids O(n) model rebuilds that cause ListView to re-render all
delegates, improving responsiveness with many sessions.
2026-08-03 19:51:54 +02:00
Levi Neely 0c93414251 optimize QML rendering performance
- Remove console.log statements from hot paths
- Add sessionExpandedCache for O(1) expanded state lookup
- Replace O(n) parentSessionExpanded() loop with cache lookup
- Preserve expanded state across session tree rebuilds
2026-08-03 19:29:45 +02:00
Levi Neely eedee30332 session pause/resume UI
- Add Pause/Resume toggle to session context menu
- Gray agent dots when parent session is paused
- Disable input field with 'Session paused' placeholder when paused
- Preserve focus on prompt input during tree rebuilds
2026-08-03 18:36:18 +02:00
Levi Neely 453cef397f live agent state indicators in session tree
- Add per-agent statewait streams to track execution state
- Agent dots show: green=idle, orange=calling, blue=thinking
- Session dots show RPC connection health (unchanged)
- Use version counter pattern to force QML binding re-evaluation
2026-08-03 18:30:14 +02:00
Levi Neely 566aeddecc feat: show paused sessions as gray, add agent count to session name
- Fetch paused status from session/{id}/paused file
- Gray dot indicator for paused sessions (green otherwise)
- Session names now show agent count: 'ollie (2)'
- Fixed rename to use baseName for sessions (without count suffix)
2026-08-03 18:12:01 +02:00
Levi Neely 22a2208526 fix: show error dialog and reset name when rename fails
commitRename now checks the return value from renameSession/renameAgent.
On failure, resets displayName to the old value and shows an error dialog
explaining the rename failed (e.g., name already in use).
2026-08-03 17:53:38 +02:00
Levi Neely 414143955f fix(gui): improve multi-agent-per-session handling
Session selection now auto-selects first agent:
- Clicking a session row selects its first agent (not just the session)
- Auto-select at startup now picks first agent of first session
- Fixes issue where clicking session would clear agent and show empty chat

Improved UI feedback:
- Header shows 'Session / Agent' name instead of just state
- State indicator gray when no agent selected
- Placeholder message when no agent selected: 'Select an agent to view chat'
- Chat ListView hidden when no agent (shows placeholder instead)
- Input already disabled with 'No agent' placeholder (existing behavior)

Cleanup:
- Remove dead declaration reconcileAgentConnections(const QVariantList&)
2026-08-03 17:42:05 +02:00