feat(stage-ui): optimize card many select component layout styles (#1096)

---------

Co-authored-by: Rin <shinohara-rin@users.noreply.github.com>
This commit is contained in:
Chara
2026-03-12 01:55:12 +08:00
committed by GitHub
co-authored by Rin
parent da62f1c16b
commit d9582d52f2
15 changed files with 62 additions and 35 deletions
@@ -296,6 +296,7 @@ pages:
provider-model-selection:
collapse: Collapse
custom_model_placeholder: Enter custom model name...
current_model_label: 'Current model:'
description: Select the suitable LLM provider for consciousness
error: Error loading models
health_check_failed: Health check failed
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: Colapsar
custom_model_placeholder: Ingresa el nombre del modelo personalizado...
current_model_label: 'Modelo actual seleccionado'
description: Selecciona el proveedor LLM adecuado para la consciencia
error: Error cargando modelos
health_check_failed: Health check failed
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: Réduire
custom_model_placeholder: Entrez le nom du modèle personnalisé...
current_model_label: 'Modèle actuel sélectionné:'
description: Sélectionnez le fournisseur LLM approprié pour la conscience
error: Erreur lors du chargement des modèles
health_check_failed: Échec de la vérification de l'état de santé
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: 折りたたむ
custom_model_placeholder: カスタムモデル名を入力…
current_model_label: '現在選択中のモデル:'
description: 意識に適したLLMプロバイダーを選択
error: モデルの読み込みエラー
health_check_failed: ヘルスチェックに失敗しました
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: 접기
custom_model_placeholder: 사용자 지정 모델 이름 입력...
current_model_label: '현재 선택된 모델:'
description: 의식에 적합한 LLM 제공자를 선택합니다.
error: 모델 목록을 불러오는데 오류가 발생했습니다.
health_check_failed: 상태 점검 실패
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: Свернуть
custom_model_placeholder: Введите название кастомной модели...
current_model_label: 'Текущая выбранная модель:'
description: Выберите подходящего LLM-провайдера для сознания
error: Ошибка загрузки моделей
health_check_failed: Health check failed
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: Thu gọn
custom_model_placeholder: Nhập tên mô hình tùy chỉnh...
current_model_label: 'Mô hình hiện được chọn'
description: Chọn nhà cung cấp LLM phù hợp cho ý thức
error: Lỗi tải mô hình
health_check_failed: Health check failed
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: 折叠
custom_model_placeholder: 输入自订模型名称
current_model_label: '当前选择模型:'
description: 为意识选择合适的 LLM 服务来源
error: 获取出错啦
health_check_failed: 健康检查失败
@@ -284,6 +284,7 @@ pages:
provider-model-selection:
collapse: 摺疊
custom_model_placeholder: 輸入自訂模型名稱
current_model_label: '目前選擇模型:'
description: 為意識選擇合適的 LLM 服務來源
error: 取得發生錯誤
health_check_failed: 健康度檢查失敗
@@ -145,8 +145,9 @@ function handleDeleteProvider(providerId: string) {
<h2 class="text-lg md:text-2xl">
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<div class="flex flex-col items-start gap-1 text-neutral-400 md:flex-row md:items-center md:justify-between dark:text-neutral-400">
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}</span>
<span v-if="activeModel" class="text-sm text-neutral-400 font-medium dark:text-neutral-400">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.current_model_label') }} {{ activeModel }}</span>
</div>
</div>
@@ -195,7 +196,7 @@ function handleDeleteProvider(providerId: string) {
<RadioCardManySelect
v-model="activeModel"
v-model:search-query="modelSearchQuery"
:items="providerModels.sort((a, b) => a.id === activeModel ? -1 : b.id === activeModel ? 1 : 0)"
:items="providerModels"
:searchable="true"
:allow-custom="true"
:search-placeholder="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.search_placeholder')"
@@ -585,14 +585,14 @@ onUnmounted(() => {
<h2 class="text-lg md:text-2xl">
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<!-- Show different description based on whether provider supports model listing and has models -->
<div class="flex flex-col items-start gap-1 text-neutral-400 md:flex-row md:items-center md:justify-between dark:text-neutral-400">
<span v-if="supportsModelListing && providerModels.length > 0">
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}
</span>
<span v-else>
Enter the transcription model to use (e.g., 'whisper-1', 'gpt-4o-transcribe')
</span>
<span v-if="activeTranscriptionModel" class="text-sm text-neutral-400 font-medium dark:text-neutral-400">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.current_model_label') }} {{ activeTranscriptionModel }}</span>
</div>
</div>
@@ -335,8 +335,9 @@ function handleDeleteProvider(providerId: string) {
<h2 class="text-lg md:text-2xl">
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.title') }}
</h2>
<div text="neutral-400 dark:neutral-400">
<div class="flex flex-col items-start gap-1 text-neutral-400 md:flex-row md:items-center md:justify-between dark:text-neutral-400">
<span>{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.subtitle') }}</span>
<span v-if="activeSpeechModel" class="text-sm text-neutral-400 font-medium dark:text-neutral-400">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.current_model_label') }} {{ activeSpeechModel }}</span>
</div>
</div>
@@ -14,6 +14,7 @@ interface Item {
interface Props {
items: Item[]
columns?: number
searchable?: boolean
searchPlaceholder?: string
searchNoResultsTitle?: string
@@ -29,6 +30,7 @@ interface Props {
}
const props = withDefaults(defineProps<Props>(), {
columns: 2,
searchable: true,
searchPlaceholder: 'Search...',
searchNoResultsTitle: 'No results found',
@@ -92,6 +94,10 @@ const filteredItems = computed(() => {
return result
})
const showExpandCollapseBtn = computed(() => {
return filteredItems.value.length > props.columns
})
function updateCustomValue(value: string) {
customValue.value = value
emit('update:customValue', value)
@@ -138,9 +144,10 @@ function updateCustomValue(value: string) {
<!-- Responsive grid container -->
<div
:class="[
'grid gap-4 mb-2',
isListExpanded
? 'grid grid-cols-1 md:grid-cols-2 gap-4'
: 'grid auto-cols-[min(300px,calc((100vw-5rem)/2))] grid-flow-col gap-4 overflow-x-auto scrollbar-none pb-2',
? 'grid-cols-1 md:grid-cols-[repeat(var(--cols),minmax(0,1fr))] snap-y snap-proximity'
: 'grid-flow-col auto-cols-[calc((100%-(var(--cols)-1)*1rem)/var(--cols))] overflow-x-auto scrollbar-none snap-x snap-proximity',
...(props.listClass
? (typeof props.listClass === 'string'
? [props.listClass]
@@ -152,7 +159,7 @@ function updateCustomValue(value: string) {
),
]"
transition="all duration-200 ease-in-out"
:style="isListExpanded ? '' : 'scroll-snap-type: x mandatory;'"
:style="{ '--cols': props.columns }"
>
<RadioCardDetail
v-for="item in filteredItems"
@@ -169,13 +176,14 @@ function updateCustomValue(value: string) {
:custom-input-value="customValue"
:custom-input-placeholder="customInputPlaceholder"
name="radio-card-detail-many-select"
class="scroll-snap-align-start"
class="snap-start"
@update:custom-input-value="updateCustomValue($event)"
/>
</div>
<!-- Expand/collapse handle -->
<div
v-if="showExpandCollapseBtn"
bg="neutral-100 dark:[rgba(0,0,0,0.3)]"
rounded-xl
:class="[
@@ -4,7 +4,6 @@ import { ref } from 'vue'
import VoiceCardManySelect from './voice-card-many-select.vue'
const selectedVoiceId = ref('voice1')
const customValue = ref('')
const searchQuery = ref('')
// Sample voice data
@@ -44,10 +43,6 @@ function handleVoiceIdUpdate(value: string) {
selectedVoiceId.value = value
}
function handleCustomValueUpdate(value: string) {
customValue.value = value
}
function handleSearchQueryUpdate(value: string) {
searchQuery.value = value
}
@@ -66,9 +61,8 @@ function handleSearchQueryUpdate(value: string) {
<Variant id="default" title="Default">
<VoiceCardManySelect
:voices="voices"
:selected-voice-id="selectedVoiceId"
@update:selected-voice-id="handleVoiceIdUpdate"
@update:custom-value="handleCustomValueUpdate"
:voice-id="selectedVoiceId"
@update:voice-id="handleVoiceIdUpdate"
@update:search-query="handleSearchQueryUpdate"
/>
</Variant>
@@ -76,20 +70,28 @@ function handleSearchQueryUpdate(value: string) {
<Variant id="no-search" title="Without Search">
<VoiceCardManySelect
:voices="voices"
:selected-voice-id="selectedVoiceId"
:voice-id="selectedVoiceId"
:searchable="false"
@update:selected-voice-id="handleVoiceIdUpdate"
@update:custom-value="handleCustomValueUpdate"
@update:voice-id="handleVoiceIdUpdate"
/>
</Variant>
<Variant id="no-visualizer" title="Without Visualizer">
<VoiceCardManySelect
:voices="voices"
:selected-voice-id="selectedVoiceId"
:voice-id="selectedVoiceId"
:show-visualizer="false"
@update:selected-voice-id="handleVoiceIdUpdate"
@update:custom-value="handleCustomValueUpdate"
@update:voice-id="handleVoiceIdUpdate"
@update:search-query="handleSearchQueryUpdate"
/>
</Variant>
<Variant id="custom-columns" title="Custom Columns">
<VoiceCardManySelect
:voices="voices"
:voice-id="selectedVoiceId"
:columns="3"
@update:voice-id="handleVoiceIdUpdate"
@update:search-query="handleSearchQueryUpdate"
/>
</Variant>
@@ -97,7 +99,7 @@ function handleSearchQueryUpdate(value: string) {
<Variant id="custom-text" title="Custom Text">
<VoiceCardManySelect
:voices="voices"
:selected-voice-id="selectedVoiceId"
:voice-id="selectedVoiceId"
search-placeholder="Find a voice..."
search-no-results-title="No matching voices"
search-no-results-description="Please try another search term"
@@ -107,8 +109,7 @@ function handleSearchQueryUpdate(value: string) {
collapse-button-text="Collapse list"
play-button-text="Listen"
pause-button-text="Stop"
@update:selected-voice-id="handleVoiceIdUpdate"
@update:custom-value="handleCustomValueUpdate"
@update:voice-id="handleVoiceIdUpdate"
@update:search-query="handleSearchQueryUpdate"
/>
</Variant>
@@ -116,9 +117,8 @@ function handleSearchQueryUpdate(value: string) {
<Variant id="empty" title="No Voices">
<VoiceCardManySelect
:voices="[]"
:selected-voice-id="selectedVoiceId"
@update:selected-voice-id="handleVoiceIdUpdate"
@update:custom-value="handleCustomValueUpdate"
:voice-id="selectedVoiceId"
@update:voice-id="handleVoiceIdUpdate"
@update:search-query="handleSearchQueryUpdate"
/>
</Variant>
@@ -126,9 +126,8 @@ function handleSearchQueryUpdate(value: string) {
<Variant id="many-voices" title="Many Voices">
<VoiceCardManySelect
:voices="[...voices, ...voices, ...voices]"
:selected-voice-id="selectedVoiceId"
@update:selected-voice-id="handleVoiceIdUpdate"
@update:custom-value="handleCustomValueUpdate"
:voice-id="selectedVoiceId"
@update:voice-id="handleVoiceIdUpdate"
@update:search-query="handleSearchQueryUpdate"
/>
</Variant>
@@ -29,6 +29,7 @@ interface Voice {
interface Props {
voices: Voice[]
columns?: number
searchable?: boolean
searchPlaceholder?: string
searchNoResultsTitle?: string
@@ -46,6 +47,7 @@ interface Props {
}
const props = withDefaults(defineProps<Props>(), {
columns: 2,
searchable: true,
searchPlaceholder: 'Search voices...',
searchNoResultsTitle: 'No voices found',
@@ -111,6 +113,10 @@ const filteredVoices = computed(() => {
})
})
const showExpandCollapseBtn = computed(() => {
return filteredVoices.value.length > props.columns
})
// Get preview URL from either field
function getPreviewUrl(voice: Voice): string | undefined {
return voice.previewURL || voice.preview_audio_url
@@ -341,9 +347,10 @@ const customVoiceName = ref('')
<!-- Responsive grid container -->
<div
:class="[
'grid gap-4 mb-2',
isListExpanded
? 'grid grid-cols-1 md:grid-cols-2 gap-4'
: 'grid auto-cols-[min(300px,calc((100vw-5rem)/2))] grid-flow-col gap-4 overflow-x-auto scrollbar-none pb-2',
? 'grid-cols-1 md:grid-cols-[repeat(var(--cols),minmax(0,1fr))] snap-y snap-proximity'
: 'grid-flow-col auto-cols-[calc((100%-(var(--cols)-1)*1rem)/var(--cols))] overflow-x-auto scrollbar-none snap-x snap-proximity',
...(props.listClass
? (typeof props.listClass === 'string'
? [props.listClass]
@@ -355,7 +362,7 @@ const customVoiceName = ref('')
),
]"
transition="all duration-200 ease-in-out"
:style="isListExpanded ? '' : 'scroll-snap-type: x mandatory;'"
:style="{ '--cols': props.columns }"
>
<!-- Not support voices warning -->
<Alert v-if="!searchQuery && filteredVoices.length === 0" type="warning">
@@ -374,6 +381,7 @@ const customVoiceName = ref('')
v-model:voice-id="voiceId"
v-model:custom-voice-name="customVoiceName"
name="voice"
class="snap-start"
:voice="voice"
:currently-playing-id="currentlyPlayingId"
:custom-input-placeholder="customInputPlaceholder"
@@ -385,6 +393,7 @@ const customVoiceName = ref('')
<!-- Expand/collapse handle -->
<div
v-if="showExpandCollapseBtn"
bg="neutral-100 dark:[rgba(0,0,0,0.3)]"
rounded-xl
:class="[