Skip to content

Commit 698bfb0

Browse files
feat: ollama setting pages now has validating animation (#523)
1 parent a817111 commit 698bfb0

4 files changed

Lines changed: 100 additions & 47 deletions

File tree

apps/stage-web/src/pages/settings/providers/ollama.vue

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const { t } = useI18n()
2020
const router = useRouter()
2121
const providersStore = useProvidersStore()
2222
const { providers } = storeToRefs(providersStore) as { providers: RemovableRef<Record<string, any>> }
23+
const loading = ref(0)
2324
2425
// Get provider metadata
2526
const providerId = 'ollama'
@@ -74,6 +75,11 @@ watch(headers, (headers) => {
7475
})
7576
7677
async function refetch() {
78+
loading.value++
79+
// service startup time
80+
const startValidationTimestamp = performance.now()
81+
let finalValidationMessage = ''
82+
7783
try {
7884
const validationResult = await providerMetadata.value.validators.validateProviderConfig({
7985
baseUrl: baseUrl.value,
@@ -84,16 +90,25 @@ async function refetch() {
8490
})
8591
8692
if (!validationResult.valid) {
87-
validationMessage.value = t('settings.dialogs.onboarding.validationError', {
93+
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
8894
error: validationResult.reason,
8995
})
9096
}
97+
else {
98+
finalValidationMessage = ''
99+
}
91100
}
92101
catch (error) {
93-
validationMessage.value = t('settings.dialogs.onboarding.validationError', {
102+
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
94103
error: error instanceof Error ? error.message : String(error),
95104
})
96105
}
106+
finally {
107+
setTimeout(() => {
108+
loading.value--
109+
validationMessage.value = finalValidationMessage
110+
}, 500 - (performance.now() - startValidationTimestamp))
111+
}
97112
}
98113
99114
watch([baseUrl, headers], refetch, { immediate: true })
@@ -122,47 +137,59 @@ function handleResetSettings() {
122137
</script>
123138

124139
<template>
125-
<Alert v-if="validationMessage" type="error">
126-
<template #title>
127-
{{ t('settings.dialogs.onboarding.validationFailed') }}
128-
</template>
129-
<template v-if="validationMessage" #content>
130-
<div class="whitespace-pre-wrap break-all">
131-
{{ validationMessage }}
132-
</div>
133-
</template>
134-
</Alert>
135-
<ProviderSettingsLayout
136-
:provider-name="providerMetadata?.localizedName"
137-
:provider-icon="providerMetadata?.icon"
138-
:on-back="() => router.back()"
139-
>
140-
<ProviderSettingsContainer>
141-
<ProviderBasicSettings
142-
:title="t('settings.pages.providers.common.section.basic.title')"
143-
:description="t('settings.pages.providers.common.section.basic.description')"
144-
:on-reset="handleResetSettings"
145-
>
146-
<ProviderBaseUrlInput
147-
v-model="baseUrl"
148-
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || ''"
149-
required
150-
/>
151-
</ProviderBasicSettings>
152-
153-
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
154-
<FieldKeyValues
155-
v-model="headers"
156-
:label="t('settings.pages.providers.common.section.advanced.fields.field.headers.label')"
157-
:description="t('settings.pages.providers.common.section.advanced.fields.field.headers.description')"
158-
:key-placeholder="t('settings.pages.providers.common.section.advanced.fields.field.headers.key.placeholder')"
159-
:value-placeholder="t('settings.pages.providers.common.section.advanced.fields.field.headers.value.placeholder')"
160-
@add="(key: string, value: string) => addKeyValue(headers, key, value)"
161-
@remove="(index: number) => removeKeyValue(index, headers)"
162-
/>
163-
</ProviderAdvancedSettings>
164-
</ProviderSettingsContainer>
165-
</ProviderSettingsLayout>
140+
<div class="flex flex-col gap-4">
141+
<Alert v-if="!!loading" type="loading">
142+
<template #title>
143+
{{ t('settings.pages.providers.provider.common.status.validating') }}
144+
</template>
145+
</Alert>
146+
<Alert v-else-if="!validationMessage" type="success">
147+
<template #title>
148+
{{ t('settings.pages.providers.provider.common.status.valid') }}
149+
</template>
150+
</Alert>
151+
<Alert v-else-if="validationMessage" type="error">
152+
<template #title>
153+
{{ t('settings.dialogs.onboarding.validationFailed') }}
154+
</template>
155+
<template v-if="validationMessage" #content>
156+
<div class="whitespace-pre-wrap break-all">
157+
{{ validationMessage }}
158+
</div>
159+
</template>
160+
</Alert>
161+
<ProviderSettingsLayout
162+
:provider-name="providerMetadata?.localizedName"
163+
:provider-icon="providerMetadata?.icon"
164+
:on-back="() => router.back()"
165+
>
166+
<ProviderSettingsContainer>
167+
<ProviderBasicSettings
168+
:title="t('settings.pages.providers.common.section.basic.title')"
169+
:description="t('settings.pages.providers.common.section.basic.description')"
170+
:on-reset="handleResetSettings"
171+
>
172+
<ProviderBaseUrlInput
173+
v-model="baseUrl"
174+
:placeholder="providerMetadata?.defaultOptions?.().baseUrl as string || ''"
175+
required
176+
/>
177+
</ProviderBasicSettings>
178+
179+
<ProviderAdvancedSettings :title="t('settings.pages.providers.common.section.advanced.title')">
180+
<FieldKeyValues
181+
v-model="headers"
182+
:label="t('settings.pages.providers.common.section.advanced.fields.field.headers.label')"
183+
:description="t('settings.pages.providers.common.section.advanced.fields.field.headers.description')"
184+
:key-placeholder="t('settings.pages.providers.common.section.advanced.fields.field.headers.key.placeholder')"
185+
:value-placeholder="t('settings.pages.providers.common.section.advanced.fields.field.headers.value.placeholder')"
186+
@add="(key: string, value: string) => addKeyValue(headers, key, value)"
187+
@remove="(index: number) => removeKeyValue(index, headers)"
188+
/>
189+
</ProviderAdvancedSettings>
190+
</ProviderSettingsContainer>
191+
</ProviderSettingsLayout>
192+
</div>
166193
</template>
167194

168195
<route lang="yaml">

packages/i18n/src/locales/en/settings.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ pages:
334334
placeholder: Input Cloudflare API Key
335335
title: Cloudflare Workers AI
336336
common:
337+
status:
338+
validating: Validating
339+
valid: Configuration seems to be valid.
337340
fields:
338341
field:
339342
pitch:

packages/i18n/src/locales/zh-Hans/settings.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ pages:
305305
placeholder: 请输入 Cloudflare Workers AI 的 API Key
306306
title: Workers AI
307307
common:
308+
status:
309+
validating: 正在验证配置
310+
valid: 配置似乎有效。
308311
fields:
309312
field:
310313
pitch:

packages/stage-ui/src/components/Misc/Alert.vue

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
<script setup lang="ts">
2-
import { computed } from 'vue'
2+
import { computed, useSlots } from 'vue'
33
44
const props = defineProps<{
5-
type?: 'error' | 'warning'
5+
type?: 'error' | 'warning' | 'success' | 'info' | 'loading'
66
}>()
77
8+
const slots = useSlots()
9+
810
const containerClass = computed(() => {
911
switch (props.type) {
1012
case 'error':
1113
return 'border-solid border-2 border-red-200 bg-red-50 dark:border-red-800/30 dark:bg-red-900/20'
1214
case 'warning':
1315
return 'border-solid border-2 border-amber-200 bg-amber-50 dark:border-amber-800/30 dark:bg-amber-900/20'
16+
case 'success':
17+
return 'border-solid border-2 border-green-200 bg-green-50 dark:border-green-800/30 text-green-700 dark:bg-green-900/30 dark:text-green-300'
18+
case 'info':
19+
return 'border-solid border-2 border-blue-200 bg-blue-50 dark:border-blue-800/30 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300'
20+
case 'loading':
21+
return 'border-solid border-2 border-blue-200 bg-blue-50 dark:border-blue-800/30 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300'
1422
}
1523
return ''
1624
})
@@ -21,6 +29,12 @@ const iconClass = computed(() => {
2129
return 'i-solar:close-circle-bold-duotone text-red-500 dark:text-red-400'
2230
case 'warning':
2331
return 'i-solar:danger-circle-bold-duotone text-amber-500 dark:text-amber-400'
32+
case 'success':
33+
return 'i-solar:check-circle-bold-duotone text-green-500 dark:text-green-400'
34+
case 'info':
35+
return 'i-solar:info-circle-bold-duotone text-blue-500 dark:text-blue-400'
36+
case 'loading':
37+
return 'i-svg-spinners:3-dots-fade text-blue-500 dark:text-blue-400'
2438
}
2539
return ''
2640
})
@@ -31,20 +45,26 @@ const titleClass = computed(() => {
3145
return 'text-red-500 dark:text-red-400'
3246
case 'warning':
3347
return 'text-amber-500 dark:text-amber-400'
48+
case 'success':
49+
return 'text-green-500 dark:text-green-400'
50+
case 'info':
51+
return 'text-blue-500 dark:text-blue-400'
52+
case 'loading':
53+
return 'text-blue-500 dark:text-blue-400'
3454
}
3555
return ''
3656
})
3757
</script>
3858

3959
<template>
40-
<div class="flex flex-col gap-3 rounded-xl px-2 pb-3 pt-2" :class="containerClass">
60+
<div class="flex flex-col gap-3 rounded-xl p-2" :class="containerClass">
4161
<div class="flex items-center gap-1.5 font-medium">
4262
<div class="text-2xl" :class="iconClass" />
4363
<div :class="titleClass">
4464
<slot name="title" />
4565
</div>
4666
</div>
47-
<div class="px-1 text-sm">
67+
<div v-if="slots.content" class="px-1 text-sm">
4868
<slot name="content" />
4969
</div>
5070
</div>

0 commit comments

Comments
 (0)