From 5760fbc46f14f6a73ff60b9239fb4dd5fa5d7f8a Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Tue, 11 Mar 2025 01:15:38 +0800 Subject: [PATCH] feat(stage-web): module consciousness setting --- apps/stage-tamagotchi/package.json | 1 + apps/stage-web/locales/en.yml | 62 ++ apps/stage-web/locales/zh-CN.yml | 62 ++ apps/stage-web/package.json | 1 + .../src/components/Menu/IconItem.vue | 4 +- .../src/components/Menu/IconStatusItem.vue | 4 +- .../pages/settings/modules/consciousness.vue | 287 +++++++++- .../src/pages/settings/providers/index.vue | 129 +---- .../src/pages/settings/providers/openai.vue | 115 ++++ .../pages/settings/providers/openrouter.vue | 32 +- .../components/Form/Radio/RadioCardDetail.vue | 161 ++++++ .../components/Form/Radio/RadioCardSimple.vue | 80 +++ packages/stage-ui/src/components/index.ts | 2 + packages/stage-ui/src/stores/index.ts | 1 + packages/stage-ui/src/stores/providers.ts | 541 +++++++++++++++++- .../stage-ui/src/stores/settings/index.ts | 1 + .../stores/settings/modules/consciousness.ts | 112 ++++ .../src/stores/settings/modules/index.ts | 1 + pnpm-lock.yaml | 46 +- 19 files changed, 1446 insertions(+), 196 deletions(-) create mode 100644 apps/stage-web/src/pages/settings/providers/openai.vue create mode 100644 packages/stage-ui/src/components/Form/Radio/RadioCardDetail.vue create mode 100644 packages/stage-ui/src/components/Form/Radio/RadioCardSimple.vue create mode 100644 packages/stage-ui/src/stores/settings/index.ts create mode 100644 packages/stage-ui/src/stores/settings/modules/consciousness.ts create mode 100644 packages/stage-ui/src/stores/settings/modules/index.ts diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index f3cdef286..8bd4f49d3 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -51,6 +51,7 @@ "@vueuse/core": "^12.8.2", "@vueuse/head": "^2.0.0", "@vueuse/shared": "^12.8.2", + "@xsai-ext/providers-cloud": "catalog:", "@xsai-ext/providers-local": "catalog:", "@xsai/generate-speech": "catalog:", "@xsai/generate-text": "catalog:", diff --git a/apps/stage-web/locales/en.yml b/apps/stage-web/locales/en.yml index 243785bf4..149aa11b2 100644 --- a/apps/stage-web/locales/en.yml +++ b/apps/stage-web/locales/en.yml @@ -78,6 +78,28 @@ settings: 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: @@ -115,3 +137,43 @@ 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 diff --git a/apps/stage-web/locales/zh-CN.yml b/apps/stage-web/locales/zh-CN.yml index dbe266996..3f3e399a7 100644 --- a/apps/stage-web/locales/zh-CN.yml +++ b/apps/stage-web/locales/zh-CN.yml @@ -65,6 +65,28 @@ settings: 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: @@ -89,3 +111,43 @@ stage: debug-menu: emotions: 表情 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 diff --git a/apps/stage-web/package.json b/apps/stage-web/package.json index 897318d2a..1f4503ac5 100644 --- a/apps/stage-web/package.json +++ b/apps/stage-web/package.json @@ -48,6 +48,7 @@ "@vueuse/core": "^12.8.2", "@vueuse/head": "^2.0.0", "@vueuse/shared": "^12.8.2", + "@xsai-ext/providers-cloud": "catalog:", "@xsai-ext/providers-local": "catalog:", "@xsai/generate-speech": "catalog:", "@xsai/generate-text": "catalog:", diff --git a/apps/stage-web/src/components/Menu/IconItem.vue b/apps/stage-web/src/components/Menu/IconItem.vue index d30806551..59f3d4d4a 100644 --- a/apps/stage-web/src/components/Menu/IconItem.vue +++ b/apps/stage-web/src/components/Menu/IconItem.vue @@ -59,15 +59,17 @@ defineProps<{ position: absolute; inset: 0; z-index: -1; - width: 0%; + width: 25%; height: 100%; transition: all 0.35s ease-in-out; mask-image: linear-gradient(120deg, white 30%, transparent 50%); + opacity: 0; } .menu-icon-item:hover::before { --at-apply: 'bg-gradient-to-r from-primary-500/20 via-primary-500/10 to-transparent dark:from-primary-400/20 dark:via-primary-400/10 dark:to-transparent'; width: 85%; + opacity: 1; } .menu-icon-item::after { diff --git a/apps/stage-web/src/components/Menu/IconStatusItem.vue b/apps/stage-web/src/components/Menu/IconStatusItem.vue index e50f6ddba..cb68b66fe 100644 --- a/apps/stage-web/src/components/Menu/IconStatusItem.vue +++ b/apps/stage-web/src/components/Menu/IconStatusItem.vue @@ -93,15 +93,17 @@ const props = defineProps<{ position: absolute; inset: 0; z-index: 0; - width: 0%; + width: 25%; height: 100%; transition: all 0.35s ease-in-out; mask-image: linear-gradient(120deg, white 100%); + opacity: 0; } .menu-icon-status-item:hover::before { --at-apply: 'bg-gradient-to-r from-primary-500/20 via-primary-500/10 to-transparent dark:from-primary-400/20 dark:via-primary-400/10 dark:to-transparent'; width: 50%; + opacity: 1; } .menu-icon-status-item-link::after { diff --git a/apps/stage-web/src/pages/settings/modules/consciousness.vue b/apps/stage-web/src/pages/settings/modules/consciousness.vue index 565629d5b..65468350c 100644 --- a/apps/stage-web/src/pages/settings/modules/consciousness.vue +++ b/apps/stage-web/src/pages/settings/modules/consciousness.vue @@ -1,7 +1,34 @@ diff --git a/apps/stage-web/src/pages/settings/providers/index.vue b/apps/stage-web/src/pages/settings/providers/index.vue index b473ae7d3..f5a11eb57 100644 --- a/apps/stage-web/src/pages/settings/providers/index.vue +++ b/apps/stage-web/src/pages/settings/providers/index.vue @@ -1,132 +1,13 @@ diff --git a/packages/stage-ui/src/components/Form/Radio/RadioCardDetail.vue b/packages/stage-ui/src/components/Form/Radio/RadioCardDetail.vue new file mode 100644 index 000000000..07411d610 --- /dev/null +++ b/packages/stage-ui/src/components/Form/Radio/RadioCardDetail.vue @@ -0,0 +1,161 @@ + + + diff --git a/packages/stage-ui/src/components/Form/Radio/RadioCardSimple.vue b/packages/stage-ui/src/components/Form/Radio/RadioCardSimple.vue new file mode 100644 index 000000000..bb719b899 --- /dev/null +++ b/packages/stage-ui/src/components/Form/Radio/RadioCardSimple.vue @@ -0,0 +1,80 @@ + + + diff --git a/packages/stage-ui/src/components/index.ts b/packages/stage-ui/src/components/index.ts index 4f2a5608e..435232764 100644 --- a/packages/stage-ui/src/components/index.ts +++ b/packages/stage-ui/src/components/index.ts @@ -1,5 +1,7 @@ export { default as BasicTextarea } from './BasicTextarea.vue' export { default as Collapsable } from './Collapsable.vue' +export { default as RadioCardDetail } from './Form/Radio/RadioCardDetail.vue' +export { default as RadioCardSimple } from './Form/Radio/RadioCardSimple.vue' export { default as Live2DCanvas } from './Live2D/Canvas.vue' export { default as Live2DModel } from './Live2D/Model.vue' export { default as SceneLive2D } from './Scenes/Live2D.vue' diff --git a/packages/stage-ui/src/stores/index.ts b/packages/stage-ui/src/stores/index.ts index ee50944db..2e8281c98 100644 --- a/packages/stage-ui/src/stores/index.ts +++ b/packages/stage-ui/src/stores/index.ts @@ -3,3 +3,4 @@ export * from './chat' export * from './llm' export * from './providers' export * from './settings' +export * from './settings/index' diff --git a/packages/stage-ui/src/stores/providers.ts b/packages/stage-ui/src/stores/providers.ts index a1ced1740..3353c9c3d 100644 --- a/packages/stage-ui/src/stores/providers.ts +++ b/packages/stage-ui/src/stores/providers.ts @@ -1,6 +1,48 @@ +/* eslint-disable no-case-declarations */ import { useLocalStorage } from '@vueuse/core' +import { + createDeepSeek, + createFireworks, + createMistral, + createMoonshot, + createNovita, + createOpenAI, + createOpenRouter, + createTogetherAI, + createWorkersAI, + createXAI, +} from '@xsai-ext/providers-cloud' +import { createOllama, createUnElevenLabs } from '@xsai-ext/providers-local' +import { listModels } from '@xsai/model' import { defineStore } from 'pinia' -import { computed } from 'vue' +import { computed, ref, watch } from 'vue' +import { useI18n } from 'vue-i18n' + +export interface ProviderMetadata { + id: string + nameKey: string // i18n key for provider name + name: string // Default name (fallback) + descriptionKey: string // i18n key for description + description: string // Default description (fallback) + icon?: string + iconColor?: string + iconImage?: string + baseUrlDefault?: string + createProvider?: (config: Record) => any + modelSelectionType: 'dynamic' | 'manual' | 'hardcoded' + fetchModelsManually?: (config: Record) => Promise + hardcodedModels?: ModelInfo[] +} + +export interface ModelInfo { + id: string + name: string + provider: string + description?: string + capabilities?: string[] + contextLength?: number + deprecated?: boolean +} export const useProvidersStore = defineStore('providers', () => { const providers = useLocalStorage>>('settings/credentials/providers', {}) @@ -8,24 +50,505 @@ export const useProvidersStore = defineStore('providers', () => { const coreControllerProvider = useLocalStorage('settings/credentials/coreControllerProvider', 'openai') const audioSynthesisProvider = useLocalStorage('settings/credentials/audioSynthesisProvider', 'elevenlabs') - const configuredForOpenRouter = computed(() => { - return !!providers.value['openrouter-ai'].baseUrl && !!providers.value['openrouter-ai'].apiKey - }) + // Helper function to fetch OpenRouter models manually + async function fetchOpenRouterModels(config: Record): Promise { + try { + const response = await fetch('https://openrouter.ai/api/v1/models', { + headers: { + 'Authorization': `Bearer ${config.apiKey as string}`, + 'Content-Type': 'application/json', + }, + }) - function configuredFor(provider: string): boolean { - switch (provider) { + if (!response.ok) { + throw new Error(`Failed to fetch OpenRouter models: ${response.statusText}`) + } + + const data = await response.json() + return data.data.map((model: any) => ({ + id: model.id, + name: model.name || model.id, + provider: 'openrouter-ai', + description: model.description || '', + contextLength: model.context_length, + deprecated: false, + })) + } + catch (error) { + console.error('Error fetching OpenRouter models:', error) + throw error + } + } + + // Centralized provider metadata with provider factory functions + const providerMetadata: Record = { + 'openrouter-ai': { + id: 'openrouter-ai', + nameKey: 'providers.openrouter.name', + name: 'OpenRouter', + descriptionKey: 'providers.openrouter.description', + description: 'openrouter.ai', + icon: 'i-lobe-icons:openrouter', + baseUrlDefault: 'https://openrouter.ai/api/v1/', + createProvider: config => createOpenRouter(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'manual', + fetchModelsManually: fetchOpenRouterModels, + }, + 'openai': { + id: 'openai', + nameKey: 'providers.openai.name', + name: 'OpenAI', + descriptionKey: 'providers.openai.description', + description: 'openai.com', + icon: 'i-lobe-icons:openai', + baseUrlDefault: 'https://api.openai.com/v1/', + createProvider: config => createOpenAI(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'ollama-ai': { + id: 'ollama-ai', + nameKey: 'providers.ollama.name', + name: 'Ollama', + descriptionKey: 'providers.ollama.description', + description: 'ollama.com', + icon: 'i-lobe-icons:ollama', + baseUrlDefault: 'http://localhost:11434/api/', + createProvider: config => createOllama(config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'vllm': { + id: 'vllm', + nameKey: 'providers.vllm.name', + name: 'vLLM', + descriptionKey: 'providers.vllm.description', + description: 'vllm.ai', + iconColor: 'i-lobe-icons:vllm-color', + createProvider: config => createOllama(config.baseUrl as string), + modelSelectionType: 'hardcoded', + hardcodedModels: [ + { + id: 'llama-2-7b', + name: 'Llama 2 (7B)', + provider: 'vllm', + description: 'Meta\'s Llama 2 7B parameter model', + contextLength: 4096, + }, + { + id: 'llama-2-13b', + name: 'Llama 2 (13B)', + provider: 'vllm', + description: 'Meta\'s Llama 2 13B parameter model', + contextLength: 4096, + }, + { + id: 'llama-2-70b', + name: 'Llama 2 (70B)', + provider: 'vllm', + description: 'Meta\'s Llama 2 70B parameter model', + contextLength: 4096, + }, + { + id: 'mistral-7b', + name: 'Mistral (7B)', + provider: 'vllm', + description: 'Mistral AI\'s 7B parameter model', + contextLength: 8192, + }, + { + id: 'mixtral-8x7b', + name: 'Mixtral (8x7B)', + provider: 'vllm', + description: 'Mistral AI\'s Mixtral 8x7B MoE model', + contextLength: 32768, + }, + { + id: 'custom', + name: 'Custom Model', + provider: 'vllm', + description: 'Specify a custom model name', + contextLength: 0, + }, + ], + }, + 'perplexity-ai': { + id: 'perplexity-ai', + nameKey: 'providers.perplexity.name', + name: 'Perplexity', + descriptionKey: 'providers.perplexity.description', + description: 'perplexity.ai', + icon: 'i-lobe-icons:perplexity', + baseUrlDefault: 'https://api.perplexity.ai', + createProvider: config => ({ + apiKey: config.apiKey as string, + baseUrl: config.baseUrl as string, + }), + modelSelectionType: 'hardcoded', + hardcodedModels: [ + { + id: 'sonar-small-online', + name: 'Sonar Small (Online)', + provider: 'perplexity-ai', + description: 'Efficient model with online search capabilities', + contextLength: 12000, + }, + { + id: 'sonar-medium-online', + name: 'Sonar Medium (Online)', + provider: 'perplexity-ai', + description: 'Balanced model with online search capabilities', + contextLength: 12000, + }, + { + id: 'sonar-large-online', + name: 'Sonar Large (Online)', + provider: 'perplexity-ai', + description: 'Powerful model with online search capabilities', + contextLength: 12000, + }, + { + id: 'codey-small', + name: 'Codey Small', + provider: 'perplexity-ai', + description: 'Specialized for code generation and understanding', + contextLength: 12000, + }, + { + id: 'codey-large', + name: 'Codey Large', + provider: 'perplexity-ai', + description: 'Advanced code generation and understanding', + contextLength: 12000, + }, + ], + }, + 'elevenlabs': { + id: 'elevenlabs', + nameKey: 'providers.elevenlabs.name', + name: 'ElevenLabs', + descriptionKey: 'providers.elevenlabs.description', + description: 'elevenlabs.io', + icon: 'i-simple-icons:elevenlabs', + baseUrlDefault: 'https://unspeech.hyp3r.link/v1/', + createProvider: config => createUnElevenLabs(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'hardcoded', + hardcodedModels: [ + { + id: 'sonar-small-online', + name: 'Sonar Small (Online)', + provider: 'perplexity-ai', + description: 'Efficient model with online search capabilities', + contextLength: 12000, + }, + ], + }, + 'xai': { + id: 'xai', + nameKey: 'providers.xai.name', + name: 'xAI', + descriptionKey: 'providers.xai.description', + description: 'x.ai', + icon: 'i-lobe-icons:xai', + createProvider: config => createXAI(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'deepseek': { + id: 'deepseek', + nameKey: 'providers.deepseek.name', + name: 'DeepSeek', + descriptionKey: 'providers.deepseek.description', + description: 'deepseek.com', + iconColor: 'i-lobe-icons:deepseek-color', + createProvider: config => createDeepSeek(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'together-ai': { + id: 'together-ai', + nameKey: 'providers.together.name', + name: 'Together.ai', + descriptionKey: 'providers.together.description', + description: 'together.ai', + iconColor: 'i-lobe-icons:together-color', + createProvider: config => createTogetherAI(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'novita-ai': { + id: 'novita-ai', + nameKey: 'providers.novita.name', + name: 'Novita', + descriptionKey: 'providers.novita.description', + description: 'novita.ai', + iconColor: 'i-lobe-icons:novita-color', + createProvider: config => createNovita(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'fireworks-ai': { + id: 'fireworks-ai', + nameKey: 'providers.fireworks.name', + name: 'Fireworks.ai', + descriptionKey: 'providers.fireworks.description', + description: 'fireworks.ai', + icon: 'i-lobe-icons:fireworks', + createProvider: config => createFireworks(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'cloudflare-workers-ai': { + id: 'cloudflare-workers-ai', + nameKey: 'providers.cloudflare.name', + name: 'Cloudflare Workers AI', + descriptionKey: 'providers.cloudflare.description', + description: 'cloudflare.com', + iconColor: 'i-lobe-icons:cloudflare-color', + createProvider: config => createWorkersAI(config.apiKey as string, config.accountId as string), + modelSelectionType: 'dynamic', + }, + 'mistral-ai': { + id: 'mistral-ai', + nameKey: 'providers.mistral.name', + name: 'Mistral', + descriptionKey: 'providers.mistral.description', + description: 'mistral.ai', + iconColor: 'i-lobe-icons:mistral-color', + createProvider: config => createMistral(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + 'moonshot-ai': { + id: 'moonshot-ai', + nameKey: 'providers.moonshot.name', + name: 'Moonshot AI', + descriptionKey: 'providers.moonshot.description', + description: 'moonshot.ai', + icon: 'i-lobe-icons:moonshot', + createProvider: config => createMoonshot(config.apiKey as string, config.baseUrl as string), + modelSelectionType: 'dynamic', + }, + } + + // Configuration validation functions + function validateProvider(providerId: string): boolean { + const config = providers.value[providerId] + if (!config) + return false + + switch (providerId) { case 'openrouter-ai': - return configuredForOpenRouter.value + return !!config.apiKey && !!config.baseUrl + case 'openai': + return !!config.apiKey + case 'ollama-ai': + return !!config.baseUrl + case 'vllm': + return !!config.baseUrl + case 'elevenlabs': + return !!config.apiKey + case 'xai': + return !!config.apiKey + case 'deepseek': + return !!config.apiKey + case 'together-ai': + return !!config.apiKey + case 'novita-ai': + return !!config.apiKey + case 'fireworks-ai': + return !!config.apiKey + case 'cloudflare-workers-ai': + return !!config.apiKey + case 'mistral-ai': + return !!config.apiKey + case 'moonshot-ai': + return !!config.apiKey default: return false } } + // Create computed properties for each provider's configuration status + const configuredProviders = ref>({}) + + // Initialize provider configurations + function initializeProvider(providerId: string) { + if (!providers.value[providerId]) { + const metadata = providerMetadata[providerId] + providers.value[providerId] = { + baseUrl: metadata.baseUrlDefault || '', + } + } + } + + // Initialize all providers + Object.keys(providerMetadata).forEach(initializeProvider) + + // Update configuration status for all providers + function updateConfigurationStatus() { + Object.keys(providerMetadata).forEach((providerId) => { + configuredProviders.value[providerId] = validateProvider(providerId) + }) + } + + // Call initially and watch for changes + updateConfigurationStatus() + watch(providers, updateConfigurationStatus, { deep: true }) + + // Available providers (only those that are properly configured) + const availableProviders = computed(() => { + return Object.keys(providerMetadata).filter(providerId => + configuredProviders.value[providerId], + ) + }) + + // Store available models for each provider + const availableModels = ref>({}) + const isLoadingModels = ref>({}) + const modelLoadError = ref>({}) + + // Check if a provider supports model listing (any type) + function supportsModelListing(providerId: string): boolean { + const metadata = providerMetadata[providerId] + if (!metadata) + return false + + return metadata.modelSelectionType === 'dynamic' + || metadata.modelSelectionType === 'manual' + || (metadata.modelSelectionType === 'hardcoded' && !!metadata.hardcodedModels?.length) + } + + // Function to fetch models for a specific provider + async function fetchModelsForProvider(providerId: string) { + const config = providers.value[providerId] + if (!config) + return [] + + const metadata = providerMetadata[providerId] + if (!metadata) + return [] + + isLoadingModels.value[providerId] = true + modelLoadError.value[providerId] = null + + try { + // Handle different model selection types + switch (metadata.modelSelectionType) { + case 'dynamic': + if (!metadata.createProvider) { + throw new Error(`Provider ${providerId} has no createProvider function`) + } + + const providerInstance = metadata.createProvider(config) + + // Check if provider supports model listing + if (!('model' in providerInstance && typeof providerInstance.model === 'function')) { + throw new Error(`Provider ${providerId} does not support model listing`) + } + + // Get models using the provider's model() function + const models = await listModels(providerInstance.model()) + + // Transform and store the models + availableModels.value[providerId] = models.map(model => ({ + id: model.id, + name: model.id, + provider: providerId, + })) + break + + case 'manual': + if (!metadata.fetchModelsManually) { + throw new Error(`Provider ${providerId} has no fetchModelsManually function`) + } + + // Use custom fetch function + availableModels.value[providerId] = await metadata.fetchModelsManually(config) + break + + case 'hardcoded': + if (!metadata.hardcodedModels) { + throw new Error(`Provider ${providerId} has no hardcodedModels defined`) + } + + // Use hardcoded models + availableModels.value[providerId] = metadata.hardcodedModels + break + } + + return availableModels.value[providerId] + } + catch (error) { + console.error(`Error fetching models for ${providerId}:`, error) + modelLoadError.value[providerId] = error instanceof Error ? error.message : 'Unknown error' + return [] + } + finally { + isLoadingModels.value[providerId] = false + } + } + + // Get models for a specific provider + function getModelsForProvider(providerId: string) { + return availableModels.value[providerId] || [] + } + + // Get all available models across all configured providers + const allAvailableModels = computed(() => { + const models: ModelInfo[] = [] + for (const providerId of availableProviders.value) { + models.push(...(availableModels.value[providerId] || [])) + } + return models + }) + + // Load models for all configured providers + async function loadModelsForConfiguredProviders() { + for (const providerId of availableProviders.value) { + if (supportsModelListing(providerId)) { + await fetchModelsForProvider(providerId) + } + } + } + + // Function to get localized provider metadata + function getProviderMetadata(providerId: string) { + const { t } = useI18n() + const metadata = providerMetadata[providerId] + + if (!metadata) + throw new Error(`Provider metadata for ${providerId} not found`) + + return { + ...metadata, + localizedName: t(metadata.nameKey, metadata.name), + localizedDescription: t(metadata.descriptionKey, metadata.description), + } + } + + // Get all providers metadata (for settings page) + const allProvidersMetadata = computed(() => { + const { t } = useI18n() + + return Object.values(providerMetadata).map(metadata => ({ + ...metadata, + localizedName: t(metadata.nameKey, metadata.name), + localizedDescription: t(metadata.descriptionKey, metadata.description), + configured: configuredProviders.value[metadata.id] || false, + })) + }) + return { providers, coreControllerProvider, audioSynthesisProvider, - configuredForOpenRouter, - configuredFor, + availableProviders, + configuredProviders, + providerMetadata, + getProviderMetadata, + allProvidersMetadata, + initializeProvider, + validateProvider, + availableModels, + isLoadingModels, + modelLoadError, + fetchModelsForProvider, + getModelsForProvider, + allAvailableModels, + loadModelsForConfiguredProviders, + supportsModelListing, } }) diff --git a/packages/stage-ui/src/stores/settings/index.ts b/packages/stage-ui/src/stores/settings/index.ts new file mode 100644 index 000000000..c72a7cce1 --- /dev/null +++ b/packages/stage-ui/src/stores/settings/index.ts @@ -0,0 +1 @@ +export * from './modules' diff --git a/packages/stage-ui/src/stores/settings/modules/consciousness.ts b/packages/stage-ui/src/stores/settings/modules/consciousness.ts new file mode 100644 index 000000000..948fa7b1e --- /dev/null +++ b/packages/stage-ui/src/stores/settings/modules/consciousness.ts @@ -0,0 +1,112 @@ +import { useLocalStorage } from '@vueuse/core' +import { defineStore } from 'pinia' +import { computed, ref, watch } from 'vue' + +import { useProvidersStore } from '../../providers' + +export const useConsciousnessStore = defineStore('consciousness', () => { + const providersStore = useProvidersStore() + + // State + const activeProvider = useLocalStorage('settings/consciousness/active-provider', '') + const activeModel = useLocalStorage('settings/consciousness/active-model', '') + const activeCustomModelName = useLocalStorage('settings/consciousness/active-custom-model', '') + const expandedDescriptions = ref>({}) + const modelSearchQuery = ref('') + + // Computed properties + const availableProvidersMetadata = computed(() => { + return providersStore.availableProviders.map(id => providersStore.getProviderMetadata(id)) + }) + + const supportsModelListing = computed(() => { + return providersStore.supportsModelListing(activeProvider.value) + }) + + const providerModels = computed(() => { + return providersStore.getModelsForProvider(activeProvider.value) + }) + + const isLoadingActiveProviderModels = computed(() => { + return providersStore.isLoadingModels[activeProvider.value] || false + }) + + const activeProviderModelError = computed(() => { + return providersStore.modelLoadError[activeProvider.value] || null + }) + + const filteredModels = computed(() => { + if (!modelSearchQuery.value.trim()) { + return providerModels.value + } + + const query = modelSearchQuery.value.toLowerCase().trim() + return providerModels.value.filter(model => + model.name.toLowerCase().includes(query) + || model.id.toLowerCase().includes(query) + || (model.description && model.description.toLowerCase().includes(query)), + ) + }) + + // Actions + function setActiveProvider(provider: string) { + activeProvider.value = provider + } + + function setActiveModel(model: string) { + activeModel.value = model + } + + function setCustomModelName(name: string) { + activeCustomModelName.value = name + } + + function setModelSearchQuery(query: string) { + modelSearchQuery.value = query + } + + function resetModelSelection() { + activeModel.value = '' + activeCustomModelName.value = '' + expandedDescriptions.value = {} + modelSearchQuery.value = '' + } + + async function loadModelsForProvider(provider: string) { + if (provider && providersStore.supportsModelListing(provider) + && providersStore.getModelsForProvider(provider).length === 0) { + await providersStore.fetchModelsForProvider(provider) + } + } + + // Watch for provider changes and load models + watch(activeProvider, async (newProvider) => { + await loadModelsForProvider(newProvider) + resetModelSelection() + }) + + return { + // State + activeProvider, + activeModel, + customModelName: activeCustomModelName, + expandedDescriptions, + modelSearchQuery, + + // Computed + availableProvidersMetadata, + supportsModelListing, + providerModels, + isLoadingActiveProviderModels, + activeProviderModelError, + filteredModels, + + // Actions + setActiveProvider, + setActiveModel, + setCustomModelName, + setModelSearchQuery, + resetModelSelection, + loadModelsForProvider, + } +}) diff --git a/packages/stage-ui/src/stores/settings/modules/index.ts b/packages/stage-ui/src/stores/settings/modules/index.ts new file mode 100644 index 000000000..48db457b1 --- /dev/null +++ b/packages/stage-ui/src/stores/settings/modules/index.ts @@ -0,0 +1 @@ +export * from './consciousness' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f223af6a..3e2f443e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -228,6 +228,9 @@ importers: '@vueuse/shared': specifier: ^12.8.2 version: 12.8.2(typescript@5.8.2) + '@xsai-ext/providers-cloud': + specifier: 'catalog:' + version: 0.1.2 '@xsai-ext/providers-local': specifier: 'catalog:' version: 0.1.2 @@ -345,7 +348,7 @@ importers: version: 2.3.0 '@intlify/unplugin-vue-i18n': specifier: ^6.0.3 - version: 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.21.0(jiti@2.4.2))(rollup@4.34.9)(typescript@5.8.2)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) + version: 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.21.0(jiti@2.4.2))(rollup@2.79.1)(typescript@5.8.2)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) '@proj-airi/elevenlabs': specifier: workspace:^ version: link:../../packages/elevenlabs @@ -372,10 +375,10 @@ importers: version: 5.2.1(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) '@vue-macros/volar': specifier: ^3.0.0-beta.3 - version: 3.0.0-beta.3(rollup@4.34.9)(typescript@5.8.2)(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) + version: 3.0.0-beta.3(rollup@2.79.1)(typescript@5.8.2)(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) '@vueuse/motion': specifier: ^2.2.6 - version: 2.2.6(magicast@0.3.5)(rollup@4.34.9)(vue@3.5.13(typescript@5.8.2)) + version: 2.2.6(magicast@0.3.5)(rollup@2.79.1)(vue@3.5.13(typescript@5.8.2)) electron: specifier: ^34.3.1 version: 34.3.1 @@ -393,13 +396,13 @@ importers: version: 3.2.0(unocss@66.1.0-beta.3(postcss@8.5.3)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2))) unplugin-auto-import: specifier: ^19.1.1 - version: 19.1.1(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.34.9))(@vueuse/core@12.8.2(typescript@5.8.2)) + version: 19.1.1(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(@vueuse/core@12.8.2(typescript@5.8.2)) unplugin-vue-components: specifier: ^28.4.1 - version: 28.4.1(@babel/parser@7.26.7)(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.34.9))(vue@3.5.13(typescript@5.8.2)) + version: 28.4.1(@babel/parser@7.26.7)(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(vue@3.5.13(typescript@5.8.2)) unplugin-vue-macros: specifier: ^2.14.5 - version: 2.14.5(@vueuse/core@12.8.2(typescript@5.8.2))(esbuild@0.25.0)(rollup@4.34.9)(typescript@5.8.2)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) + version: 2.14.5(@vueuse/core@12.8.2(typescript@5.8.2))(esbuild@0.25.0)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) unplugin-vue-markdown: specifier: ^28.3.1 version: 28.3.1(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)) @@ -408,13 +411,13 @@ importers: version: 0.12.0(vue-router@4.5.0(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) vite-bundle-visualizer: specifier: ^1.2.1 - version: 1.2.1(rollup@4.34.9) + version: 1.2.1(rollup@2.79.1) vite-plugin-pwa: specifier: ^0.21.1 version: 0.21.1(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) vite-plugin-vue-devtools: specifier: ^7.7.2 - version: 7.7.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.34.9))(rollup@4.34.9)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) + version: 7.7.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(rollup@2.79.1)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) vite-plugin-vue-layouts: specifier: ^0.11.0 version: 0.11.0(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-router@4.5.0(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) @@ -517,6 +520,9 @@ importers: '@vueuse/shared': specifier: ^12.8.2 version: 12.8.2(typescript@5.8.2) + '@xsai-ext/providers-cloud': + specifier: 'catalog:' + version: 0.1.2 '@xsai-ext/providers-local': specifier: 'catalog:' version: 0.1.2 @@ -655,7 +661,7 @@ importers: version: 2.3.0 '@intlify/unplugin-vue-i18n': specifier: ^6.0.3 - version: 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.21.0(jiti@2.4.2))(rollup@2.79.1)(typescript@5.8.2)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) + version: 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.21.0(jiti@2.4.2))(rollup@4.34.9)(typescript@5.8.2)(vue-i18n@11.1.2(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) '@proj-airi/drizzle-duckdb-wasm': specifier: workspace:^ version: link:../../packages/drizzle-duckdb-wasm @@ -697,10 +703,10 @@ importers: version: 5.2.1(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) '@vue-macros/volar': specifier: ^3.0.0-beta.3 - version: 3.0.0-beta.3(rollup@2.79.1)(typescript@5.8.2)(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) + version: 3.0.0-beta.3(rollup@4.34.9)(typescript@5.8.2)(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) '@vueuse/motion': specifier: ^2.2.6 - version: 2.2.6(magicast@0.3.5)(rollup@2.79.1)(vue@3.5.13(typescript@5.8.2)) + version: 2.2.6(magicast@0.3.5)(rollup@4.34.9)(vue@3.5.13(typescript@5.8.2)) hfup: specifier: workspace:^ version: link:../../packages/hfup @@ -712,13 +718,13 @@ importers: version: 4.0.1 unplugin-auto-import: specifier: ^19.1.1 - version: 19.1.1(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(@vueuse/core@12.8.2(typescript@5.8.2)) + version: 19.1.1(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.34.9))(@vueuse/core@12.8.2(typescript@5.8.2)) unplugin-vue-components: specifier: ^28.4.1 - version: 28.4.1(@babel/parser@7.26.7)(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(vue@3.5.13(typescript@5.8.2)) + version: 28.4.1(@babel/parser@7.26.7)(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.34.9))(vue@3.5.13(typescript@5.8.2)) unplugin-vue-macros: specifier: ^2.14.5 - version: 2.14.5(@vueuse/core@12.8.2(typescript@5.8.2))(esbuild@0.19.12)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) + version: 2.14.5(@vueuse/core@12.8.2(typescript@5.8.2))(esbuild@0.25.0)(rollup@4.34.9)(typescript@5.8.2)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) unplugin-vue-markdown: specifier: ^28.3.1 version: 28.3.1(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)) @@ -727,13 +733,13 @@ importers: version: 0.12.0(vue-router@4.5.0(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) vite-bundle-visualizer: specifier: ^1.2.1 - version: 1.2.1(rollup@2.79.1) + version: 1.2.1(rollup@4.34.9) vite-plugin-pwa: specifier: ^0.21.1 version: 0.21.1(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0) vite-plugin-vue-devtools: specifier: ^7.7.2 - version: 7.7.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(rollup@2.79.1)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) + version: 7.7.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.34.9))(rollup@4.34.9)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) vite-plugin-vue-layouts: specifier: ^0.11.0 version: 0.11.0(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-router@4.5.0(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)) @@ -23170,9 +23176,9 @@ snapshots: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.34.9) '@vueuse/core': 12.8.2(typescript@5.8.2) - unplugin-combine@1.2.0(esbuild@0.19.12)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)): + unplugin-combine@1.2.0(esbuild@0.25.0)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)): optionalDependencies: - esbuild: 0.19.12 + esbuild: 0.25.0 rollup: 2.79.1 unplugin: 1.16.1 vite: 6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0) @@ -23231,7 +23237,7 @@ snapshots: transitivePeerDependencies: - vue - unplugin-vue-macros@2.14.5(@vueuse/core@12.8.2(typescript@5.8.2))(esbuild@0.19.12)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)): + unplugin-vue-macros@2.14.5(@vueuse/core@12.8.2(typescript@5.8.2))(esbuild@0.25.0)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)): dependencies: '@vue-macros/better-define': 1.11.4(vue@3.5.13(typescript@5.8.2)) '@vue-macros/boolean-prop': 0.5.5(vue@3.5.13(typescript@5.8.2)) @@ -23263,7 +23269,7 @@ snapshots: '@vue-macros/short-vmodel': 1.5.5(vue@3.5.13(typescript@5.8.2)) '@vue-macros/volar': 0.30.15(rollup@2.79.1)(typescript@5.8.2)(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)) unplugin: 1.16.1 - unplugin-combine: 1.2.0(esbuild@0.19.12)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)) + unplugin-combine: 1.2.0(esbuild@0.25.0)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)) unplugin-vue-define-options: 1.5.5(vue@3.5.13(typescript@5.8.2)) vue: 3.5.13(typescript@5.8.2) transitivePeerDependencies: