refactor(stage-*): de-centralize for route header

This commit is contained in:
Neko Ayaka
2026-01-18 04:54:57 +08:00
parent e796ee59ad
commit 17debf3e96
49 changed files with 242 additions and 359 deletions
@@ -1,3 +1,7 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
+42 -128
View File
@@ -2,7 +2,6 @@
import { PageHeader } from '@proj-airi/stage-ui/components'
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
import { useTheme } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterView, useRoute } from 'vue-router'
@@ -15,139 +14,54 @@ const route = useRoute()
const { isDark: dark } = useTheme()
const { t } = useI18n()
const providersStore = useProvidersStore()
const { allProvidersMetadata } = storeToRefs(providersStore)
const routeMeta = computed(() => route.meta as {
titleKey?: string
subtitleKey?: string
title?: string
subtitle?: string
})
const routeHeaderMetadataMap = computed(() => {
const map: Record<string, { subtitle?: string, title: string }> = {
'/settings/airi-card': {
subtitle: t('settings.title'),
title: t('settings.pages.card.title'),
},
'/settings/characters': {
subtitle: t('settings.title'),
title: 'Characters',
},
'/settings/system': {
subtitle: t('settings.title'),
title: t('settings.pages.system.title'),
},
'/settings/system/general': {
subtitle: t('settings.title'),
title: t('settings.pages.system.general.title'),
},
'/settings/system/color-scheme': {
subtitle: t('settings.title'),
title: t('settings.pages.system.color-scheme.title'),
},
'/settings/system/developer': {
subtitle: t('settings.title'),
title: t('settings.pages.system.developer.title'),
},
'/settings/memory': {
subtitle: t('settings.title'),
title: t('settings.pages.memory.title'),
},
'/settings/models': {
subtitle: t('settings.title'),
title: t('settings.pages.models.title'),
},
'/settings/modules': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.title'),
},
'/settings/modules/consciousness': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.consciousness.title'),
},
'/settings/modules/speech': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.speech.title'),
},
'/settings/modules/hearing': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.hearing.title'),
},
'/settings/modules/vision': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.vision.title'),
},
'/settings/modules/memory-short-term': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.memory-short-term.title'),
},
'/settings/modules/memory-long-term': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.memory-long-term.title'),
},
'/settings/modules/messaging-discord': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.messaging-discord.title'),
},
'/settings/modules/x': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.x.title'),
},
'/settings/modules/gaming-minecraft': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.gaming-minecraft.title'),
},
'/settings/modules/gaming-factorio': {
subtitle: t('settings.title'),
title: t('settings.pages.modules.gaming-factorio.title'),
},
'/settings/providers': {
subtitle: t('settings.title'),
title: t('settings.pages.providers.title'),
},
'/settings/data': {
subtitle: t('settings.title'),
title: t('settings.pages.data.title'),
},
'/settings/connection': {
subtitle: t('settings.title'),
title: t('settings.pages.connection.title'),
},
'/settings/scene': {
subtitle: t('settings.title'),
title: t('settings.pages.scene.title'),
},
'/settings': {
title: t('settings.title'),
},
'/devtools/context-flow': {
subtitle: t('tamagotchi.settings.devtools.title'),
title: t('tamagotchi.settings.devtools.pages.context-flow.title'),
},
'/devtools/websocket-inspector': {
subtitle: t('tamagotchi.settings.devtools.title'),
title: 'WebSocket Inspector',
},
'/devtools/performance-visualizer': {
subtitle: t('settings.title'),
title: t('settings.pages.system.sections.section.developer.sections.section.performance-visualizer.title'),
},
'/devtools/markdown-stress': {
subtitle: t('settings.title'),
title: t('settings.pages.system.sections.section.developer.sections.section.markdown-stress.title'),
},
'/devtools/screen-capture': {
subtitle: t('settings.title'),
title: 'Screen Capture',
},
const providerTitle = computed(() => {
if (!route.path.startsWith('/settings/providers/'))
return undefined
const segments = route.path.split('/').filter(Boolean)
const providerId = segments[3]
if (!providerId)
return undefined
try {
const metadata = providersStore.getProviderMetadata(providerId)
return t(metadata.nameKey)
}
for (const metadata of allProvidersMetadata.value) {
map[`/settings/providers/${metadata.category}/${metadata.id}`] = {
subtitle: t('settings.title'),
title: t(metadata.nameKey),
}
catch {
return undefined
}
return map
})
// const activeSettingsTutorial = ref('default')
const routeHeaderMetadata = computed(() => routeHeaderMetadataMap.value[route.path])
const routeHeaderMetadata = computed(() => {
const { titleKey, subtitleKey, title, subtitle } = routeMeta.value
const resolvedTitle = titleKey ? t(titleKey) : title
const resolvedSubtitle = subtitleKey ? t(subtitleKey) : subtitle
if (resolvedTitle || resolvedSubtitle) {
return {
title: resolvedTitle,
subtitle: resolvedSubtitle,
}
}
if (providerTitle.value) {
return {
title: providerTitle.value,
subtitle: t('settings.title'),
}
}
return undefined
})
const { updateThemeColor } = useThemeColor(themeColorFromValue({ light: 'rgb(255 255 255)', dark: 'rgb(18 18 18)' }))
watch(dark, () => updateThemeColor(), { immediate: true })
@@ -347,4 +347,6 @@ function hitVSequence() {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.beat_sync.title
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -586,4 +586,6 @@ onUnmounted(() => {
<route lang="yaml">
meta:
layout: settings
titleKey: tamagotchi.settings.devtools.pages.context-flow.title
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -62,4 +62,6 @@ async function handleFileChange(event: Event) {
<route lang="yaml">
meta:
layout: settings
title: Image
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -157,4 +157,6 @@ function toggleMode() {
<route lang="yaml">
meta:
layout: settings
titleKey: tamagotchi.settings.devtools.pages.markdown-stress.title
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -503,4 +503,6 @@ onBeforeUnmount(async () => {
<route lang="yaml">
meta:
layout: settings
title: Aliyun NLS Realtime Transcription
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -65,4 +65,6 @@ watch(files, (files) => {
<route lang="yaml">
meta:
layout: settings
title: Vibrant
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -188,4 +188,6 @@ function payloadClasses(direction: 'incoming' | 'outgoing') {
<route lang="yaml">
meta:
layout: settings
title: WebSocket Inspector
subtitleKey: tamagotchi.settings.devtools.title
</route>
@@ -331,6 +331,12 @@ function getModuleShortName(id: string, module: 'consciousness' | 'voice') {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.card.title
subtitleKey: settings.title
descriptionKey: settings.pages.card.description
icon: i-solar:emoji-funny-square-bold-duotone
settingsEntry: true
order: 1
stageTransition:
name: slide
</route>
@@ -9,6 +9,12 @@ import ConnectionSettings from './ConnectionSettings.vue'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.connection.title
subtitleKey: settings.title
descriptionKey: settings.pages.connection.description
icon: i-solar:wi-fi-router-bold-duotone
settingsEntry: true
order: 8
stageTransition:
name: slide
</route>
@@ -286,6 +286,12 @@ async function handleImport(event: Event) {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.data.title
subtitleKey: settings.title
descriptionKey: settings.pages.data.description
icon: i-solar:database-bold-duotone
settingsEntry: true
order: 7
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -26,62 +26,18 @@ const removeBeforeEach = router.beforeEach(async (_, __, next) => {
next()
})
const settings = computed(() => [
{
title: t('settings.pages.card.title'),
description: t('settings.pages.card.description'),
icon: 'i-solar:emoji-funny-square-bold-duotone',
to: '/settings/airi-card',
},
{
title: t('settings.pages.modules.title'),
description: t('settings.pages.modules.description'),
icon: 'i-solar:layers-bold-duotone',
to: '/settings/modules',
},
{
title: t('settings.pages.scene.title'),
description: t('settings.pages.scene.description'),
icon: 'i-solar:armchair-2-bold-duotone',
to: '/settings/scene',
},
{
title: t('settings.pages.models.title'),
description: t('settings.pages.models.description'),
icon: 'i-solar:people-nearby-bold-duotone',
to: '/settings/models',
},
{
title: t('settings.pages.memory.title'),
description: t('settings.pages.memory.description'),
icon: 'i-solar:leaf-bold-duotone',
to: '/settings/memory',
},
{
title: t('settings.pages.providers.title'),
description: t('settings.pages.providers.description'),
icon: 'i-solar:box-minimalistic-bold-duotone',
to: '/settings/providers',
},
{
title: t('settings.pages.data.title'),
description: t('settings.pages.data.description'),
icon: 'i-solar:database-bold-duotone',
to: '/settings/data',
},
{
title: t('settings.pages.connection.title'),
description: t('settings.pages.connection.description'),
icon: 'i-solar:wi-fi-router-bold-duotone',
to: '/settings/connection',
},
{
title: t('settings.pages.system.title'),
description: t('settings.pages.system.description'),
icon: 'i-solar:filters-bold-duotone',
to: '/settings/system',
},
])
const settings = computed(() => {
return router
.getRoutes()
.filter(route => route.meta?.settingsEntry)
.sort((a, b) => (Number(a.meta?.order ?? 0) - Number(b.meta?.order ?? 0)))
.map(route => ({
title: route.meta?.titleKey ? t(route.meta.titleKey as string) : (route.meta?.title as string | undefined),
description: route.meta?.descriptionKey ? t(route.meta.descriptionKey as string) : (route.meta?.description as string | undefined) || '',
icon: route.meta?.icon as string | undefined,
to: route.path,
}))
})
</script>
<template>
@@ -122,6 +78,7 @@ const settings = computed(() => [
<route lang="yaml">
meta:
layout: settings
titleKey: settings.title
stageTransition:
name: slide
</route>
@@ -9,6 +9,12 @@ import { WIP } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.memory.title
subtitleKey: settings.title
descriptionKey: settings.pages.memory.description
icon: i-solar:leaf-bold-duotone
settingsEntry: true
order: 5
stageTransition:
name: slide
</route>
@@ -59,6 +59,12 @@ async function extractColorsFromModel() {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.models.title
subtitleKey: settings.title
descriptionKey: settings.pages.models.description
icon: i-solar:people-nearby-bold-duotone
settingsEntry: true
order: 4
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -350,6 +350,8 @@ onUnmounted(() => {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.beat_sync.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -249,6 +249,8 @@ function handleDeleteProvider(providerId: string) {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.consciousness.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -9,6 +9,8 @@ import { GamingFactorio } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.gaming-factorio.title
subtitleKey: settings.title
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -9,6 +9,8 @@ import { GamingMinecraft } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.gaming-minecraft.title
subtitleKey: settings.title
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -919,6 +919,8 @@ onUnmounted(() => {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.hearing.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -45,6 +45,12 @@ const { lastClickedIndex, setLastClickedIndex } = useRippleGridState()
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.title
subtitleKey: settings.title
descriptionKey: settings.pages.modules.description
icon: i-solar:layers-bold-duotone
settingsEntry: true
order: 2
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -9,6 +9,8 @@ import { WIP } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.memory-long-term.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -9,6 +9,8 @@ import { WIP } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.memory-short-term.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -9,6 +9,8 @@ import { MessagingDiscord } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.messaging-discord.title
subtitleKey: settings.title
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -593,6 +593,8 @@ function updateCustomModelName(value: string | undefined) {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.speech.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -9,6 +9,8 @@ import { WIP } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.vision.title
subtitleKey: settings.title
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -9,6 +9,8 @@ import { X } from '@proj-airi/stage-ui/components'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.modules.x.title
subtitleKey: settings.title
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -137,6 +137,12 @@ useScrollToHash(() => route.hash, {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.providers.title
subtitleKey: settings.title
descriptionKey: settings.pages.providers.description
icon: i-solar:box-minimalistic-bold-duotone
settingsEntry: true
order: 6
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -31,6 +31,12 @@ import { Callout } from '@proj-airi/ui'
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.scene.title
subtitleKey: settings.title
descriptionKey: settings.pages.scene.description
icon: i-solar:armchair-2-bold-duotone
settingsEntry: true
order: 3
stageTransition:
name: slide
pageSpecificAvailable: true
@@ -227,6 +227,8 @@ const themePresets = computed(() => {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.system.color-scheme.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>
@@ -87,6 +87,8 @@ const languages = computed(() => {
<route lang="yaml">
meta:
layout: settings
titleKey: settings.pages.system.general.title
subtitleKey: settings.title
stageTransition:
name: slide
</route>