feat: fanalize straeming adapters
This commit is contained in:
@@ -37,7 +37,6 @@ type ConfigSection string
|
||||
|
||||
const (
|
||||
SectionProviders ConfigSection = "providers"
|
||||
SectionLanguage ConfigSection = "language"
|
||||
SectionTranscription ConfigSection = "transcription"
|
||||
SectionLLM ConfigSection = "llm"
|
||||
SectionKeywords ConfigSection = "keywords"
|
||||
@@ -111,11 +110,6 @@ func runEditExisting(cfg *config.Config) (*ConfigureResult, error) {
|
||||
}
|
||||
configuredProviders = getConfiguredProviders(cfg)
|
||||
|
||||
case SectionLanguage:
|
||||
if err := editLanguage(cfg); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
case SectionTranscription:
|
||||
var err error
|
||||
configuredProviders, err = editTranscription(cfg, configuredProviders)
|
||||
@@ -160,7 +154,6 @@ func runEditExisting(cfg *config.Config) (*ConfigureResult, error) {
|
||||
func selectSection(cfg *config.Config) (ConfigSection, error) {
|
||||
options := []huh.Option[ConfigSection]{
|
||||
huh.NewOption(formatProvidersLabel(cfg), SectionProviders),
|
||||
huh.NewOption(formatLanguageMenuLabel(cfg), SectionLanguage),
|
||||
huh.NewOption(formatTranscriptionLabel(cfg), SectionTranscription),
|
||||
huh.NewOption(formatLLMLabel(cfg), SectionLLM),
|
||||
huh.NewOption(formatKeywordsLabel(cfg), SectionKeywords),
|
||||
|
||||
@@ -13,11 +13,6 @@ func formatProvidersLabel(cfg *config.Config) string {
|
||||
return "Providers"
|
||||
}
|
||||
|
||||
// formatLanguageMenuLabel formats the language menu option
|
||||
func formatLanguageMenuLabel(cfg *config.Config) string {
|
||||
return "Language"
|
||||
}
|
||||
|
||||
// formatTranscriptionLabel formats the transcription menu option
|
||||
func formatTranscriptionLabel(cfg *config.Config) string {
|
||||
return "Transcription"
|
||||
@@ -54,10 +49,11 @@ func showSummary(cfg *config.Config) (bool, error) {
|
||||
}
|
||||
fmt.Printf(" %s %s\n", StyleLabel.Render("Providers:"), strings.Join(providers, ", "))
|
||||
|
||||
fmt.Printf(" %s %s (%s)\n", StyleLabel.Render("Transcription:"), cfg.Transcription.Provider, cfg.Transcription.Model)
|
||||
if cfg.Transcription.Language != "" {
|
||||
fmt.Printf(" %s %s\n", StyleLabel.Render("Language:"), cfg.Transcription.Language)
|
||||
lang := cfg.Transcription.Language
|
||||
if lang == "" {
|
||||
lang = "auto-detect"
|
||||
}
|
||||
fmt.Printf(" %s %s/%s (%s)\n", StyleLabel.Render("Transcription:"), cfg.Transcription.Provider, cfg.Transcription.Model, lang)
|
||||
|
||||
if cfg.LLM.Enabled {
|
||||
fmt.Printf(" %s %s (%s)\n", StyleLabel.Render("LLM:"), cfg.LLM.Provider, cfg.LLM.Model)
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/config"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/language"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/provider"
|
||||
)
|
||||
|
||||
// editLanguage allows the user to select the global transcription language
|
||||
func editLanguage(cfg *config.Config) error {
|
||||
// no model-specific warnings for global language selection
|
||||
languageOptions := getLanguageOptions(nil, cfg.General.Language)
|
||||
|
||||
selectedLanguage := cfg.General.Language
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Title("Language").
|
||||
Description("Select language for transcription (applies globally)").
|
||||
Options(languageOptions...).
|
||||
Filtering(true).
|
||||
Value(&selectedLanguage),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := form.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if current transcription model supports the selected language
|
||||
if selectedLanguage != "" && cfg.Transcription.Provider != "" && cfg.Transcription.Model != "" {
|
||||
registryName := mapConfigProviderToRegistry(cfg.Transcription.Provider)
|
||||
model, err := provider.GetModel(registryName, cfg.Transcription.Model)
|
||||
if err == nil && !model.SupportsLanguage(selectedLanguage) {
|
||||
langName := language.FromCode(selectedLanguage).Name
|
||||
if langName == "" {
|
||||
langName = selectedLanguage
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println(StyleWarning.Render("Language-Model Compatibility Warning"))
|
||||
fmt.Printf("Your current model '%s' does not support %s.\n", model.Name, langName)
|
||||
fmt.Println()
|
||||
fmt.Println(StyleMuted.Render("You can:"))
|
||||
fmt.Println(StyleMuted.Render(" - Keep this language and change the model later"))
|
||||
fmt.Println(StyleMuted.Render(" - Use 'Auto-detect' for language"))
|
||||
fmt.Println(StyleMuted.Render(" - Choose a different language"))
|
||||
fmt.Println()
|
||||
|
||||
var action string
|
||||
actionForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Title("What would you like to do?").
|
||||
Options(
|
||||
huh.NewOption("Keep this language (change model later)", "keep"),
|
||||
huh.NewOption("Use Auto-detect instead", "auto"),
|
||||
huh.NewOption("Choose a different language", "retry"),
|
||||
).
|
||||
Value(&action),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := actionForm.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "auto":
|
||||
selectedLanguage = ""
|
||||
case "retry":
|
||||
return editLanguage(cfg)
|
||||
case "keep":
|
||||
// proceed with incompatible language
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.General.Language = selectedLanguage
|
||||
return nil
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/config"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/deps"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/language"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/models/whisper"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/provider"
|
||||
)
|
||||
@@ -114,13 +113,7 @@ func editTranscription(cfg *config.Config, configuredProviders []string) ([]stri
|
||||
}
|
||||
cfg.Transcription.Provider = selectedProvider
|
||||
|
||||
// use effective language for model compatibility display
|
||||
effectiveLanguage := cfg.General.Language
|
||||
if cfg.Transcription.Language != "" {
|
||||
effectiveLanguage = cfg.Transcription.Language
|
||||
}
|
||||
|
||||
modelOptions := getTranscriptionModelOptions(selectedProvider, effectiveLanguage)
|
||||
modelOptions := getTranscriptionModelOptions(selectedProvider)
|
||||
selectedModel := cfg.Transcription.Model
|
||||
if selectedModel == "" && len(modelOptions) > 0 {
|
||||
// skip header options (empty value) to find first real model
|
||||
@@ -156,41 +149,7 @@ func editTranscription(cfg *config.Config, configuredProviders []string) ([]stri
|
||||
return editTranscription(cfg, configuredProviders)
|
||||
}
|
||||
|
||||
// validate language-model compatibility before saving
|
||||
registryName := mapConfigProviderToRegistry(selectedProvider)
|
||||
if err := provider.ValidateModelLanguage(registryName, selectedModel, effectiveLanguage); err != nil {
|
||||
// show error dialog - user needs to change language in Language menu
|
||||
fmt.Println()
|
||||
fmt.Println(StyleError.Render("Language-Model Incompatibility"))
|
||||
fmt.Println(StyleMuted.Render(err.Error()))
|
||||
fmt.Println()
|
||||
fmt.Println(StyleMuted.Render("You can:"))
|
||||
fmt.Println(StyleMuted.Render(" - Choose a different model that supports your language"))
|
||||
fmt.Println(StyleMuted.Render(" - Change language to 'Auto-detect' in the Language menu"))
|
||||
fmt.Println()
|
||||
|
||||
var retry bool
|
||||
retryForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Try again?").
|
||||
Description("Choose a different model").
|
||||
Affirmative("Yes, let me pick another model").
|
||||
Negative("Cancel").
|
||||
Value(&retry),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := retryForm.Run(); err != nil {
|
||||
return configuredProviders, err
|
||||
}
|
||||
|
||||
if retry {
|
||||
// recurse to let user pick another model
|
||||
return editTranscription(cfg, configuredProviders)
|
||||
}
|
||||
return configuredProviders, nil
|
||||
}
|
||||
|
||||
// for whisper-cpp, check if model needs download
|
||||
if selectedProvider == "whisper-cpp" && !whisper.IsInstalled(selectedModel) {
|
||||
@@ -245,35 +204,55 @@ func editTranscription(cfg *config.Config, configuredProviders []string) ([]stri
|
||||
|
||||
cfg.Transcription.Model = selectedModel
|
||||
|
||||
// set streaming mode based on model capabilities
|
||||
// select language for this model
|
||||
model, err := provider.GetModel(registryName, selectedModel)
|
||||
if err == nil {
|
||||
if model.SupportsBothModes() {
|
||||
// model supports both: ask user
|
||||
useStreaming := cfg.Transcription.Streaming
|
||||
streamingForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Enable streaming mode?").
|
||||
Description("This model supports both batch and streaming modes").
|
||||
Affirmative("Yes, use streaming (real-time)").
|
||||
Negative("No, use batch (after recording)").
|
||||
Value(&useStreaming),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
if err != nil {
|
||||
return configuredProviders, err
|
||||
}
|
||||
|
||||
if err := streamingForm.Run(); err != nil {
|
||||
return configuredProviders, err
|
||||
}
|
||||
cfg.Transcription.Streaming = useStreaming
|
||||
} else if model.SupportsStreaming {
|
||||
// streaming-only model
|
||||
cfg.Transcription.Streaming = true
|
||||
fmt.Println(StyleSuccess.Render("Streaming mode enabled (this model only supports streaming)"))
|
||||
} else {
|
||||
// batch-only model
|
||||
cfg.Transcription.Streaming = false
|
||||
languageOptions := getModelLanguageOptions(model, cfg.Transcription.Language)
|
||||
selectedLanguage := cfg.Transcription.Language
|
||||
|
||||
languageForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Title("Language").
|
||||
Description("Select language for transcription").
|
||||
Options(languageOptions...).
|
||||
Filtering(true).
|
||||
Value(&selectedLanguage),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := languageForm.Run(); err != nil {
|
||||
return configuredProviders, err
|
||||
}
|
||||
|
||||
cfg.Transcription.Language = selectedLanguage
|
||||
|
||||
// set streaming mode based on model capabilities
|
||||
if model.SupportsBothModes() {
|
||||
useStreaming := cfg.Transcription.Streaming
|
||||
streamingForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Enable streaming mode?").
|
||||
Description("This model supports both batch and streaming modes").
|
||||
Affirmative("Yes, use streaming (real-time)").
|
||||
Negative("No, use batch (after recording)").
|
||||
Value(&useStreaming),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := streamingForm.Run(); err != nil {
|
||||
return configuredProviders, err
|
||||
}
|
||||
cfg.Transcription.Streaming = useStreaming
|
||||
} else if model.SupportsStreaming {
|
||||
cfg.Transcription.Streaming = true
|
||||
fmt.Println(StyleSuccess.Render("Streaming mode enabled (this model only supports streaming)"))
|
||||
} else {
|
||||
cfg.Transcription.Streaming = false
|
||||
}
|
||||
|
||||
return configuredProviders, nil
|
||||
@@ -304,7 +283,7 @@ func getUnconfiguredTranscriptionOptions(configuredProviders []string) []huh.Opt
|
||||
return options
|
||||
}
|
||||
|
||||
func getTranscriptionModelOptions(configProvider string, currentLang string) []huh.Option[string] {
|
||||
func getTranscriptionModelOptions(configProvider string) []huh.Option[string] {
|
||||
// special case: groq-translation only supports whisper-large-v3
|
||||
if configProvider == "groq-translation" {
|
||||
return []huh.Option[string]{
|
||||
@@ -323,7 +302,7 @@ func getTranscriptionModelOptions(configProvider string, currentLang string) []h
|
||||
|
||||
var options []huh.Option[string]
|
||||
for _, m := range models {
|
||||
label := buildModelLabel(m, currentLang)
|
||||
label := buildModelLabel(m)
|
||||
if m.Local && registryName == "whisper-cpp" {
|
||||
if whisper.IsInstalled(m.ID) {
|
||||
label = "[x] " + label
|
||||
@@ -350,7 +329,7 @@ func mapConfigProviderToRegistry(configProvider string) string {
|
||||
}
|
||||
|
||||
// buildModelLabel creates the display label for a model option
|
||||
func buildModelLabel(m provider.Model, currentLang string) string {
|
||||
func buildModelLabel(m provider.Model) string {
|
||||
label := fmt.Sprintf("%s (%s)", m.Name, m.Description)
|
||||
|
||||
// append size for local models
|
||||
@@ -364,22 +343,6 @@ func buildModelLabel(m provider.Model, currentLang string) string {
|
||||
} else if m.SupportsStreaming {
|
||||
label += " [streaming]"
|
||||
}
|
||||
// batch-only models don't need a tag (it's the default)
|
||||
|
||||
// append language warning if model doesn't support current language
|
||||
if currentLang != "" && !m.SupportsLanguage(currentLang) {
|
||||
langName := getLangName(currentLang)
|
||||
label += fmt.Sprintf(" (does not support %s)", langName)
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
// getLangName returns a human-readable language name for a code
|
||||
func getLangName(code string) string {
|
||||
lang := language.FromCode(code)
|
||||
if lang.Code == "" {
|
||||
return code // unknown code, return as-is
|
||||
}
|
||||
return lang.Name
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
func TestGetTranscriptionModelOptions_ShowsCapabilities(t *testing.T) {
|
||||
// test elevenlabs - has batch-only and streaming-only models
|
||||
options := getTranscriptionModelOptions("elevenlabs", "")
|
||||
options := getTranscriptionModelOptions("elevenlabs")
|
||||
|
||||
// should have 3 models: scribe_v1, scribe_v2, scribe_v2_realtime
|
||||
if len(options) != 3 {
|
||||
@@ -40,7 +40,7 @@ func TestGetTranscriptionModelOptions_ShowsCapabilities(t *testing.T) {
|
||||
|
||||
func TestGetTranscriptionModelOptions_NoHeadersAnymore(t *testing.T) {
|
||||
// we removed batch/streaming section headers
|
||||
options := getTranscriptionModelOptions("elevenlabs", "")
|
||||
options := getTranscriptionModelOptions("elevenlabs")
|
||||
|
||||
for _, opt := range options {
|
||||
if opt.Value == "" {
|
||||
@@ -50,7 +50,7 @@ func TestGetTranscriptionModelOptions_NoHeadersAnymore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTranscriptionModelOptions_OpenAI_ShowsCapabilities(t *testing.T) {
|
||||
options := getTranscriptionModelOptions("openai", "")
|
||||
options := getTranscriptionModelOptions("openai")
|
||||
|
||||
// OpenAI has 3 transcription models: whisper-1, gpt-4o-transcribe, gpt-4o-mini-transcribe
|
||||
if len(options) != 3 {
|
||||
@@ -68,7 +68,7 @@ func TestGetTranscriptionModelOptions_OpenAI_ShowsCapabilities(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTranscriptionModelOptions_Deepgram_ShowsBothModes(t *testing.T) {
|
||||
options := getTranscriptionModelOptions("deepgram", "")
|
||||
options := getTranscriptionModelOptions("deepgram")
|
||||
|
||||
// Deepgram has 2 models: nova-3, nova-2 - both support batch+streaming
|
||||
if len(options) != 2 {
|
||||
@@ -84,7 +84,7 @@ func TestGetTranscriptionModelOptions_Deepgram_ShowsBothModes(t *testing.T) {
|
||||
|
||||
func TestGetTranscriptionModelOptions_Groq_BatchOnly(t *testing.T) {
|
||||
// test groq - batch only (no streaming models)
|
||||
options := getTranscriptionModelOptions("groq-transcription", "")
|
||||
options := getTranscriptionModelOptions("groq-transcription")
|
||||
|
||||
// should have 2 models: whisper-large-v3, whisper-large-v3-turbo
|
||||
if len(options) != 2 {
|
||||
|
||||
@@ -39,12 +39,7 @@ func runFreshInstall(cfg *config.Config) (*ConfigureResult, error) {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
|
||||
// 4. Language selection
|
||||
if err := editLanguage(cfg); err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
|
||||
// 5. Keywords
|
||||
// 4. Keywords
|
||||
keywords, err := inputKeywords(cfg.Keywords)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
|
||||
@@ -8,10 +8,8 @@ import (
|
||||
"github.com/leonardotrapani/hyprvoice/internal/provider"
|
||||
)
|
||||
|
||||
// getLanguageOptions returns language options for the dropdown
|
||||
// if currentModel is provided, languages unsupported by that model will be marked
|
||||
// currentLang is the currently selected language code (empty string for auto-detect)
|
||||
func getLanguageOptions(currentModel *provider.Model, currentLang string) []huh.Option[string] {
|
||||
// getModelLanguageOptions returns language options supported by the given model
|
||||
func getModelLanguageOptions(model *provider.Model, currentLang string) []huh.Option[string] {
|
||||
var options []huh.Option[string]
|
||||
|
||||
// auto-detect is always first
|
||||
@@ -21,18 +19,15 @@ func getLanguageOptions(currentModel *provider.Model, currentLang string) []huh.
|
||||
}
|
||||
options = append(options, huh.NewOption(autoLabel, ""))
|
||||
|
||||
// add all languages
|
||||
// only show languages supported by the model
|
||||
for _, lang := range language.List() {
|
||||
label := formatLanguageLabel(lang)
|
||||
|
||||
// mark current selection
|
||||
if lang.Code == currentLang {
|
||||
label += " (current)"
|
||||
if model != nil && !model.SupportsLanguage(lang.Code) {
|
||||
continue
|
||||
}
|
||||
|
||||
// add warning if model doesn't support this language
|
||||
if currentModel != nil && !currentModel.SupportsLanguage(lang.Code) {
|
||||
label += " (not supported by current model)"
|
||||
label := formatLanguageLabel(lang)
|
||||
if lang.Code == currentLang {
|
||||
label += " (current)"
|
||||
}
|
||||
|
||||
options = append(options, huh.NewOption(label, lang.Code))
|
||||
|
||||
Reference in New Issue
Block a user