Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/TypeWhisper.Linux/Resources/Localization/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,17 @@
"Plugins.Extensions": "Erweiterungen",
"Plugins.ExtensionsSubtitle": "Plugins erweitern TypeWhisper um zusätzliche Transkriptions-Engines, LLM-Anbieter und Nachbearbeitung.",
"Plugins.HeaderSummary": "{0} installiert, {1} aktiv",
"Plugins.Installed": "Installiert",
"Plugins.LoadFailures": "Plugin-Ladefehler",
"Plugins.NoDisabledPlugins": "Keine Plugins deaktiviert. Alle installierten Plugins sind derzeit aktiv.",
"Plugins.NoEditableFields": "Dieses Plugin stellt einen Einstellungsanbieter bereit, aber keine bearbeitbaren Felder.",
"Plugins.NoEnabledPlugins": "Keine Plugins aktiv. Aktivieren Sie eines im Tab „Deaktiviert“, um es zu nutzen.",
"Plugins.NoHostNeutralSettings": "Dieses Plugin stellt noch keine host-neutralen Einstellungen bereit.",
"Plugins.NoValidationAvailable": "Keine Validierung verfügbar.",
"Plugins.SettingsSaved": "Einstellungen gespeichert.",
"Plugins.SummaryFailed": " · {0} konnte nicht geladen werden",
"Plugins.SummaryFailed": " · {0} nicht geladen",
"Plugins.SummaryLoaded": "{0} Plugin(s) geladen",
"Plugins.TabDisabled": "Deaktiviert ({0})",
"Plugins.TabEnabled": "Aktiv ({0})",
"Plugins.UnableToLoadSettings": "Plugin-Einstellungen können nicht geladen werden.",
"Plugins.Validate": "Validieren",
"Profiles.Activation": "Aktivierung",
Expand Down
5 changes: 4 additions & 1 deletion src/TypeWhisper.Linux/Resources/Localization/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,17 @@
"Plugins.Extensions": "Extensions",
"Plugins.ExtensionsSubtitle": "Plugins extend TypeWhisper with additional transcription engines, LLM providers, and post-processing.",
"Plugins.HeaderSummary": "{0} installed, {1} enabled",
"Plugins.Installed": "Installed",
"Plugins.LoadFailures": "Plugin load failures",
"Plugins.NoDisabledPlugins": "No plugins are disabled. Every installed plugin is currently enabled.",
"Plugins.NoEditableFields": "This plugin exposes a settings provider, but no editable fields.",
"Plugins.NoEnabledPlugins": "No plugins are enabled. Enable one from the Disabled tab to start using it.",
"Plugins.NoHostNeutralSettings": "This plugin does not expose host-neutral settings yet.",
"Plugins.NoValidationAvailable": "No validation available.",
"Plugins.SettingsSaved": "Settings saved.",
"Plugins.SummaryFailed": " · {0} failed to load",
"Plugins.SummaryLoaded": "{0} plugin(s) loaded",
"Plugins.TabDisabled": "Disabled ({0})",
"Plugins.TabEnabled": "Enabled ({0})",
"Plugins.UnableToLoadSettings": "Unable to load plugin settings.",
"Plugins.Validate": "Validate",
"Profiles.Activation": "Activation",
Expand Down
7 changes: 5 additions & 2 deletions src/TypeWhisper.Linux/Resources/Localization/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,17 @@
"Plugins.Extensions": "Extensiones",
"Plugins.ExtensionsSubtitle": "Los plugins amplían TypeWhisper con motores de transcripción, proveedores de LLM y posprocesamiento adicionales.",
"Plugins.HeaderSummary": "{0} instalados, {1} activados",
"Plugins.Installed": "Instalado",
"Plugins.LoadFailures": "Errores de carga de plugins",
"Plugins.NoDisabledPlugins": "No hay plugins desactivados. Todos los plugins instalados están activados actualmente.",
"Plugins.NoEditableFields": "Este plugin ofrece un proveedor de ajustes, pero ningún campo editable.",
"Plugins.NoEnabledPlugins": "No hay plugins activados. Activa uno desde la pestaña Desactivados para empezar a usarlo.",
"Plugins.NoHostNeutralSettings": "Este plugin aún no ofrece ajustes independientes del host.",
"Plugins.NoValidationAvailable": "No hay validación disponible.",
"Plugins.SettingsSaved": "Ajustes guardados.",
"Plugins.SummaryFailed": " · {0} no se pudieron cargar",
"Plugins.SummaryFailed": " · {0} sin cargar",
"Plugins.SummaryLoaded": "{0} plugin(s) cargado(s)",
"Plugins.TabDisabled": "Desactivados ({0})",
"Plugins.TabEnabled": "Activados ({0})",
"Plugins.UnableToLoadSettings": "No se pudieron cargar los ajustes del plugin.",
"Plugins.Validate": "Validar",
"Profiles.Activation": "Activación",
Expand Down
5 changes: 4 additions & 1 deletion src/TypeWhisper.Linux/Resources/Localization/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,17 @@
"Plugins.Extensions": "Расширения",
"Plugins.ExtensionsSubtitle": "Плагины расширяют TypeWhisper дополнительными движками транскрипции, LLM-провайдерами и постобработкой.",
"Plugins.HeaderSummary": "Установлено: {0}, включено: {1}",
"Plugins.Installed": "Установлено",
"Plugins.LoadFailures": "Сбои загрузки плагинов",
"Plugins.NoDisabledPlugins": "Нет отключённых плагинов. Все установленные плагины сейчас включены.",
"Plugins.NoEditableFields": "Этот плагин предоставляет провайдер настроек, но без редактируемых полей.",
"Plugins.NoEnabledPlugins": "Нет включённых плагинов. Включите плагин на вкладке «Отключено», чтобы начать им пользоваться.",
"Plugins.NoHostNeutralSettings": "Этот плагин пока не предоставляет хост-независимых настроек.",
"Plugins.NoValidationAvailable": "Проверка недоступна.",
"Plugins.SettingsSaved": "Настройки сохранены.",
"Plugins.SummaryFailed": " · не удалось загрузить: {0}",
"Plugins.SummaryLoaded": "Загружено плагинов: {0}",
"Plugins.TabDisabled": "Отключено ({0})",
"Plugins.TabEnabled": "Включено ({0})",
"Plugins.UnableToLoadSettings": "Не удалось загрузить настройки плагина.",
"Plugins.Validate": "Проверить",
"Profiles.Activation": "Активация",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
[ObservableProperty]
private string _headerSummary = "";

