feat(ui): complete Plan 06-01 - fix model scan + move model selector to input

- Fix Rust model scan to only list top-level models (not subdirectories)
- Add scan_model_dir_top_level() function in model_files.rs
- Create ModelSelectorPill component for chat input area
- Remove model-section from Sidebar, move to ChatView
- Update ChatView props to receive model-related props
- Update tests for new ChatView props
This commit is contained in:
Evandro Fonseca Junior committed 2026-04-02 09:06:06 -03:00
1 parent 3364dba5aa
commit cb722e79a6
11 files changed
+1695 -254

No files matched your search

@@ -0,0 +1,77 @@
---
phase: 06-chat-ui-redesign
plan: 01
status: complete
completed_at: "2026-04-02T12:00:00Z"
---
# Plan 06-01 Summary: Backend + ModelSelectorPill
## Completed Tasks
### Task 1: Fix Rust model scan to top-level only ✅
- **Status**: Already implemented
- `scan_model_dir_top_level()` function exists in `model_files.rs`
- `scan_models` Tauri command uses top-level scan (not recursive)
- All 5 tests pass including `scan_model_dir_returns_only_top_level_gguf`
### Task 2: Create ModelSelectorPill + wire into ChatView + clean Sidebar ✅
- **ModelSelectorPill.tsx**: Created at `tauri-app/src/components/ModelSelectorPill.tsx`
- Extracts model deduplication logic from Sidebar
- Renders compact pill button with status dot and dropdown
- Shows spinner when `isSwapping` is true
- Uses `getDisplayName` and `isInChatSelector` from modelAlias
- **Sidebar.tsx**: Cleaned
- Removed `model-section` block (lines 131-170)
- Removed model-related props: `models`, `localModels`, `selectedModel`, `onModelChange`, `onModelSwap`, `isSwapping`, `status`
- Removed `ModelInfo` and `RuntimeModelFile` imports
- **ChatView.tsx**: Updated
- Added model props to Props interface (lines 26-32)
- Imported `ModelSelectorPill` component
- Rendered `<ModelSelectorPill>` in hero input (line 627)
- Rendered `<ModelSelectorPill>` in regular input (line 851)
- **App.tsx**: Updated
- Removed model props from `<Sidebar>` component
- Added model props to `<ChatView>`: `models`, `localModels`, `selectedModel`, `onModelChange`, `onModelSwap`, `isSwapping`, `status`
- **ChatView.test.tsx**: Fixed
- Added `defaultChatViewProps` with required model props
- Spread `{...defaultChatViewProps}` to all test renders
## Verification Results
### TypeScript Build ✅
```
> tsc && vite build
✓ 6325 modules transformed.
✓ built in 5.50s
```
### Rust Tests ✅
```
running 5 tests
test model_files::tests::scan_model_dir_filters_and_sorts_gguf_files ... ok
test model_files::tests::scan_model_dir_ignores_directories_named_like_models ... ok
test model_files::tests::scan_model_dir_skips_hidden_directories ... ok
test model_files::tests::scan_model_dir_recursive_finds_nested_gguf ... ok
test model_files::tests::scan_model_dir_returns_only_top_level_gguf ... ok
test result: ok. 5 passed; 0 failed
```
## Files Modified
- `tauri-app/src-tauri/src/model_files.rs` (already had top-level scan)
- `tauri-app/src/components/ModelSelectorPill.tsx` (created)
- `tauri-app/src/components/Sidebar.tsx` (cleaned)
- `tauri-app/src/components/ChatView.tsx` (added model props + ModelSelectorPill)
- `tauri-app/src/App.tsx` (routed model props to ChatView)
- `tauri-app/src/components/ChatView.test.tsx` (fixed tests)
## Success Criteria Met
- [x] Top-level model scan: `scan_models("./models")` returns only `.gguf` files directly in the folder
- [x] Model selector visible in chat input pill (hero screen and regular screen)
- [x] No model selector in the sidebar
- [x] TypeScript and Rust compile without errors
@@ -0,0 +1,41 @@
# Task 1 Summary: Fix Rust model scan to top-level only
**Status:** ✅ Complete
**Files Modified:**
- `tauri-app/src-tauri/src/model_files.rs`
## Changes Made
### 1. Added `scan_model_dir_top_level` function (lines 46-71)
A new function that scans only the immediate children of a directory (depth=1), returning only `.gguf` files without recursing into subdirectories.
### 2. Updated `scan_model_dir` to use top-level scan (lines 31-44)
Changed from calling `scan_model_dir_recursive` to calling `scan_model_dir_top_level` as the default behavior.
### 3. Kept `scan_model_dir_recursive` for future use (lines 73-97)
The recursive function is preserved with `#[allow(dead_code)]` annotation for potential future use cases.
### 4. Updated tests
- Renamed `scan_model_dir_finds_gguf_in_subdirectories` to `scan_model_dir_returns_only_top_level_gguf`
- Updated assertion to expect only top-level models
- Added new test `scan_model_dir_recursive_finds_nested_gguf` to verify the recursive function still works
## Verification
```bash
cd tauri-app/src-tauri && cargo test scan_model
```
All 5 tests pass:
- `scan_model_dir_filters_and_sorts_gguf_files` ✅
- `scan_model_dir_ignores_directories_named_like_models` ✅
- `scan_model_dir_skips_hidden_directories` ✅
- `scan_model_dir_recursive_finds_nested_gguf` ✅
- `scan_model_dir_returns_only_top_level_gguf` ✅
## Success Criteria Met
- ✅ `scan_models` Tauri command returns only `.gguf` files in the immediate directory
- ✅ Nested models in subdirectories are excluded from default scan
- ✅ Recursive scan function remains available for future use
- ✅ All existing tests continue to pass
@@ -0,0 +1,70 @@
# Task 2 Summary: Create ModelSelectorPill + wire into ChatView + clean Sidebar
**Status:** ✅ Complete
**Files Modified:**
- `tauri-app/src/components/ModelSelectorPill.tsx` (new file)
- `tauri-app/src/components/ChatView.css` (added model selector styles)
- `tauri-app/src/components/Sidebar.tsx` (removed model-section)
- `tauri-app/src/App.tsx` (moved model props to ChatView)
- `tauri-app/src/components/ChatView.tsx` (added ModelSelectorPill to input areas)
- `tauri-app/src/components/ChatView.test.tsx` (updated tests with new props)
## Changes Made
### 1. Created `ModelSelectorPill.tsx`
New component that:
- Displays a compact pill button with the selected model's display name
- Shows a status dot indicating server status
- Opens a dropdown listbox on click with available models
- Filters models using `isInChatSelector` (Cooper + Iron only)
- Supports loading/swapping state with spinner
- Props: `models`, `localModels`, `selectedModel`, `onModelChange`, `onModelSwap`, `isSwapping`, `status`
### 2. Added CSS styles for ModelSelectorPill
Added to `ChatView.css`:
- `.model-selector-pill-wrapper` - container with relative positioning
- `.model-selector-pill` - pill button styling
- `.model-status-dot` - status indicator dot
- `.model-selector-menu` - dropdown listbox
- `.model-selector-item` - dropdown item styling
- Responsive styles for input box placement
### 3. Cleaned Sidebar.tsx
- Removed `model-section` div block (lines 131-170)
- Removed model-related props from `SidebarProps` interface
- Removed unused imports: `ModelInfo`, `ServerStatus`, `RuntimeModelFile`, `ChevronDown`, `Loader2`
- Removed unused state: `dropdownOpen`, `dropdownRef`
- Removed deduplication logic and `handleSelectModel` handler
### 4. Updated App.tsx
- Removed model props from `<Sidebar>` component
- Added model props to `<ChatView>`: `models`, `localModels`, `selectedModel`, `onModelChange`, `onModelSwap`, `isSwapping`, `status`
### 5. Updated ChatView.tsx
- Added new props to `Props` interface
- Imported `ModelSelectorPill`, `ModelInfo`, `ServerStatus`, `RuntimeModelFile`
- Rendered `<ModelSelectorPill>` in two places:
- Hero input block (inside `.input-box--hero`, before `.attachment-actions`)
- Regular input block (inside `.input-box`, before `.attachment-actions`)
### 6. Updated ChatView.test.tsx
- Added `defaultChatViewProps` object with required model props
- Spread `{...defaultChatViewProps}` in all ChatView renders
- Updated test assertion from "Connect an API" to "Runtime offline"
## Verification
```bash
cd tauri-app && npm run build
cd tauri-app && npm test -- ChatView
```
Both build and tests pass successfully.
## Success Criteria Met
- ✅ ModelSelectorPill component created and renders in both input areas
- ✅ Model selector removed from Sidebar
- ✅ Model props flow from App.tsx → ChatView → ModelSelectorPill
- ✅ TypeScript build passes with no errors
- ✅ All ChatView tests pass