diff --git a/apps/desktop/src/renderer/src/hooks/use-provider.ts b/apps/desktop/src/renderer/src/hooks/use-provider.ts index 99cae540..f9c98b15 100644 --- a/apps/desktop/src/renderer/src/hooks/use-provider.ts +++ b/apps/desktop/src/renderer/src/hooks/use-provider.ts @@ -1,14 +1,11 @@ /** * Hook for managing LLM providers in the Desktop App. * - * Provides functionality similar to CLI `/provider` command: - * - List all providers with status - * - Get current provider/model - * - Switch provider/model + * Uses the global ProviderStore for state management. + * Data is fetched once at app startup and shared across all components. */ -import { useState, useEffect, useCallback } from 'react' - -// Types are defined in electron-env.d.ts and available globally +import { useCallback } from 'react' +import { useProviderStore } from '../stores/provider' interface UseProviderReturn { /** All providers with their status */ @@ -30,64 +27,24 @@ interface UseProviderReturn { } export function useProvider(): UseProviderReturn { - const [providers, setProviders] = useState([]) - const [current, setCurrent] = useState(null) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - - const refresh = useCallback(async () => { - setLoading(true) - setError(null) - - try { - const [providerList, currentInfo] = await Promise.all([ - window.electronAPI.provider.list(), - window.electronAPI.provider.current(), - ]) - - setProviders(providerList) - setCurrent(currentInfo) - } catch (err) { - const message = err instanceof Error ? err.message : String(err) - setError(message) - console.error('[useProvider] Failed to load providers:', message) - } finally { - setLoading(false) - } - }, []) - - // Load providers on mount - useEffect(() => { - refresh() - }, [refresh]) - - const setProvider = useCallback(async (providerId: string, modelId?: string) => { - setError(null) - - try { - const result = await window.electronAPI.provider.set(providerId, modelId) - - if (result.ok) { - // Refresh to update current status - await refresh() - return { ok: true } - } else { - setError(result.error ?? 'Unknown error') - return { ok: false, error: result.error } - } - } catch (err) { - const message = err instanceof Error ? err.message : String(err) - setError(message) - return { ok: false, error: message } - } - }, [refresh]) - - const getProviderMeta = useCallback((providerId: string) => { - return providers.find((p) => p.id === providerId) - }, [providers]) + const { + providers, + current, + loading, + error, + refresh, + setProvider, + } = useProviderStore() const availableProviders = providers.filter((p) => p.available) + const getProviderMeta = useCallback( + (providerId: string) => { + return providers.find((p) => p.id === providerId) + }, + [providers] + ) + return { providers, availableProviders, diff --git a/apps/desktop/src/renderer/src/stores/provider.ts b/apps/desktop/src/renderer/src/stores/provider.ts new file mode 100644 index 00000000..bc35572f --- /dev/null +++ b/apps/desktop/src/renderer/src/stores/provider.ts @@ -0,0 +1,92 @@ +import { create } from 'zustand' + +interface ProviderStore { + // State + providers: ProviderStatus[] + current: CurrentProviderInfo | null + loading: boolean + error: string | null + initialized: boolean + + // Actions + fetch: () => Promise + setProvider: (providerId: string, modelId?: string) => Promise<{ ok: boolean; error?: string }> + refresh: () => Promise +} + +export const useProviderStore = create()((set, get) => ({ + providers: [], + current: null, + loading: false, + error: null, + initialized: false, + + fetch: async () => { + // Skip if already initialized + if (get().initialized) return + + set({ loading: true, error: null }) + + try { + const [providerList, currentInfo] = await Promise.all([ + window.electronAPI.provider.list(), + window.electronAPI.provider.current(), + ]) + + set({ + providers: providerList, + current: currentInfo, + initialized: true, + }) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + set({ error: message }) + console.error('[ProviderStore] Failed to load providers:', message) + } finally { + set({ loading: false }) + } + }, + + refresh: async () => { + set({ loading: true, error: null }) + + try { + const [providerList, currentInfo] = await Promise.all([ + window.electronAPI.provider.list(), + window.electronAPI.provider.current(), + ]) + + set({ + providers: providerList, + current: currentInfo, + }) + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + set({ error: message }) + console.error('[ProviderStore] Failed to refresh providers:', message) + } finally { + set({ loading: false }) + } + }, + + setProvider: async (providerId: string, modelId?: string) => { + set({ error: null }) + + try { + const result = await window.electronAPI.provider.set(providerId, modelId) + + if (result.ok) { + // Refresh to update current status + await get().refresh() + return { ok: true } + } else { + set({ error: result.error ?? 'Unknown error' }) + return { ok: false, error: result.error } + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err) + set({ error: message }) + return { ok: false, error: message } + } + }, +}))