// 0 = Enabled, 1 = Disabled. Drives which plugin list the section shows.
[ObservableProperty]
private int _selectedTab;

[ObservableProperty]
private string _summary = "";

Expand All @@ -111,10 +115,27 @@
Refresh();
}

public ObservableCollection<PluginCategoryGroup> PluginGroups { get; } = [];
public ObservableCollection<PluginCategoryGroup> EnabledGroups { get; } = [];
public ObservableCollection<PluginCategoryGroup> DisabledGroups { get; } = [];
public ObservableCollection<PluginFailureRow> LoadFailures { get; } = [];
public bool HasLoadFailures => LoadFailures.Count > 0;

public bool IsEnabledTabSelected => SelectedTab == 0;
public bool IsDisabledTabSelected => SelectedTab == 1;

public int EnabledCount => EnabledGroups.Sum(group => group.Plugins.Count);
public int DisabledCount => DisabledGroups.Sum(group => group.Plugins.Count);
public bool HasEnabledPlugins => EnabledCount > 0;
public bool HasDisabledPlugins => DisabledCount > 0;

public string EnabledTabLabel => Loc.Instance.GetString("Plugins.TabEnabled", EnabledCount);
public string DisabledTabLabel => Loc.Instance.GetString("Plugins.TabDisabled", DisabledCount);

// Rows live in whichever partition their IsEnabled state lands them in; several
// operations (expand preservation, single-expand enforcement) need to see both.
private IEnumerable<PluginRow> AllRows =>
EnabledGroups.Concat(DisabledGroups).SelectMany(group => group.Plugins);

