fix(desktop): reflect active toolset provider in config panel
The toolset config panel highlighted the first keyless provider (e.g.
Nous Portal) on load instead of the provider actually written to config.
The /api/tools/toolsets/{name}/config endpoint never reported which
provider was active, so the GUI's default-expand logic fell back to
"first configured" — and keyless providers are always "configured".
Backend now annotates each provider with is_active (via the same
_is_provider_active helper the CLI 'hermes tools' picker uses) plus a
top-level active_provider summary. The panel prefers that signal before
falling back to first-configured/first.
Adds a frontend regression test (active provider is expanded on load)
and backend coverage (config reports is_active/active_provider; selecting
a provider round-trips into the next config read).
This commit is contained in:
parent
59510d7b44
commit
134643a2fa
5 changed files with 122 additions and 7 deletions
|
|
@ -26,6 +26,7 @@ function config(overrides: Partial<ToolsetConfig> = {}): ToolsetConfig {
|
|||
return {
|
||||
name: 'tts',
|
||||
has_category: true,
|
||||
active_provider: null,
|
||||
providers: [
|
||||
{
|
||||
name: 'Microsoft Edge TTS',
|
||||
|
|
@ -33,7 +34,8 @@ function config(overrides: Partial<ToolsetConfig> = {}): ToolsetConfig {
|
|||
tag: 'No API key needed',
|
||||
env_vars: [],
|
||||
post_setup: null,
|
||||
requires_nous_auth: false
|
||||
requires_nous_auth: false,
|
||||
is_active: false
|
||||
},
|
||||
{
|
||||
name: 'ElevenLabs',
|
||||
|
|
@ -43,7 +45,8 @@ function config(overrides: Partial<ToolsetConfig> = {}): ToolsetConfig {
|
|||
{ key: 'ELEVENLABS_API_KEY', prompt: 'ElevenLabs API key', url: 'https://x', default: null, is_set: false }
|
||||
],
|
||||
post_setup: null,
|
||||
requires_nous_auth: false
|
||||
requires_nous_auth: false,
|
||||
is_active: false
|
||||
}
|
||||
],
|
||||
...overrides
|
||||
|
|
@ -99,4 +102,54 @@ describe('ToolsetConfigPanel', () => {
|
|||
|
||||
await waitFor(() => expect(setEnvVar).toHaveBeenCalledWith('ELEVENLABS_API_KEY', 'sk-test-123'))
|
||||
})
|
||||
|
||||
it('expands the active provider on load, not just the first configured one', async () => {
|
||||
// ElevenLabs is the active provider per config, even though the keyless
|
||||
// Edge TTS provider sorts first and is also "configured". The panel must
|
||||
// honor is_active and expand ElevenLabs (so its API-key field renders)
|
||||
// rather than defaulting to the first keyless provider. Regression test
|
||||
// for the GUI showing the wrong provider selected after relaunch.
|
||||
getToolsetConfig.mockResolvedValue(
|
||||
config({
|
||||
active_provider: 'ElevenLabs',
|
||||
providers: [
|
||||
{
|
||||
name: 'Microsoft Edge TTS',
|
||||
badge: 'free',
|
||||
tag: 'No API key needed',
|
||||
env_vars: [],
|
||||
post_setup: null,
|
||||
requires_nous_auth: false,
|
||||
is_active: false
|
||||
},
|
||||
{
|
||||
name: 'ElevenLabs',
|
||||
badge: 'paid',
|
||||
tag: 'Most natural voices',
|
||||
env_vars: [
|
||||
{
|
||||
key: 'ELEVENLABS_API_KEY',
|
||||
prompt: 'ElevenLabs API key',
|
||||
url: 'https://x',
|
||||
default: null,
|
||||
is_set: true
|
||||
}
|
||||
],
|
||||
post_setup: null,
|
||||
requires_nous_auth: false,
|
||||
is_active: true
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
const { ToolsetConfigPanel } = await import('./toolset-config-panel')
|
||||
render(<ToolsetConfigPanel onConfiguredChange={vi.fn()} toolset="tts" />)
|
||||
|
||||
// The active provider's env-var field only renders when it's the expanded
|
||||
// one — so finding it proves ElevenLabs (not Edge TTS) was auto-expanded.
|
||||
expect(await screen.findByText('ELEVENLABS_API_KEY')).toBeTruthy()
|
||||
// No provider selection was triggered — this is purely reflecting state.
|
||||
expect(selectToolsetProvider).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -195,16 +195,23 @@ export function ToolsetConfigPanel({ toolset, onConfiguredChange }: ToolsetConfi
|
|||
|
||||
const providers = useMemo(() => cfg?.providers ?? [], [cfg])
|
||||
|
||||
// Default the expanded provider to the first one that is fully configured,
|
||||
// else the first provider.
|
||||
// Default the expanded provider to the one actually active in config
|
||||
// (`is_active` / `cfg.active_provider`, mirroring the CLI picker), then the
|
||||
// first fully-configured provider, else the first provider. Without this the
|
||||
// panel highlighted the first keyless provider (e.g. Nous Portal) even when
|
||||
// the user had already selected another (e.g. DuckDuckGo).
|
||||
useEffect(() => {
|
||||
if (activeProvider || providers.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const configured = providers.find(p => providerConfigured(p, envState))
|
||||
setActiveProvider((configured ?? providers[0]).name)
|
||||
}, [activeProvider, providers, envState])
|
||||
const selected =
|
||||
providers.find(p => p.is_active) ??
|
||||
(cfg?.active_provider ? providers.find(p => p.name === cfg.active_provider) : undefined) ??
|
||||
providers.find(p => providerConfigured(p, envState)) ??
|
||||
providers[0]
|
||||
setActiveProvider(selected.name)
|
||||
}, [activeProvider, providers, envState, cfg])
|
||||
|
||||
async function handleSelect(provider: ToolProvider) {
|
||||
setActiveProvider(provider.name)
|
||||
|
|
|
|||
|
|
@ -471,12 +471,17 @@ export interface ToolProvider {
|
|||
env_vars: ToolEnvVar[]
|
||||
post_setup: string | null
|
||||
requires_nous_auth: boolean
|
||||
/** True when this is the provider currently written to config (mirrors the
|
||||
* CLI `hermes tools` active-provider detection). */
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
export interface ToolsetConfig {
|
||||
name: string
|
||||
has_category: boolean
|
||||
providers: ToolProvider[]
|
||||
/** Name of the currently active provider, or null if none is configured. */
|
||||
active_provider: string | null
|
||||
}
|
||||
|
||||
export interface SessionSearchResult {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue