From 7358023bbb4adb5eb09007c06dc2ff573698bd34 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Sun, 16 Mar 2025 21:56:57 +0800 Subject: [PATCH] feat(stage-ui|stage-tamagotchi|stage-web): ollama, header & values --- .../src/pages/settings/providers/ollama.vue | 119 ++++++ .../src/components/Layouts/MobileHeader.vue | 30 -- .../Layouts/MobileInteractiveArea.vue | 49 +-- .../src/components/Widgets/MobileSettings.vue | 228 ----------- .../Widgets/ModelProviderSettings.vue | 381 ------------------ apps/stage-web/src/pages/index.vue | 4 +- .../src/pages/settings/providers/ollama.vue | 119 ++++++ packages/stage-ui/package.json | 4 +- .../Form/Checkbox/Checkbox.story.vue | 8 +- .../Form/Field/FieldCheckbox.story.vue | 6 +- .../Form/Field/FieldInput.story.vue | 10 +- .../Form/Field/FieldKeyValues.story.vue | 181 +++++++++ .../components/Form/Field/FieldKeyValues.vue | 63 +++ .../Form/Field/FieldRange.story.vue | 10 +- .../src/components/Form/Field/index.ts | 1 + .../src/components/Form/Input/Input.story.vue | 20 +- .../components/Form/Input/InputKeyValue.vue | 19 + .../src/components/Form/Input/index.ts | 1 + .../ProviderAccountIdInput.story.vue | 8 +- .../ProviderAdvancedSettings.story.vue | 6 +- .../Providers/ProviderApiKeyInput.story.vue | 8 +- .../Providers/ProviderBaseUrlInput.story.vue | 10 +- .../Providers/ProviderBasicSettings.story.vue | 6 +- .../ProviderSettingsContainer.story.vue | 4 +- .../ProviderSettingsLayout.story.vue | 6 +- packages/stage-ui/src/stores/providers.ts | 8 +- packages/stage-ui/stories/setup.ts | 5 + pnpm-lock.yaml | 6 + 28 files changed, 577 insertions(+), 743 deletions(-) create mode 100644 apps/stage-tamagotchi/src/renderer/src/pages/settings/providers/ollama.vue delete mode 100644 apps/stage-web/src/components/Layouts/MobileHeader.vue delete mode 100644 apps/stage-web/src/components/Widgets/MobileSettings.vue delete mode 100644 apps/stage-web/src/components/Widgets/ModelProviderSettings.vue create mode 100644 apps/stage-web/src/pages/settings/providers/ollama.vue create mode 100644 packages/stage-ui/src/components/Form/Field/FieldKeyValues.story.vue create mode 100644 packages/stage-ui/src/components/Form/Field/FieldKeyValues.vue create mode 100644 packages/stage-ui/src/components/Form/Input/InputKeyValue.vue diff --git a/apps/stage-tamagotchi/src/renderer/src/pages/settings/providers/ollama.vue b/apps/stage-tamagotchi/src/renderer/src/pages/settings/providers/ollama.vue new file mode 100644 index 000000000..3b11b1b8f --- /dev/null +++ b/apps/stage-tamagotchi/src/renderer/src/pages/settings/providers/ollama.vue @@ -0,0 +1,119 @@ + + + diff --git a/apps/stage-web/src/components/Layouts/MobileHeader.vue b/apps/stage-web/src/components/Layouts/MobileHeader.vue deleted file mode 100644 index f517661b4..000000000 --- a/apps/stage-web/src/components/Layouts/MobileHeader.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - - diff --git a/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue b/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue index 151da4687..56b5fa77b 100644 --- a/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue +++ b/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue @@ -5,16 +5,10 @@ import { BasicTextarea } from '@proj-airi/stage-ui/components' import { useMicVAD } from '@proj-airi/stage-ui/composables' import { useChatStore, useConsciousnessStore, useProvidersStore, useSettings } from '@proj-airi/stage-ui/stores' import { storeToRefs } from 'pinia' -import { DrawerContent, DrawerOverlay, DrawerPortal, DrawerRoot, DrawerTrigger } from 'vaul-vue' import { onMounted, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import MobileChatHistory from '../Widgets/MobileChatHistory.vue' -import MobileSettings from '../Widgets/MobileSettings.vue' - -const emit = defineEmits<{ - (e: 'settingsOpen', open: boolean): void -}>() const messageInput = ref('') const listening = ref(false) @@ -67,21 +61,6 @@ function handleTranscription(_buffer: Float32Array) { alert('Transcription is not implemented yet') } -// async function handleAudioInputChange(event: Event) { -// const target = event.target as HTMLSelectElement -// const found = audioInputs.value.find(d => d.deviceId === target.value) -// if (!found) { -// selectedAudioDevice.value = undefined -// return -// } - -// selectedAudioDevice.value = found -// } - -function handleSettingsOpen(open: boolean) { - emit('settingsOpen', open) -} - watch(isAudioInputOn, async (value) => { if (value === 'false') { destroy() @@ -106,34 +85,14 @@ onMounted(() => { v-model="messageInput" :placeholder="t('stage.message')" border="solid 2 primary-100 dark:primary-400/20" - text="primary-400 hover:primary-600 dark:[#905073] dark:hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600 placeholder:dark:[#905073] placeholder:dark:hover:primary-600" - bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" - w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none + text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500 placeholder:primary-300 placeholder:hover:primary-500 placeholder:dark:primary-300/50 placeholder:dark:hover:primary-500" + bg="primary-100 dark:primary-400/20" + max-h="[10lh]" min-h="[1lh]" + w-full resize-none overflow-y-scroll rounded-xl p-2 font-medium outline-none transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out" @submit="handleSend" /> - - -
- - - - -
- -
-
-
-
diff --git a/apps/stage-web/src/components/Widgets/MobileSettings.vue b/apps/stage-web/src/components/Widgets/MobileSettings.vue deleted file mode 100644 index 8ba7710ff..000000000 --- a/apps/stage-web/src/components/Widgets/MobileSettings.vue +++ /dev/null @@ -1,228 +0,0 @@ - - - - - diff --git a/apps/stage-web/src/components/Widgets/ModelProviderSettings.vue b/apps/stage-web/src/components/Widgets/ModelProviderSettings.vue deleted file mode 100644 index a6d69d603..000000000 --- a/apps/stage-web/src/components/Widgets/ModelProviderSettings.vue +++ /dev/null @@ -1,381 +0,0 @@ - - - diff --git a/apps/stage-web/src/pages/index.vue b/apps/stage-web/src/pages/index.vue index 169668c0d..03d38b19a 100644 --- a/apps/stage-web/src/pages/index.vue +++ b/apps/stage-web/src/pages/index.vue @@ -6,7 +6,6 @@ import { ref } from 'vue' import Cross from '../components/Backgrounds/Cross.vue' import Header from '../components/Layouts/Header.vue' import InteractiveArea from '../components/Layouts/InteractiveArea.vue' -import MobileHeader from '../components/Layouts/MobileHeader.vue' import MobileInteractiveArea from '../components/Layouts/MobileInteractiveArea.vue' import AnimatedWave from '../components/Widgets/AnimatedWave.vue' @@ -28,8 +27,7 @@ function handleSettingsOpen(open: boolean) {
-
-
diff --git a/apps/stage-web/src/pages/settings/providers/ollama.vue b/apps/stage-web/src/pages/settings/providers/ollama.vue new file mode 100644 index 000000000..3b11b1b8f --- /dev/null +++ b/apps/stage-web/src/pages/settings/providers/ollama.vue @@ -0,0 +1,119 @@ + + + diff --git a/packages/stage-ui/package.json b/packages/stage-ui/package.json index 228f38c99..bb018d42a 100644 --- a/packages/stage-ui/package.json +++ b/packages/stage-ui/package.json @@ -56,7 +56,9 @@ "story:preview": "histoire preview" }, "dependencies": { - "reka-ui": "^2.1.0" + "@vueuse/motion": "^3.0.3", + "reka-ui": "^2.1.0", + "vue": "link:@formkit/auto-animate/vue" }, "devDependencies": { "@electron-toolkit/preload": "^3.0.1", diff --git a/packages/stage-ui/src/components/Form/Checkbox/Checkbox.story.vue b/packages/stage-ui/src/components/Form/Checkbox/Checkbox.story.vue index d5f915c56..a2077181b 100644 --- a/packages/stage-ui/src/components/Form/Checkbox/Checkbox.story.vue +++ b/packages/stage-ui/src/components/Form/Checkbox/Checkbox.story.vue @@ -22,7 +22,7 @@ const interactiveValue = ref(false) id="checked" title="Checked" > -
+
@@ -31,7 +31,7 @@ const interactiveValue = ref(false) id="unchecked" title="Unchecked" > -
+
@@ -40,7 +40,7 @@ const interactiveValue = ref(false) id="interactive" title="Interactive" > -
+
@@ -54,7 +54,7 @@ const interactiveValue = ref(false) id="sizes" title="Different Contexts" > -
+
diff --git a/packages/stage-ui/src/components/Form/Field/FieldCheckbox.story.vue b/packages/stage-ui/src/components/Form/Field/FieldCheckbox.story.vue index 7b72c13ac..ca62fe907 100644 --- a/packages/stage-ui/src/components/Form/Field/FieldCheckbox.story.vue +++ b/packages/stage-ui/src/components/Form/Field/FieldCheckbox.story.vue @@ -21,7 +21,7 @@ const uncheckedValue = ref(false) id="checked" title="Checked" > -
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+import { reactive, watch } from 'vue' + +import FieldKeyValues from './FieldKeyValues.vue' + +const emptyHeaders = reactive<{ key: string, value: string }[]>([ + { key: '', value: '' }, +]) +const singleHeader = reactive<{ key: string, value: string }[]>([ + { key: 'Content-Type', value: 'application/json' }, +]) +const multipleHeaders = reactive<{ key: string, value: string }[]>([ + { key: 'Authorization', value: 'Bearer token123' }, + { key: 'Accept', value: 'application/json' }, + { key: 'X-API-Key', value: 'abc123xyz456' }, +]) + +function addKeyValue(headers: { key: string, value: string }[], key: string, value: string) { + if (!headers) + return + + headers.push({ key, value }) +} + +function removeKeyValue(index: number, headers: { key: string, value: string }[]) { + if (!headers) + return + + if (headers.length === 1) { + headers[0].key = '' + headers[0].value = '' + } + else { + headers.splice(index, 1) + } +} + +watch(emptyHeaders, (headers) => { + if (headers.length > 0 && (headers[headers.length - 1].key !== '' || headers[headers.length - 1].value !== '')) { + emptyHeaders.push({ key: '', value: '' }) + } +}, { + deep: true, + immediate: true, +}) + +watch(singleHeader, (headers) => { + if (headers.length > 0 && (headers[headers.length - 1].key !== '' || headers[headers.length - 1].value !== '')) { + singleHeader.push({ key: '', value: '' }) + } +}, { + deep: true, + immediate: true, +}) + +watch(multipleHeaders, (headers) => { + if (headers.length > 0 && (headers[headers.length - 1].key !== '' || headers[headers.length - 1].value !== '')) { + multipleHeaders.push({ key: '', value: '' }) + } +}, { + deep: true, + immediate: true, +}) + + + diff --git a/packages/stage-ui/src/components/Form/Field/FieldKeyValues.vue b/packages/stage-ui/src/components/Form/Field/FieldKeyValues.vue new file mode 100644 index 000000000..65b4cb350 --- /dev/null +++ b/packages/stage-ui/src/components/Form/Field/FieldKeyValues.vue @@ -0,0 +1,63 @@ + + + diff --git a/packages/stage-ui/src/components/Form/Field/FieldRange.story.vue b/packages/stage-ui/src/components/Form/Field/FieldRange.story.vue index 27071e629..a337618e2 100644 --- a/packages/stage-ui/src/components/Form/Field/FieldRange.story.vue +++ b/packages/stage-ui/src/components/Form/Field/FieldRange.story.vue @@ -27,7 +27,7 @@ function formatPercentage(value: number): string { id="default-min" title="Default (min)" > -
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+import Input from './Input.vue' + +const props = defineProps<{ + name?: string + keyPlaceholder?: string + valuePlaceholder?: string +}>() + +const key = defineModel('propertyKey', { required: true }) +const value = defineModel('propertyValue', { required: true }) + + + diff --git a/packages/stage-ui/src/components/Form/Input/index.ts b/packages/stage-ui/src/components/Form/Input/index.ts index a691dd6c1..30da630e1 100644 --- a/packages/stage-ui/src/components/Form/Input/index.ts +++ b/packages/stage-ui/src/components/Form/Input/index.ts @@ -1 +1,2 @@ export { default as Input } from './Input.vue' +export { default as InputKeyValue } from './InputKeyValue.vue' diff --git a/packages/stage-ui/src/components/Providers/ProviderAccountIdInput.story.vue b/packages/stage-ui/src/components/Providers/ProviderAccountIdInput.story.vue index 059111e3b..d9e5d9c20 100644 --- a/packages/stage-ui/src/components/Providers/ProviderAccountIdInput.story.vue +++ b/packages/stage-ui/src/components/Providers/ProviderAccountIdInput.story.vue @@ -21,7 +21,7 @@ const filledAccountId = ref('1234567890abcdef1234567890abcdef') id="empty" title="Empty" > -
+
-
+
-
+
-
+
-
+

Advanced settings content (initially hidden)

@@ -29,7 +29,7 @@ import ProviderAdvancedSettings from './ProviderAdvancedSettings.vue' id="expanded" title="Expanded" > -
+

Advanced settings content (initially visible)

@@ -42,7 +42,7 @@ import ProviderAdvancedSettings from './ProviderAdvancedSettings.vue' id="custom-title" title="Custom Title" > -
+

Expert settings content

diff --git a/packages/stage-ui/src/components/Providers/ProviderApiKeyInput.story.vue b/packages/stage-ui/src/components/Providers/ProviderApiKeyInput.story.vue index 16b3669fd..b2b216904 100644 --- a/packages/stage-ui/src/components/Providers/ProviderApiKeyInput.story.vue +++ b/packages/stage-ui/src/components/Providers/ProviderApiKeyInput.story.vue @@ -21,7 +21,7 @@ const filledApiKey = ref('sk-1234567890abcdefghijklmnopqrstuvwxyz') id="empty" title="Empty" > -
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+

Settings content goes here

@@ -29,7 +29,7 @@ import ProviderBasicSettings from './ProviderBasicSettings.vue' id="with-title-description" title="With Title & Description" > -
+
-
+
-
+

Container content goes here

@@ -31,7 +31,7 @@ import ProviderSettingsContainer from './ProviderSettingsContainer.vue' id="with-sections" title="With Sections" > -
+
-
+

Provider settings content goes here

@@ -29,7 +29,7 @@ import ProviderSettingsLayout from './ProviderSettingsLayout.vue' id="with-icon" title="With Icon" > -
+
@@ -44,7 +44,7 @@ import ProviderSettingsLayout from './ProviderSettingsLayout.vue' id="with-icon-color" title="With Icon Color" > -
+
diff --git a/packages/stage-ui/src/stores/providers.ts b/packages/stage-ui/src/stores/providers.ts index 339a0c957..14f048d06 100644 --- a/packages/stage-ui/src/stores/providers.ts +++ b/packages/stage-ui/src/stores/providers.ts @@ -161,8 +161,8 @@ export const useProvidersStore = defineStore('providers', () => { }, }, }, - 'ollama-ai': { - id: 'ollama-ai', + 'ollama': { + id: 'ollama', nameKey: 'providers.ollama.name', name: 'Ollama', descriptionKey: 'providers.ollama.description', @@ -180,7 +180,7 @@ export const useProvidersStore = defineStore('providers', () => { return { id: model.id, name: model.id, - provider: 'ollama-ai', + provider: 'ollama', description: '', contextLength: 0, deprecated: false, @@ -538,7 +538,7 @@ export const useProvidersStore = defineStore('providers', () => { return !!config.apiKey && !!config.baseUrl case 'openai': return !!config.apiKey - case 'ollama-ai': + case 'ollama': return !!config.baseUrl case 'vllm': return !!config.baseUrl diff --git a/packages/stage-ui/stories/setup.ts b/packages/stage-ui/stories/setup.ts index c08a5fa03..95c04c0f4 100644 --- a/packages/stage-ui/stories/setup.ts +++ b/packages/stage-ui/stories/setup.ts @@ -1,4 +1,6 @@ +import { autoAnimatePlugin } from '@formkit/auto-animate/vue' import { defineSetupVue3 } from '@histoire/plugin-vue' +import { MotionPlugin } from '@vueuse/motion' import ThemeColorsHueControl from './ThemeColorsHueControl.vue' @@ -7,5 +9,8 @@ import '@unocss/reset/tailwind.css' import './main.css' export const setupVue3 = defineSetupVue3(({ app }) => { + app.use(MotionPlugin) + app.use(autoAnimatePlugin) + app.component('ThemeColorsHueControl', ThemeColorsHueControl) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e664b8c10..f02df8bfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1053,9 +1053,15 @@ importers: packages/stage-ui: dependencies: + '@vueuse/motion': + specifier: ^3.0.3 + version: 3.0.3(magicast@0.3.5)(rollup@4.35.0)(vue@3.5.13(typescript@5.8.2)) reka-ui: specifier: ^2.1.0 version: 2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)) + vue: + specifier: link:@formkit/auto-animate/vue + version: 3.5.13(typescript@5.8.2) devDependencies: '@electron-toolkit/preload': specifier: ^3.0.1