fix: model card, make sure voice list working
This commit is contained in:
@@ -51,9 +51,9 @@ async function extractColorsFromModel() {
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.models.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -46,9 +46,9 @@ function updateCustomModelName(value: string) {
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.pages.modules.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.modules.consciousness.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -109,9 +109,9 @@ const modulesList = computed<Module[]>(() => [
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.modules.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { RadioCardDetailManySelect, RadioCardSimple, Range } from '@proj-airi/stage-ui/components'
|
||||
import {
|
||||
FieldCheckbox,
|
||||
FieldRange,
|
||||
RadioCardDetailManySelect,
|
||||
RadioCardSimple,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
|
||||
@@ -21,7 +26,6 @@ const {
|
||||
rate,
|
||||
isLoadingSpeechProviderVoices,
|
||||
speechProviderError,
|
||||
supportsSSML,
|
||||
ssmlEnabled,
|
||||
availableVoices,
|
||||
} = storeToRefs(speechStore)
|
||||
@@ -41,6 +45,10 @@ onMounted(async () => {
|
||||
await speechStore.loadVoicesForProvider(activeSpeechProvider.value)
|
||||
})
|
||||
|
||||
watch(activeSpeechProvider, async () => {
|
||||
await speechStore.loadVoicesForProvider(activeSpeechProvider.value)
|
||||
})
|
||||
|
||||
function updateVoiceName(value: string) {
|
||||
voiceId.value = value
|
||||
updateSSMLExample()
|
||||
@@ -64,22 +72,18 @@ function updateSSMLExample() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-motion
|
||||
flex="~ row" items-center gap-2
|
||||
:initial="{ opacity: 0, x: 10 }"
|
||||
:enter="{ opacity: 1, x: 0 }"
|
||||
:leave="{ opacity: 0, x: -10 }"
|
||||
:duration="250"
|
||||
v-motion flex="~ row" items-center gap-2 :initial="{ opacity: 0, x: 10 }" :enter="{ opacity: 1, x: 0 }"
|
||||
:leave="{ opacity: 0, x: -10 }" :duration="250"
|
||||
>
|
||||
<button @click="router.back()">
|
||||
<div i-solar:alt-arrow-left-line-duotone text-xl />
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.pages.modules.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
Speech
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.modules.speech.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
@@ -88,37 +92,27 @@ function updateSSMLExample() {
|
||||
<div flex="~ col gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
|
||||
Provider
|
||||
{{ t('settings.pages.modules.speech.sections.section.provider-voice-selection.title') }}
|
||||
</h2>
|
||||
<div text="neutral-400 dark:neutral-500">
|
||||
<span>Select the suitable speech provider</span>
|
||||
<span>{{ t('settings.pages.modules.speech.sections.section.provider-voice-selection.description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div max-w-full>
|
||||
<fieldset
|
||||
v-if="availableProviders.length > 0"
|
||||
flex="~ row gap-4"
|
||||
:style="{ 'scrollbar-width': 'none' }"
|
||||
min-w-0 of-x-scroll scroll-smooth
|
||||
role="radiogroup"
|
||||
v-if="availableProviders.length > 0" flex="~ row gap-4" :style="{ 'scrollbar-width': 'none' }"
|
||||
min-w-0 of-x-scroll scroll-smooth role="radiogroup"
|
||||
>
|
||||
<RadioCardSimple
|
||||
v-for="metadata in availableProvidersMetadata"
|
||||
:id="metadata.id"
|
||||
:key="metadata.id"
|
||||
v-model="activeSpeechProvider"
|
||||
name="speech-provider"
|
||||
:value="metadata.id"
|
||||
:title="metadata.localizedName"
|
||||
v-for="metadata in availableProvidersMetadata" :id="metadata.id" :key="metadata.id"
|
||||
v-model="activeSpeechProvider" name="speech-provider" :value="metadata.id" :title="metadata.localizedName"
|
||||
:description="metadata.localizedDescription"
|
||||
/>
|
||||
</fieldset>
|
||||
<div v-else>
|
||||
<RouterLink
|
||||
class="flex items-center gap-3 rounded-lg p-4"
|
||||
border="2 dashed neutral-200 dark:neutral-800"
|
||||
bg="neutral-50 dark:neutral-800"
|
||||
transition="colors duration-200 ease-in-out" to="/settings/providers"
|
||||
class="flex items-center gap-3 rounded-lg p-4" border="2 dashed neutral-200 dark:neutral-800"
|
||||
bg="neutral-50 dark:neutral-800" transition="colors duration-200 ease-in-out" to="/settings/providers"
|
||||
>
|
||||
<div i-solar:warning-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
@@ -166,24 +160,25 @@ function updateSSMLExample() {
|
||||
</div>
|
||||
|
||||
<!-- Voice selection with RadioCardDetailManySelect -->
|
||||
<div v-else-if="availableVoices[activeSpeechProvider] && availableVoices[activeSpeechProvider].length > 0" class="space-y-6">
|
||||
<div
|
||||
v-else-if="availableVoices[activeSpeechProvider] && availableVoices[activeSpeechProvider].length > 0"
|
||||
class="space-y-6"
|
||||
>
|
||||
<RadioCardDetailManySelect
|
||||
v-model="voiceId"
|
||||
v-model:search-query="voiceSearchQuery"
|
||||
v-model="voiceId" v-model:search-query="voiceSearchQuery"
|
||||
:items="availableVoices[activeSpeechProvider]?.map(voice => ({
|
||||
id: voice.name,
|
||||
name: voice.name,
|
||||
description: voice.description || `${voice.labels?.gender || 'Neutral'} voice`,
|
||||
description: voice.description,
|
||||
customizable: true,
|
||||
}))"
|
||||
:searchable="true"
|
||||
search-placeholder="Search voices..."
|
||||
search-no-results-title="No voices found"
|
||||
search-no-results-description="Try a different search term or enter a custom voice name"
|
||||
search-results-text="{count} of {total} voices"
|
||||
custom-input-placeholder="Enter custom voice name"
|
||||
expand-button-text="Show more voices"
|
||||
collapse-button-text="Show fewer voices"
|
||||
}))" :searchable="true"
|
||||
:search-placeholder="t('settings.pages.modules.speech.sections.section.provider-voice-selection.search_voices_placeholder')"
|
||||
:search-no-results-title="t('settings.pages.modules.speech.sections.section.provider-voice-selection.no_voices')"
|
||||
:search-no-results-description="t('settings.pages.modules.speech.sections.section.provider-voice-selection.no_voices_description')"
|
||||
:search-results-text="t('settings.pages.modules.speech.sections.section.provider-voice-selection.search_voices_results', { count: 0, total: 0 })"
|
||||
:custom-input-placeholder="t('settings.pages.modules.speech.sections.section.provider-voice-selection.custom_voice_placeholder')"
|
||||
:expand-button-text="t('settings.pages.modules.speech.sections.section.provider-voice-selection.show_more')"
|
||||
:collapse-button-text="t('settings.pages.modules.speech.sections.section.provider-voice-selection.show_less')"
|
||||
@update:custom-value="updateCustomVoiceName"
|
||||
/>
|
||||
|
||||
@@ -209,66 +204,25 @@ function updateSSMLExample() {
|
||||
</div>
|
||||
|
||||
<!-- Voice parameters -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Pitch Adjustment (%)
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="pitch" :min="-50" :max="50" :step="5"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ pitch }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Speech Rate
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="rate" :min="0.5" :max="2" :step="0.1"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ rate }}x</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSML Support -->
|
||||
<div v-if="supportsSSML" class="border border-2 border-neutral-200 rounded-lg p-4 dark:border-neutral-700">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label class="font-medium">SSML Support</label>
|
||||
<div class="relative mr-2 inline-block w-10 select-none align-middle">
|
||||
<input
|
||||
id="ssml-toggle"
|
||||
v-model="ssmlEnabled"
|
||||
type="checkbox"
|
||||
class="sr-only"
|
||||
>
|
||||
<label
|
||||
for="ssml-toggle"
|
||||
class="block h-6 cursor-pointer overflow-hidden rounded-full bg-neutral-300 dark:bg-neutral-700"
|
||||
>
|
||||
<span
|
||||
:class="{ 'translate-x-4': ssmlEnabled, 'translate-x-0': !ssmlEnabled }"
|
||||
class="block h-6 w-6 transform rounded-full bg-white shadow transition-transform duration-200 ease-in-out"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-3 text-sm text-neutral-500">
|
||||
Enable Speech Synthesis Markup Language for more control over speech output
|
||||
</p>
|
||||
<div v-if="ssmlEnabled" class="mt-3">
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
SSML Example
|
||||
</label>
|
||||
<pre class="overflow-auto rounded bg-neutral-50 p-3 text-xs dark:bg-neutral-800">{{ ssmlExample }}</pre>
|
||||
</div>
|
||||
<div flex="~ col gap-4">
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
label="Pitch Adjustment (%)"
|
||||
description="Tune the pitch of the speech"
|
||||
:min="-100" :max="100" :step="0.1"
|
||||
/>
|
||||
<FieldRange
|
||||
v-model="rate"
|
||||
label="Speech Rate"
|
||||
description="Adjust the speed of the speech"
|
||||
:min="0.5" :max="2" :step="0.01"
|
||||
/>
|
||||
<!-- SSML Support -->
|
||||
<FieldCheckbox
|
||||
v-model="ssmlEnabled"
|
||||
label="Enable SSML"
|
||||
description="Enable Speech Synthesis Markup Language for more control over speech output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -287,14 +241,16 @@ function updateSSMLExample() {
|
||||
</div>
|
||||
|
||||
<!-- Manual voice input when no voices are available -->
|
||||
<div v-if="!availableVoices[activeSpeechProvider] || availableVoices[activeSpeechProvider].length === 0" class="mt-2 space-y-6">
|
||||
<div
|
||||
v-if="!availableVoices[activeSpeechProvider] || availableVoices[activeSpeechProvider].length === 0"
|
||||
class="mt-2 space-y-6"
|
||||
>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Voice Name
|
||||
</label>
|
||||
<input
|
||||
v-model="voiceId"
|
||||
type="text"
|
||||
v-model="voiceId" type="text"
|
||||
class="w-full border border-neutral-300 rounded bg-white px-3 py-2 dark:border-neutral-700 dark:bg-neutral-900"
|
||||
placeholder="Enter voice name (e.g., 'Rachel', 'Josh')"
|
||||
@input="(event) => updateVoiceName((event.target as HTMLInputElement).value)"
|
||||
@@ -325,74 +281,35 @@ function updateSSMLExample() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Voice parameters -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Pitch Adjustment (%)
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="pitch" :min="-50" :max="50" :step="5"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ pitch }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Speech Rate
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="rate" :min="0.5" :max="2" :step="0.1"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ rate }}x</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSML Support -->
|
||||
<div v-if="supportsSSML" class="border border-2 border-neutral-200 rounded-lg p-4 dark:border-neutral-700">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label class="font-medium">SSML Support</label>
|
||||
<div class="relative mr-2 inline-block w-10 select-none align-middle">
|
||||
<input
|
||||
id="ssml-toggle"
|
||||
v-model="ssmlEnabled"
|
||||
type="checkbox"
|
||||
class="sr-only"
|
||||
>
|
||||
<label
|
||||
for="ssml-toggle"
|
||||
class="block h-6 cursor-pointer overflow-hidden rounded-full bg-neutral-300 dark:bg-neutral-700"
|
||||
>
|
||||
<span
|
||||
:class="{ 'translate-x-4': ssmlEnabled, 'translate-x-0': !ssmlEnabled }"
|
||||
class="block h-6 w-6 transform rounded-full bg-white shadow transition-transform duration-200 ease-in-out"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-3 text-sm text-neutral-500">
|
||||
Enable Speech Synthesis Markup Language for more control over speech output
|
||||
</p>
|
||||
<div v-if="ssmlEnabled" class="mt-3">
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
SSML Example
|
||||
</label>
|
||||
<pre class="overflow-auto rounded bg-neutral-50 p-3 text-xs dark:bg-neutral-800">{{ ssmlExample }}</pre>
|
||||
</div>
|
||||
<div flex="~ col gap-4">
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
label="Pitch Adjustment (%)"
|
||||
description="Tune the pitch of the speech"
|
||||
:min="-100" :max="100" :step="0.1"
|
||||
/>
|
||||
<FieldRange
|
||||
v-model="rate"
|
||||
label="Speech Rate"
|
||||
description="Adjust the speed of the speech"
|
||||
:min="0.5" :max="2" :step="0.01"
|
||||
/>
|
||||
<!-- SSML Support -->
|
||||
<FieldCheckbox
|
||||
v-model="ssmlEnabled"
|
||||
label="Enable SSML"
|
||||
description="Enable Speech Synthesis Markup Language for more control over speech output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div text="neutral-200/50 dark:neutral-500/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10 translate-y-10>
|
||||
<div
|
||||
text="neutral-200/50 dark:neutral-500/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10
|
||||
translate-y-10
|
||||
>
|
||||
<div text="40" i-lucide:volume-2 />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -25,9 +25,9 @@ const { allProvidersMetadata } = storeToRefs(providersStore)
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.providers.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -151,9 +151,9 @@ function isColorSelected(hexColor: string): boolean {
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.themes.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -76,10 +76,9 @@ settings:
|
||||
sections:
|
||||
section:
|
||||
provider-model-selection:
|
||||
title: Model
|
||||
description: Select the suitable LLM provider for consciousness
|
||||
collapse: Collapse
|
||||
custom_model_placeholder: Enter custom model name...
|
||||
description: Select the suitable LLM provider for consciousness
|
||||
error: Error loading models
|
||||
expand: Expand
|
||||
loading: Loading available models...
|
||||
@@ -96,6 +95,7 @@ settings:
|
||||
show_less: Show less
|
||||
show_more: Show more
|
||||
subtitle: Select a model from the provider
|
||||
title: Model
|
||||
title: Consciousness
|
||||
description: Thinking, vision, speech synthesis, gaming, etc.
|
||||
gaming-factorio:
|
||||
@@ -118,6 +118,23 @@ settings:
|
||||
title: Discord
|
||||
speech:
|
||||
description: Speech synthesis
|
||||
sections:
|
||||
section:
|
||||
provider-voice-selection:
|
||||
description: Select the suitable speech provider
|
||||
title: Provider
|
||||
custom_model_placeholder: Enter custom model name...
|
||||
custom_voice_placeholder: Enter custom voice ID...
|
||||
no_models: No models available
|
||||
no_models_description: No models were found for this provider
|
||||
no_voices: No voices available
|
||||
no_voices_description: No voices were found for this provider
|
||||
search_models_placeholder: Search models...
|
||||
search_models_results: Found {count} of {total} models
|
||||
search_voices_placeholder: Search voices...
|
||||
search_voices_results: Found {count} of {total} voices
|
||||
show_less: Show less
|
||||
show_more: Show more
|
||||
title: Speech
|
||||
title: Modules
|
||||
vision:
|
||||
|
||||
@@ -107,6 +107,23 @@ settings:
|
||||
speech:
|
||||
description: 语音合成
|
||||
title: 发声
|
||||
sections:
|
||||
section:
|
||||
provider-voice-selection:
|
||||
description: 选择合适的语音提供商
|
||||
title: 提供商
|
||||
custom_model_placeholder: 输入指定模型名字...
|
||||
custom_voice_placeholder: 输入指定声线 ID...
|
||||
no_models: 没有任何可用的模型
|
||||
no_models_description: 该提供商的模型列表没有返回任何的数据
|
||||
no_voices: 没有任何可用的声线
|
||||
no_voices_description: 该提供商的声线列表没有返回任何的数据
|
||||
search_models_placeholder: 搜索模型...
|
||||
search_models_results: 找到 {count} / {total} 个模型
|
||||
search_voices_placeholder: 搜索声线...
|
||||
search_voices_results: 找到 {count} / {total} 个声线
|
||||
show_less: 显示更少
|
||||
show_more: 显示更多
|
||||
title: 机体模块
|
||||
vision:
|
||||
description: 视觉
|
||||
|
||||
@@ -51,9 +51,9 @@ async function extractColorsFromModel() {
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.models.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -46,9 +46,9 @@ function updateCustomModelName(value: string) {
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.pages.modules.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.modules.consciousness.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -109,9 +109,9 @@ const modulesList = computed<Module[]>(() => [
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.modules.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { RadioCardDetailManySelect, RadioCardSimple, Range } from '@proj-airi/stage-ui/components'
|
||||
import {
|
||||
FieldCheckbox,
|
||||
FieldRange,
|
||||
RadioCardDetailManySelect,
|
||||
RadioCardSimple,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
|
||||
@@ -21,7 +26,6 @@ const {
|
||||
rate,
|
||||
isLoadingSpeechProviderVoices,
|
||||
speechProviderError,
|
||||
supportsSSML,
|
||||
ssmlEnabled,
|
||||
availableVoices,
|
||||
} = storeToRefs(speechStore)
|
||||
@@ -41,6 +45,10 @@ onMounted(async () => {
|
||||
await speechStore.loadVoicesForProvider(activeSpeechProvider.value)
|
||||
})
|
||||
|
||||
watch(activeSpeechProvider, async () => {
|
||||
await speechStore.loadVoicesForProvider(activeSpeechProvider.value)
|
||||
})
|
||||
|
||||
function updateVoiceName(value: string) {
|
||||
voiceId.value = value
|
||||
updateSSMLExample()
|
||||
@@ -64,22 +72,18 @@ function updateSSMLExample() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-motion
|
||||
flex="~ row" items-center gap-2
|
||||
:initial="{ opacity: 0, x: 10 }"
|
||||
:enter="{ opacity: 1, x: 0 }"
|
||||
:leave="{ opacity: 0, x: -10 }"
|
||||
:duration="250"
|
||||
v-motion flex="~ row" items-center gap-2 :initial="{ opacity: 0, x: 10 }" :enter="{ opacity: 1, x: 0 }"
|
||||
:leave="{ opacity: 0, x: -10 }" :duration="250"
|
||||
>
|
||||
<button @click="router.back()">
|
||||
<div i-solar:alt-arrow-left-line-duotone text-xl />
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.pages.modules.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
Speech
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.modules.speech.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
@@ -88,37 +92,27 @@ function updateSSMLExample() {
|
||||
<div flex="~ col gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
|
||||
Provider
|
||||
{{ t('settings.pages.modules.speech.sections.section.provider-voice-selection.title') }}
|
||||
</h2>
|
||||
<div text="neutral-400 dark:neutral-500">
|
||||
<span>Select the suitable speech provider</span>
|
||||
<span>{{ t('settings.pages.modules.speech.sections.section.provider-voice-selection.description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div max-w-full>
|
||||
<fieldset
|
||||
v-if="availableProviders.length > 0"
|
||||
flex="~ row gap-4"
|
||||
:style="{ 'scrollbar-width': 'none' }"
|
||||
min-w-0 of-x-scroll scroll-smooth
|
||||
role="radiogroup"
|
||||
v-if="availableProviders.length > 0" flex="~ row gap-4" :style="{ 'scrollbar-width': 'none' }"
|
||||
min-w-0 of-x-scroll scroll-smooth role="radiogroup"
|
||||
>
|
||||
<RadioCardSimple
|
||||
v-for="metadata in availableProvidersMetadata"
|
||||
:id="metadata.id"
|
||||
:key="metadata.id"
|
||||
v-model="activeSpeechProvider"
|
||||
name="speech-provider"
|
||||
:value="metadata.id"
|
||||
:title="metadata.localizedName"
|
||||
v-for="metadata in availableProvidersMetadata" :id="metadata.id" :key="metadata.id"
|
||||
v-model="activeSpeechProvider" name="speech-provider" :value="metadata.id" :title="metadata.localizedName"
|
||||
:description="metadata.localizedDescription"
|
||||
/>
|
||||
</fieldset>
|
||||
<div v-else>
|
||||
<RouterLink
|
||||
class="flex items-center gap-3 rounded-lg p-4"
|
||||
border="2 dashed neutral-200 dark:neutral-800"
|
||||
bg="neutral-50 dark:neutral-800"
|
||||
transition="colors duration-200 ease-in-out" to="/settings/providers"
|
||||
class="flex items-center gap-3 rounded-lg p-4" border="2 dashed neutral-200 dark:neutral-800"
|
||||
bg="neutral-50 dark:neutral-800" transition="colors duration-200 ease-in-out" to="/settings/providers"
|
||||
>
|
||||
<div i-solar:warning-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
@@ -166,24 +160,26 @@ function updateSSMLExample() {
|
||||
</div>
|
||||
|
||||
<!-- Voice selection with RadioCardDetailManySelect -->
|
||||
<div v-else-if="availableVoices[activeSpeechProvider] && availableVoices[activeSpeechProvider].length > 0" class="space-y-6">
|
||||
<div
|
||||
v-else-if="availableVoices[activeSpeechProvider] && availableVoices[activeSpeechProvider].length > 0"
|
||||
class="space-y-6"
|
||||
>
|
||||
<RadioCardDetailManySelect
|
||||
v-model="voiceId"
|
||||
v-model:search-query="voiceSearchQuery"
|
||||
:items="availableVoices[activeSpeechProvider]?.map(voice => ({
|
||||
id: voice.name,
|
||||
name: voice.name,
|
||||
description: voice.description || `${voice.labels?.gender || 'Neutral'} voice`,
|
||||
description: voice.description,
|
||||
customizable: true,
|
||||
}))"
|
||||
:searchable="true"
|
||||
search-placeholder="Search voices..."
|
||||
search-no-results-title="No voices found"
|
||||
search-no-results-description="Try a different search term or enter a custom voice name"
|
||||
search-results-text="{count} of {total} voices"
|
||||
custom-input-placeholder="Enter custom voice name"
|
||||
expand-button-text="Show more voices"
|
||||
collapse-button-text="Show fewer voices"
|
||||
}))" :searchable="true"
|
||||
:search-placeholder="t('settings.pages.modules.speech.sections.section.provider-voice-selection.search_voices_placeholder')"
|
||||
:search-no-results-title="t('settings.pages.modules.speech.sections.section.provider-voice-selection.no_voices')"
|
||||
:search-no-results-description="t('settings.pages.modules.speech.sections.section.provider-voice-selection.no_voices_description')"
|
||||
:search-results-text="t('settings.pages.modules.speech.sections.section.provider-voice-selection.search_voices_results', { count: 0, total: 0 })"
|
||||
:custom-input-placeholder="t('settings.pages.modules.speech.sections.section.provider-voice-selection.custom_voice_placeholder')"
|
||||
:expand-button-text="t('settings.pages.modules.speech.sections.section.provider-voice-selection.show_more')"
|
||||
:collapse-button-text="t('settings.pages.modules.speech.sections.section.provider-voice-selection.show_less')"
|
||||
@update:custom-value="updateCustomVoiceName"
|
||||
/>
|
||||
|
||||
@@ -209,66 +205,25 @@ function updateSSMLExample() {
|
||||
</div>
|
||||
|
||||
<!-- Voice parameters -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Pitch Adjustment (%)
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="pitch" :min="-50" :max="50" :step="5"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ pitch }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Speech Rate
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="rate" :min="0.5" :max="2" :step="0.1"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ rate }}x</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSML Support -->
|
||||
<div v-if="supportsSSML" class="border border-2 border-neutral-200 rounded-lg p-4 dark:border-neutral-700">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label class="font-medium">SSML Support</label>
|
||||
<div class="relative mr-2 inline-block w-10 select-none align-middle">
|
||||
<input
|
||||
id="ssml-toggle"
|
||||
v-model="ssmlEnabled"
|
||||
type="checkbox"
|
||||
class="sr-only"
|
||||
>
|
||||
<label
|
||||
for="ssml-toggle"
|
||||
class="block h-6 cursor-pointer overflow-hidden rounded-full bg-neutral-300 dark:bg-neutral-700"
|
||||
>
|
||||
<span
|
||||
:class="{ 'translate-x-4': ssmlEnabled, 'translate-x-0': !ssmlEnabled }"
|
||||
class="block h-6 w-6 transform rounded-full bg-white shadow transition-transform duration-200 ease-in-out"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-3 text-sm text-neutral-500">
|
||||
Enable Speech Synthesis Markup Language for more control over speech output
|
||||
</p>
|
||||
<div v-if="ssmlEnabled" class="mt-3">
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
SSML Example
|
||||
</label>
|
||||
<pre class="overflow-auto rounded bg-neutral-50 p-3 text-xs dark:bg-neutral-800">{{ ssmlExample }}</pre>
|
||||
</div>
|
||||
<div flex="~ col gap-4">
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
label="Pitch Adjustment (%)"
|
||||
description="Tune the pitch of the speech"
|
||||
:min="-100" :max="100" :step="0.1"
|
||||
/>
|
||||
<FieldRange
|
||||
v-model="rate"
|
||||
label="Speech Rate"
|
||||
description="Adjust the speed of the speech"
|
||||
:min="0.5" :max="2" :step="0.01"
|
||||
/>
|
||||
<!-- SSML Support -->
|
||||
<FieldCheckbox
|
||||
v-model="ssmlEnabled"
|
||||
label="Enable SSML"
|
||||
description="Enable Speech Synthesis Markup Language for more control over speech output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -287,14 +242,16 @@ function updateSSMLExample() {
|
||||
</div>
|
||||
|
||||
<!-- Manual voice input when no voices are available -->
|
||||
<div v-if="!availableVoices[activeSpeechProvider] || availableVoices[activeSpeechProvider].length === 0" class="mt-2 space-y-6">
|
||||
<div
|
||||
v-if="!availableVoices[activeSpeechProvider] || availableVoices[activeSpeechProvider].length === 0"
|
||||
class="mt-2 space-y-6"
|
||||
>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Voice Name
|
||||
</label>
|
||||
<input
|
||||
v-model="voiceId"
|
||||
type="text"
|
||||
v-model="voiceId" type="text"
|
||||
class="w-full border border-neutral-300 rounded bg-white px-3 py-2 dark:border-neutral-700 dark:bg-neutral-900"
|
||||
placeholder="Enter voice name (e.g., 'Rachel', 'Josh')"
|
||||
@input="(event) => updateVoiceName((event.target as HTMLInputElement).value)"
|
||||
@@ -325,74 +282,35 @@ function updateSSMLExample() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Voice parameters -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Pitch Adjustment (%)
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="pitch" :min="-50" :max="50" :step="5"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ pitch }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
Speech Rate
|
||||
</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<Range
|
||||
v-model="rate" :min="0.5" :max="2" :step="0.1"
|
||||
class="w-full"
|
||||
@change="updateSSMLExample"
|
||||
/>
|
||||
<span class="w-12 text-center">{{ rate }}x</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSML Support -->
|
||||
<div v-if="supportsSSML" class="border border-2 border-neutral-200 rounded-lg p-4 dark:border-neutral-700">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label class="font-medium">SSML Support</label>
|
||||
<div class="relative mr-2 inline-block w-10 select-none align-middle">
|
||||
<input
|
||||
id="ssml-toggle"
|
||||
v-model="ssmlEnabled"
|
||||
type="checkbox"
|
||||
class="sr-only"
|
||||
>
|
||||
<label
|
||||
for="ssml-toggle"
|
||||
class="block h-6 cursor-pointer overflow-hidden rounded-full bg-neutral-300 dark:bg-neutral-700"
|
||||
>
|
||||
<span
|
||||
:class="{ 'translate-x-4': ssmlEnabled, 'translate-x-0': !ssmlEnabled }"
|
||||
class="block h-6 w-6 transform rounded-full bg-white shadow transition-transform duration-200 ease-in-out"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-3 text-sm text-neutral-500">
|
||||
Enable Speech Synthesis Markup Language for more control over speech output
|
||||
</p>
|
||||
<div v-if="ssmlEnabled" class="mt-3">
|
||||
<label class="mb-1 block text-sm font-medium">
|
||||
SSML Example
|
||||
</label>
|
||||
<pre class="overflow-auto rounded bg-neutral-50 p-3 text-xs dark:bg-neutral-800">{{ ssmlExample }}</pre>
|
||||
</div>
|
||||
<div flex="~ col gap-4">
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
label="Pitch Adjustment (%)"
|
||||
description="Tune the pitch of the speech"
|
||||
:min="-100" :max="100" :step="0.1"
|
||||
/>
|
||||
<FieldRange
|
||||
v-model="rate"
|
||||
label="Speech Rate"
|
||||
description="Adjust the speed of the speech"
|
||||
:min="0.5" :max="2" :step="0.01"
|
||||
/>
|
||||
<!-- SSML Support -->
|
||||
<FieldCheckbox
|
||||
v-model="ssmlEnabled"
|
||||
label="Enable SSML"
|
||||
description="Enable Speech Synthesis Markup Language for more control over speech output"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div text="neutral-200/50 dark:neutral-500/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10 translate-y-10>
|
||||
<div
|
||||
text="neutral-200/50 dark:neutral-500/20" pointer-events-none fixed bottom-0 right-0 z--1 translate-x-10
|
||||
translate-y-10
|
||||
>
|
||||
<div text="40" i-lucide:volume-2 />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -25,9 +25,9 @@ const { allProvidersMetadata } = storeToRefs(providersStore)
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.providers.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -151,9 +151,9 @@ function isColorSelected(hexColor: string): boolean {
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>{{ t('settings.title') }}</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ t('settings.pages.themes.title') }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import ProviderInput from './FieldInput.vue'
|
||||
import FieldInput from './FieldInput.vue'
|
||||
|
||||
const textValue = ref('')
|
||||
const passwordValue = ref('secret-password')
|
||||
@@ -23,7 +23,7 @@ const emailValue = ref('user@example.com')
|
||||
title="Text Input (Empty)"
|
||||
>
|
||||
<div>
|
||||
<ProviderInput
|
||||
<FieldInput
|
||||
v-model="textValue"
|
||||
label="Text Input"
|
||||
description="A basic text input field"
|
||||
@@ -37,7 +37,7 @@ const emailValue = ref('user@example.com')
|
||||
title="Password Input"
|
||||
>
|
||||
<div>
|
||||
<ProviderInput
|
||||
<FieldInput
|
||||
v-model="passwordValue"
|
||||
label="Password"
|
||||
description="A password input field"
|
||||
@@ -52,7 +52,7 @@ const emailValue = ref('user@example.com')
|
||||
title="Email Input"
|
||||
>
|
||||
<div>
|
||||
<ProviderInput
|
||||
<FieldInput
|
||||
v-model="emailValue"
|
||||
label="Email Address"
|
||||
description="An email input field"
|
||||
@@ -67,7 +67,7 @@ const emailValue = ref('user@example.com')
|
||||
title="Required Input"
|
||||
>
|
||||
<div>
|
||||
<ProviderInput
|
||||
<FieldInput
|
||||
v-model="textValue"
|
||||
label="Required Field"
|
||||
description="This field is required"
|
||||
@@ -82,7 +82,7 @@ const emailValue = ref('user@example.com')
|
||||
title="Optional Input"
|
||||
>
|
||||
<div>
|
||||
<ProviderInput
|
||||
<FieldInput
|
||||
v-model="textValue"
|
||||
label="Optional Field"
|
||||
description="This field is optional"
|
||||
|
||||
@@ -39,11 +39,10 @@ watch([inputKey, inputValue], () => {
|
||||
{{ props.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div flex="~ col gap-2">
|
||||
<div v-auto-animate flex="~ col gap-2">
|
||||
<div
|
||||
v-for="(keyValue, index) in keyValues"
|
||||
:key="index"
|
||||
v-auto-animate
|
||||
w-full flex items-center gap-2
|
||||
>
|
||||
<InputKeyValue
|
||||
|
||||
@@ -24,7 +24,7 @@ const modelValue = defineModel<number>({ required: true })
|
||||
{{ description }}
|
||||
</div>
|
||||
</div>
|
||||
<span font-mono>{{ props.formatValue?.(modelValue) }}</span>
|
||||
<span font-mono>{{ props.formatValue?.(modelValue) || modelValue }}</span>
|
||||
</div>
|
||||
<div flex="~ row" items-center gap-2>
|
||||
<Range
|
||||
|
||||
@@ -10,8 +10,8 @@ const modelValue = defineModel<string>({ required: true })
|
||||
<input
|
||||
v-model="modelValue"
|
||||
:type="props.type || 'text'"
|
||||
border="neutral-100 dark:neutral-800 solid 2 focus:neutral-200 dark:focus:neutral-700"
|
||||
transition="all duration-250 ease-in-out"
|
||||
border="focus:primary-100 dark:focus:primary-400/50 2 solid neutral-200 dark:neutral-800"
|
||||
transition="all duration-200 ease-in-out"
|
||||
text="disabled:neutral-400 dark:disabled:neutral-600"
|
||||
cursor="disabled:not-allowed"
|
||||
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
|
||||
|
||||
@@ -138,7 +138,7 @@ function updateCustomInput(event: Event) {
|
||||
<!-- Expand/collapse button for long descriptions -->
|
||||
<button
|
||||
v-if="showExpandCollapse && description.length > expandCollapseThreshold"
|
||||
class="text-primary-500 dark:text-primary-400 mt-0.5 inline-flex items-center text-xs"
|
||||
class="text-primary-500 dark:text-primary-600 mt-0.5 inline-flex items-center text-xs"
|
||||
@click.prevent="toggleExpansion"
|
||||
>
|
||||
<span>{{ isExpanded ? 'Show less' : 'Show more' }}</span>
|
||||
|
||||
@@ -55,10 +55,6 @@ const filteredItems = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
function clearSearch() {
|
||||
searchQuery.value = ''
|
||||
}
|
||||
|
||||
function updateCustomValue(value: string) {
|
||||
customValue.value = value
|
||||
emit('update:customValue', value)
|
||||
@@ -68,7 +64,7 @@ function updateCustomValue(value: string) {
|
||||
<template>
|
||||
<div class="radio-card-detail-many-select">
|
||||
<!-- Search bar -->
|
||||
<div v-if="searchable" class="relative">
|
||||
<div v-if="searchable" class="relative" inline-flex="~" w-full items-center>
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<div i-solar:magnifer-line-duotone class="text-neutral-500 dark:text-neutral-400" />
|
||||
</div>
|
||||
@@ -76,23 +72,11 @@ function updateCustomValue(value: string) {
|
||||
v-model="searchQuery"
|
||||
type="search"
|
||||
class="w-full rounded-xl p-2.5 pl-10 text-sm outline-none"
|
||||
border="focus:primary-100 dark:focus:primary-900 ~ neutral-200 dark:neutral-800 2"
|
||||
border="focus:primary-100 dark:focus:primary-400/50 2 solid neutral-200 dark:neutral-800"
|
||||
transition="all duration-200 ease-in-out"
|
||||
ring="focus:primary-500 dark:focus:primary-400 0 focus:2 focus:offset-0 focus:opacity-50"
|
||||
bg="white dark:neutral-900"
|
||||
:placeholder="searchPlaceholder"
|
||||
>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
type="button"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3"
|
||||
@click="clearSearch"
|
||||
>
|
||||
<div
|
||||
i-solar:close-circle-line-duotone
|
||||
class="text-neutral-500 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Items list with search results info -->
|
||||
@@ -176,3 +160,9 @@ function updateCustomValue(value: string) {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
input[type='search']::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,9 +21,9 @@ defineProps<{
|
||||
</button>
|
||||
<h1 relative>
|
||||
<div absolute left-0 top-0 translate-y="[-80%]">
|
||||
<span text="neutral-300 dark:neutral-500">Provider</span>
|
||||
<span text="neutral-300 dark:neutral-500" text-nowrap>Provider</span>
|
||||
</div>
|
||||
<div text-3xl font-semibold>
|
||||
<div text-nowrap text-3xl font-semibold>
|
||||
{{ providerName }}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Voice } from '../fix/voice'
|
||||
import type { VoiceInfo } from '../providers'
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
@@ -20,7 +20,7 @@ export const useSpeechStore = defineStore('speech', () => {
|
||||
const ssmlEnabled = useLocalStorage('settings/speech/ssml-enabled', false)
|
||||
const isLoadingSpeechProviderVoices = ref(false)
|
||||
const speechProviderError = ref<string | null>(null)
|
||||
const availableVoices = ref<Record<string, Voice[]>>({})
|
||||
const availableVoices = ref<Record<string, VoiceInfo[]>>({})
|
||||
const selectedLanguage = useLocalStorage('settings/speech/language', 'en-US')
|
||||
|
||||
// Computed properties
|
||||
@@ -77,7 +77,9 @@ export const useSpeechStore = defineStore('speech', () => {
|
||||
speechProviderError.value = null
|
||||
|
||||
try {
|
||||
return await providersStore.getProviderMetadata(provider).capabilities.listVoices?.(providersStore.getProviderConfig(provider)) || []
|
||||
const voices = await providersStore.getProviderMetadata(provider).capabilities.listVoices?.(providersStore.getProviderConfig(provider)) || []
|
||||
availableVoices.value[provider] = voices
|
||||
return voices
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error fetching voices for ${provider}:`, error)
|
||||
|
||||
@@ -623,7 +623,10 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
// Transform and store the models
|
||||
availableModels.value[providerId] = models.map(model => ({
|
||||
id: model.id,
|
||||
name: model.id,
|
||||
name: model.name,
|
||||
description: model.description,
|
||||
contextLength: model.contextLength,
|
||||
deprecated: model.deprecated,
|
||||
provider: providerId,
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user