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 }">