chore(stage-web,stage-tamagotchi): sync providers, fix ollama provider issue

This commit is contained in:
Neko Ayaka
2025-09-16 00:03:28 +08:00
parent 306a479f03
commit 967a99f63d
28 changed files with 1053 additions and 1153 deletions
@@ -40,7 +40,7 @@ const {
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: '' }])
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) {
if (!headers)
@@ -40,7 +40,7 @@ const {
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: '' }])
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) {
if (!headers)
@@ -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-semibold text="orange-700 dark:orange-500">
{{ t('settings.pages.providers.provider.anthropic.helpinfo.title') }}
</h2>
<p>
{{ t('settings.pages.providers.provider.anthropic.helpinfo.description.part1') }}
<a underline href="https://docs.anthropic.com/en/api/openai-sdk">{{ t('settings.pages.providers.provider.anthropic.helpinfo.description.part2') }}</a>,
{{ t('settings.pages.providers.provider.anthropic.helpinfo.description.part3') }} <a underline href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS</a>
{{ t('settings.pages.providers.provider.anthropic.helpinfo.description.part4') }}
</p>
<p>
{{ t('settings.pages.providers.provider.anthropic.helpinfo.description.part5') }}
<a underline href="https://workers.cloudflare.com/">Cloudflare Workers</a> {{ t('settings.pages.providers.provider.anthropic.helpinfo.description.part6') }}
</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>
@@ -2,6 +2,7 @@
import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAccountIdInput,
ProviderAdvancedSettings,
ProviderApiKeyInput,
@@ -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 = 'azure-ai-foundry'
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
// Get provider metadata
const providerId = 'azure-ai-foundry'
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,7 +34,6 @@ const resourceName = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].resourceName = value
},
})
@@ -50,7 +43,6 @@ const apiVersion = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].apiVersion = value
},
})
@@ -60,44 +52,26 @@ const modelId = computed({
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = {}
providers.value[providerId].modelId = value
},
})
onMounted(() => {
// Initialize provider if it doesn't exist
if (!providers.value[providerId]) {
providers.value[providerId] = {}
}
// Initialize refs with current values
apiKey.value = providers.value[providerId]?.apiKey || ''
resourceName.value = providers.value[providerId]?.resourceName || ''
apiVersion.value = providers.value[providerId]?.apiVersion || ''
modelId.value = providers.value[providerId]?.modelId || ''
})
// Watch settings and update the provider configuration
watch([apiKey, resourceName, apiVersion, modelId], () => {
providers.value[providerId] = {
...providers.value[providerId],
apiKey: apiKey.value,
resourceName: resourceName.value,
apiVersion: apiVersion.value,
modelId: modelId.value,
}
})
function handleResetSettings() {
providers.value[providerId] = {}
}
// 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 || 'Azure OpenAI'"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
@@ -108,7 +82,7 @@ function handleResetSettings() {
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'Azure OpenAI'"
:provider-name="providerMetadata?.localizedName"
placeholder="..."
required
/>
@@ -136,6 +110,23 @@ function handleResetSettings() {
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>
@@ -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>
@@ -7,29 +7,36 @@ import {
} from '@proj-airi/stage-ui/components'
import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { computed, watch } from 'vue'
import { computed, onMounted, watch } from 'vue'
// import { useI18n } from 'vue-i18n'
// const { t } = useI18n()
const providerId = 'index-tts-vllm'
const defaultModel = 'IndexTTS-1.5'
const speechStore = useSpeechStore()
const providersStore = useProvidersStore()
const { providers } = storeToRefs(providersStore)
// const { t } = useI18n()
// const { providers } = storeToRefs(providersStore)
// Check if API key is configured
// const apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey)
const apiKeyConfigured = true // Assuming API key is always configured as its not required
// Get available voices for Index TTS
// Get available voices for Index TTS provider
const availableVoices = computed(() => {
return speechStore.availableVoices[providerId] || []
})
// Generate speech with IndexTTS-specific parameters
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
onMounted(async () => {
await speechStore.loadVoicesForProvider(providerId)
})
watch([apiKeyConfigured], async () => {
await speechStore.loadVoicesForProvider(providerId)
})
async function handleGenerateSpeech(input: string, voiceId: string) {
const provider = await providersStore.getProviderInstance(providerId) as SpeechProvider
if (!provider) {
throw new Error('Failed to initialize speech provider')
@@ -41,44 +48,28 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo
// Get model from configuration or use default
const model = providerConfig.model as string | undefined || defaultModel
// Index TTS doesn't need SSML conversion, but if SSML is provided, use it directly
const options = {
...providerConfig,
}
return await speechStore.speech(
provider,
model,
input,
voiceId,
{
...providerConfig,
},
options,
)
}
watch([providers], async () => {
const providerConfig = providersStore.getProviderConfig(providerId)
const providerMetadata = providersStore.getProviderMetadata(providerId)
if (await providerMetadata.validators.validateProviderConfig(providerConfig)) {
await speechStore.loadVoicesForProvider(providerId)
}
else {
console.error('Failed to validate provider config', providerConfig)
}
}, {
immediate: true,
})
</script>
<template>
<SpeechProviderSettings
:provider-id="providerId"
:default-model="defaultModel"
>
<SpeechProviderSettings :provider-id="providerId" :default-model="defaultModel">
<!-- Replace the default playground with our standalone component -->
<template #playground>
<SpeechPlayground
:available-voices="availableVoices"
:generate-speech="handleGenerateSpeech"
:api-key-configured="apiKeyConfigured"
default-text="Hello! This is a test of the Index TTS voice synthesis."
:available-voices="availableVoices" :generate-speech="handleGenerateSpeech"
:api-key-configured="apiKeyConfigured" :use-ssml="false"
default-text="Hello! This is a test of the Index TTS Speech synthesis?."
/>
</template>
</SpeechProviderSettings>
@@ -2,14 +2,11 @@
import { IconStatusItem } from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import IconAnimation from '../../../components/IconAnimation.vue'
import { useIconAnimation } from '../../../composables/icon-animation'
const { t } = useI18n()
const providersStore = useProvidersStore()
const {
allChatProvidersMetadata,
@@ -25,29 +22,25 @@ const {
</script>
<template>
<div flex flex-col gap-5 pb-12>
<div class="rounded-lg bg-primary-500/10 p-4 dark:bg-primary-800/25">
<div class="mb-2 text-xl text-primary-800 font-semibold dark:text-primary-100">
{{ $t('settings.pages.providers.helpinfo.title') }}
<div mb-6 flex flex-col gap-5>
<div bg="primary-500/10 dark:primary-800/25" rounded-lg p-4>
<div mb-2 text-xl font-normal text="primary-800 dark:primary-100">
First time here?
</div>
<div class="text-primary-700 dark:text-primary-300">
<i18n-t keypath="settings.pages.providers.helpinfo.description">
<template #chat>
<span class="inline-flex translate-y-[0.25lh] items-center gap-1 rounded-lg bg-primary-500/10 px-2 py-0.5 dark:bg-primary-800/25">
<div class="i-solar:chat-square-like-bold-duotone" />
<strong class="font-semibold">Chat</strong>
</span>
</template>
</i18n-t>
<div text="primary-700 dark:primary-300">
AIRI requires at least one <div bg="primary-500/10 dark:primary-800/25" inline-flex items-center gap-1 rounded-lg px-2 py-0.5 translate-y="[0.25lh]">
<div i-solar:chat-square-like-bold-duotone /><strong font-normal>Chat</strong>
</div> provider to be configured to think, and behave properly. You could think of
it as the brain of the characters living in AIRI system.
</div>
</div>
<div flex="~ row items-center gap-2">
<div i-solar:chat-square-like-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.providers.explained.chat') }} </span>
<span text="neutral-300 dark:neutral-500 sm sm:base">Text generation model providers. e.g. OpenRouter, OpenAI, Ollama.</span>
</div>
<div flex text-nowrap text-3xl font-normal>
<div flex text-nowrap text="2xl sm:3xl" font-normal>
<div>
Chat
</div>
@@ -56,10 +49,19 @@ const {
</div>
<div grid="~ cols-1 sm:cols-2 xl:cols-3 gap-4">
<IconStatusItem
v-for="(provider, index) of allChatProvidersMetadata" :key="provider.id" v-motion
:initial="{ opacity: 0, y: 10 }" :enter="{ opacity: 1, y: 0 }" :duration="250 + index * 10" :delay="index * 50"
:title="provider.localizedName || 'Unknown'" :description="provider.localizedDescription" :icon="provider.icon"
:icon-color="provider.iconColor" :icon-image="provider.iconImage" :to="`/settings/providers/${provider.id}`"
v-for="(provider, index) of allChatProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="index * 50"
:title="provider.localizedName || 'Unknown'"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
@@ -67,9 +69,9 @@ const {
<div i-solar:user-speak-rounded-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.providers.explained.Speech') }}</span>
<span text="neutral-300 dark:neutral-500 sm sm:base">Speech (text-to-speech) model providers. e.g. ElevenLabs, Azure Speech.</span>
</div>
<div flex text-nowrap text-3xl font-normal>
<div flex text-nowrap text="2xl sm:3xl" font-normal>
<div>
Speech
</div>
@@ -78,20 +80,29 @@ const {
</div>
<div grid="~ cols-1 sm:cols-2 xl:cols-3 gap-4">
<IconStatusItem
v-for="(provider, index) of allAudioSpeechProvidersMetadata" :key="provider.id" v-motion
:initial="{ opacity: 0, y: 10 }" :enter="{ opacity: 1, y: 0 }" :duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + index) * 50" :title="provider.localizedName || 'Unknown'"
:description="provider.localizedDescription" :icon="provider.icon" :icon-color="provider.iconColor"
:icon-image="provider.iconImage" :to="`/settings/providers/${provider.id}`" :configured="provider.configured"
v-for="(provider, index) of allAudioSpeechProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + index) * 50"
:title="provider.localizedName || 'Unknown'"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
<div flex="~ row items-center gap-2" my-5>
<div i-solar:microphone-3-bold-duotone text="neutral-500 dark:neutral-400 4xl" />
<div>
<div>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.providers.explained.Transcription') }}</span>
<span text="neutral-300 dark:neutral-500 sm sm:base">Transcription (speech-to-text) model providers. e.g. Whisper.cpp, OpenAI, Azure Speech</span>
</div>
<div flex text-nowrap text-3xl font-normal>
<div flex text-nowrap text="2xl sm:3xl" font-normal>
<div>
Transcription
</div>
@@ -100,24 +111,44 @@ const {
</div>
<div grid="~ cols-1 sm:cols-2 xl:cols-3 gap-4">
<IconStatusItem
v-for="(provider, index) of allAudioTranscriptionProvidersMetadata" :key="provider.id" v-motion
:initial="{ opacity: 0, y: 10 }" :enter="{ opacity: 1, y: 0 }" :duration="250 + index * 10"
v-for="(provider, index) of allAudioTranscriptionProvidersMetadata"
:key="provider.id"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + index * 10"
:delay="(allChatProvidersMetadata.length + allAudioSpeechProvidersMetadata.length + index) * 50"
:title="provider.localizedName || 'Unknown'" :description="provider.localizedDescription" :icon="provider.icon"
:icon-color="provider.iconColor" :icon-image="provider.iconImage" :to="`/settings/providers/${provider.id}`"
:title="provider.localizedName || 'Unknown'"
:description="provider.localizedDescription"
:icon="provider.icon"
:icon-color="provider.iconColor"
:icon-image="provider.iconImage"
:to="`/settings/providers/${provider.id}`"
:configured="provider.configured"
/>
</div>
</div>
<IconAnimation
v-if="showIconAnimation" :z-index="-1" :icon="animationIcon" :icon-size="12" :duration="1000"
:started="iconAnimationStarted" :is-reverse="true" position="calc(100dvw - 9.5rem), calc(100dvh - 9.5rem)"
v-if="showIconAnimation"
:z-index="-1"
:icon="animationIcon"
:icon-size="12"
:duration="1000"
:started="iconAnimationStarted"
:is-reverse="true"
position="calc(100dvw - 9.5rem), calc(100dvh - 9.5rem)"
text-color="text-neutral-200/50 dark:text-neutral-600/20"
/>
<div
v-else v-motion text="neutral-500/50 dark:neutral-600/20" pointer-events-none fixed top="[calc(100dvh-15rem)]"
bottom-0 right--5 z--1 :initial="{ scale: 0.9, opacity: 0, y: 20 }" :enter="{ scale: 1, opacity: 1, y: 0 }"
:duration="500" size-60 flex items-center justify-center
v-else
v-motion
text="neutral-500/5 dark:neutral-600/20" pointer-events-none
fixed top="[calc(100dvh-15rem)]" bottom-0 right--5 z--1
:initial="{ scale: 0.9, opacity: 0, y: 20 }"
:enter="{ scale: 1, opacity: 1, y: 0 }"
:duration="500"
size-60
flex items-center justify-center
>
<div text="60" i-solar:box-minimalistic-bold-duotone />
</div>
@@ -4,137 +4,56 @@ import type { RemovableRef } from '@vueuse/core'
import {
Alert,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
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 { 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'
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))
const validationMessage = ref('')
const baseUrl = computed({
get: () => providers.value[providerId]?.baseUrl || providerMetadata.value?.defaultOptions?.().baseUrl || '',
// 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
},
})
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) {
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[headers.length - 1].key !== '' || headers[headers.length - 1].value !== '')) {
headers.push({ key: '', value: '' })
}
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,
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: error instanceof Error ? error.message : String(error),
})
}
}
watch([baseUrl, headers], refetch, { immediate: true })
watch(headers, refetch, { 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: '' }]
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions?.() || {}),
}
}
// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
} = useProviderValidation(providerId)
</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>
@@ -143,31 +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 || ''"
required
<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')">
<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)"
<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>
@@ -35,7 +35,7 @@ const volume = ref(0)
// Additional settings specific to Microsoft Speech (region)
const region = computed({
get: () => providers.value[providerId]?.region as string | undefined,
get: () => providers.value[providerId]?.region as string | undefined || 'eastasia',
set: (value) => {
if (!providers.value[providerId])
providers.value[providerId] = { region: 'eastasia' }
@@ -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,36 +9,38 @@ 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>> }
const loading = ref(0)
// 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
},
})
const headers = ref(Object.entries(providers.value[providerId]?.headers ?? {}).map(([key, value]) => ({ key, value } as { key: string, value: string })) || [{ key: '', 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) {
if (!headers)
@@ -75,11 +77,6 @@ watch(headers, (headers) => {
})
async function refetch() {
loading.value++
// service startup time
const startValidationTimestamp = performance.now()
let finalValidationMessage = ''
try {
const validationResult = await providerMetadata.value.validators.validateProviderConfig({
baseUrl: baseUrl.value,
@@ -90,25 +87,16 @@ async function refetch() {
})
if (!validationResult.valid) {
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
validationMessage.value = t('settings.dialogs.onboarding.validationError', {
error: validationResult.reason,
})
}
else {
finalValidationMessage = ''
}
}
catch (error) {
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
validationMessage.value = t('settings.dialogs.onboarding.validationError', {
error: error instanceof Error ? error.message : String(error),
})
}
finally {
setTimeout(() => {
loading.value--
validationMessage.value = finalValidationMessage
}, 500 - (performance.now() - startValidationTimestamp))
}
}
watch([baseUrl, headers], refetch, { immediate: true })
@@ -128,73 +116,61 @@ onMounted(() => {
headers.value = [{ key: '', value: '' }]
}
})
function handleResetSettings() {
providers.value[providerId] = {
...(providerMetadata.value?.defaultOptions as any),
}
}
</script>
<template>
<div class="flex flex-col gap-4">
<Alert v-if="!!loading" type="loading">
<template #title>
{{ t('settings.pages.providers.provider.common.status.validating') }}
</template>
</Alert>
<Alert v-else-if="!validationMessage" type="success">
<template #title>
{{ t('settings.pages.providers.provider.common.status.valid') }}
</template>
</Alert>
<Alert v-else-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"
: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="providerMetadata?.defaultOptions?.().baseUrl as string || ''"
required
/>
</ProviderBasicSettings>
<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')">
<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>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</div>
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<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>
<!-- 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>
@@ -148,7 +124,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"
@@ -158,6 +134,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
@@ -171,8 +164,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>
@@ -83,7 +64,6 @@ function handleResetSettings() {
>
<ProviderApiKeyInput
v-model="apiKey"
:required="false"
:provider-name="providerMetadata?.localizedName"
placeholder="sk-..."
/>
@@ -92,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,7 +3,7 @@ import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
import type { UnElevenLabsOptions } from 'unspeech'
import {
Callout,
Alert,
SpeechPlayground,
SpeechProviderSettings,
} from '@proj-airi/stage-ui/components'
@@ -18,13 +18,11 @@ const defaultModel = 'v1'
const speedRatio = ref<number>(1.0)
const speechStore = useSpeechStore()
const providersStore = useProvidersStore()
const { t } = useI18n()
// Get available voices for Player2
const availableVoices = computed(() => {
return speechStore.availableVoices[providerId] || []
})
// Generate speech with Player2-specific parameters
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
const provider = await providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
@@ -56,7 +54,6 @@ onMounted(async () => {
else {
console.error('Failed to validate provider config', providerConfig)
}
try {
const baseUrl = (providerConfig.baseUrl as string | undefined) ?? ''
const res = await fetch(`${baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl}/health`, {
@@ -65,7 +62,6 @@ onMounted(async () => {
'player2-game-key': 'airi',
},
})
hasPlayer2.value = res.status === 200
}
catch (e) {
@@ -73,7 +69,6 @@ onMounted(async () => {
hasPlayer2.value = false
}
})
watch(speedRatio, async () => {
const providerConfig = providersStore.getProviderConfig(providerId)
providerConfig.speed = speedRatio.value
@@ -81,26 +76,6 @@ watch(speedRatio, async () => {
</script>
<template>
<div v-if="!hasPlayer2" style="color: red; margin-bottom: 1rem;">
<Callout theme="orange">
<template #label>
Player 2 is not running
</template>
<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>
</Callout>
</div>
<SpeechProviderSettings
:provider-id="providerId"
:default-model="defaultModel"
@@ -126,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,9 +2,8 @@
import type { RemovableRef } from '@vueuse/shared'
import {
Callout,
Alert,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
@@ -45,7 +44,6 @@ onMounted(async () => {
'player2-game-key': 'airi',
},
})
hasPlayer2.value = res.status === 200
}
catch (e) {
@@ -70,27 +68,6 @@ function handleResetSettings() {
</script>
<template>
<div v-if="!hasPlayer2" mb-1>
<Callout theme="orange">
<template #label>
Player 2 is not running
</template>
<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>
</Callout>
</div>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
@@ -106,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>