feat(stage-web|stage-tamagotchi|stage-tamagotchi-tauri): support gemini #103

This commit is contained in:
Neko Ayaka
2025-04-05 17:22:01 +08:00
parent cfd0e0e624
commit 8fcf79cf00
4 changed files with 426 additions and 85 deletions
@@ -0,0 +1,104 @@
<script setup lang="ts">
import type { RemovableRef } from '@vueuse/core'
import {
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
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 = 'google-generative-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 || 'https://generativelanguage.googleapis.com/v1beta/openai/',
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/',
}
}
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'Google | Gemini'"
: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"
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'Google'"
placeholder="sk-..."
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://generativelanguage.googleapis.com/v1beta/openai/"
/>
</ProviderAdvancedSettings>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
@@ -0,0 +1,104 @@
<script setup lang="ts">
import type { RemovableRef } from '@vueuse/core'
import {
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
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 = 'google-generative-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 || 'https://generativelanguage.googleapis.com/v1beta/openai/',
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/',
}
}
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'Google | Gemini'"
: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"
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'Google'"
placeholder="sk-..."
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://generativelanguage.googleapis.com/v1beta/openai/"
/>
</ProviderAdvancedSettings>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
@@ -0,0 +1,104 @@
<script setup lang="ts">
import type { RemovableRef } from '@vueuse/core'
import {
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderSettingsContainer,
ProviderSettingsLayout,
} from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
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 = 'google-generative-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 || 'https://generativelanguage.googleapis.com/v1beta/openai/',
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/',
}
}
</script>
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName || 'Google | Gemini'"
: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"
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName || 'Google'"
placeholder="sk-..."
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://generativelanguage.googleapis.com/v1beta/openai/"
/>
</ProviderAdvancedSettings>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>
+114 -85
View File
@@ -17,6 +17,7 @@ import {
createAnthropic,
createDeepSeek,
createFireworks,
createGoogleGenerativeAI,
createMistral,
createMoonshot,
createNovita,
@@ -319,6 +320,84 @@ export const useProvidersStore = defineStore('providers', () => {
},
},
},
'google-generative-ai': {
id: 'google-generative-ai',
nameKey: 'settings.pages.providers.provider.google-generative-ai.title',
name: 'Google Gemini',
descriptionKey: 'settings.pages.providers.provider.google-generative-ai.description',
description: 'ai.google.dev',
icon: 'i-lobe-icons:gemini',
defaultOptions: {
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',
},
createProvider: config => createGoogleGenerativeAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
capabilities: {
listModels: async (config) => {
return (await listModels({
...createGoogleGenerativeAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'google-generative-ai',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
})
},
},
},
'xai': {
id: 'xai',
nameKey: 'settings.pages.providers.provider.xai.title',
name: 'xAI',
descriptionKey: 'settings.pages.providers.provider.xai.description',
description: 'x.ai',
icon: 'i-lobe-icons:xai',
createProvider: config => createXAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
capabilities: {
listModels: async (config) => {
return (await listModels({
...createXAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'xai',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
})
},
},
},
'deepseek': {
id: 'deepseek',
nameKey: 'settings.pages.providers.provider.deepseek.title',
name: 'DeepSeek',
descriptionKey: 'settings.pages.providers.provider.deepseek.description',
description: 'deepseek.com',
iconColor: 'i-lobe-icons:deepseek-color',
createProvider: config => createDeepSeek((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
capabilities: {
listModels: async (config) => {
return (await listModels({
...createDeepSeek((config.apiKey as string).trim(), (config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'deepseek',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
})
},
},
},
'elevenlabs': {
id: 'elevenlabs',
nameKey: 'settings.pages.providers.provider.elevenlabs.title',
@@ -383,52 +462,46 @@ export const useProvidersStore = defineStore('providers', () => {
},
},
},
'xai': {
id: 'xai',
nameKey: 'settings.pages.providers.provider.xai.title',
name: 'xAI',
descriptionKey: 'settings.pages.providers.provider.xai.description',
description: 'x.ai',
icon: 'i-lobe-icons:xai',
createProvider: config => createXAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
capabilities: {
listModels: async (config) => {
return (await listModels({
...createXAI((config.apiKey as string).trim(), (config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'xai',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
})
},
'microsoft-speech': {
id: 'microsoft-speech',
nameKey: 'settings.pages.providers.provider.microsoft-speech.title',
name: 'Microsoft / Azure Speech',
descriptionKey: 'settings.pages.providers.provider.microsoft-speech.description',
description: 'speech.microsoft.com',
iconColor: 'i-lobe-icons:microsoft-color',
defaultOptions: {
baseUrl: 'https://unspeech.hyp3r.link/v1/',
},
},
'deepseek': {
id: 'deepseek',
nameKey: 'settings.pages.providers.provider.deepseek.title',
name: 'DeepSeek',
descriptionKey: 'settings.pages.providers.provider.deepseek.description',
description: 'deepseek.com',
iconColor: 'i-lobe-icons:deepseek-color',
createProvider: config => createDeepSeek((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
createProvider: config => createUnMicrosoft((config.apiKey as string).trim(), (config.baseUrl as string).trim()) as SpeechProviderWithExtraOptions<string, UnMicrosoftOptions>,
capabilities: {
listModels: async (config) => {
return (await listModels({
...createDeepSeek((config.apiKey as string).trim(), (config.baseUrl as string).trim()).model(),
})).map((model) => {
return {
id: model.id,
name: model.id,
provider: 'deepseek',
listModels: async () => {
return [
{
id: 'v1',
name: 'v1',
provider: 'microsoft-speech',
description: '',
contextLength: 0,
deprecated: false,
} satisfies ModelInfo
},
]
},
listVoices: async (config) => {
const provider = createUnMicrosoft((config.apiKey as string).trim(), (config.baseUrl as string).trim()) as VoiceProviderWithExtraOptions<UnMicrosoftOptions>
const voices = await listVoices({
...provider.voice({ region: config.region as string }),
})
return voices.map((voice) => {
return {
id: voice.id,
name: voice.name,
provider: 'microsoft-speech',
previewURL: voice.preview_audio_url,
languages: voice.languages,
gender: voice.labels?.gender,
}
})
},
},
@@ -508,50 +581,6 @@ export const useProvidersStore = defineStore('providers', () => {
},
},
},
'microsoft-speech': {
id: 'microsoft-speech',
nameKey: 'settings.pages.providers.provider.microsoft-speech.title',
name: 'Microsoft / Azure Speech',
descriptionKey: 'settings.pages.providers.provider.microsoft-speech.description',
description: 'speech.microsoft.com',
iconColor: 'i-lobe-icons:microsoft-color',
defaultOptions: {
baseUrl: 'https://unspeech.hyp3r.link/v1/',
},
createProvider: config => createUnMicrosoft((config.apiKey as string).trim(), (config.baseUrl as string).trim()) as SpeechProviderWithExtraOptions<string, UnMicrosoftOptions>,
capabilities: {
listModels: async () => {
return [
{
id: 'v1',
name: 'v1',
provider: 'microsoft-speech',
description: '',
contextLength: 0,
deprecated: false,
},
]
},
listVoices: async (config) => {
const provider = createUnMicrosoft((config.apiKey as string).trim(), (config.baseUrl as string).trim()) as VoiceProviderWithExtraOptions<UnMicrosoftOptions>
const voices = await listVoices({
...provider.voice({ region: config.region as string }),
})
return voices.map((voice) => {
return {
id: voice.id,
name: voice.name,
provider: 'microsoft-speech',
previewURL: voice.preview_audio_url,
languages: voice.languages,
gender: voice.labels?.gender,
}
})
},
},
},
'featherless-ai': {
id: 'featherless-ai',
nameKey: 'settings.pages.providers.provider.featherless.title',