diff --git a/apps/stage-tamagotchi-electron/src/renderer/main.ts b/apps/stage-tamagotchi-electron/src/renderer/main.ts index 88c8fe621..27c201a92 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/main.ts +++ b/apps/stage-tamagotchi-electron/src/renderer/main.ts @@ -1,12 +1,6 @@ -import type { Plugin } from 'vue' - -import Tres from '@tresjs/core' - -import { autoAnimatePlugin } from '@formkit/auto-animate/vue' -import { MotionPlugin } from '@vueuse/motion' +import { initializeApp } from '@proj-airi/stage-ui/services' import { createPinia } from 'pinia' import { setupLayouts } from 'virtual:generated-layouts' -import { createApp } from 'vue' import { createRouter, createWebHashHistory } from 'vue-router' import { routes } from 'vue-router/auto-routes' @@ -38,12 +32,11 @@ const router = createRouter({ routes: setupLayouts(routes), }) -createApp(App) - .use(MotionPlugin) - // TODO: Fix autoAnimatePlugin type error - .use(autoAnimatePlugin as unknown as Plugin) - .use(router) - .use(pinia) - .use(i18n) - .use(Tres) - .mount('#app') +// Initialize and mount the app using the shared initialization logic +initializeApp(App, { + router, + pinia, + i18n, +}).then((app) => { + app.mount('#app') +}) diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/gaming-factorio.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/gaming-factorio.vue new file mode 100644 index 000000000..e7b6383fe --- /dev/null +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/gaming-factorio.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/gaming-minecraft.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/gaming-minecraft.vue new file mode 100644 index 000000000..2a17631c5 --- /dev/null +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/gaming-minecraft.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/index.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/index.vue index f0037b0f2..652dc548d 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/index.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/index.vue @@ -1,118 +1,12 @@ + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/x.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/x.vue new file mode 100644 index 000000000..f3b856294 --- /dev/null +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/modules/x.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/302-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/302-ai.vue index fc3dcc696..47206f4b8 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/302-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/302-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/anthropic.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/anthropic.vue index b1d86c866..54d14f743 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/anthropic.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/anthropic.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/azure-ai-foundry.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/azure-ai-foundry.vue index f9f1c9f54..d725cbbb1 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/azure-ai-foundry.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/azure-ai-foundry.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/cloudflare-workers-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/cloudflare-workers-ai.vue index 2bcbe94a8..3e67b4234 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/cloudflare-workers-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/cloudflare-workers-ai.vue @@ -9,7 +9,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/deepseek.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/deepseek.vue index 330b739f5..477785b83 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/deepseek.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/deepseek.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/featherless-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/featherless-ai.vue index 9aaa22845..577e0417e 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/featherless-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/featherless-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/fireworks-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/fireworks-ai.vue index 1f7204371..49c0fc9f4 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/fireworks-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/fireworks-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/google-generative-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/google-generative-ai.vue index 64cb22a92..1c2e7b7dd 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/google-generative-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/google-generative-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/lm-studio.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/lm-studio.vue index 3e976e546..f0bfa0f64 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/lm-studio.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/lm-studio.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/mistral-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/mistral-ai.vue index 8eed3a977..21558baa9 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/mistral-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/mistral-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/modelscope.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/modelscope.vue index 7206be6fb..6fa2f1ce6 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/modelscope.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/modelscope.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed, onMounted, watch } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/moonshot-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/moonshot-ai.vue index 0c70c165d..829e426dd 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/moonshot-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/moonshot-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/novita-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/novita-ai.vue index 20b03319e..e073cff49 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/novita-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/novita-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/ollama.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/ollama.vue index f979ff1a7..2ca0baf25 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/ollama.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/ollama.vue @@ -9,7 +9,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldKeyValues } from '@proj-airi/ui' import { storeToRefs } from 'pinia' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-speech.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-speech.vue index 3bfdcf8eb..3cb52b3f9 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-speech.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-speech.vue @@ -12,7 +12,7 @@ import { ProviderSettingsLayout, SpeechPlaygroundOpenAICompatible, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldRange } from '@proj-airi/ui' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-transcription.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-transcription.vue index e64689b47..27b5376e5 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-transcription.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible-audio-transcription.vue @@ -12,7 +12,7 @@ import { ProviderSettingsLayout, TranscriptionPlayground, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useHearingStore } from '@proj-airi/stage-ui/stores/modules/hearing' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldInput } from '@proj-airi/ui' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible.vue index 5e0539a7a..421a3437f 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai-compatible.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai.vue index de816baaa..e96c5bf21 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openrouter-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openrouter-ai.vue index 5d2fc3b97..e0ca7870f 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openrouter-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/openrouter-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/together-ai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/together-ai.vue index 07adae877..4c8285d00 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/together-ai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/together-ai.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'together-ai' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/vllm.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/vllm.vue index 31b7252df..864d173af 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/vllm.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/vllm.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'vllm' diff --git a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/xai.vue b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/xai.vue index 65af9e544..ac070cb28 100644 --- a/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/xai.vue +++ b/apps/stage-tamagotchi-electron/src/renderer/pages/settings/providers/xai.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'xai' diff --git a/apps/stage-tamagotchi/README.md b/apps/stage-tamagotchi/README.md index 482da284a..53da4ee3e 100644 --- a/apps/stage-tamagotchi/README.md +++ b/apps/stage-tamagotchi/README.md @@ -12,17 +12,15 @@ $ pnpm install ### Development +Run from the project root: + ```bash -$ cd /apps/stage $ pnpm dev:tamagotchi ``` -Then open another terminal and run: +This automatically starts both the WebSocket server and the Tamagotchi app. -```bash -$ cd /apps/tamagotchi -$ pnpm dev:tamagotchi -``` +> **Note:** The Tamagotchi app requires the WebSocket server (`@proj-airi/server-runtime`) running on port 6121. The `dev:tamagotchi` command handles this automatically. If you need to run components separately for debugging, use `pnpm dev:server` and `pnpm dev:tamagotchi:app-only`. ### Build diff --git a/apps/stage-tamagotchi/scripts/dev-with-server.ts b/apps/stage-tamagotchi/scripts/dev-with-server.ts new file mode 100644 index 000000000..c5c4435ce --- /dev/null +++ b/apps/stage-tamagotchi/scripts/dev-with-server.ts @@ -0,0 +1,124 @@ +#!/usr/bin/env tsx +/** + * Development script to start both the WebSocket server and Tamagotchi app + * This ensures the server-runtime is running before starting the app + */ + +import type { Buffer } from 'node:buffer' + +import process from 'node:process' + +import { spawn } from 'node:child_process' + +const isWindows = process.platform === 'win32' + +console.log('🚀 Starting AIRI WebSocket server and Tamagotchi app...\n') + +// Start the WebSocket server in the background +console.log('📡 Starting WebSocket server on port 6121...') +const serverProcess = spawn( + 'pnpm', + ['-F', '@proj-airi/server-runtime', 'dev'], + { + stdio: ['pipe', 'pipe', 'pipe'], // Change from 'inherit' to capture output + shell: isWindows, + }, +) + +// Wait for the server to be ready by listening for a ready message +console.log('⏳ Waiting for server to initialize...\n') +const serverReady = new Promise((resolve, reject) => { + // Listen typically outputs a URL when ready, looking for patterns like: + // "Listening on http://localhost:6121" or similar + const readyPatterns = [ + /listening.*:6121/i, + /ready.*:6121/i, + /localhost:6121/i, + /http:\/\/localhost:6121/i, + ] + + const timeout = setTimeout(() => { + console.log('⚠️ Server ready timeout reached, continuing anyway...') + resolve() // Resolve even if we don't see the message to avoid hanging + }, 10000) // 10 second timeout + + // Handle errors from the server process + serverProcess.on('error', (error) => { + console.error('Failed to start server:', error) + clearTimeout(timeout) + reject(error) + }) + + const checkOutput = (data: Buffer) => { + const output = data.toString() + console.log(output) // Still output to console for visibility + + if (readyPatterns.some(pattern => pattern.test(output))) { + clearTimeout(timeout) + resolve() + } + } + + serverProcess.stdout?.on('data', checkOutput) + serverProcess.stderr?.on('data', checkOutput) +}) + +await serverReady + +// Start the Tamagotchi app +console.log('🎮 Starting Tamagotchi app...\n') +const tamagotchiProcess = spawn( + 'pnpm', + ['-F', '@proj-airi/stage-tamagotchi', 'app:dev'], + { + stdio: 'inherit', + shell: isWindows, + }, +) + +let isCleaningUp = false + +// Handle cleanup on exit +function cleanup() { + if (isCleaningUp) + return + isCleaningUp = true + console.log('\n🛑 Shutting down...') + try { + if (serverProcess && !serverProcess.killed) { + serverProcess.kill() + } + } + catch (error) { + console.error('Error killing server process:', error) + } + try { + if (tamagotchiProcess && !tamagotchiProcess.killed) { + tamagotchiProcess.kill() + } + } + catch (error) { + console.error('Error killing tamagotchi process:', error) + } +} + +process.on('SIGINT', cleanup) +process.on('SIGTERM', cleanup) + +// Wait for processes +await Promise.race([ + new Promise((_resolve) => { + serverProcess.on('exit', (code) => { + console.log(`Server process exited with code ${code}`) + cleanup() + process.exit(code || 0) + }) + }), + new Promise((_resolve) => { + tamagotchiProcess.on('exit', (code) => { + console.log(`Tamagotchi process exited with code ${code}`) + cleanup() + process.exit(code || 0) + }) + }), +]) diff --git a/apps/stage-tamagotchi/src/main.ts b/apps/stage-tamagotchi/src/main.ts index 88c8fe621..27c201a92 100644 --- a/apps/stage-tamagotchi/src/main.ts +++ b/apps/stage-tamagotchi/src/main.ts @@ -1,12 +1,6 @@ -import type { Plugin } from 'vue' - -import Tres from '@tresjs/core' - -import { autoAnimatePlugin } from '@formkit/auto-animate/vue' -import { MotionPlugin } from '@vueuse/motion' +import { initializeApp } from '@proj-airi/stage-ui/services' import { createPinia } from 'pinia' import { setupLayouts } from 'virtual:generated-layouts' -import { createApp } from 'vue' import { createRouter, createWebHashHistory } from 'vue-router' import { routes } from 'vue-router/auto-routes' @@ -38,12 +32,11 @@ const router = createRouter({ routes: setupLayouts(routes), }) -createApp(App) - .use(MotionPlugin) - // TODO: Fix autoAnimatePlugin type error - .use(autoAnimatePlugin as unknown as Plugin) - .use(router) - .use(pinia) - .use(i18n) - .use(Tres) - .mount('#app') +// Initialize and mount the app using the shared initialization logic +initializeApp(App, { + router, + pinia, + i18n, +}).then((app) => { + app.mount('#app') +}) diff --git a/apps/stage-tamagotchi/src/pages/settings/modules/gaming-factorio.vue b/apps/stage-tamagotchi/src/pages/settings/modules/gaming-factorio.vue new file mode 100644 index 000000000..e7b6383fe --- /dev/null +++ b/apps/stage-tamagotchi/src/pages/settings/modules/gaming-factorio.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi/src/pages/settings/modules/gaming-minecraft.vue b/apps/stage-tamagotchi/src/pages/settings/modules/gaming-minecraft.vue new file mode 100644 index 000000000..2a17631c5 --- /dev/null +++ b/apps/stage-tamagotchi/src/pages/settings/modules/gaming-minecraft.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi/src/pages/settings/modules/index.vue b/apps/stage-tamagotchi/src/pages/settings/modules/index.vue index f0037b0f2..652dc548d 100644 --- a/apps/stage-tamagotchi/src/pages/settings/modules/index.vue +++ b/apps/stage-tamagotchi/src/pages/settings/modules/index.vue @@ -1,118 +1,12 @@ + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi/src/pages/settings/modules/x.vue b/apps/stage-tamagotchi/src/pages/settings/modules/x.vue new file mode 100644 index 000000000..f3b856294 --- /dev/null +++ b/apps/stage-tamagotchi/src/pages/settings/modules/x.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/302-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/302-ai.vue index fc3dcc696..47206f4b8 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/302-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/302-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/anthropic.vue b/apps/stage-tamagotchi/src/pages/settings/providers/anthropic.vue index b1d86c866..54d14f743 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/anthropic.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/anthropic.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/azure-ai-foundry.vue b/apps/stage-tamagotchi/src/pages/settings/providers/azure-ai-foundry.vue index f9f1c9f54..d725cbbb1 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/azure-ai-foundry.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/azure-ai-foundry.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/cloudflare-workers-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/cloudflare-workers-ai.vue index 2bcbe94a8..3e67b4234 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/cloudflare-workers-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/cloudflare-workers-ai.vue @@ -9,7 +9,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/deepseek.vue b/apps/stage-tamagotchi/src/pages/settings/providers/deepseek.vue index 330b739f5..477785b83 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/deepseek.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/deepseek.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/featherless-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/featherless-ai.vue index 9aaa22845..577e0417e 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/featherless-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/featherless-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/fireworks-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/fireworks-ai.vue index 1f7204371..49c0fc9f4 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/fireworks-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/fireworks-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/google-generative-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/google-generative-ai.vue index 64cb22a92..1c2e7b7dd 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/google-generative-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/google-generative-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/lm-studio.vue b/apps/stage-tamagotchi/src/pages/settings/providers/lm-studio.vue index 3e976e546..f0bfa0f64 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/lm-studio.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/lm-studio.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/mistral-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/mistral-ai.vue index 8eed3a977..21558baa9 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/mistral-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/mistral-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/modelscope.vue b/apps/stage-tamagotchi/src/pages/settings/providers/modelscope.vue index 7206be6fb..6fa2f1ce6 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/modelscope.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/modelscope.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed, onMounted, watch } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/moonshot-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/moonshot-ai.vue index 0c70c165d..829e426dd 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/moonshot-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/moonshot-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/novita-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/novita-ai.vue index 20b03319e..e073cff49 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/novita-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/novita-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/ollama.vue b/apps/stage-tamagotchi/src/pages/settings/providers/ollama.vue index f979ff1a7..2ca0baf25 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/ollama.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/ollama.vue @@ -9,7 +9,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldKeyValues } from '@proj-airi/ui' import { storeToRefs } from 'pinia' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-speech.vue b/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-speech.vue index 3bfdcf8eb..3cb52b3f9 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-speech.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-speech.vue @@ -12,7 +12,7 @@ import { ProviderSettingsLayout, SpeechPlaygroundOpenAICompatible, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldRange } from '@proj-airi/ui' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-transcription.vue b/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-transcription.vue index e64689b47..27b5376e5 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-transcription.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible-audio-transcription.vue @@ -12,7 +12,7 @@ import { ProviderSettingsLayout, TranscriptionPlayground, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useHearingStore } from '@proj-airi/stage-ui/stores/modules/hearing' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldInput } from '@proj-airi/ui' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible.vue b/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible.vue index 5e0539a7a..421a3437f 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/openai-compatible.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/openai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/openai.vue index de816baaa..e96c5bf21 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/openai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/openai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/openrouter-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/openrouter-ai.vue index 5d2fc3b97..e0ca7870f 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/openrouter-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/openrouter-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/together-ai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/together-ai.vue index 07adae877..4c8285d00 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/together-ai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/together-ai.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'together-ai' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/vllm.vue b/apps/stage-tamagotchi/src/pages/settings/providers/vllm.vue index 31b7252df..864d173af 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/vllm.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/vllm.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'vllm' diff --git a/apps/stage-tamagotchi/src/pages/settings/providers/xai.vue b/apps/stage-tamagotchi/src/pages/settings/providers/xai.vue index 65af9e544..ac070cb28 100644 --- a/apps/stage-tamagotchi/src/pages/settings/providers/xai.vue +++ b/apps/stage-tamagotchi/src/pages/settings/providers/xai.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'xai' diff --git a/apps/stage-web/src/main.ts b/apps/stage-web/src/main.ts index 269c9b342..bc0339f12 100644 --- a/apps/stage-web/src/main.ts +++ b/apps/stage-web/src/main.ts @@ -1,14 +1,11 @@ -import type { Plugin } from 'vue' +import type { App as VueApp } from 'vue' import type { Router } from 'vue-router' -import Tres from '@tresjs/core' import NProgress from 'nprogress' -import { autoAnimatePlugin } from '@formkit/auto-animate/vue' -import { MotionPlugin } from '@vueuse/motion' +import { initializeApp } from '@proj-airi/stage-ui/services' import { createPinia } from 'pinia' import { setupLayouts } from 'virtual:generated-layouts' -import { createApp } from 'vue' import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' import { routes } from 'vue-router/auto-routes' @@ -40,26 +37,30 @@ router.afterEach(() => { NProgress.done() }) -router.isReady() - .then(async () => { - if (import.meta.env.SSR) { - return - } - if (import.meta.env.VITE_APP_TARGET_HUGGINGFACE_SPACE) { - return - } +// Custom initialization callback for web-specific post-initialization +async function onInitialized(_app: VueApp, router: Router) { + // Handle PWA registration after router is ready + router.isReady() + .then(async () => { + if (import.meta.env.SSR) { + return + } + if (import.meta.env.VITE_APP_TARGET_HUGGINGFACE_SPACE) { + return + } - const { registerSW } = await import('./modules/pwa') - registerSW({ immediate: true }) - }) - .catch(() => {}) + const { registerSW } = await import('./modules/pwa') + registerSW({ immediate: true }) + }) + .catch(error => console.error('Failed during post-initialization:', error)) +} -createApp(App) - .use(MotionPlugin) - // TODO: Fix autoAnimatePlugin type error - .use(autoAnimatePlugin as unknown as Plugin) - .use(router) - .use(pinia) - .use(i18n) - .use(Tres) - .mount('#app') +// Initialize and mount the app using the shared initialization logic with web-specific options +initializeApp(App, { + router, + pinia, + i18n, + onInitialized, +}).then((app) => { + app.mount('#app') +}) diff --git a/apps/stage-web/src/pages/settings/modules/gaming-factorio.vue b/apps/stage-web/src/pages/settings/modules/gaming-factorio.vue new file mode 100644 index 000000000..e7b6383fe --- /dev/null +++ b/apps/stage-web/src/pages/settings/modules/gaming-factorio.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-web/src/pages/settings/modules/gaming-minecraft.vue b/apps/stage-web/src/pages/settings/modules/gaming-minecraft.vue new file mode 100644 index 000000000..2a17631c5 --- /dev/null +++ b/apps/stage-web/src/pages/settings/modules/gaming-minecraft.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-web/src/pages/settings/modules/index.vue b/apps/stage-web/src/pages/settings/modules/index.vue index 519fb458b..652dc548d 100644 --- a/apps/stage-web/src/pages/settings/modules/index.vue +++ b/apps/stage-web/src/pages/settings/modules/index.vue @@ -1,121 +1,12 @@ + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-web/src/pages/settings/modules/x.vue b/apps/stage-web/src/pages/settings/modules/x.vue new file mode 100644 index 000000000..f3b856294 --- /dev/null +++ b/apps/stage-web/src/pages/settings/modules/x.vue @@ -0,0 +1,15 @@ + + + + + + + +meta: + layout: settings + stageTransition: + name: slide + pageSpecificAvailable: true + diff --git a/apps/stage-web/src/pages/settings/providers/302-ai.vue b/apps/stage-web/src/pages/settings/providers/302-ai.vue index fc3dcc696..47206f4b8 100644 --- a/apps/stage-web/src/pages/settings/providers/302-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/302-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/anthropic.vue b/apps/stage-web/src/pages/settings/providers/anthropic.vue index b1d86c866..54d14f743 100644 --- a/apps/stage-web/src/pages/settings/providers/anthropic.vue +++ b/apps/stage-web/src/pages/settings/providers/anthropic.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/azure-ai-foundry.vue b/apps/stage-web/src/pages/settings/providers/azure-ai-foundry.vue index f9f1c9f54..d725cbbb1 100644 --- a/apps/stage-web/src/pages/settings/providers/azure-ai-foundry.vue +++ b/apps/stage-web/src/pages/settings/providers/azure-ai-foundry.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/cloudflare-workers-ai.vue b/apps/stage-web/src/pages/settings/providers/cloudflare-workers-ai.vue index 2bcbe94a8..3e67b4234 100644 --- a/apps/stage-web/src/pages/settings/providers/cloudflare-workers-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/cloudflare-workers-ai.vue @@ -9,7 +9,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/deepseek.vue b/apps/stage-web/src/pages/settings/providers/deepseek.vue index 330b739f5..477785b83 100644 --- a/apps/stage-web/src/pages/settings/providers/deepseek.vue +++ b/apps/stage-web/src/pages/settings/providers/deepseek.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/featherless-ai.vue b/apps/stage-web/src/pages/settings/providers/featherless-ai.vue index 9aaa22845..577e0417e 100644 --- a/apps/stage-web/src/pages/settings/providers/featherless-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/featherless-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/fireworks-ai.vue b/apps/stage-web/src/pages/settings/providers/fireworks-ai.vue index 1f7204371..49c0fc9f4 100644 --- a/apps/stage-web/src/pages/settings/providers/fireworks-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/fireworks-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/google-generative-ai.vue b/apps/stage-web/src/pages/settings/providers/google-generative-ai.vue index 64cb22a92..1c2e7b7dd 100644 --- a/apps/stage-web/src/pages/settings/providers/google-generative-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/google-generative-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/lm-studio.vue b/apps/stage-web/src/pages/settings/providers/lm-studio.vue index 3e976e546..f0bfa0f64 100644 --- a/apps/stage-web/src/pages/settings/providers/lm-studio.vue +++ b/apps/stage-web/src/pages/settings/providers/lm-studio.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/mistral-ai.vue b/apps/stage-web/src/pages/settings/providers/mistral-ai.vue index 8eed3a977..21558baa9 100644 --- a/apps/stage-web/src/pages/settings/providers/mistral-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/mistral-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/modelscope.vue b/apps/stage-web/src/pages/settings/providers/modelscope.vue index 7206be6fb..6fa2f1ce6 100644 --- a/apps/stage-web/src/pages/settings/providers/modelscope.vue +++ b/apps/stage-web/src/pages/settings/providers/modelscope.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed, onMounted, watch } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/moonshot-ai.vue b/apps/stage-web/src/pages/settings/providers/moonshot-ai.vue index 0c70c165d..829e426dd 100644 --- a/apps/stage-web/src/pages/settings/providers/moonshot-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/moonshot-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/novita-ai.vue b/apps/stage-web/src/pages/settings/providers/novita-ai.vue index 20b03319e..e073cff49 100644 --- a/apps/stage-web/src/pages/settings/providers/novita-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/novita-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/ollama.vue b/apps/stage-web/src/pages/settings/providers/ollama.vue index f979ff1a7..2ca0baf25 100644 --- a/apps/stage-web/src/pages/settings/providers/ollama.vue +++ b/apps/stage-web/src/pages/settings/providers/ollama.vue @@ -9,7 +9,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldKeyValues } from '@proj-airi/ui' import { storeToRefs } from 'pinia' diff --git a/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-speech.vue b/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-speech.vue index 3bfdcf8eb..3cb52b3f9 100644 --- a/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-speech.vue +++ b/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-speech.vue @@ -12,7 +12,7 @@ import { ProviderSettingsLayout, SpeechPlaygroundOpenAICompatible, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldRange } from '@proj-airi/ui' diff --git a/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-transcription.vue b/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-transcription.vue index e64689b47..27b5376e5 100644 --- a/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-transcription.vue +++ b/apps/stage-web/src/pages/settings/providers/openai-compatible-audio-transcription.vue @@ -12,7 +12,7 @@ import { ProviderSettingsLayout, TranscriptionPlayground, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useHearingStore } from '@proj-airi/stage-ui/stores/modules/hearing' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { FieldInput } from '@proj-airi/ui' diff --git a/apps/stage-web/src/pages/settings/providers/openai-compatible.vue b/apps/stage-web/src/pages/settings/providers/openai-compatible.vue index 5e0539a7a..421a3437f 100644 --- a/apps/stage-web/src/pages/settings/providers/openai-compatible.vue +++ b/apps/stage-web/src/pages/settings/providers/openai-compatible.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/openai.vue b/apps/stage-web/src/pages/settings/providers/openai.vue index de816baaa..e96c5bf21 100644 --- a/apps/stage-web/src/pages/settings/providers/openai.vue +++ b/apps/stage-web/src/pages/settings/providers/openai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/openrouter-ai.vue b/apps/stage-web/src/pages/settings/providers/openrouter-ai.vue index 5d2fc3b97..e0ca7870f 100644 --- a/apps/stage-web/src/pages/settings/providers/openrouter-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/openrouter-ai.vue @@ -10,7 +10,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' import { storeToRefs } from 'pinia' import { computed } from 'vue' diff --git a/apps/stage-web/src/pages/settings/providers/together-ai.vue b/apps/stage-web/src/pages/settings/providers/together-ai.vue index 07adae877..4c8285d00 100644 --- a/apps/stage-web/src/pages/settings/providers/together-ai.vue +++ b/apps/stage-web/src/pages/settings/providers/together-ai.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'together-ai' diff --git a/apps/stage-web/src/pages/settings/providers/vllm.vue b/apps/stage-web/src/pages/settings/providers/vllm.vue index 31b7252df..864d173af 100644 --- a/apps/stage-web/src/pages/settings/providers/vllm.vue +++ b/apps/stage-web/src/pages/settings/providers/vllm.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'vllm' diff --git a/apps/stage-web/src/pages/settings/providers/xai.vue b/apps/stage-web/src/pages/settings/providers/xai.vue index 65af9e544..ac070cb28 100644 --- a/apps/stage-web/src/pages/settings/providers/xai.vue +++ b/apps/stage-web/src/pages/settings/providers/xai.vue @@ -8,7 +8,7 @@ import { ProviderSettingsContainer, ProviderSettingsLayout, } from '@proj-airi/stage-ui/components' -import { useProviderValidation } from '@proj-airi/stage-ui/composables/useProviderValidation' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' const providerId = 'xai' diff --git a/package.json b/package.json index 82a0004b7..933442aeb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "dev:docs": "pnpm -rF @proj-airi/docs run dev", "dev:ui": "pnpm -rF @proj-airi/stage-ui run story:dev", "dev:web": "pnpm -rF @proj-airi/stage-web run dev", - "dev:tamagotchi": "pnpm -rF @proj-airi/stage-tamagotchi run app:dev", + "dev:server": "pnpm -rF @proj-airi/server-runtime run dev", + "dev:tamagotchi": "tsx apps/stage-tamagotchi/scripts/dev-with-server.ts", + "dev:tamagotchi:app-only": "pnpm -rF @proj-airi/stage-tamagotchi run app:dev", "dev:tamagotchi-electron": "pnpm -rF @proj-airi/stage-tamagotchi-electron run app:dev", "dev:apps": "pnpm -rF=\"./apps/*\" run --parallel dev", "dev:packages": "pnpm -rF=\"./packages/*\" --parallel run dev", diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml index a88c2c70c..0d55df922 100644 --- a/packages/i18n/src/locales/en/settings.yaml +++ b/packages/i18n/src/locales/en/settings.yaml @@ -1,3 +1,6 @@ +common: + save: Save + animations: stage-transitions: title: Disable Stage Transitions @@ -238,9 +241,31 @@ pages: gaming-factorio: description: Playing Factorio! title: Factorio + enable: Enable Factorio Integration + enable-description: Allow AIRI to interact with Factorio game + server-address: Server Address + server-address-description: The IP address or hostname of the Factorio server + server-address-placeholder: Enter server address (e.g., localhost or 192.168.1.100) + server-port: Server Port + server-port-description: The port number of the Factorio server (default 34197) + username: Username + username-description: Your username in the Factorio server + username-placeholder: Enter your Factorio username + configured: Factorio is properly configured! gaming-minecraft: description: Playing Minecraft! title: Minecraft + enable: Enable Minecraft Integration + enable-description: Allow AIRI to interact with Minecraft game + server-address: Server Address + server-address-description: The IP address or hostname of the Minecraft server + server-address-placeholder: Enter server address (e.g., localhost or play.example.com) + server-port: Server Port + server-port-description: The port number of the Minecraft server (default 25565) + username: Username + username-description: Your username in the Minecraft server + username-placeholder: Enter your Minecraft username + configured: Minecraft is properly configured! hearing: description: Configure how speech recognition works title: Hearing @@ -257,6 +282,12 @@ pages: messaging-discord: description: Chat & voice chat over Discord title: Discord + enable: Enable Discord Integration + enable-description: Allow AIRI to interact through Discord + token: Bot Token + token-description: Your Discord bot token + token-placeholder: Enter your Discord bot token + configured: Discord is properly configured! speech: description: Speech synthesis sections: @@ -305,6 +336,21 @@ pages: x: description: X / Twitter browsing and usage title: X / Twitter + enable: Enable X / Twitter Integration + enable-description: Allow AIRI to interact with X / Twitter + api-key: API Key + api-key-description: Your X / Twitter API key + api-key-placeholder: Enter your X / Twitter API key + api-secret: API Secret + api-secret-description: Your X / Twitter API secret + api-secret-placeholder: Enter your X / Twitter API secret + access-token: Access Token + access-token-description: Your X / Twitter access token + access-token-placeholder: Enter your X / Twitter access token + access-token-secret: Access Token Secret + access-token-secret-description: Your X / Twitter access token secret + access-token-secret-placeholder: Enter your X / Twitter access token secret + configured: X / Twitter is properly configured! mcp-server: description: Connect and manage MCP server and tools title: MCP Server diff --git a/packages/i18n/src/locales/es/settings.yaml b/packages/i18n/src/locales/es/settings.yaml index 7f07a5fa5..1cb3abb18 100644 --- a/packages/i18n/src/locales/es/settings.yaml +++ b/packages/i18n/src/locales/es/settings.yaml @@ -1,3 +1,6 @@ +common: + save: Guardar + animations: stage-transitions: title: Desactivar Transiciones del Escenario @@ -190,9 +193,31 @@ pages: gaming-factorio: description: ¡Jugando Factorio! title: Factorio + enable: Habilitar integración con Factorio + enable-description: Permitir que AIRI interactúe con el juego Factorio + server-address: Dirección del servidor + server-address-description: La dirección IP o nombre de host del servidor de Factorio + server-address-placeholder: Ingrese la dirección del servidor (por ejemplo, localhost o 192.168.1.100) + server-port: Puerto del servidor + server-port-description: El número de puerto del servidor de Factorio (por defecto 34197) + username: Nombre de usuario + username-description: Su nombre de usuario en el servidor de Factorio + username-placeholder: Ingrese su nombre de usuario de Factorio + configured: ¡Factorio está configurado correctamente! gaming-minecraft: description: ¡Jugando Minecraft! title: Minecraft + enable: Habilitar integración con Minecraft + enable-description: Permitir que AIRI interactúe con el juego Minecraft + server-address: Dirección del servidor + server-address-description: La dirección IP o nombre de host del servidor de Minecraft + server-address-placeholder: Ingrese la dirección del servidor (por ejemplo, localhost o play.ejemplo.com) + server-port: Puerto del servidor + server-port-description: El número de puerto del servidor de Minecraft (por defecto 25565) + username: Nombre de usuario + username-description: Su nombre de usuario en el servidor de Minecraft + username-placeholder: Ingrese su nombre de usuario de Minecraft + configured: ¡Minecraft está configurado correctamente! hearing: description: Configurar cómo funciona el reconocimiento de voz title: Audición @@ -205,6 +230,12 @@ pages: messaging-discord: description: Chat y chat de voz por Discord title: Discord + enable: Habilitar integración con Discord + enable-description: Permitir que AIRI interactúe a través de Discord + token: Token del bot + token-description: Su token de bot de Discord + token-placeholder: Ingrese su token de bot de Discord + configured: ¡Discord está configurado correctamente! speech: description: Síntesis de voz sections: @@ -246,6 +277,21 @@ pages: x: description: Navegación y uso de X / Twitter title: X / Twitter + enable: Habilitar integración con X / Twitter + enable-description: Permitir que AIRI interactúe con X / Twitter + api-key: Clave API + api-key-description: Su clave API de X / Twitter + api-key-placeholder: Ingrese su clave API de X / Twitter + api-secret: Secreto API + api-secret-description: Su secreto API de X / Twitter + api-secret-placeholder: Ingrese su secreto API de X / Twitter + access-token: Token de acceso + access-token-description: Su token de acceso de X / Twitter + access-token-placeholder: Ingrese su token de acceso de X / Twitter + access-token-secret: Secreto de token de acceso + access-token-secret-description: Su secreto de token de acceso de X / Twitter + access-token-secret-placeholder: Ingrese su secreto de token de acceso de X / Twitter + configured: ¡X / Twitter está configurado correctamente! mcp-server: description: Conectar y gestionar servidor MCP y herramientas title: Servidor MCP diff --git a/packages/i18n/src/locales/fr/settings.yaml b/packages/i18n/src/locales/fr/settings.yaml index 5a436b597..c17c99ed9 100644 --- a/packages/i18n/src/locales/fr/settings.yaml +++ b/packages/i18n/src/locales/fr/settings.yaml @@ -1,3 +1,6 @@ +common: + save: Sauvegarder + animations: stage-transitions: title: Désactiver les transitions de scène @@ -192,9 +195,31 @@ pages: gaming-factorio: description: Jouer à Factorio ! title: Factorio + enable: Activer l'intégration Factorio + enable-description: Autoriser AIRI à interagir avec le jeu Factorio + server-address: Adresse du serveur + server-address-description: L'adresse IP ou le nom d'hôte du serveur Factorio + server-address-placeholder: Entrez l'adresse du serveur (ex. localhost ou 192.168.1.100) + server-port: Port du serveur + server-port-description: Le numéro de port du serveur Factorio (par défaut 34197) + username: Nom d'utilisateur + username-description: Votre nom d'utilisateur sur le serveur Factorio + username-placeholder: Entrez votre nom d'utilisateur Factorio + configured: Factorio est correctement configuré ! gaming-minecraft: description: Jouer à Minecraft ! title: Minecraft + enable: Activer l'intégration Minecraft + enable-description: Autoriser AIRI à interagir avec le jeu Minecraft + server-address: Adresse du serveur + server-address-description: L'adresse IP ou le nom d'hôte du serveur Minecraft + server-address-placeholder: Entrez l'adresse du serveur (ex. localhost ou play.exemple.com) + server-port: Port du serveur + server-port-description: Le numéro de port du serveur Minecraft (par défaut 25565) + username: Nom d'utilisateur + username-description: Votre nom d'utilisateur sur le serveur Minecraft + username-placeholder: Entrez votre nom d'utilisateur Minecraft + configured: Minecraft est correctement configuré ! hearing: description: Configurer la reconnaissance vocale title: Audition @@ -207,6 +232,12 @@ pages: messaging-discord: description: Chat et chat vocal via Discord title: Discord + enable: Activer l'intégration Discord + enable-description: Autoriser AIRI à interagir via Discord + token: Jeton du bot + token-description: Votre jeton de bot Discord + token-placeholder: Entrez votre jeton de bot Discord + configured: Discord est correctement configuré ! speech: description: Synthèse vocale sections: @@ -249,6 +280,21 @@ pages: x: description: Navigation et utilisation X / Twitter title: X / Twitter + enable: Activer l'intégration X / Twitter + enable-description: Autoriser AIRI à interagir avec X / Twitter + api-key: Clé API + api-key-description: Votre clé API X / Twitter + api-key-placeholder: Entrez votre clé API X / Twitter + api-secret: Secret API + api-secret-description: Votre secret API X / Twitter + api-secret-placeholder: Entrez votre secret API X / Twitter + access-token: Jeton d'accès + access-token-description: Votre jeton d'accès X / Twitter + access-token-placeholder: Entrez votre jeton d'accès X / Twitter + access-token-secret: Secret du jeton d'accès + access-token-secret-description: Votre secret de jeton d'accès X / Twitter + access-token-secret-placeholder: Entrez votre secret de jeton d'accès X / Twitter + configured: X / Twitter est correctement configuré ! mcp-server: description: Connecter et gérer le serveur MCP et ses outils title: Serveur MCP diff --git a/packages/i18n/src/locales/ru/settings.yaml b/packages/i18n/src/locales/ru/settings.yaml index c43f330c1..df8372d71 100644 --- a/packages/i18n/src/locales/ru/settings.yaml +++ b/packages/i18n/src/locales/ru/settings.yaml @@ -1,3 +1,6 @@ +common: + save: Сохранить + animations: stage-transitions: title: Отключить переходы между сценами @@ -183,9 +186,31 @@ pages: gaming-factorio: description: Играем в Factorio! title: Factorio + enable: Включить интеграцию Factorio + enable-description: Разрешить AIRI взаимодействовать с игрой Factorio + server-address: Адрес сервера + server-address-description: IP-адрес или имя хоста сервера Factorio + server-address-placeholder: Введите адрес сервера (например, localhost или 192.168.1.100) + server-port: Порт сервера + server-port-description: Номер порта сервера Factorio (по умолчанию 34197) + username: Имя пользователя + username-description: Ваше имя пользователя на сервере Factorio + username-placeholder: Введите ваше имя пользователя Factorio + configured: Factorio настроен правильно! gaming-minecraft: description: Играем в Minecraft! title: Minecraft + enable: Включить интеграцию Minecraft + enable-description: Разрешить AIRI взаимодействовать с игрой Minecraft + server-address: Адрес сервера + server-address-description: IP-адрес или имя хоста сервера Minecraft + server-address-placeholder: Введите адрес сервера (например, localhost или play.example.com) + server-port: Порт сервера + server-port-description: Номер порта сервера Minecraft (по умолчанию 25565) + username: Имя пользователя + username-description: Ваше имя пользователя на сервере Minecraft + username-placeholder: Введите ваше имя пользователя Minecraft + configured: Minecraft настроен правильно! hearing: description: Настройка распознавания речи title: Слух @@ -198,6 +223,12 @@ pages: messaging-discord: description: Чат и голосовой чат через Discord title: Discord + enable: Включить интеграцию Discord + enable-description: Разрешить AIRI взаимодействовать через Discord + token: Токен бота + token-description: Ваш токен бота Discord + token-placeholder: Введите токен бота Discord + configured: Discord настроен правильно! speech: description: Синтез речи sections: @@ -239,6 +270,21 @@ pages: x: description: Просмотр и использование X / Twitter title: X / Twitter + enable: Включить интеграцию X / Twitter + enable-description: Разрешить AIRI взаимодействовать с X / Twitter + api-key: API-ключ + api-key-description: Ваш API-ключ X / Twitter + api-key-placeholder: Введите ваш API-ключ X / Twitter + api-secret: API-секрет + api-secret-description: Ваш API-секрет X / Twitter + api-secret-placeholder: Введите ваш API-секрет X / Twitter + access-token: Токен доступа + access-token-description: Ваш токен доступа X / Twitter + access-token-placeholder: Введите ваш токен доступа X / Twitter + access-token-secret: Секрет токена доступа + access-token-secret-description: Ваш секрет токена доступа X / Twitter + access-token-secret-placeholder: Введите ваш секрет токена доступа X / Twitter + configured: X / Twitter настроен правильно! mcp-server: description: Подключение и управление MCP-сервером и инструментами title: MCP-сервер diff --git a/packages/i18n/src/locales/vi/settings.yaml b/packages/i18n/src/locales/vi/settings.yaml index b109ab63f..6ff143a2b 100644 --- a/packages/i18n/src/locales/vi/settings.yaml +++ b/packages/i18n/src/locales/vi/settings.yaml @@ -1,3 +1,6 @@ +common: + save: Lưu + animations: stage-transitions: title: Tắt hiệu ứng chuyển cảnh @@ -189,9 +192,31 @@ pages: gaming-factorio: description: Chơi Factorio! title: Factorio + enable: Bật tích hợp Factorio + enable-description: Cho phép AIRI tương tác với trò chơi Factorio + server-address: Địa chỉ máy chủ + server-address-description: Địa chỉ IP hoặc tên máy chủ của máy chủ Factorio + server-address-placeholder: Nhập địa chỉ máy chủ (ví dụ localhost hoặc 192.168.1.100) + server-port: Cổng máy chủ + server-port-description: Số cổng của máy chủ Factorio (mặc định 34197) + username: Tên người dùng + username-description: Tên người dùng của bạn trên máy chủ Factorio + username-placeholder: Nhập tên người dùng Factorio của bạn + configured: Đã cấu hình Factorio thành công! gaming-minecraft: description: Chơi Minecraft! title: Minecraft + enable: Bật tích hợp Minecraft + enable-description: Cho phép AIRI tương tác với trò chơi Minecraft + server-address: Địa chỉ máy chủ + server-address-description: Địa chỉ IP hoặc tên máy chủ của máy chủ Minecraft + server-address-placeholder: Nhập địa chỉ máy chủ (ví dụ localhost hoặc play.example.com) + server-port: Cổng máy chủ + server-port-description: Số cổng của máy chủ Minecraft (mặc định 25565) + username: Tên người dùng + username-description: Tên người dùng của bạn trên máy chủ Minecraft + username-placeholder: Nhập tên người dùng Minecraft của bạn + configured: Đã cấu hình Minecraft thành công! hearing: description: Cấu hình nhận diện giọng nói title: Nghe @@ -202,8 +227,14 @@ pages: description: Cài đặt và quản lý trí nhớ ngắn hạn title: Trí nhớ ngắn hạn messaging-discord: - description: Chat & Gọi thoại qua Discord + description: Trò chuyện & gọi thoại qua Discord title: Discord + enable: Bật tích hợp Discord + enable-description: Cho phép AIRI tương tác qua Discord + token: Mã token của bot + token-description: Mã token bot Discord của bạn + token-placeholder: Nhập mã token bot Discord của bạn + configured: Đã cấu hình Discord thành công! speech: description: Tổng hợp giọng nói sections: @@ -245,6 +276,21 @@ pages: x: description: Sử Dụng Twitter title: X / Twitter + enable: Bật tích hợp X / Twitter + enable-description: Cho phép AIRI tương tác với X / Twitter + api-key: Khóa API + api-key-description: Khóa API X / Twitter của bạn + api-key-placeholder: Nhập khóa API X / Twitter của bạn + api-secret: Bí mật API + api-secret-description: Bí mật API X / Twitter của bạn + api-secret-placeholder: Nhập bí mật API X / Twitter của bạn + access-token: Mã truy cập + access-token-description: Mã truy cập X / Twitter của bạn + access-token-placeholder: Nhập mã truy cập X / Twitter của bạn + access-token-secret: Bí mật mã truy cập + access-token-secret-description: Bí mật mã truy cập X / Twitter của bạn + access-token-secret-placeholder: Nhập bí mật mã truy cập X / Twitter của bạn + configured: Đã cấu hình X / Twitter thành công! mcp-server: description: Kết nối, quản lý máy chủ MCP và công cụ title: Máy chủ MCP diff --git a/packages/i18n/src/locales/zh-Hans/settings.yaml b/packages/i18n/src/locales/zh-Hans/settings.yaml index ba4a06f12..89399df24 100644 --- a/packages/i18n/src/locales/zh-Hans/settings.yaml +++ b/packages/i18n/src/locales/zh-Hans/settings.yaml @@ -1,3 +1,6 @@ +common: + save: 保存 + animations: stage-transitions: title: 是否开启舞台动画 @@ -182,9 +185,31 @@ pages: gaming-factorio: description: 一起玩 Factorio! title: 异星工厂 Factorio + enable: 启用异星工厂集成 + enable-description: 允许AIRI与异星工厂游戏互动 + server-address: 服务器地址 + server-address-description: 异星工厂服务器的IP地址或主机名 + server-address-placeholder: 输入服务器地址(例如:localhost或192.168.1.100) + server-port: 服务器端口 + server-port-description: 异星工厂服务器的端口号(默认 34197) + username: 用户名 + username-description: 您在异星工厂服务器中的用户名 + username-placeholder: 输入您的异星工厂用户名 + configured: 异星工厂已正确配置! gaming-minecraft: description: 一起玩 Minecraft! title: 我的世界 Minecraft + enable: 启用我的世界集成 + enable-description: 允许AIRI与我的世界游戏互动 + server-address: 服务器地址 + server-address-description: 我的世界服务器的IP地址或主机名 + server-address-placeholder: 输入服务器地址(例如:localhost或play.example.com) + server-port: 服务器端口 + server-port-description: 我的世界服务器的端口号(默认 25565) + username: 用户名 + username-description: 您在我的世界服务器中的用户名 + username-placeholder: 输入您的我的世界用户名 + configured: 我的世界已正确配置! hearing: description: 语音识别 title: 听觉 @@ -201,6 +226,12 @@ pages: messaging-discord: description: Discord 发消息和语音聊天 title: Discord + enable: 启用Discord集成 + enable-description: 允许AIRI通过Discord进行互动 + token: 机器人令牌 + token-description: 您的Discord机器人令牌 + token-placeholder: 输入您的Discord机器人令牌 + configured: Discord已正确配置! speech: description: 语音合成 sections: @@ -247,6 +278,21 @@ pages: x: description: X / Twitter 的浏览和使用 title: X / Twitter + enable: 启用X / Twitter集成 + enable-description: 允许AIRI与X / Twitter互动 + api-key: API密钥 + api-key-description: 您的X / Twitter API密钥 + api-key-placeholder: 输入您的X / Twitter API密钥 + api-secret: API密钥密文 + api-secret-description: 您的X / Twitter API密钥密文 + api-secret-placeholder: 输入您的X / Twitter API密钥密文 + access-token: 访问令牌 + access-token-description: 您的X / Twitter访问令牌 + access-token-placeholder: 输入您的X / Twitter访问令牌 + access-token-secret: 访问令牌密文 + access-token-secret-description: 您的X / Twitter访问令牌密文 + access-token-secret-placeholder: 输入您的X / Twitter访问令牌密文 + configured: X / Twitter已正确配置! mcp-server: description: 连接和管理 MCP 服务器及工具 title: MCP 服务器 diff --git a/packages/i18n/src/locales/zh-Hant/settings.yaml b/packages/i18n/src/locales/zh-Hant/settings.yaml index 24653c789..202eba00c 100644 --- a/packages/i18n/src/locales/zh-Hant/settings.yaml +++ b/packages/i18n/src/locales/zh-Hant/settings.yaml @@ -1,3 +1,6 @@ +common: + save: 儲存 + animations: stage-transitions: title: 是否開啟舞台動畫 @@ -174,9 +177,31 @@ pages: gaming-factorio: description: 一起玩 Factorio! title: 異星工廠 Factorio + enable: 啟用異星工廠集成 + enable-description: 允許 AIRI 與異星工廠遊戲互動 + server-address: 伺服器地址 + server-address-description: 異星工廠伺服器的 IP 地址或主機名稱 + server-address-placeholder: 輸入伺服器地址(例如:localhost 或 192.168.1.100) + server-port: 伺服器端口 + server-port-description: 異星工廠伺服器的端口號(預設 34197) + username: 使用者名稱 + username-description: 您在異星工廠伺服器中的使用者名稱 + username-placeholder: 輸入您的異星工廠使用者名稱 + configured: 異星工廠已正確設定! gaming-minecraft: description: 一起玩 Minecraft! title: 我的世界 Minecraft + enable: 啟用我的世界集成 + enable-description: 允許 AIRI 與我的世界遊戲互動 + server-address: 伺服器地址 + server-address-description: 我的世界伺服器的 IP 地址或主機名稱 + server-address-placeholder: 輸入伺服器地址(例如:localhost 或 play.example.com) + server-port: 伺服器端口 + server-port-description: 我的世界伺服器的端口號(預設 25565) + username: 使用者名稱 + username-description: 您在我的世界伺服器中的使用者名稱 + username-placeholder: 輸入您的我的世界使用者名稱 + configured: 我的世界已正確設定! hearing: description: 語音辨識 title: 聽覺 @@ -189,6 +214,12 @@ pages: messaging-discord: description: Discord 訊息與語音聊天 title: Discord + enable: 啟用 Discord 集成 + enable-description: 允許 AIRI 透過 Discord 進行互動 + token: 機器人權杖 + token-description: 您的 Discord 機器人权杖 + token-placeholder: 輸入您的 Discord 機器人权杖 + configured: Discord 已正確設定! speech: description: 語音合成 sections: @@ -230,6 +261,21 @@ pages: x: description: X / Twitter 的瀏覽與使用 title: X / Twitter + enable: 啟用 X / Twitter 集成 + enable-description: 允許 AIRI 與 X / Twitter 互動 + api-key: API 金鑰 + api-key-description: 您的 X / Twitter API 金鑰 + api-key-placeholder: 輸入您的 X / Twitter API 金鑰 + api-secret: API 金鑰密文 + api-secret-description: 您的 X / Twitter API 金鑰密文 + api-secret-placeholder: 輸入您的 X / Twitter API 金鑰密文 + access-token: 存取權杖 + access-token-description: 您的 X / Twitter 存取權杖 + access-token-placeholder: 輸入您的 X / Twitter 存取權杖 + access-token-secret: 存取權杖密文 + access-token-secret-description: 您的 X / Twitter 存取權杖密文 + access-token-secret-placeholder: 輸入您的 X / Twitter 存取權杖密文 + configured: X / Twitter 已正確設定! mcp-server: description: 連線與管理 MCP 伺服器及工具 title: MCP 伺服器 diff --git a/packages/stage-ui/package.json b/packages/stage-ui/package.json index d4a93dca6..516ffb098 100644 --- a/packages/stage-ui/package.json +++ b/packages/stage-ui/package.json @@ -26,6 +26,8 @@ "./constants": "./src/constants/index.ts", "./libs/*": "./src/libs/*.ts", "./libs": "./src/libs/index.ts", + "./services/*": "./src/services/*.ts", + "./services": "./src/services/index.ts", "./stores/*": "./src/stores/*.ts", "./stores": "./src/stores/index.ts", "./utils": "./src/utils/index.ts" diff --git a/packages/stage-ui/src/components/index.ts b/packages/stage-ui/src/components/index.ts index ff588f571..c7f788f05 100644 --- a/packages/stage-ui/src/components/index.ts +++ b/packages/stage-ui/src/components/index.ts @@ -5,6 +5,7 @@ export * from './layouts' export * from './markdown' export * from './menu' export * from './misc' +export * from './modules' export * from './physics' export * from './scenarios' export * from './widgets' diff --git a/packages/stage-ui/src/components/modules/GamingFactorio.vue b/packages/stage-ui/src/components/modules/GamingFactorio.vue new file mode 100644 index 000000000..31fa297fc --- /dev/null +++ b/packages/stage-ui/src/components/modules/GamingFactorio.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/packages/stage-ui/src/components/modules/GamingMinecraft.vue b/packages/stage-ui/src/components/modules/GamingMinecraft.vue new file mode 100644 index 000000000..1179d5206 --- /dev/null +++ b/packages/stage-ui/src/components/modules/GamingMinecraft.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/packages/stage-ui/src/components/modules/GamingModuleSettings.vue b/packages/stage-ui/src/components/modules/GamingModuleSettings.vue new file mode 100644 index 000000000..3caa2d242 --- /dev/null +++ b/packages/stage-ui/src/components/modules/GamingModuleSettings.vue @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + {{ t(`${i18nKeyPrefix}.configured`) }} + + + diff --git a/packages/stage-ui/src/components/modules/MessagingDiscord.vue b/packages/stage-ui/src/components/modules/MessagingDiscord.vue new file mode 100644 index 000000000..9a3e93888 --- /dev/null +++ b/packages/stage-ui/src/components/modules/MessagingDiscord.vue @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + {{ t('settings.pages.modules.messaging-discord.configured') }} + + + diff --git a/packages/stage-ui/src/components/modules/X.vue b/packages/stage-ui/src/components/modules/X.vue new file mode 100644 index 000000000..81e906322 --- /dev/null +++ b/packages/stage-ui/src/components/modules/X.vue @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + {{ t('settings.pages.modules.x.configured') }} + + + diff --git a/packages/stage-ui/src/components/modules/index.ts b/packages/stage-ui/src/components/modules/index.ts new file mode 100644 index 000000000..bfa555d2f --- /dev/null +++ b/packages/stage-ui/src/components/modules/index.ts @@ -0,0 +1,4 @@ +export { default as GamingFactorio } from './GamingFactorio.vue' +export { default as GamingMinecraft } from './GamingMinecraft.vue' +export { default as MessagingDiscord } from './MessagingDiscord.vue' +export { default as X } from './X.vue' diff --git a/packages/stage-ui/src/composables/use-modules-list.ts b/packages/stage-ui/src/composables/use-modules-list.ts new file mode 100644 index 000000000..a47571a36 --- /dev/null +++ b/packages/stage-ui/src/composables/use-modules-list.ts @@ -0,0 +1,169 @@ +import { computed } from 'vue' +import { useI18n } from 'vue-i18n' + +import { useBeatSyncStore } from '../stores/beat-sync' +import { useConsciousnessStore } from '../stores/modules/consciousness' +import { useDiscordStore } from '../stores/modules/discord' +import { useFactorioStore } from '../stores/modules/gaming-factorio' +import { useMinecraftStore } from '../stores/modules/gaming-minecraft' +import { useSpeechStore } from '../stores/modules/speech' +import { useTwitterStore } from '../stores/modules/twitter' + +export interface Module { + id: string + name: string + description: string + icon?: string + iconColor?: string + iconImage?: string + to: string + configured: boolean + category: string +} + +export function useModulesList() { + const { t } = useI18n() + + // Initialize stores + const consciousnessStore = useConsciousnessStore() + const speechStore = useSpeechStore() + const discordStore = useDiscordStore() + const twitterStore = useTwitterStore() + const minecraftStore = useMinecraftStore() + const factorioStore = useFactorioStore() + const beatSyncStore = useBeatSyncStore() + + const modulesList = computed(() => [ + { + id: 'consciousness', + name: t('settings.pages.modules.consciousness.title'), + description: t('settings.pages.modules.consciousness.description'), + icon: 'i-solar:ghost-bold-duotone', + to: '/settings/modules/consciousness', + configured: consciousnessStore.configured, + category: 'essential', + }, + { + id: 'speech', + name: t('settings.pages.modules.speech.title'), + description: t('settings.pages.modules.speech.description'), + icon: 'i-solar:user-speak-rounded-bold-duotone', + to: '/settings/modules/speech', + configured: speechStore.configured, + category: 'essential', + }, + { + id: 'hearing', + name: t('settings.pages.modules.hearing.title'), + description: t('settings.pages.modules.hearing.description'), + icon: 'i-solar:microphone-3-bold-duotone', + to: '', + configured: false, + category: 'essential', + }, + { + id: 'vision', + name: t('settings.pages.modules.vision.title'), + description: t('settings.pages.modules.vision.description'), + icon: 'i-solar:eye-closed-bold-duotone', + to: '', + configured: false, + category: 'essential', + }, + { + id: 'memory-short-term', + name: t('settings.pages.modules.memory-short-term.title'), + description: t('settings.pages.modules.memory-short-term.description'), + icon: 'i-solar:bookmark-bold-duotone', + to: '/settings/modules/memory-short-term', + configured: false, + category: 'essential', + }, + { + id: 'memory-long-term', + name: t('settings.pages.modules.memory-long-term.title'), + description: t('settings.pages.modules.memory-long-term.description'), + icon: 'i-solar:book-bookmark-bold-duotone', + to: '/settings/modules/memory-long-term', + configured: false, + category: 'essential', + }, + { + id: 'messaging-discord', + name: t('settings.pages.modules.messaging-discord.title'), + description: t('settings.pages.modules.messaging-discord.description'), + icon: 'i-simple-icons:discord', + to: '/settings/modules/messaging-discord', + configured: discordStore.configured, + category: 'messaging', + }, + { + id: 'x', + name: t('settings.pages.modules.x.title'), + description: t('settings.pages.modules.x.description'), + icon: 'i-simple-icons:x', + to: '/settings/modules/x', + configured: twitterStore.configured, + category: 'messaging', + }, + { + id: 'gaming-minecraft', + name: t('settings.pages.modules.gaming-minecraft.title'), + description: t('settings.pages.modules.gaming-minecraft.description'), + iconColor: 'i-vscode-icons:file-type-minecraft', + to: '/settings/modules/gaming-minecraft', + configured: minecraftStore.configured, + category: 'gaming', + }, + { + id: 'gaming-factorio', + name: t('settings.pages.modules.gaming-factorio.title'), + description: t('settings.pages.modules.gaming-factorio.description'), + to: '/settings/modules/gaming-factorio', + configured: factorioStore.configured, + category: 'gaming', + }, + { + id: 'mcp-server', + name: t('settings.pages.modules.mcp-server.title'), + description: t('settings.pages.modules.mcp-server.description'), + icon: 'i-solar:server-bold-duotone', + to: '/settings/modules/mcp', + configured: false, + category: 'essential', + }, + { + id: 'beat-sync', + name: t('settings.pages.modules.beat_sync.title'), + description: t('settings.pages.modules.beat_sync.description'), + icon: 'i-solar:music-notes-bold-duotone', + to: '/settings/modules/beat-sync', + configured: beatSyncStore.isActive, + category: 'essential', + }, + ]) + + const categorizedModules = computed(() => { + return modulesList.value.reduce((categories, module) => { + const { category } = module + if (!categories[category]) { + categories[category] = [] + } + categories[category].push(module) + return categories + }, {} as Record) + }) + + // Define category display names + const categoryNames = computed(() => ({ + essential: t('settings.pages.modules.categories.essential'), + messaging: t('settings.pages.modules.categories.messaging'), + gaming: t('settings.pages.modules.categories.gaming'), + })) + + return { + modulesList, + categorizedModules, + categoryNames, + } +} diff --git a/packages/stage-ui/src/composables/use-number-string.ts b/packages/stage-ui/src/composables/use-number-string.ts new file mode 100644 index 000000000..97fb0e3ab --- /dev/null +++ b/packages/stage-ui/src/composables/use-number-string.ts @@ -0,0 +1,20 @@ +import type { Ref } from 'vue' + +// packages/stage-ui/src/composables/useNumberString.ts +import { computed } from 'vue' + +export function useNumberString(numberRef: Ref) { + return computed({ + get: () => numberRef.value?.toString() ?? '', + set: (value) => { + if (value === '') { + numberRef.value = null + return + } + const numValue = Number.parseInt(value, 10) + if (!Number.isNaN(numValue)) { + numberRef.value = numValue + } + }, + }) +} diff --git a/packages/stage-ui/src/composables/useProviderValidation.ts b/packages/stage-ui/src/composables/use-provider-validation.ts similarity index 100% rename from packages/stage-ui/src/composables/useProviderValidation.ts rename to packages/stage-ui/src/composables/use-provider-validation.ts diff --git a/packages/stage-ui/src/services/app-initializer.ts b/packages/stage-ui/src/services/app-initializer.ts new file mode 100644 index 000000000..981ea1e1f --- /dev/null +++ b/packages/stage-ui/src/services/app-initializer.ts @@ -0,0 +1,66 @@ +import type { Pinia } from 'pinia' +import type { App, Component, Plugin } from 'vue' +import type { I18n } from 'vue-i18n' +import type { Router } from 'vue-router' + +import Tres from '@tresjs/core' + +import { autoAnimatePlugin } from '@formkit/auto-animate/vue' +import { MotionPlugin } from '@vueuse/motion' +import { createApp } from 'vue' + +import { settingsBroadcaster } from './settings-broadcaster' + +interface AppInitializationOptions { + /** Router for the app - must be provided */ + router: Router + /** Pinia instance for state management */ + pinia: Pinia + /** I18n instance for internationalization - using any to maintain compatibility with various locale configurations */ + i18n: I18n + /** Optional callback to run before mounting the app */ + beforeMountCallback?: () => void | Promise + /** Callback to run after the app is initialized but before mounting */ + onInitialized?: (app: App, router: Router) => void | Promise +} + +/** + * Initializes a Vue application with common plugins and services. + * This function creates a Vue app instance with all the standard plugins + * and services used across AIRI applications. + * + * Note: The router, pinia, and i18n instances must be provided by the caller + * as they are application-specific and cannot be created in the shared module. + */ +export async function initializeApp( + AppComponent: Component, + options: AppInitializationOptions, +): Promise { + const app = createApp(AppComponent) + .use(MotionPlugin) + .use(autoAnimatePlugin as unknown as Plugin) // TODO: Fix autoAnimatePlugin type error + .use(options.router) + .use(options.pinia) + .use(options.i18n) + .use(Tres) + + // Run any custom initialization logic + if (options.onInitialized) { + await options.onInitialized(app, options.router) + } + + // Initialize settings broadcaster connection + try { + await settingsBroadcaster.connect() + } + catch (error) { + console.error('Failed to connect settings broadcaster:', error) + } + + // Run any custom before-mount logic + if (options.beforeMountCallback) { + await options.beforeMountCallback() + } + + return app +} diff --git a/packages/stage-ui/src/services/index.ts b/packages/stage-ui/src/services/index.ts new file mode 100644 index 000000000..4c47f9201 --- /dev/null +++ b/packages/stage-ui/src/services/index.ts @@ -0,0 +1,2 @@ +export { initializeApp } from './app-initializer' +export { settingsBroadcaster } from './settings-broadcaster' diff --git a/packages/stage-ui/src/services/settings-broadcaster.ts b/packages/stage-ui/src/services/settings-broadcaster.ts new file mode 100644 index 000000000..b22fdab5a --- /dev/null +++ b/packages/stage-ui/src/services/settings-broadcaster.ts @@ -0,0 +1,92 @@ +import { Client } from '@proj-airi/server-sdk' + +class SettingsBroadcaster { + private client: Client | null = null + private connected = false + private pendingConfigurations: Array<{ moduleName: string, config: Record }> = [] + + constructor() { + this.initClient() + } + + private initClient() { + try { + this.client = new Client({ + name: 'settings-broadcaster', + url: import.meta.env.VITE_AIRI_WS_URL || 'ws://localhost:6121/ws', + possibleEvents: [ + 'ui:configure', + 'module:authenticated', + ], + }) + + this.client.onEvent('module:authenticated', (event) => { + if (event.data.authenticated) { + this.connected = true + // Send any pending configurations + this.sendPendingConfigurations() + } + }) + } + catch (error) { + // In a real implementation, you might want to use a proper error handling mechanism + // For now, we'll just log the error to comply with linting rules + console.error('Failed to initialize SettingsBroadcaster client:', error) + } + } + + private sendPendingConfigurations() { + if (!this.client) { + console.warn('SettingsBroadcaster client is not initialized. Pending configurations were not sent.') + return + } + for (const { moduleName, config } of this.pendingConfigurations) { + this.client.send({ + type: 'ui:configure' as const, + data: { + moduleName, + config, + }, + }) + } + this.pendingConfigurations = [] + } + + public sendConfiguration(moduleName: string, config: Record): void { + if (!this.client) { + console.warn('SettingsBroadcaster client is not initialized. Configuration was not sent.', { moduleName, config }) + return + } + + const configData = { + type: 'ui:configure' as const, + data: { + moduleName, + config, + }, + } + + if (this.connected) { + this.client.send(configData) + } + else { + // Queue the configuration to send when connected + this.pendingConfigurations.push({ moduleName, config }) + } + } + + public async connect(): Promise { + if (this.client) { + await this.client.connect() + } + } + + public disconnect(): void { + if (this.client) { + this.client.close() + } + } +} + +// Create a singleton instance +export const settingsBroadcaster = new SettingsBroadcaster() diff --git a/packages/stage-ui/src/stores/modules/discord.ts b/packages/stage-ui/src/stores/modules/discord.ts new file mode 100644 index 000000000..0d2341801 --- /dev/null +++ b/packages/stage-ui/src/stores/modules/discord.ts @@ -0,0 +1,30 @@ +import { useLocalStorage } from '@vueuse/core' +import { defineStore } from 'pinia' +import { computed } from 'vue' + +import { settingsBroadcaster } from '../../services/settings-broadcaster' + +export const useDiscordStore = defineStore('discord', () => { + const enabled = useLocalStorage('settings/discord/enabled', false) + const token = useLocalStorage('settings/discord/token', '') + + function saveSettings() { + // Data is automatically saved to localStorage via useLocalStorage + // Also broadcast configuration to backend + settingsBroadcaster.sendConfiguration('discord', { + token: token.value, + enabled: enabled.value, + }) + } + + const configured = computed(() => { + return !!token.value.trim() + }) + + return { + enabled, + token, + configured, + saveSettings, + } +}) diff --git a/packages/stage-ui/src/stores/modules/gaming-factorio.ts b/packages/stage-ui/src/stores/modules/gaming-factorio.ts new file mode 100644 index 000000000..4312337b1 --- /dev/null +++ b/packages/stage-ui/src/stores/modules/gaming-factorio.ts @@ -0,0 +1,3 @@ +import { createGamingModuleStore } from './gaming-module-factory' + +export const useFactorioStore = createGamingModuleStore('factorio', 34197) diff --git a/packages/stage-ui/src/stores/modules/gaming-minecraft.ts b/packages/stage-ui/src/stores/modules/gaming-minecraft.ts new file mode 100644 index 000000000..c0839e718 --- /dev/null +++ b/packages/stage-ui/src/stores/modules/gaming-minecraft.ts @@ -0,0 +1,3 @@ +import { createGamingModuleStore } from './gaming-module-factory' + +export const useMinecraftStore = createGamingModuleStore('minecraft', 25565) diff --git a/packages/stage-ui/src/stores/modules/gaming-module-factory.ts b/packages/stage-ui/src/stores/modules/gaming-module-factory.ts new file mode 100644 index 000000000..1c8ebfd53 --- /dev/null +++ b/packages/stage-ui/src/stores/modules/gaming-module-factory.ts @@ -0,0 +1,29 @@ +import { useLocalStorage } from '@vueuse/core' +import { defineStore } from 'pinia' +import { computed } from 'vue' + +import { settingsBroadcaster } from '../../services/settings-broadcaster' + +export function createGamingModuleStore(moduleName: string, defaultPort: number) { + return defineStore(moduleName, () => { + const enabled = useLocalStorage(`settings/${moduleName}/enabled`, false) + const serverAddress = useLocalStorage(`settings/${moduleName}/server-address`, '') + const serverPort = useLocalStorage(`settings/${moduleName}/server-port`, defaultPort) + const username = useLocalStorage(`settings/${moduleName}/username`, '') + + function saveSettings() { + settingsBroadcaster.sendConfiguration(moduleName, { + enabled: enabled.value, + serverAddress: serverAddress.value, + serverPort: serverPort.value, + username: username.value, + }) + } + + const configured = computed(() => { + return !!(serverAddress.value.trim() && username.value.trim() && serverPort.value !== null) + }) + + return { enabled, serverAddress, serverPort, username, configured, saveSettings } + }) +} diff --git a/packages/stage-ui/src/stores/modules/index.ts b/packages/stage-ui/src/stores/modules/index.ts index 2734eaf5d..c2d55cbf5 100644 --- a/packages/stage-ui/src/stores/modules/index.ts +++ b/packages/stage-ui/src/stores/modules/index.ts @@ -1,4 +1,8 @@ export * from './airi-card' export * from './consciousness' +export * from './discord' +export * from './gaming-factorio' +export * from './gaming-minecraft' export * from './hearing' export * from './speech' +export * from './twitter' diff --git a/packages/stage-ui/src/stores/modules/twitter.ts b/packages/stage-ui/src/stores/modules/twitter.ts new file mode 100644 index 000000000..da2ae3b89 --- /dev/null +++ b/packages/stage-ui/src/stores/modules/twitter.ts @@ -0,0 +1,39 @@ +import { useLocalStorage } from '@vueuse/core' +import { defineStore } from 'pinia' +import { computed } from 'vue' + +import { settingsBroadcaster } from '../../services/settings-broadcaster' + +export const useTwitterStore = defineStore('twitter', () => { + const enabled = useLocalStorage('settings/twitter/enabled', false) + const apiKey = useLocalStorage('settings/twitter/api-key', '') + const apiSecret = useLocalStorage('settings/twitter/api-secret', '') + const accessToken = useLocalStorage('settings/twitter/access-token', '') + const accessTokenSecret = useLocalStorage('settings/twitter/access-token-secret', '') + + function saveSettings() { + // Data is automatically saved to localStorage via useLocalStorage + // Also broadcast configuration to backend + settingsBroadcaster.sendConfiguration('twitter', { + enabled: enabled.value, + apiKey: apiKey.value, + apiSecret: apiSecret.value, + accessToken: accessToken.value, + accessTokenSecret: accessTokenSecret.value, + }) + } + + const configured = computed(() => { + return !!(apiKey.value.trim() && apiSecret.value.trim() && accessToken.value.trim() && accessTokenSecret.value.trim()) + }) + + return { + enabled, + apiKey, + apiSecret, + accessToken, + accessTokenSecret, + configured, + saveSettings, + } +}) diff --git a/services/discord-bot/src/adapters/airi-adapter.ts b/services/discord-bot/src/adapters/airi-adapter.ts new file mode 100644 index 000000000..b61b2a9d3 --- /dev/null +++ b/services/discord-bot/src/adapters/airi-adapter.ts @@ -0,0 +1,188 @@ +import type { Interaction } from 'discord.js' + +import { env } from 'node:process' + +import { useLogg } from '@guiiai/logg' +import { Client as AiriClient } from '@proj-airi/server-sdk' +import { Client, Events, GatewayIntentBits } from 'discord.js' + +import { handlePing, registerCommands, VoiceManager } from '../bots/discord/commands' + +const log = useLogg('DiscordAdapter') + +export interface DiscordAdapterConfig { + discordToken?: string + airiToken?: string + airiUrl?: string +} + +// Define Discord configuration type +interface DiscordConfig { + token?: string + enabled?: boolean +} + +// Type guard to safely validate the configuration object +function isDiscordConfig(config: unknown): config is DiscordConfig { + if (typeof config !== 'object' || config === null) + return false + const c = config as Record + return (typeof c.token === 'string' || typeof c.token === 'undefined') + && (typeof c.enabled === 'boolean' || typeof c.enabled === 'undefined') +} + +export class DiscordAdapter { + private airiClient: AiriClient + private discordClient: Client + private discordToken: string + private voiceManager: VoiceManager + private isReconnecting = false + + constructor(config: DiscordAdapterConfig) { + this.discordToken = config.discordToken || env.DISCORD_TOKEN || '' + + // Initialize Discord client + this.discordClient = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates], + }) + + // Initialize AIRI client + this.airiClient = new AiriClient({ + name: 'discord-bot', + possibleEvents: [ + 'input:text', + 'input:text:voice', + 'input:voice', + 'ui:configure', + ], + token: config.airiToken, + url: config.airiUrl, + }) + + this.voiceManager = new VoiceManager(this.discordClient, this.airiClient) + + this.setupEventHandlers() + } + + private setupEventHandlers(): void { + // Handle configuration from UI + this.airiClient.onEvent('ui:configure', async (event) => { + if (event.data.moduleName === 'discord') { + if (this.isReconnecting) { + log.warn('A reconnect is already in progress, skipping this configuration event.') + return + } + this.isReconnecting = true + try { + log.log('Received Discord configuration:', event.data.config) + + if (isDiscordConfig(event.data.config)) { + const config = event.data.config as DiscordConfig + const { token, enabled } = config + + if (enabled === false) { + if (this.discordClient.isReady) { + log.log('Disabling Discord bot as per configuration...') + await this.discordClient.destroy() + } + return + } + + // If enabled, but no token is provided, stop the bot if it's running. + if (!token) { + log.warn('Discord bot enabled, but no token provided. Stopping bot.') + if (this.discordClient.isReady) { + await this.discordClient.destroy() + } + return + } + + // Connect or reconnect if token changed or client is not ready. + if (this.discordToken !== token || !this.discordClient.isReady) { + this.discordToken = token + if (this.discordClient.isReady) { + log.log('Reconnecting Discord client with new token...') + await this.discordClient.destroy() + } + log.log('Connecting Discord client...') + await this.discordClient.login(this.discordToken) + log.log('Discord client connected.') + } + } + else { + log.warn('Invalid Discord configuration received, skipping...') + } + } + catch (error) { + log.withError(error as Error).error('Failed to apply Discord configuration.') + } + finally { + this.isReconnecting = false + } + } + }) + + // Handle input from AIRI system + this.airiClient.onEvent('input:text', async (event) => { + log.log('Received input from AIRI system:', event.data.text) + // Process Discord-related commands + // For now, we'll just log the input + }) + + // Set up Discord event handlers + this.discordClient.once(Events.ClientReady, (readyClient) => { + log.log(`Discord bot ready! User: ${readyClient.user.tag}`) + }) + + this.discordClient.on(Events.InteractionCreate, async (interaction: Interaction) => { + if (!interaction.isChatInputCommand()) + return + + log.log('Interaction received:', interaction) + + switch (interaction.commandName) { + case 'ping': + await handlePing(interaction) + break + case 'summon': + await this.voiceManager.handleJoinChannelCommand(interaction) + break + } + }) + } + + async start(): Promise { + log.log('Starting Discord adapter...') + + try { + // Register commands + await registerCommands() + + // Log in to Discord if token is available + if (this.discordToken) { + await this.discordClient.login(this.discordToken) + log.log('Discord adapter started successfully') + } + else { + log.warn('Discord token not provided. Waiting for configuration from UI.') + } + } + catch (error) { + log.withError(error).error('Failed to start Discord adapter') + throw error + } + } + + async stop(): Promise { + log.log('Stopping Discord adapter...') + try { + await this.discordClient.destroy() + this.airiClient.close() + log.log('Discord adapter stopped') + } + catch (error) { + log.withError(error).error('Error stopping Discord adapter') + throw error + } + } +} diff --git a/services/discord-bot/src/index.ts b/services/discord-bot/src/index.ts index 67aba0fb9..671f5406e 100644 --- a/services/discord-bot/src/index.ts +++ b/services/discord-bot/src/index.ts @@ -1,10 +1,8 @@ -import { env } from 'node:process' +import process, { env } from 'node:process' import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg' -import { Client as AiriClient } from '@proj-airi/server-sdk' -import { Client, Events, GatewayIntentBits } from 'discord.js' -import { handlePing, registerCommands, VoiceManager } from './bots/discord/commands' +import { DiscordAdapter } from './adapters/airi-adapter' setGlobalFormat(Format.Pretty) setGlobalLogLevel(LogLevel.Log) @@ -12,38 +10,29 @@ const log = useLogg('Bot').useGlobalConfig() // Create a new client instance async function main() { - // await WhisperLargeV3Pipeline.getInstance() - - const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] }) - const airiClient = new AiriClient({ name: 'discord-bot', possibleEvents: ['input:text', 'input:text:voice', 'input:voice'], token: 'abcd' }) - const voiceManager = new VoiceManager(client, airiClient) - - // When the client is ready, run this code (only once). - // The distinction between `client: Client` and `readyClient: Client` is important for TypeScript developers. - // It makes some properties non-nullable. - client.once(Events.ClientReady, (readyClient) => { - log.withField('identity', readyClient.user.tag).log(`Ready!`) + // Create Discord adapter with configuration + const adapter = new DiscordAdapter({ + discordToken: env.DISCORD_TOKEN || '', // Fallback to env, but will be updated via WebSocket + airiToken: env.AIRI_TOKEN || 'abcd', + airiUrl: env.AIRI_URL || 'ws://localhost:6121/ws', }) - client.on(Events.InteractionCreate, async (interaction) => { - if (!interaction.isChatInputCommand()) - return + await adapter.start() - log.log(interaction) + // Set up process shutdown hooks + async function gracefulShutdown(signal: string) { + log.log(`Received ${signal}, shutting down...`) + await adapter.stop() + process.exit(0) + } - switch (interaction.commandName) { - case 'ping': - await handlePing(interaction) - break - case 'summon': - await voiceManager.handleJoinChannelCommand(interaction) - break - } + process.on('SIGINT', async () => { + await gracefulShutdown('SIGINT') }) - await registerCommands() - // Log in to Discord with your client's token - await client.login(env.DISCORD_TOKEN) + process.on('SIGTERM', async () => { + await gracefulShutdown('SIGTERM') + }) } main().catch(err => log.withError(err).error('An error occurred')) diff --git a/services/twitter-services/src/adapters/adapter.ts b/services/twitter-services/src/adapters/adapter.ts index 19ceea40f..9e21fb88f 100644 --- a/services/twitter-services/src/adapters/adapter.ts +++ b/services/twitter-services/src/adapters/adapter.ts @@ -9,19 +9,19 @@ export function useAdapter() { const adapters: { airi?: AiriAdapter, mcp?: MCPAdapter } = {} async function initAdapters(config: Config, ctx: Context): Promise<{ airi?: AiriAdapter, mcp?: MCPAdapter }> { - // if (config.adapters.airi?.enabled) { - // logger.main.log('Starting Airi adapter...') - // const { AiriAdapter } = await import('./adapters/airi-adapter') + if (config.adapters.airi?.enabled) { + logger.main.log('Starting Airi adapter...') + const { AiriAdapter } = await import('./airi-adapter') - // adapters.airi = new AiriAdapter(twitterService, { - // url: config.adapters.airi.url, - // token: config.adapters.airi.token, - // credentials: {}, - // }) + adapters.airi = new AiriAdapter(ctx, { + url: config.adapters.airi.url, + token: config.adapters.airi.token, + credentials: config.credentials || {}, + }) - // await adapters.airi.start() - // logger.main.log('Airi adapter started') - // } + await adapters.airi.start() + logger.main.log('Airi adapter started') + } if (config.adapters.mcp?.enabled) { logger.main.log('Starting MCP adapter...') diff --git a/services/twitter-services/src/adapters/airi-adapter.ts b/services/twitter-services/src/adapters/airi-adapter.ts index c4099e97e..23118ff72 100644 --- a/services/twitter-services/src/adapters/airi-adapter.ts +++ b/services/twitter-services/src/adapters/airi-adapter.ts @@ -1,5 +1,371 @@ /** * Airi Adapter - * Adapts the Twitter service as an Airi module + * Adapts the X service as an Airi module */ -export class AiriAdapter {} +import type { Context } from '../core/browser/context' +import type { Tweet } from '../core/services/tweet' +import type { TwitterServices } from '../types/services' + +import * as fs from 'node:fs/promises' + +import { Client } from '@proj-airi/server-sdk' + +import { getDefaultConfig } from '../config/types' +import { initBrowser, useContext, useSessionFileAsync } from '../core/browser/context' +import { useTwitterTimelineServices } from '../core/services/timeline' +import { useTwitterTweetServices } from '../core/services/tweet' +import { useTwitterUserServices } from '../core/services/user' +import { parseTwitterCommand } from '../parsers/command-parser' +import { logger } from '../utils/logger' + +export interface AiriAdapterConfig { + url?: string + token?: string + credentials: { + apiKey?: string + apiSecret?: string + accessToken?: string + accessTokenSecret?: string + } +} + +export interface XConfig { + apiKey?: string + apiSecret?: string + accessToken?: string + accessTokenSecret?: string +} + +export class AiriAdapter { + private client: Client + private ctx: Context + private twitterServices: TwitterServices + private config: AiriAdapterConfig + + constructor(ctx: Context, config: AiriAdapterConfig) { + this.ctx = ctx + this.config = config + this.client = new Client({ + name: 'x', + url: config.url || 'ws://localhost:6121/ws', + token: config.token, + possibleEvents: [ + 'module:authenticate', + 'module:authenticated', + 'module:announce', + 'ui:configure', + 'input:text', + ], + }) + + this.twitterServices = { + timeline: useTwitterTimelineServices(this.ctx), + tweet: useTwitterTweetServices(this.ctx), + user: useTwitterUserServices(this.ctx), + } + + // Set up event handlers + this.setupEventHandlers() + } + + private setupEventHandlers(): void { + // Handle configuration from UI + this.client.onEvent('ui:configure', async (event) => { + if (event.data && event.data.moduleName === 'x' && event.data.config && isXConfig(event.data.config)) { + logger.main.log('Received configuration from UI for X module') + logger.main.log('Twitter configuration received:', event.data.config) + + // Check if any credentials have changed + const newCreds = event.data.config + const credKeys: (keyof AiriAdapterConfig['credentials'])[] = ['apiKey', 'apiSecret', 'accessToken', 'accessTokenSecret'] + const credsChanged = credKeys.some(key => key in newCreds && newCreds[key] !== this.config.credentials[key]) + + if (credsChanged) { + // Update the configuration with the new credentials + this.config.credentials = { + ...this.config.credentials, + ...newCreds, + } + + logger.main.log('X credentials updated from configuration, re-initializing session.') + + // If Twitter API keys are provided, we might need to re-authenticate + // For now, we'll clear the session to force re-authentication + // since the session might be tied to the previous API credentials + try { + // Close existing browser context and create a new one with fresh session + if (this.ctx.browser) + await this.ctx.browser.close() + await this.reinitializeBrowserContext() + + logger.main.log('Browser context reinitialized with new credentials') + } + catch (error) { + logger.main.errorWithError('Failed to reinitialize browser context with new credentials:', error) + } + } + } + else if (event.data && event.data.moduleName === 'x') { + // Log error if config is not valid + logger.main.error('Invalid configuration received for X module') + } + }) + + // Handle input from AIRI system + this.client.onEvent('input:text', async (event) => { + logger.main.log('Received input from AIRI system:', event.data.text) + // Process Twitter-related commands + await this.handleInput(event.data.text) + }) + + // Handle authentication + this.client.onEvent('module:authenticated', async (event) => { + if (event.data.authenticated) { + logger.main.log('X module authenticated with AIRI server') + } + else { + logger.main.warn('X module authentication failed') + } + }) + } + + private async handlePostTweet(content: string): Promise { + if (content) { + await this.twitterServices.tweet.postTweet(content) + logger.main.log('Posted tweet:', content) + } + else { + throw new Error('Tweet text is empty. Please provide text to post.') + } + } + + private async handleSearchTweets(content: string): Promise { + if (content) { + const tweets = await this.twitterServices.tweet.searchTweets(content) + logger.main.log(`Found ${tweets.length} tweets for query: ${content}`) + // Return results to the user + this.client.send({ + type: 'input:text', + data: { + text: `Found ${tweets.length} tweets for '${content}': +${tweets.slice(0, 5).map((t: Tweet) => `- ${t.text.substring(0, 100)}...`).join('\n')}`, + }, + }) + return true + } + else { + throw new Error('Search query is empty. Please provide a query to search.') + } + } + + private async handleLikeTweet(content: string): Promise { + if (content) { + await this.twitterServices.tweet.likeTweet(content) + logger.main.log(`Liked tweet: ${content}`) + } + else { + throw new Error('Tweet ID is empty. Please provide a tweet ID to like.') + } + } + + private async handleRetweet(content: string): Promise { + if (content) { + await this.twitterServices.tweet.retweet(content) + logger.main.log(`Retweeted: ${content}`) + } + else { + throw new Error('Tweet ID is empty. Please provide a tweet ID to retweet.') + } + } + + private async handleGetUser(content: string): Promise { + if (content) { + const userProfile = await this.twitterServices.user.getUserProfile(content) + logger.main.log(`Retrieved profile for user: @${content}`) + // Return user info to the user + this.client.send({ + type: 'input:text', + data: { + text: `User Profile for @${userProfile.username}: +Display Name: ${userProfile.displayName} +Bio: ${userProfile.bio || 'N/A'} +Followers: ${userProfile.followersCount || 0} +Following: ${userProfile.followingCount || 0}`, + }, + }) + return true + } + else { + throw new Error('Username is empty. Please provide a username to retrieve.') + } + } + + private async handleGetTimeline(count: number): Promise { + const timelineOptions = { count } + const tweets = await this.twitterServices.timeline.getTimeline(timelineOptions) + logger.main.log(`Retrieved ${tweets.length} tweets from timeline`) + // Return timeline to the user + this.client.send({ + type: 'input:text', + data: { + text: `Latest ${tweets.length} tweets from your timeline: +${tweets.map((t: Tweet) => `- ${t.author.displayName}: ${t.text.substring(0, 80)}...`).join('\n')}`, + }, + }) + return true + } + + private async handleInput(input: string): Promise { + let responseSent = false + try { + // Parse and handle X commands + logger.main.log('Processing X command:', input) + + // Parse the command using the dedicated parsing function + const parsedCommand = parseTwitterCommand(input) + + if (!parsedCommand) { + throw new Error(`Unknown X command: ${input}. Supported commands: "post tweet: ", "search tweets: ", "like tweet: ", "retweet: ", "get user: ", "get timeline [count: N]"`) + } + + // Execute the appropriate command handler based on the parsed command + switch (parsedCommand.command) { + case 'post tweet': + await this.handlePostTweet(parsedCommand.content) + break + + case 'search tweets': + responseSent = await this.handleSearchTweets(parsedCommand.content) + break + + case 'like tweet': + await this.handleLikeTweet(parsedCommand.content) + break + + case 'retweet': + await this.handleRetweet(parsedCommand.content) + break + + case 'get user': + responseSent = await this.handleGetUser(parsedCommand.content) + break + + case 'get timeline': + responseSent = await this.handleGetTimeline(parsedCommand.count || 10) + break + + default: + // This should not happen if parseTwitterCommand is working correctly + throw new Error(`Unknown X command: ${input}`) + } + + // Only send the original processing response if we haven't already sent a specific response + if (!responseSent) { + this.client.send({ + type: 'input:text', + data: { + text: `Processed X command: ${input}`, + }, + }) + } + } + catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error) + logger.main.errorWithError('Error handling input:', error) + this.client.send({ + type: 'input:text', + data: { + text: `Error processing X command: ${errorMessage}`, + }, + }) + } + } + + /** + * Start the AiriAdapter and connect to the AIRI server + */ + async start(): Promise { + logger.main.log('Starting Airi adapter for X...') + try { + await this.client.connect() + logger.main.log('Airi adapter for X started successfully') + } + catch (error) { + logger.main.errorWithError('Failed to start Airi adapter for X:', error) + throw error + } + } + + /** + * Stop the AiriAdapter and disconnect from the AIRI server + */ + async stop(): Promise { + logger.main.log('Stopping Airi adapter for X...') + try { + this.client.close() + logger.main.log('Airi adapter for X stopped') + } + catch (error) { + logger.main.errorWithError('Error stopping Airi adapter for X:', error) + throw error + } + } + + /** + * Reinitialize the browser context to refresh session state + * This is needed when credentials are updated from the UI + */ + private async reinitializeBrowserContext(): Promise { + try { + // Clear the session file to force re-authentication with new credentials + const sessionFile = await useSessionFileAsync() + + // Clear the session file to force re-authentication + await fs.writeFile( + sessionFile, + JSON.stringify({ cookies: [], origins: [] }, null, 2), + ) + + logger.main.log('Session file cleared, re-initializing browser context') + + // Use the updated configuration with new credentials + const config = { + ...getDefaultConfig(), + credentials: { + ...getDefaultConfig().credentials, + ...this.config.credentials, + }, + } + + await initBrowser(config) + + // Update the context reference + this.ctx = useContext() + + // Reinitialize services with the new context + this.twitterServices = { + timeline: useTwitterTimelineServices(this.ctx), + tweet: useTwitterTweetServices(this.ctx), + user: useTwitterUserServices(this.ctx), + } + + logger.main.log('Browser context reinitialized successfully with new credentials') + } + catch (error) { + logger.main.errorWithError('Failed to reinitialize browser context:', error) + throw error + } + } +} + +function isXConfig(config: unknown): config is XConfig { + if (typeof config !== 'object' || config === null) + return false + const c = config as Record + const checkStringOrUndefined = (key: string) => typeof c[key] === 'string' || typeof c[key] === 'undefined' + + return checkStringOrUndefined('apiKey') + && checkStringOrUndefined('apiSecret') + && checkStringOrUndefined('accessToken') + && checkStringOrUndefined('accessTokenSecret') +} diff --git a/services/twitter-services/src/config/types.ts b/services/twitter-services/src/config/types.ts index 5c6313184..31aad2d73 100644 --- a/services/twitter-services/src/config/types.ts +++ b/services/twitter-services/src/config/types.ts @@ -13,6 +13,14 @@ export interface Config { endpoint?: string // Optional Stagehand service endpoint } + // Twitter API credentials + credentials?: { + apiKey?: string + apiSecret?: string + accessToken?: string + accessTokenSecret?: string + } + // Twitter configuration twitter: { defaultOptions?: { @@ -62,6 +70,12 @@ export function getDefaultConfig(): Config { requestTimeout: Number.parseInt(process.env.BROWSER_REQUEST_TIMEOUT || '20000'), requestRetries: Number.parseInt(process.env.BROWSER_REQUEST_RETRIES || '2'), }, + credentials: { + apiKey: process.env.TWITTER_API_KEY, + apiSecret: process.env.TWITTER_API_SECRET, + accessToken: process.env.TWITTER_ACCESS_TOKEN, + accessTokenSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET, + }, twitter: { defaultOptions: { timeline: { diff --git a/services/twitter-services/src/parsers/command-parser.test.ts b/services/twitter-services/src/parsers/command-parser.test.ts new file mode 100644 index 000000000..f71db9a9e --- /dev/null +++ b/services/twitter-services/src/parsers/command-parser.test.ts @@ -0,0 +1,106 @@ +/** + * Tests for command parser + * These are unit tests to verify the command parsing logic without running the full service + */ +import { describe, expect, it } from 'vitest' + +import { parseTwitterCommand } from './command-parser' + +describe('command Parser', () => { + it('should correctly parse post tweet command', () => { + const result = parseTwitterCommand('post tweet: Hello world') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'post tweet', content: 'Hello world' }) + }) + + it('should correctly parse search tweets command', () => { + const result = parseTwitterCommand('search tweets: javascript') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'search tweets', content: 'javascript' }) + }) + + it('should correctly parse like tweet command', () => { + const result = parseTwitterCommand('like tweet: 123456789') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'like tweet', content: '123456789' }) + }) + + it('should correctly parse retweet command', () => { + const result = parseTwitterCommand('retweet: 987654321') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'retweet', content: '987654321' }) + }) + + it('should correctly parse get user command', () => { + const result = parseTwitterCommand('get user: username') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'get user', content: 'username' }) + }) + + it('should correctly parse get timeline command', () => { + const result = parseTwitterCommand('get timeline') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'get timeline', content: '', count: 10 }) + }) + + it('should handle get timeline with count parameter', () => { + const result = parseTwitterCommand('get timeline count: 5') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'get timeline', content: 'count: 5', count: 5 }) + }) + + it('should handle get timeline with count parameter and additional text', () => { + const result = parseTwitterCommand('get timeline with extra text count: 7 and more') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'get timeline', content: 'with extra text count: 7 and more', count: 7 }) + }) + + it('should handle get timeline with default count when no count is specified', () => { + const result = parseTwitterCommand('get timeline random text') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'get timeline', content: 'random text', count: 10 }) + }) + + it('should handle get timeline with count parameter containing spaces', () => { + const result = parseTwitterCommand('get timeline count: 15 ') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'get timeline', content: 'count: 15 ', count: 15 }) + }) + + it('should reject unknown commands', () => { + const result = parseTwitterCommand('invalid command') + + expect(result).toBeNull() + }) + + it('should not match partial command prefixes', () => { + // This should not match 'post tweet:' command because it doesn't start with that prefix + const result = parseTwitterCommand('random text with post and tweet in it') + + expect(result).toBeNull() + }) + + it('should handle commands with mixed case', () => { + const result = parseTwitterCommand('POST TWEET: Hello world') + + expect(result).not.toBeNull() + expect(result).toMatchObject({ command: 'post tweet', content: 'Hello world' }) + }) + + it('should not be fooled by colon in the middle of a sentence', () => { + // This should not match because it doesn't start with a command prefix + const result = parseTwitterCommand('I like to say: hello world') + + expect(result).toBeNull() + }) +}) diff --git a/services/twitter-services/src/parsers/command-parser.ts b/services/twitter-services/src/parsers/command-parser.ts new file mode 100644 index 000000000..41b00fa2f --- /dev/null +++ b/services/twitter-services/src/parsers/command-parser.ts @@ -0,0 +1,52 @@ +// Define a union type for the command parsing result +export type ParseResult + = | { command: 'post tweet', content: string } + | { command: 'search tweets', content: string } + | { command: 'like tweet', content: string } + | { command: 'retweet', content: string } + | { command: 'get user', content: string } + | { command: 'get timeline', content: string, count: number } + +type NonTimelineCommands = 'post tweet' | 'search tweets' | 'like tweet' | 'retweet' | 'get user' + +/** + * Parses a Twitter command from the input string + * @param input The input string containing the command + * @returns Parsed command and content, or null if no valid command found + */ +export function parseTwitterCommand(input: string): ParseResult | null { + // Handle commands based on explicit prefixes for better reliability + const normalizedInput = input.trim().toLowerCase() + + // Define command patterns + const commandPatterns: Array<{ pattern: string, command: string }> = [ + { pattern: 'post tweet:', command: 'post tweet' }, + { pattern: 'search tweets:', command: 'search tweets' }, + { pattern: 'like tweet:', command: 'like tweet' }, + { pattern: 'retweet:', command: 'retweet' }, + { pattern: 'get user:', command: 'get user' }, + { pattern: 'get timeline', command: 'get timeline' }, + ] + + // Find the matching command pattern + for (const { pattern, command } of commandPatterns) { + if (normalizedInput.startsWith(pattern)) { + // Extract the content after the prefix + const content = input.substring(pattern.length) + + // Special handling for 'get timeline' command to extract count parameter + if (command === 'get timeline') { + const countMatch = content.match(/count:\s*(\d+)/) + const count = countMatch ? Number.parseInt(countMatch[1], 10) : 10 + // For timeline command, use full trim for consistency + const trimmedContent = content.trim() + return { command: command as ParseResult['command'], content: trimmedContent, count } + } + + return { command: command as NonTimelineCommands, content: content.trim() } + } + } + + // No valid command found + return null +} diff --git a/services/twitter-services/src/types/browser.ts b/services/twitter-services/src/types/browser.ts new file mode 100644 index 000000000..48ae3a452 --- /dev/null +++ b/services/twitter-services/src/types/browser.ts @@ -0,0 +1,17 @@ +/** + * Browser Configuration Types + */ + +export interface Viewport { + width: number + height: number +} + +export interface BrowserConfig { + headless: boolean + userAgent: string + viewport: Viewport + timeout: number + requestTimeout: number + requestRetries: number +} diff --git a/services/twitter-services/src/types/twitter.ts b/services/twitter-services/src/types/twitter.ts new file mode 100644 index 000000000..5d2bc423a --- /dev/null +++ b/services/twitter-services/src/types/twitter.ts @@ -0,0 +1,21 @@ +/** + * Twitter Service Types + */ + +export interface SearchOptions { + count?: number + includeReplies?: boolean + includeRetweets?: boolean + lang?: string + resultType?: 'recent' | 'popular' | 'mixed' + maxId?: string + sinceId?: string +} + +export interface TimelineOptions { + count?: number + includeReplies?: boolean + includeRetweets?: boolean + excludeReplies?: boolean + includePromoted?: boolean +}