refactor(desktop): migrate provider state to zustand store

- Create ProviderStore for global provider state management
- Refactor useProvider hook to use the store
- Data fetched once at startup, shared across components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-02-11 17:45:19 +08:00
parent 5ae86feb2b
commit fc77cb89d3
2 changed files with 111 additions and 62 deletions

View file

@ -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<ProviderStatus[]>([])
const [current, setCurrent] = useState<CurrentProviderInfo | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(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,

View file

@ -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<void>
setProvider: (providerId: string, modelId?: string) => Promise<{ ok: boolean; error?: string }>
refresh: () => Promise<void>
}
export const useProviderStore = create<ProviderStore>()((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 }
}
},
}))