mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix: fix data inconsistency in ai provider config (#11198)
🐛 fix: fix ai provider api error
This commit is contained in:
@@ -4,8 +4,16 @@ import { cleanObject } from './object';
|
||||
|
||||
describe('cleanObject', () => {
|
||||
it('should remove null, undefined and empty string fields', () => {
|
||||
const input = { a: 1, b: null, c: undefined, d: '', e: 0, f: false } as const;
|
||||
const input = {
|
||||
a: 1,
|
||||
b: null,
|
||||
c: undefined,
|
||||
d: '',
|
||||
e: 0,
|
||||
f: false,
|
||||
abc: { d: undefined },
|
||||
} as const;
|
||||
const res = cleanObject({ ...input });
|
||||
expect(res).toEqual({ a: 1, e: 0, f: false });
|
||||
expect(res).toEqual({ a: 1, e: 0, f: false, abc: {} });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,7 +155,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
||||
isProviderEndpointNotEmpty,
|
||||
isProviderApiKeyNotEmpty,
|
||||
] = useAiInfraStore((s) => [
|
||||
aiProviderSelectors.activeProviderConfig(s),
|
||||
aiProviderSelectors.providerDetailById(id)(s),
|
||||
s.updateAiProviderConfig,
|
||||
aiProviderSelectors.isProviderEnabled(id)(s),
|
||||
aiProviderSelectors.isAiProviderConfigLoading(id)(s),
|
||||
|
||||
@@ -62,6 +62,7 @@ describe('aiModelSelectors', () => {
|
||||
],
|
||||
activeProviderModelList: [],
|
||||
aiProviderConfigUpdatingIds: [],
|
||||
aiProviderDetailMap: {},
|
||||
aiProviderList: [],
|
||||
aiProviderLoadingIds: [],
|
||||
providerSearchKeyword: '',
|
||||
|
||||
@@ -10,11 +10,13 @@ describe('aiProviderSelectors', () => {
|
||||
{ id: 'provider3', enabled: true, sort: 0, source: 'builtin' },
|
||||
{ id: 'custom1', enabled: false, sort: 3, source: 'custom' },
|
||||
],
|
||||
aiProviderDetail: {
|
||||
id: 'provider1',
|
||||
keyVaults: {
|
||||
baseURL: 'https://api.example.com',
|
||||
apiKey: 'test-key',
|
||||
aiProviderDetailMap: {
|
||||
provider1: {
|
||||
id: 'provider1',
|
||||
keyVaults: {
|
||||
baseURL: 'https://api.example.com',
|
||||
apiKey: 'test-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
aiProviderLoadingIds: ['loading-provider'],
|
||||
@@ -97,20 +99,37 @@ describe('aiProviderSelectors', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeProviderConfig', () => {
|
||||
it('should return active provider config', () => {
|
||||
expect(aiProviderSelectors.activeProviderConfig(mockState)).toEqual(
|
||||
mockState.aiProviderDetail,
|
||||
describe('providerDetailById', () => {
|
||||
it('should return provider detail by id', () => {
|
||||
expect(aiProviderSelectors.providerDetailById('provider1')(mockState)).toEqual(
|
||||
mockState.aiProviderDetailMap.provider1,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined for non-existing provider', () => {
|
||||
expect(aiProviderSelectors.providerDetailById('non-existing')(mockState)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeProviderConfig', () => {
|
||||
it('should return active provider config from map', () => {
|
||||
expect(aiProviderSelectors.activeProviderConfig(mockState)).toEqual(
|
||||
mockState.aiProviderDetailMap.provider1,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined when no active provider', () => {
|
||||
const stateWithoutActive = { ...mockState, activeAiProvider: undefined };
|
||||
expect(aiProviderSelectors.activeProviderConfig(stateWithoutActive)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAiProviderConfigLoading', () => {
|
||||
it('should return true if provider id does not match active provider', () => {
|
||||
it('should return true if provider is not in detail map (not loaded)', () => {
|
||||
expect(aiProviderSelectors.isAiProviderConfigLoading('provider2')(mockState)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if provider id matches active provider', () => {
|
||||
it('should return false if provider is in detail map (loaded)', () => {
|
||||
expect(aiProviderSelectors.isAiProviderConfigLoading('provider1')(mockState)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -123,7 +142,9 @@ describe('aiProviderSelectors', () => {
|
||||
it('should return false when no endpoint info exists', () => {
|
||||
const stateWithoutEndpoint = {
|
||||
...mockState,
|
||||
aiProviderDetail: { keyVaults: {} },
|
||||
aiProviderDetailMap: {
|
||||
provider1: { id: 'provider1', keyVaults: {} },
|
||||
},
|
||||
};
|
||||
expect(aiProviderSelectors.isActiveProviderEndpointNotEmpty(stateWithoutEndpoint)).toBe(
|
||||
false,
|
||||
@@ -139,7 +160,9 @@ describe('aiProviderSelectors', () => {
|
||||
it('should return false when no api key exists', () => {
|
||||
const stateWithoutApiKey = {
|
||||
...mockState,
|
||||
aiProviderDetail: { keyVaults: {} },
|
||||
aiProviderDetailMap: {
|
||||
provider1: { id: 'provider1', keyVaults: {} },
|
||||
},
|
||||
};
|
||||
expect(aiProviderSelectors.isActiveProviderApiKeyNotEmpty(stateWithoutApiKey)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
type UpdateAiProviderParams,
|
||||
} from '@/types/aiProvider';
|
||||
|
||||
|
||||
export type ProviderModelListItem = {
|
||||
abilities: ModelAbilities;
|
||||
approximatePricePerImage?: number;
|
||||
@@ -293,7 +294,14 @@ export const createAiProviderSlice: StateCreator<
|
||||
onSuccess: (data) => {
|
||||
if (!data) return;
|
||||
|
||||
set({ activeAiProvider: id, aiProviderDetail: data }, false, 'useFetchAiProviderItem');
|
||||
set(
|
||||
(state) => ({
|
||||
activeAiProvider: id,
|
||||
aiProviderDetailMap: { ...state.aiProviderDetailMap, [id]: data },
|
||||
}),
|
||||
false,
|
||||
'useFetchAiProviderItem',
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
@@ -12,7 +12,11 @@ export interface AIProviderState {
|
||||
activeAiProvider?: string;
|
||||
activeProviderModelList: any[];
|
||||
aiProviderConfigUpdatingIds: string[];
|
||||
aiProviderDetail?: AiProviderDetailItem | null;
|
||||
/**
|
||||
* Map of provider id to provider detail, used for caching provider details
|
||||
* to avoid data inconsistency when switching providers
|
||||
*/
|
||||
aiProviderDetailMap: Record<string, AiProviderDetailItem>;
|
||||
aiProviderList: AiProviderListItem[];
|
||||
aiProviderLoadingIds: string[];
|
||||
aiProviderRuntimeConfig: Record<string, AiProviderRuntimeConfig>;
|
||||
@@ -29,6 +33,7 @@ export interface AIProviderState {
|
||||
export const initialAIProviderState: AIProviderState = {
|
||||
activeProviderModelList: [],
|
||||
aiProviderConfigUpdatingIds: [],
|
||||
aiProviderDetailMap: {},
|
||||
aiProviderList: [],
|
||||
aiProviderLoadingIds: [],
|
||||
aiProviderRuntimeConfig: {},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isProviderDisableBrowserRequest } from 'model-bank/modelProviders';
|
||||
|
||||
import { type AIProviderStoreState } from '@/store/aiInfra/initialState';
|
||||
import { type AiProviderRuntimeConfig, AiProviderSourceEnum } from '@/types/aiProvider';
|
||||
import { type GlobalLLMProviderKey } from '@/types/user/settings';
|
||||
@@ -21,12 +22,24 @@ const isProviderEnabled = (id: string) => (s: AIProviderStoreState) =>
|
||||
const isProviderLoading = (id: string) => (s: AIProviderStoreState) =>
|
||||
s.aiProviderLoadingIds.includes(id);
|
||||
|
||||
const activeProviderConfig = (s: AIProviderStoreState) => s.aiProviderDetail;
|
||||
|
||||
// Detail
|
||||
|
||||
/**
|
||||
* Get provider detail by id from the cache map
|
||||
*/
|
||||
const providerDetailById = (id: string) => (s: AIProviderStoreState) => s.aiProviderDetailMap[id];
|
||||
|
||||
/**
|
||||
* Get active provider config from the cache map
|
||||
*/
|
||||
const activeProviderConfig = (s: AIProviderStoreState) =>
|
||||
s.activeAiProvider ? s.aiProviderDetailMap[s.activeAiProvider] : undefined;
|
||||
|
||||
/**
|
||||
* Check if provider config is loading (data not yet in cache)
|
||||
*/
|
||||
const isAiProviderConfigLoading = (id: string) => (s: AIProviderStoreState) =>
|
||||
s.activeAiProvider !== id;
|
||||
!s.aiProviderDetailMap[id];
|
||||
|
||||
const providerWhitelist = new Set(['ollama', 'lmstudio']);
|
||||
|
||||
@@ -134,5 +147,6 @@ export const aiProviderSelectors = {
|
||||
isProviderHasBuiltinSearchConfig,
|
||||
isProviderLoading,
|
||||
providerConfigById,
|
||||
providerDetailById,
|
||||
providerKeyVaults,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user