fix(stage-pages): add vision provider settings (#1987)
This commit is contained in:
@@ -1006,6 +1006,9 @@ pages:
|
||||
chat:
|
||||
title: Chat
|
||||
description: Text generation model providers. e.g. OpenRouter, OpenAI, Ollama.
|
||||
vision:
|
||||
title: Vision
|
||||
description: Vision model providers for image understanding.
|
||||
speech:
|
||||
title: Speech
|
||||
description: Speech (text-to-speech) model providers. e.g. ElevenLabs, Azure Speech.
|
||||
|
||||
@@ -960,6 +960,9 @@ pages:
|
||||
chat:
|
||||
title: 聊天
|
||||
description: Text generation model providers. e.g. OpenRouter, OpenAI, Ollama.
|
||||
vision:
|
||||
title: 视觉
|
||||
description: 用于图像理解的视觉模型服务来源。
|
||||
speech:
|
||||
title: Speech
|
||||
description: Speech (text-to-speech) model providers. e.g. ElevenLabs, Azure Speech.
|
||||
|
||||
@@ -12,7 +12,7 @@ import { RouterLink } from 'vue-router'
|
||||
const providersStore = useProvidersStore()
|
||||
const visionStore = useVisionStore()
|
||||
const visionProcessingStore = useVisionProcessingStore()
|
||||
const { persistedChatProvidersMetadata, configuredProviders } = storeToRefs(providersStore)
|
||||
const { persistedVisionProvidersMetadata, configuredProviders } = storeToRefs(providersStore)
|
||||
const {
|
||||
activeProvider,
|
||||
activeModel,
|
||||
@@ -61,7 +61,11 @@ function handleDeleteProvider(providerId: string) {
|
||||
|
||||
const formattedLastCapture = computed(() => formatRelativeTime(lastCaptureAt.value))
|
||||
const formattedLastContextUpdate = computed(() => formatRelativeTime(lastContextUpdateAt.value))
|
||||
const isOllamaVisionProvider = computed(() => activeProvider.value === 'ollama')
|
||||
const isOllamaVisionProvider = computed(() => activeProvider.value === 'vision-ollama')
|
||||
|
||||
function canDeleteProvider(providerId: string) {
|
||||
return !providerId.startsWith('official-provider') && !providerId.startsWith('vision-official-provider')
|
||||
}
|
||||
|
||||
function formatRelativeTime(timestamp: number | null) {
|
||||
if (!timestamp)
|
||||
@@ -93,12 +97,12 @@ function formatRelativeTime(timestamp: number | null) {
|
||||
</div>
|
||||
<div :class="['max-w-full']">
|
||||
<fieldset
|
||||
v-if="persistedChatProvidersMetadata.length > 0"
|
||||
v-if="persistedVisionProvidersMetadata.length > 0"
|
||||
:class="['flex', 'min-w-0', 'flex-row', 'gap-4', 'of-x-auto', 'scroll-smooth']"
|
||||
role="radiogroup"
|
||||
>
|
||||
<RadioCardSimple
|
||||
v-for="metadata in persistedChatProvidersMetadata"
|
||||
v-for="metadata in persistedVisionProvidersMetadata"
|
||||
:id="metadata.id"
|
||||
:key="metadata.id"
|
||||
v-model="activeProvider"
|
||||
@@ -108,7 +112,7 @@ function formatRelativeTime(timestamp: number | null) {
|
||||
:description="metadata.localizedDescription"
|
||||
@click="trackProviderClick(metadata.id, 'vision')"
|
||||
>
|
||||
<template #topRight>
|
||||
<template v-if="canDeleteProvider(metadata.id)" #topRight>
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
@@ -147,7 +151,7 @@ function formatRelativeTime(timestamp: number | null) {
|
||||
</template>
|
||||
</RadioCardSimple>
|
||||
<RouterLink
|
||||
to="/settings/providers"
|
||||
to="/settings/providers#vision"
|
||||
:class="[
|
||||
'relative',
|
||||
'min-w-50',
|
||||
@@ -178,7 +182,7 @@ function formatRelativeTime(timestamp: number | null) {
|
||||
</fieldset>
|
||||
<div v-else>
|
||||
<RouterLink
|
||||
to="/settings/providers"
|
||||
to="/settings/providers#vision"
|
||||
:class="[
|
||||
'flex',
|
||||
'items-center',
|
||||
|
||||
@@ -50,6 +50,7 @@ const {
|
||||
allChatProvidersMetadata,
|
||||
allAudioSpeechProvidersMetadata,
|
||||
allAudioTranscriptionProvidersMetadata,
|
||||
allVisionProvidersMetadata,
|
||||
} = storeToRefs(providersStore)
|
||||
|
||||
const allArtistryProvidersMetadata = computed<ProviderSourceCard[]>((): ProviderSourceCard[] => {
|
||||
@@ -115,6 +116,13 @@ const providerBlocksConfig: ProviderBlockConfig[] = [
|
||||
description: t('settings.pages.providers.categories.chat.description'),
|
||||
providersRef: allChatProvidersMetadata,
|
||||
},
|
||||
{
|
||||
id: 'vision',
|
||||
icon: 'i-solar:eye-bold-duotone',
|
||||
title: t('settings.pages.providers.categories.vision.title'),
|
||||
description: t('settings.pages.providers.categories.vision.description'),
|
||||
providersRef: allVisionProvidersMetadata,
|
||||
},
|
||||
{
|
||||
id: 'speech',
|
||||
icon: 'i-solar:user-speak-rounded-bold-duotone',
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import type { RemovableRef } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
ProviderAdvancedSettings,
|
||||
ProviderApiKeyInput,
|
||||
ProviderBaseUrlInput,
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
ProviderValidationAlerts,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||
import { getDefinedProvider } from '@proj-airi/stage-ui/libs'
|
||||
import { useVisionStore } from '@proj-airi/stage-ui/stores/modules/vision'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const sourceProviderId = route.params.providerId as string
|
||||
const providerId = `vision-${sourceProviderId}`
|
||||
const providersStore = useProvidersStore()
|
||||
const visionStore = useVisionStore()
|
||||
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
|
||||
const { activeProvider } = storeToRefs(visionStore)
|
||||
|
||||
// Define computed properties for credentials
|
||||
const apiKey = computed({
|
||||
get: () => providers.value[providerId]?.apiKey || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].apiKey = value
|
||||
},
|
||||
})
|
||||
|
||||
const baseUrl = computed({
|
||||
get: () => providers.value[providerId]?.baseUrl || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].baseUrl = value
|
||||
},
|
||||
})
|
||||
|
||||
// Use the composable to get validation logic and state
|
||||
const {
|
||||
t,
|
||||
router,
|
||||
providerMetadata,
|
||||
isValidating,
|
||||
isValid,
|
||||
validationMessage,
|
||||
handleResetSettings,
|
||||
forceValid,
|
||||
hasManualValidators,
|
||||
isManualTesting,
|
||||
manualTestPassed,
|
||||
manualTestMessage,
|
||||
runManualTest,
|
||||
} = useProviderValidation(providerId)
|
||||
|
||||
const apiKeyPlaceholder = computed(() => {
|
||||
const definition = getDefinedProvider(sourceProviderId)
|
||||
if (!definition?.createProviderConfig)
|
||||
return 'sk-...'
|
||||
|
||||
const schema = definition.createProviderConfig({ t }) as any
|
||||
const shape = typeof schema?.shape === 'function' ? schema.shape() : schema?.shape
|
||||
const apiKeySchema = shape?.apiKey
|
||||
if (!apiKeySchema)
|
||||
return 'sk-...'
|
||||
|
||||
const meta = typeof apiKeySchema.meta === 'function' ? apiKeySchema.meta() : undefined
|
||||
return typeof meta?.placeholderLocalized === 'string' ? meta.placeholderLocalized : 'sk-...'
|
||||
})
|
||||
|
||||
function goToModelSelection() {
|
||||
activeProvider.value = providerId
|
||||
router.push('/settings/modules/vision')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon="providerMetadata?.icon"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<ProviderBasicSettings
|
||||
:title="t('settings.pages.providers.common.section.basic.title')"
|
||||
:description="t('settings.pages.providers.common.section.basic.description')"
|
||||
:on-reset="handleResetSettings"
|
||||
>
|
||||
<ProviderApiKeyInput
|
||||
v-model="apiKey"
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:placeholder="apiKeyPlaceholder"
|
||||
/>
|
||||
</ProviderBasicSettings>
|
||||
|
||||
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
|
||||
<ProviderBaseUrlInput
|
||||
v-model="baseUrl"
|
||||
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || 'Base URL of your provider'"
|
||||
/>
|
||||
</ProviderAdvancedSettings>
|
||||
|
||||
<ProviderValidationAlerts
|
||||
:is-valid="isValid"
|
||||
:is-validating="isValidating"
|
||||
:validation-message="validationMessage"
|
||||
:has-manual-validators="hasManualValidators"
|
||||
:is-manual-testing="isManualTesting"
|
||||
:manual-test-passed="manualTestPassed"
|
||||
:manual-test-message="manualTestMessage"
|
||||
:on-run-test="runManualTest"
|
||||
:on-force-valid="forceValid"
|
||||
:on-go-to-model-selection="goToModelSelection"
|
||||
/>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -0,0 +1,130 @@
|
||||
<script setup lang="ts">
|
||||
import type { RemovableRef } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
Alert,
|
||||
ProviderAccountIdInput,
|
||||
ProviderApiKeyInput,
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||
import { useVisionStore } from '@proj-airi/stage-ui/stores/modules/vision'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const providerId = 'vision-amazon-bedrock'
|
||||
const providersStore = useProvidersStore()
|
||||
const visionStore = useVisionStore()
|
||||
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
|
||||
const { activeProvider } = storeToRefs(visionStore)
|
||||
|
||||
const apiKey = computed({
|
||||
get: () => providers.value[providerId]?.apiKey || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].apiKey = value
|
||||
},
|
||||
})
|
||||
|
||||
const region = computed({
|
||||
get: () => providers.value[providerId]?.region || 'us-east-1',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].region = value
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
t,
|
||||
router,
|
||||
providerMetadata,
|
||||
isValidating,
|
||||
isValid,
|
||||
validationMessage,
|
||||
handleResetSettings,
|
||||
forceValid,
|
||||
} = useProviderValidation(providerId)
|
||||
|
||||
function goToModelSelection() {
|
||||
activeProvider.value = providerId
|
||||
router.push('/settings/modules/vision')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<ProviderBasicSettings
|
||||
:title="t('settings.pages.providers.common.section.basic.title')"
|
||||
:description="t('settings.pages.providers.common.section.basic.description')"
|
||||
:on-reset="handleResetSettings"
|
||||
>
|
||||
<ProviderApiKeyInput
|
||||
v-model="apiKey"
|
||||
:label="t('settings.pages.providers.provider.amazon-bedrock.config.api-key.label')"
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:description="t('settings.pages.providers.provider.amazon-bedrock.config.api-key.description')"
|
||||
:placeholder="t('settings.pages.providers.provider.amazon-bedrock.config.api-key.placeholder')"
|
||||
required
|
||||
/>
|
||||
<ProviderAccountIdInput
|
||||
v-model="region"
|
||||
:label="t('settings.pages.providers.provider.amazon-bedrock.config.region.label')"
|
||||
:description="t('settings.pages.providers.provider.amazon-bedrock.config.region.description')"
|
||||
placeholder="us-east-1"
|
||||
/>
|
||||
</ProviderBasicSettings>
|
||||
|
||||
<!-- Validation Status -->
|
||||
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
||||
<template #title>
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
||||
@click="forceValid"
|
||||
>
|
||||
{{ t('settings.pages.providers.common.continueAnyway') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="validationMessage" #content>
|
||||
<div class="whitespace-pre-wrap break-all">
|
||||
{{ validationMessage }}
|
||||
</div>
|
||||
</template>
|
||||
</Alert>
|
||||
<Alert v-if="isValid && isValidating === 0" type="success">
|
||||
<template #title>
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<span>{{ t('settings.dialogs.onboarding.validationSuccess') }}</span>
|
||||
<button
|
||||
type="button"
|
||||
:class="['ml-2 rounded px-2 py-0.5 text-xs font-medium transition-colors', 'bg-green-100 text-green-600 hover:bg-green-200', 'dark:bg-green-800/30 dark:text-green-300 dark:hover:bg-green-700/40']"
|
||||
@click="goToModelSelection"
|
||||
>
|
||||
{{ t('settings.pages.providers.common.goToModelSelection') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Alert>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -0,0 +1,141 @@
|
||||
<script setup lang="ts">
|
||||
import type { RemovableRef } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
ProviderAccountIdInput,
|
||||
ProviderAdvancedSettings,
|
||||
ProviderApiKeyInput,
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
ProviderValidationAlerts,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const providerId = 'vision-azure-ai-foundry'
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
|
||||
|
||||
// Define computed properties for credentials
|
||||
const apiKey = computed({
|
||||
get: () => providers.value[providerId]?.apiKey || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].apiKey = value
|
||||
},
|
||||
})
|
||||
|
||||
const resourceName = computed({
|
||||
get: () => providers.value[providerId]?.resourceName || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].resourceName = value
|
||||
},
|
||||
})
|
||||
|
||||
const apiVersion = computed({
|
||||
get: () => providers.value[providerId]?.apiVersion || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].apiVersion = value
|
||||
},
|
||||
})
|
||||
|
||||
const modelId = computed({
|
||||
get: () => providers.value[providerId]?.modelId || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].modelId = value
|
||||
},
|
||||
})
|
||||
|
||||
// Use the composable to get validation logic and state
|
||||
const {
|
||||
t,
|
||||
router,
|
||||
providerMetadata,
|
||||
isValidating,
|
||||
isValid,
|
||||
validationMessage,
|
||||
handleResetSettings,
|
||||
forceValid,
|
||||
hasManualValidators,
|
||||
isManualTesting,
|
||||
manualTestPassed,
|
||||
manualTestMessage,
|
||||
runManualTest,
|
||||
} = useProviderValidation(providerId)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<ProviderBasicSettings
|
||||
:title="t('settings.pages.providers.common.section.basic.title')"
|
||||
:description="t('settings.pages.providers.common.section.basic.description')"
|
||||
:on-reset="handleResetSettings"
|
||||
>
|
||||
<ProviderApiKeyInput
|
||||
v-model="apiKey"
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
placeholder="..."
|
||||
required
|
||||
/>
|
||||
<ProviderAccountIdInput
|
||||
v-model="resourceName"
|
||||
label="Resouce name"
|
||||
placeholder="..."
|
||||
description="Prefix used in https://<prefix>.services.ai.azure.com"
|
||||
required
|
||||
/>
|
||||
<ProviderAccountIdInput
|
||||
v-model="modelId"
|
||||
label="Model id"
|
||||
placeholder="..."
|
||||
description="Model ID on Azure AI Foundry"
|
||||
required
|
||||
/>
|
||||
</ProviderBasicSettings>
|
||||
|
||||
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
|
||||
<ProviderAccountIdInput
|
||||
v-model="apiVersion"
|
||||
label="API version"
|
||||
placeholder="e.g. 2025-04-01-preview"
|
||||
description="API version for snapshot of the models"
|
||||
/>
|
||||
</ProviderAdvancedSettings>
|
||||
|
||||
<ProviderValidationAlerts
|
||||
:is-valid="isValid"
|
||||
:is-validating="isValidating"
|
||||
:validation-message="validationMessage"
|
||||
:has-manual-validators="hasManualValidators"
|
||||
:is-manual-testing="isManualTesting"
|
||||
:manual-test-passed="manualTestPassed"
|
||||
:manual-test-message="manualTestMessage"
|
||||
:on-run-test="runManualTest"
|
||||
:on-force-valid="forceValid"
|
||||
:on-go-to-model-selection="() => router.push('/settings/modules/vision')"
|
||||
/>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script setup lang="ts">
|
||||
import type { RemovableRef } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
ProviderAccountIdInput,
|
||||
ProviderApiKeyInput,
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
ProviderValidationAlerts,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const providerId = 'vision-cloudflare-workers-ai'
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
|
||||
|
||||
// Define computed properties for credentials
|
||||
const apiKey = computed({
|
||||
get: () => providers.value[providerId]?.apiKey || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].apiKey = value
|
||||
},
|
||||
})
|
||||
|
||||
const accountId = computed({
|
||||
get: () => providers.value[providerId]?.accountId || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].accountId = value
|
||||
},
|
||||
})
|
||||
|
||||
// Use the composable to get validation logic and state
|
||||
const {
|
||||
t,
|
||||
router,
|
||||
providerMetadata,
|
||||
isValidating,
|
||||
isValid,
|
||||
validationMessage,
|
||||
handleResetSettings,
|
||||
forceValid,
|
||||
hasManualValidators,
|
||||
isManualTesting,
|
||||
manualTestPassed,
|
||||
manualTestMessage,
|
||||
runManualTest,
|
||||
} = useProviderValidation(providerId)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon="providerMetadata?.icon"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<ProviderBasicSettings
|
||||
:title="t('settings.pages.providers.common.section.basic.title')"
|
||||
:description="t('settings.pages.providers.common.section.basic.description')"
|
||||
:on-reset="handleResetSettings"
|
||||
>
|
||||
<ProviderApiKeyInput
|
||||
v-model="apiKey"
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.api-key.placeholder')"
|
||||
/>
|
||||
|
||||
<ProviderAccountIdInput
|
||||
v-model="accountId"
|
||||
:label="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.label')"
|
||||
:description="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.description')"
|
||||
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.placeholder')"
|
||||
/>
|
||||
</ProviderBasicSettings>
|
||||
|
||||
<ProviderValidationAlerts
|
||||
:is-valid="isValid"
|
||||
:is-validating="isValidating"
|
||||
:validation-message="validationMessage"
|
||||
:has-manual-validators="hasManualValidators"
|
||||
:is-manual-testing="isManualTesting"
|
||||
:manual-test-passed="manualTestPassed"
|
||||
:manual-test-message="manualTestMessage"
|
||||
:on-run-test="runManualTest"
|
||||
:on-force-valid="forceValid"
|
||||
:on-go-to-model-selection="() => router.push('/settings/modules/vision')"
|
||||
/>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import type { RemovableRef } from '@vueuse/core'
|
||||
|
||||
import {
|
||||
ProviderBaseUrlInput,
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
ProviderValidationAlerts,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const providerId = 'vision-lm-studio'
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
|
||||
|
||||
// Define computed properties for credentials
|
||||
|
||||
const baseUrl = computed({
|
||||
get: () => providers.value[providerId]?.baseUrl || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].baseUrl = value
|
||||
},
|
||||
})
|
||||
|
||||
// Use the composable to get validation logic and state
|
||||
const {
|
||||
t,
|
||||
router,
|
||||
providerMetadata,
|
||||
isValidating,
|
||||
isValid,
|
||||
validationMessage,
|
||||
handleResetSettings,
|
||||
forceValid,
|
||||
hasManualValidators,
|
||||
isManualTesting,
|
||||
manualTestPassed,
|
||||
manualTestMessage,
|
||||
runManualTest,
|
||||
} = useProviderValidation(providerId)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<ProviderBasicSettings
|
||||
:title="t('settings.pages.providers.common.section.basic.title')"
|
||||
:description="t('settings.pages.providers.common.section.basic.description')"
|
||||
:on-reset="handleResetSettings"
|
||||
>
|
||||
<ProviderBaseUrlInput
|
||||
v-model="baseUrl"
|
||||
placeholder="http://localhost:1234/v1/"
|
||||
/>
|
||||
</ProviderBasicSettings>
|
||||
|
||||
<ProviderValidationAlerts
|
||||
:is-valid="isValid"
|
||||
:is-validating="isValidating"
|
||||
:validation-message="validationMessage"
|
||||
:has-manual-validators="hasManualValidators"
|
||||
:is-manual-testing="isManualTesting"
|
||||
:manual-test-passed="manualTestPassed"
|
||||
:manual-test-message="manualTestMessage"
|
||||
:on-run-test="runManualTest"
|
||||
:on-force-valid="forceValid"
|
||||
:on-go-to-model-selection="() => router.push('/settings/modules/vision')"
|
||||
/>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -0,0 +1,98 @@
|
||||
<script setup lang="ts">
|
||||
import { isFluxPurchaseDisabled } from '@proj-airi/stage-shared'
|
||||
import {
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useAuthStore } from '@proj-airi/stage-ui/stores/auth'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { Callout } from '@proj-airi/ui'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
const providersStore = useProvidersStore()
|
||||
const { isAuthenticated, credits, needsLogin } = storeToRefs(authStore)
|
||||
|
||||
const providerId = 'vision-official-provider'
|
||||
const providerMetadata = providersStore.getProviderMetadata(providerId)
|
||||
const fluxPurchaseDisabled = isFluxPurchaseDisabled()
|
||||
|
||||
function handleLogin() {
|
||||
needsLogin.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
v-if="providerMetadata"
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<div v-if="!isAuthenticated" flex flex-col gap-4>
|
||||
<Callout theme="primary">
|
||||
<template #label>
|
||||
{{ t('settings.dialogs.onboarding.official.title') }}
|
||||
</template>
|
||||
<div flex flex-col gap-3>
|
||||
<p>{{ t('settings.dialogs.onboarding.loginPrompt') }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="w-fit rounded-lg bg-primary-500 px-4 py-2 text-white transition-colors active:scale-95 hover:bg-primary-600"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ t('settings.dialogs.onboarding.loginAction') }}
|
||||
</button>
|
||||
</div>
|
||||
</Callout>
|
||||
</div>
|
||||
|
||||
<div v-else flex flex-col gap-6>
|
||||
<div class="rounded-xl bg-neutral-100/50 p-6 backdrop-blur-sm dark:bg-neutral-800/50">
|
||||
<div flex items-center justify-between>
|
||||
<div flex flex-col gap-1>
|
||||
<span text="sm neutral-500 dark:neutral-400 font-medium uppercase tracking-wider">
|
||||
{{ t('settings.dialogs.onboarding.flux') }}
|
||||
</span>
|
||||
<span text="3xl font-bold text-primary-600 dark:text-primary-400">
|
||||
{{ credits }}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="!fluxPurchaseDisabled"
|
||||
type="button"
|
||||
class="rounded-full bg-primary-500/10 px-6 py-2 text-sm text-primary-600 font-semibold transition-all dark:bg-primary-400/10 hover:bg-primary-500 dark:text-primary-400 hover:text-white dark:hover:bg-primary-400 dark:hover:text-neutral-900"
|
||||
@click="router.push('/settings/flux')"
|
||||
>
|
||||
{{ t('settings.dialogs.onboarding.buyFlux') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border border-neutral-200/50 rounded-xl p-4 dark:border-neutral-700/50">
|
||||
<div flex items-center gap-3>
|
||||
<div class="h-2 w-2 animate-pulse rounded-full bg-green-500" />
|
||||
<span text="sm neutral-600 dark:neutral-300">
|
||||
{{ t('settings.pages.providers.provider.common.status.valid') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
<div v-else class="p-8 text-center text-neutral-500">
|
||||
Provider is not available.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -0,0 +1,205 @@
|
||||
<script setup lang="ts">
|
||||
import type { RemovableRef } from '@vueuse/core'
|
||||
|
||||
import { errorMessageFromValue } from '@proj-airi/stage-shared'
|
||||
import {
|
||||
ProviderAdvancedSettings,
|
||||
ProviderBaseUrlInput,
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
ProviderValidationAlerts,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { FieldCombobox, FieldKeyValues } from '@proj-airi/ui'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
const providerId = 'vision-ollama'
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
|
||||
|
||||
// Define computed properties for credentials
|
||||
const baseUrl = computed({
|
||||
get: () => providers.value[providerId]?.baseUrl || 'http://localhost:11434/v1/',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].baseUrl = value
|
||||
},
|
||||
})
|
||||
|
||||
// Use the composable to get validation logic and state
|
||||
const {
|
||||
t,
|
||||
router,
|
||||
providerMetadata,
|
||||
isValidating,
|
||||
isValid,
|
||||
validationMessage,
|
||||
handleResetSettings,
|
||||
forceValid,
|
||||
hasManualValidators,
|
||||
isManualTesting,
|
||||
manualTestPassed,
|
||||
manualTestMessage,
|
||||
runManualTest,
|
||||
} = useProviderValidation(providerId)
|
||||
|
||||
const headers = ref<{ key: string, value: string }[]>(Object.entries(providers.value[providerId]?.headers || {}).map(([key, value]) => ({ key, value } as { key: string, value: string })) || [{ key: '', value: '' }])
|
||||
const thinkingMode = computed({
|
||||
get: () => providers.value[providerId]?.thinkingMode || 'auto',
|
||||
set: (value: string) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
providers.value[providerId].thinkingMode = value
|
||||
},
|
||||
})
|
||||
|
||||
function addKeyValue(headers: { key: string, value: string }[], key: string, value: string) {
|
||||
if (!headers)
|
||||
return
|
||||
|
||||
headers.push({ key, value })
|
||||
}
|
||||
|
||||
function removeKeyValue(index: number, headers: { key: string, value: string }[]) {
|
||||
if (!headers)
|
||||
return
|
||||
|
||||
if (headers.length === 1) {
|
||||
headers[0].key = ''
|
||||
headers[0].value = ''
|
||||
}
|
||||
else {
|
||||
headers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
watch(headers, (headers) => {
|
||||
if (headers.length > 0 && (headers.at(-1)!.key !== '' || headers.at(-1)!.value !== '')) {
|
||||
headers.push({ key: '', value: '' })
|
||||
}
|
||||
if (!providers.value[providerId])
|
||||
return
|
||||
providers.value[providerId].headers = headers.filter(header => header.key !== '').reduce((acc, header) => {
|
||||
acc[header.key] = header.value
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
async function refetch() {
|
||||
try {
|
||||
const validationResult = await providerMetadata.value.validators.validateProviderConfig({
|
||||
baseUrl: baseUrl.value,
|
||||
thinkingMode: thinkingMode.value,
|
||||
headers: headers.value.filter(header => header.key !== '').reduce((acc, header) => {
|
||||
acc[header.key] = header.value
|
||||
return acc
|
||||
}, {} as Record<string, string>),
|
||||
})
|
||||
|
||||
if (!validationResult.valid) {
|
||||
validationMessage.value = t('settings.dialogs.onboarding.validationError', {
|
||||
error: validationResult.reason,
|
||||
})
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
validationMessage.value = t('settings.dialogs.onboarding.validationError', {
|
||||
error: errorMessageFromValue(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch([baseUrl, thinkingMode, headers], refetch, { immediate: true, deep: true })
|
||||
onMounted(() => {
|
||||
providersStore.initializeProvider(providerId)
|
||||
|
||||
// Initialize refs with current values
|
||||
baseUrl.value = providers.value[providerId]?.baseUrl || providerMetadata.value?.defaultOptions?.().baseUrl || ''
|
||||
|
||||
// Initialize headers if not already set
|
||||
if (!providers.value[providerId]?.headers) {
|
||||
providers.value[providerId].headers = {}
|
||||
}
|
||||
if (headers.value.length === 0) {
|
||||
headers.value = [{ key: '', value: '' }]
|
||||
}
|
||||
|
||||
if (!providers.value[providerId].thinkingMode) {
|
||||
providers.value[providerId].thinkingMode = 'auto'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProviderSettingsLayout
|
||||
:provider-name="providerMetadata?.localizedName"
|
||||
:provider-icon-color="providerMetadata?.iconColor"
|
||||
:on-back="() => router.back()"
|
||||
>
|
||||
<ProviderSettingsContainer>
|
||||
<ProviderBasicSettings
|
||||
:title="t('settings.pages.providers.common.section.basic.title')"
|
||||
:description="t('settings.pages.providers.common.section.basic.description')"
|
||||
:on-reset="handleResetSettings"
|
||||
>
|
||||
<ProviderBaseUrlInput
|
||||
v-model="baseUrl"
|
||||
placeholder="http://localhost:11434/v1/"
|
||||
/>
|
||||
</ProviderBasicSettings>
|
||||
|
||||
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
|
||||
<FieldCombobox
|
||||
v-model="thinkingMode"
|
||||
:label="t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.label')"
|
||||
:description="t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.description')"
|
||||
:options="[
|
||||
{ label: t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.options.auto'), value: 'auto' },
|
||||
{ label: t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.options.disable'), value: 'disable' },
|
||||
{ label: t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.options.enable'), value: 'enable' },
|
||||
{ label: t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.options.low'), value: 'low' },
|
||||
{ label: t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.options.medium'), value: 'medium' },
|
||||
{ label: t('settings.pages.providers.catalog.edit.config.common.fields.field.thinking-mode.options.high'), value: 'high' },
|
||||
]"
|
||||
/>
|
||||
|
||||
<FieldKeyValues
|
||||
v-model="headers"
|
||||
:label="t('settings.pages.providers.common.section.advanced.fields.field.headers.label')"
|
||||
:description="t('settings.pages.providers.common.section.advanced.fields.field.headers.description')"
|
||||
:key-placeholder="t('settings.pages.providers.common.section.advanced.fields.field.headers.key.placeholder')"
|
||||
:value-placeholder="t('settings.pages.providers.common.section.advanced.fields.field.headers.value.placeholder')"
|
||||
@add="(key: string, value: string) => addKeyValue(headers, key, value)"
|
||||
@remove="(index: number) => removeKeyValue(index, headers)"
|
||||
/>
|
||||
</ProviderAdvancedSettings>
|
||||
|
||||
<ProviderValidationAlerts
|
||||
:is-valid="isValid"
|
||||
:is-validating="isValidating"
|
||||
:validation-message="validationMessage"
|
||||
:has-manual-validators="hasManualValidators"
|
||||
:is-manual-testing="isManualTesting"
|
||||
:manual-test-passed="manualTestPassed"
|
||||
:manual-test-message="manualTestMessage"
|
||||
:on-run-test="runManualTest"
|
||||
:on-force-valid="forceValid"
|
||||
:on-go-to-model-selection="() => router.push('/settings/modules/vision')"
|
||||
/>
|
||||
</ProviderSettingsContainer>
|
||||
</ProviderSettingsLayout>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -6,14 +6,16 @@ import { useAuthStore } from '../stores/auth'
|
||||
import { useConsciousnessStore } from '../stores/modules/consciousness'
|
||||
import { useHearingStore } from '../stores/modules/hearing'
|
||||
import { useSpeechStore } from '../stores/modules/speech'
|
||||
import { useVisionStore } from '../stores/modules/vision'
|
||||
import { useProvidersStore } from '../stores/providers'
|
||||
|
||||
/**
|
||||
* Provider IDs to auto-activate on sign-in.
|
||||
* Edit this list to enable/disable official providers.
|
||||
*/
|
||||
const AUTH_ACTIVATED_PROVIDERS: Array<{ id: string, module: 'consciousness' | 'speech' | 'hearing' }> = [
|
||||
const AUTH_ACTIVATED_PROVIDERS: Array<{ id: string, module: 'consciousness' | 'speech' | 'hearing' | 'vision' }> = [
|
||||
{ id: 'official-provider', module: 'consciousness' },
|
||||
{ id: 'vision-official-provider', module: 'vision' },
|
||||
{ id: 'official-provider-speech', module: 'speech' },
|
||||
{ id: OFFICIAL_TRANSCRIPTION_PROVIDER_ID, module: 'hearing' },
|
||||
]
|
||||
@@ -36,6 +38,7 @@ export function useAuthProviderSync() {
|
||||
const authStore = useAuthStore()
|
||||
const providersStore = useProvidersStore()
|
||||
const consciousnessStore = useConsciousnessStore()
|
||||
const visionStore = useVisionStore()
|
||||
const speechStore = useSpeechStore()
|
||||
const hearingStore = useHearingStore()
|
||||
|
||||
@@ -67,6 +70,12 @@ export function useAuthProviderSync() {
|
||||
consciousnessStore.activeModel = 'auto'
|
||||
}
|
||||
break
|
||||
case 'vision':
|
||||
if (!visionStore.activeProvider) {
|
||||
visionStore.activeProvider = id
|
||||
visionStore.activeModel = 'auto'
|
||||
}
|
||||
break
|
||||
case 'speech':
|
||||
if (!speechStore.activeSpeechProvider || speechStore.activeSpeechProvider === 'speech-noop') {
|
||||
speechStore.activeSpeechProvider = id
|
||||
@@ -88,7 +97,9 @@ export function useAuthProviderSync() {
|
||||
toActivate.map(({ id, module }) =>
|
||||
module === 'consciousness'
|
||||
? consciousnessStore.loadModelsForProvider(id)
|
||||
: providersStore.fetchModelsForProvider(id),
|
||||
: module === 'vision'
|
||||
? visionStore.loadModelsForProvider(id)
|
||||
: providersStore.fetchModelsForProvider(id),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -168,6 +179,12 @@ export function useAuthProviderSync() {
|
||||
consciousnessStore.activeModel = ''
|
||||
}
|
||||
break
|
||||
case 'vision':
|
||||
if (visionStore.activeProvider === id) {
|
||||
visionStore.activeProvider = ''
|
||||
visionStore.activeModel = ''
|
||||
}
|
||||
break
|
||||
case 'speech':
|
||||
if (speechStore.activeSpeechProvider === id) {
|
||||
speechStore.activeSpeechProvider = ''
|
||||
|
||||
@@ -50,7 +50,7 @@ export function useVisionInference() {
|
||||
const workload = getVisionWorkload(input.workloadId)
|
||||
const prompt = input.promptOverride ?? workload.prompt
|
||||
const { url } = parseDataUrl(input.imageDataUrl)
|
||||
const visionProvider = activeProvider.value === 'ollama'
|
||||
const visionProvider = activeProvider.value === 'vision-ollama'
|
||||
? {
|
||||
...provider,
|
||||
chat(model: string) {
|
||||
|
||||
@@ -81,8 +81,9 @@ function toListVoicesOptions<T>(provider: VoiceProviderWithExtraOptions<T>, opti
|
||||
|
||||
export interface ProviderMetadata {
|
||||
id: string
|
||||
to?: string
|
||||
order?: number
|
||||
category: 'chat' | 'embed' | 'speech' | 'transcription'
|
||||
category: 'chat' | 'embed' | 'speech' | 'transcription' | 'vision'
|
||||
tasks: string[]
|
||||
nameKey: string // i18n key for provider name
|
||||
name: string // Default name (fallback)
|
||||
@@ -2240,6 +2241,18 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
},
|
||||
}
|
||||
|
||||
const VISION_PROVIDER_ID_PREFIX = 'vision-'
|
||||
|
||||
function createVisionProviderMetadata(metadata: ProviderMetadata): ProviderMetadata {
|
||||
return {
|
||||
...metadata,
|
||||
id: `${VISION_PROVIDER_ID_PREFIX}${metadata.id}`,
|
||||
to: `/settings/providers/vision/${metadata.id}`,
|
||||
category: 'vision',
|
||||
tasks: Array.from(new Set([...metadata.tasks, 'vision', 'image-understanding'])),
|
||||
}
|
||||
}
|
||||
|
||||
// Progressive migration bridge:
|
||||
// translate unified provider definitions from libs/providers to legacy store metadata.
|
||||
// Existing metadata remains as fallback for providers not yet migrated.
|
||||
@@ -2260,6 +2273,7 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
})
|
||||
if (intervalMs && intervalMs > 0) {
|
||||
providerValidationIntervalMsById.set(definition.id, intervalMs)
|
||||
providerValidationIntervalMsById.set(`${VISION_PROVIDER_ID_PREFIX}${definition.id}`, intervalMs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2273,6 +2287,12 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
providerMetadata[providerId] = translated
|
||||
}
|
||||
|
||||
for (const metadata of Object.values(providerMetadata)
|
||||
.filter(metadata => metadata.category === 'chat')
|
||||
.map(createVisionProviderMetadata)) {
|
||||
providerMetadata[metadata.id] = metadata
|
||||
}
|
||||
|
||||
for (const metadata of Object.values(providerMetadata)) {
|
||||
if (definedProviderIds.has(metadata.id))
|
||||
continue
|
||||
@@ -2774,6 +2794,10 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
return availableProvidersMetadata.value.filter(metadata => metadata.category === 'transcription')
|
||||
})
|
||||
|
||||
const allVisionProvidersMetadata = computed(() => {
|
||||
return availableProvidersMetadata.value.filter(metadata => metadata.category === 'vision')
|
||||
})
|
||||
|
||||
const configuredChatProvidersMetadata = computed(() => {
|
||||
return allChatProvidersMetadata.value.filter(metadata => configuredProviders.value[metadata.id])
|
||||
})
|
||||
@@ -2786,6 +2810,10 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
return allAudioTranscriptionProvidersMetadata.value.filter(metadata => configuredProviders.value[metadata.id])
|
||||
})
|
||||
|
||||
const configuredVisionProvidersMetadata = computed(() => {
|
||||
return allVisionProvidersMetadata.value.filter(metadata => configuredProviders.value[metadata.id])
|
||||
})
|
||||
|
||||
function isProviderConfigDirty(providerId: string) {
|
||||
const config = providerCredentials.value[providerId]
|
||||
if (!config)
|
||||
@@ -2815,6 +2843,10 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
return persistedProvidersMetadata.value.filter(metadata => metadata.category === 'transcription')
|
||||
})
|
||||
|
||||
const persistedVisionProvidersMetadata = computed(() => {
|
||||
return persistedProvidersMetadata.value.filter(metadata => metadata.category === 'vision')
|
||||
})
|
||||
|
||||
function getProviderConfig(providerId: string) {
|
||||
return providerCredentials.value[providerId]
|
||||
}
|
||||
@@ -2852,12 +2884,15 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
allChatProvidersMetadata,
|
||||
allAudioSpeechProvidersMetadata,
|
||||
allAudioTranscriptionProvidersMetadata,
|
||||
allVisionProvidersMetadata,
|
||||
configuredChatProvidersMetadata,
|
||||
configuredSpeechProvidersMetadata,
|
||||
configuredTranscriptionProvidersMetadata,
|
||||
configuredVisionProvidersMetadata,
|
||||
persistedProvidersMetadata,
|
||||
persistedChatProvidersMetadata,
|
||||
persistedSpeechProvidersMetadata,
|
||||
persistedTranscriptionProvidersMetadata,
|
||||
persistedVisionProvidersMetadata,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -10,6 +10,9 @@ import { CHAT_COMPLETIONS_VALIDATOR_ID, isModelProvider } from '../../libs/provi
|
||||
import { getValidatorsOfProvider, validateProvider } from '../../libs/providers/validators/run'
|
||||
|
||||
function getCategoryFromTasks(tasks: string[]): ProviderMetadata['category'] {
|
||||
if (tasks.some(task => ['vision', 'image-understanding', 'image-to-text', 'multimodal'].includes(task.toLowerCase()))) {
|
||||
return 'vision'
|
||||
}
|
||||
if (tasks.some(task => ['speech-to-text', 'automatic-speech-recognition', 'asr', 'stt'].includes(task.toLowerCase()))) {
|
||||
return 'transcription'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user