From 9011d3e4ac4afca1a59304adc42b08adea9026a0 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Sat, 22 Feb 2025 16:51:29 +0800 Subject: [PATCH] refactor(stage-web): better ui Co-authored-by: Rizumu Ayaka <19204772+LittleSound@users.noreply.github.com> --- apps/stage-web/package.json | 3 + .../components/Widgets/DesktopSettings.vue | 2 +- .../src/components/Widgets/MobileSettings.vue | 4 +- .../Widgets/ModelProviderSettings.vue | 318 ++++++++++++++++-- .../stage-ui/src/components/Collapsable.vue | 2 +- pnpm-lock.yaml | 41 ++- 6 files changed, 324 insertions(+), 46 deletions(-) diff --git a/apps/stage-web/package.json b/apps/stage-web/package.json index 8d8773a8d..7b9a72992 100644 --- a/apps/stage-web/package.json +++ b/apps/stage-web/package.json @@ -39,12 +39,14 @@ "@pixiv/three-vrm-core": "^3.3.4", "@proj-airi/stage-ui": "workspace:^", "@ricky0123/vad-web": "^0.0.22", + "@standard-schema/spec": "^1.0.0", "@tresjs/cientos": "^4.1.0", "@tresjs/core": "^4.3.3", "@types/yauzl": "^2.10.3", "@typeschema/valibot": "^0.14.0", "@unhead/vue": "^2.0.0-alpha.16", "@unocss/reset": "^66.0.0", + "@valibot/to-json-schema": "1.0.0-rc.0", "@vueuse/core": "^12.7.0", "@vueuse/head": "^2.0.0", "@vueuse/shared": "^12.7.0", @@ -84,6 +86,7 @@ "@iconify-json/eos-icons": "^1.2.2", "@iconify-json/lucide": "^1.2.26", "@iconify-json/mingcute": "^1.2.3", + "@iconify-json/simple-icons": "^1.2.25", "@iconify-json/solar": "^1.2.2", "@iconify-json/svg-spinners": "^1.2.2", "@iconify/utils": "^2.3.0", diff --git a/apps/stage-web/src/components/Widgets/DesktopSettings.vue b/apps/stage-web/src/components/Widgets/DesktopSettings.vue index 8ab45b8c9..e81a159ea 100644 --- a/apps/stage-web/src/components/Widgets/DesktopSettings.vue +++ b/apps/stage-web/src/components/Widgets/DesktopSettings.vue @@ -24,7 +24,7 @@ const isOpen = ref(false) class="max-w-40% min-w-500px w-full" flex="~ col" bg="white dark:zinc-900" - fixed inset-y-4 right-4 z-50 of-hidden rounded-lg + fixed inset-y-4 right-4 z-50 of-hidden rounded-lg outline-none >
diff --git a/apps/stage-web/src/components/Widgets/MobileSettings.vue b/apps/stage-web/src/components/Widgets/MobileSettings.vue index 90bd0c8fc..3d04efaa0 100644 --- a/apps/stage-web/src/components/Widgets/MobileSettings.vue +++ b/apps/stage-web/src/components/Widgets/MobileSettings.vue @@ -35,10 +35,10 @@ function navigateBack() {
-

+

{{ t('settings.title') }}

-
+
import { Collapsable } from '@proj-airi/stage-ui/components' -import { ref } from 'vue' +import { toJsonSchema } from '@valibot/to-json-schema' +import { description, object, optional, pipe, record, string, title } from 'valibot' +import { computed, ref } from 'vue' interface ModelProvider { id: string name: string icon?: string - models: { - id: string - name: string - capabilities: string[] - }[] + fields: ReturnType } -const providers = ref([ +const providers = computed(() => [ + { + id: 'openrouter-ai', + name: 'OpenRouter', + icon: 'i-lobe-icons:openrouter', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for OpenRouter')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://openrouter.ai/api/v1/'), + }), + ), + }, { id: 'openai', name: 'OpenAI', icon: 'i-lobe-icons:openai', - models: [ - { id: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: ['chat', 'vision'] }, - { id: 'gpt-4', name: 'GPT-4', capabilities: ['chat'] }, - { id: 'whisper-1', name: 'Whisper', capabilities: ['stt'] }, - ], + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for OpenAI services')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.openai.com/v1/'), + }), + ), }, { - id: 'openrouter', - name: 'OpenRouter', - icon: 'i-lobe-icons:openrouter', - models: [ - { id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus', capabilities: ['chat', 'vision'] }, - { id: 'google/gemini-pro', name: 'Gemini Pro', capabilities: ['chat'] }, - ], + id: 'ollama-ai', + name: 'Ollama', + icon: 'i-lobe-icons:ollama', + fields: toJsonSchema( + object({ + baseUrl: optional(pipe(string(), title('Host'), description('Host of the Ollama instance (optional)'))), + extraHeaders: optional(pipe(record(string(), string()), title('Headers'), description('Custom Headers for Ollama instance (optional)'))), + }), + ), + }, + { + id: 'vllm', + name: 'vLLM', + icon: 'i-lobe-icons:vllm-color', + fields: toJsonSchema( + object({ + baseUrl: optional(pipe(string(), title('Host'), description('Host of the vLLM instance (optional)'))), + apiKey: optional(pipe(string(), title('API Key'), description('API Key for vLLM'))), + extraHeaders: optional(pipe(record(string(), string()), title('Headers'), description('Custom Headers for vLLM instance (optional)'))), + }), + ), + }, + { + id: 'elevenlabs', + name: 'ElevenLabs', + icon: 'i-simple-icons:elevenlabs', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for ElevenLabs')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)'))), + }), + ), + }, + { + id: 'xai', + name: 'xAI', + icon: 'i-lobe-icons:xai', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for xAI')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.x.ai/v1/'), + }), + ), + }, + { + id: 'deepseek', + name: 'DeepSeek', + icon: 'i-lobe-icons:deepseek-color', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for DeepSeek')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.deepseek.com/'), + }), + ), + }, + { + id: 'together-ai', + name: 'Together.ai', + icon: 'i-lobe-icons:together-color', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for Together.ai')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.together.xyz/v1/'), + }), + ), + }, + { + id: 'novita-ai', + name: 'Novita', + icon: 'i-lobe-icons:novita-color', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for Novita')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.novita.ai/v3/openai/'), + }), + ), + }, + { + id: 'fireworks-ai', + name: 'Fireworks.ai', + icon: 'i-lobe-icons:fireworks', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for Fireworks.ai')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.fireworks.ai/inference/v1/'), + }), + ), + }, + { + id: 'cloudflare-workers-ai', + name: 'Cloudflare Workers AI', + icon: 'i-lobe-icons:cloudflare-color', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key')), + accountId: pipe(string(), title('Account ID'), description('Cloudflare Account ID')), + }), + ), + }, + { + id: 'mistral-ai', + name: 'Mistral', + icon: 'i-lobe-icons:mistral-color', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for OpenRouter')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.mistral.ai/v1/'), + }), + ), + }, + { + id: 'moonshot-ai', + name: 'Moonshot AI', + icon: 'i-lobe-icons:moonshot', + fields: toJsonSchema( + object({ + apiKey: pipe(string(), title('API Key'), description('API Key for OpenRouter')), + baseUrl: optional(pipe(string(), title('Base URL'), description('Custom base URL (optional)')), 'https://api.moonshot.cn/v1/'), + }), + ), }, ]) + +const providerValues = ref>>({}) + +function getFieldValue(providerId: string, fieldName: string): string { + return providerValues.value[providerId]?.[fieldName] || '' +} + +function setFieldValue(providerId: string, fieldName: string, value: string) { + if (!providerValues.value[providerId]) { + providerValues.value[providerId] = {} + } + providerValues.value[providerId][fieldName] = value +} + +function getRecordEntries(providerId: string, fieldName: string): Array<[string, string]> { + const value = providerValues.value[providerId]?.[fieldName] + if (!value) + return [['', '']] + try { + return Object.entries(JSON.parse(value)) + } + catch { + return [['', '']] + } +} + +function setRecordValue(providerId: string, fieldName: string, entries: Array<[string, string]>) { + const validEntries = entries.filter(([key, value]) => key || value) + if (validEntries.length === 0) { + delete providerValues.value[providerId]?.[fieldName] + return + } + + const record = Object.fromEntries(validEntries) + setFieldValue(providerId, fieldName, JSON.stringify(record)) +} + +function addRecordEntry(entries: Array<[string, string]>) { + entries.push(['', '']) +} + +function removeRecordEntry(entries: Array<[string, string]>, index: number) { + entries.splice(index, 1) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73ff0ee90..9c0f9e656 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -505,6 +505,9 @@ importers: '@ricky0123/vad-web': specifier: ^0.0.22 version: 0.0.22 + '@standard-schema/spec': + specifier: ^1.0.0 + version: 1.0.0 '@tresjs/cientos': specifier: ^4.1.0 version: 4.1.0(@tresjs/core@4.3.3(three@0.173.0)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))(@types/three@0.173.0)(react@18.3.1)(three@0.173.0)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) @@ -523,6 +526,9 @@ importers: '@unocss/reset': specifier: ^66.0.0 version: 66.0.0 + '@valibot/to-json-schema': + specifier: 1.0.0-rc.0 + version: 1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)) '@vueuse/core': specifier: ^12.7.0 version: 12.7.0(typescript@5.7.3) @@ -635,6 +641,9 @@ importers: '@iconify-json/mingcute': specifier: ^1.2.3 version: 1.2.3 + '@iconify-json/simple-icons': + specifier: ^1.2.25 + version: 1.2.25 '@iconify-json/solar': specifier: ^1.2.2 version: 1.2.2 @@ -1102,7 +1111,7 @@ importers: version: 0.1.0-beta.5 '@xsai/tool': specifier: 'catalog:' - version: 0.1.0-beta.5(@xsai/generate-text@0.1.0-beta.5)(zod-to-json-schema@3.24.1(zod@3.24.2)) + version: 0.1.0-beta.5(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)))(@xsai/generate-text@0.1.0-beta.5)(zod-to-json-schema@3.24.1(zod@3.24.2)) dotenv: specifier: ^16.4.7 version: 16.4.7 @@ -2813,6 +2822,9 @@ packages: '@iconify-json/mingcute@1.2.3': resolution: {integrity: sha512-yiEQfLBF5iwyOdxuY0kEU06+8Mp6Mrp14KVXTb+5jjSVuD71C9EQrzM/mm1Efd8Nu2amJalTPisl3loC8pHBqQ==} + '@iconify-json/simple-icons@1.2.25': + resolution: {integrity: sha512-2E1/gOCO97rF6usfhhiXxwzCb+UhdEsxW3lW1Sew+xZY0COY6dp82Z/r1rUt2fWKneWjuoGcNeJHHXQyG8mIuw==} + '@iconify-json/solar@1.2.2': resolution: {integrity: sha512-lcTb6DWL4HZObiY1W3fHfuxxuQHUc6CFHFeywKEx7Ry0k+dU6POZCMC7oVLr0F8vuf+KgaQ3oOoGO/yFzOwrNg==} @@ -3944,6 +3956,9 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@stylistic/eslint-plugin@4.0.1': resolution: {integrity: sha512-RwKkRKiDrF4ptiur54ckDhOByQYKYZ1dEmI5K8BJCmuGpauFJXzVL1UQYTA2zq702CqMFdYiJcVFJWfokIgFxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4436,6 +4451,11 @@ packages: peerDependencies: vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 + '@valibot/to-json-schema@1.0.0-rc.0': + resolution: {integrity: sha512-F3WDgnPzcDs9Y8qZwU9qfPnEJBQ6lCMCFjI7VsMjAza6yAixGr4cZ50gOy6zniSCk49GkFvq2a6cBKfZjTpyOw==} + peerDependencies: + valibot: ^1.0.0 || ^1.0.0-beta.5 || ^1.0.0-rc + '@vitejs/plugin-vue@5.2.1': resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -12729,6 +12749,10 @@ snapshots: dependencies: '@iconify/types': 2.0.0 + '@iconify-json/simple-icons@1.2.25': + dependencies: + '@iconify/types': 2.0.0 + '@iconify-json/solar@1.2.2': dependencies: '@iconify/types': 2.0.0 @@ -13915,6 +13939,8 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.0.0': {} + '@stylistic/eslint-plugin@4.0.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/utils': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) @@ -14510,6 +14536,10 @@ snapshots: transitivePeerDependencies: - vue + '@valibot/to-json-schema@1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3))': + dependencies: + valibot: 1.0.0-beta.9(typescript@5.7.3) + '@vitejs/plugin-vue@5.2.1(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(less@4.2.1)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))': dependencies: vite: 6.1.1(@types/node@22.13.4)(jiti@2.4.2)(less@4.2.1)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0) @@ -15106,11 +15136,11 @@ snapshots: dependencies: '@xsai/shared-chat': 0.1.0-beta.6 - '@xsai/tool@0.1.0-beta.5(@xsai/generate-text@0.1.0-beta.5)(zod-to-json-schema@3.24.1(zod@3.24.2))': + '@xsai/tool@0.1.0-beta.5(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)))(@xsai/generate-text@0.1.0-beta.5)(zod-to-json-schema@3.24.1(zod@3.24.2))': dependencies: '@xsai/shared': 0.1.0-beta.6 '@xsai/shared-chat': 0.1.0-beta.6 - xsschema: 0.1.0-beta.6(zod-to-json-schema@3.24.1(zod@3.24.2)) + xsschema: 0.1.0-beta.6(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)))(zod-to-json-schema@3.24.1(zod@3.24.2)) optionalDependencies: '@xsai/generate-text': 0.1.0-beta.5 transitivePeerDependencies: @@ -19647,7 +19677,7 @@ snapshots: '@xsai/providers': 0.1.0-beta.5 '@xsai/shared-chat': 0.1.0-beta.5 '@xsai/stream-text': 0.1.0-beta.5 - '@xsai/tool': 0.1.0-beta.5(@xsai/generate-text@0.1.0-beta.5)(zod-to-json-schema@3.24.1(zod@3.24.2)) + '@xsai/tool': 0.1.0-beta.5(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)))(@xsai/generate-text@0.1.0-beta.5)(zod-to-json-schema@3.24.1(zod@3.24.2)) '@xsai/utils-chat': 0.1.0-beta.5 defu: 6.1.4 fetch-mock: 12.3.0 @@ -23167,8 +23197,9 @@ snapshots: xmlhttprequest-ssl@2.1.2: {} - xsschema@0.1.0-beta.6(zod-to-json-schema@3.24.1(zod@3.24.2)): + xsschema@0.1.0-beta.6(@valibot/to-json-schema@1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)))(zod-to-json-schema@3.24.1(zod@3.24.2)): optionalDependencies: + '@valibot/to-json-schema': 1.0.0-rc.0(valibot@1.0.0-beta.9(typescript@5.7.3)) zod-to-json-schema: 3.24.1(zod@3.24.2) xtend@4.0.2: {}