143 lines
5.6 KiB
TypeScript
143 lines
5.6 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import type { ProviderConfig, ModelInfo, ProviderPreset } from '@/contracts'
|
|
import { createProvider, deleteProvider as deleteProviderRequest, getCredentialStatus, listModels, listProviderPresets, listProviders, putCredential, testProvider as testProviderRequest, updateProvider as updateProviderRequest } from '@/services/providerService'
|
|
import { ApiErrorClass } from '@/services/apiClient'
|
|
import { t } from '@/i18n'
|
|
|
|
export const useProviderStore = defineStore('provider', () => {
|
|
const providers = ref<ProviderConfig[]>([])
|
|
const presets = ref<ProviderPreset[]>([])
|
|
const modelsByProvider = ref<Record<string, ModelInfo[]>>({})
|
|
const modelLoadingByProvider = ref<Record<string, boolean>>({})
|
|
const modelErrorsByProvider = ref<Record<string, string>>({})
|
|
const credentialConfiguredById = ref<Record<string, boolean>>({})
|
|
const defaultProviderId = ref('')
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
const enabledProviders = computed(() => providers.value.filter((p) => p.enabled))
|
|
const defaultProvider = computed(() =>
|
|
providers.value.find((p) => p.provider_id === defaultProviderId.value) || null
|
|
)
|
|
|
|
async function loadProviders() {
|
|
isLoading.value = true
|
|
try {
|
|
providers.value = await listProviders()
|
|
if (!enabledProviders.value.some(p => p.provider_id === defaultProviderId.value)) {
|
|
defaultProviderId.value = enabledProviders.value[0]?.provider_id ?? ''
|
|
}
|
|
error.value = null
|
|
} catch (reason) {
|
|
error.value = reason instanceof Error ? reason.message : t('Provider 加载失败', 'Failed to load Providers')
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function loadPresets() {
|
|
try {
|
|
presets.value = await listProviderPresets()
|
|
} catch (reason) {
|
|
error.value = reason instanceof Error ? reason.message : t('Provider 预设加载失败', 'Failed to load Provider presets')
|
|
}
|
|
}
|
|
|
|
async function loadModels(providerId: string): Promise<ModelInfo[]> {
|
|
modelLoadingByProvider.value[providerId] = true
|
|
delete modelErrorsByProvider.value[providerId]
|
|
try {
|
|
const models = await listModels(providerId)
|
|
const uniqueModels = [...new Map(models.map((model) => [model.model_id, model])).values()]
|
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
modelsByProvider.value[providerId] = uniqueModels
|
|
return uniqueModels
|
|
} catch (reason) {
|
|
const provider = providers.value.find((item) => item.provider_id === providerId)
|
|
const credentialId = provider?.credential_id
|
|
let message = reason instanceof Error ? reason.message : t('模型列表获取失败', 'Failed to load the model list')
|
|
if (reason instanceof ApiErrorClass && reason.code === 'PROVIDER_CREDENTIAL_MISSING') {
|
|
message = t('尚未配置 API Key,请编辑该 Provider 后填写并保存。', 'No API key is configured. Edit this Provider, enter a key, and save it.')
|
|
} else if (reason instanceof ApiErrorClass && reason.code === 'PROVIDER_AUTH_FAILED') {
|
|
message = t(`鉴权失败,请检查凭据“${credentialId || '未设置'}”对应的 API Key 是否有效。`, `Authentication failed. Check the API key for credential “${credentialId || 'not set'}”.`)
|
|
}
|
|
modelErrorsByProvider.value[providerId] = message
|
|
throw reason
|
|
} finally {
|
|
modelLoadingByProvider.value[providerId] = false
|
|
}
|
|
}
|
|
|
|
async function refreshEnabledModels() {
|
|
await Promise.allSettled(
|
|
providers.value.filter((provider) => provider.enabled).map((provider) => loadModels(provider.provider_id))
|
|
)
|
|
}
|
|
|
|
async function loadCredentialStatus(credentialId: string): Promise<boolean> {
|
|
const configured = await getCredentialStatus(credentialId)
|
|
credentialConfiguredById.value[credentialId] = configured
|
|
return configured
|
|
}
|
|
|
|
async function saveCredential(credentialId: string, apiKey: string) {
|
|
await putCredential(credentialId, apiKey)
|
|
credentialConfiguredById.value[credentialId] = true
|
|
}
|
|
|
|
async function addProvider(data: Omit<ProviderConfig, 'provider_id'>) {
|
|
const newProvider = await createProvider(data)
|
|
providers.value.push(newProvider)
|
|
return newProvider
|
|
}
|
|
|
|
async function updateProvider(providerId: string, data: Partial<ProviderConfig>) {
|
|
const updated = await updateProviderRequest(providerId, data)
|
|
const index = providers.value.findIndex((provider) => provider.provider_id === providerId)
|
|
if (index >= 0) providers.value[index] = updated
|
|
return updated
|
|
}
|
|
|
|
async function deleteProvider(providerId: string) {
|
|
await deleteProviderRequest(providerId)
|
|
const idx = providers.value.findIndex((p) => p.provider_id === providerId)
|
|
if (idx > -1) providers.value.splice(idx, 1)
|
|
delete modelsByProvider.value[providerId]
|
|
}
|
|
|
|
async function testProvider(providerId: string): Promise<{ success: boolean; latency_ms?: number; error?: string }> {
|
|
const result = await testProviderRequest(providerId)
|
|
return { success: result.success, latency_ms: result.latency_ms, error: result.error_message }
|
|
}
|
|
|
|
function setDefaultProvider(providerId: string) {
|
|
defaultProviderId.value = providerId
|
|
}
|
|
|
|
return {
|
|
providers,
|
|
presets,
|
|
modelsByProvider,
|
|
modelLoadingByProvider,
|
|
modelErrorsByProvider,
|
|
credentialConfiguredById,
|
|
defaultProviderId,
|
|
enabledProviders,
|
|
defaultProvider,
|
|
isLoading,
|
|
error,
|
|
loadProviders,
|
|
loadPresets,
|
|
loadModels,
|
|
refreshEnabledModels,
|
|
loadCredentialStatus,
|
|
saveCredential,
|
|
addProvider,
|
|
updateProvider,
|
|
deleteProvider,
|
|
testProvider,
|
|
setDefaultProvider,
|
|
}
|
|
})
|