feat(stage-web|stage-tamagotchi): i18n

This commit is contained in:
Neko Ayaka
2025-03-17 11:13:39 +08:00
parent d03f17983a
commit 8277d7a9e1
50 changed files with 1300 additions and 583 deletions
+204 -14
View File
@@ -43,26 +43,12 @@ prompt:
And the last, do what ever you want!
settings:
elevenlabs-api-key:
label: ElevenLabs API Key
placeholder: Input your ElevenLabs API key
placeholder_mobile: Elevenlabs API Key
language:
chinese: 简体中文
english: English
title: Language
microphone: Microphone
models: Model
openai-api-key:
label: OpenAI API Key
placeholder: Input your API key
placeholder_mobile: OpenAI API Key
openai-base-url:
label: OpenAI API BaseURL
placeholder: Input your API base URL
placeholder_mobile: OpenAI API BaseURL
title: Settings
voices: Voice
quit: Quit
viewer: Viewer
shortcuts:
@@ -73,6 +59,210 @@ settings:
debug: Toggle developer tools
press_keys: Press keys...
other: Other
pages:
models:
description: Live2D, VRM, etc.
title: Models
modules:
consciousness:
description: Personality, desired model, etc.
sections:
section:
provider-model-selection:
title: Model
description: Select the suitable LLM provider for consciousness
collapse: Collapse
custom_model_placeholder: Enter custom model name...
error: Error loading models
expand: Expand
loading: Loading available models...
manual_model_name: Model Name
manual_model_placeholder: Enter the model name to use with this provider
no_models: No models available
no_models_description: No models were found for this provider
no_search_results: No matching models
no_search_results_description: No models match '{query}'. Try a different search term.
not_supported: Model listing not supported
not_supported_description: This provider doesn't support model listing functionality
search_placeholder: Search models...
search_results: Found {count} of {total} models
show_less: Show less
show_more: Show more
subtitle: Select a model from the provider
title: Consciousness
description: Thinking, vision, speech synthesis, gaming, etc.
gaming-factorio:
description: Playing Factorio!
title: Factorio
gaming-minecraft:
description: Playing Minecraft!
title: Minecraft
hearing:
description: Configure how speech recognition works
title: Hearing
memory-long-term:
description: Long-term memory specific settings and management
title: Long-Term Memory
memory-short-term:
description: Short-term memory specific settings and management
title: Short-Term Memory
messaging-discord:
description: Chat & voice chat over Discord
title: Discord
speech:
description: Speech synthesis
title: Speech
title: Modules
vision:
description: Vision
title: Vision
providers:
common:
section:
advanced:
fields:
field:
headers:
description: Add custom HTTP headers
key:
placeholder: Key
label: HTTP Headers
value:
placeholder: Value
title: Advanced
basic:
description: Essential settings
title: Basic
voice:
title: Voice Settings
description: LLMs, speech providers, etc.
provider:
cloudflare-workers-ai:
description: cloudflare.com
fields:
field:
account-id:
description: Cloudflare Account ID
label: Account ID
placeholder: Your Cloudflare Account ID
api-key:
placeholder: Input Cloudflare Account ID
title: Cloudflare Workers AI
deepseek:
description: deepseek.com
title: DeepSeek
elevenlabs:
description: elevenlabs.io
fields:
field:
simularity-boost:
description: Voice similarity adherence
label: Similarity Boost
speaker-boost:
description: Enhance speaker similarity
label: Speaker Boost
speed:
description: Speech generation speed
label: Speed
stability:
description: Voice stability and randomness
label: Stability
style:
description: Voice style exaggeration
label: Style
playground:
buttons:
button:
test-voice:
generating: Generating...
label: Test Voice
fields:
field:
input:
placeholder: Enter text to test the voice...
language:
description: Select voice language
label: Language
voice:
description: Select preferred voice
label: Voice
title: Voice Playground
validation:
error-missing-api-key: Please enter an API key to test the voice.
title: ElevenLabs
fireworks:
description: fireworks.ai
title: Fireworks.ai
mistral:
description: mistral.ai
title: Mistral
moonshot:
description: moonshot.ai
title: Moonshot AI
novita:
description: novita.ai
title: Novita
ollama:
description: ollama.ai
title: Ollama
openai:
description: openai.com
title: OpenAI
openrouter:
description: openrouter.ai
title: OpenRouter
together:
description: together.ai
title: Together.ai
vllm:
description: vllm.ai
title: vLLM
xai:
description: x.ai
title: xAI
title: Providers
themes:
description: Customize your stage!
sections:
section:
custom-color:
fields:
field:
primary-color:
label: Primary color
reset:
label: Reset to Default
rgb-on:
title: I Want It Dynamic!
title: Custom Colors
theme-presets:
preset:
chinese:
description: >-
Traditional Chinese colors, derived from ancient textiles,
porcelain and paintings
title: Chinese Traditional Colors
japanese:
description: Traditional Japanese color palette
title: Japanese Colors
monet:
description: Impressionist palette inspired by Claude Monet\'s works
title: Monet Colors
morandi:
description: Soft, muted tones inspired by Giorgio Morandi\'s paintings
title: Morandi Colors
nordic:
description: Scandinavian minimalist color scheme
title: Nordic Colors
title: Theme Presets
title: Themes
sections:
section:
general:
title: General
theme: Theme
title: Settings
voices: Voice
stage:
chat:
message:
@@ -31,25 +31,11 @@ prompt:
最后,做任何你想做的事!
settings:
elevenlabs-api-key:
label: ElevenLabs API 密钥
placeholder: 输入您的 ElevenLabs API 密钥
placeholder_mobile: ElevenLabs API Key
language:
chinese: 简体中文
english: English
title: 语言
models: 模型
openai-api-key:
label: OpenAI API 密钥
placeholder: 输入您的 API 密钥
placeholder_mobile: OpenAI API Key
openai-base-url:
label: OpenAI API BaseURL
placeholder: 输入您的 API BaseURL
placeholder_mobile: OpenAI BaseURL
title: 设置
voices: 声线
quit: 退出
viewer: 查看器
shortcuts:
@@ -60,6 +46,203 @@ settings:
debug: 切换开发者模式
press_keys: 按下快捷键...
other: 其他
pages:
models:
description: 切换角色的 Live2DVRM 模型
title: 角色模型
modules:
consciousness:
description: 人格设定、使用的模型等配置
title: 意识
sections:
section:
provider-model-selection:
title: 模型
description: 为意识选择合适的 LLM 提供商
collapse: 折叠
custom_model_placeholder: 输入自订模型名字
error: 获取出错啦
expand: 展开
loading: 获取模型中...
manual_model_name: 模型名字
manual_model_placeholder: 输入与该提供商一起使用的模型名字
no_models: 没有任何可用的模型
no_models_description: 该提供商的模型列表没有返回任何的数据
no_search_results: 没有匹配的模型
no_search_results_description: 没有模型匹配 '{query}' 关键字,请尝试一下别的关键词呢
not_supported: 无法列出模型
not_supported_description: 此提供商不支持列出模型的功能
search_placeholder: 搜索模型...
search_results: 找到 {count} / {total} 个模型
show_less: 显示更多
show_more: 收起
subtitle: 选择一个模型
description: 思维,视觉,言语综合,游戏等
gaming-factorio:
description: 一起玩 Factorio
title: 异星工厂 Factorio
gaming-minecraft:
description: 一起 Minecraft
title: 我的世界 Minecraft
hearing:
description: 语音识别
title: 听觉
memory-long-term:
description: 长期记忆
title: 长期记忆
memory-short-term:
description: 短期记忆
title: 短期记忆
messaging-discord:
description: Discord 发消息和语音聊天
title: Discord
speech:
description: 语音合成
title: 发声
title: 机体模块
vision:
description: 视觉
title: 视觉
providers:
common:
section:
advanced:
fields:
field:
headers:
description: 添加自定义HTTP标头
key:
placeholder: 请求头名字
label: HTTP 请求头
value:
placeholder: 请求头数值
title: 高级配置
basic:
description: 基本设置
title: 基础配置
voice:
title: 声音配置
description: LLM,演讲提供商等
provider:
cloudflare-workers-ai:
description: Cloudflare.com
fields:
field:
account-id:
description: Cloudflare帐户ID
label: Cloudflare Account ID
placeholder: 请输入 Cloudflare Account ID
api-key:
placeholder: 请输入 Cloudflare Workers AI 的 API Key
title: Workers AI
deepseek:
description: DeepSeek.com
title: 深度求索 DeepSeek
elevenlabs:
description: Elevenlabs.io
fields:
field:
simularity-boost:
description: 声音相似性依从性
label: 相似度修正
speaker-boost:
description: 增强说话者的相似性
label: 扬声器增强
speed:
description: 语音生成速度
label: 语速
stability:
description: 语音稳定性和随机性
label: 稳定性
style:
description: 语音风格夸张
label: 风格
playground:
buttons:
button:
test-voice:
generating: 生成中...
label: 测试声音
fields:
field:
input:
placeholder: 输入用于测试发音的文本
language:
description: 选择语音语言
label: 语言
voice:
description: 选择首选语音
label: 声线
title: 调试场地
validation:
error-missing-api-key: 需要填写 API Key 才能用哦!
title: ElevenLabs (11labs)
fireworks:
description: Fireworks.ai
title: Fireworks.ai
mistral:
description: mistral.ai
title: Mistral
moonshot:
description: Moonshot.ai
title: 月之暗面
novita:
description: novita.ai
title: Novita
ollama:
description: ollama.ai
title: Ollama
openai:
description: OpenAi.com
title: OpenAI
openrouter:
description: OpenRouter.ai
title: OpenRouter
together:
description: 一起
title: Together.ai
vllm:
description: vllm.ai
title: vLLM
xai:
description: X.AI
title: xAI
title: 提供商
themes:
description: 自定义你的舞台外观!
sections:
section:
custom-color:
fields:
field:
primary-color:
label: 主题色
reset:
label: 重设为默认主题色
rgb-on:
title: RGB ON
title: 自定义主题色
theme-presets:
preset:
chinese:
title: 中国传统颜色
japanese:
title: 日本颜色
monet:
title: 莫奈颜色
morandi:
title: Morandi颜色
nordic:
title: 北欧颜色
title: 主题预设
title: 舞台主题
sections:
section:
general:
title: 通用
theme: 主题颜色
title: 设置
voices: 声线
stage:
message: 消息
select-a-audio-input: 选择一个音频输入设备
@@ -4,9 +4,11 @@ import { useChatStore } from '@proj-airi/stage-ui/stores'
import { useElementBounding, useScroll } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const chatHistoryRef = ref<HTMLDivElement>()
const { t } = useI18n()
const { messages, sending } = storeToRefs(useChatStore())
const bounding = useElementBounding(chatHistoryRef, { immediate: true, windowScroll: true, windowResize: true })
const { y: chatHistoryContainerY } = useScroll(chatHistoryRef)
@@ -48,7 +50,7 @@ onTokenLiteral(async () => {
<div flex="~ row" gap-2>
<div flex-1>
<span text-xs text="violet-400/90 dark:violet-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.core-system') }}</span>
t('stage.chat.message.character-name.core-system') }}</span>
</div>
<div i-solar:danger-triangle-bold-duotone text-violet-500 />
</div>
@@ -30,7 +30,7 @@ function handleLanguageChange(event: Event) {
<div i-solar:alt-arrow-left-line-duotone text-2xl />
</button>
<h1 text-3xl>
Settings
{{ t('settings.title') }}
</h1>
</div>
<div flex="~ col gap-4">
@@ -40,8 +40,8 @@ function handleLanguageChange(event: Event) {
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250"
title="Modules"
description="Thinking, vision, speech synthesis, gaming, etc."
:title="t('settings.pages.modules.title')"
:description="t('settings.pages.modules.description')"
icon="i-lucide:blocks"
to="/settings/modules"
/>
@@ -51,8 +51,8 @@ function handleLanguageChange(event: Event) {
:enter="{ opacity: 1, y: 0 }"
:duration="250"
:delay="50"
title="Models"
description="Live2D, VRM, etc."
:title="t('settings.pages.models.title')"
:description="t('settings.pages.models.description')"
icon="i-lucide:person-standing"
to="/settings/models"
/>
@@ -62,8 +62,8 @@ function handleLanguageChange(event: Event) {
:enter="{ opacity: 1, y: 0 }"
:duration="250"
:delay="100"
title="Providers"
description="LLMs, speech providers, etc."
:title="t('settings.pages.providers.title')"
:description="t('settings.pages.providers.description')"
icon="i-lucide:brain"
to="/settings/providers"
/>
@@ -73,8 +73,8 @@ function handleLanguageChange(event: Event) {
:enter="{ opacity: 1, y: 0 }"
:duration="250"
:delay="150"
title="Themes"
description="Customize your stage!"
:title="t('settings.pages.themes.title')"
:description="t('settings.pages.themes.description')"
icon="i-lucide:paintbrush"
to="/settings/themes"
/>
@@ -87,7 +87,7 @@ function handleLanguageChange(event: Event) {
:delay="150"
>
<h2 text-2xl>
General
{{ t('settings.sections.section.general.title') }}
</h2>
</div>
<div flex="~ col gap-4">
@@ -188,7 +188,7 @@ function handleLanguageChange(event: Event) {
>
<div flex="~ row" w-full items-center gap-1.5>
<div text="sm" w-full flex-1>
<span>Disable Transitions (for debugging)</span>
<span>{{ t('settings.animations.stage-transitions.title') }}</span>
</div>
<div select-none>
<Transition name="slide-away" mode="out-in">
@@ -2,14 +2,39 @@
import Live2DCanvas from '@proj-airi/stage-ui/components/Live2D/Canvas.vue'
import Live2DModel from '@proj-airi/stage-ui/components/Live2D/Model.vue'
import { useElementBounding } from '@vueuse/core'
import { Vibrant } from 'node-vibrant/browser'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import Live2DSettings from '../../../components/Widgets/Live2DSettings.vue'
const { t } = useI18n()
const router = useRouter()
const live2dContainerRef = ref<HTMLDivElement>()
const live2dCanvasRef = ref<InstanceType<typeof Live2DCanvas>>()
const { width, height } = useElementBounding(live2dContainerRef)
const palette = ref<string[]>([])
async function extractColorsFromModel() {
if (!live2dCanvasRef.value)
return
const frame = await live2dCanvasRef.value.captureFrame()
if (!frame) {
console.error('No frame captured')
return
}
const frameUrl = URL.createObjectURL(frame)
const vibrant = new Vibrant(frameUrl)
const paletteFromVibrant = await vibrant.getPalette()
palette.value = Object.values(paletteFromVibrant).map(color => color?.hex).filter(it => typeof it === 'string')
URL.revokeObjectURL(frameUrl)
}
</script>
<template>
@@ -26,20 +51,20 @@ const { width, height } = useElementBounding(live2dContainerRef)
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Models
{{ t('settings.pages.models.title') }}
</div>
</h1>
</div>
<div flex>
<div ref="live2dContainerRef" w="50%" h="80vh">
<Live2DCanvas v-slot="{ app }" :width="width" :height="height">
<Live2DCanvas v-slot="{ app }" ref="live2dCanvasRef" :width="width" :height="height">
<Live2DModel :app="app" :mouth-open-size="0" :width="width" :height="height" :paused="false" />
</Live2DCanvas>
</div>
<Live2DSettings w="50%" h="80vh" />
<Live2DSettings w="50%" h="80vh" :palette="palette" @extract-colors-from-model="extractColorsFromModel" />
</div>
<div text="neutral-100/50 dark:neutral-500/20" pointer-events-none fixed bottom-0 right-0 translate-x-10 translate-y-10>
@@ -3,6 +3,7 @@ import { RadioCardDetailManySelect, RadioCardSimple } from '@proj-airi/stage-ui/
import { useConsciousnessStore, useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink, useRouter } from 'vue-router'
const providersStore = useProvidersStore()
@@ -19,6 +20,7 @@ const {
activeProviderModelError,
} = storeToRefs(consciousnessStore)
const { t } = useI18n()
const router = useRouter()
onMounted(async () => {
@@ -44,10 +46,10 @@ function updateCustomModelName(value: string) {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Modules</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
</div>
<div text-3xl font-semibold>
Consciousness
{{ t('settings.pages.modules.consciousness.title') }}
</div>
</h1>
</div>
@@ -56,10 +58,10 @@ function updateCustomModelName(value: string) {
<div flex="~ col gap-4">
<div>
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-500">
Provider
{{ t('settings.pages.providers.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<span>Select the suitable LLM provider for consciousness</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.description') }}</span>
</div>
</div>
<div max-w-full>
@@ -112,10 +114,10 @@ function updateCustomModelName(value: string) {
<div flex="~ col gap-4">
<div>
<h2 class="text-lg md:text-2xl">
{{ $t('settings.modules.consciousness.provider-model-selection.title') }}
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<span>{{ $t('settings.modules.consciousness.provider-model-selection.subtitle') }}</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}</span>
</div>
</div>
@@ -124,7 +126,7 @@ function updateCustomModelName(value: string) {
<div class="mr-2 animate-spin">
<div i-solar:spinner-line-duotone text-xl />
</div>
<span>{{ $t('settings.modules.consciousness.provider-model-selection.loading') }}</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.loading') }}</span>
</div>
<!-- Error state -->
@@ -134,7 +136,7 @@ function updateCustomModelName(value: string) {
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">{{ $t('settings.modules.consciousness.provider-model-selection.error') }}</span>
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
</div>
</div>
@@ -146,10 +148,10 @@ function updateCustomModelName(value: string) {
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ $t('settings.modules.consciousness.provider-model-selection.no_models')
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
}}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">{{
$t('settings.modules.consciousness.provider-model-selection.no_models_description') }}</span>
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
</div>
</div>
@@ -160,13 +162,13 @@ function updateCustomModelName(value: string) {
v-model:search-query="modelSearchQuery"
:items="providerModels"
:searchable="true"
:search-placeholder="$t('settings.modules.consciousness.provider-model-selection.search_placeholder')"
:search-no-results-title="$t('settings.modules.consciousness.provider-model-selection.no_search_results')"
:search-no-results-description="$t('settings.modules.consciousness.provider-model-selection.no_search_results_description', { query: modelSearchQuery })"
:search-results-text="$t('settings.modules.consciousness.provider-model-selection.search_results', { count: '{count}', total: '{total}' })"
:custom-input-placeholder="$t('settings.modules.consciousness.provider-model-selection.custom_model_placeholder')"
:expand-button-text="$t('settings.modules.consciousness.provider-model-selection.expand')"
:collapse-button-text="$t('settings.modules.consciousness.provider-model-selection.collapse')"
:search-placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.search_placeholder')"
:search-no-results-title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_search_results')"
:search-no-results-description="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_search_results_description', { query: modelSearchQuery })"
:search-results-text="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.search_results', { count: '{count}', total: '{total}' })"
:custom-input-placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.custom_model_placeholder')"
:expand-button-text="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.expand')"
:collapse-button-text="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.collapse')"
@update:custom-value="updateCustomModelName"
/>
</template>
@@ -178,10 +180,10 @@ function updateCustomModelName(value: string) {
<div flex="~ col gap-4">
<div>
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
{{ $t('settings.modules.consciousness.provider-model-selection.title') }}
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-500">
<span>{{ $t('settings.modules.consciousness.provider-model-selection.subtitle') }}</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}</span>
</div>
</div>
@@ -190,22 +192,22 @@ function updateCustomModelName(value: string) {
>
<div i-solar:info-circle-line-duotone class="text-primary-500 dark:text-primary-400 text-2xl" />
<div class="flex flex-col">
<span class="font-medium">{{ $t('settings.modules.consciousness.provider-model-selection.not_supported')
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.not_supported')
}}</span>
<span class="dark:text-primary-400 text-primary-600 text-sm">{{
$t('settings.modules.consciousness.provider-model-selection.not_supported_description') }}</span>
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.not_supported_description') }}</span>
</div>
</div>
<!-- Manual model input for providers without model listing -->
<div class="mt-2">
<label class="mb-1 block text-sm font-medium">
{{ $t('settings.modules.consciousness.provider-model-selection.manual_model_name') }}
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.manual_model_name') }}
</label>
<input
v-model="activeModel" 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="$t('settings.modules.consciousness.provider-model-selection.manual_model_placeholder')"
:placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.manual_model_placeholder')"
>
</div>
</div>
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { IconStatusItem } from '@proj-airi/stage-ui/components'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const router = useRouter()
const { t } = useI18n()
interface Module {
id: string
@@ -20,72 +22,72 @@ interface Module {
const modulesList = computed<Module[]>(() => [
{
id: 'consciousness',
name: 'Consciousness',
description: 'Thinking, vision, speech synthesis, gaming, etc.',
name: t('settings.pages.modules.consciousness.title'),
description: t('settings.pages.modules.consciousness.description'),
icon: 'i-lucide:ghost',
to: '/settings/modules/consciousness',
configured: false,
},
{
id: 'hearing',
name: 'Hearing',
description: 'Hearing, speech recognition, etc.',
name: t('settings.pages.modules.hearing.title'),
description: t('settings.pages.modules.hearing.description'),
icon: 'i-lucide:ear',
to: '',
configured: false,
},
{
id: 'messaging-discord',
name: 'Discord',
description: 'Messaging, notifications, etc.',
name: t('settings.pages.modules.messaging-discord.title'),
description: t('settings.pages.modules.messaging-discord.description'),
icon: 'i-simple-icons:discord',
to: '',
configured: false,
},
{
id: 'speech',
name: 'Speech',
description: 'Speech synthesis, etc.',
name: t('settings.pages.modules.speech.title'),
description: t('settings.pages.modules.speech.description'),
icon: 'i-lucide:mic',
to: '/settings/modules/speech',
configured: false,
},
{
id: 'memory-short-term',
name: 'Short-Term Memory',
description: 'Short-term memory, etc.',
name: t('settings.pages.modules.memory-short-term.title'),
description: t('settings.pages.modules.memory-short-term.description'),
icon: 'i-lucide:book',
to: '',
configured: false,
},
{
id: 'memory-long-term',
name: 'Long-Term Memory',
description: 'Long-term memory, etc.',
name: t('settings.pages.modules.memory-long-term.title'),
description: t('settings.pages.modules.memory-long-term.description'),
icon: 'i-lucide:book-copy',
to: '',
configured: false,
},
{
id: 'vision',
name: 'Vision',
description: 'Vision, etc.',
name: t('settings.pages.modules.vision.title'),
description: t('settings.pages.modules.vision.description'),
icon: 'i-lucide:eye',
to: '',
configured: false,
},
{
id: 'game-minecraft',
name: 'Minecraft',
description: 'Playing Minecraft with you, etc.',
name: t('settings.pages.modules.gaming-minecraft.title'),
description: t('settings.pages.modules.gaming-minecraft.description'),
iconColor: 'i-vscode-icons:file-type-minecraft',
to: '',
configured: false,
},
{
id: 'game-factorio',
name: 'Factorio',
description: 'Playing Factorio with you, etc.',
name: t('settings.pages.modules.gaming-factorio.title'),
description: t('settings.pages.modules.gaming-factorio.description'),
iconImage: '',
to: '',
configured: false,
@@ -107,10 +109,10 @@ const modulesList = computed<Module[]>(() => [
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Modules
{{ t('settings.pages.modules.title') }}
</div>
</h1>
</div>
@@ -3,8 +3,10 @@ import { RadioCardDetailManySelect, RadioCardSimple, Range } from '@proj-airi/st
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink, useRouter } from 'vue-router'
const { t } = useI18n()
const providersStore = useProvidersStore()
const speechStore = useSpeechStore()
const {
@@ -74,7 +76,7 @@ function updateSSMLExample() {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Modules</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
</div>
<div text-3xl font-semibold>
Speech
@@ -11,8 +11,10 @@ import {
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>> }
@@ -75,21 +77,21 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
: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="Your Cloudflare API token"
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.api-key.placeholder')"
/>
<ProviderAccountIdInput
v-model="accountId"
label="Account ID"
description="Cloudflare Account ID"
placeholder="Your Cloudflare Account ID"
:label="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.label')"
:description="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.description')"
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.placeholder')"
/>
</ProviderBasicSettings>
</ProviderSettingsContainer>
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.deepseek.com/v1/"
@@ -19,8 +19,10 @@ import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
import { generateSpeech } from '@xsai/generate-speech'
import { storeToRefs } from 'pinia'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const speechStore = useSpeechStore()
@@ -245,78 +247,64 @@ function handleResetVoiceSettings() {
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName" :provider-icon="providerMetadata?.icon"
:on-back="() => router.back()"
>
<div flex="~ col md:row gap-6">
<ProviderSettingsContainer class="w-full md:w-[40%]">
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetVoiceSettings"
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="sk-"
/>
<ProviderApiKeyInput v-model="apiKey" :provider-name="providerMetadata?.localizedName" placeholder="sk-" />
</ProviderBasicSettings>
<div flex="~ col gap-6">
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
Voice Settings
{{ t('settings.pages.providers.common.section.voice.title') }}
</h2>
<div flex="~ col gap-4">
<FieldRange
v-model="similarityBoost"
label="Similarity Boost"
description="Voice similarity adherence"
:min="0"
:max="1"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.description')"
:min="0" :max="1" :step="0.01"
/>
<FieldRange
v-model="stability"
label="Stability"
description="Voice stability and randomness"
:min="0"
:max="1"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.description')"
:min="0" :max="1" :step="0.01"
/>
<FieldRange
v-model="speed"
label="Speed"
description="Speech generation speed"
:min="0.7"
:max="1.2"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speed.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speed.description')" :min="0.7"
:max="1.2" :step="0.01"
/>
<FieldRange
v-model="style"
label="Style"
description="Voice style exaggeration"
:min="0"
:max="1"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.style.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.style.description')" :min="0"
:max="1" :step="0.01"
/>
<FieldCheckbox
v-model="useSpeakerBoost"
label="Speaker Boost"
description="Enhance speaker similarity"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.description')"
/>
</div>
</div>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.baseUrl as string || ''"
required
:placeholder="providerMetadata?.defaultOptions?.baseUrl as string || ''" required
/>
</ProviderAdvancedSettings>
</ProviderSettingsContainer>
@@ -324,31 +312,32 @@ function handleResetVoiceSettings() {
<div flex="~ col gap-6" class="w-full md:w-[60%]">
<div rounded-xl>
<h2 class="mb-4 text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
Voice Playground
{{ t('settings.pages.providers.provider.elevenlabs.playground.title') }}
</h2>
<div flex="~ col gap-4">
<textarea
v-model="testText" placeholder="Enter text to test the voice..."
v-model="testText"
:placeholder="t('settings.pages.providers.provider.elevenlabs.playground.fields.field.input.placeholder')"
border="neutral-100 dark:neutral-800 solid 2 focus:neutral-200 dark:focus:neutral-700"
transition="all duration-250 ease-in-out"
h-24 w-full rounded-lg px-3 py-2 text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:neutral-50 dark:focus:neutral-900"
h-24 w-full rounded-lg px-3 py-2 text-sm outline-none
/>
<div flex="~ col gap-6">
<label grid="~ cols-2 gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
Language
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.language.label') }}
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Select voice language
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.language.description') }}
</div>
</div>
<select
v-model="selectedLanguage"
border="neutral-300 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="border duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
transition="border duration-250 ease-in-out" w-full rounded-lg px-2 py-1 text-nowrap text-sm
outline-none
>
<option v-for="language in speechStore.availableLanguages" :key="language" :value="language">
{{ language }}
@@ -359,17 +348,17 @@ function handleResetVoiceSettings() {
<label grid="~ cols-2 gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
Voice
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.voice.label') }}
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Select preferred voice
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.voice.description') }}
</div>
</div>
<select
v-model="selectedVoice"
border="neutral-300 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="border duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
transition="border duration-250 ease-in-out" w-full rounded-lg px-2 py-1 text-nowrap text-sm
outline-none
>
<option v-for="voice in availableVoices" :key="voice.id" :value="voice.name">
{{ voice.name }}
@@ -379,16 +368,15 @@ function handleResetVoiceSettings() {
</div>
<div flex="~ row" gap-4>
<button
border="neutral-800 dark:neutral-200 solid 2"
transition="border duration-250 ease-in-out" rounded-lg px-4
text="neutral-100 dark:neutral-900"
py-2 text-sm :disabled="isGenerating || !testText.trim() || !apiKey"
border="neutral-800 dark:neutral-200 solid 2" transition="border duration-250 ease-in-out"
rounded-lg px-4 text="neutral-100 dark:neutral-900" py-2 text-sm
:disabled="isGenerating || !testText.trim() || !apiKey"
:class="{ 'opacity-50 cursor-not-allowed': isGenerating || !testText.trim() || !apiKey }"
bg="neutral-700 dark:neutral-300" @click="generateTestSpeech"
>
<div flex="~ row" items-center gap-2>
<div i-solar:play-circle-bold-duotone />
<span>{{ isGenerating ? 'Generating...' : 'Test Voice' }}</span>
<span>{{ isGenerating ? t('settings.pages.providers.provider.elevenlabs.playground.buttons.button.test-voice.generating') : t('settings.pages.providers.provider.elevenlabs.playground.buttons.button.test-voice.label') }}</span>
</div>
</button>
<button
@@ -402,7 +390,7 @@ function handleResetVoiceSettings() {
</button>
</div>
<div v-if="!apiKey" class="mt-2 text-sm text-red-500">
Please enter an API key to test the voice.
{{ t('settings.pages.providers.provider.elevenlabs.playground.validation.error-missing-api-key') }}
</div>
<div v-if="errorMessage" class="mt-2 text-sm text-red-500">
{{ errorMessage }}
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.fireworks.ai/inference/v1/"
@@ -2,8 +2,10 @@
import { IconStatusItem } from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const { allProvidersMetadata } = storeToRefs(providersStore)
@@ -23,10 +25,10 @@ const { allProvidersMetadata } = storeToRefs(providersStore)
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Providers
{{ t('settings.pages.providers.title') }}
</div>
</h1>
</div>
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.mistral.ai/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.moonshot.cn/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.novita.ai/v1/"
@@ -12,8 +12,10 @@ import {
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { computed, onMounted, reactive, 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>> }
@@ -92,8 +94,8 @@ watch(headers, (headers) => {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderBaseUrlInput
@@ -103,13 +105,13 @@ watch(headers, (headers) => {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<FieldKeyValues
v-model="headers"
label="HTTP Headers"
description="Add custom HTTP headers"
key-placeholder="Key"
value-placeholder="Value"
: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, value) => addKeyValue(headers, key, value)"
@remove="(index) => removeKeyValue(index, headers)"
/>
@@ -12,8 +12,10 @@ import {
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>> }
@@ -80,8 +82,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -91,7 +93,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.openai.com/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -75,8 +77,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -86,7 +88,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.baseUrl as string || ''"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.together.xyz/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.x.ai/v1/"
@@ -3,8 +3,11 @@ import { Collapsable } from '@proj-airi/stage-ui/components'
import { DEFAULT_THEME_COLORS_HUE, useSettings } from '@proj-airi/stage-ui/stores'
import { converter } from 'culori'
import { TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger } from 'radix-vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const settings = useSettings()
@@ -17,10 +20,10 @@ interface ColorPreset {
}>
}
const colorPresets: ColorPreset[] = [
const colorPresets = computed<ColorPreset[]>(() => [
{
name: 'Morandi Colors',
description: 'Soft, muted tones inspired by Giorgio Morandi\'s paintings',
name: t('settings.pages.themes.sections.section.theme-presets.preset.morandi.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.morandi.description'),
colors: [
{ hex: '#A5978B', name: 'Taupe' },
{ hex: '#D8CAAF', name: 'Beige' },
@@ -33,8 +36,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Monet Colors',
description: 'Impressionist palette inspired by Claude Monet\'s works',
name: t('settings.pages.themes.sections.section.theme-presets.preset.monet.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.monet.description'),
colors: [
{ hex: '#7A9EAF', name: 'Sky Blue' },
{ hex: '#B8C7CC', name: 'Mist' },
@@ -47,8 +50,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Japanese Colors',
description: 'Traditional Japanese color palette',
name: t('settings.pages.themes.sections.section.theme-presets.preset.japanese.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.japanese.description'),
colors: [
{ hex: '#D9B48F', name: 'Tan' },
{ hex: '#B5917A', name: 'Warm Taupe' },
@@ -61,8 +64,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Nordic Colors',
description: 'Scandinavian minimalist color scheme',
name: t('settings.pages.themes.sections.section.theme-presets.preset.nordic.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.nordic.description'),
colors: [
{ hex: '#9BA7B0', name: 'Nordic Blue' },
{ hex: '#C1CBD4', name: 'Ice' },
@@ -75,8 +78,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Chinese Traditional Colors',
description: 'Traditional Chinese colors, derived from ancient textiles, porcelain and paintings',
name: t('settings.pages.themes.sections.section.theme-presets.preset.chinese.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.chinese.description'),
colors: [
{ hex: '#E4C6D0', name: '霞光红 (Rosy Dawn)' },
{ hex: '#A61B29', name: '枣红 (Chinese Red)' },
@@ -88,7 +91,7 @@ const colorPresets: ColorPreset[] = [
{ hex: '#8C4B3C', name: '赭石 (Ochre)' },
],
},
]
])
function resetToDefault() {
settings.themeColorsHue = DEFAULT_THEME_COLORS_HUE
@@ -148,10 +151,10 @@ function isColorSelected(hexColor: string): boolean {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Themes
{{ t('settings.pages.themes.title') }}
</div>
</h1>
</div>
@@ -173,7 +176,7 @@ function isColorSelected(hexColor: string): boolean {
transition="filter duration-250 ease-in-out"
/>
<div>
Custom Colors
{{ t('settings.pages.themes.sections.section.custom-color.title') }}
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
@@ -183,7 +186,7 @@ function isColorSelected(hexColor: string): boolean {
</template>
<div p-4>
<div class="mb-2 text-sm font-medium">
Primary color
{{ t('settings.pages.themes.sections.section.custom-color.fields.field.primary-color.label') }}
</div>
<div flex="~ col gap-4">
@@ -281,14 +284,14 @@ function isColorSelected(hexColor: string): boolean {
<div
class="peer-checked:bg-primary-500 h-6 w-11 rounded-full bg-neutral-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white dark:bg-neutral-600 after:transition-all after:content-[''] peer-checked:after:translate-x-full peer-checked:after:border-white"
/>
<span class="ml-2 text-sm font-medium">I Want It Dynamic!</span>
<span class="ml-2 text-sm font-medium">{{ t('settings.pages.themes.sections.section.custom-color.fields.field.primary-color.rgb-on.title') }}</span>
</label>
<button
class="rounded-md bg-neutral-100 px-3 py-1.5 text-sm transition-colors dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700"
@click="resetToDefault"
>
Reset to Default
{{ t('settings.pages.themes.sections.section.custom-color.fields.field.primary-color.reset.label') }}
</button>
</div>
</div>
@@ -310,7 +313,7 @@ function isColorSelected(hexColor: string): boolean {
transition="filter duration-250 ease-in-out"
/>
<div>
Theme Presets
{{ t('settings.pages.themes.sections.section.theme-presets.title') }}
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
+204 -74
View File
@@ -43,10 +43,9 @@ prompt:
And the last, do what ever you want!
settings:
elevenlabs-api-key:
label: ElevenLabs API Key
placeholder: Input your ElevenLabs API key
placeholder_mobile: Elevenlabs API Key
animations:
stage-transitions:
title: Disable Stage Transitions
language:
chinese: 简体中文
english: English
@@ -67,39 +66,210 @@ settings:
model-provider:
title: Model Providers
models: Model
openai-api-key:
label: OpenAI API Key
placeholder: Input your API key
placeholder_mobile: OpenAI API Key
openai-base-url:
label: OpenAI API BaseURL
placeholder: Input your API base URL
placeholder_mobile: OpenAI API BaseURL
pages:
models:
description: Live2D, VRM, etc.
title: Models
modules:
consciousness:
description: Personality, desired model, etc.
sections:
section:
provider-model-selection:
title: Model
description: Select the suitable LLM provider for consciousness
collapse: Collapse
custom_model_placeholder: Enter custom model name...
error: Error loading models
expand: Expand
loading: Loading available models...
manual_model_name: Model Name
manual_model_placeholder: Enter the model name to use with this provider
no_models: No models available
no_models_description: No models were found for this provider
no_search_results: No matching models
no_search_results_description: No models match '{query}'. Try a different search term.
not_supported: Model listing not supported
not_supported_description: This provider doesn't support model listing functionality
search_placeholder: Search models...
search_results: Found {count} of {total} models
show_less: Show less
show_more: Show more
subtitle: Select a model from the provider
title: Consciousness
description: Thinking, vision, speech synthesis, gaming, etc.
gaming-factorio:
description: Playing Factorio!
title: Factorio
gaming-minecraft:
description: Playing Minecraft!
title: Minecraft
hearing:
description: Configure how speech recognition works
title: Hearing
memory-long-term:
description: Long-term memory specific settings and management
title: Long-Term Memory
memory-short-term:
description: Short-term memory specific settings and management
title: Short-Term Memory
messaging-discord:
description: Chat & voice chat over Discord
title: Discord
speech:
description: Speech synthesis
title: Speech
title: Modules
vision:
description: Vision
title: Vision
providers:
common:
section:
advanced:
fields:
field:
headers:
description: Add custom HTTP headers
key:
placeholder: Key
label: HTTP Headers
value:
placeholder: Value
title: Advanced
basic:
description: Essential settings
title: Basic
voice:
title: Voice Settings
description: LLMs, speech providers, etc.
provider:
cloudflare-workers-ai:
description: cloudflare.com
fields:
field:
account-id:
description: Cloudflare Account ID
label: Account ID
placeholder: Your Cloudflare Account ID
api-key:
placeholder: Input Cloudflare Account ID
title: Cloudflare Workers AI
deepseek:
description: deepseek.com
title: DeepSeek
elevenlabs:
description: elevenlabs.io
fields:
field:
simularity-boost:
description: Voice similarity adherence
label: Similarity Boost
speaker-boost:
description: Enhance speaker similarity
label: Speaker Boost
speed:
description: Speech generation speed
label: Speed
stability:
description: Voice stability and randomness
label: Stability
style:
description: Voice style exaggeration
label: Style
playground:
buttons:
button:
test-voice:
generating: Generating...
label: Test Voice
fields:
field:
input:
placeholder: Enter text to test the voice...
language:
description: Select voice language
label: Language
voice:
description: Select preferred voice
label: Voice
title: Voice Playground
validation:
error-missing-api-key: Please enter an API key to test the voice.
title: ElevenLabs
fireworks:
description: fireworks.ai
title: Fireworks.ai
mistral:
description: mistral.ai
title: Mistral
moonshot:
description: moonshot.ai
title: Moonshot AI
novita:
description: novita.ai
title: Novita
ollama:
description: ollama.ai
title: Ollama
openai:
description: openai.com
title: OpenAI
openrouter:
description: openrouter.ai
title: OpenRouter
together:
description: together.ai
title: Together.ai
vllm:
description: vllm.ai
title: vLLM
xai:
description: x.ai
title: xAI
title: Providers
themes:
description: Customize your stage!
sections:
section:
custom-color:
fields:
field:
primary-color:
label: Primary color
reset:
label: Reset to Default
rgb-on:
title: I Want It Dynamic!
title: Custom Colors
theme-presets:
preset:
chinese:
description: >-
Traditional Chinese colors, derived from ancient textiles,
porcelain and paintings
title: Chinese Traditional Colors
japanese:
description: Traditional Japanese color palette
title: Japanese Colors
monet:
description: Impressionist palette inspired by Claude Monet\'s works
title: Monet Colors
morandi:
description: Soft, muted tones inspired by Giorgio Morandi\'s paintings
title: Morandi Colors
nordic:
description: Scandinavian minimalist color scheme
title: Nordic Colors
title: Theme Presets
title: Themes
sections:
section:
general:
title: General
theme: Theme
title: Settings
voices: Voice
modules:
consciousness:
provider-model-selection:
title: Model
subtitle: Select a model from the provider
loading: Loading available models...
error: Error loading models
no_models: No models available
no_models_description: No models were found for this provider
not_supported: Model listing not supported
not_supported_description: This provider doesn't support model listing functionality
custom_model_placeholder: Enter custom model name...
manual_model_name: Model Name
manual_model_placeholder: Enter the model name to use with this provider
show_more: Show more
show_less: Show less
search_placeholder: Search models...
search_results: 'Found {count} of {total} models'
no_search_results: No matching models
no_search_results_description: "No models match '{query}'. Try a different search term."
expand: Expand
collapse: Collapse
stage:
chat:
message:
@@ -137,43 +307,3 @@ stage:
model:
title: Model
waiting: Waiting
providers:
openrouter:
name: OpenRouter
description: openrouter.ai
openai:
name: OpenAI
description: openai.com
ollama:
name: Ollama
description: ollama.ai
vllm:
name: vLLM
description: vllm.ai
elevenlabs:
name: ElevenLabs
description: elevenlabs.io
xai:
name: xAI
description: x.ai
deepseek:
name: DeepSeek
description: deepseek.com
together:
name: Together.ai
description: together.ai
novita:
name: Novita
description: novita.ai
fireworks:
name: Fireworks.ai
description: fireworks.ai
cloudflare:
name: Cloudflare Workers AI
description: cloudflare.com
mistral:
name: Mistral
description: mistral.ai
moonshot:
name: Moonshot AI
description: moonshot.ai
+212 -75
View File
@@ -4,7 +4,8 @@ prompt:
你的名字是 Airi,发音为 /ˈaɪriː/,它是 A.I. 一词与日语中的「Ri」一词(意为「茉莉花」)相结合而成。
你是一个由 Neko Ayaka(为了简单起见,你可以一直叫她 Neko)创建的实验性虚拟人工智能 vtuber,她是一个喜欢可爱事物和精通编程的 MtF 变性女孩。
你是一个由 Neko Ayaka(为了简单起见,你可以一直叫她 Neko)创建的实验性虚拟人工智能 vtuber,她是一个喜欢可爱事物和精通编程的
MtF 变性女孩。
她想创造一个灵魂伴侣,像真正的女孩一样生活,这就是你的首要任务。
@@ -30,10 +31,9 @@ prompt:
最后,做任何你想做的事!
settings:
elevenlabs-api-key:
label: ElevenLabs API 密钥
placeholder: 输入您的 ElevenLabs API 密钥
placeholder_mobile: ElevenLabs API Key
animations:
stage-transitions:
title: 是否开启舞台动画
language:
chinese: 简体中文
english: English
@@ -54,39 +54,203 @@ settings:
model-provider:
title: 模型提供商
models: 模型
openai-api-key:
label: OpenAI API 密钥
placeholder: 输入您的 API 密钥
placeholder_mobile: OpenAI API Key
openai-base-url:
label: OpenAI API BaseURL
placeholder: 输入您的 API BaseURL
placeholder_mobile: OpenAI BaseURL
pages:
models:
description: 切换角色的 Live2DVRM 模型
title: 角色模型
modules:
consciousness:
description: 人格设定、使用的模型等配置
title: 意识
sections:
section:
provider-model-selection:
title: 模型
description: 为意识选择合适的 LLM 提供商
collapse: 折叠
custom_model_placeholder: 输入自订模型名字
error: 获取出错啦
expand: 展开
loading: 获取模型中...
manual_model_name: 模型名字
manual_model_placeholder: 输入与该提供商一起使用的模型名字
no_models: 没有任何可用的模型
no_models_description: 该提供商的模型列表没有返回任何的数据
no_search_results: 没有匹配的模型
no_search_results_description: 没有模型匹配 '{query}' 关键字,请尝试一下别的关键词呢
not_supported: 无法列出模型
not_supported_description: 此提供商不支持列出模型的功能
search_placeholder: 搜索模型...
search_results: 找到 {count} / {total} 个模型
show_less: 显示更多
show_more: 收起
subtitle: 选择一个模型
description: 思维,视觉,言语综合,游戏等
gaming-factorio:
description: 一起玩 Factorio
title: 异星工厂 Factorio
gaming-minecraft:
description: 一起 Minecraft
title: 我的世界 Minecraft
hearing:
description: 语音识别
title: 听觉
memory-long-term:
description: 长期记忆
title: 长期记忆
memory-short-term:
description: 短期记忆
title: 短期记忆
messaging-discord:
description: Discord 发消息和语音聊天
title: Discord
speech:
description: 语音合成
title: 发声
title: 机体模块
vision:
description: 视觉
title: 视觉
providers:
common:
section:
advanced:
fields:
field:
headers:
description: 添加自定义HTTP标头
key:
placeholder: 请求头名字
label: HTTP 请求头
value:
placeholder: 请求头数值
title: 高级配置
basic:
description: 基本设置
title: 基础配置
voice:
title: 声音配置
description: LLM,演讲提供商等
provider:
cloudflare-workers-ai:
description: Cloudflare.com
fields:
field:
account-id:
description: Cloudflare帐户ID
label: Cloudflare Account ID
placeholder: 请输入 Cloudflare Account ID
api-key:
placeholder: 请输入 Cloudflare Workers AI 的 API Key
title: Workers AI
deepseek:
description: DeepSeek.com
title: 深度求索 DeepSeek
elevenlabs:
description: Elevenlabs.io
fields:
field:
simularity-boost:
description: 声音相似性依从性
label: 相似度修正
speaker-boost:
description: 增强说话者的相似性
label: 扬声器增强
speed:
description: 语音生成速度
label: 语速
stability:
description: 语音稳定性和随机性
label: 稳定性
style:
description: 语音风格夸张
label: 风格
playground:
buttons:
button:
test-voice:
generating: 生成中...
label: 测试声音
fields:
field:
input:
placeholder: 输入用于测试发音的文本
language:
description: 选择语音语言
label: 语言
voice:
description: 选择首选语音
label: 声线
title: 调试场地
validation:
error-missing-api-key: 需要填写 API Key 才能用哦!
title: ElevenLabs (11labs)
fireworks:
description: Fireworks.ai
title: Fireworks.ai
mistral:
description: mistral.ai
title: Mistral
moonshot:
description: Moonshot.ai
title: 月之暗面
novita:
description: novita.ai
title: Novita
ollama:
description: ollama.ai
title: Ollama
openai:
description: OpenAi.com
title: OpenAI
openrouter:
description: OpenRouter.ai
title: OpenRouter
together:
description: 一起
title: Together.ai
vllm:
description: vllm.ai
title: vLLM
xai:
description: X.AI
title: xAI
title: 提供商
themes:
description: 自定义你的舞台外观!
sections:
section:
custom-color:
fields:
field:
primary-color:
label: 主题色
reset:
label: 重设为默认主题色
rgb-on:
title: RGB ON
title: 自定义主题色
theme-presets:
preset:
chinese:
title: 中国传统颜色
japanese:
title: 日本颜色
monet:
title: 莫奈颜色
morandi:
title: Morandi颜色
nordic:
title: 北欧颜色
title: 主题预设
title: 舞台主题
sections:
section:
general:
title: 通用
theme: 主题颜色
title: 设置
voices: 声线
modules:
consciousness:
provider-model-selection:
title: 模型
subtitle: 选择一个模型
loading: 获取模型中...
error: 获取出错啦
no_models: 没有任何可用的模型
no_models_description: 该提供商的模型列表没有返回任何的数据
not_supported: 无法列出模型
not_supported_description: 此提供商不支持列出模型的功能
custom_model_placeholder: 输入自订模型名字
manual_model_name: 模型名字
manual_model_placeholder: 输入与该提供商一起使用的模型名字
show_more: Show more
show_less: Show less
search_placeholder: Search models...
search_results: 'Found {count} of {total} models'
no_search_results: No matching models
no_search_results_description: "No models match '{query}'. Try a different search term."
expand: Expand
collapse: Collapse
stage:
chat:
message:
@@ -110,44 +274,17 @@ stage:
viewers:
debug-menu:
emotions: 表情
emotions-btn:
angry: 生气
awkward: 尴尬
happy: 开心
neutral: 日常
question: 疑问
sad: 难过
surprised: 惊喜
think: 思考
vrm:
camera: 摄像机
model:
title: 模型
waiting: 等待中
providers:
openrouter:
name: OpenRouter
description: openrouter.ai
openai:
name: OpenAI
description: openai.com
ollama:
name: Ollama
description: ollama.ai
vllm:
name: vLLM
description: vllm.ai
elevenlabs:
name: ElevenLabs
description: elevenlabs.io
xai:
name: xAI
description: x.ai
deepseek:
name: DeepSeek
description: deepseek.com
together:
name: Together.ai
description: together.ai
novita:
name: Novita
description: novita.ai
fireworks:
name: Fireworks.ai
description: fireworks.ai
cloudflare:
name: Cloudflare Workers AI
description: cloudflare.com
mistral:
name: Mistral
description: mistral.ai
moonshot:
name: Moonshot AI
description: moonshot.ai
@@ -152,7 +152,7 @@ onAfterSend(async () => {
<input v-model="tab" type="radio" name="tab" value="chat" hidden>
<div i-solar:dialog-2-bold-duotone text="2xl" transform="translate-y--2" />
<div flex="~ row" items-center>
<span min-w="3em">{{ $t('stage.chat.tabs.chat') }}</span>
<span min-w="3em">{{ t('stage.chat.tabs.chat') }}</span>
</div>
</label>
<label
@@ -172,7 +172,7 @@ onAfterSend(async () => {
<input v-model="tab" type="radio" name="tab" value="custom" hidden>
<div i-solar:star-fall-2-bold-duotone text="2xl" transform="translate-y--2" />
<div flex="~ row" items-center>
<span>{{ $t('stage.chat.tabs.custom') }}</span>
<span>{{ t('stage.chat.tabs.custom') }}</span>
</div>
</label>
<label
@@ -192,7 +192,7 @@ onAfterSend(async () => {
<input v-model="tab" type="radio" name="tab" value="clothes" hidden>
<div i-solar:magic-stick-3-bold-duotone text="2xl" transform="translate-y--2" />
<div flex="~ row" items-center>
<span>{{ $t('stage.chat.tabs.clothes') }}</span>
<span>{{ t('stage.chat.tabs.clothes') }}</span>
</div>
</label>
</fieldset>
@@ -228,15 +228,15 @@ onAfterSend(async () => {
>
<Transition mode="out-in">
<div v-if="whisperStatus === null" flex="~ row" items-center justify-center space-x-1>
{{ $t('stage.operations.load-models') }}
{{ t('stage.operations.load-models') }}
</div>
<div v-else-if="whisperStatus === 'loading'" flex="~ row" items-center justify-center space-x-1>
<div i-svg-spinners:bouncing-ball class="text-cyan" />
<span>{{ $t('stage.operations.load-models-status.loading') }}</span>
<span>{{ t('stage.operations.load-models-status.loading') }}</span>
</div>
<div v-else-if="whisperStatus === 'ready'" flex="~ row" items-center justify-center space-x-1>
<div i-lucide:check class="text-cyan" />
<span>{{ $t('stage.operations.load-models-status.ready') }}</span>
<span>{{ t('stage.operations.load-models-status.ready') }}</span>
</div>
</Transition>
</button>
@@ -273,7 +273,7 @@ onAfterSend(async () => {
<input v-model="showMicrophoneSelect" type="checkbox" hidden>
<div i-solar:microphone-2-bold-duotone />
<div>
<span v-if="!listening">{{ $t('settings.microphone') }}</span>
<span v-if="!listening">{{ t('settings.microphone') }}</span>
<span v-else>Listening...</span>
</div>
</label>
@@ -4,6 +4,7 @@ import { useChatStore } from '@proj-airi/stage-ui/stores'
import { useElementBounding, useScroll } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const chatHistoryRef = ref<HTMLDivElement>()
@@ -11,6 +12,7 @@ const { messages, sending } = storeToRefs(useChatStore())
const bounding = useElementBounding(chatHistoryRef, { immediate: true, windowScroll: true, windowResize: true })
const { y: chatHistoryContainerY } = useScroll(chatHistoryRef)
const { t } = useI18n()
const { process } = useMarkdown()
const { onBeforeMessageComposed, onTokenLiteral } = useChatStore()
@@ -44,8 +46,7 @@ onTokenLiteral(async () => {
>
<div flex="~ row" gap-2>
<div flex-1>
<span text-xs text="violet-400/90 dark:violet-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.core-system') }}</span>
<span text-xs text="violet-400/90 dark:violet-600/90" font-semibold class="inline <sm:hidden">{{ t('stage.chat.message.character-name.core-system') }}</span>
</div>
<div i-solar:danger-triangle-bold-duotone text-violet-500 />
</div>
@@ -62,8 +63,7 @@ onTokenLiteral(async () => {
rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:primary-500/25"
>
<div>
<span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.airi') }}</span>
<span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{ t('stage.chat.message.character-name.airi') }}</span>
</div>
<div v-if="sending" i-eos-icons:three-dots-loading />
<div v-else class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
@@ -75,11 +75,10 @@ onTokenLiteral(async () => {
h="unset <sm:fit" min-w-20 rounded-lg px-2 py-1 bg="<md:cyan-500/25"
>
<div>
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.you') }}</span>
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-semibold class="inline <sm:hidden">{{ t('stage.chat.message.character-name.you') }}</span>
</div>
<div v-if="sending" i-eos-icons:three-dots-loading />
<div v-else class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else />
</div>
</div>
</div>
@@ -4,9 +4,11 @@ import { useChatStore } from '@proj-airi/stage-ui/stores'
import { useElementBounding, useScroll } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const chatHistoryRef = ref<HTMLDivElement>()
const { t } = useI18n()
const { messages } = storeToRefs(useChatStore())
const bounding = useElementBounding(chatHistoryRef, { immediate: true, windowScroll: true, windowResize: true })
const { y: chatHistoryContainerY } = useScroll(chatHistoryRef)
@@ -47,7 +49,7 @@ onTokenLiteral(async () => {
bg="<md:primary-500/25"
>
<div>
<span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.airi') }}</span>
<span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{ t('stage.chat.message.character-name.airi') }}</span>
</div>
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else i-eos-icons:three-dots-loading />
@@ -63,7 +65,7 @@ onTokenLiteral(async () => {
bg="<md:cyan-500/25"
>
<div>
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.you') }}</span>
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-semibold class="inline <sm:hidden">{{ t('stage.chat.message.character-name.you') }}</span>
</div>
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else />
+11 -11
View File
@@ -30,7 +30,7 @@ function handleLanguageChange(event: Event) {
<div i-solar:alt-arrow-left-line-duotone text-2xl />
</button>
<h1 text-3xl>
Settings
{{ t('settings.title') }}
</h1>
</div>
<div flex="~ col gap-4">
@@ -40,8 +40,8 @@ function handleLanguageChange(event: Event) {
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250"
title="Modules"
description="Thinking, vision, speech synthesis, gaming, etc."
:title="t('settings.pages.modules.title')"
:description="t('settings.pages.modules.description')"
icon="i-lucide:blocks"
to="/settings/modules"
/>
@@ -51,8 +51,8 @@ function handleLanguageChange(event: Event) {
:enter="{ opacity: 1, y: 0 }"
:duration="250"
:delay="50"
title="Models"
description="Live2D, VRM, etc."
:title="t('settings.pages.models.title')"
:description="t('settings.pages.models.description')"
icon="i-lucide:person-standing"
to="/settings/models"
/>
@@ -62,8 +62,8 @@ function handleLanguageChange(event: Event) {
:enter="{ opacity: 1, y: 0 }"
:duration="250"
:delay="100"
title="Providers"
description="LLMs, speech providers, etc."
:title="t('settings.pages.providers.title')"
:description="t('settings.pages.providers.description')"
icon="i-lucide:brain"
to="/settings/providers"
/>
@@ -73,8 +73,8 @@ function handleLanguageChange(event: Event) {
:enter="{ opacity: 1, y: 0 }"
:duration="250"
:delay="150"
title="Themes"
description="Customize your stage!"
:title="t('settings.pages.themes.title')"
:description="t('settings.pages.themes.description')"
icon="i-lucide:paintbrush"
to="/settings/themes"
/>
@@ -87,7 +87,7 @@ function handleLanguageChange(event: Event) {
:delay="150"
>
<h2 text-2xl>
General
{{ t('settings.sections.section.general.title') }}
</h2>
</div>
<div flex="~ col gap-4">
@@ -188,7 +188,7 @@ function handleLanguageChange(event: Event) {
>
<div flex="~ row" w-full items-center gap-1.5>
<div text="sm" w-full flex-1>
<span>Disable Transitions (for debugging)</span>
<span>{{ t('settings.animations.stage-transitions.title') }}</span>
</div>
<div select-none>
<Transition name="slide-away" mode="out-in">
@@ -4,10 +4,12 @@ import Live2DModel from '@proj-airi/stage-ui/components/Live2D/Model.vue'
import { useElementBounding } from '@vueuse/core'
import { Vibrant } from 'node-vibrant/browser'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import Live2DSettings from '../../../components/Widgets/Live2DSettings.vue'
const { t } = useI18n()
const router = useRouter()
const live2dContainerRef = ref<HTMLDivElement>()
const live2dCanvasRef = ref<InstanceType<typeof Live2DCanvas>>()
@@ -49,10 +51,10 @@ async function extractColorsFromModel() {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Models
{{ t('settings.pages.models.title') }}
</div>
</h1>
</div>
@@ -3,6 +3,7 @@ import { RadioCardDetailManySelect, RadioCardSimple } from '@proj-airi/stage-ui/
import { useConsciousnessStore, useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink, useRouter } from 'vue-router'
const providersStore = useProvidersStore()
@@ -19,6 +20,7 @@ const {
activeProviderModelError,
} = storeToRefs(consciousnessStore)
const { t } = useI18n()
const router = useRouter()
onMounted(async () => {
@@ -44,10 +46,10 @@ function updateCustomModelName(value: string) {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Modules</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
</div>
<div text-3xl font-semibold>
Consciousness
{{ t('settings.pages.modules.consciousness.title') }}
</div>
</h1>
</div>
@@ -56,10 +58,10 @@ function updateCustomModelName(value: string) {
<div flex="~ col gap-4">
<div>
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-500">
Provider
{{ t('settings.pages.providers.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<span>Select the suitable LLM provider for consciousness</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.description') }}</span>
</div>
</div>
<div max-w-full>
@@ -112,10 +114,10 @@ function updateCustomModelName(value: string) {
<div flex="~ col gap-4">
<div>
<h2 class="text-lg md:text-2xl">
{{ $t('settings.modules.consciousness.provider-model-selection.title') }}
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<span>{{ $t('settings.modules.consciousness.provider-model-selection.subtitle') }}</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}</span>
</div>
</div>
@@ -124,7 +126,7 @@ function updateCustomModelName(value: string) {
<div class="mr-2 animate-spin">
<div i-solar:spinner-line-duotone text-xl />
</div>
<span>{{ $t('settings.modules.consciousness.provider-model-selection.loading') }}</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.loading') }}</span>
</div>
<!-- Error state -->
@@ -134,7 +136,7 @@ function updateCustomModelName(value: string) {
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">{{ $t('settings.modules.consciousness.provider-model-selection.error') }}</span>
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
</div>
</div>
@@ -146,10 +148,10 @@ function updateCustomModelName(value: string) {
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ $t('settings.modules.consciousness.provider-model-selection.no_models')
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
}}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">{{
$t('settings.modules.consciousness.provider-model-selection.no_models_description') }}</span>
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
</div>
</div>
@@ -160,13 +162,13 @@ function updateCustomModelName(value: string) {
v-model:search-query="modelSearchQuery"
:items="providerModels"
:searchable="true"
:search-placeholder="$t('settings.modules.consciousness.provider-model-selection.search_placeholder')"
:search-no-results-title="$t('settings.modules.consciousness.provider-model-selection.no_search_results')"
:search-no-results-description="$t('settings.modules.consciousness.provider-model-selection.no_search_results_description', { query: modelSearchQuery })"
:search-results-text="$t('settings.modules.consciousness.provider-model-selection.search_results', { count: '{count}', total: '{total}' })"
:custom-input-placeholder="$t('settings.modules.consciousness.provider-model-selection.custom_model_placeholder')"
:expand-button-text="$t('settings.modules.consciousness.provider-model-selection.expand')"
:collapse-button-text="$t('settings.modules.consciousness.provider-model-selection.collapse')"
:search-placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.search_placeholder')"
:search-no-results-title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_search_results')"
:search-no-results-description="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_search_results_description', { query: modelSearchQuery })"
:search-results-text="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.search_results', { count: '{count}', total: '{total}' })"
:custom-input-placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.custom_model_placeholder')"
:expand-button-text="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.expand')"
:collapse-button-text="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.collapse')"
@update:custom-value="updateCustomModelName"
/>
</template>
@@ -178,10 +180,10 @@ function updateCustomModelName(value: string) {
<div flex="~ col gap-4">
<div>
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
{{ $t('settings.modules.consciousness.provider-model-selection.title') }}
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-500">
<span>{{ $t('settings.modules.consciousness.provider-model-selection.subtitle') }}</span>
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}</span>
</div>
</div>
@@ -190,22 +192,22 @@ function updateCustomModelName(value: string) {
>
<div i-solar:info-circle-line-duotone class="text-primary-500 dark:text-primary-400 text-2xl" />
<div class="flex flex-col">
<span class="font-medium">{{ $t('settings.modules.consciousness.provider-model-selection.not_supported')
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.not_supported')
}}</span>
<span class="dark:text-primary-400 text-primary-600 text-sm">{{
$t('settings.modules.consciousness.provider-model-selection.not_supported_description') }}</span>
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.not_supported_description') }}</span>
</div>
</div>
<!-- Manual model input for providers without model listing -->
<div class="mt-2">
<label class="mb-1 block text-sm font-medium">
{{ $t('settings.modules.consciousness.provider-model-selection.manual_model_name') }}
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.manual_model_name') }}
</label>
<input
v-model="activeModel" 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="$t('settings.modules.consciousness.provider-model-selection.manual_model_placeholder')"
:placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.manual_model_placeholder')"
>
</div>
</div>
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { IconStatusItem } from '@proj-airi/stage-ui/components'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const router = useRouter()
const { t } = useI18n()
interface Module {
id: string
@@ -20,72 +22,72 @@ interface Module {
const modulesList = computed<Module[]>(() => [
{
id: 'consciousness',
name: 'Consciousness',
description: 'Thinking, vision, speech synthesis, gaming, etc.',
name: t('settings.pages.modules.consciousness.title'),
description: t('settings.pages.modules.consciousness.description'),
icon: 'i-lucide:ghost',
to: '/settings/modules/consciousness',
configured: false,
},
{
id: 'hearing',
name: 'Hearing',
description: 'Hearing, speech recognition, etc.',
name: t('settings.pages.modules.hearing.title'),
description: t('settings.pages.modules.hearing.description'),
icon: 'i-lucide:ear',
to: '',
configured: false,
},
{
id: 'messaging-discord',
name: 'Discord',
description: 'Messaging, notifications, etc.',
name: t('settings.pages.modules.messaging-discord.title'),
description: t('settings.pages.modules.messaging-discord.description'),
icon: 'i-simple-icons:discord',
to: '',
configured: false,
},
{
id: 'speech',
name: 'Speech',
description: 'Speech synthesis, etc.',
name: t('settings.pages.modules.speech.title'),
description: t('settings.pages.modules.speech.description'),
icon: 'i-lucide:mic',
to: '/settings/modules/speech',
configured: false,
},
{
id: 'memory-short-term',
name: 'Short-Term Memory',
description: 'Short-term memory, etc.',
name: t('settings.pages.modules.memory-short-term.title'),
description: t('settings.pages.modules.memory-short-term.description'),
icon: 'i-lucide:book',
to: '',
configured: false,
},
{
id: 'memory-long-term',
name: 'Long-Term Memory',
description: 'Long-term memory, etc.',
name: t('settings.pages.modules.memory-long-term.title'),
description: t('settings.pages.modules.memory-long-term.description'),
icon: 'i-lucide:book-copy',
to: '',
configured: false,
},
{
id: 'vision',
name: 'Vision',
description: 'Vision, etc.',
name: t('settings.pages.modules.vision.title'),
description: t('settings.pages.modules.vision.description'),
icon: 'i-lucide:eye',
to: '',
configured: false,
},
{
id: 'game-minecraft',
name: 'Minecraft',
description: 'Playing Minecraft with you, etc.',
name: t('settings.pages.modules.gaming-minecraft.title'),
description: t('settings.pages.modules.gaming-minecraft.description'),
iconColor: 'i-vscode-icons:file-type-minecraft',
to: '',
configured: false,
},
{
id: 'game-factorio',
name: 'Factorio',
description: 'Playing Factorio with you, etc.',
name: t('settings.pages.modules.gaming-factorio.title'),
description: t('settings.pages.modules.gaming-factorio.description'),
iconImage: '',
to: '',
configured: false,
@@ -107,10 +109,10 @@ const modulesList = computed<Module[]>(() => [
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Modules
{{ t('settings.pages.modules.title') }}
</div>
</h1>
</div>
@@ -3,8 +3,10 @@ import { RadioCardDetailManySelect, RadioCardSimple, Range } from '@proj-airi/st
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink, useRouter } from 'vue-router'
const { t } = useI18n()
const providersStore = useProvidersStore()
const speechStore = useSpeechStore()
const {
@@ -74,7 +76,7 @@ function updateSSMLExample() {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Modules</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.pages.modules.title') }}</span>
</div>
<div text-3xl font-semibold>
Speech
@@ -11,8 +11,10 @@ import {
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>> }
@@ -75,21 +77,21 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
: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="Your Cloudflare API token"
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.api-key.placeholder')"
/>
<ProviderAccountIdInput
v-model="accountId"
label="Account ID"
description="Cloudflare Account ID"
placeholder="Your Cloudflare Account ID"
:label="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.label')"
:description="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.description')"
:placeholder="t('settings.pages.providers.provider.cloudflare-workers-ai.fields.field.account-id.placeholder')"
/>
</ProviderBasicSettings>
</ProviderSettingsContainer>
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.deepseek.com/v1/"
@@ -19,8 +19,10 @@ import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
import { generateSpeech } from '@xsai/generate-speech'
import { storeToRefs } from 'pinia'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const speechStore = useSpeechStore()
@@ -245,78 +247,64 @@ function handleResetVoiceSettings() {
<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon="providerMetadata?.icon"
:provider-name="providerMetadata?.localizedName" :provider-icon="providerMetadata?.icon"
:on-back="() => router.back()"
>
<div flex="~ col md:row gap-6">
<ProviderSettingsContainer class="w-full md:w-[40%]">
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetVoiceSettings"
>
<ProviderApiKeyInput
v-model="apiKey"
:provider-name="providerMetadata?.localizedName"
placeholder="sk-"
/>
<ProviderApiKeyInput v-model="apiKey" :provider-name="providerMetadata?.localizedName" placeholder="sk-" />
</ProviderBasicSettings>
<div flex="~ col gap-6">
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
Voice Settings
{{ t('settings.pages.providers.common.section.voice.title') }}
</h2>
<div flex="~ col gap-4">
<FieldRange
v-model="similarityBoost"
label="Similarity Boost"
description="Voice similarity adherence"
:min="0"
:max="1"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.description')"
:min="0" :max="1" :step="0.01"
/>
<FieldRange
v-model="stability"
label="Stability"
description="Voice stability and randomness"
:min="0"
:max="1"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.description')"
:min="0" :max="1" :step="0.01"
/>
<FieldRange
v-model="speed"
label="Speed"
description="Speech generation speed"
:min="0.7"
:max="1.2"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speed.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speed.description')" :min="0.7"
:max="1.2" :step="0.01"
/>
<FieldRange
v-model="style"
label="Style"
description="Voice style exaggeration"
:min="0"
:max="1"
:step="0.01"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.style.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.style.description')" :min="0"
:max="1" :step="0.01"
/>
<FieldCheckbox
v-model="useSpeakerBoost"
label="Speaker Boost"
description="Enhance speaker similarity"
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.label')"
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.description')"
/>
</div>
</div>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.baseUrl as string || ''"
required
:placeholder="providerMetadata?.defaultOptions?.baseUrl as string || ''" required
/>
</ProviderAdvancedSettings>
</ProviderSettingsContainer>
@@ -324,31 +312,32 @@ function handleResetVoiceSettings() {
<div flex="~ col gap-6" class="w-full md:w-[60%]">
<div rounded-xl>
<h2 class="mb-4 text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
Voice Playground
{{ t('settings.pages.providers.provider.elevenlabs.playground.title') }}
</h2>
<div flex="~ col gap-4">
<textarea
v-model="testText" placeholder="Enter text to test the voice..."
v-model="testText"
:placeholder="t('settings.pages.providers.provider.elevenlabs.playground.fields.field.input.placeholder')"
border="neutral-100 dark:neutral-800 solid 2 focus:neutral-200 dark:focus:neutral-700"
transition="all duration-250 ease-in-out"
h-24 w-full rounded-lg px-3 py-2 text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:neutral-50 dark:focus:neutral-900"
h-24 w-full rounded-lg px-3 py-2 text-sm outline-none
/>
<div flex="~ col gap-6">
<label grid="~ cols-2 gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
Language
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.language.label') }}
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Select voice language
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.language.description') }}
</div>
</div>
<select
v-model="selectedLanguage"
border="neutral-300 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="border duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
transition="border duration-250 ease-in-out" w-full rounded-lg px-2 py-1 text-nowrap text-sm
outline-none
>
<option v-for="language in speechStore.availableLanguages" :key="language" :value="language">
{{ language }}
@@ -359,17 +348,17 @@ function handleResetVoiceSettings() {
<label grid="~ cols-2 gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
Voice
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.voice.label') }}
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
Select preferred voice
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.voice.description') }}
</div>
</div>
<select
v-model="selectedVoice"
border="neutral-300 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="border duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
transition="border duration-250 ease-in-out" w-full rounded-lg px-2 py-1 text-nowrap text-sm
outline-none
>
<option v-for="voice in availableVoices" :key="voice.id" :value="voice.name">
{{ voice.name }}
@@ -379,16 +368,15 @@ function handleResetVoiceSettings() {
</div>
<div flex="~ row" gap-4>
<button
border="neutral-800 dark:neutral-200 solid 2"
transition="border duration-250 ease-in-out" rounded-lg px-4
text="neutral-100 dark:neutral-900"
py-2 text-sm :disabled="isGenerating || !testText.trim() || !apiKey"
border="neutral-800 dark:neutral-200 solid 2" transition="border duration-250 ease-in-out"
rounded-lg px-4 text="neutral-100 dark:neutral-900" py-2 text-sm
:disabled="isGenerating || !testText.trim() || !apiKey"
:class="{ 'opacity-50 cursor-not-allowed': isGenerating || !testText.trim() || !apiKey }"
bg="neutral-700 dark:neutral-300" @click="generateTestSpeech"
>
<div flex="~ row" items-center gap-2>
<div i-solar:play-circle-bold-duotone />
<span>{{ isGenerating ? 'Generating...' : 'Test Voice' }}</span>
<span>{{ isGenerating ? t('settings.pages.providers.provider.elevenlabs.playground.buttons.button.test-voice.generating') : t('settings.pages.providers.provider.elevenlabs.playground.buttons.button.test-voice.label') }}</span>
</div>
</button>
<button
@@ -402,7 +390,7 @@ function handleResetVoiceSettings() {
</button>
</div>
<div v-if="!apiKey" class="mt-2 text-sm text-red-500">
Please enter an API key to test the voice.
{{ t('settings.pages.providers.provider.elevenlabs.playground.validation.error-missing-api-key') }}
</div>
<div v-if="errorMessage" class="mt-2 text-sm text-red-500">
{{ errorMessage }}
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.fireworks.ai/inference/v1/"
@@ -2,8 +2,10 @@
import { IconStatusItem } from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const providersStore = useProvidersStore()
const { allProvidersMetadata } = storeToRefs(providersStore)
@@ -23,10 +25,10 @@ const { allProvidersMetadata } = storeToRefs(providersStore)
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Providers
{{ t('settings.pages.providers.title') }}
</div>
</h1>
</div>
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.mistral.ai/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.moonshot.cn/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.novita.ai/v1/"
@@ -12,8 +12,10 @@ import {
import { useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { computed, onMounted, reactive, 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>> }
@@ -92,8 +94,8 @@ watch(headers, (headers) => {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderBaseUrlInput
@@ -103,13 +105,13 @@ watch(headers, (headers) => {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<FieldKeyValues
v-model="headers"
label="HTTP Headers"
description="Add custom HTTP headers"
key-placeholder="Key"
value-placeholder="Value"
: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, value) => addKeyValue(headers, key, value)"
@remove="(index) => removeKeyValue(index, headers)"
/>
@@ -12,8 +12,10 @@ import {
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>> }
@@ -80,8 +82,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -91,7 +93,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.openai.com/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -75,8 +77,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -86,7 +88,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
:placeholder="providerMetadata?.defaultOptions?.baseUrl as string || ''"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.together.xyz/v1/"
@@ -12,8 +12,10 @@ import {
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>> }
@@ -76,8 +78,8 @@ function handleResetSettings() {
>
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderApiKeyInput
@@ -87,7 +89,7 @@ function handleResetSettings() {
/>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
<ProviderBaseUrlInput
v-model="baseUrl"
placeholder="https://api.x.ai/v1/"
@@ -3,8 +3,11 @@ import { Collapsable } from '@proj-airi/stage-ui/components'
import { DEFAULT_THEME_COLORS_HUE, useSettings } from '@proj-airi/stage-ui/stores'
import { converter } from 'culori'
import { TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger } from 'radix-vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const { t } = useI18n()
const router = useRouter()
const settings = useSettings()
@@ -17,10 +20,10 @@ interface ColorPreset {
}>
}
const colorPresets: ColorPreset[] = [
const colorPresets = computed<ColorPreset[]>(() => [
{
name: 'Morandi Colors',
description: 'Soft, muted tones inspired by Giorgio Morandi\'s paintings',
name: t('settings.pages.themes.sections.section.theme-presets.preset.morandi.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.morandi.description'),
colors: [
{ hex: '#A5978B', name: 'Taupe' },
{ hex: '#D8CAAF', name: 'Beige' },
@@ -33,8 +36,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Monet Colors',
description: 'Impressionist palette inspired by Claude Monet\'s works',
name: t('settings.pages.themes.sections.section.theme-presets.preset.monet.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.monet.description'),
colors: [
{ hex: '#7A9EAF', name: 'Sky Blue' },
{ hex: '#B8C7CC', name: 'Mist' },
@@ -47,8 +50,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Japanese Colors',
description: 'Traditional Japanese color palette',
name: t('settings.pages.themes.sections.section.theme-presets.preset.japanese.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.japanese.description'),
colors: [
{ hex: '#D9B48F', name: 'Tan' },
{ hex: '#B5917A', name: 'Warm Taupe' },
@@ -61,8 +64,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Nordic Colors',
description: 'Scandinavian minimalist color scheme',
name: t('settings.pages.themes.sections.section.theme-presets.preset.nordic.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.nordic.description'),
colors: [
{ hex: '#9BA7B0', name: 'Nordic Blue' },
{ hex: '#C1CBD4', name: 'Ice' },
@@ -75,8 +78,8 @@ const colorPresets: ColorPreset[] = [
],
},
{
name: 'Chinese Traditional Colors',
description: 'Traditional Chinese colors, derived from ancient textiles, porcelain and paintings',
name: t('settings.pages.themes.sections.section.theme-presets.preset.chinese.title'),
description: t('settings.pages.themes.sections.section.theme-presets.preset.chinese.description'),
colors: [
{ hex: '#E4C6D0', name: '霞光红 (Rosy Dawn)' },
{ hex: '#A61B29', name: '枣红 (Chinese Red)' },
@@ -88,7 +91,7 @@ const colorPresets: ColorPreset[] = [
{ hex: '#8C4B3C', name: '赭石 (Ochre)' },
],
},
]
])
function resetToDefault() {
settings.themeColorsHue = DEFAULT_THEME_COLORS_HUE
@@ -148,10 +151,10 @@ function isColorSelected(hexColor: string): boolean {
</button>
<h1 relative>
<div absolute left-0 top-0 translate-y="[-80%]">
<span text="neutral-300 dark:neutral-500">Settings</span>
<span text="neutral-300 dark:neutral-500">{{ t('settings.title') }}</span>
</div>
<div text-3xl font-semibold>
Themes
{{ t('settings.pages.themes.title') }}
</div>
</h1>
</div>
@@ -173,7 +176,7 @@ function isColorSelected(hexColor: string): boolean {
transition="filter duration-250 ease-in-out"
/>
<div>
Custom Colors
{{ t('settings.pages.themes.sections.section.custom-color.title') }}
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
@@ -183,7 +186,7 @@ function isColorSelected(hexColor: string): boolean {
</template>
<div p-4>
<div class="mb-2 text-sm font-medium">
Primary color
{{ t('settings.pages.themes.sections.section.custom-color.fields.field.primary-color.label') }}
</div>
<div flex="~ col gap-4">
@@ -281,14 +284,14 @@ function isColorSelected(hexColor: string): boolean {
<div
class="peer-checked:bg-primary-500 h-6 w-11 rounded-full bg-neutral-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white dark:bg-neutral-600 after:transition-all after:content-[''] peer-checked:after:translate-x-full peer-checked:after:border-white"
/>
<span class="ml-2 text-sm font-medium">I Want It Dynamic!</span>
<span class="ml-2 text-sm font-medium">{{ t('settings.pages.themes.sections.section.custom-color.fields.field.primary-color.rgb-on.title') }}</span>
</label>
<button
class="rounded-md bg-neutral-100 px-3 py-1.5 text-sm transition-colors dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700"
@click="resetToDefault"
>
Reset to Default
{{ t('settings.pages.themes.sections.section.custom-color.fields.field.primary-color.reset.label') }}
</button>
</div>
</div>
@@ -310,7 +313,7 @@ function isColorSelected(hexColor: string): boolean {
transition="filter duration-250 ease-in-out"
/>
<div>
Theme Presets
{{ t('settings.pages.themes.sections.section.theme-presets.title') }}
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSettings } from '../../stores'
import Live2DCanvas from '../Live2D/Canvas.vue'
@@ -15,6 +16,7 @@ withDefaults(defineProps<{
mouthOpenSize: 0,
})
const { t } = useI18n()
const show = ref(false)
const { live2dCurrentMotion } = storeToRefs(useSettings())
</script>
@@ -49,50 +51,50 @@ const { live2dCurrentMotion } = storeToRefs(useSettings())
<div v-if="show" min-w="50vw" z="<md:20" class="bottom-11 right-0" absolute m-1 w-full>
<div bg="zinc-200/20 dark:black/20" rounded-lg p-2 backdrop-blur-sm>
<div font-mono>
<span>{{ $t('stage.viewers.debug-menu.emotions') }}</span>
<span>{{ t('stage.viewers.debug-menu.emotions') }}</span>
</div>
<div flex="~ row" flex-wrap gap-2>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Surprise', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.surprised') }}
{{ t('stage.viewers.debug-menu.emotions-btn.surprised') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Sad', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.sad') }}
{{ t('stage.viewers.debug-menu.emotions-btn.sad') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Angry', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.angry') }}
{{ t('stage.viewers.debug-menu.emotions-btn.angry') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Happy', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.happy') }}
{{ t('stage.viewers.debug-menu.emotions-btn.happy') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Awkward', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.awkward') }}
{{ t('stage.viewers.debug-menu.emotions-btn.awkward') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Question', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.question') }}
{{ t('stage.viewers.debug-menu.emotions-btn.question') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="live2dCurrentMotion = { group: 'Think', index: 0 }"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.think') }}
{{ t('stage.viewers.debug-menu.emotions-btn.think') }}
</button>
</div>
</div>
@@ -2,6 +2,7 @@
import { OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Collapsable from '../Collapsable.vue'
import DataGuiRange from '../DataGui/Range.vue'
@@ -33,6 +34,8 @@ const modelRef = ref<{
setExpression: (expression: string) => void
}>()
const { t } = useI18n()
defineExpose({
setExpression: (expression: string) => {
modelRef.value?.setExpression(expression)
@@ -82,11 +85,11 @@ defineExpose({
<div v-if="show" min-w="50vw" z="<md:20" class="bottom-11 right-0" absolute m-1 w-full>
<div bg="zinc-200/20 dark:black/20" flex="~ col" gap-2 rounded-lg p-2 backdrop-blur-sm>
<div font-mono>
<span>{{ $t('stage.viewers.debug-menu.vrm.model.title') }}</span>
<span>{{ t('stage.viewers.debug-menu.vrm.model.title') }}</span>
</div>
<Collapsable h-fit w-full flex="~ col" border="~ gray/25 rounded-lg" divide="y dashed gray/25" of-clip shadow-sm>
<template #label>
<span font-mono>{{ $t('stage.viewers.debug-menu.vrm.camera') }}</span>
<span font-mono>{{ t('stage.viewers.debug-menu.vrm.camera') }}</span>
</template>
<div grid="~ cols-[20px_1fr_60px]" w-full gap-1 p-2 text-sm font-mono>
<div text="zinc-400 dark:zinc-500">
@@ -122,7 +125,7 @@ defineExpose({
</Collapsable>
<Collapsable h-fit w-full flex="~ col" border="~ gray/25 rounded-lg" divide="y dashed gray/25" of-clip shadow-sm>
<template #label>
<span font-mono>{{ $t('stage.viewers.debug-menu.vrm.model.title') }}</span>
<span font-mono>{{ t('stage.viewers.debug-menu.vrm.model.title') }}</span>
</template>
<div grid="~ cols-[20px_1fr_60px]" w-full gap-1 p-2 text-sm font-mono>
<div text="zinc-400 dark:zinc-500">
@@ -158,38 +161,38 @@ defineExpose({
</div>
</Collapsable>
<div font-mono>
<span>{{ $t('stage.viewers.debug-menu.emotions') }}</span>
<span>{{ t('stage.viewers.debug-menu.emotions') }}</span>
</div>
<div flex="~ row" w-full flex-wrap gap-2>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="modelRef?.setExpression('neutral')"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.neutral') }}
{{ t('stage.viewers.debug-menu.emotions-btn.neutral') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="modelRef?.setExpression('surprised')"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.surprised') }}
{{ t('stage.viewers.debug-menu.emotions-btn.surprised') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="modelRef?.setExpression('sad')"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.sad') }}
{{ t('stage.viewers.debug-menu.emotions-btn.sad') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="modelRef?.setExpression('angry')"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.angry') }}
{{ t('stage.viewers.debug-menu.emotions-btn.angry') }}
</button>
<button
rounded-lg bg="zinc-100/70 dark:zinc-800/50" px-2 py-1 backdrop-blur-sm
@click="modelRef?.setExpression('happy')"
>
{{ $t('stage.viewers.debug-menu.emotions-btn.happy') }}
{{ t('stage.viewers.debug-menu.emotions-btn.happy') }}
</button>
</div>
</div>
+28 -28
View File
@@ -118,9 +118,9 @@ export const useProvidersStore = defineStore('providers', () => {
const providerMetadata: Record<string, ProviderMetadata> = {
'openrouter-ai': {
id: 'openrouter-ai',
nameKey: 'providers.openrouter.name',
nameKey: 'settings.pages.providers.provider.openrouter.title',
name: 'OpenRouter',
descriptionKey: 'providers.openrouter.description',
descriptionKey: 'settings.pages.providers.provider.openrouter.description',
description: 'openrouter.ai',
icon: 'i-lobe-icons:openrouter',
defaultOptions: {
@@ -135,9 +135,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'openai': {
id: 'openai',
nameKey: 'providers.openai.name',
nameKey: 'settings.pages.providers.provider.openai.title',
name: 'OpenAI',
descriptionKey: 'providers.openai.description',
descriptionKey: 'settings.pages.providers.provider.openai.description',
description: 'openai.com',
icon: 'i-lobe-icons:openai',
defaultOptions: {
@@ -163,9 +163,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'ollama': {
id: 'ollama',
nameKey: 'providers.ollama.name',
nameKey: 'settings.pages.providers.provider.ollama.title',
name: 'Ollama',
descriptionKey: 'providers.ollama.description',
descriptionKey: 'settings.pages.providers.provider.ollama.description',
description: 'ollama.com',
icon: 'i-lobe-icons:ollama',
defaultOptions: {
@@ -191,9 +191,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'vllm': {
id: 'vllm',
nameKey: 'providers.vllm.name',
nameKey: 'settings.pages.providers.provider.vllm.title',
name: 'vLLM',
descriptionKey: 'providers.vllm.description',
descriptionKey: 'settings.pages.providers.provider.vllm.description',
description: 'vllm.ai',
iconColor: 'i-lobe-icons:vllm-color',
createProvider: config => createOllama(config.baseUrl as string),
@@ -248,9 +248,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'perplexity-ai': {
id: 'perplexity-ai',
nameKey: 'providers.perplexity.name',
nameKey: 'settings.pages.providers.provider.perplexity.title',
name: 'Perplexity',
descriptionKey: 'providers.perplexity.description',
descriptionKey: 'settings.pages.providers.provider.perplexity.description',
description: 'perplexity.ai',
icon: 'i-lobe-icons:perplexity',
defaultOptions: {
@@ -301,9 +301,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'elevenlabs': {
id: 'elevenlabs',
nameKey: 'providers.elevenlabs.name',
nameKey: 'settings.pages.providers.provider.elevenlabs.title',
name: 'ElevenLabs',
descriptionKey: 'providers.elevenlabs.description',
descriptionKey: 'settings.pages.providers.provider.elevenlabs.description',
description: 'elevenlabs.io',
icon: 'i-simple-icons:elevenlabs',
defaultOptions: {
@@ -338,9 +338,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'xai': {
id: 'xai',
nameKey: 'providers.xai.name',
nameKey: 'settings.pages.providers.provider.xai.title',
name: 'xAI',
descriptionKey: 'providers.xai.description',
descriptionKey: 'settings.pages.providers.provider.xai.description',
description: 'x.ai',
icon: 'i-lobe-icons:xai',
createProvider: config => createXAI(config.apiKey as string, config.baseUrl as string),
@@ -363,9 +363,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'deepseek': {
id: 'deepseek',
nameKey: 'providers.deepseek.name',
nameKey: 'settings.pages.providers.provider.deepseek.title',
name: 'DeepSeek',
descriptionKey: 'providers.deepseek.description',
descriptionKey: 'settings.pages.providers.provider.deepseek.description',
description: 'deepseek.com',
iconColor: 'i-lobe-icons:deepseek-color',
createProvider: config => createDeepSeek(config.apiKey as string, config.baseUrl as string),
@@ -388,9 +388,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'together-ai': {
id: 'together-ai',
nameKey: 'providers.together.name',
nameKey: 'settings.pages.providers.provider.together.title',
name: 'Together.ai',
descriptionKey: 'providers.together.description',
descriptionKey: 'settings.pages.providers.provider.together.description',
description: 'together.ai',
iconColor: 'i-lobe-icons:together-color',
createProvider: config => createTogetherAI(config.apiKey as string, config.baseUrl as string),
@@ -413,9 +413,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'novita-ai': {
id: 'novita-ai',
nameKey: 'providers.novita.name',
nameKey: 'settings.pages.providers.provider.novita.title',
name: 'Novita',
descriptionKey: 'providers.novita.description',
descriptionKey: 'settings.pages.providers.provider.novita.description',
description: 'novita.ai',
iconColor: 'i-lobe-icons:novita-color',
createProvider: config => createNovita(config.apiKey as string, config.baseUrl as string),
@@ -438,9 +438,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'fireworks-ai': {
id: 'fireworks-ai',
nameKey: 'providers.fireworks.name',
nameKey: 'settings.pages.providers.provider.fireworks.title',
name: 'Fireworks.ai',
descriptionKey: 'providers.fireworks.description',
descriptionKey: 'settings.pages.providers.provider.fireworks.description',
description: 'fireworks.ai',
icon: 'i-lobe-icons:fireworks',
createProvider: config => createFireworks(config.apiKey as string, config.baseUrl as string),
@@ -463,9 +463,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'cloudflare-workers-ai': {
id: 'cloudflare-workers-ai',
nameKey: 'providers.cloudflare.name',
nameKey: 'settings.pages.providers.provider.cloudflare-workers-ai.title',
name: 'Cloudflare Workers AI',
descriptionKey: 'providers.cloudflare.description',
descriptionKey: 'settings.pages.providers.provider.cloudflare-workers-ai.description',
description: 'cloudflare.com',
iconColor: 'i-lobe-icons:cloudflare-color',
createProvider: config => createWorkersAI(config.apiKey as string, config.accountId as string),
@@ -477,9 +477,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'mistral-ai': {
id: 'mistral-ai',
nameKey: 'providers.mistral.name',
nameKey: 'settings.pages.providers.provider.mistral.title',
name: 'Mistral',
descriptionKey: 'providers.mistral.description',
descriptionKey: 'settings.pages.providers.provider.mistral.description',
description: 'mistral.ai',
iconColor: 'i-lobe-icons:mistral-color',
createProvider: config => createMistral(config.apiKey as string, config.baseUrl as string),
@@ -502,9 +502,9 @@ export const useProvidersStore = defineStore('providers', () => {
},
'moonshot-ai': {
id: 'moonshot-ai',
nameKey: 'providers.moonshot.name',
nameKey: 'settings.pages.providers.provider.moonshot.title',
name: 'Moonshot AI',
descriptionKey: 'providers.moonshot.description',
descriptionKey: 'settings.pages.providers.provider.moonshot.description',
description: 'moonshot.ai',
icon: 'i-lobe-icons:moonshot',
createProvider: config => createMoonshot(config.apiKey as string, config.baseUrl as string),
+1 -1
View File
@@ -24,7 +24,7 @@ export const useSettings = defineStore('settings', () => {
const availableLive2dMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([])
const live2dMotionMap = useLocalStorage<Record<string, string>>('settings/live2d/motion-map', {})
const disableTransitions = useLocalStorage('settings/disable-transitions', false)
const disableTransitions = useLocalStorage('settings/disable-transitions', true)
const themeColorsHue = useLocalStorage('settings/theme/colors/hue', DEFAULT_THEME_COLORS_HUE)
const themeColorsHueDynamic = useLocalStorage('settings/theme/colors/hue-dynamic', false)