feat:(stage-ui) Modify settings card (#1027)

This commit is contained in:
leafyy
2026-02-09 15:10:18 +08:00
committed by GitHub
parent 9419a77810
commit 19d635d12a
20 changed files with 388 additions and 65 deletions
-1
View File
@@ -1 +0,0 @@
sha256-69tCpJaxRUnyR9CrHlmWJWEWLDpYzyzska7ui9++QoY=
+10 -1
View File
@@ -91,6 +91,8 @@ pages:
card_not_found: Card not found
character: Character
close: Close
chat:
provider: Chat / Provider
consciousness:
model: Consciousness / Model
created_by: created by
@@ -119,6 +121,9 @@ pages:
scenario: You recently woke up and forgot everything about your previous life.
systemprompt: You will receive messages, answer to them like a real human.
posthistoryinstructions: Remember to imitate an human.
modules_info: Configure modules for this character card. If not set, default module configuration will be used.
use_default: Use default
use_default_not_configured: Use default (not configured)
fields_info:
subtitle: >-
You can put here some details about the character you are creating,
@@ -139,6 +144,8 @@ pages:
version: >-
Card version, you should increase this if you are making changes from
a previous card.
consciousness_model: Select the consciousness model (AI model) for this character.
speech_model: Select the speech synthesis model and voice for this character.
errors:
name: Name should be valid or non-empty.
version: 'Error: Invalid version number !'
@@ -148,6 +155,7 @@ pages:
systemprompt: 'Error: Please, provide a system prompt.'
posthistoryinstructions: 'Error: Post history prompt is required.'
modules: Modules
name_asc: Name (A-Z)
name_desc: Name (Z-A)
no_cards: No cards yet. Click the button above to upload one!
@@ -159,6 +167,7 @@ pages:
search: Search cards...
sort_by: Sort by
speech:
provider: Speech / Provider
model: Speech / Model
voice: Speech / Voice
systemprompt: System Prompt
@@ -305,7 +314,7 @@ pages:
search_results: Found {count} of {total} models
show_less: Show less
show_more: Show more
subtitle: Select a model from the provider
subtitle: Select a default model from the provider
title: Model
title: Consciousness
description: Thinking, vision, speech synthesis, gaming, etc.
+1 -1
View File
@@ -294,7 +294,7 @@ pages:
search_results: Encontrados {count} de {total} modelos
show_less: Mostrar menos
show_more: Mostrar más
subtitle: Selecciona un modelo del proveedor
subtitle: Seleccionar un modelo predeterminado del proveedor
title: Modelo
title: Consciencia
description: Pensamiento, visión, síntesis de voz, juegos, etc.
+1 -1
View File
@@ -294,7 +294,7 @@ pages:
search_results: '{count} modèles trouvés sur {total}'
show_less: Afficher moins
show_more: Afficher plus
subtitle: Sélectionnez un modèle du fournisseur
subtitle: Sélectionner un modèle par défaut du fournisseur
title: Modèle
title: Conscience
description: Synthèse, vision, parole, jeux, etc.
+1 -1
View File
@@ -294,7 +294,7 @@ pages:
search_results: 結果{total}個中{count}個のモデルが見つかりました
show_less: 表示を減らす
show_more: もっと表示
subtitle: プロバイダーからモデルを選択
subtitle: プロバイダーからデフォルトモデルを選択
title: モデル
title: 意識
description: 思考、視覚、音声合成、ゲームなど。
+1 -1
View File
@@ -294,7 +294,7 @@ pages:
search_results: Found {count} of {total} models
show_less: Show less
show_more: Show more
subtitle: Select a model from the provider
subtitle: 제공업체에서 기본 모델 선택
title: Model
title: Consciousness
description: Thinking, vision, speech synthesis, gaming, etc.
+1 -1
View File
@@ -294,7 +294,7 @@ pages:
search_results: Найдено {count} из {total} моделей
show_less: Показать меньше
show_more: Показать больше
subtitle: Выберите модель у провайдера
subtitle: Выберите модель по умолчанию у провайдера
title: Модель
title: Сознание
description: Мыслительный процесс, зрение, синтез речи, игры и т. д.
+1 -1
View File
@@ -294,7 +294,7 @@ pages:
search_results: Tìm thấy {count}/{total} mô hình
show_less: Ẩn bớt
show_more: Hiện thêm
subtitle: Chọn mô hình từ nhà cung cấp
subtitle: Chọn mô hình mặc định từ nhà cung cấp
title: Mô hình
title: Ý thức
description: Tư duy, thị giác, tổng hợp giọng nói, chơi game, v.v.
@@ -88,6 +88,8 @@ pages:
card_not_found: 未找到角色卡
character: 角色设定
close: 关闭
chat:
provider: 聊天 / 提供商
consciousness:
model: 意识 / 模型
created_by: 创建者
@@ -116,6 +118,9 @@ pages:
scenario: 你最近醒来,忘记了之前的所有生活。
systemprompt: 你将收到消息,请像真实人类一样回复。
posthistoryinstructions: 记得模仿人类的行为。
modules_info: 配置此角色卡使用的模块。如果不设置,将使用默认模块配置。
use_default: 使用默认
use_default_not_configured: 使用默认 (当前未配置)
fields_info:
subtitle: >-
您可以在这里填写有关您正在创建的角色的一些详细信息,解释他的背景和情境,以及应该如何回应您的互动。
@@ -127,11 +132,13 @@ pages:
在这里描述您的角色的个性。例如:害羞?好奇?其他?
scenario: 周围环境是怎样的?当前的情境是什么?
greetings_field: 问候语
greetings: 您的角色应该如何说你好
greetings: 您的角色应该如何说"你好"
systemprompt: 在这里向 AI LLM 解释当被提示时应该如何回应。
posthistoryinstructions: 在消息历史之后,放入 AI LLM 应该阅读的内容。
version: >-
卡片版本,如果您从之前的卡片做了更改,应当增加此版本号。
consciousness_model: 选择此角色使用的意识模型(AI 模型)。
speech_model: 选择此角色使用的语音合成模型和声线。
errors:
name: 错误:你必须提供一个有效的名称!
version: '错误:版本号无效!'
@@ -152,6 +159,7 @@ pages:
search: 搜索角色卡...
sort_by: 排序方式
speech:
provider: 声音 / 提供商
model: 声音 / 模型
voice: 声音 / 声线
systemprompt: 系统提示词
@@ -294,7 +302,7 @@ pages:
search_results: 找到 {count} / {total} 个模型
show_less: 显示更多
show_more: 收起
subtitle: 选择一个模型
subtitle: 选择一个默认模型
title: 模型
title: 意识
description: 思维,视觉,言语综合,游戏等
@@ -294,7 +294,7 @@ pages:
search_results: 找到 {count} / {total} 個模型
show_less: 顯示更少
show_more: 顯示更多
subtitle: 選擇一個模型
subtitle: 選擇一個預設模型
title: 模型
title: 意識
description: 思維、視覺、語音綜合、遊戲等
@@ -1,10 +1,16 @@
<script setup lang="ts">
import type { Card } from '@proj-airi/ccc'
import type { AiriExtension } from '@proj-airi/stage-ui/stores/modules/airi-card'
import kebabcase from '@stdlib/string-base-kebabcase'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness'
import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { Button, FieldInput, FieldValues } from '@proj-airi/ui'
import { Select } from '@proj-airi/ui/components/form'
import { storeToRefs } from 'pinia'
import {
DialogContent,
DialogOverlay,
@@ -29,10 +35,125 @@ const modelValue = defineModel<boolean>()
const { t } = useI18n()
const cardStore = useAiriCardStore()
const consciousnessStore = useConsciousnessStore()
const speechStore = useSpeechStore()
const providersStore = useProvidersStore()
const { activeProvider: consciousnessProvider, activeModel: defaultConsciousnessModel } = storeToRefs(consciousnessStore)
const { activeSpeechProvider: speechProvider, activeSpeechModel: defaultSpeechModel, activeSpeechVoiceId: defaultSpeechVoiceId } = storeToRefs(speechStore)
// Determine if we're in edit mode
const isEditMode = computed(() => !!props.cardId)
// Modules configuration
const selectedConsciousnessProvider = ref<string>('')
const selectedConsciousnessModel = ref<string>('')
const selectedSpeechProvider = ref<string>('')
const selectedSpeechModel = ref<string>('')
const selectedSpeechVoiceId = ref<string>('')
// Computed: available consciousness provider options
const consciousnessProviderOptions = computed(() => {
return providersStore.configuredChatProvidersMetadata.map(provider => ({
value: provider.id,
label: provider.localizedName || provider.name,
}))
})
// Computed: available consciousness models options
const consciousnessModelOptions = computed(() => {
const provider = selectedConsciousnessProvider.value || consciousnessProvider.value
if (!provider)
return []
const models = providersStore.getModelsForProvider(provider)
return models.map(model => ({
value: model.id,
label: model.name || model.id,
}))
})
// Computed: available speech provider options
const speechProviderOptions = computed(() => {
return providersStore.configuredSpeechProvidersMetadata.map(provider => ({
value: provider.id,
label: provider.localizedName || provider.name,
}))
})
// Computed: available speech models options
const speechModelOptions = computed(() => {
const provider = selectedSpeechProvider.value || speechProvider.value
if (!provider)
return []
const models = providersStore.getModelsForProvider(provider)
return models.map(model => ({
value: model.id,
label: model.name || model.id,
}))
})
// Computed: available speech voices options
const speechVoiceOptions = computed(() => {
const provider = selectedSpeechProvider.value || speechProvider.value
if (!provider)
return []
const voices = speechStore.getVoicesForProvider(provider)
return voices.map(voice => ({
value: voice.id,
label: voice.name || voice.id,
}))
})
// Load models for current providers on init
watch(() => [consciousnessProvider.value, speechProvider.value], async ([consProvider, spProvider]) => {
if (consProvider) {
await consciousnessStore.loadModelsForProvider(consProvider)
}
if (spProvider) {
await speechStore.loadVoicesForProvider(spProvider)
const metadata = providersStore.getProviderMetadata(spProvider)
if (metadata?.capabilities.listModels) {
await providersStore.fetchModelsForProvider(spProvider)
}
}
}, { immediate: true })
// Watch consciousness provider changes and reload models
watch(selectedConsciousnessProvider, async (newProvider, oldProvider) => {
if (oldProvider !== undefined && newProvider !== oldProvider && newProvider) {
await consciousnessStore.loadModelsForProvider(newProvider)
// Reset model selection to default or empty
selectedConsciousnessModel.value = ''
}
})
// Watch speech provider changes and reload models/voices
watch(selectedSpeechProvider, async (newProvider, oldProvider) => {
if (oldProvider !== undefined && newProvider !== oldProvider && newProvider) {
await speechStore.loadVoicesForProvider(newProvider)
const metadata = providersStore.getProviderMetadata(newProvider)
if (metadata?.capabilities.listModels) {
await providersStore.fetchModelsForProvider(newProvider)
}
// Reset model and voice selection
selectedSpeechModel.value = ''
selectedSpeechVoiceId.value = ''
}
})
// Reset voice when speech model changes (different models may have different voices)
watch(selectedSpeechModel, async (newModel, oldModel) => {
// Only reset if model actually changed and we're not initializing
const provider = selectedSpeechProvider.value || speechProvider.value
if (oldModel !== undefined && newModel !== oldModel && provider) {
// Reload voices for the current provider
await speechStore.loadVoicesForProvider(provider)
// Reset voice selection to default
selectedSpeechVoiceId.value = defaultSpeechVoiceId.value || ''
}
})
// Tab type definition
interface Tab {
id: string
@@ -47,6 +168,7 @@ const activeTabId = ref('')
const tabs: Tab[] = [
{ id: 'identity', label: t('settings.pages.card.creation.identity'), icon: 'i-solar:emoji-funny-square-bold-duotone' },
{ id: 'behavior', label: t('settings.pages.card.creation.behavior'), icon: 'i-solar:chat-round-line-bold-duotone' },
{ id: 'modules', label: t('settings.pages.card.modules'), icon: 'i-solar:widget-4-bold-duotone' },
{ id: 'settings', label: t('settings.pages.card.creation.settings'), icon: 'i-solar:settings-bold-duotone' },
]
@@ -116,13 +238,35 @@ function saveCard(card: Card): boolean {
}
showError.value = false
// Build card with modules extension
const cardWithModules = {
...rawCard,
extensions: {
...rawCard.extensions,
airi: {
modules: {
consciousness: {
provider: selectedConsciousnessProvider.value || consciousnessProvider.value,
model: selectedConsciousnessModel.value || defaultConsciousnessModel.value,
},
speech: {
provider: selectedSpeechProvider.value || speechProvider.value,
model: selectedSpeechModel.value || defaultSpeechModel.value,
voice_id: selectedSpeechVoiceId.value || defaultSpeechVoiceId.value,
},
},
agents: {},
} as AiriExtension,
},
}
if (isEditMode.value && props.cardId) {
// Edit mode: update existing card
cardStore.updateCard(props.cardId, rawCard)
cardStore.updateCard(props.cardId, cardWithModules)
}
else {
// Create mode: add new card
cardStore.addCard(rawCard)
cardStore.addCard(cardWithModules)
}
modelValue.value = false // Close this
@@ -133,14 +277,22 @@ function saveCard(card: Card): boolean {
// Initialize card data - load from existing card if in edit mode
function initializeCard(): Card {
if (isEditMode.value && props.cardId) {
const existingCard = cardStore.getCard(props.cardId)
if (existingCard) {
return { ...toRaw(existingCard) }
}
// Extract existing card data if in edit mode
const existingCard = (isEditMode.value && props.cardId) ? cardStore.getCard(props.cardId) : undefined
const airiExt = existingCard?.extensions?.airi as AiriExtension | undefined
// Initialize module selections with fallback logic (handles all cases: create, edit with/without extension)
selectedConsciousnessProvider.value = airiExt?.modules?.consciousness?.provider || consciousnessProvider.value
selectedConsciousnessModel.value = airiExt?.modules?.consciousness?.model || defaultConsciousnessModel.value
selectedSpeechProvider.value = airiExt?.modules?.speech?.provider || speechProvider.value
selectedSpeechModel.value = airiExt?.modules?.speech?.model || defaultSpeechModel.value
selectedSpeechVoiceId.value = airiExt?.modules?.speech?.voice_id || defaultSpeechVoiceId.value
// Return existing card data or defaults
if (existingCard) {
return { ...toRaw(existingCard) }
}
// Default values for create mode
return {
name: t('settings.pages.card.creation.defaults.name'),
nickname: undefined,
@@ -202,6 +354,13 @@ const cardGreetings = computed({
const cardVersion = makeComputed('version')
const cardSystemPrompt = makeComputed('systemPrompt')
const cardPostHistoryInstructions = makeComputed('postHistoryInstructions')
// Helper function to generate placeholder text for default values
function getDefaultPlaceholder(defaultValue: string | undefined): string {
return defaultValue
? `${t('settings.pages.card.creation.use_default')} (${defaultValue})`
: t('settings.pages.card.creation.use_default_not_configured')
}
</script>
<template>
@@ -267,6 +426,87 @@ const cardPostHistoryInstructions = makeComputed('postHistoryInstructions')
<FieldValues v-model="cardGreetings" :label="t('settings.pages.card.creation.greetings')" :description="t('settings.pages.card.creation.fields_info.greetings')" />
</div>
</div>
<!-- Modules -->
<div v-else-if="activeTab === 'modules'" class="tab-content ml-auto mr-auto w-95%">
<p class="mb-3">
{{ t('settings.pages.card.creation.modules_info') }}
</p>
<div :class="['grid', 'grid-cols-1', 'sm:grid-cols-2', 'gap-4', 'ml-auto', 'mr-auto', 'w-90%']">
<!-- Consciousness Provider -->
<div :class="['flex', 'flex-col', 'gap-2']">
<label :class="['flex', 'flex-row', 'items-center', 'gap-2', 'text-sm', 'text-neutral-500', 'dark:text-neutral-400']">
<div i-lucide:brain />
{{ t('settings.pages.card.chat.provider') }}
</label>
<Select
v-model="selectedConsciousnessProvider"
:options="consciousnessProviderOptions"
:placeholder="getDefaultPlaceholder(consciousnessProvider)"
class="w-full"
/>
</div>
<!-- Consciousness Model -->
<div :class="['flex', 'flex-col', 'gap-2']">
<label :class="['flex', 'flex-row', 'items-center', 'gap-2', 'text-sm', 'text-neutral-500', 'dark:text-neutral-400']">
<div i-lucide:ghost />
{{ t('settings.pages.card.consciousness.model') }}
</label>
<Select
v-model="selectedConsciousnessModel"
:options="consciousnessModelOptions"
:placeholder="getDefaultPlaceholder(defaultConsciousnessModel)"
:disabled="!selectedConsciousnessProvider && !consciousnessProvider"
class="w-full"
/>
</div>
<!-- Speech Provider -->
<div :class="['flex', 'flex-col', 'gap-2']">
<label :class="['flex', 'flex-row', 'items-center', 'gap-2', 'text-sm', 'text-neutral-500', 'dark:text-neutral-400']">
<div i-lucide:radio />
{{ t('settings.pages.card.speech.provider') }}
</label>
<Select
v-model="selectedSpeechProvider"
:options="speechProviderOptions"
:placeholder="getDefaultPlaceholder(speechProvider)"
class="w-full"
/>
</div>
<!-- Speech Model -->
<div :class="['flex', 'flex-col', 'gap-2']">
<label :class="['flex', 'flex-row', 'items-center', 'gap-2', 'text-sm', 'text-neutral-500', 'dark:text-neutral-400']">
<div i-lucide:mic />
{{ t('settings.pages.card.speech.model') }}
</label>
<Select
v-model="selectedSpeechModel"
:options="speechModelOptions"
:placeholder="getDefaultPlaceholder(defaultSpeechModel)"
:disabled="!selectedSpeechProvider && !speechProvider"
class="w-full"
/>
</div>
<!-- Speech Voice -->
<div :class="['flex', 'flex-col', 'gap-2']">
<label :class="['flex', 'flex-row', 'items-center', 'gap-2', 'text-sm', 'text-neutral-500', 'dark:text-neutral-400']">
<div i-lucide:music />
{{ t('settings.pages.card.speech.voice') }}
</label>
<Select
v-model="selectedSpeechVoiceId"
:options="speechVoiceOptions"
:placeholder="getDefaultPlaceholder(defaultSpeechVoiceId)"
:disabled="!selectedSpeechProvider && !speechProvider"
class="w-full"
/>
</div>
</div>
</div>
<!-- Settings -->
<div v-else-if="activeTab === 'settings'" class="tab-content ml-auto mr-auto w-95%">
<div class="input-list ml-auto mr-auto w-90% flex flex-row flex-wrap justify-center gap-8">
@@ -4,6 +4,8 @@ import type { AiriCard } from '@proj-airi/stage-ui/stores/modules/airi-card'
import DOMPurify from 'dompurify'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness'
import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech'
import { Button } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import {
@@ -30,8 +32,12 @@ const emit = defineEmits<{
const { t } = useI18n()
const cardStore = useAiriCardStore()
const consciousnessStore = useConsciousnessStore()
const speechStore = useSpeechStore()
const { removeCard } = cardStore
const { activeCardId } = storeToRefs(cardStore)
const { activeProvider: consciousnessProvider, activeModel: defaultConsciousnessModel } = storeToRefs(consciousnessStore)
const { activeSpeechProvider: speechProvider, activeSpeechModel: defaultSpeechModel, activeSpeechVoiceId: defaultVoiceId } = storeToRefs(speechStore)
// Get selected card data
const selectedCard = computed<AiriCard | undefined>(() => {
@@ -44,7 +50,9 @@ const selectedCard = computed<AiriCard | undefined>(() => {
const moduleSettings = computed(() => {
if (!selectedCard.value || !selectedCard.value.extensions?.airi?.modules) {
return {
consciousnessProvider: '',
consciousness: '',
speechProvider: '',
speech: '',
voice: '',
}
@@ -52,7 +60,9 @@ const moduleSettings = computed(() => {
const airiExt = selectedCard.value.extensions.airi.modules
return {
consciousnessProvider: airiExt.consciousness?.provider || '',
consciousness: airiExt.consciousness?.model || '',
speechProvider: airiExt.speech?.provider || '',
speech: airiExt.speech?.model || '',
voice: airiExt.speech?.voice_id || '',
}
@@ -163,6 +173,18 @@ const activeTab = computed({
activeTabId.value = value
},
})
// Helper function to generate placeholder text for default values
function getDefaultPlaceholder(defaultValue: string | undefined): string {
return defaultValue
? `${t('settings.pages.card.creation.use_default')} (${defaultValue})`
: t('settings.pages.card.creation.use_default_not_configured')
}
// Helper function to get display value for module settings
function getModuleDisplayValue(value: string | undefined, defaultValue: string | undefined): string {
return value || getDefaultPlaceholder(defaultValue)
}
</script>
<template>
@@ -275,7 +297,24 @@ const activeTab = computed({
<!-- Modules -->
<div v-if="activeTab === 'modules'">
<div grid="~ cols-1 sm:cols-3" gap-4>
<div grid="~ cols-1 sm:cols-2" gap-4>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-1 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:brain />
{{ t('settings.pages.card.chat.provider') }}
</span>
<div truncate font-medium>
{{ getModuleDisplayValue(moduleSettings.consciousnessProvider, consciousnessProvider) }}
</div>
</div>
<div
flex="~ col"
bg="white/60 dark:black/30"
@@ -289,7 +328,24 @@ const activeTab = computed({
{{ t('settings.pages.card.consciousness.model') }}
</span>
<div truncate font-medium>
{{ moduleSettings.consciousness ?? 'default' }}
{{ getModuleDisplayValue(moduleSettings.consciousness, defaultConsciousnessModel) }}
</div>
</div>
<div
flex="~ col"
bg="white/60 dark:black/30"
gap-1 rounded-lg p-3
border="~ neutral-200/50 dark:neutral-700/30"
transition="all duration-200"
hover="bg-white/80 dark:bg-black/40"
>
<span flex="~ row" items-center gap-2 text-sm text-neutral-500 dark:text-neutral-400>
<div i-lucide:radio />
{{ t('settings.pages.card.speech.provider') }}
</span>
<div truncate font-medium>
{{ getModuleDisplayValue(moduleSettings.speechProvider, speechProvider) }}
</div>
</div>
@@ -306,7 +362,7 @@ const activeTab = computed({
{{ t('settings.pages.card.speech.model') }}
</span>
<div truncate font-medium>
{{ moduleSettings.speech ?? 'default' }}
{{ getModuleDisplayValue(moduleSettings.speech, defaultSpeechModel) }}
</div>
</div>
@@ -323,7 +379,7 @@ const activeTab = computed({
{{ t('settings.pages.card.speech.voice') }}
</span>
<div truncate font-medium>
{{ moduleSettings.voice ?? 'default' }}
{{ getModuleDisplayValue(moduleSettings.voice, defaultVoiceId) }}
</div>
</div>
</div>
@@ -25,9 +25,15 @@ const {
const { t } = useI18n()
const { trackProviderClick } = useAnalytics()
watch(activeProvider, async (provider) => {
watch(activeProvider, async (provider, oldProvider) => {
if (!provider)
return
// Reset model when switching providers (but not on initial load)
if (oldProvider !== undefined && oldProvider !== provider) {
activeModel.value = ''
}
await consciousnessStore.loadModelsForProvider(provider)
}, { immediate: true })
@@ -90,9 +90,17 @@ onMounted(async () => {
syncOpenAICompatibleSettings()
})
watch(activeSpeechProvider, async (newProvider) => {
watch(activeSpeechProvider, async (newProvider, oldProvider) => {
await providersStore.loadModelsForConfiguredProviders()
await speechStore.loadVoicesForProvider(newProvider)
// Reset model and voice when switching providers (but not on initial load)
if (oldProvider !== undefined && oldProvider !== newProvider) {
activeSpeechModel.value = ''
activeSpeechVoiceId.value = ''
activeSpeechVoice.value = undefined
}
syncOpenAICompatibleSettings()
})
@@ -1,3 +1,4 @@
import type { InvocableEventContext } from '@moeru/eventa'
import type { AnalyserBeatEvent, AnalyserWorkletParameters } from '@nekopaw/tempora'
import type { BeatSyncDetectorState } from './types'
@@ -28,9 +29,9 @@ function getBroadcastChannel() {
return _broadcastChannel
}
export function createContext() {
export function createContext(): InvocableEventContext<any, { raw?: any }> {
if (isElectronWindow(window)) {
return createBroadcastChannelContext(getBroadcastChannel()).context
return createBroadcastChannelContext(getBroadcastChannel()).context as InvocableEventContext<any, { raw?: any }>
}
else {
return createWebContext()
@@ -59,9 +59,11 @@ describe('store character', () => {
agents: {},
modules: {
consciousness: {
provider: 'mock-provider',
model: 'mock-model',
},
speech: {
provider: 'mock-speech-provider',
model: 'mock-speech-model',
voice_id: 'alloy',
},
@@ -126,9 +126,11 @@ describe('store character-orchestrator', () => {
agents: {},
modules: {
consciousness: {
provider: 'mock-provider',
model: 'mock-model',
},
speech: {
provider: 'mock-speech-provider',
model: 'mock-speech-model',
voice_id: 'alloy',
},
@@ -14,10 +14,12 @@ import { useSpeechStore } from './speech'
export interface AiriExtension {
modules: {
consciousness: {
provider: string // Example: "openai"
model: string // Example: "gpt-4o"
}
speech: {
provider: string // Example: "elevenlabs"
model: string // Example: "eleven_multilingual_v2"
voice_id: string // Example: "alloy"
@@ -66,10 +68,12 @@ export const useAiriCardStore = defineStore('airi-card', () => {
const speechStore = useSpeechStore()
const {
activeProvider: activeConsciousnessProvider,
activeModel: activeConsciousnessModel,
} = storeToRefs(consciousnessStore)
const {
activeSpeechProvider,
activeSpeechVoiceId,
activeSpeechModel,
} = storeToRefs(speechStore)
@@ -111,9 +115,11 @@ export const useAiriCardStore = defineStore('airi-card', () => {
// Create default modules config
const defaultModules = {
consciousness: {
provider: activeConsciousnessProvider.value,
model: activeConsciousnessModel.value,
},
speech: {
provider: activeSpeechProvider.value,
model: activeSpeechModel.value,
voice_id: activeSpeechVoiceId.value,
},
@@ -131,9 +137,11 @@ export const useAiriCardStore = defineStore('airi-card', () => {
return {
modules: {
consciousness: {
provider: existingExtension.modules?.consciousness?.provider ?? defaultModules.consciousness.provider,
model: existingExtension.modules?.consciousness?.model ?? defaultModules.consciousness.model,
},
speech: {
provider: existingExtension.modules?.speech?.provider ?? defaultModules.speech.provider,
model: existingExtension.modules?.speech?.model ?? defaultModules.speech.model,
voice_id: existingExtension.modules?.speech?.voice_id ?? defaultModules.speech.voice_id,
pitch: existingExtension.modules?.speech?.pitch,
@@ -221,7 +229,10 @@ export const useAiriCardStore = defineStore('airi-card', () => {
if (!extension)
return
activeConsciousnessProvider.value = extension?.modules?.consciousness?.provider
activeConsciousnessModel.value = extension?.modules?.consciousness?.model
activeSpeechProvider.value = extension?.modules?.speech?.provider
activeSpeechModel.value = extension?.modules?.speech?.model
activeSpeechVoiceId.value = extension?.modules?.speech?.voice_id
})
@@ -245,9 +256,11 @@ export const useAiriCardStore = defineStore('airi-card', () => {
currentModels: computed(() => {
return {
consciousness: {
provider: activeConsciousnessProvider.value,
model: activeConsciousnessModel.value,
},
speech: {
provider: activeSpeechProvider.value,
model: activeSpeechModel.value,
voice_id: activeSpeechVoiceId.value,
},
@@ -18,7 +18,7 @@ export const useSpeechStore = defineStore('speech', () => {
// State
const activeSpeechProvider = useLocalStorageManualReset<string>('settings/speech/active-provider', '')
const activeSpeechModel = useLocalStorageManualReset<string>('settings/speech/active-model', 'eleven_multilingual_v2')
const activeSpeechModel = useLocalStorageManualReset<string>('settings/speech/active-model', '')
const activeSpeechVoiceId = useLocalStorageManualReset<string>('settings/speech/voice', '')
const activeSpeechVoice = refManualReset<VoiceInfo | undefined>(undefined)
+16 -37
View File
@@ -495,7 +495,7 @@ importers:
version: 3.8.1
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.14
@@ -886,7 +886,7 @@ importers:
version: 3.8.1
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.14
@@ -1277,7 +1277,7 @@ importers:
version: 3.8.1
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.14
@@ -1962,7 +1962,7 @@ importers:
dependencies:
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.14
@@ -1974,7 +1974,7 @@ importers:
dependencies:
'@moeru/eventa':
specifier: 'catalog:'
version: 1.0.0-alpha.14(electron@40.0.0)
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@xsai/shared-chat':
specifier: 'catalog:'
version: 0.4.0-beta.13
@@ -1983,7 +1983,7 @@ importers:
dependencies:
'@moeru/eventa':
specifier: 'catalog:'
version: 1.0.0-alpha.14(electron@40.0.0)
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@proj-airi/server-shared':
specifier: workspace:*
version: link:../server-shared
@@ -2038,7 +2038,7 @@ importers:
dependencies:
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.14
@@ -2141,7 +2141,7 @@ importers:
dependencies:
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.14
@@ -2269,7 +2269,7 @@ importers:
version: 3.0.2(electron@40.0.0)
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@nekopaw/tempora':
specifier: 'catalog:'
version: 0.4.0-alpha.1
@@ -2293,7 +2293,7 @@ importers:
version: 3.8.1
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@proj-airi/audio':
specifier: workspace:^
version: link:../audio
@@ -3018,7 +3018,7 @@ importers:
version: 1.2.4
'@moeru/eventa':
specifier: ^1.0.0-alpha.11
version: 1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
version: 1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))
'@proj-airi/server-sdk':
specifier: workspace:^
version: link:../../packages/server-sdk
@@ -3033,7 +3033,7 @@ importers:
version: 14.1.0(vue@3.5.26(typescript@5.9.3))
'@wxt-dev/module-vue':
specifier: ^1.0.3
version: 1.0.3(vite@8.0.0-beta.9(@types/node@24.10.9)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.55.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
version: 1.0.3(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.55.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
nanoid:
specifier: ^5.1.6
version: 5.1.6
@@ -6008,20 +6008,6 @@ packages:
web-worker:
optional: true
'@moeru/eventa@1.0.0-alpha.11':
resolution: {integrity: sha512-5i2LYA19E9siAdqhobVm386dqkV4EZw0+uGDEwLiGqfopE/3yX1zdgOkdvEoT+YJf0SIUqDwuMZoYXkvfWV/kA==}
peerDependencies:
electron: '>=30'
h3: 2.0.0-beta.1
web-worker: ^1.5.0
peerDependenciesMeta:
electron:
optional: true
h3:
optional: true
web-worker:
optional: true
'@moeru/eventa@1.0.0-alpha.14':
resolution: {integrity: sha512-VMz75aYTJO85yXj0jj/M8ImakOYr6OUZnV/I5gmseVrMFcqBc8UC6Pk2LTwoMQw/EWaU59557a1lpnUqeyTMQw==}
peerDependencies:
@@ -19704,14 +19690,6 @@ snapshots:
electron: 40.0.0
h3: 2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1))
'@moeru/eventa@1.0.0-alpha.11(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))':
dependencies:
nanoid: 5.1.6
picomatch: 4.0.3
optionalDependencies:
electron: 40.0.0
h3: 2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1))
'@moeru/eventa@1.0.0-alpha.14(electron@39.2.7)':
dependencies:
nanoid: 5.1.6
@@ -19719,12 +19697,13 @@ snapshots:
optionalDependencies:
electron: 39.2.7
'@moeru/eventa@1.0.0-alpha.14(electron@40.0.0)':
'@moeru/eventa@1.0.0-alpha.14(electron@40.0.0)(h3@2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1)))':
dependencies:
nanoid: 5.1.6
picomatch: 4.0.3
optionalDependencies:
electron: 40.0.0
h3: 2.0.1-rc.5(crossws@0.4.3(srvx@0.10.1))
'@moeru/std@0.1.0-beta.1': {}
@@ -23303,9 +23282,9 @@ snapshots:
'@types/filesystem': 0.0.36
'@types/har-format': 1.2.16
'@wxt-dev/module-vue@1.0.3(vite@8.0.0-beta.9(@types/node@24.10.9)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.55.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
'@wxt-dev/module-vue@1.0.3(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.55.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitejs/plugin-vue': 6.0.3(vite@8.0.0-beta.9(@types/node@24.10.9)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
'@vitejs/plugin-vue': 6.0.3(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
wxt: 0.20.13(@types/node@24.10.9)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.55.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- vite