feat(stage-ui|stage-web|stage-tamagotchi): Input, Field component & story, most of providers supported

This commit is contained in:
Neko Ayaka
2025-03-16 14:03:26 +08:00
parent ff9111ff58
commit 3ef246a9ab
30 changed files with 891 additions and 160 deletions
@@ -5,14 +5,14 @@ import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
import {
FieldCheckbox,
FieldRange,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderCheckboxInput,
ProviderSettingsContainer,
ProviderSettingsLayout,
ProviderSliderInput,
} from '@proj-airi/stage-ui/components'
import { voiceMap } from '@proj-airi/stage-ui/constants'
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
@@ -268,7 +268,7 @@ function handleResetVoiceSettings() {
Voice Settings
</h2>
<div flex="~ col gap-4">
<ProviderSliderInput
<FieldRange
v-model="similarityBoost"
label="Similarity Boost"
description="Voice similarity adherence"
@@ -277,7 +277,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderSliderInput
<FieldRange
v-model="stability"
label="Stability"
description="Voice stability and randomness"
@@ -286,7 +286,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderSliderInput
<FieldRange
v-model="speed"
label="Speed"
description="Speech generation speed"
@@ -295,7 +295,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderSliderInput
<FieldRange
v-model="style"
label="Style"
description="Voice style exaggeration"
@@ -304,7 +304,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderCheckboxInput
<FieldCheckbox
v-model="useSpeakerBoost"
label="Speaker Boost"
description="Enhance speaker similarity"
@@ -329,10 +329,10 @@ function handleResetVoiceSettings() {
<div flex="~ col gap-4">
<textarea
v-model="testText" placeholder="Enter text to test the voice..."
border="neutral-200 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
border="neutral-100 dark:neutral-800 solid 2 focus:neutral-200 dark:focus:neutral-700"
transition="all duration-250 ease-in-out"
h-24 w-full rounded-lg px-3 py-2 text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:white dark:focus:neutral-700"
bg="neutral-100 dark:neutral-800 focus:neutral-50 dark:focus:neutral-900"
/>
<div flex="~ col gap-6">
<label grid="~ cols-2 gap-4">
@@ -5,14 +5,14 @@ import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
import {
FieldCheckbox,
FieldRange,
ProviderAdvancedSettings,
ProviderApiKeyInput,
ProviderBaseUrlInput,
ProviderBasicSettings,
ProviderCheckboxInput,
ProviderSettingsContainer,
ProviderSettingsLayout,
ProviderSliderInput,
} from '@proj-airi/stage-ui/components'
import { voiceMap } from '@proj-airi/stage-ui/constants'
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
@@ -268,7 +268,7 @@ function handleResetVoiceSettings() {
Voice Settings
</h2>
<div flex="~ col gap-4">
<ProviderSliderInput
<FieldRange
v-model="similarityBoost"
label="Similarity Boost"
description="Voice similarity adherence"
@@ -277,7 +277,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderSliderInput
<FieldRange
v-model="stability"
label="Stability"
description="Voice stability and randomness"
@@ -286,7 +286,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderSliderInput
<FieldRange
v-model="speed"
label="Speed"
description="Speech generation speed"
@@ -295,7 +295,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderSliderInput
<FieldRange
v-model="style"
label="Style"
description="Voice style exaggeration"
@@ -304,7 +304,7 @@ function handleResetVoiceSettings() {
:step="0.01"
/>
<ProviderCheckboxInput
<FieldCheckbox
v-model="useSpeakerBoost"
label="Speaker Boost"
description="Enhance speaker similarity"
@@ -329,10 +329,10 @@ function handleResetVoiceSettings() {
<div flex="~ col gap-4">
<textarea
v-model="testText" placeholder="Enter text to test the voice..."
border="neutral-200 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
border="neutral-100 dark:neutral-800 solid 2 focus:neutral-200 dark:focus:neutral-700"
transition="all duration-250 ease-in-out"
h-24 w-full rounded-lg px-3 py-2 text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:white dark:focus:neutral-700"
bg="neutral-100 dark:neutral-800 focus:neutral-50 dark:focus:neutral-900"
/>
<div flex="~ col gap-6">
<label grid="~ cols-2 gap-4">
+6 -2
View File
@@ -16,13 +16,17 @@ export default defineConfig({
id: 'design-system',
title: 'Design System',
},
{
id: 'menu',
title: 'Menu',
},
{
id: 'form',
title: 'Form',
},
{
id: 'menu',
title: 'Menu',
id: 'providers',
title: 'Providers',
},
{
id: 'data-gui',
+3
View File
@@ -55,6 +55,9 @@
"story:build-base": "histoire build --base /ui",
"story:preview": "histoire preview"
},
"dependencies": {
"reka-ui": "^2.1.0"
},
"devDependencies": {
"@electron-toolkit/preload": "^3.0.1",
"@histoire/plugin-vue": "1.0.0-alpha.2",
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { SwitchRoot, SwitchThumb } from 'reka-ui'
const modelValue = defineModel<boolean>({ required: true })
</script>
<template>
<SwitchRoot
v-model="modelValue"
transition="background duration-250 ease-in-out"
outline="focus-within:none"
flex="~"
border="neutral-300 dark:neutral-700 data-[state=checked]:primary-200 data-[state=unchecked]:neutral-300 focus-within:neutral-800"
bg="data-[state=checked]:primary-400 data-[state=unchecked]:neutral-300 dark:data-[state=checked]:primary-700 dark:data-[state=unchecked]:neutral-800"
relative h-7 w="12.5" rounded-full
shadow="sm focus-within:shadow-neutral-800 focus-within:[0_0_0_1px] "
>
<SwitchThumb
my-auto size-6
flex items-center justify-center
translate-x="0.5 data-[state=checked]:full"
rounded-full bg-white text-xs shadow-xl
transition="transform duration-250 ease-in-out"
will-change-transform
/>
</SwitchRoot>
</template>
@@ -0,0 +1 @@
export { default as Checkbox } from './Checkbox.vue'
@@ -0,0 +1,62 @@
<script setup lang="ts">
import { ref } from 'vue'
import FieldCheckbox from './FieldCheckbox.vue'
const checkedValue = ref(true)
const uncheckedValue = ref(false)
</script>
<template>
<Story
title="Field Checkbox"
group="form"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="checked"
title="Checked"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldCheckbox
v-model="checkedValue"
label="Speaker Boost"
description="Enhance speaker similarity"
/>
</div>
</Variant>
<Variant
id="unchecked"
title="Unchecked"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldCheckbox
v-model="uncheckedValue"
label="Use High Quality"
description="Use higher quality but slower processing"
/>
</div>
</Variant>
<Variant
id="interactive"
title="Interactive"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldCheckbox
v-model="checkedValue"
label="Toggle Me"
description="This checkbox can be toggled"
/>
<div class="mt-4 text-sm">
Current value: {{ checkedValue ? 'On' : 'Off' }}
</div>
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,26 @@
<script setup lang="ts">
import Checkbox from '../Checkbox/Checkbox.vue'
const props = defineProps<{
label?: string
description?: string
}>()
const modelValue = defineModel<boolean>({ required: true })
</script>
<template>
<label flex="~ col gap-4">
<div flex="~ row" items-center gap-2>
<div flex="1">
<div class="flex items-center gap-1 text-sm font-medium">
{{ props.label }}
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
{{ props.description }}
</div>
</div>
<Checkbox v-model="modelValue" />
</div>
</label>
</template>
@@ -0,0 +1,95 @@
<script setup lang="ts">
import { ref } from 'vue'
import ProviderInput from './FieldInput.vue'
const textValue = ref('')
const passwordValue = ref('secret-password')
const emailValue = ref('user@example.com')
</script>
<template>
<Story
title="Field Input"
group="form"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="text-empty"
title="Text Input (Empty)"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderInput
v-model="textValue"
label="Text Input"
description="A basic text input field"
placeholder="Enter text..."
/>
</div>
</Variant>
<Variant
id="password"
title="Password Input"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderInput
v-model="passwordValue"
label="Password"
description="A password input field"
placeholder="Enter password..."
type="password"
/>
</div>
</Variant>
<Variant
id="email"
title="Email Input"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderInput
v-model="emailValue"
label="Email Address"
description="An email input field"
placeholder="Enter email..."
type="email"
/>
</div>
</Variant>
<Variant
id="required"
title="Required Input"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderInput
v-model="textValue"
label="Required Field"
description="This field is required"
placeholder="Enter value..."
required
/>
</div>
</Variant>
<Variant
id="not-required"
title="Optional Input"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderInput
v-model="textValue"
label="Optional Field"
description="This field is optional"
placeholder="Enter value..."
:required="false"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,14 +1,20 @@
<script setup lang="ts">
import { computed } from 'vue'
import Input from '../Input/Input.vue'
const props = defineProps<{
modelValue: boolean
modelValue: string
label?: string
description?: string
placeholder?: string
required?: boolean
type?: string
inputClass?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'update:modelValue', value: string): void
}>()
const value = computed({
@@ -18,20 +24,23 @@ const value = computed({
</script>
<template>
<label flex="~ col gap-4">
<div flex="~ row" items-center gap-2>
<div flex="1">
<div max-w-full>
<label flex="~ col gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ label }}
<span v-if="required !== false" class="text-red-500">*</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400">
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-nowrap>
{{ description }}
</div>
</div>
<span font-mono>{{ value ? 'On' : 'Off' }}</span>
</div>
<div flex="~ row" items-center gap-2>
<input v-model="value" type="checkbox">
</div>
</label>
<Input
v-model="value"
:type="type"
:placeholder="placeholder"
:class="inputClass"
/>
</label>
</div>
</template>
@@ -0,0 +1,95 @@
<script setup lang="ts">
import { ref } from 'vue'
import FieldRange from './FieldRange.vue'
const minValue = ref(0)
const midValue = ref(0.5)
const maxValue = ref(1)
const customValue = ref(75)
function formatPercentage(value: number): string {
return `${Math.round(value * 100)}%`
}
</script>
<template>
<Story
title="Field Range"
group="form"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="default-min"
title="Default (min)"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldRange
v-model="minValue"
label="Minimum Value"
description="Slider at minimum value"
/>
</div>
</Variant>
<Variant
id="default-mid"
title="Default (mid)"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldRange
v-model="midValue"
label="Medium Value"
description="Slider at medium value"
/>
</div>
</Variant>
<Variant
id="default-max"
title="Default (max)"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldRange
v-model="maxValue"
label="Maximum Value"
description="Slider at maximum value"
/>
</div>
</Variant>
<Variant
id="custom-range"
title="Custom Range"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldRange
v-model="customValue"
label="Custom Range"
description="Slider with custom min, max, and step values"
:min="0"
:max="100"
:step="5"
/>
</div>
</Variant>
<Variant
id="formatted-value"
title="Formatted Value"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<FieldRange
v-model="midValue"
label="Formatted Value"
description="Slider with custom value formatting"
:format-value="formatPercentage"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,10 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import Range from '../../../components/Range.vue'
import Range from '../Range/Range.vue'
const props = defineProps<{
modelValue: number
min?: number
max?: number
step?: number
@@ -13,21 +10,7 @@ const props = defineProps<{
formatValue?: (value: number) => string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: number): void
}>()
const value = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
})
const formattedValue = computed(() => {
if (props.formatValue) {
return props.formatValue(value.value)
}
return value.value.toFixed(2)
})
const modelValue = defineModel<number>({ required: true })
</script>
<template>
@@ -41,11 +24,11 @@ const formattedValue = computed(() => {
{{ description }}
</div>
</div>
<span font-mono>{{ formattedValue }}</span>
<span font-mono>{{ props.formatValue?.(modelValue) }}</span>
</div>
<div flex="~ row" items-center gap-2>
<Range
v-model="value"
v-model="modelValue"
:min="min || 0"
:max="max || 1"
:step="step || 0.01"
@@ -0,0 +1,3 @@
export { default as FieldCheckbox } from './FieldCheckbox.vue'
export { default as FieldInput } from './FieldInput.vue'
export { default as FieldRange } from './FieldRange.vue'
@@ -0,0 +1,18 @@
<script setup lang="ts">
const props = defineProps<{
type?: string
}>()
const modelValue = defineModel<string>({ required: true })
</script>
<template>
<input
v-model="modelValue"
:type="props.type || 'text'"
border="neutral-100 dark:neutral-800 solid 2 focus:neutral-200 dark:focus:neutral-700"
transition="all duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:neutral-50 dark:focus:neutral-900"
>
</template>
@@ -0,0 +1 @@
export { default as Input } from './Input.vue'
@@ -0,0 +1 @@
export { default as Range } from './Range.vue'
@@ -1,3 +1,6 @@
export * from './Checkbox'
export * from './Field'
export * from './Input'
export * from './Radio'
export { default as RadioCardDetailManySelect } from './RadioCardDetailManySelect.vue'
export { default as Range } from './Range/Range.vue'
export * from './Range'
@@ -0,0 +1,71 @@
<script setup lang="ts">
import { ref } from 'vue'
import ProviderAccountIdInput from './ProviderAccountIdInput.vue'
const emptyAccountId = ref('')
const filledAccountId = ref('1234567890abcdef1234567890abcdef')
</script>
<template>
<Story
title="Account ID Input"
group="providers"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="empty"
title="Empty"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAccountIdInput
v-model="emptyAccountId"
placeholder="Your Cloudflare Account ID"
/>
</div>
</Variant>
<Variant
id="filled"
title="Filled"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAccountIdInput
v-model="filledAccountId"
placeholder="Your Cloudflare Account ID"
/>
</div>
</Variant>
<Variant
id="custom-label"
title="Custom Label"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAccountIdInput
v-model="emptyAccountId"
label="Cloudflare Account ID"
description="The ID of your Cloudflare account"
placeholder="Your Cloudflare Account ID"
/>
</div>
</Variant>
<Variant
id="not-required"
title="Not Required"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAccountIdInput
v-model="emptyAccountId"
:required="false"
placeholder="Your Cloudflare Account ID"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,45 +1,23 @@
<script setup lang="ts">
import { computed } from 'vue'
import FieldInput from '../Form/Field/FieldInput.vue'
const props = defineProps<{
modelValue: string
placeholder?: string
required?: boolean
label?: string
description?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
const value = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
})
const modelValue = defineModel<string>({ required: true })
</script>
<template>
<div max-w-full>
<label flex="~ col gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ label || 'Account ID' }}
<span v-if="required !== false" class="text-red-500">*</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-nowrap>
{{ description || 'Account ID' }}
</div>
</div>
<input
v-model="value"
type="text"
border="neutral-200 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="all duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:white dark:focus:neutral-700"
:placeholder="placeholder"
>
</label>
</div>
<FieldInput
v-model="modelValue"
:label="props.label || 'Account ID'"
:description="props.description || 'Account ID'"
:placeholder="props.placeholder"
:required="props.required"
type="text"
/>
</template>
@@ -0,0 +1,54 @@
<script setup lang="ts">
import ProviderAdvancedSettings from './ProviderAdvancedSettings.vue'
</script>
<template>
<Story
title="Advanced Settings"
group="providers"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="collapsed"
title="Collapsed (Default)"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAdvancedSettings>
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Advanced settings content (initially hidden)</p>
</div>
</ProviderAdvancedSettings>
</div>
</Variant>
<Variant
id="expanded"
title="Expanded"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAdvancedSettings :initial-visible="true">
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Advanced settings content (initially visible)</p>
</div>
</ProviderAdvancedSettings>
</div>
</Variant>
<Variant
id="custom-title"
title="Custom Title"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderAdvancedSettings title="Expert Settings">
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Expert settings content</p>
</div>
</ProviderAdvancedSettings>
</div>
</Variant>
</Story>
</template>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import Collapsable from '../../../components/Collapsable.vue'
import Collapsable from '../Collapsable.vue'
const props = defineProps<{
title?: string
@@ -0,0 +1,75 @@
<script setup lang="ts">
import { ref } from 'vue'
import ProviderApiKeyInput from './ProviderApiKeyInput.vue'
const apiKey = ref('')
const filledApiKey = ref('sk-1234567890abcdefghijklmnopqrstuvwxyz')
</script>
<template>
<Story
title="API Key Input"
group="providers"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="empty"
title="Empty"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderApiKeyInput
v-model="apiKey"
provider-name="OpenAI"
placeholder="sk-..."
/>
</div>
</Variant>
<Variant
id="filled"
title="Filled"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderApiKeyInput
v-model="filledApiKey"
provider-name="OpenAI"
placeholder="sk-..."
/>
</div>
</Variant>
<Variant
id="custom-label"
title="Custom Label"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderApiKeyInput
v-model="apiKey"
provider-name="OpenAI"
label="Secret API Key"
description="Enter your secret API key from the provider dashboard"
placeholder="sk-..."
/>
</div>
</Variant>
<Variant
id="not-required"
title="Not Required"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderApiKeyInput
v-model="apiKey"
provider-name="OpenAI"
:required="false"
placeholder="sk-..."
/>
</div>
</Variant>
</Story>
</template>
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue'
import FieldInput from '../Form/Field/FieldInput.vue'
const props = defineProps<{
modelValue: string
providerName: string
placeholder?: string
required?: boolean
@@ -10,37 +11,20 @@ const props = defineProps<{
description?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
const modelValue = defineModel<string>({ required: true })
const value = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
const computedDescription = computed(() => {
return props.description || `API Key for ${props.providerName}`
})
</script>
<template>
<div max-w-full>
<label flex="~ col gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ label || 'API Key' }}
<span v-if="required !== false" class="text-red-500">*</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-nowrap>
{{ description || `API Key for ${providerName}` }}
</div>
</div>
<input
v-model="value"
type="password"
border="neutral-200 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="all duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:white dark:focus:neutral-700"
:placeholder="placeholder"
>
</label>
</div>
<FieldInput
v-model="modelValue"
:label="label || 'API Key'"
:description="computedDescription"
:placeholder="placeholder"
:required="required"
type="password"
/>
</template>
@@ -0,0 +1,84 @@
<script setup lang="ts">
import { ref } from 'vue'
import ProviderBaseUrlInput from './ProviderBaseUrlInput.vue'
const emptyUrl = ref('')
const defaultUrl = ref('https://api.openai.com/v1/')
const customUrl = ref('https://custom-api.example.com/v1/')
</script>
<template>
<Story
title="Base URL Input"
group="providers"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="empty"
title="Empty"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBaseUrlInput
v-model="emptyUrl"
placeholder="https://api.example.com/v1/"
/>
</div>
</Variant>
<Variant
id="default-url"
title="Default URL"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBaseUrlInput
v-model="defaultUrl"
placeholder="https://api.openai.com/v1/"
/>
</div>
</Variant>
<Variant
id="custom-url"
title="Custom URL"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBaseUrlInput
v-model="customUrl"
placeholder="https://api.example.com/v1/"
/>
</div>
</Variant>
<Variant
id="required"
title="Required"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBaseUrlInput
v-model="emptyUrl"
placeholder="https://api.example.com/v1/"
required
/>
</div>
</Variant>
<Variant
id="custom-label"
title="Custom Label"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBaseUrlInput
v-model="emptyUrl"
label="API Endpoint"
description="The base URL for the API service"
placeholder="https://api.example.com/v1/"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,43 +1,23 @@
<script setup lang="ts">
import { computed } from 'vue'
import FieldInput from '../Form/Field/FieldInput.vue'
const props = defineProps<{
modelValue: string
placeholder?: string
required?: boolean
label?: string
description?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
const value = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
})
const modelValue = defineModel<string>({ required: true })
</script>
<template>
<label flex="~ col gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ label || 'Base URL' }}
<span v-if="required" class="text-red-500">*</span>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-nowrap>
{{ description || 'Custom base URL (optional)' }}
</div>
</div>
<input
v-model="value"
type="text"
border="neutral-200 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
transition="all duration-250 ease-in-out"
w-full rounded-lg px-2 py-1 text-nowrap text-sm outline-none
bg="neutral-100 dark:neutral-800 focus:white dark:focus:neutral-700"
:placeholder="placeholder"
>
</label>
<FieldInput
v-model="modelValue"
:label="props.label || 'Base URL'"
:description="props.description || 'Custom base URL (optional)'"
:placeholder="props.placeholder"
:required="props.required"
type="text"
/>
</template>
@@ -0,0 +1,60 @@
<script setup lang="ts">
import ProviderBasicSettings from './ProviderBasicSettings.vue'
</script>
<template>
<Story
title="Basic Settings"
group="providers"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="default"
title="Default"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBasicSettings>
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Settings content goes here</p>
</div>
</ProviderBasicSettings>
</div>
</Variant>
<Variant
id="with-title-description"
title="With Title & Description"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBasicSettings
title="Custom Title"
description="Custom description for this section"
>
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Settings content goes here</p>
</div>
</ProviderBasicSettings>
</div>
</Variant>
<Variant
id="with-reset"
title="With Reset Button"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderBasicSettings
title="Basic Settings"
description="Essential settings for this provider"
>
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Settings content goes here</p>
</div>
</ProviderBasicSettings>
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,54 @@
<script setup lang="ts">
import ProviderAdvancedSettings from './ProviderAdvancedSettings.vue'
import ProviderBasicSettings from './ProviderBasicSettings.vue'
import ProviderSettingsContainer from './ProviderSettingsContainer.vue'
</script>
<template>
<Story
title="Settings Container"
group="providers"
:layout="{ type: 'grid', width: 800 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="empty"
title="Empty"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderSettingsContainer>
<div class="p-4">
<p>Container content goes here</p>
</div>
</ProviderSettingsContainer>
</div>
</Variant>
<Variant
id="with-sections"
title="With Sections"
>
<div class="bg-white p-4 dark:bg-neutral-900">
<ProviderSettingsContainer>
<ProviderBasicSettings
title="Basic"
description="Essential settings"
>
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Basic settings content</p>
</div>
</ProviderBasicSettings>
<ProviderAdvancedSettings title="Advanced">
<div class="border border-neutral-200 rounded p-4 dark:border-neutral-800">
<p>Advanced settings content</p>
</div>
</ProviderAdvancedSettings>
</ProviderSettingsContainer>
</div>
</Variant>
</Story>
</template>
@@ -0,0 +1,58 @@
<script setup lang="ts">
import ProviderSettingsLayout from './ProviderSettingsLayout.vue'
</script>
<template>
<Story
title="Settings Layout"
group="providers"
:layout="{ type: 'grid', width: 800 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="default"
title="Default"
>
<div class="p-4">
<ProviderSettingsLayout provider-name="Example Provider">
<div class="mt-4 rounded-xl bg-neutral-50 p-4 dark:bg-[rgba(0,0,0,0.3)]">
<p>Provider settings content goes here</p>
</div>
</ProviderSettingsLayout>
</div>
</Variant>
<Variant
id="with-icon"
title="With Icon"
>
<div class="p-4">
<ProviderSettingsLayout
provider-name="OpenAI" provider-icon="i-lobe-icons:openai"
>
<div class="mt-4 rounded-xl bg-neutral-50 p-4 dark:bg-[rgba(0,0,0,0.3)]">
<p>Provider settings content goes here</p>
</div>
</ProviderSettingsLayout>
</div>
</Variant>
<Variant
id="with-icon-color"
title="With Icon Color"
>
<div class="p-4">
<ProviderSettingsLayout
provider-name="Mistral AI" provider-icon-color="i-lobe-icons:mistral-color"
>
<div class="mt-4 rounded-xl bg-neutral-50 p-4 dark:bg-[rgba(0,0,0,0.3)]">
<p>Provider settings content goes here</p>
</div>
</ProviderSettingsLayout>
</div>
</Variant>
</Story>
</template>
@@ -3,7 +3,5 @@ export { default as ProviderAdvancedSettings } from './ProviderAdvancedSettings.
export { default as ProviderApiKeyInput } from './ProviderApiKeyInput.vue'
export { default as ProviderBaseUrlInput } from './ProviderBaseUrlInput.vue'
export { default as ProviderBasicSettings } from './ProviderBasicSettings.vue'
export { default as ProviderCheckboxInput } from './ProviderCheckboxInput.vue'
export { default as ProviderSettingsContainer } from './ProviderSettingsContainer.vue'
export { default as ProviderSettingsLayout } from './ProviderSettingsLayout.vue'
export { default as ProviderSliderInput } from './ProviderSliderInput.vue'
+16 -12
View File
@@ -314,7 +314,7 @@ importers:
version: 1.0.0-beta.9(typescript@5.8.2)
vaul-vue:
specifier: ^0.4.0
version: 0.4.0(reka-ui@2.0.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
version: 0.4.0(reka-ui@2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.8.2)
@@ -447,7 +447,7 @@ importers:
version: 28.4.1(@babel/parser@7.26.10)(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@2.79.1))(vue@3.5.13(typescript@5.8.2))
unplugin-vue-macros:
specifier: ^2.14.5
version: 2.14.5(@vueuse/core@13.0.0(vue@3.5.13(typescript@5.8.2)))(esbuild@0.19.12)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2))
version: 2.14.5(@vueuse/core@13.0.0(vue@3.5.13(typescript@5.8.2)))(esbuild@0.25.0)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2))
unplugin-vue-markdown:
specifier: ^28.3.1
version: 28.3.1(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))
@@ -648,7 +648,7 @@ importers:
version: 1.0.0-beta.9(typescript@5.8.2)
vaul-vue:
specifier: ^0.4.0
version: 0.4.0(reka-ui@2.0.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
version: 0.4.0(reka-ui@2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.8.2)
@@ -1052,6 +1052,10 @@ importers:
version: 0.3.4
packages/stage-ui:
dependencies:
reka-ui:
specifier: ^2.1.0
version: 2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
devDependencies:
'@electron-toolkit/preload':
specifier: ^3.0.1
@@ -9962,8 +9966,8 @@ packages:
rehype@13.0.2:
resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==}
reka-ui@2.0.0:
resolution: {integrity: sha512-/1amDDvTnDIkSFbTZnUkiFWkxlc/UEe0pZsgvz3nDI15R9+yspQv/gIbM62Mo9LALK6A76NsgMNlXie04kv7PA==}
reka-ui@2.1.0:
resolution: {integrity: sha512-w4kEDEyXhIqv4QeFJeiuBc4mQP37hH/UTRpEb9dMbPdR49JG5TcV/s0+ntNRONUUW4LDLX7E1ZPcwBw5hnu0yw==}
peerDependencies:
vue: '>= 3.2.0'
@@ -22265,7 +22269,7 @@ snapshots:
rehype-stringify: 10.0.1
unified: 11.0.5
reka-ui@2.0.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)):
reka-ui@2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)):
dependencies:
'@floating-ui/dom': 1.6.13
'@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.8.2))
@@ -23531,9 +23535,9 @@ snapshots:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.35.0)
'@vueuse/core': 13.0.0(vue@3.5.13(typescript@5.8.2))
unplugin-combine@1.2.1(esbuild@0.19.12)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)):
unplugin-combine@1.2.1(esbuild@0.25.0)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)):
optionalDependencies:
esbuild: 0.19.12
esbuild: 0.25.0
rollup: 2.79.1
unplugin: 1.16.1
vite: 6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0)
@@ -23592,7 +23596,7 @@ snapshots:
transitivePeerDependencies:
- vue
unplugin-vue-macros@2.14.5(@vueuse/core@13.0.0(vue@3.5.13(typescript@5.8.2)))(esbuild@0.19.12)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)):
unplugin-vue-macros@2.14.5(@vueuse/core@13.0.0(vue@3.5.13(typescript@5.8.2)))(esbuild@0.25.0)(rollup@2.79.1)(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2)):
dependencies:
'@vue-macros/better-define': 1.11.4(vue@3.5.13(typescript@5.8.2))
'@vue-macros/boolean-prop': 0.5.5(vue@3.5.13(typescript@5.8.2))
@@ -23624,7 +23628,7 @@ snapshots:
'@vue-macros/short-vmodel': 1.5.5(vue@3.5.13(typescript@5.8.2))
'@vue-macros/volar': 0.30.15(typescript@5.8.2)(vue-tsc@2.2.8(typescript@5.8.2))(vue@3.5.13(typescript@5.8.2))
unplugin: 1.16.1
unplugin-combine: 1.2.1(esbuild@0.19.12)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))
unplugin-combine: 1.2.1(esbuild@0.25.0)(rollup@2.79.1)(unplugin@1.16.1)(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.2)(terser@5.17.6)(tsx@4.19.3)(yaml@2.7.0))
unplugin-vue-define-options: 1.5.5(vue@3.5.13(typescript@5.8.2))
vue: 3.5.13(typescript@5.8.2)
transitivePeerDependencies:
@@ -23810,10 +23814,10 @@ snapshots:
vary@1.1.2: {}
vaul-vue@0.4.0(reka-ui@2.0.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)):
vaul-vue@0.4.0(reka-ui@2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2)):
dependencies:
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.2))
reka-ui: 2.0.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
reka-ui: 2.1.0(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
vue: 3.5.13(typescript@5.8.2)
transitivePeerDependencies:
- '@vue/composition-api'