1- import React , { useState , useEffect , useCallback } from "react" ;
1+ import React , { useState , useCallback } from "react" ;
22import { Plus , Loader2 } from "lucide-react" ;
3- import type { ProvidersConfigMap } from "../types" ;
43import { SUPPORTED_PROVIDERS , PROVIDER_DISPLAY_NAMES } from "@/common/constants/providers" ;
54import { KNOWN_MODELS } from "@/common/constants/knownModels" ;
65import { useModelLRU } from "@/browser/hooks/useModelLRU" ;
76import { ModelRow } from "./ModelRow" ;
87import { useAPI } from "@/browser/contexts/API" ;
8+ import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig" ;
99
1010interface NewModelForm {
1111 provider : string ;
@@ -20,22 +20,12 @@ interface EditingState {
2020
2121export function ModelsSection ( ) {
2222 const { api } = useAPI ( ) ;
23- const [ config , setConfig ] = useState < ProvidersConfigMap | null > ( null ) ;
23+ const { config, loading , updateModelsOptimistically } = useProvidersConfig ( ) ;
2424 const [ newModel , setNewModel ] = useState < NewModelForm > ( { provider : "" , modelId : "" } ) ;
25- const [ saving , setSaving ] = useState ( false ) ;
2625 const [ editing , setEditing ] = useState < EditingState | null > ( null ) ;
2726 const [ error , setError ] = useState < string | null > ( null ) ;
2827 const { defaultModel, setDefaultModel } = useModelLRU ( ) ;
2928
30- // Load config on mount
31- useEffect ( ( ) => {
32- if ( ! api ) return ;
33- void ( async ( ) => {
34- const cfg = await api . providers . getConfig ( ) ;
35- setConfig ( cfg ?? null ) ;
36- } ) ( ) ;
37- } , [ api ] ) ;
38-
3929 // Check if a model already exists (for duplicate prevention)
4030 const modelExists = useCallback (
4131 ( provider : string , modelId : string , excludeOriginal ?: string ) : boolean => {
@@ -46,7 +36,7 @@ export function ModelsSection() {
4636 [ config ]
4737 ) ;
4838
49- const handleAddModel = useCallback ( async ( ) => {
39+ const handleAddModel = useCallback ( ( ) => {
5040 if ( ! config || ! newModel . provider || ! newModel . modelId . trim ( ) ) return ;
5141
5242 const trimmedModelId = newModel . modelId . trim ( ) ;
@@ -59,40 +49,31 @@ export function ModelsSection() {
5949
6050 if ( ! api ) return ;
6151 setError ( null ) ;
62- setSaving ( true ) ;
63- try {
64- const currentModels = config [ newModel . provider ] ?. models ?? [ ] ;
65- const updatedModels = [ ...currentModels , trimmedModelId ] ;
6652
67- await api . providers . setModels ( { provider : newModel . provider , models : updatedModels } ) ;
53+ // Optimistic update - returns new models array for API call
54+ const updatedModels = updateModelsOptimistically ( newModel . provider , ( models ) => [
55+ ...models ,
56+ trimmedModelId ,
57+ ] ) ;
58+ setNewModel ( { provider : "" , modelId : "" } ) ;
6859
69- // Refresh config
70- const cfg = await api . providers . getConfig ( ) ;
71- setConfig ( cfg ?? null ) ;
72- setNewModel ( { provider : "" , modelId : "" } ) ;
73- } finally {
74- setSaving ( false ) ;
75- }
76- } , [ api , newModel , config , modelExists ] ) ;
60+ // Save in background
61+ void api . providers . setModels ( { provider : newModel . provider , models : updatedModels } ) ;
62+ } , [ api , newModel , config , modelExists , updateModelsOptimistically ] ) ;
7763
7864 const handleRemoveModel = useCallback (
79- async ( provider : string , modelId : string ) => {
65+ ( provider : string , modelId : string ) => {
8066 if ( ! config || ! api ) return ;
81- setSaving ( true ) ;
82- try {
83- const currentModels = config [ provider ] ?. models ?? [ ] ;
84- const updatedModels = currentModels . filter ( ( m ) => m !== modelId ) ;
8567
86- await api . providers . setModels ( { provider, models : updatedModels } ) ;
68+ // Optimistic update - returns new models array for API call
69+ const updatedModels = updateModelsOptimistically ( provider , ( models ) =>
70+ models . filter ( ( m ) => m !== modelId )
71+ ) ;
8772
88- // Refresh config
89- const cfg = await api . providers . getConfig ( ) ;
90- setConfig ( cfg ?? null ) ;
91- } finally {
92- setSaving ( false ) ;
93- }
73+ // Save in background
74+ void api . providers . setModels ( { provider, models : updatedModels } ) ;
9475 } ,
95- [ api , config ]
76+ [ api , config , updateModelsOptimistically ]
9677 ) ;
9778
9879 const handleStartEdit = useCallback ( ( provider : string , modelId : string ) => {
@@ -105,7 +86,7 @@ export function ModelsSection() {
10586 setError ( null ) ;
10687 } , [ ] ) ;
10788
108- const handleSaveEdit = useCallback ( async ( ) => {
89+ const handleSaveEdit = useCallback ( ( ) => {
10990 if ( ! config || ! editing || ! api ) return ;
11091
11192 const trimmedModelId = editing . newModelId . trim ( ) ;
@@ -123,26 +104,19 @@ export function ModelsSection() {
123104 }
124105
125106 setError ( null ) ;
126- setSaving ( true ) ;
127- try {
128- const currentModels = config [ editing . provider ] ?. models ?? [ ] ;
129- const updatedModels = currentModels . map ( ( m ) =>
130- m === editing . originalModelId ? trimmedModelId : m
131- ) ;
132107
133- await api . providers . setModels ( { provider : editing . provider , models : updatedModels } ) ;
108+ // Optimistic update - returns new models array for API call
109+ const updatedModels = updateModelsOptimistically ( editing . provider , ( models ) =>
110+ models . map ( ( m ) => ( m === editing . originalModelId ? trimmedModelId : m ) )
111+ ) ;
112+ setEditing ( null ) ;
134113
135- // Refresh config
136- const cfg = await api . providers . getConfig ( ) ;
137- setConfig ( cfg ?? null ) ;
138- setEditing ( null ) ;
139- } finally {
140- setSaving ( false ) ;
141- }
142- } , [ api , editing , config , modelExists ] ) ;
114+ // Save in background
115+ void api . providers . setModels ( { provider : editing . provider , models : updatedModels } ) ;
116+ } , [ api , editing , config , modelExists , updateModelsOptimistically ] ) ;
143117
144118 // Show loading state while config is being fetched
145- if ( config === null ) {
119+ if ( loading || ! config ) {
146120 return (
147121 < div className = "flex items-center justify-center gap-2 py-12" >
148122 < Loader2 className = "text-muted h-5 w-5 animate-spin" />
@@ -211,8 +185,8 @@ export function ModelsSection() {
211185 />
212186 < button
213187 type = "button"
214- onClick = { ( ) => void handleAddModel ( ) }
215- disabled = { saving || ! newModel . provider || ! newModel . modelId . trim ( ) }
188+ onClick = { handleAddModel }
189+ disabled = { ! newModel . provider || ! newModel . modelId . trim ( ) }
216190 className = "bg-accent hover:bg-accent-dark disabled:bg-border-medium flex items-center gap-1 rounded px-2 py-1 text-xs text-white transition-colors disabled:cursor-not-allowed"
217191 >
218192 < Plus className = "h-3.5 w-3.5" />
@@ -237,16 +211,16 @@ export function ModelsSection() {
237211 isEditing = { isModelEditing }
238212 editValue = { isModelEditing ? editing . newModelId : undefined }
239213 editError = { isModelEditing ? error : undefined }
240- saving = { saving }
214+ saving = { false }
241215 hasActiveEdit = { editing !== null }
242216 onSetDefault = { ( ) => setDefaultModel ( model . fullId ) }
243217 onStartEdit = { ( ) => handleStartEdit ( model . provider , model . modelId ) }
244- onSaveEdit = { ( ) => void handleSaveEdit ( ) }
218+ onSaveEdit = { handleSaveEdit }
245219 onCancelEdit = { handleCancelEdit }
246220 onEditChange = { ( value ) =>
247221 setEditing ( ( prev ) => ( prev ? { ...prev , newModelId : value } : null ) )
248222 }
249- onRemove = { ( ) => void handleRemoveModel ( model . provider , model . modelId ) }
223+ onRemove = { ( ) => handleRemoveModel ( model . provider , model . modelId ) }
250224 />
251225 ) ;
252226 } ) }
0 commit comments