/// <summary>
/// Re-polls providers so newly pulled models appear when a model dropdown opens, without
/// requiring "Validate". Debounce/guard live in <see cref="PluginManager" />.
Expand All @@ -127,12 +148,10 @@
private void Refresh()
{
// Preserve expanded state across rebuilds so the user doesn't lose their open settings panel.
var expandedPluginId = PluginGroups
.SelectMany(group => group.Plugins)
.FirstOrDefault(plugin => plugin.IsExpanded)
?.Id;
var expandedPluginId = AllRows.FirstOrDefault(plugin => plugin.IsExpanded)?.Id;

PluginGroups.Clear();
EnabledGroups.Clear();
DisabledGroups.Clear();
_pluginById.Clear();

var plugins = _pluginManager
Expand Down Expand Up @@ -163,12 +182,11 @@
.ThenBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
.ToList();

foreach (var group in plugins.GroupBy(p => p.CategoryKey))
{
var categoryPlugins = group.ToList();
var categoryLabel = categoryPlugins[0].CategoryLabel;
PluginGroups.Add(new PluginCategoryGroup(categoryLabel, categoryPlugins));
}
// Partition by enabled state so each tab shows only what belongs there. The
// list is already ordered by category sort order, so GroupBy yields categories
// in that order within each partition.
BuildGroups(EnabledGroups, plugins.Where(p => p.IsEnabled));
BuildGroups(DisabledGroups, plugins.Where(p => !p.IsEnabled));

LoadFailures.Clear();
foreach (var failure in _pluginManager.LoadFailures)
Expand All @@ -189,14 +207,19 @@
var enabledCount = plugins.Count(p => p.IsEnabled);
HeaderSummary = Loc.Instance.GetString("Plugins.HeaderSummary", plugins.Count, enabledCount);

OnPropertyChanged(nameof(EnabledCount));
OnPropertyChanged(nameof(DisabledCount));
OnPropertyChanged(nameof(HasEnabledPlugins));
OnPropertyChanged(nameof(HasDisabledPlugins));
OnPropertyChanged(nameof(EnabledTabLabel));
OnPropertyChanged(nameof(DisabledTabLabel));

if (expandedPluginId is null)
{
return;
}

var expandedPlugin = PluginGroups
.SelectMany(group => group.Plugins)
.FirstOrDefault(plugin => plugin.Id == expandedPluginId);
var expandedPlugin = AllRows.FirstOrDefault(plugin => plugin.Id == expandedPluginId);
if (expandedPlugin is null)
{
return;
Expand All @@ -206,6 +229,37 @@
_ = LoadPluginSettingsAsync(expandedPlugin);
}

private static void BuildGroups(
ObservableCollection<PluginCategoryGroup> target,
IEnumerable<PluginRow> rows
)
{
foreach (var group in rows.GroupBy(p => p.CategoryKey))
{
var categoryPlugins = group.ToList();
target.Add(new PluginCategoryGroup(categoryPlugins[0].CategoryLabel, categoryPlugins));
}
Comment on lines +237 to +241
}

[RelayCommand]
private void SetTab(object? tab)
{
SelectedTab = tab switch
{
int intValue => intValue,
string stringValue when int.TryParse(stringValue, out var parsed) => parsed,
// Leave the current tab unchanged for any other value; the
// [ObservableProperty] setter's equality guard makes this a no-op.
_ => SelectedTab
};
}

partial void OnSelectedTabChanged(int value)
{
OnPropertyChanged(nameof(IsEnabledTabSelected));
OnPropertyChanged(nameof(IsDisabledTabSelected));
}

[RelayCommand]
private async Task ToggleEnabled(PluginRow row)
{
Expand All @@ -228,7 +282,7 @@
return;
}

foreach (var other in PluginGroups.SelectMany(group => group.Plugins).Where(p => p != row))
foreach (var other in AllRows.Where(p => p != row))
{
other.IsExpanded = false;
}
Expand Down
Loading
Loading