# Ralph Progress Log
Started: Sat Jan 31 08:30:51 PM CET 2026
---

## Task 1: Create Provider interface and registry - COMPLETE

Created internal/provider package with:
- Provider interface with all required methods
- ProviderConfig struct for API key storage
- 4 provider implementations: OpenAI, Groq, Mistral, ElevenLabs
- Registry with GetProvider(), ListProviders(), ListProvidersWithLLM(), ListProvidersWithTranscription()
- Comprehensive tests (all passing)

Key decisions:
- OpenAI and Groq support both transcription + LLM
- Mistral and ElevenLabs are transcription-only
- ValidateAPIKey checks prefix for OpenAI (sk-) and Groq (gsk_), accepts any non-empty for others

## Task 2: Refactor config to unified provider structure - COMPLETE

Added to internal/config/config.go:
- `Providers map[string]ProviderConfig` for centralized API key storage
- `Keywords []string` at config root level
- `LLMConfig` with Enabled, Provider, Model, PostProcessing, CustomPrompt
- `LLMPostProcessingConfig` with RemoveStutters, AddPunctuation, FixGrammar, RemoveFillerWords (all default true)
- `LLMCustomPromptConfig` with Enabled, Prompt
- `LLMAdapterConfig` struct for passing to LLM adapters
- `ToLLMConfig()` method
- `IsLLMEnabled()` helper
- `resolveAPIKeyForProvider()` - unified API key resolution: providers map -> legacy transcription.api_key -> env var
- `resolveAPIKeyForLLMProvider()` - same for LLM
- `migrateTranscriptionAPIKey()` - auto-migrates old config format
- `applyLLMDefaults()` - sets post-processing options to true if all are zero

Migration:
- Old configs with `transcription.api_key` auto-migrate to `providers` map on Load()
- Logs warning: "Run 'hyprvoice configure' to update config format"
- Both old and new config formats work (backward compatible)

Validation:
- LLM validation only runs when `llm.enabled = true`
- Checks provider is openai or groq
- Checks API key is available for LLM provider

Key decisions:
- API key resolution order: providers.X.api_key -> transcription.api_key -> ENV_VAR
- LLM provider names are "openai" and "groq" (not "groq-transcription")
- PostProcessing defaults to all true only if ALL options are false (zero values)
- Keywords at root level (global), used by both transcription and LLM

## Task 3: Create LLM adapter interface and implementations - COMPLETE

Created internal/llm package with:
- `Adapter` interface: `Process(ctx, text) (string, error)`
- `Config` struct mirroring config.LLMAdapterConfig
- `prompt.go` with `BuildSystemPrompt(opts, keywords)` and `BuildUserPrompt(text, customPrompt)`
- `OpenAIAdapter` using go-openai chat completions API
- `GroqAdapter` using Groq's OpenAI-compatible API (baseURL override)
- `NewAdapter(config)` factory function

Key decisions:
- Low temperature (0.3) for consistent text cleanup
- Default models: gpt-4o-mini (OpenAI), llama-3.3-70b-versatile (Groq)
- System prompt builds dynamically based on enabled options
- Keywords included in system prompt for correct spelling hints
- Custom prompt prepended to user prompt if enabled

## Task 4: Integrate LLM phase into pipeline - COMPLETE

Updated internal/pipeline/pipeline.go:
- Added `Processing` status for LLM post-processing phase
- After transcription, checks `config.IsLLMEnabled()` before LLM processing
- Creates LLM adapter using config.ToLLMConfig()
- Processes text with adapter, uses result for injection
- Graceful fallback: logs warning and uses raw transcription text on any error

Key decisions:
- LLM processing happens between transcription and injection
- Adapter creation and processing errors are logged but don't fail the pipeline
- Sets status to Processing during LLM phase, then back to Injecting

## Task 5: Pass keywords to transcription adapters - COMPLETE

Added Keywords support to transcription adapters:
- Added `Keywords []string` to transcriber.Config struct
- Updated `ToTranscriberConfig()` to pass keywords from config
- OpenAI adapter uses keywords in `Prompt` field (initial_prompt parameter)
- Groq transcription adapter uses keywords in `Prompt` field
- Groq translation adapter uses keywords in `Prompt` field
- Mistral and ElevenLabs adapters ignore keywords (APIs don't support initial_prompt)

Key decisions:
- Keywords joined with ", " to form a single string for the Prompt field
- Whisper uses this as "initial_prompt" to help with spelling/terminology
- Only added to adapters that clearly support it (OpenAI/Groq via go-openai lib)

## Task 6: Add TUI dependencies and base components - COMPLETE

Added Charmbracelet TUI stack:
- bubbletea v1.3.10, lipgloss v1.1.0, huh v0.8.0
- Created internal/tui package

Files created:
- `internal/tui/theme.go` - color palette (purple primary, cyan secondary, status colors)
- `internal/tui/styles.go` - lipgloss styles (header, label, success, error, muted, highlight, selected, box styles)
- `Logo()` function for ASCII branding

Key decisions:
- Purple (#7C3AED) as primary accent, matches hyprvoice "voice" theme
- Dark slate backgrounds for terminal aesthetics
- Box styles with rounded borders for form containers

## Task 7: Create TUI configure - fresh install flow - COMPLETE

Created internal/tui/configure.go with full TUI wizard:
- `Run(existingConfig)` entry point returning ConfigureResult
- `runFreshInstall()` - linear flow through all configuration steps
- `selectProviders()` - multi-select for OpenAI, Groq, Mistral, ElevenLabs
- `inputAPIKey()` - password-masked input with validation per provider
- `configureTranscription()` - provider dropdown (only configured+capable), model dropdown, language input
- `configureLLM()` - enable confirm (defaults YES, labeled "Recommended"), provider, model, post-processing toggles, custom prompt
- `inputKeywords()` - comma-separated input
- `selectBackends()` - multi-select with descriptions
- `configureNotifications()` - enable toggle
- `showSummary()` - displays all config, confirm button
- `getTheme()` - applies hyprvoice color scheme to huh forms

Key decisions:
- Linear flow for fresh installs, all steps required
- Transcription providers mapped: groq -> groq-transcription + groq-translation options
- LLM enabled by default, "Yes (Recommended)" as affirmative text
- Post-processing options all default to true
- Uses huh library forms with custom theme matching styles.go colors
