Skip to content

feat: add model selection for remote transcription - #22

Merged
AzimovS merged 5 commits into
mainfrom
feat/remote-transcription-model-selection
Mar 31, 2026
Merged

feat: add model selection for remote transcription#22
AzimovS merged 5 commits into
mainfrom
feat/remote-transcription-model-selection

Conversation

@AzimovS

@AzimovS AzimovS commented Mar 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a dedicated endpointUrl column to transcript_settings, separating endpoint URL from model name (previously overloaded in the model column for remote providers)
  • Remote transcription settings UI now shows three fields: Endpoint URL, Model Name, and API Key
  • Model name is sent as the model parameter in the multipart form to OpenAI-compatible transcription endpoints
  • Fixes pre-existing bug where TranscriptSetting struct was missing the runpodApiKey field

Key Design Decisions

  • Two-phase migration: URL kept in both model and endpointUrl columns for safe rollback. Rust code reads endpointUrl first with fallback to model
  • Conditional model sending: model form parameter only sent when non-empty (some self-hosted servers don't expect it)
  • Input validation: Model name limited to 256 chars, no control characters. URL sanitized in logs (query params stripped)
  • Migrated data safety: resolve_remote_params() detects when model equals the URL (migrated data) and treats it as empty to avoid sending URL as the model name

Test plan

  • Save remote transcription config with all three fields, navigate away and back -- all fields persist
  • Switch from remote to parakeet and back -- remote fields restored
  • Fresh install defaults to parakeet with no errors
  • Existing remote config users: URL migrated to endpointUrl, transcription still works
  • cargo check passes
  • pnpm run build passes

🤖 Generated with Claude Code

AzimovS and others added 5 commits March 30, 2026 15:57
Add a dedicated endpointUrl column to transcript_settings, separating the
endpoint URL from the model name (which was previously overloaded in the
model column for remote providers). The remote transcription UI now shows
three fields: Endpoint URL, Model Name, and API Key. The model name is
sent as the 'model' parameter in the multipart form to OpenAI-compatible
transcription endpoints.

Also fixes a pre-existing bug where TranscriptSetting struct was missing
the runpodApiKey field, causing potential runtime errors with SELECT *.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explain in the migration SQL and engine.rs fallback code why the URL is
kept in both columns and when the fallback can be removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix P1 logic bug: migrated remote users would have their endpoint URL
  sent as the model name because model column still contains the URL.
  The new resolve_remote_params() helper detects when model equals the
  URL and treats it as empty.
- Extract duplicated fallback logic into TranscriptConfig::resolve_remote_params()
  (was copy-pasted in validate_transcription_model_ready and
  get_or_init_transcription_engine).
- Remove redundant isDoneDisabled condition in TranscriptSettings.tsx
  (remote-specific model check was already covered by isRemoteProvider check).
- Use PRAGMA foreign_keys=off/on in migration to match existing convention
  (defer_foreign_keys was inconsistent with prior migrations).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The TranscriptSettings component initialized local draft state (uiProvider,
uiModel, uiEndpointUrl, uiApiKey) from the context prop via useState, but
never re-synced when the context updated after the async config load.

This caused endpointUrl to always appear empty because:
1. ConfigContext defaults to { provider: 'parakeet', model: '...' } (no endpointUrl)
2. Component mounts and initializes uiEndpointUrl = '' from the default
3. Async load completes and updates context with the saved endpointUrl
4. But useState initializer doesn't re-run -- local state stays empty

Fix: Add useEffect that syncs all local draft state when transcriptModelConfig
changes, ensuring the saved config is reflected after async load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The endpointUrl was being wiped because multiple callers of
api_save_transcript_config did not include it in the invoke payload.
Since the UPSERT sets endpointUrl = excluded.endpointUrl, omitting it
caused it to be set to NULL whenever any other component saved config.

Fixed callers:
- settings/page.tsx: include endpointUrl when loading config into state
- Sidebar/index.tsx: pass endpointUrl in handleSaveTranscriptConfig
- ParakeetModelManager.tsx: pass endpointUrl: null explicitly
- WhisperModelManager.tsx: pass endpointUrl: null explicitly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@AzimovS

AzimovS commented Mar 30, 2026

Copy link
Copy Markdown
Owner Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Reviewed areas:

  • SQLite migration pattern (table-recreation, PRAGMA usage, data transformation)
  • Rust structs and repository layer (TranscriptSetting, save/get methods)
  • Remote provider model validation and multipart form handling
  • Frontend state management (TranscriptSettings, ConfigContext, Sidebar)
  • All callers of api_save_transcript_config pass endpointUrl

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@AzimovS
AzimovS merged commit 9ed6247 into main Mar 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant