refactor(stage-ui): <Alert> <ErrorContainer> (#295)

---------

Co-authored-by: Neko <neko@ayaka.moe>
This commit is contained in:
Typed SIGTERM
2025-07-19 01:17:07 +08:00
committed by GitHub
co-authored by Neko
parent 532066e684
commit eeadf3b92d
14 changed files with 194 additions and 177 deletions
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { ccv3 } from '@proj-airi/ccc'
import { Alert } from '@proj-airi/stage-ui/components'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
import { InputFile } from '@proj-airi/ui'
import { Select } from '@proj-airi/ui/components/form'
@@ -255,18 +256,18 @@ function getModuleShortName(id: string, module: 'consciousness' | 'voice') {
</div>
<!-- No search results -->
<div
<Alert
v-if="searchQuery && sortedFilteredCards.length === 0"
class="col-span-full flex items-center gap-3 border-2 border-amber-200 rounded-xl bg-amber-50/80 p-4 dark:border-amber-800 dark:bg-amber-900/30"
type="warning"
class="col-span-full"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.card.no_results') }}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">
{{ t('settings.pages.card.try_different_search') }}
</span>
</div>
</div>
<template #title>
{{ t('settings.pages.card.no_results') }}
</template>
<template #content>
{{ t('settings.pages.card.try_different_search') }}
</template>
</Alert>
</div>
</div>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
import { Alert, ErrorContainer, RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
import { useConsciousnessStore, useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
@@ -109,30 +109,24 @@ function updateCustomModelName(value: string) {
</div>
<!-- Error state -->
<div
<ErrorContainer
v-else-if="activeProviderModelError"
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
</div>
</div>
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
:error="activeProviderModelError"
/>
<!-- No models available -->
<div
<Alert
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
type="warning"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
}}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">{{
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
</div>
</div>
<template #title>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
</template>
<template #content>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
</template>
</Alert>
<!-- Using the new RadioCardManySelect component -->
<template v-else-if="providerModels.length > 0">
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
import { ErrorContainer, LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
import { FieldCheckbox, FieldRange, FieldSelect } from '@proj-airi/ui'
import { useDevicesList } from '@vueuse/core'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
@@ -432,10 +432,7 @@ onUnmounted(() => {
<span class="text-sm">Loading...</span>
</div>
<div v-else-if="vadModelError" class="flex items-center gap-2 text-red-600 dark:text-red-400">
<div class="text-sm" i-solar:close-circle-bold-duotone />
<span class="text-sm">Inference error: {{ vadModelError }}</span>
</div>
<ErrorContainer v-else-if="vadModelError" title="Inference error" :error="vadModelError" />
<div v-else-if="isVADModelLoaded" class="flex items-center gap-2 text-green-600 dark:text-green-400">
<div class="text-sm" i-solar:check-circle-bold-duotone />
@@ -2,6 +2,8 @@
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
import {
Alert,
ErrorContainer,
RadioCardManySelect,
RadioCardSimple,
Skeleton,
@@ -230,30 +232,24 @@ function updateCustomModelName(value: string) {
</div>
<!-- Error state -->
<div
<ErrorContainer
v-else-if="activeProviderModelError"
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
</div>
</div>
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
:error="activeProviderModelError"
/>
<!-- No models available -->
<div
<Alert
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
type="warning"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
}}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">{{
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
</div>
</div>
<template #title>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
</template>
<template #content>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
</template>
</Alert>
<!-- Using the new RadioCardManySelect component -->
<template v-else-if="providerModels.length > 0">
@@ -342,29 +338,21 @@ function updateCustomModelName(value: string) {
/>
</div>
<div
<ErrorContainer
v-else-if="speechProviderError"
class="flex items-center gap-3 border border-2 border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">Error loading voices</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ speechProviderError }}</span>
</div>
</div>
title="Error loading voices"
:error="speechProviderError"
/>
<!-- No voices available -->
<div
v-else
class="flex items-center gap-3 border border-2 border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">No voices available</span>
<span class="text-sm text-amber-600 dark:text-amber-400">
No voices were found for this provider. You can enter a custom voice name below.
</span>
</div>
</div>
<Alert v-else type="warning">
<template #title>
No voices available
</template>
<template #content>
No voices were found for this provider. You can enter a custom voice name below.
</template>
</Alert>
<!-- Voice parameters -->
<div flex="~ col gap-4">
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { ccv3 } from '@proj-airi/ccc'
import { Alert } from '@proj-airi/stage-ui/components'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
import { InputFile } from '@proj-airi/ui'
import { Select } from '@proj-airi/ui/components/form'
@@ -255,18 +256,14 @@ function getModuleShortName(id: string, module: 'consciousness' | 'voice') {
</div>
<!-- No search results -->
<div
v-if="searchQuery && sortedFilteredCards.length === 0"
class="col-span-full flex items-center gap-3 border-2 border-amber-200 rounded-xl bg-amber-50/80 p-4 dark:border-amber-800 dark:bg-amber-900/30"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.card.no_results') }}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">
{{ t('settings.pages.card.try_different_search') }}
</span>
</div>
</div>
<Alert v-if="searchQuery && sortedFilteredCards.length === 0" type="warning">
<template #title>
{{ t('settings.pages.card.no_results') }}
</template>
<template #content>
{{ t('settings.pages.card.try_different_search') }}
</template>
</Alert>
</div>
</div>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
import { Alert, ErrorContainer, RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
import { useConsciousnessStore, useProvidersStore } from '@proj-airi/stage-ui/stores'
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
@@ -109,30 +109,24 @@ function updateCustomModelName(value: string) {
</div>
<!-- Error state -->
<div
<ErrorContainer
v-else-if="activeProviderModelError"
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
</div>
</div>
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
:error="activeProviderModelError"
/>
<!-- No models available -->
<div
<Alert
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
type="warning"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
}}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">{{
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
</div>
</div>
<template #title>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
</template>
<template #content>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
</template>
</Alert>
<!-- Using the new RadioCardManySelect component -->
<template v-else-if="providerModels.length > 0">
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
import { ErrorContainer, LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
import { FieldCheckbox, FieldRange, FieldSelect } from '@proj-airi/ui'
import { useDevicesList } from '@vueuse/core'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
@@ -430,10 +430,11 @@ onUnmounted(() => {
<span class="text-sm">Loading...</span>
</div>
<div v-else-if="vadModelError" class="flex items-center gap-2 text-red-600 dark:text-red-400">
<div class="text-sm" i-solar:close-circle-bold-duotone />
<span class="text-sm">Inference error: {{ vadModelError }}</span>
</div>
<ErrorContainer
v-else-if="vadModelError"
title="Inference error"
:error="vadModelError"
/>
<div v-else-if="isVADModelLoaded" class="flex items-center gap-2 text-green-600 dark:text-green-400">
<div class="text-sm" i-solar:check-circle-bold-duotone />
@@ -2,6 +2,8 @@
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
import {
Alert,
ErrorContainer,
RadioCardManySelect,
RadioCardSimple,
Skeleton,
@@ -230,30 +232,21 @@ function updateCustomModelName(value: string) {
</div>
<!-- Error state -->
<div
<ErrorContainer
v-else-if="activeProviderModelError"
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
</div>
</div>
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
:error="activeProviderModelError"
/>
<!-- No models available -->
<div
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
}}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">{{
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
</div>
</div>
<Alert v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels" type="warning">
<template #title>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
</template>
<template #content>
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
</template>
</Alert>
<!-- Using the new RadioCardManySelect component -->
<template v-else-if="providerModels.length > 0">
@@ -342,29 +335,27 @@ function updateCustomModelName(value: string) {
/>
</div>
<div
<ErrorContainer
v-else-if="speechProviderError"
class="flex items-center gap-3 border border-2 border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
>
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
<div class="flex flex-col">
<span class="font-medium">Error loading voices</span>
<span class="text-sm text-red-600 dark:text-red-400">{{ speechProviderError }}</span>
</div>
</div>
class="mb-2"
title="Error loading voices"
:error="speechProviderError"
/>
<!-- No voices available -->
<div
<Alert
v-else
class="flex items-center gap-3 border border-2 border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
type="warning"
icon="i-solar:info-circle-line-duotone"
class="mb-2"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">No voices available</span>
<span class="text-sm text-amber-600 dark:text-amber-400">
No voices were found for this provider. You can enter a custom voice name below.
</span>
</div>
</div>
<template #title>
No voices available
</template>
<template #content>
No voices were found for this provider. You can enter a custom voice name below.
</template>
</Alert>
<!-- Voice parameters -->
<div flex="~ col gap-4">
+1 -1
View File
@@ -320,7 +320,7 @@ pages:
label: Account ID
placeholder: Your Cloudflare Account ID
api-key:
placeholder: Input Cloudflare Account ID
placeholder: Input Cloudflare API Key
title: Cloudflare Workers AI
common:
fields:
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import Alert from '../Misc/Alert.vue'
import RadioCardDetail from './RadioCardDetail.vue'
interface Item {
@@ -91,18 +92,14 @@ function updateCustomValue(value: string) {
</div>
<!-- No search results -->
<div
v-if="searchQuery && filteredItems.length === 0"
class="flex items-center gap-3 border border-2 border-amber-200 rounded-xl bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ searchNoResultsTitle }}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
</span>
</div>
</div>
<Alert v-if="searchQuery && filteredItems.length === 0" type="warning">
<template #title>
{{ searchNoResultsTitle }}
</template>
<template #content>
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
</template>
</Alert>
<!-- Items grid -->
<div class="relative">
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import Alert from '../Misc/Alert.vue'
import VoiceCard from './VoiceCard.vue'
interface VoiceLanguage {
@@ -322,18 +323,14 @@ const customVoiceName = ref('')
</div>
<!-- No search results -->
<div
v-if="searchQuery && filteredVoices.length === 0"
class="flex items-center gap-3 border border-2 border-amber-200 rounded-xl bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
>
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
<div class="flex flex-col">
<span class="font-medium">{{ searchNoResultsTitle }}</span>
<span class="text-sm text-amber-600 dark:text-amber-400">
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
</span>
</div>
</div>
<Alert v-if="searchQuery && filteredVoices.length === 0" type="warning">
<template #title>
{{ searchNoResultsTitle }}
</template>
<template #content>
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
</template>
</Alert>
<!-- Voices grid -->
<div class="relative">
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
type?: 'error' | 'warning'
}>()
const containerClass = computed(() => {
switch (props.type) {
case 'error':
return 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/20'
case 'warning':
return 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-900/20'
}
return ''
})
const iconClass = computed(() => {
switch (props.type) {
case 'error':
return 'i-solar:close-circle-line-duotone text-red-500 dark:text-red-400'
case 'warning':
return 'i-solar:danger-circle-line-duotone text-amber-500 dark:text-amber-400'
}
return ''
})
</script>
<template>
<div class="flex flex-col gap-3 border rounded-lg p-4" :class="containerClass">
<div class="flex items-center gap-1 font-medium">
<div class="text-2xl" :class="iconClass" />
<slot name="title" />
</div>
<div class="text-sm">
<slot name="content" />
</div>
</div>
</template>
@@ -0,0 +1,19 @@
<script setup lang="ts">
import Alert from './Alert.vue'
defineProps<{
title?: string
error?: any
}>()
</script>
<template>
<Alert type="error">
<template #title>
{{ title }}
</template>
<template #content>
<pre class="whitespace-pre-wrap break-all">{{ String(error) }}</pre>
</template>
</Alert>
</template>
@@ -1,6 +1,8 @@
export { default as Alert } from './Alert.vue'
export { default as BidirectionalTransition } from './BidirectionalTransition.vue'
export { default as Button } from './Button.vue'
export { default as Collapsable } from './Collapsable.vue'
export { default as ErrorContainer } from './ErrorContainer.vue'
export { default as Progress } from './Progress.vue'
export { default as Screen } from './Screen.vue'
export { default as Skeleton } from './Skeleton.vue'