feat(stage-ui): Refactor providers and add credential validation (#474)

This commit is contained in:
Ilya Bogdanov
2025-09-03 12:53:05 +08:00
committed by GitHub
parent e90cb60889
commit ea80efdb5f
30 changed files with 1625 additions and 1902 deletions
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,92 +10,52 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'anthropic'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'anthropic'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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 || 'https://api.anthropic.com/v1/',
get: () => providers.value[providerId]?.baseUrl || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
if (!providers.value[providerId]) {
providers.value[providerId] = {
baseUrl: 'https://api.anthropic.com/v1/',
}
}
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || 'https://api.anthropic.com/v1/'
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || 'https://api.anthropic.com/v1/',
}
})
function handleResetSettings() {
providers.value[providerId] = {
baseUrl: 'https://api.anthropic.com/v1/',
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'Anthropic | Claude'"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<div bg="orange-50 dark:orange-900/20" rounded-xl p-4 flex="~ col gap-3">
<h2 text-xl font-normal text="orange-700 dark:orange-500">
Before you start
</h2>
<p>
While Anthropic recently did announce that they are having a beta support for
OpenAI SDK compatibility <a underline href="https://docs.anthropic.com/en/api/openai-sdk">(you can read more here)</a>,
but due to the implementation details comes with <a underline href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS</a>
restrictions which not aligned with the OpenAI SDK, it's currently not possible to use this provider in the browser.
</p>
<p>
If you do need to use this provider, you will need a dedicated proxy backend like a Serverless Function running on
<a underline href="https://workers.cloudflare.com/">Cloudflare Workers</a> or some CORS bypassing services to bypass the CORS restrictions.
</p>
</div>
<ProviderSettingsContainer>
<ProviderBasicSettings
:title="t('settings.pages.providers.common.section.basic.title')"
@@ -103,8 +64,8 @@ function handleResetSettings() {
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'Anthropic'"
placeholder="sk-..."
:provider-name="providerMetadata?.localizedName"
placeholder="sk-ant-..."
/>
</ProviderBasicSettings>
@@ -114,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.anthropic.com/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -0,0 +1,139 @@
<script setup lang="ts">
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAccountIdInput,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBasicSettings,
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
const providerId = '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,
} = 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>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,34 +2,28 @@
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/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'cloudflare-workers-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'cloudflare-workers-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -39,34 +33,20 @@ const accountId = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].accountId = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
accountId.value = providers.value[providerId]?.accountId || ''
})
// Watch settings and update the provider configuration
watch([apiKey, accountId], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
accountId: accountId.value,
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
@@ -94,13 +74,30 @@ function handleResetSettings() {
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.placeholder')"
/>
</ProviderBasicSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'deepseek'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'deepseek'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,34 +34,20 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
@@ -85,7 +65,7 @@ function handleResetSettings() {
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="sk-..."
placeholder="ds-..."
/>
</ProviderBasicSettings>
@@ -95,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.deepseek.com/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'featherless-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'featherless-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,40 +34,26 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -85,7 +65,7 @@ function handleResetSettings() {
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="fw-..."
placeholder="sk-..."
/>
</ProviderBasicSettings>
@@ -95,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.featherless.ai/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'fireworks-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'fireworks-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,40 +34,26 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -95,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.fireworks.ai/inference/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'google-generative-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'google-generative-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,44 +34,26 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
if (!providers.value[providerId]) {
providers.value[providerId] = {
baseUrl: 'https://api.anthropic.com/v1/',
}
}
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || 'https://generativelanguage.googleapis.com/v1beta/openai/'
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || 'https://generativelanguage.googleapis.com/v1beta/openai/',
}
})
function handleResetSettings() {
providers.value[providerId] = {
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'Google | Gemini'"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -88,8 +64,8 @@ function handleResetSettings() {
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'Google'"
placeholder="GEMINI_API_KEY"
:provider-name="providerMetadata?.localizedName"
placeholder="AIza..."
/>
</ProviderBasicSettings>
@@ -99,13 +75,30 @@ function handleResetSettings() {
placeholder="https://generativelanguage.googleapis.com/v1beta/openai/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,60 +10,50 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'lm-studio'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'lm-studio'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// 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 || providerMetadata.value?.defaultOptions?.().baseUrl || '',
get: () => providers.value[providerId]?.baseUrl || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || providerMetadata.value?.defaultOptions?.().baseUrl || ''
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -71,26 +62,44 @@ function handleResetSettings() {
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || ''"
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="lm-studio"
:is-required="false"
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="..."
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="http://localhost:1234/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'mistral-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'mistral-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,34 +34,20 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
@@ -85,7 +65,7 @@ function handleResetSettings() {
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="mis-..."
placeholder="sk-..."
/>
</ProviderBasicSettings>
@@ -95,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.mistral.ai/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,20 +10,26 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'modelscope'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
// Use computed properties for settings
const apiKey = computed({
@@ -62,12 +69,6 @@ watch([apiKey, baseUrl], () => {
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
</script>
<template>
@@ -95,6 +96,23 @@ function handleResetSettings() {
placeholder="https://api-inference.modelscope.cn/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'moonshot-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'moonshot-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,40 +34,26 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -85,7 +65,7 @@ function handleResetSettings() {
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="ms-..."
placeholder="sk-..."
/>
</ProviderBasicSettings>
@@ -95,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.moonshot.cn/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,28 +10,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'novita-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'novita-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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
},
})
@@ -40,34 +34,20 @@ const baseUrl = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
@@ -85,7 +65,7 @@ function handleResetSettings() {
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="nvt-..."
placeholder="sk-..."
/>
</ProviderBasicSettings>
@@ -95,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.novita.ai/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -9,34 +9,37 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { FieldKeyValues } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const providerId = 'ollama'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'ollama'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
const validationMessage = ref('')
// Define computed properties for credentials
const baseUrl = computed({
get: () => providers.value[providerId]?.baseUrl || providerMetadata.value?.defaultOptions?.().baseUrl || '',
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,
} = 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: '' }])
function addKeyValue(headers: { key: string, value: string }[], key: string, value: string) {
@@ -113,28 +116,12 @@ onMounted(() => {
headers.value = [{ key: '', value: '' }]
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
</script>
<template>
<Alert v-if="validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</template>
<template v-if="validationMessage" #content>
<div class="whitespace-pre-wrap break-all">
{{ validationMessage }}
</div>
</template>
</Alert>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -145,8 +132,7 @@ function handleResetSettings() {
>
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || ''"
required
placeholder="http://localhost:11434/v1/"
/>
</ProviderBasicSettings>
@@ -161,13 +147,30 @@ function handleResetSettings() {
@remove="(index: number) => removeKeyValue(index, headers)"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -3,6 +3,7 @@ import type { RemovableRef } from '@vueuse/core'
import type { SpeechProvider } from '@xsai-ext/shared-providers'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -11,19 +12,16 @@ import {
ProviderSettingsLayout,
SpeechPlaygroundOpenAICompatible,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { FieldRange } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed, ref } from 'vue'
const speechStore = useSpeechStore()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
const { t } = useI18n()
const router = useRouter()
const defaultVoiceSettings = {
speed: 1.0,
@@ -31,7 +29,6 @@ const defaultVoiceSettings = {
// Get provider metadata
const providerId = 'openai-compatible-audio-speech'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Settings refs
const apiKey = computed({
@@ -92,43 +89,22 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo
)
}
onMounted(() => {
providersStore.initializeProvider(providerId)
const config = providers.value[providerId] || {}
apiKey.value = config.apiKey || ''
baseUrl.value = config.baseUrl || ''
model.value = config.model || 'tts-1'
voice.value = config.voice || 'alloy'
speed.value = config.speed || 1.0
})
watch(speed, (newSpeed) => {
if (providers.value[providerId])
providers.value[providerId].speed = newSpeed
})
function handleResetSettings() {
const defaults = providerMetadata.value?.defaultOptions?.() || {}
providers.value[providerId] = {
apiKey: '',
baseUrl: defaults.baseUrl || '',
model: 'tts-1',
voice: 'alloy',
speed: 1.0,
}
// Force update refs
apiKey.value = ''
baseUrl.value = defaults.baseUrl || ''
model.value = 'tts-1'
voice.value = 'alloy'
speed.value = 1.0
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'OpenAI Compatible'"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -147,7 +123,7 @@ function handleResetSettings() {
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.example.com/v1/"
placeholder="https://api.openai.com/v1/"
/>
<FieldRange
v-model="speed"
@@ -157,6 +133,23 @@ function handleResetSettings() {
:max="2.0" :step="0.01"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
<SpeechPlaygroundOpenAICompatible
@@ -170,8 +163,8 @@ function handleResetSettings() {
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -3,6 +3,7 @@ import type { RemovableRef } from '@vueuse/core'
import type { TranscriptionProvider } from '@xsai-ext/shared-providers'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -11,39 +12,44 @@ import {
ProviderSettingsLayout,
TranscriptionPlayground,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useHearingStore } from '@proj-airi/stage-ui/stores/modules/hearing'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { FieldInput } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const providerId = 'openai-compatible-audio-transcription'
const hearingStore = useHearingStore()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
const { t } = useI18n()
const router = useRouter()
// Get provider metadata
const providerId = 'openai-compatible-audio-transcription'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
const pageTitle = computed(() => providerMetadata.value?.localizedName || t('settings.pages.providers.provider.openai-compatible-audio-transcription.title'))
// Settings refs
// Define computed properties for credentials
const apiKey = computed({
get: () => providers.value[providerId]?.apiKey || '',
set: value => (providers.value[providerId] = { ...providers.value[providerId], apiKey: value }),
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].apiKey = value
},
})
const baseUrl = computed({
get: () => providers.value[providerId]?.baseUrl || '',
set: value => (providers.value[providerId] = { ...providers.value[providerId], baseUrl: value }),
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
const model = computed({
get: () => providers.value[providerId]?.model || 'whisper-1',
set: value => (providers.value[providerId] = { ...providers.value[providerId], model: value }),
get: () => providers.value[providerId]?.model || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].model = value
},
})
// Check if API key is configured
@@ -63,32 +69,22 @@ async function handleGenerateTranscription(file: File) {
)
}
onMounted(() => {
providersStore.initializeProvider(providerId)
const config = providers.value[providerId] || {}
apiKey.value = config.apiKey || ''
baseUrl.value = config.baseUrl || ''
model.value = config.model || 'whisper-1'
})
function handleResetSettings() {
const defaults = providerMetadata.value?.defaultOptions?.() || {}
providers.value[providerId] = {
apiKey: '',
baseUrl: defaults.baseUrl || '',
model: 'whisper-1',
}
// Force update refs
apiKey.value = ''
baseUrl.value = defaults.baseUrl || ''
model.value = 'whisper-1'
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="pageTitle"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -112,23 +108,38 @@ function handleResetSettings() {
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.example.com/v1/"
placeholder="https://api.openai.com/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
<template #playground>
<TranscriptionPlayground
:generate-transcription="handleGenerateTranscription"
:api-key-configured="apiKeyConfigured"
/>
</template>
<TranscriptionPlayground
:generate-transcription="handleGenerateTranscription"
:api-key-configured="apiKeyConfigured"
/>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,70 +10,50 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'openai-compatible'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'openai-compatible'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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 || providerMetadata.value?.defaultOptions?.().baseUrl || '',
get: () => providers.value[providerId]?.baseUrl || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || providerMetadata.value?.defaultOptions?.().baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || providerMetadata.value?.defaultOptions?.().baseUrl || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'OpenAI Compatible'"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -91,16 +72,33 @@ function handleResetSettings() {
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || 'https://api.example.com/v1/'"
placeholder="https://api.openai.com/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,75 +10,50 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'openai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'openai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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 || 'https://api.openai.com/v1/',
get: () => providers.value[providerId]?.baseUrl || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
if (!providers.value[providerId]) {
providers.value[providerId] = {
baseUrl: 'https://api.openai.com/v1/',
}
}
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || 'https://api.openai.com/v1/'
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || 'https://api.openai.com/v1/',
}
})
function handleResetSettings() {
providers.value[providerId] = {
baseUrl: 'https://api.openai.com/v1/',
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'OpenAI'"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -88,7 +64,7 @@ function handleResetSettings() {
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'OpenAI'"
:provider-name="providerMetadata?.localizedName"
placeholder="sk-..."
/>
</ProviderBasicSettings>
@@ -99,13 +75,30 @@ function handleResetSettings() {
placeholder="https://api.openai.com/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,70 +10,50 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
const { t } = useI18n()
const router = useRouter()
const providerId = 'openrouter-ai'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'openrouter-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
// 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 || providerMetadata.value?.defaultOptions?.().baseUrl || '',
get: () => providers.value[providerId]?.baseUrl || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
onMounted(() => {
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || providerMetadata.value?.defaultOptions?.().baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || providerMetadata.value?.defaultOptions?.().baseUrl || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -91,16 +72,33 @@ function handleResetSettings() {
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || ''"
placeholder="https://openrouter.ai/api/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -3,6 +3,7 @@ import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
import type { UnElevenLabsOptions } from 'unspeech'
import {
Alert,
SpeechPlayground,
SpeechProviderSettings,
} from '@proj-airi/stage-ui/components'
@@ -75,21 +76,6 @@ watch(speedRatio, async () => {
</script>
<template>
<div v-if="!hasPlayer2" style="color: red; margin-bottom: 1rem;">
<div>
Please download and run the Player2 App:
<a href="https://player2.game" target="_blank" rel="noopener noreferrer">
https://player2.game
</a>
<div>
After downloading, if you still are having trouble, please reach out to us on Discord:
<a href="https://player2.game/discord" target="_blank" rel="noopener noreferrer">
https://player2.game/discord
</a>.
</div>
</div>
</div>
<SpeechProviderSettings
:provider-id="providerId"
:default-model="defaultModel"
@@ -115,6 +101,28 @@ watch(speedRatio, async () => {
/>
</template>
</SpeechProviderSettings>
<Alert v-if="!hasPlayer2" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</template>
<template #content>
<div class="whitespace-pre-wrap break-all">
<div>
Please download and run the Player2 App:
<a href="https://player2.game" target="_blank" rel="noopener noreferrer">
https://player2.game
</a>
<div>
After downloading, if you still are having trouble, please reach out to us on Discord:
<a href="https://player2.game/discord" target="_blank" rel="noopener noreferrer">
https://player2.game/discord
</a>.
</div>
</div>
</div>
</template>
</Alert>
</template>
<route lang="yaml">
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/shared'
import {
Alert,
ProviderBaseUrlInput,
ProviderSettingsContainer,
ProviderSettingsLayout,
@@ -67,22 +68,6 @@ function handleResetSettings() {
</script>
<template>
<div v-if="!hasPlayer2" style="color: red; margin-bottom: 1rem;">
<div>
Please download and run the Player2 App:
<a href="https://player2.game" target="_blank" rel="noopener noreferrer">
https://player2.game
</a>
<div>
After downloading, if you still are having trouble, please reach out to us on Discord:
<a href="https://player2.game/discord" target="_blank" rel="noopener noreferrer">
https://player2.game/discord
</a>.
</div>
</div>
</div>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
@@ -98,6 +83,28 @@ function handleResetSettings() {
</ProviderBasicSettings>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
<Alert v-if="!hasPlayer2" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</template>
<template #content>
<div class="whitespace-pre-wrap break-all">
<div>
Please download and run the Player2 App:
<a href="https://player2.game" target="_blank" rel="noopener noreferrer">
https://player2.game
</a>
<div>
After downloading, if you still are having trouble, please reach out to us on Discord:
<a href="https://player2.game/discord" target="_blank" rel="noopener noreferrer">
https://player2.game/discord
</a>.
</div>
</div>
</div>
</template>
</Alert>
</template>
<route lang="yaml">
@@ -1,7 +1,6 @@
<script setup lang="ts">
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,65 +8,21 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'together-ai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
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
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
const {
t,
router,
providerMetadata,
apiKey,
baseUrl,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
@@ -85,7 +40,7 @@ function handleResetSettings() {
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="togetherapi-..."
placeholder="sk-..."
/>
</ProviderBasicSettings>
@@ -95,13 +50,30 @@ function handleResetSettings() {
placeholder="https://api.together.xyz/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -0,0 +1,79 @@
<script setup lang="ts">
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
const providerId = 'vllm'
const {
t,
router,
providerMetadata,
apiKey,
baseUrl,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = 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="token-..."
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="http://localhost:8000/v1"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -1,7 +1,6 @@
<script setup lang="ts">
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
@@ -9,71 +8,27 @@ import {
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'xai'
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// Use computed properties for settings
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
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
providersStore.initializeProvider(providerId)
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
baseUrl.value = providers.value[providerId]?.baseUrl || ''
})
// Watch settings and update the provider configuration
watch([apiKey, baseUrl], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
baseUrl: baseUrl.value || '',
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
const {
t,
router,
providerMetadata,
apiKey,
baseUrl,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -95,13 +50,30 @@ function handleResetSettings() {
placeholder="https://api.x.ai/v1/"
/>
</ProviderAdvancedSettings>
<!-- Validation Status -->
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
<template #title>
{{ t('settings.dialogs.onboarding.validationFailed') }}
</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>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
meta:
layout: settings
stageTransition:
name: slide
</route>
@@ -17,6 +17,7 @@ dialogs:
baseUrl: Base URL
baseUrlHelp: API endpoint URL (use default if unsure)
accountId: Account ID
validationSuccess: Configuration validation success
validationFailed: Configuration validation failed
validationError: 'Validation error: {error}'
skipForNow: Skip for now
@@ -410,6 +411,9 @@ pages:
index-tts-vllm:
description: https://index-tts.github.io/
title: Bilibili / IndexTTS
azure-ai-foundry:
description: Azure AI Foundry
title: Azure AI Foundry
mistral:
description: mistral.ai
title: Mistral
@@ -17,6 +17,7 @@ dialogs:
baseUrl: URL Base
baseUrlHelp: URL del endpoint de la API (usa el predeterminado si no estás seguro)
accountId: ID de Cuenta
validationSuccess: La validación de la configuración fue exitosa
validationFailed: La validación de la configuración falló
validationError: 'Error de validación: {error}'
skipForNow: Omitir por ahora
@@ -397,6 +398,9 @@ pages:
index-tts-vllm:
description: https://index-tts.github.io/
title: Bilibili / IndexTTS
azure-ai-foundry:
description: Azure AI Foundry
title: Azure AI Foundry
mistral:
description: mistral.ai
title: Mistral
@@ -15,6 +15,7 @@ dialogs:
baseUrl: 基础 URL
baseUrlHelp: API 端点 URL(如果不确定请使用默认值)
accountId: 账户 ID
validationSuccess: 配置验证成功
validationFailed: 配置验证失败
validationError: 验证错误:{error}
skipForNow: 暂时跳过
@@ -372,6 +373,12 @@ pages:
description: 服务 Endpoint 地区(比如亚太 eastasia 区域)
label: Endpoint 地区
title: Microsoft / Azure 语音服务
index-tts-vllm:
description: https://index-tts.github.io/
title: Bilibili / IndexTTS
azure-ai-foundry:
description: Azure AI Foundry
title: Azure AI Foundry
mistral:
description: mistral.ai
title: Mistral
@@ -41,7 +41,7 @@ const {
// Popular providers for first-time setup
const popularProviders = computed(() => {
const popular = ['openai', 'anthropic', 'google-generative-ai', 'openrouter-ai', 'ollama', 'deepseek', 'player2']
const popular = ['openai', 'anthropic', 'google-generative-ai', 'openrouter-ai', 'ollama', 'deepseek', 'player2', 'openai-compatible']
return allChatProvidersMetadata.value
.filter(provider => popular.includes(provider.id))
.sort((a, b) => popular.indexOf(a.id) - popular.indexOf(b.id))
@@ -410,6 +410,11 @@ onMounted(() => {
</div>
</template>
</Alert>
<Alert v-if="isValid && isValidating === 0" type="success">
<template #title>
{{ t('settings.dialogs.onboarding.validationSuccess') }}
</template>
</Alert>
</div>
<!-- Action Buttons -->
@@ -0,0 +1,137 @@
import type { RemovableRef } from '@vueuse/core'
import { useDebounceFn } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useProvidersStore } from '../stores/providers'
export function useProviderValidation(providerId: string) {
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
const providerMetadata = computed(() => providersStore.getProviderMetadata(providerId))
// --- Internal Computed Properties for Credentials ---
const credentials = computed(() => providers.value[providerId] || {})
const apiKey = computed({
get: () => credentials.value.apiKey || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].apiKey = value
},
})
const baseUrl = computed({
get: () => credentials.value.baseUrl || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].baseUrl = value
},
})
const accountId = computed({
get: () => credentials.value.accountId || '',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].accountId = value
},
})
// --- End of Internal Computed Properties ---
const debounceTime = 500
const isValidating = ref(0)
const isValid = ref(false)
const validationMessage = ref('')
async function validateConfiguration() {
if (!providerMetadata.value)
return
isValidating.value++
validationMessage.value = ''
const startValidationTimestamp = performance.now()
let finalValidationMessage = ''
try {
const config = { ...credentials.value }
if (config.apiKey)
config.apiKey = config.apiKey.trim()
if (config.baseUrl)
config.baseUrl = config.baseUrl.trim()
const validationResult = await providerMetadata.value.validators.validateProviderConfig(config)
isValid.value = validationResult.valid
if (!isValid.value)
finalValidationMessage = validationResult.reason
}
catch (error) {
isValid.value = false
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
error: error instanceof Error ? error.message : String(error),
})
}
finally {
setTimeout(() => {
isValidating.value--
validationMessage.value = finalValidationMessage
}, Math.max(0, debounceTime - (performance.now() - startValidationTimestamp)))
}
}
const debouncedValidateConfiguration = useDebounceFn(() => {
const config = credentials.value
const hasApiKey = 'apiKey' in config && !!config.apiKey?.trim()
const hasBaseUrl = 'baseUrl' in config && !!config.baseUrl?.trim()
const hasAccountId = 'accountId' in config && !!config.accountId?.trim()
if (!hasApiKey && !hasBaseUrl && !hasAccountId) {
isValid.value = false
validationMessage.value = ''
isValidating.value = 0
return
}
validateConfiguration()
}, debounceTime)
onMounted(() => {
providersStore.initializeProvider(providerId)
if (Object.keys(credentials.value).some(key => !!credentials.value[key])) {
validateConfiguration()
}
})
watch(credentials, () => {
debouncedValidateConfiguration()
}, { deep: true })
function handleResetSettings() {
const defaultOptions = providerMetadata.value?.defaultOptions ? providerMetadata.value.defaultOptions() : {}
providers.value[providerId] = { ...defaultOptions }
isValid.value = false
validationMessage.value = ''
isValidating.value = 0
}
return {
t,
router,
providerMetadata,
apiKey,
baseUrl,
accountId,
isValidating,
isValid,
validationMessage,
handleResetSettings,
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,159 @@
import type { ModelInfo, ProviderMetadata } from '../providers'
import { listModels } from '@xsai/model'
import { isUrl } from '../../utils/url'
type ProviderCreator = (apiKey: string, baseUrl: string) => any
export function buildOpenAICompatibleProvider(
options: Partial<ProviderMetadata> & {
id: string
name: string
icon: string
description: string
nameKey: string
descriptionKey: string
category?: 'chat' | 'embed' | 'speech' | 'transcription'
tasks?: string[]
defaultBaseUrl?: string
creator: ProviderCreator
capabilities?: ProviderMetadata['capabilities']
validators?: ProviderMetadata['validators']
validation?: ('health' | 'model_list' | 'chat_completions')[]
additionalHeaders?: Record<string, string>
},
): ProviderMetadata {
const { id, name, icon, description, nameKey, descriptionKey, category, tasks, defaultBaseUrl, creator, capabilities, validators, validation, additionalHeaders, ...rest } = options
const finalCapabilities = capabilities || {
listModels: async (config: Record<string, unknown>) => {
const provider = creator(
(config.apiKey as string || '').trim(),
(config.baseUrl as string || '').trim(),
)
if (provider.model) {
return (await listModels({
...provider.model(),
})).map((model: any) => {
return {
id: model.id,
name: model.name || model.display_name || model.id,
provider: id,
description: model.description || '',
contextLength: model.context_length || 0,
deprecated: false,
} satisfies ModelInfo
})
}
return []
},
}
const finalValidators = validators || {
validateProviderConfig: async (config: Record<string, unknown>) => {
const errors: Error[] = []
if (!config.apiKey) {
errors.push(new Error('API key is required'))
}
if (!config.baseUrl) {
errors.push(new Error('Base URL is required'))
}
if (errors.length > 0) {
return { errors, reason: errors.map(e => e.message).join(', '), valid: false }
}
if (!isUrl(config.baseUrl as string) || new URL(config.baseUrl as string).host.length === 0) {
errors.push(new Error('Base URL is not absolute. Check your input.'))
}
if (!(config.baseUrl as string).endsWith('/')) {
errors.push(new Error('Base URL must end with a trailing slash (/).'))
}
if (errors.length > 0) {
return { errors, reason: errors.map(e => e.message).join(', '), valid: false }
}
const validationChecks = validation || []
let responseModelList = null
let responseChat = null
if (validationChecks.includes('health')) {
try {
responseChat = await fetch(`${config.baseUrl as string}chat/completions`, { headers: { Authorization: `Bearer ${config.apiKey}`, ...additionalHeaders }, method: 'POST' })
responseModelList = await fetch(`${config.baseUrl as string}models`, { headers: { Authorization: `Bearer ${config.apiKey}`, ...additionalHeaders } })
if (!([200, 400, 401].includes(responseChat.status) || [200, 400, 401].includes(responseModelList.status))) {
errors.push(new Error(`Invalid Base URL, ${config.baseUrl} is not supported`))
}
}
catch (e) {
errors.push(new Error(`Invalid Base URL, ${(e as Error).message}`))
}
}
if (errors.length > 0) {
return { errors, reason: errors.map(e => e.message).join(', '), valid: false }
}
if (validationChecks.includes('model_list')) {
try {
let response = responseModelList
if (!response) {
response = await fetch(`${config.baseUrl as string}models`, { headers: { Authorization: `Bearer ${config.apiKey}`, ...additionalHeaders } })
}
if (!response.ok) {
errors.push(new Error(`Invalid API Key`))
}
}
catch (e) {
errors.push(new Error(`Model list check failed: ${(e as Error).message}`))
}
}
if (validationChecks.includes('chat_completions')) {
try {
let response = responseChat
if (!response) {
response = await fetch(`${config.baseUrl as string}chat/completions`, { headers: { Authorization: `Bearer ${config.apiKey}`, ...additionalHeaders }, method: 'POST' })
}
if (!response.ok) {
errors.push(new Error(`Invalid API Key`))
}
}
catch (e) {
errors.push(new Error(`Chat Completions check Failed: ${(e as Error).message}`))
}
}
return {
errors,
reason: errors.map(e => e.message).join(', ') || '',
valid: errors.length === 0,
}
},
}
return {
id,
category: category || 'chat',
tasks: tasks || ['text-generation'],
nameKey,
name,
descriptionKey,
description,
icon,
defaultOptions: () => ({
baseUrl: defaultBaseUrl || '',
}),
createProvider: async config => creator((config.apiKey as string || '').trim(), (config.baseUrl as string || '').trim()),
capabilities: finalCapabilities,
validators: finalValidators,
...rest,
} as ProviderMetadata
}