Merged from feature/interrupt-on-type branch. Backend cancellation infrastructure: - Added tokio watch channel to SessionState for cancellation signaling - Implemented cancel_chat command - Modified chat command to use tokio::select! for racing requests vs cancellation - When cancelled, HTTP request to Ollama is dropped and returns early - Added tokio dependency with sync feature Story updates: - Story 13: Updated to use Stop button pattern (industry standard) - Story 18: Created placeholder for streaming responses - Stories 15-17: Placeholders for future features Frontend changes: - Removed auto-interrupt on typing behavior (too confusing) - Backend infrastructure ready for Stop button implementation Note: Story 13 UI (Stop button) not yet implemented - backend ready
11 KiB
Functional Spec: UI/UX Responsiveness
Problem
Currently, the chat command in Rust is an async function that performs a long-running, blocking loop (waiting for LLM, executing tools). While Tauri executes this on a separate thread from the UI, the frontend awaits the entire result before re-rendering. This makes the app feel "frozen" because there is no feedback during the 10-60 seconds of generation.
Solution: Event-Driven Feedback
Instead of waiting for the final array of messages, the Backend should emit Events to the Frontend in real-time.
1. Events
chat:token: Emitted when a text token is generated (Streaming text).chat:tool-start: Emitted when a tool call begins (e.g.,{ tool: "git status" }).chat:tool-end: Emitted when a tool call finishes (e.g.,{ output: "..." }).
2. Implementation Strategy (MVP)
For this story, we won't fully implement token streaming (as reqwest blocking/async mixed with stream parsing is complex). We will focus on State Updates:
- Refactor
chatcommand:- Instead of returning
Vec<Message>at the very end, it accepts aAppHandle. - Inside the loop, after every step (LLM response, Tool Execution), emit an event
chat:updatecontaining the current partial history. - The Frontend listens to
chat:updateand re-renders immediately.
- Instead of returning
3. Visuals
- Loading State: The "Send" button should show a spinner or "Stop" button.
- Auto-Scroll: The chat view should stick to the bottom as new events arrive.
Tool Output Display
Problem
Tool outputs (like file contents, search results, or command output) can be very long, making the chat history difficult to read. Users need to see the Agent's reasoning and responses without being overwhelmed by verbose tool output.
Solution: Collapsible Tool Outputs
Tool outputs should be rendered in a collapsible component that is closed by default.
Requirements
- Default State: Tool outputs are collapsed/closed when first rendered
- Summary Line: Shows essential information without expanding:
- Tool name (e.g.,
read_file,exec_shell) - Key arguments (e.g., file path, command name)
- Format: "▶ tool_name(key_arg)"
- Example: "▶ read_file(src/main.rs)"
- Example: "▶ exec_shell(cargo check)"
- Tool name (e.g.,
- Expandable: User can click the summary to toggle expansion
- Output Display: When expanded, shows the complete tool output in a readable format:
- Use
<pre>or monospace font for code/terminal output - Preserve whitespace and line breaks
- Limit height with scrolling for very long outputs (e.g., max-height: 300px)
- Use
- Visual Indicator: Clear arrow or icon showing collapsed/expanded state
- Styling: Consistent with the dark theme, distinguishable from assistant messages
Implementation Notes
- Use native
<details>and<summary>HTML elements for accessibility - Or implement custom collapsible component with proper ARIA attributes
- Tool outputs should be visually distinct (border, background color, or badge)
- Multiple tool calls in sequence should each be independently collapsible
Scroll Bar Styling
Problem
Visible scroll bars create visual clutter and make the interface feel less polished. Standard browser scroll bars can be distracting and break the clean aesthetic of the dark theme.
Solution: Hidden Scroll Bars with Maintained Functionality
Scroll bars should be hidden while maintaining full scroll functionality.
Requirements
- Visual: Scroll bars should not be visible to the user
- Functionality: Scrolling must still work perfectly:
- Mouse wheel scrolling
- Trackpad scrolling
- Keyboard navigation (arrow keys, page up/down)
- Auto-scroll to bottom for new messages
- Cross-browser: Solution must work on Chrome, Firefox, and Safari
- Areas affected:
- Main chat message area (vertical scroll)
- Tool output content (both vertical and horizontal)
- Any other scrollable containers
Implementation Notes
- Use CSS
scrollbar-width: nonefor Firefox - Use
::-webkit-scrollbar { display: none; }for Chrome/Safari/Edge - Maintain
overflow: autooroverflow-y: scrollto preserve scroll functionality - Ensure
overflow-x: hiddenwhere horizontal scroll is not needed - Test with very long messages and large tool outputs to ensure no layout breaking
Text Alignment and Readability
Problem
Center-aligned text in a chat interface is unconventional and reduces readability, especially for code blocks and long-form content. Standard chat UIs align messages differently based on the sender.
Solution: Context-Appropriate Text Alignment
Messages should follow standard chat UI conventions with proper alignment based on message type.
Requirements
- User Messages: Right-aligned (standard pattern showing messages sent by the user)
- Assistant Messages: Left-aligned (standard pattern showing messages received)
- Tool Outputs: Left-aligned (part of the system/assistant response flow)
- Code Blocks: Always left-aligned regardless of message type (for readability)
- Container: Remove any center-alignment from the chat container
- Max-Width: Maintain current max-width constraint (e.g., 768px) for optimal readability
- Spacing: Maintain proper padding and visual hierarchy between messages
Implementation Notes
- Check for
textAlign: "center"in inline styles and remove - Check for
text-align: centerin CSS and remove from chat-related classes - Ensure flexbox alignment is set appropriately:
- User messages:
alignItems: "flex-end" - Assistant/Tool messages:
alignItems: "flex-start"
- User messages:
- Code blocks should have
text-align: leftexplicitly set
Syntax Highlighting
Problem
Code blocks in assistant responses currently lack syntax highlighting, making them harder to read and understand. Developers expect colored syntax highlighting similar to their code editors.
Solution: Syntax Highlighting for Code Blocks
Integrate syntax highlighting into markdown code blocks rendered by the assistant.
Requirements
- Languages Supported: At minimum:
- JavaScript/TypeScript
- Rust
- Python
- JSON
- Markdown
- Shell/Bash
- HTML/CSS
- SQL
- Theme: Use a dark theme that complements the existing dark UI (e.g.,
oneDark,vsDark,dracula) - Integration: Work seamlessly with
react-markdowncomponent - Performance: Should not significantly impact rendering performance
- Fallback: Plain monospace text for unrecognized languages
- Inline Code: Inline code (single backticks) should maintain simple styling without full syntax highlighting
Implementation Notes
- Use
react-syntax-highlighterlibrary withreact-markdown - Or use
rehype-highlightplugin forreact-markdown - Configure with a dark theme preset (e.g.,
oneDarkfromreact-syntax-highlighter/dist/esm/styles/prism) - Apply to code blocks via
react-markdowncomponents prop:<Markdown components={{ code: ({node, inline, className, children, ...props}) => { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter style={oneDark} language={match[1]} {...props}> {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}>{children}</code> ); } }} /> - Ensure syntax highlighted code blocks are left-aligned
- Test with various code samples to ensure proper rendering
Input Focus Management
Problem
When the app loads with a project selected, users need to click into the chat input box before they can start typing. This adds unnecessary friction to the user experience.
Solution: Auto-focus on Component Mount
The chat input field should automatically receive focus when the chat component mounts, allowing users to immediately start typing.
Requirements
- Auto-focus: Input field receives focus automatically when chat component loads
- Visible Cursor: Cursor should be visible and blinking in the input field
- Immediate Typing: User can start typing without clicking into the field
- Non-intrusive: Should not interfere with other UI interactions or accessibility
- Timing: Focus should be set after the component fully mounts
Implementation Notes
- Use React
useRefto create a reference to the input element - Use
useEffectwith empty dependency array to run once on mount - Call
inputRef.current?.focus()in the effect - Ensure the ref is properly attached to the input element
- Example implementation:
const inputRef = useRef<HTMLInputElement>(null); useEffect(() => { inputRef.current?.focus(); }, []); return <input ref={inputRef} ... />
Response Interruption
Problem
Users may want to interrupt a long-running model response to ask a different question or change direction. Having to wait for the full response to complete creates friction and wastes time.
Solution: Interrupt on Typing
When the user starts typing in the input field while the model is generating a response, the generation should be cancelled immediately, allowing the user to send a new message.
Requirements
- Input Always Enabled: The input field should remain enabled and usable even while the model is generating
- Interrupt Detection: Detect when user types in the input field while
loadingstate is true - Immediate Cancellation: Cancel the ongoing generation as soon as typing is detected
- Preserve Partial Response: Any partial response generated before interruption should remain visible in the chat
- State Reset: UI should return to normal state (ready to send) after interruption
- Preserve User Input: The user's new input should be preserved in the input field
- Visual Feedback: "Thinking..." indicator should disappear when generation is interrupted
Implementation Notes
- Do NOT disable the input field during loading
- Listen for input changes while
loadingis true - When user types during loading, call backend to cancel generation (if possible) or just stop waiting
- Set
loadingstate to false immediately when typing detected - Backend may need a
cancel_chatcommand or similar - Consider if Ollama requests can be cancelled mid-generation or if we just stop processing the response
- Example implementation:
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; setInput(newValue); // If user starts typing while model is generating, interrupt if (loading && newValue.length > input.length) { setLoading(false); // Optionally call backend to cancel: invoke("cancel_chat") } };