| 1 | import { useCallback, useEffect, useMemo, useState } from 'react' |
| 2 | import { useNavigate } from 'react-router-dom' |
| 3 | import { useT } from '@renderer/i18n' |
| 4 | import { useSettingsStore, useToastStore } from '@renderer/store' |
| 5 | import type { ModelConfig } from '@renderer/lib/ipc' |
| 6 | |
| 7 | export interface ModelActionState { |
| 8 | modelConfigs: ModelConfig[] |
| 9 | selectedModelConfigId: string |
| 10 | activatingModelConfigId: string | null |
| 11 | hasMultipleModelConfigs: boolean |
| 12 | currentModelConfig: ModelConfig | null |
| 13 | ensureModelActive: (modelConfigId?: string) => Promise<string | null> |
| 14 | } |
| 15 | |
| 16 | export function useModelAction(): ModelActionState { |
| 17 | const t = useT() |
| 18 | const navigate = useNavigate() |
| 19 | const { error: toastError, warning: toastWarning } = useToastStore() |
| 20 | const { modelConfigs, fetchSettings } = useSettingsStore() |
| 21 | const [selectedModelConfigId, setSelectedModelConfigId] = useState('') |
| 22 | const [activatingModelConfigId, setActivatingModelConfigId] = useState<string | null>(null) |
| 23 | |
| 24 | useEffect(() => { |
| 25 | void fetchSettings() |
| 26 | }, [fetchSettings]) |
| 27 | |
| 28 | useEffect(() => { |
| 29 | setSelectedModelConfigId((current) => { |
| 30 | if (current && modelConfigs.some((config) => config.id === current)) return current |
| 31 | return modelConfigs.find((config) => config.active)?.id || modelConfigs[0]?.id || '' |
| 32 | }) |
| 33 | }, [modelConfigs]) |
| 34 | |
| 35 | const ensureModelActive = useCallback( |
| 36 | async (modelConfigId = selectedModelConfigId): Promise<string | null> => { |
| 37 | const warnModelSettingsRequired = (): void => { |
| 38 | toastWarning(t('settings.modelSettingsRequiredTitle'), { |
| 39 | description: t('settings.modelSettingsRequiredDescription'), |
| 40 | action: { |
| 41 | label: t('home.goToSettings'), |
| 42 | onClick: () => navigate('/settings') |
| 43 | } |
| 44 | }) |
| 45 | } |
| 46 | if (activatingModelConfigId) return null |
| 47 | |
| 48 | let latestConfigs = useSettingsStore.getState().modelConfigs |
| 49 | if (latestConfigs.length === 0) { |
| 50 | await fetchSettings() |
| 51 | latestConfigs = useSettingsStore.getState().modelConfigs |
| 52 | } |
| 53 | |
| 54 | const nextModelConfigId = |
| 55 | modelConfigId || |
| 56 | selectedModelConfigId || |
| 57 | latestConfigs.find((config) => config.active)?.id || |
| 58 | latestConfigs[0]?.id || |
| 59 | '' |
| 60 | if (!nextModelConfigId) { |
| 61 | warnModelSettingsRequired() |
| 62 | return null |
| 63 | } |
| 64 | |
| 65 | const selected = |
| 66 | latestConfigs.find((config) => config.id === nextModelConfigId) || |
| 67 | modelConfigs.find((config) => config.id === nextModelConfigId) |
| 68 | if (!selected) { |
| 69 | warnModelSettingsRequired() |
| 70 | return null |
| 71 | } |
| 72 | if (!selected.model.trim() || !selected.apiKey.trim()) { |
| 73 | warnModelSettingsRequired() |
| 74 | return null |
| 75 | } |
| 76 | |
| 77 | const previousModelConfigId = selectedModelConfigId |
| 78 | setSelectedModelConfigId(nextModelConfigId) |
| 79 | setActivatingModelConfigId(nextModelConfigId) |
| 80 | try { |
| 81 | return nextModelConfigId |
| 82 | } catch (error) { |
| 83 | setSelectedModelConfigId(previousModelConfigId) |
| 84 | toastError(t('settings.activateModelFailed'), { |
| 85 | description: error instanceof Error ? error.message : String(error) |
| 86 | }) |
| 87 | return null |
| 88 | } finally { |
| 89 | setActivatingModelConfigId(null) |
| 90 | } |
| 91 | }, |
| 92 | [ |
| 93 | activatingModelConfigId, |
| 94 | fetchSettings, |
| 95 | modelConfigs, |
| 96 | navigate, |
| 97 | selectedModelConfigId, |
| 98 | t, |
| 99 | toastError, |
| 100 | toastWarning |
| 101 | ] |
| 102 | ) |
| 103 | |
| 104 | const currentModelConfig = useMemo( |
| 105 | () => modelConfigs.find((config) => config.id === selectedModelConfigId) || null, |
| 106 | [modelConfigs, selectedModelConfigId] |
| 107 | ) |
| 108 | |
| 109 | return { |
| 110 | modelConfigs, |
| 111 | selectedModelConfigId, |
| 112 | activatingModelConfigId, |
| 113 | hasMultipleModelConfigs: modelConfigs.length > 1, |
| 114 | currentModelConfig, |
| 115 | ensureModelActive |
| 116 | } |
| 117 | } |
| 118 |