refactor(aisidebar): restructure project and implement reasoning mode toggle

- Reorganize project structure and file locations
- Add ReasoningController to manage model selection and reasoning mode
- Update design and requirements for reasoning mode toggle
- Implement model switching between Qwen3-4B-Instruct and Qwen3-4B-Thinking models
- Remove deprecated files and consolidate project layout
- Add new steering and specification documentation
- Clean up and remove unnecessary files and directories
- Prepare for enhanced AI sidebar functionality with more flexible model handling
This commit is contained in:
Melvin Ragusa
2025-10-26 09:10:31 +01:00
parent 58bd935af0
commit 239242e2fc
73 changed files with 3094 additions and 2348 deletions

View File

@@ -233,7 +233,11 @@ class ConversationArchive:
```python
class ReasoningController:
"""Manages reasoning mode state and API parameters."""
"""Manages reasoning mode state and model selection."""
# Model names for reasoning toggle
INSTRUCT_MODEL = "hf.co/unsloth/Qwen3-4B-Instruct-2507-GGUF:Q8_K_XL"
THINKING_MODEL = "hf.co/unsloth/Qwen3-4B-Thinking-2507-GGUF:Q8_K_XL"
def __init__(self):
self._enabled = False
@@ -245,8 +249,9 @@ class ReasoningController:
def toggle(self) -> bool:
"""Toggle reasoning mode and persist preference."""
def get_chat_options(self) -> dict:
"""Return Ollama API options for reasoning mode."""
def get_model_name(self) -> str:
"""Return the appropriate model name based on reasoning mode."""
return self.THINKING_MODEL if self._enabled else self.INSTRUCT_MODEL
```
#### UI Components
@@ -254,41 +259,35 @@ class ReasoningController:
Add toggle button to header area:
```python
self._reasoning_toggle = Gtk.ToggleButton(label="🧠 Reasoning")
self._reasoning_toggle.connect("toggled", self._on_reasoning_toggled)
self._reasoning_toggle = widgets.Button(label="🧠 Reasoning: OFF")
self._reasoning_toggle.connect("clicked", self._on_reasoning_toggled)
```
#### Ollama Integration
When reasoning mode is enabled, pass additional options to Ollama:
When reasoning mode is toggled, switch between models:
```python
# Standard mode
ollama.chat(model=model, messages=messages)
# Get model based on reasoning mode
model = self._reasoning_controller.get_model_name()
# Reasoning mode (model-dependent)
ollama.chat(
model=model,
messages=messages,
options={
"temperature": 0.7,
# Model-specific reasoning parameters
}
)
# Use the selected model for chat
ollama.chat(model=model, messages=messages)
```
#### Message Formatting
When reasoning is enabled and model supports it:
When using the thinking model:
- Display thinking process in distinct style (italic, gray text)
- Separate reasoning from final answer with visual divider
- Use expandable/collapsible section for reasoning (optional)
- Parse `<think>` tags from model output to extract reasoning content
#### Persistence
- Save reasoning preference to `~/.config/aisidebar/preferences.json`
- Load preference on startup
- Apply to all new conversations
- Automatically switch models when preference changes
## Data Models

View File

@@ -62,15 +62,15 @@ This document outlines the requirements for enhancing the AI sidebar module for
### Requirement 5: Reasoning Mode Toggle
**User Story:** As a user, I want to enable or disable the model's reasoning output, so that I can choose whether to see the thinking process or just the final answer based on my needs.
**User Story:** As a user, I want to toggle between a reasoning model and an instruct model, so that I can choose whether to use a model that shows its thinking process or one that provides direct answers.
#### Acceptance Criteria
1. THE AI Sidebar SHALL provide a toggle button or control to enable reasoning mode
2. WHEN reasoning mode is enabled, THE AI Sidebar SHALL request and display the model's thinking process before the final answer
3. WHEN reasoning mode is disabled, THE AI Sidebar SHALL request and display only the final answer without intermediate reasoning
1. THE AI Sidebar SHALL provide a toggle button or control to switch between reasoning and instruct models
2. WHEN reasoning mode is enabled, THE AI Sidebar SHALL switch to the Qwen3-4B-Thinking model and display the model's thinking process
3. WHEN reasoning mode is disabled, THE AI Sidebar SHALL switch to the Qwen3-4B-Instruct model for direct answers
4. THE AI Sidebar SHALL persist the reasoning mode preference across conversation sessions
5. THE AI Sidebar SHALL visually distinguish reasoning content from final answer content when reasoning mode is enabled
5. THE AI Sidebar SHALL visually distinguish reasoning content from final answer content when using the thinking model
### Requirement 6: Graceful Ollama Unavailability Handling

