feat: add UI customization and model management features (#60)

* feat: add UI customization and model management features

Add comprehensive UI customization options and enhanced model selection:

- **Favorite & Recent Models**: Star models for quick access, track 5 most recent
  - Add favorite/recent sections to model dropdowns
  - Persist preferences in local storage
  - Works in ModelControls and ModelSelector components

- **Tool Call Expansion Settings**: Control default expansion state for tool outputs
  - Three modes: collapsed, activity (summary), detailed
  - Applies to activity groups and individual tool calls
  - Configurable in Appearance Settings

- **Font Size & Spacing Controls**: Adjustable typography and layout density
  - Font size: 50-200% scaling of all semantic typography
  - Spacing/padding: 50-200% scaling of margins, gaps, line heights
  - Real-time preview with reset buttons
  - Settings in Appearance Settings

- **VSCode Extension Settings View**: Full settings access in VSCode extension
  - Add settings navigation and view type
  - Settings button in header
  - Navigate between sessions, chat, and settings

Technical changes:
- Enhanced useUIStore with new state management
- Dynamic CSS variable scaling for typography and spacing
- Typography helper functions for variable access
- ThemeProvider integration for auto-applying scales

* fix: hide theme mode in VSCode and fix detailed tool expansion

- Hide "Theme Mode" setting in VSCode extension settings as VSCode
  dynamically applies its own theme to the extension
- Fix bug where tools from subsequent messages in a turn weren't
  expanded when "Detailed" mode was selected
- Now correctly aggregates all tool IDs from turnGroupingContext when
  calculating effective expanded tools for progressive groups
This commit is contained in:
theblazehen
2025-12-15 18:11:38 +02:00
committed by GitHub
parent 42e25d03f3
commit 235f80643e
14 changed files with 1192 additions and 86 deletions
@@ -1,6 +1,7 @@
import React from 'react';
import type { Message, Part } from '@opencode-ai/sdk';
import { useCurrentSessionActivity } from '@/hooks/useSessionActivity';
import { useUIStore } from '@/stores/useUIStore';
export interface ChatMessageEntry {
info: Message;
@@ -302,30 +303,34 @@ export const useTurnGrouping = (messages: ChatMessageEntry[]): UseTurnGroupingRe
() => new Map()
);
const toolCallExpansion = useUIStore((state) => state.toolCallExpansion);
// Activity group is expanded for 'activity' and 'detailed', collapsed for 'collapsed'
const defaultActivityExpanded = toolCallExpansion === 'activity' || toolCallExpansion === 'detailed';
const getOrCreateTurnState = React.useCallback(
(turnId: string): TurnUiState => {
const existing = turnUiStates.get(turnId);
if (existing) return existing;
return { isExpanded: false, previewedPartIds: new Set<string>() };
return { isExpanded: defaultActivityExpanded, previewedPartIds: new Set<string>() };
},
[turnUiStates]
[turnUiStates, defaultActivityExpanded]
);
const toggleGroup = React.useCallback((turnId: string) => {
setTurnUiStates((prev) => {
const next = new Map(prev);
const current = next.get(turnId) ?? { isExpanded: false, previewedPartIds: new Set<string>() };
const current = next.get(turnId) ?? { isExpanded: defaultActivityExpanded, previewedPartIds: new Set<string>() };
next.set(turnId, { ...current, isExpanded: !current.isExpanded });
return next;
});
}, []);
}, [defaultActivityExpanded]);
const markPartsPreviewedInternal = React.useCallback((turnId: string, partIds: string[]) => {
if (partIds.length === 0) return;
setTurnUiStates((prev) => {
const next = new Map(prev);
const state = next.get(turnId) ?? { isExpanded: false, previewedPartIds: new Set<string>() };
const state = next.get(turnId) ?? { isExpanded: defaultActivityExpanded, previewedPartIds: new Set<string>() };
const newPreviewed = new Set(state.previewedPartIds);
partIds.forEach((id) => {
if (id && id.trim().length > 0) {