fix: use webprovider services to fetch models
This commit is contained in:
parent
045778406f
commit
66af5c7386
@ -1,22 +1,19 @@
|
|||||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
import { renderHook, waitFor } from '@testing-library/react'
|
import { renderHook, waitFor } from '@testing-library/react'
|
||||||
import { useProviderModels } from '../useProviderModels'
|
import { useProviderModels } from '../useProviderModels'
|
||||||
|
import { WebProvidersService } from '../../services/providers/web'
|
||||||
|
|
||||||
// Mock the providers service
|
let fetchModelsSpy: ReturnType<typeof vi.spyOn>
|
||||||
vi.mock('@/services/providers', () => ({
|
|
||||||
fetchModelsFromProvider: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
import { fetchModelsFromProvider } from '@/services/providers'
|
// Local minimal provider type for tests
|
||||||
const mockFetchModelsFromProvider = vi.mocked(fetchModelsFromProvider)
|
type MockModelProvider = {
|
||||||
|
active: boolean
|
||||||
import type { ModelProvider } from '@/types/modelProviders'
|
provider: string
|
||||||
|
base_url?: string
|
||||||
// Mock ModelProvider type
|
api_key?: string
|
||||||
type MockModelProvider = Pick<
|
settings: any[]
|
||||||
ModelProvider,
|
models: any[]
|
||||||
'active' | 'provider' | 'base_url' | 'api_key' | 'settings' | 'models'
|
}
|
||||||
>
|
|
||||||
|
|
||||||
describe('useProviderModels', () => {
|
describe('useProviderModels', () => {
|
||||||
const mockProvider: MockModelProvider = {
|
const mockProvider: MockModelProvider = {
|
||||||
@ -31,7 +28,12 @@ describe('useProviderModels', () => {
|
|||||||
const mockModels = ['gpt-4', 'gpt-3.5-turbo', 'gpt-4-turbo']
|
const mockModels = ['gpt-4', 'gpt-3.5-turbo', 'gpt-4-turbo']
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
fetchModelsSpy = vi.spyOn(
|
||||||
|
WebProvidersService.prototype,
|
||||||
|
'fetchModelsFromProvider'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should initialize with empty state', () => {
|
it('should initialize with empty state', () => {
|
||||||
@ -45,17 +47,17 @@ describe('useProviderModels', () => {
|
|||||||
|
|
||||||
it('should not fetch models when provider is undefined', () => {
|
it('should not fetch models when provider is undefined', () => {
|
||||||
renderHook(() => useProviderModels(undefined))
|
renderHook(() => useProviderModels(undefined))
|
||||||
expect(mockFetchModelsFromProvider).not.toHaveBeenCalled()
|
expect(fetchModelsSpy).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not fetch models when provider has no base_url', () => {
|
it('should not fetch models when provider has no base_url', () => {
|
||||||
const providerWithoutUrl = { ...mockProvider, base_url: undefined }
|
const providerWithoutUrl = { ...mockProvider, base_url: undefined }
|
||||||
renderHook(() => useProviderModels(providerWithoutUrl))
|
renderHook(() => useProviderModels(providerWithoutUrl))
|
||||||
expect(mockFetchModelsFromProvider).not.toHaveBeenCalled()
|
expect(fetchModelsSpy).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should fetch and sort models', async () => {
|
it('should fetch and sort models', async () => {
|
||||||
mockFetchModelsFromProvider.mockResolvedValueOnce(mockModels)
|
fetchModelsSpy.mockResolvedValueOnce(mockModels)
|
||||||
|
|
||||||
const { result } = renderHook(() => useProviderModels(mockProvider))
|
const { result } = renderHook(() => useProviderModels(mockProvider))
|
||||||
|
|
||||||
@ -66,11 +68,11 @@ describe('useProviderModels', () => {
|
|||||||
// Should be sorted alphabetically
|
// Should be sorted alphabetically
|
||||||
expect(result.current.models).toEqual(['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo'])
|
expect(result.current.models).toEqual(['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo'])
|
||||||
expect(result.current.error).toBe(null)
|
expect(result.current.error).toBe(null)
|
||||||
expect(mockFetchModelsFromProvider).toHaveBeenCalledWith(mockProvider)
|
expect(fetchModelsSpy).toHaveBeenCalledWith(mockProvider)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should clear models when switching to invalid provider', async () => {
|
it('should clear models when switching to invalid provider', async () => {
|
||||||
mockFetchModelsFromProvider.mockResolvedValueOnce(mockModels)
|
fetchModelsSpy.mockResolvedValueOnce(mockModels)
|
||||||
|
|
||||||
const { result, rerender } = renderHook(
|
const { result, rerender } = renderHook(
|
||||||
({ provider }) => useProviderModels(provider),
|
({ provider }) => useProviderModels(provider),
|
||||||
@ -96,6 +98,6 @@ describe('useProviderModels', () => {
|
|||||||
|
|
||||||
result.current.refetch()
|
result.current.refetch()
|
||||||
|
|
||||||
expect(mockFetchModelsFromProvider).not.toHaveBeenCalled()
|
expect(fetchModelsSpy).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react'
|
||||||
import { fetchModelsFromProvider } from '@/services/providers'
|
import { WebProvidersService } from '../services/providers/web'
|
||||||
|
|
||||||
type UseProviderModelsState = {
|
type UseProviderModelsState = {
|
||||||
models: string[]
|
models: string[]
|
||||||
@ -12,6 +12,7 @@ const modelsCache = new Map<string, { models: string[]; timestamp: number }>()
|
|||||||
const CACHE_DURATION = 5 * 60 * 1000 // 5 minutes
|
const CACHE_DURATION = 5 * 60 * 1000 // 5 minutes
|
||||||
|
|
||||||
export const useProviderModels = (provider?: ModelProvider): UseProviderModelsState => {
|
export const useProviderModels = (provider?: ModelProvider): UseProviderModelsState => {
|
||||||
|
const providersService = useMemo(() => new WebProvidersService(), [])
|
||||||
const [models, setModels] = useState<string[]>([])
|
const [models, setModels] = useState<string[]>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@ -50,7 +51,7 @@ export const useProviderModels = (provider?: ModelProvider): UseProviderModelsSt
|
|||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fetchedModels = await fetchModelsFromProvider(provider)
|
const fetchedModels = await providersService.fetchModelsFromProvider(provider)
|
||||||
if (currentRequestId !== requestIdRef.current) return
|
if (currentRequestId !== requestIdRef.current) return
|
||||||
const sortedModels = fetchedModels.sort((a, b) => a.localeCompare(b))
|
const sortedModels = fetchedModels.sort((a, b) => a.localeCompare(b))
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ export const useProviderModels = (provider?: ModelProvider): UseProviderModelsSt
|
|||||||
} finally {
|
} finally {
|
||||||
if (currentRequestId === requestIdRef.current) setLoading(false)
|
if (currentRequestId === requestIdRef.current) setLoading(false)
|
||||||
}
|
}
|
||||||
}, [provider])
|
}, [provider, providersService])
|
||||||
|
|
||||||
const refetch = useCallback(() => {
|
const refetch = useCallback(() => {
|
||||||
if (provider) {
|
if (provider) {
|
||||||
|
|||||||
@ -138,9 +138,24 @@ export class WebProvidersService implements ProvidersService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
// Provide more specific error messages based on status code
|
||||||
`Cannot connect to ${provider.provider} at ${provider.base_url}. Please check that the service is running and accessible.`
|
if (response.status === 401) {
|
||||||
)
|
throw new Error(
|
||||||
|
`Authentication failed: API key is required or invalid for ${provider.provider}`
|
||||||
|
)
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
throw new Error(
|
||||||
|
`Access forbidden: Check your API key permissions for ${provider.provider}`
|
||||||
|
)
|
||||||
|
} else if (response.status === 404) {
|
||||||
|
throw new Error(
|
||||||
|
`Models endpoint not found for ${provider.provider}. Check the base URL configuration.`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
@ -170,13 +185,28 @@ export class WebProvidersService implements ProvidersService {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching models from provider:', error)
|
console.error('Error fetching models from provider:', error)
|
||||||
|
|
||||||
// Provide helpful error message for any connection errors
|
const structuredErrorPrefixes = [
|
||||||
if (error instanceof Error && error.message.includes('Cannot connect')) {
|
'Authentication failed',
|
||||||
throw error
|
'Access forbidden',
|
||||||
|
'Models endpoint not found',
|
||||||
|
'Failed to fetch models from'
|
||||||
|
]
|
||||||
|
|
||||||
|
if (error instanceof Error &&
|
||||||
|
structuredErrorPrefixes.some(prefix => (error as Error).message.startsWith(prefix))) {
|
||||||
|
throw new Error(error.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provide helpful error message for any connection errors
|
||||||
|
if (error instanceof Error && error.message.includes('Cannot connect')) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot connect to ${provider.provider} at ${provider.base_url}. Please check that the service is running and accessible.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic fallback
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot connect to ${provider.provider} at ${provider.base_url}. Please check that the service is running and accessible.`
|
`Unexpected error while fetching models from ${provider.provider}: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user