feat(stage-web|stage-tamagotchi): multi-categorized providers

This commit is contained in:
Neko Ayaka
2025-06-06 18:20:05 +08:00
parent 2e1260f0c2
commit 7d52df6cc0
7 changed files with 351 additions and 64 deletions
@@ -8,7 +8,7 @@ import { RouterLink } from 'vue-router'
const providersStore = useProvidersStore()
const consciousnessStore = useConsciousnessStore()
const { availableProviders, availableTextGenerationsProvidersMetadata } = storeToRefs(providersStore)
const { availableProviders, allChatProvidersMetadata } = storeToRefs(providersStore)
const {
activeProvider,
activeModel,
@@ -57,7 +57,7 @@ function updateCustomModelName(value: string) {
role="radiogroup"
>
<RadioCardSimple
v-for="metadata in availableTextGenerationsProvidersMetadata"
v-for="metadata in allChatProvidersMetadata"
:id="metadata.id"
:key="metadata.id"
v-model="activeProvider"
@@ -26,7 +26,7 @@ const providersStore = useProvidersStore()
const speechStore = useSpeechStore()
const {
availableProviders,
availableAudioSpeechProvidersMetadata,
allAudioSpeechProvidersMetadata,
} = storeToRefs(providersStore)
const {
activeSpeechProvider,
@@ -185,7 +185,7 @@ function updateCustomModelName(value: string) {
min-w-0 of-x-scroll scroll-smooth role="radiogroup"
>
<RadioCardSimple
v-for="metadata in availableAudioSpeechProvidersMetadata"
v-for="metadata in allAudioSpeechProvidersMetadata"
:id="metadata.id"
:key="metadata.id"
v-model="activeSpeechProvider"
@@ -6,7 +6,11 @@ import { storeToRefs } from 'pinia'
import { useIconAnimation } from '../../../composables/useIconAnimation'
const providersStore = useProvidersStore()
const { allProvidersMetadata } = storeToRefs(providersStore)
const {
allChatProvidersMetadata,
allAudioSpeechProvidersMetadata,
allAudioTranscriptionProvidersMetadata,
} = storeToRefs(providersStore)
const {
iconAnimationStarted,
@@ -16,23 +20,111 @@ const {
</script>
<template>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="index * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
<div flex flex-col gap-5>
<div bg="primary-500/10 dark:primary-800/25" rounded-lg p-4>
<div mb-2 text-xl font-semibold text="primary-800 dark:primary-100">
First time here?
</div>
<div text="primary-700 dark:primary-300">
AIRI requires at least one <div bg="primary-500/10 dark:primary-800/25" inline-flex items-center gap-1 rounded-lg px-2 py-0.5 translate-y="[0.25lh]">
<div i-solar:chat-square-like-bold-duotone /><strong font-semibold>Chat</strong>
</div> provider to be configured to think, and behave properly. You could think of
it as the brain of the characters living in AIRI system.
</div>
</div>
<div flex="~ row items-center gap-2">
<div i-solar:chat-square-like-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">Text generation model providers. e.g. OpenRouter, OpenAI, Ollama.</span>
</div>
<div flex text-nowrap text-3xl font-semibold>
<div>
Chat
</div>
</div>
</div>
</div>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allChatProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="index * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
<div flex="~ row items-center gap-2" my-5>
<div i-solar:user-speak-rounded-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">Speech (text-to-speech) model providers. e.g. ElevenLabs, Azure Speech.</span>
</div>
<div flex text-nowrap text-3xl font-semibold>
<div>
Speech
</div>
</div>
</div>
</div>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allAudioSpeechProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + index) * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
<div flex="~ row items-center gap-2" my-5>
<div i-solar:microphone-3-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">Transcription (speech-to-text) model providers. e.g. Whisper.cpp, OpenAI, Azure Speech</span>
</div>
<div flex text-nowrap text-3xl font-semibold>
<div>
Transcription
</div>
</div>
</div>
</div>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allAudioTranscriptionProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + allAudioSpeechProvidersMetadata.length + index) * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
</div>
<IconAnimation
v-if="showIconAnimation"
@@ -48,7 +140,7 @@ const {
<div
v-else
v-motion
text="neutral-200/50 dark:neutral-600/20" pointer-events-none
text="neutral-500/50 dark:neutral-600/20" pointer-events-none
fixed top="[calc(100dvh-15rem)]" bottom-0 right--5 z--1
:initial="{ scale: 0.9, opacity: 0, y: 20 }"
:enter="{ scale: 1, opacity: 1, y: 0 }"
@@ -8,7 +8,7 @@ import { RouterLink } from 'vue-router'
const providersStore = useProvidersStore()
const consciousnessStore = useConsciousnessStore()
const { availableProviders, availableTextGenerationsProvidersMetadata } = storeToRefs(providersStore)
const { availableProviders, allChatProvidersMetadata } = storeToRefs(providersStore)
const {
activeProvider,
activeModel,
@@ -57,7 +57,7 @@ function updateCustomModelName(value: string) {
role="radiogroup"
>
<RadioCardSimple
v-for="metadata in availableTextGenerationsProvidersMetadata"
v-for="metadata in allChatProvidersMetadata"
:id="metadata.id"
:key="metadata.id"
v-model="activeProvider"
@@ -26,7 +26,7 @@ const providersStore = useProvidersStore()
const speechStore = useSpeechStore()
const {
availableProviders,
availableAudioSpeechProvidersMetadata,
allAudioSpeechProvidersMetadata,
} = storeToRefs(providersStore)
const {
activeSpeechProvider,
@@ -185,7 +185,7 @@ function updateCustomModelName(value: string) {
min-w-0 of-x-scroll scroll-smooth role="radiogroup"
>
<RadioCardSimple
v-for="metadata in availableAudioSpeechProvidersMetadata"
v-for="metadata in allAudioSpeechProvidersMetadata"
:id="metadata.id"
:key="metadata.id"
v-model="activeSpeechProvider"
@@ -6,7 +6,11 @@ import { storeToRefs } from 'pinia'
import { useIconAnimation } from '../../../composables/useIconAnimation'
const providersStore = useProvidersStore()
const { allProvidersMetadata } = storeToRefs(providersStore)
const {
allChatProvidersMetadata,
allAudioSpeechProvidersMetadata,
allAudioTranscriptionProvidersMetadata,
} = storeToRefs(providersStore)
const {
iconAnimationStarted,
@@ -16,23 +20,111 @@ const {
</script>
<template>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="index * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
<div flex flex-col gap-5>
<div bg="primary-500/10 dark:primary-800/25" rounded-lg p-4>
<div mb-2 text-xl font-semibold text="primary-800 dark:primary-100">
First time here?
</div>
<div text="primary-700 dark:primary-300">
AIRI requires at least one <div bg="primary-500/10 dark:primary-800/25" inline-flex items-center gap-1 rounded-lg px-2 py-0.5 translate-y="[0.25lh]">
<div i-solar:chat-square-like-bold-duotone /><strong font-semibold>Chat</strong>
</div> provider to be configured to think, and behave properly. You could think of
it as the brain of the characters living in AIRI system.
</div>
</div>
<div flex="~ row items-center gap-2">
<div i-solar:chat-square-like-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">Text generation model providers. e.g. OpenRouter, OpenAI, Ollama.</span>
</div>
<div flex text-nowrap text-3xl font-semibold>
<div>
Chat
</div>
</div>
</div>
</div>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allChatProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="index * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
<div flex="~ row items-center gap-2" my-5>
<div i-solar:user-speak-rounded-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">Speech (text-to-speech) model providers. e.g. ElevenLabs, Azure Speech.</span>
</div>
<div flex text-nowrap text-3xl font-semibold>
<div>
Speech
</div>
</div>
</div>
</div>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allAudioSpeechProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + index) * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
<div flex="~ row items-center gap-2" my-5>
<div i-solar:microphone-3-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">Transcription (speech-to-text) model providers. e.g. Whisper.cpp, OpenAI, Azure Speech</span>
</div>
<div flex text-nowrap text-3xl font-semibold>
<div>
Transcription
</div>
</div>
</div>
</div>
<div grid="~ cols-2 gap-4">
<IconStatusItem
v-for="(provider, index) of allAudioTranscriptionProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + allAudioSpeechProvidersMetadata.length + index) * 50"
:title="provider.localizedName"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
</div>
<IconAnimation
v-if="showIconAnimation"
@@ -48,7 +140,7 @@ const {
<div
v-else
v-motion
text="neutral-200/50 dark:neutral-600/20" pointer-events-none
text="neutral-500/50 dark:neutral-600/20" pointer-events-none
fixed top="[calc(100dvh-15rem)]" bottom-0 right--5 z--1
:initial="{ scale: 0.9, opacity: 0, y: 20 }"
:enter="{ scale: 1, opacity: 1, y: 0 }"
+121 -18
View File
@@ -49,6 +49,8 @@ import { models as elevenLabsModels } from './providers/elevenlabs/list-models'
export interface ProviderMetadata {
id: string
category: 'chat' | 'embed' | 'speech' | 'transcription'
tasks: string[]
nameKey: string // i18n key for provider name
name: string // Default name (fallback)
descriptionKey: string // i18n key for description
@@ -137,6 +139,8 @@ export const useProvidersStore = defineStore('providers', () => {
const providerMetadata: Record<string, ProviderMetadata> = {
'openrouter-ai': {
id: 'openrouter-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.openrouter.title',
name: 'OpenRouter',
descriptionKey: 'settings.pages.providers.provider.openrouter.description',
@@ -159,6 +163,43 @@ export const useProvidersStore = defineStore('providers', () => {
},
'ollama': {
id: 'ollama',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.ollama.title',
name: 'Ollama',
descriptionKey: 'settings.pages.providers.provider.ollama.description',
description: 'ollama.com',
icon: 'i-lobe-icons:ollama',
defaultOptions: {
baseUrl: 'http://localhost:11434/v1/',
},
createProvider: config => createOllama((config.baseUrl as string).trim()),
capabilities: {
listModels: async (config) => {
return (await listModels({
...createOllama((config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'ollama',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
})
},
},
validators: {
validateProviderConfig: (config) => {
return !!config.baseUrl
},
},
},
'ollama-embedding': {
id: 'ollama-embedding',
category: 'embed',
tasks: ['text-feature-extraction'],
nameKey: 'settings.pages.providers.provider.ollama.title',
name: 'Ollama',
descriptionKey: 'settings.pages.providers.provider.ollama.description',
@@ -192,6 +233,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'vllm': {
id: 'vllm',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.vllm.title',
name: 'vLLM',
descriptionKey: 'settings.pages.providers.provider.vllm.description',
@@ -254,6 +297,43 @@ export const useProvidersStore = defineStore('providers', () => {
},
'openai': {
id: 'openai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.openai.title',
name: 'OpenAI',
descriptionKey: 'settings.pages.providers.provider.openai.description',
description: 'openai.com',
icon: 'i-lobe-icons:openai',
defaultOptions: {
baseUrl: 'https://api.openai.com/v1/',
},
createProvider: config => createOpenAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
capabilities: {
listModels: async (config) => {
return (await listModels({
...createOpenAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'openai',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
})
},
},
validators: {
validateProviderConfig: (config) => {
return !!config.apiKey && !!config.baseUrl
},
},
},
'openai-audio-speech': {
id: 'openai-audio-speech',
category: 'speech',
tasks: ['text-to-speech'],
nameKey: 'settings.pages.providers.provider.openai.title',
name: 'OpenAI',
descriptionKey: 'settings.pages.providers.provider.openai.description',
@@ -287,6 +367,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'anthropic': {
id: 'anthropic',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.anthropic.title',
name: 'Anthropic',
descriptionKey: 'settings.pages.providers.provider.anthropic.description',
@@ -358,6 +440,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'google-generative-ai': {
id: 'google-generative-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.google-generative-ai.title',
name: 'Google Gemini',
descriptionKey: 'settings.pages.providers.provider.google-generative-ai.description',
@@ -391,6 +475,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'xai': {
id: 'xai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.xai.title',
name: 'xAI',
descriptionKey: 'settings.pages.providers.provider.xai.description',
@@ -420,6 +506,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
},
'deepseek': {
category: 'chat',
tasks: ['text-generation'],
id: 'deepseek',
nameKey: 'settings.pages.providers.provider.deepseek.title',
name: 'DeepSeek',
@@ -451,6 +539,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'elevenlabs': {
id: 'elevenlabs',
category: 'speech',
tasks: ['text-to-speech'],
nameKey: 'settings.pages.providers.provider.elevenlabs.title',
name: 'ElevenLabs',
descriptionKey: 'settings.pages.providers.provider.elevenlabs.description',
@@ -520,6 +610,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'microsoft-speech': {
id: 'microsoft-speech',
category: 'speech',
tasks: ['text-to-speech'],
nameKey: 'settings.pages.providers.provider.microsoft-speech.title',
name: 'Microsoft / Azure Speech',
descriptionKey: 'settings.pages.providers.provider.microsoft-speech.description',
@@ -569,6 +661,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'alibaba-cloud-model-studio': {
id: 'alibaba-cloud-model-studio',
category: 'speech',
tasks: ['text-to-speech'],
nameKey: 'settings.pages.providers.provider.alibaba-cloud-model-studio.title',
name: 'Alibaba Cloud Model Studio',
descriptionKey: 'settings.pages.providers.provider.alibaba-cloud-model-studio.description',
@@ -626,6 +720,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'volcengine': {
id: 'volcengine',
category: 'speech',
tasks: ['text-to-speech'],
nameKey: 'settings.pages.providers.provider.volcengine.title',
name: 'settings.pages.providers.provider.volcengine.title',
descriptionKey: 'settings.pages.providers.provider.volcengine.description',
@@ -675,6 +771,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'together-ai': {
id: 'together-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.together.title',
name: 'Together.ai',
descriptionKey: 'settings.pages.providers.provider.together.description',
@@ -705,6 +803,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'novita-ai': {
id: 'novita-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.novita.title',
name: 'Novita',
descriptionKey: 'settings.pages.providers.provider.novita.description',
@@ -735,6 +835,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'fireworks-ai': {
id: 'fireworks-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.fireworks.title',
name: 'Fireworks.ai',
descriptionKey: 'settings.pages.providers.provider.fireworks.description',
@@ -765,6 +867,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'featherless-ai': {
id: 'featherless-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.featherless.title',
name: 'Featherless.ai',
descriptionKey: 'settings.pages.providers.provider.featherless.description',
@@ -798,6 +902,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'cloudflare-workers-ai': {
id: 'cloudflare-workers-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.cloudflare-workers-ai.title',
name: 'Cloudflare Workers AI',
descriptionKey: 'settings.pages.providers.provider.cloudflare-workers-ai.description',
@@ -817,6 +923,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'perplexity-ai': {
id: 'perplexity-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.perplexity.title',
name: 'Perplexity',
descriptionKey: 'settings.pages.providers.provider.perplexity.description',
@@ -875,6 +983,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'mistral-ai': {
id: 'mistral-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.mistral.title',
name: 'Mistral',
descriptionKey: 'settings.pages.providers.provider.mistral.description',
@@ -905,6 +1015,8 @@ export const useProvidersStore = defineStore('providers', () => {
},
'moonshot-ai': {
id: 'moonshot-ai',
category: 'chat',
tasks: ['text-generation'],
nameKey: 'settings.pages.providers.provider.moonshot.title',
name: 'Moonshot AI',
descriptionKey: 'settings.pages.providers.provider.moonshot.description',
@@ -1095,25 +1207,16 @@ export const useProvidersStore = defineStore('providers', () => {
return availableProviders.value.map(id => getProviderMetadata(id))
})
const availableTextGenerationsProvidersMetadata = computed(() => {
return availableProvidersMetadata.value.filter((metadata) => {
const provider = getProviderInstance(metadata.id)
return 'chat' in provider && typeof provider.chat === 'function'
})
const allChatProvidersMetadata = computed(() => {
return allProvidersMetadata.value.filter(metadata => metadata.category === 'chat')
})
const availableAudioTranscriptionProvidersMetadata = computed(() => {
return availableProvidersMetadata.value.filter((metadata) => {
const provider = getProviderInstance(metadata.id)
return 'transcription' in provider && typeof provider.transcription === 'function'
})
const allAudioSpeechProvidersMetadata = computed(() => {
return allProvidersMetadata.value.filter(metadata => metadata.category === 'speech')
})
const availableAudioSpeechProvidersMetadata = computed(() => {
return availableProvidersMetadata.value.filter((metadata) => {
const provider = getProviderInstance(metadata.id)
return 'speech' in provider && typeof provider.speech === 'function'
})
const allAudioTranscriptionProvidersMetadata = computed(() => {
return allProvidersMetadata.value.filter(metadata => metadata.category === 'transcription')
})
function getProviderConfig(providerId: string) {
@@ -1139,8 +1242,8 @@ export const useProvidersStore = defineStore('providers', () => {
loadModelsForConfiguredProviders,
getProviderInstance,
availableProvidersMetadata,
availableTextGenerationsProvidersMetadata,
availableAudioSpeechProvidersMetadata,
availableAudioTranscriptionProvidersMetadata,
allChatProvidersMetadata,
allAudioSpeechProvidersMetadata,
allAudioTranscriptionProvidersMetadata,
}
})