View File

@@ -1,19 +1,19 @@
# Implementation Plan
- [ ] 1. Implement streaming response infrastructure
- [x] 1. Implement streaming response infrastructure
- Create StreamingHandler class in new file `streaming_handler.py` with token buffering, UI update methods, and stream state management
- Add `_handle_stream_token()` method to SidebarWindow that uses GLib.idle_add for thread-safe UI updates
- Implement token buffering logic (accumulate 3-5 tokens before UI update) to reduce overhead
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [ ] 2. Integrate streaming into SidebarWindow
- [x] 2. Integrate streaming into SidebarWindow
- Modify `_request_response()` to use `ollama_client.stream_chat()` instead of blocking `chat()`
- Update worker thread to iterate over stream and call `_handle_stream_token()` for each chunk
- Add streaming state indicator (visual feedback during generation)
- Handle stream errors and interruptions gracefully with try-except blocks
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [ ] 3. Replace single-line Entry with multi-line TextView
- [x] 3. Replace single-line Entry with multi-line TextView
- Replace `Gtk.Entry` with `Gtk.TextView` wrapped in `Gtk.ScrolledWindow` in `_build_ui()`
- Configure text view with word wrapping, min height 40px, max height 200px
- Implement key event controller to handle Enter (submit) vs Shift+Enter (newline)
@@ -21,37 +21,37 @@
- Update `_on_submit()` to extract text from TextView buffer instead of Entry
- _Requirements: 2.1, 2.2, 2.3, 2.4_
- [ ] 4. Create command processing system
- [x] 4. Create command processing system
- Create `command_processor.py` with CommandProcessor class
- Implement command parsing logic with `is_command()` and `execute()` methods
- Define CommandResult dataclass for structured command responses
- Add command registry dictionary mapping command strings to handler methods
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
- [ ] 5. Implement conversation management commands
- [ ] 5.1 Implement `/new` and `/clear` commands
- [x] 5. Implement conversation management commands
- [x] 5.1 Implement `/new` and `/clear` commands
- Add `_cmd_new_conversation()` method to save current conversation and reset to fresh state
- Clear message list UI and show confirmation message
- _Requirements: 3.1, 3.2_
- [ ] 5.2 Implement `/models` command
- [x] 5.2 Implement `/models` command
- Add `_cmd_list_models()` method to query and display available models
- Format model list with current model highlighted
- _Requirements: 3.3_
- [ ] 5.3 Implement `/model` command
- [x] 5.3 Implement `/model` command
- Add `_cmd_switch_model()` method to validate and switch active model
- Update model label in header UI
- _Requirements: 3.4_
- [ ] 5.4 Integrate CommandProcessor into SidebarWindow
- [x] 5.4 Integrate CommandProcessor into SidebarWindow
- Add CommandProcessor instance to SidebarWindow initialization
- Modify `_on_submit()` to check for commands before processing as user message
- Display command results as system messages with distinct styling
- _Requirements: 3.5_
- [ ] 6. Implement conversation archive system
- [ ] 6.1 Create ConversationArchive class
- [x] 6. Implement conversation archive system
- [x] 6.1 Create ConversationArchive class
- Create `conversation_archive.py` with ConversationArchive class
- Implement `list_conversations()` to scan storage directory and return metadata
- Implement `archive_conversation()` to save with timestamp-based ID format
@@ -59,60 +59,60 @@
- Define ConversationMetadata dataclass
- _Requirements: 4.1, 4.2_
- [ ] 6.2 Implement conversation loading
- [x] 6.2 Implement conversation loading
- Add `load_conversation()` method to ConversationArchive
- Handle JSON parsing errors and missing files gracefully
- Return ConversationState compatible with existing ConversationManager
- _Requirements: 4.4_
- [ ] 6.3 Implement `/list` and `/resume` commands
- [x] 6.3 Implement `/list` and `/resume` commands
- Add `_cmd_list_conversations()` to display archived conversations with metadata
- Add `_cmd_resume_conversation()` to load and display selected conversation
- Update SidebarWindow to repopulate message list from loaded conversation
- _Requirements: 4.3, 4.4, 4.5_
- [ ] 7. Implement reasoning mode toggle
- [ ] 7.1 Create ReasoningController class
- [x] 7. Implement reasoning mode toggle
- [x] 7.1 Create ReasoningController class
- Create `reasoning_controller.py` with ReasoningController class
- Implement preference persistence to `~/.config/aisidebar/preferences.json`
- Add `toggle()`, `is_enabled()`, and `get_chat_options()` methods
- Define PreferencesState dataclass
- _Requirements: 5.4_
- [ ] 7.2 Add reasoning toggle UI
- [x] 7.2 Add reasoning toggle UI
- Add ToggleButton to header area in `_build_ui()`
- Connect toggle signal to `_on_reasoning_toggled()` callback
- Update button state from persisted preference on startup
- _Requirements: 5.1_
- [ ] 7.3 Integrate reasoning mode with Ollama calls
- [x] 7.3 Integrate reasoning mode with Ollama calls
- Modify `_request_response()` to include reasoning options when enabled
- Pass model-specific parameters via `get_chat_options()`
- Handle both streaming and non-streaming modes with reasoning
- _Requirements: 5.2, 5.3_
- [ ] 7.4 Implement reasoning content formatting
- [x] 7.4 Implement reasoning content formatting
- Add visual distinction for reasoning content (italic, gray text, or expandable section)
- Separate reasoning from final answer with visual divider
- Update message rendering to handle reasoning metadata
- _Requirements: 5.5_
- [-] 8. Implement graceful Ollama unavailability handling
- [ ] 8.1 Update OllamaClient initialization
- [x] 8. Implement graceful Ollama unavailability handling
- [x] 8.1 Update OllamaClient initialization
- Modify `__init__()` to never raise exceptions during initialization
- Add connection check that sets internal availability flag
- Update `list_models()` to return empty list instead of raising on connection failure
- Update `chat()` and `stream_chat()` to return error messages instead of raising
- _Requirements: 6.1, 6.3, 6.5_
- [ ] 8.2 Create OllamaAvailabilityMonitor
- [x] 8.2 Create OllamaAvailabilityMonitor
- Create `ollama_monitor.py` with OllamaAvailabilityMonitor class
- Implement periodic availability checking using GLib.timeout_add (30s interval)
- Add callback mechanism to notify UI of state changes
- Ensure checks are non-blocking and don't impact UI responsiveness
- _Requirements: 6.4_
- [ ] 8.3 Update SidebarWindow for Ollama unavailability
- [x] 8.3 Update SidebarWindow for Ollama unavailability
- Initialize OllamaAvailabilityMonitor in SidebarWindow
- Display "Ollama not running" status message when unavailable at startup
- Update model label to show connection status
@@ -120,7 +120,7 @@
- Add callback to re-enable features when Ollama becomes available
- _Requirements: 6.1, 6.2, 6.4_
- [ ] 8.4 Add user-friendly error messages
- [x] 8.4 Add user-friendly error messages
- Display clear instructions when user tries to chat without Ollama
- Show notification when Ollama connection is restored
- Update all command handlers to check Ollama availability