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

23
.kiro/steering/product.md Normal file
View File

@@ -0,0 +1,23 @@
---
inclusion: always
---
# Product Overview
AI Sidebar is a slide-in chat interface for the Ignis desktop environment that provides local AI assistance through Ollama integration.
## Core Features
- Slide-in sidebar from the left side with smooth animations
- Local AI chat using Ollama models
- Automatic conversation persistence across sessions
- Material Design 3 theming that matches Ignis
- Keyboard shortcut toggle support
- Automatic Ollama availability monitoring with graceful degradation
## User Experience
- Clicking outside the sidebar closes it (same as QuickCenter)
- Conversations are automatically saved to `~/.config/ignis/modules/aisidebar/data/conversations/`
- The UI gracefully handles Ollama being unavailable and notifies users when connectivity is restored
- Default width is 400px to match QuickCenter

View File

@@ -0,0 +1,70 @@
---
inclusion: always
---
# Project Structure
## File Organization
```
aisidebar/
├── __init__.py # Module exports (AISidebar class)
├── aisidebar.py # Main RevealerWindow implementation
├── chat_widget.py # Chat UI widget with message handling
├── ollama_client.py # HTTP client for Ollama REST API
├── ollama_monitor.py # Availability monitoring with callbacks
├── conversation_manager.py # Conversation persistence layer
└── data/
└── conversations/ # JSON conversation files (auto-created)
└── default.json # Default conversation transcript
```
## Module Responsibilities
### `aisidebar.py`
- Main window class extending `widgets.RevealerWindow`
- Handles slide-in animation from left side
- Manages window visibility and keyboard focus
- Integrates with Ignis WindowManager
### `chat_widget.py`
- Complete chat UI implementation
- Message list rendering and scrolling
- Input handling and submission
- Background thread management for AI requests
- Ollama availability monitoring integration
### `ollama_client.py`
- Low-level HTTP client for Ollama API
- Model listing with caching
- Blocking chat API calls
- Connection health checking
- Graceful error handling without exceptions
### `ollama_monitor.py`
- Periodic availability checking (30s interval)
- Callback-based state change notifications
- GLib timeout integration for non-blocking checks
### `conversation_manager.py`
- JSON-based conversation persistence
- Atomic file writes for data safety
- Message validation (system/user/assistant roles)
- Timestamp tracking for messages
## Naming Conventions
- Private methods/attributes: `_method_name`, `_attribute_name`
- Widget references: `self._widget_name` (e.g., `self._entry`, `self._message_list`)
- CSS classes: kebab-case (e.g., `ai-sidebar`, `ai-sidebar-content`)
- Constants: UPPER_SNAKE_CASE (e.g., `VALID_ROLES`, `DEFAULT_CONVERSATION_ID`)
## Code Style
- Type hints on function signatures
- Docstrings for classes and public methods
- Dataclasses for structured data (`ConversationState`)
- Context managers for file operations
- Property decorators for computed attributes
- Threading: daemon threads for background work
- Error messages: user-friendly with actionable instructions

63
.kiro/steering/tech.md Normal file
View File

@@ -0,0 +1,63 @@
---
inclusion: always
---
# Technology Stack
## Framework & Environment
- **Platform**: Ignis desktop environment (Python-based GTK4 framework)
- **Python Version**: 3.10+
- **UI Framework**: GTK4 via Ignis widgets
- **Async/Threading**: GLib for main loop, Python threading for background tasks
## Key Dependencies
- `ignis` - Desktop environment framework providing widgets and window management
- `ollama` - Python package for Ollama API integration
- GTK4 (`gi.repository.GLib`) - UI toolkit and event loop
## Architecture Patterns
### Widget System
- Uses Ignis widget abstractions (`widgets.Box`, `widgets.RevealerWindow`, etc.)
- Material Design 3 styling via CSS classes
- Revealer-based slide animations
### API Communication
- Direct HTTP calls to Ollama REST API (no external HTTP library)
- Uses `urllib.request` for HTTP operations
- Timeout handling: 2s for health checks, 5s for model lists, 120s for chat
### State Management
- Conversation persistence via JSON files
- Atomic file writes using `tempfile` and `os.replace()`
- In-memory caching for model lists
### Threading Model
- UI operations on GLib main thread
- AI requests in background daemon threads
- `GLib.idle_add()` for thread-safe UI updates
### Error Handling
- Graceful degradation when Ollama is unavailable
- Availability monitoring with 30-second polling interval
- User-facing error messages instead of exceptions
## Common Commands
Since this is an Ignis module, there are no build/test commands. The module is loaded directly by Ignis:
```bash
# Reload Ignis to apply changes
ignis reload
# Run Ignis with console output for debugging
ignis
# Check Ollama status
curl http://127.0.0.1:11434/api/tags
# List installed Ollama models
ollama list
```