feat(stage): custom primary colors (#53)

* feat(stage-ui): allow disable transitions
* fix(stage-web): apply motion map from file
* chore(stage-web): change icon
* feat(stage): custom primary colors
This commit is contained in:
LemonNeko
2025-03-08 16:51:48 +08:00
committed by GitHub
parent 8b824e8ccf
commit 25e06085f3
23 changed files with 402 additions and 215 deletions
@@ -36,7 +36,7 @@ onTokenLiteral(async () => {
<div flex-1 /> <!-- spacer --> <div flex-1 /> <!-- spacer -->
<div <div
ref="chatHistoryRef" v-auto-animate max-h="30vh" flex="~ col" ref="chatHistoryRef" v-auto-animate max-h="30vh" flex="~ col"
scrollbar="~ w-2 track-color-transparent thumb-color-pink-300 rounded" h-full w-full overflow-scroll scrollbar="~ w-2 track-color-transparent thumb-color-primary-300 rounded" h-full w-full overflow-scroll
> >
<div flex-1 /> <!-- spacer --> <div flex-1 /> <!-- spacer -->
<div v-for="(message, index) in messages" :key="index" mb-2> <div v-for="(message, index) in messages" :key="index" mb-2>
@@ -61,14 +61,14 @@ onTokenLiteral(async () => {
</div> </div>
<div v-if="message.role === 'assistant'" flex mr="12"> <div v-if="message.role === 'assistant'" flex mr="12">
<div <div
flex="~ col" border="4 solid pink-200" shadow="md pink-200/50" min-w-20 rounded-lg px-2 py-1 h="fit" flex="~ col" border="4 solid primary-200" shadow="md primary-200/50" min-w-20 rounded-lg px-2 py-1 h="fit"
bg="pink-100" bg="primary-100"
> >
<div> <div>
<span text-xs text="pink-400/90" font-semibold class="inline hidden">Airi</span> <span text-xs text="primary-400/90" font-semibold class="inline hidden">Airi</span>
</div> </div>
<div <div
v-if="message.content" class="markdown-content" text="xs pink-400" v-if="message.content" class="markdown-content" text="xs primary-400"
v-html="process(message.content as string)" v-html="process(message.content as string)"
/> />
<div v-else i-eos-icons:three-dots-loading /> <div v-else i-eos-icons:three-dots-loading />
@@ -104,9 +104,9 @@ onMounted(() => {
<BasicTextarea <BasicTextarea
v-model="messageInput" v-model="messageInput"
:placeholder="t('stage.message')" :placeholder="t('stage.message')"
border="solid 2 pink-100" border="solid 2 primary-100"
text="pink-400 hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600" text="primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out" transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
@submit="handleSend" @submit="handleSend"
@@ -114,9 +114,9 @@ onMounted(() => {
</div> </div>
<div <div
class="px-4 py-2.5" class="px-4 py-2.5"
border="solid 2 pink-100 " border="solid 2 primary-100 "
text="lg pink-400 hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600" text="lg primary-400 hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
flex items-center justify-center rounded-r-xl flex items-center justify-center rounded-r-xl
@click="openSettings" @click="openSettings"
> >
@@ -64,7 +64,7 @@ const modeIndicatorClass = computed(() => {
> >
<div class="absolute h-32 w-full flex items-center justify-center b-2 b-pink bg-white"> <div class="absolute h-32 w-full flex items-center justify-center b-2 b-pink bg-white">
<div class="wall absolute top-0 h-8" /> <div class="wall absolute top-0 h-8" />
<div class="absolute left-0 top-0 h-full w-full flex animate-flash animate-duration-5s animate-count-infinite items-center justify-center text-1.5rem text-pink-300 font-bold"> <div class="text-primary-300 absolute left-0 top-0 h-full w-full flex animate-flash animate-duration-5s animate-count-infinite items-center justify-center text-1.5rem font-bold">
DRAG HERE TO MOVE DRAG HERE TO MOVE
</div> </div>
<div class="wall absolute bottom-0 h-8" /> <div class="wall absolute bottom-0 h-8" />
@@ -113,7 +113,7 @@ const modeIndicatorClass = computed(() => {
} }
.wall { .wall {
@apply text-pink-200; @apply text-primary-200;
--wall-width: 8px; --wall-width: 8px;
animation: wall-move 1s linear infinite; animation: wall-move 1s linear infinite;
@@ -153,9 +153,9 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div> <div>
<div <div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" px-2 py-1 text="pink-400" bg="[#fff6fc]" px-2 py-1 text="primary-400"
> >
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.openai-base-url.label') }}</span> <span>{{ t('settings.openai-base-url.label') }}</span>
</div> </div>
<div flex="~ row" w-full text="xs"> <div flex="~ row" w-full text="xs">
@@ -166,7 +166,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
> >
</div> </div>
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.openai-api-key.label') }}</span> <span>{{ t('settings.openai-api-key.label') }}</span>
</div> </div>
<div flex="~ row" w-full text="xs"> <div flex="~ row" w-full text="xs">
@@ -177,7 +177,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
> >
</div> </div>
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.elevenlabs-api-key.label') }}</span> <span>{{ t('settings.elevenlabs-api-key.label') }}</span>
</div> </div>
<div flex="~ row" w-full text="xs"> <div flex="~ row" w-full text="xs">
@@ -188,7 +188,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
> >
</div> </div>
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.language.title') }}</span> <span>{{ t('settings.language.title') }}</span>
</div> </div>
<div flex="~ row" w-full text="xs"> <div flex="~ row" w-full text="xs">
@@ -204,7 +204,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
</option> </option>
</select> </select>
</div> </div>
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.models') }}</span> <span>{{ t('settings.models') }}</span>
</div> </div>
<div flex="~ row" w-full text="xs"> <div flex="~ row" w-full text="xs">
@@ -223,7 +223,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
</option> </option>
</select> </select>
</div> </div>
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.voices') }}</span> <span>{{ t('settings.voices') }}</span>
</div> </div>
<div flex="~ row" w-full text="xs"> <div flex="~ row" w-full text="xs">
@@ -257,9 +257,9 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div> <div>
<div <div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" px-2 py-1 text="pink-400" bg="[#fff6fc]" px-2 py-1 text="primary-400"
> >
<div text="xs pink-500"> <div text="xs primary-500">
<span>{{ t('settings.viewer') }}</span> <span>{{ t('settings.viewer') }}</span>
</div> </div>
<select <select
@@ -281,16 +281,16 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div pb-2> <div pb-2>
<div <div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" p-2 text="pink-400" bg="[#fff6fc]" p-2 text="primary-400"
> >
<template v-for="shortcut in shortcuts" :key="shortcut.type"> <template v-for="shortcut in shortcuts" :key="shortcut.type">
<span text="xs pink-500"> <span text="xs primary-500">
{{ t(shortcut.name) }} {{ t(shortcut.name) }}
</span> </span>
<div <div
class="shortcut-item flex items-center justify-end gap-x-2 px-2 py-0.5" class="shortcut-item flex items-center justify-end gap-x-2 px-2 py-0.5"
:class="{ recording: recordingFor === shortcut.type }" :class="{ recording: recordingFor === shortcut.type }"
text="xs pink-500" text="xs primary-500"
cursor-pointer cursor-pointer
@click="startRecording(shortcut)" @click="startRecording(shortcut)"
> >
@@ -300,7 +300,7 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div v-else class="pointer-events-none"> <div v-else class="pointer-events-none">
{{ shortcut.shortcut }} {{ shortcut.shortcut }}
</div> </div>
<div v-if="isConflict(shortcut)" text="xs pink-500" i-solar:danger-square-bold w-4 /> <div v-if="isConflict(shortcut)" text="xs primary-500" i-solar:danger-square-bold w-4 />
</div> </div>
</template> </template>
</div> </div>
@@ -311,14 +311,14 @@ function isConflict(shortcut: typeof shortcuts.value[0]) {
<div pb-2> <div pb-2>
<div <div
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
bg="[#fff6fc]" p-2 text="pink-400" @click="handleQuit" bg="[#fff6fc]" p-2 text="primary-400" @click="handleQuit"
> >
<div text="xs pink-500"> <div text="xs primary-500">
<span> <span>
{{ t('settings.quit') }} {{ t('settings.quit') }}
</span> </span>
</div> </div>
<div text="sm pink-500" text-right> <div text="sm primary-500" text-right>
<div i-solar:exit-bold-duotone ml-auto /> <div i-solar:exit-bold-duotone ml-auto />
</div> </div>
</div> </div>
+10 -1
View File
@@ -3,7 +3,7 @@ import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { StageTransitionGroup } from '@proj-airi/ui-transitions' import { StageTransitionGroup } from '@proj-airi/ui-transitions'
import { useDark } from '@vueuse/core' import { useDark } from '@vueuse/core'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { watch } from 'vue' import { onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
@@ -14,6 +14,14 @@ const isDark = useDark()
watch(settings.language, () => { watch(settings.language, () => {
i18n.locale.value = settings.language.value i18n.locale.value = settings.language.value
}) })
watch(settings.themeColorsHue, () => {
document.documentElement.style.setProperty('--theme-colors-hue', settings.themeColorsHue.value.toString())
})
onMounted(() => {
document.documentElement.style.setProperty('--theme-colors-hue', settings.themeColorsHue.value.toString())
})
</script> </script>
<template> <template>
@@ -23,6 +31,7 @@ watch(settings.language, () => {
tertiary-color="#121212" tertiary-color="#121212"
:colors="!isDark ? ['#FF57C8', '#946BFF', '#64BCFF', '#FFFFFF'] : ['#FF57C8', '#946BFF', '#64BCFF', '#121212']" :colors="!isDark ? ['#FF57C8', '#946BFF', '#64BCFF', '#FFFFFF'] : ['#FF57C8', '#946BFF', '#64BCFF', '#121212']"
:z-index="100" :z-index="100"
:disable-transitions="settings.disableTransitions.value"
> >
<RouterView /> <RouterView />
</StageTransitionGroup> </StageTransitionGroup>
@@ -11,11 +11,12 @@ https://www.magicpattern.design/tools/css-backgrounds
*/ */
.cross-background-container { .cross-background-container {
background-color: #ffffff; background-color: #ffffff;
--cross-color: oklch(0.95 0.06 var(--theme-colors-hue));
background: background:
radial-gradient(circle, transparent 20%, #ffffff 20%, #ffffff 80%, transparent 80%, transparent), radial-gradient(circle, transparent 20%, #ffffff 20%, #ffffff 80%, transparent 80%, transparent),
radial-gradient(circle, transparent 20%, #ffffff 20%, #ffffff 80%, transparent 80%, transparent) 25px 25px, radial-gradient(circle, transparent 20%, #ffffff 20%, #ffffff 80%, transparent 80%, transparent) 25px 25px,
linear-gradient(#f4ebf1 2px, transparent 2px) 0 -1px, linear-gradient(var(--cross-color) 2px, transparent 2px) 0 -1px,
linear-gradient(90deg, #f4ebf1 2px, #ffffff 2px) -1px 0; linear-gradient(90deg, var(--cross-color) 2px, #ffffff 2px) -1px 0;
background-size: background-size:
50px 50px, 50px 50px,
50px 50px, 50px 50px,
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<WaveProps>(), {
height: 40, height: 40,
amplitude: 14, amplitude: 14,
waveLength: 250, waveLength: 250,
fillColor: '#f8e8f2', fillColor: 'oklch(95% 0.10 var(--theme-colors-hue))',
direction: 'down', direction: 'down',
animationSpeed: 50, animationSpeed: 50,
}) })
@@ -120,15 +120,15 @@ onAfterSend(async () => {
<fieldset flex="~ row" w-fit rounded-lg> <fieldset flex="~ row" w-fit rounded-lg>
<label <label
:class="[ :class="[
tab === 'chat' ? 'bg-pink-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]', tab === 'chat' ? 'bg-primary-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'chat' ? 'text-pink-500 dark:text-pink-500' : '', tab === 'chat' ? 'text-primary-500 dark:text-primary-500' : '',
]" ]"
flex="~ row" flex="~ row"
:checked="tab === 'chat'" :checked="tab === 'chat'"
:aria-checked="tab === 'chat'" :aria-checked="tab === 'chat'"
border="solid l-2 t-2 b-2 pink-100 dark:[#3c2632]" border="solid l-2 t-2 b-2 primary-100 dark:[#3c2632]"
bg="hover:pink-100 dark:hover:[#3c2632]" bg="hover:primary-100 dark:hover:[#3c2632]"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500" text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500"
transition="all duration-250 ease-in-out" transition="all duration-250 ease-in-out"
cursor-pointer items-center gap-1 rounded-l-lg px-2 cursor-pointer items-center gap-1 rounded-l-lg px-2
> >
@@ -140,15 +140,15 @@ onAfterSend(async () => {
</label> </label>
<label <label
:class="[ :class="[
tab === 'custom' ? 'bg-pink-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]', tab === 'custom' ? 'bg-primary-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'custom' ? 'text-pink-500 dark:text-pink-500' : '', tab === 'custom' ? 'text-primary-500 dark:text-primary-500' : '',
]" ]"
flex="~ row" flex="~ row"
:checked="tab === 'custom'" :checked="tab === 'custom'"
:aria-checked="tab === 'custom'" :aria-checked="tab === 'custom'"
border="solid t-2 b-2 pink-100 dark:[#3c2632]" border="solid t-2 b-2 primary-100 dark:[#3c2632]"
bg="hover:pink-100 dark:hover:[#3c2632]" bg="hover:primary-100 dark:hover:[#3c2632]"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500" text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500"
transition="all duration-250 ease-in-out" transition="all duration-250 ease-in-out"
cursor-pointer items-center gap-1 px-2 cursor-pointer items-center gap-1 px-2
> >
@@ -160,15 +160,15 @@ onAfterSend(async () => {
</label> </label>
<label <label
:class="[ :class="[
tab === 'clothes' ? 'bg-pink-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]', tab === 'clothes' ? 'bg-primary-100 dark:bg-[#3c2632]' : 'bg-white dark:bg-[#121212]',
tab === 'clothes' ? 'text-pink-500 dark:text-pink-500' : '', tab === 'clothes' ? 'text-primary-500 dark:text-primary-500' : '',
]" ]"
flex="~ row" flex="~ row"
:checked="tab === 'clothes'" :checked="tab === 'clothes'"
:aria-checked="tab === 'clothes'" :aria-checked="tab === 'clothes'"
border="solid r-2 t-2 b-2 pink-100 dark:[#3c2632]" border="solid r-2 t-2 b-2 primary-100 dark:[#3c2632]"
bg="hover:pink-100 dark:hover:[#3c2632]" bg="hover:primary-100 dark:hover:[#3c2632]"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500" text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500"
transition="all duration-250 ease-in-out" transition="all duration-250 ease-in-out"
cursor-pointer items-center gap-1 rounded-r-lg px-2 cursor-pointer items-center gap-1 rounded-r-lg px-2
> >
@@ -182,7 +182,7 @@ onAfterSend(async () => {
<div h-full max-h="[85vh]" w-full px="12 <md:0" py="4"> <div h-full max-h="[85vh]" w-full px="12 <md:0" py="4">
<div <div
flex="~ col" flex="~ col"
border="solid 4 pink-100 dark:pink-400/20" border="solid 4 primary-100 dark:primary-400/20"
h-full w-full overflow-scroll rounded-xl h-full w-full overflow-scroll rounded-xl
bg="white dark:[#0f060c]" bg="white dark:[#0f060c]"
> >
@@ -191,8 +191,8 @@ onAfterSend(async () => {
<BasicTextarea <BasicTextarea
v-model="messageInput" v-model="messageInput"
:placeholder="t('stage.message')" :placeholder="t('stage.message')"
text="pink-300 hover:pink-500 dark:pink-300/50 dark:hover:pink-500 placeholder:pink-300 placeholder:hover:pink-500 placeholder:dark:pink-300/50 placeholder:dark:hover:pink-500" text="primary-300 hover:primary-500 dark:primary-300/50 dark:hover:primary-500 placeholder:primary-300 placeholder:hover:primary-500 placeholder:dark:primary-300/50 placeholder:dark:hover:primary-500"
bg="pink-100 dark:pink-400/20" bg="primary-100 dark:primary-400/20"
min-h="[100px]" w-full min-h="[100px]" w-full
rounded-t-xl p-4 font-medium rounded-t-xl p-4 font-medium
outline-none transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out" outline-none transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
@@ -100,9 +100,9 @@ onMounted(() => {
<BasicTextarea <BasicTextarea
v-model="messageInput" v-model="messageInput"
:placeholder="t('stage.message')" :placeholder="t('stage.message')"
border="solid 2 pink-100 dark:pink-400/20" border="solid 2 primary-100 dark:primary-400/20"
text="pink-400 hover:pink-600 dark:[#905073] dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:[#905073] placeholder:dark:hover:pink-600" text="primary-400 hover:primary-600 dark:[#905073] dark:hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600 placeholder:dark:[#905073] placeholder:dark:hover:primary-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none w-full resize-none overflow-y-scroll rounded-l-xl p-2 font-medium outline-none
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out" transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
@submit="handleSend" @submit="handleSend"
@@ -111,9 +111,9 @@ onMounted(() => {
<DrawerRoot should-scale-background @update:open="handleSettingsOpen"> <DrawerRoot should-scale-background @update:open="handleSettingsOpen">
<DrawerTrigger <DrawerTrigger
class="px-4 py-2.5" class="px-4 py-2.5"
border="solid 2 pink-100 dark:pink-400/20" border="solid 2 primary-100 dark:primary-400/20"
text="lg pink-400 hover:pink-600 dark:[#905073] dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:[#905073] placeholder:dark:hover:pink-600" text="lg primary-400 hover:primary-600 dark:[#905073] dark:hover:primary-600 placeholder:primary-400 placeholder:hover:primary-600 placeholder:dark:[#905073] placeholder:dark:hover:primary-600"
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" rounded-r-xl bg="primary-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" rounded-r-xl
> >
<div i-solar:settings-bold-duotone /> <div i-solar:settings-bold-duotone />
</DrawerTrigger> </DrawerTrigger>
@@ -11,9 +11,9 @@ defineProps<{
<template> <template>
<RouterLink <RouterLink
flex="~ row" bg="neutral-50 dark:neutral-800" flex="~ row" bg="neutral-50 dark:neutral-800"
border="neutral-100 dark:neutral-700 hover:pink-300 dark:hover:pink-300/40 solid 2" border="neutral-100 dark:neutral-700 hover:primary-300 dark:hover:primary-300/40 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none" drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
class="after:mask-[linear-gradient(165deg,white_30%,transparent_50%)] after:bg-dotted-[neutral-200] after:hover:bg-dotted-[pink-300/50] dark:after:bg-dotted-[neutral-700/80] dark:after:hover:bg-dotted-[pink-200/20] after:absolute after:left-0 after:top-0 after:z--1 after:h-full after:w-full after:bg-[size:10px_10px] after:content-empty [&_.settings-section-icon]:hover:scale-120 [&_.settings-section-description]:hover:text-pink-400/80 [&_.settings-section-icon]:hover:text-pink-200 [&_.settings-section-title]:hover:text-pink-500 dark:[&_.settings-section-icon]:hover:text-pink-200/40 dark:[&_.settings-section-title]:hover:text-pink-400" class="after:mask-[linear-gradient(165deg,white_30%,transparent_50%)] after:bg-dotted-[neutral-200] after:hover:bg-dotted-[primary-300/50] dark:after:bg-dotted-[neutral-700/80] [&_.settings-section-description]:hover:text-primary-400/80 [&_.settings-section-icon]:hover:text-primary-200 [&_.settings-section-title]:hover:text-primary-500 dark:after:hover:bg-dotted-[primary-200/20] dark:[&_.settings-section-icon]:hover:text-primary-200/40 dark:[&_.settings-section-title]:hover:text-primary-400 after:absolute after:left-0 after:top-0 after:z--1 after:h-full after:w-full after:bg-[size:10px_10px] after:content-empty [&_.settings-section-icon]:hover:scale-120"
transition="all ease-in-out duration-200" relative w-full items-center overflow-hidden rounded-lg p-5 text-left transition="all ease-in-out duration-200" relative w-full items-center overflow-hidden rounded-lg p-5 text-left
:to="to" :to="to"
> >
@@ -58,11 +58,11 @@ onTokenLiteral(async () => {
</div> </div>
<div v-if="message.role === 'assistant'" flex mr="12"> <div v-if="message.role === 'assistant'" flex mr="12">
<div <div
flex="~ col" border="4 solid pink-200/50 dark:pink-500/50" shadow="md pink-200/50 dark:none" min-w-20 flex="~ col" border="4 solid primary-200/50 dark:primary-500/50" shadow="md primary-200/50 dark:none" min-w-20
rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:pink-500/25" rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:primary-500/25"
> >
<div> <div>
<span text-xs text="pink-400/90 dark:pink-600/90" font-semibold class="inline <sm:hidden">{{ <span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.airi') }}</span> $t('stage.chat.message.character-name.airi') }}</span>
</div> </div>
<div v-if="sending" i-eos-icons:three-dots-loading /> <div v-if="sending" i-eos-icons:three-dots-loading />
@@ -31,8 +31,15 @@ watch(() => settings.loadingLive2dModel, (value) => {
return return
} }
if (settings.live2dLoadSource !== 'file') {
return
}
settings.availableLive2dMotions.forEach((motion) => { settings.availableLive2dMotions.forEach((motion) => {
if (!settings.live2dMotionMap[motion.fileName]) { if (motion.motionName in Emotion) {
settings.live2dMotionMap[motion.fileName] = motion.motionName
}
else {
settings.live2dMotionMap[motion.fileName] = EmotionNeutralMotionName settings.live2dMotionMap[motion.fileName] = EmotionNeutralMotionName
} }
}) })
@@ -88,161 +95,163 @@ const exportObjectUrl = useObjectUrl(settings.live2dModelFile)
</script> </script>
<template> <template>
<Collapsable w-full> <div>
<template #trigger="slotProps"> <Collapsable w-full :default="true">
<button <template #trigger="slotProps">
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
w-full flex items-center gap-1.5 rounded-lg px-4 py-3 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
@click="slotProps.setVisible(!slotProps.visible)"
>
<div flex="~ row 1" items-center gap-1.5>
<div
i-solar:magic-stick-3-bold-duotone class="provider-icon size-6"
transition="filter duration-250 ease-in-out"
/>
<div>
{{ t('settings.live2d.change-model.title') }}
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
<div i-solar:alt-arrow-down-bold-duotone />
</div>
</button>
</template>
<div p-4>
<div class="space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ t('settings.live2d.change-model.from-url') }}
</div>
</div>
<div>
<input
v-model="modelUrl"
:disabled="settings.loadingLive2dModel"
type="text"
rounded
border="zinc-300 dark:zinc-800 solid 1 focus:zinc-400 dark:focus:zinc-600"
transition="border duration-250 ease-in-out"
px-2 py-1 text-sm outline-none
:placeholder="t('settings.live2d.change-model.from-url-placeholder')"
>
<button
:disabled="settings.loadingLive2dModel"
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
ml-2 rounded px-2 py-1 text-sm outline-none
@click="settings.live2dModelUrl = modelUrl"
>
{{ t('settings.live2d.change-model.from-url-confirm') }}
</button>
</div>
</div>
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ t('settings.live2d.change-model.from-file') }}
</div>
</div>
<button
:disabled="settings.loadingLive2dModel"
rounded
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
px-2 py-1 text-sm outline-none
@click="modelFile.open()"
>
{{ t('settings.live2d.change-model.from-file-select') }}
</button>
</div>
</div>
</div>
</Collapsable>
<Collapsable mt-4 w-full>
<template #trigger="slotProps">
<button
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
w-full flex items-center gap-1.5 rounded-lg px-4 py-3 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
@click="slotProps.setVisible(!slotProps.visible)"
>
<div flex="~ row 1" items-center gap-1.5>
<div
i-solar:face-scan-circle-bold-duotone class="provider-icon size-6"
transition="filter duration-250 ease-in-out"
/>
<div>
Edit motion map
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
<div i-solar:alt-arrow-down-bold-duotone />
</div>
</button>
</template>
<div p-4>
<div v-if="settings.live2dLoadSource === 'file'" class="space-y-4">
<div v-for="motion in settings.availableLive2dMotions" :key="motion.fileName" class="flex items-center justify-between">
<div class="flex items-center gap-1 text-sm font-medium">
{{ motion.fileName }}
</div>
<div flex gap-2>
<select v-model="settings.live2dMotionMap[motion.fileName]">
<option v-for="emotion in Object.keys(Emotion)" :key="emotion">
{{ emotion }}
</option>
</select>
<button
:disabled="settings.loadingLive2dModel"
rounded
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
px-2 py-1 text-sm outline-none
@click="settings.live2dCurrentMotion = { group: motion.motionName, index: motion.motionIndex }"
>
Play
</button>
</div>
</div>
<button <button
:disabled="settings.loadingLive2dModel"
w-full rounded
bg="zinc-100 dark:zinc-800" bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700" hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250" transition="all ease-in-out duration-250"
@click="saveMotionMap" w-full flex items-center gap-1.5 rounded-lg px-4 py-3 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
@click="slotProps.setVisible(!slotProps.visible)"
> >
Save and patch <div flex="~ row 1" items-center gap-1.5>
<div
i-solar:magic-stick-3-bold-duotone class="provider-icon size-6"
transition="filter duration-250 ease-in-out"
/>
<div>
{{ t('settings.live2d.change-model.title') }}
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
<div i-solar:alt-arrow-down-bold-duotone />
</div>
</button> </button>
<a </template>
mt-2 block :href="exportObjectUrl" <div p-4>
:download="`${settings.live2dModelFile?.name}-motion-edited.zip`" <div class="space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ t('settings.live2d.change-model.from-url') }}
</div>
</div>
<div>
<input
v-model="modelUrl"
:disabled="settings.loadingLive2dModel"
type="text"
rounded
border="zinc-300 dark:zinc-800 solid 1 focus:zinc-400 dark:focus:zinc-600"
transition="border duration-250 ease-in-out"
px-2 py-1 text-sm outline-none
:placeholder="t('settings.live2d.change-model.from-url-placeholder')"
>
<button
:disabled="settings.loadingLive2dModel"
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
ml-2 rounded px-2 py-1 text-sm outline-none
@click="settings.live2dModelUrl = modelUrl"
>
{{ t('settings.live2d.change-model.from-url-confirm') }}
</button>
</div>
</div>
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
{{ t('settings.live2d.change-model.from-file') }}
</div>
</div>
<button
:disabled="settings.loadingLive2dModel"
rounded
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
px-2 py-1 text-sm outline-none
@click="modelFile.open()"
>
{{ t('settings.live2d.change-model.from-file-select') }}
</button>
</div>
</div>
</div>
</Collapsable>
<Collapsable mt-4 w-full :default="true">
<template #trigger="slotProps">
<button
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
w-full flex items-center gap-1.5 rounded-lg px-4 py-3 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
@click="slotProps.setVisible(!slotProps.visible)"
> >
<div flex="~ row 1" items-center gap-1.5>
<div
i-solar:face-scan-circle-bold-duotone class="provider-icon size-6"
transition="filter duration-250 ease-in-out"
/>
<div>
Edit motion map
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
<div i-solar:alt-arrow-down-bold-duotone />
</div>
</button>
</template>
<div p-4>
<div v-if="settings.live2dLoadSource === 'file'" class="space-y-4">
<div v-for="motion in settings.availableLive2dMotions" :key="motion.fileName" class="flex items-center justify-between">
<div class="flex items-center gap-1 text-sm font-medium">
{{ motion.fileName }}
</div>
<div flex gap-2>
<select v-model="settings.live2dMotionMap[motion.fileName]">
<option v-for="emotion in Object.keys(Emotion)" :key="emotion">
{{ emotion }}
</option>
</select>
<button
:disabled="settings.loadingLive2dModel"
rounded
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
px-2 py-1 text-sm outline-none
@click="settings.live2dCurrentMotion = { group: motion.motionName, index: motion.motionIndex }"
>
Play
</button>
</div>
</div>
<button <button
:disabled="settings.loadingLive2dModel" :disabled="settings.loadingLive2dModel"
w-full rounded w-full rounded
bg="zinc-100 dark:zinc-800" bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700" hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250" transition="all ease-in-out duration-250"
@click="saveMotionMap"
> >
Export Save and patch
</button> </button>
</a> <a
mt-2 block :href="exportObjectUrl"
:download="`${settings.live2dModelFile?.name}-motion-edited.zip`"
>
<button
:disabled="settings.loadingLive2dModel"
w-full rounded
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
>
Export
</button>
</a>
</div>
<div v-else>
Not available for URL model
</div>
</div> </div>
<div v-else> </Collapsable>
Not available for URL model </div>
</div>
</div>
</Collapsable>
</template> </template>
@@ -40,14 +40,14 @@ onTokenLiteral(async () => {
<div v-if="message.role === 'assistant'" flex mr="12"> <div v-if="message.role === 'assistant'" flex mr="12">
<div <div
flex="~ col" flex="~ col"
border="4 solid pink-200/50 dark:pink-500/50" border="4 solid primary-200/50 dark:primary-500/50"
shadow="md pink-200/50 dark:none" shadow="md primary-200/50 dark:none"
min-w-20 rounded-lg px-2 py-1 min-w-20 rounded-lg px-2 py-1
h="unset <sm:fit" h="unset <sm:fit"
bg="<md:pink-500/25" bg="<md:primary-500/25"
> >
<div> <div>
<span text-xs text="pink-400/90 dark:pink-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.airi') }}</span> <span text-xs text="primary-400/90 dark:primary-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.airi') }}</span>
</div> </div>
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" /> <div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else i-eos-icons:three-dots-loading /> <div v-else i-eos-icons:three-dots-loading />
+1 -1
View File
@@ -20,7 +20,7 @@ function handleSettingsOpen(open: boolean) {
<template> <template>
<Cross> <Cross>
<AnimatedBackground :fill-color="dark ? '#563544' : '#f8e8f2'"> <AnimatedBackground :fill-color="dark ? 'oklch(95% 0.20 var(--theme-colors-hue))' : 'oklch(99% 0.10 var(--theme-colors-hue))'">
<div relative flex="~ col" z-2 h-100vh w-100vw of-hidden> <div relative flex="~ col" z-2 h-100vh w-100vw of-hidden>
<!-- header --> <!-- header -->
<div> <div>
+37 -2
View File
@@ -9,12 +9,12 @@ import IconItem from '../../components/Menu/IconItem.vue'
const router = useRouter() const router = useRouter()
const { t } = useI18n() const { t } = useI18n()
const settings = storeToRefs(useSettings()) const { language, disableTransitions } = storeToRefs(useSettings())
const dark = useDark() const dark = useDark()
function handleLanguageChange(event: Event) { function handleLanguageChange(event: Event) {
const target = event.target as HTMLSelectElement const target = event.target as HTMLSelectElement
settings.language.value = target.value language.value = target.value
} }
</script> </script>
@@ -104,6 +104,41 @@ function handleLanguageChange(event: Event) {
</div> </div>
</div> </div>
</label> </label>
<!-- Developer Settings -->
<label
bg="neutral-100 dark:neutral-800"
hover="bg-neutral-200 dark:bg-neutral-700"
transition="all ease-in-out duration-250"
w-full flex cursor-pointer rounded-lg px-4 py-3
>
<input
v-model="disableTransitions"
text="neutral-800 dark:neutral-100"
:checked="disableTransitions"
:aria-checked="disableTransitions"
type="checkbox"
hidden appearance-none outline-none
>
<div flex="~ row" w-full items-center gap-1.5>
<div text="sm" w-full flex-1>
<span>Disable Transitions (for debugging)</span>
</div>
<div select-none>
<Transition name="slide-away" mode="out-in">
<div
v-if="disableTransitions"
i-solar:people-nearby-bold-duotone
transition="all ease-in-out duration-250"
/>
<div
v-else
i-solar:running-2-line-duotone
transition="all ease-in-out duration-250"
/>
</Transition>
</div>
</div>
</label>
</div> </div>
<div fixed bottom-0 right-0 text="neutral-100/50 dark:neutral-500/20"> <div fixed bottom-0 right-0 text="neutral-100/50 dark:neutral-500/20">
<div text="40" i-lucide:cog translate-x-10 translate-y-10 /> <div text="40" i-lucide:cog translate-x-10 translate-y-10 />
@@ -1,6 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { WidgetStage } from '@proj-airi/stage-ui/components'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Live2DSettings from '../../../components/Widgets/Live2DSettings.vue'
const router = useRouter() const router = useRouter()
</script> </script>
@@ -18,6 +21,11 @@ const router = useRouter()
</div> </div>
</h1> </h1>
</div> </div>
<div flex>
<WidgetStage w="50%" h="80vh" />
<Live2DSettings w="50%" h="80vh" />
</div>
<div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20"> <div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20">
<div text="40" i-lucide:person-standing translate-x-10 translate-y-10 /> <div text="40" i-lucide:person-standing translate-x-10 translate-y-10 />
</div> </div>
@@ -111,9 +111,9 @@ const modulesList = computed<Module[]>(() => [
<div <div
v-for="module in modulesList" :key="module.id" v-for="module in modulesList" :key="module.id"
bg="neutral-200/50 dark:neutral-700" bg="neutral-200/50 dark:neutral-700"
border="neutral-100 dark:neutral-700 hover:pink-300 dark:hover:pink-300/40 solid 2" border="neutral-100 dark:neutral-700 hover:primary-300 dark:hover:primary-300/40 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none" drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
class="[&_.settings-section-icon]:hover:scale-120 [&_.settings-section-description]:hover:text-pink-400/80 [&_.settings-section-icon]:hover:text-pink-200 [&_.settings-section-icon]:hover:grayscale-0 dark:[&_.settings-section-icon]:hover:text-pink-200/40 dark:[&_.settings-section-title]:hover:text-pink-400" class="[&_.settings-section-description]:hover:text-primary-400/80 [&_.settings-section-icon]:hover:text-primary-200 dark:[&_.settings-section-icon]:hover:text-primary-200/40 dark:[&_.settings-section-title]:hover:text-primary-400 [&_.settings-section-icon]:hover:scale-120 [&_.settings-section-icon]:hover:grayscale-0"
w-full of-hidden rounded-xl w-full of-hidden rounded-xl
flex="~ col 1" flex="~ col 1"
> >
@@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { Collapsable } from '@proj-airi/stage-ui/components'
import { useSettings } from '@proj-airi/stage-ui/stores'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const settings = useSettings()
</script> </script>
<template> <template>
@@ -18,11 +21,98 @@ const router = useRouter()
</div> </div>
</h1> </h1>
</div> </div>
<Collapsable mt-4 w-full :default="true">
<template #trigger="slotProps">
<button
bg="zinc-100 dark:zinc-800"
hover="bg-zinc-200 dark:bg-zinc-700"
transition="all ease-in-out duration-250"
w-full flex items-center gap-1.5 rounded-lg px-4 py-3 outline-none
class="[&_.provider-icon]:grayscale-100 [&_.provider-icon]:hover:grayscale-0"
@click="slotProps.setVisible(!slotProps.visible)"
>
<div flex="~ row 1" items-center gap-1.5>
<div
i-solar:pallete-2-bold-duotone class="provider-icon size-6"
transition="filter duration-250 ease-in-out"
/>
<div>
Colors
</div>
</div>
<div transform transition="transform duration-250" :class="{ 'rotate-180': slotProps.visible }">
<div i-solar:alt-arrow-down-bold-duotone />
</div>
</button>
</template>
<div p-4>
<div class="flex items-center justify-between">
<div class="flex items-center gap-1 text-sm font-medium">
Primary color
</div>
<input v-model="settings.themeColorsHue" type="range" min="0" max="360" step="1">
</div>
<div mt-4 h-10 w-full flex overflow-hidden rounded-lg>
<div bg="primary-50" class="primary-color-bar">
50
</div>
<div bg="primary-100" class="primary-color-bar">
100
</div>
<div bg="primary-200" class="primary-color-bar">
200
</div>
<div bg="primary-300" class="primary-color-bar">
300
</div>
<div bg="primary-400" class="primary-color-bar">
400
</div>
<div bg="primary-500" class="primary-color-bar">
500
</div>
<div bg="primary-600" class="primary-color-bar">
600
</div>
<div bg="primary-700" class="primary-color-bar">
700
</div>
<div bg="primary-800" class="primary-color-bar" text-white>
800
</div>
<div bg="primary-900" class="primary-color-bar" text-white>
900
</div>
<div bg="primary-950" class="primary-color-bar" text-white>
950
</div>
<div bg="primary-960" class="primary-color-bar" text-white>
960
</div>
<div bg="primary-970" class="primary-color-bar" text-white>
970
</div>
<div bg="primary-980" class="primary-color-bar" text-white>
980
</div>
<div bg="primary-990" class="primary-color-bar" text-white>
990
</div>
</div>
</div>
</Collapsable>
<div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20"> <div fixed bottom-0 right-0 z--1 text="neutral-100/80 dark:neutral-500/20">
<div text="40" i-lucide:paintbrush translate-x-10 translate-y-10 /> <div text="40" i-lucide:paintbrush translate-x-10 translate-y-10 />
</div> </div>
</template> </template>
<style>
.primary-color-bar {
@apply w-full h-full flex-1 flex items-center justify-center;
}
</style>
<route lang="yaml"> <route lang="yaml">
meta: meta:
stageTransition: stageTransition:
+1
View File
@@ -10,4 +10,5 @@
--airi-theme-primary-800: #a70a3e; --airi-theme-primary-800: #a70a3e;
--airi-theme-primary-900: #8e0d3b; --airi-theme-primary-900: #8e0d3b;
--airi-theme-primary-950: #50011b; --airi-theme-primary-950: #50011b;
--theme-colors-hue: 354.31;
} }
+22
View File
@@ -69,4 +69,26 @@ export default defineConfig({
} }
}], }],
], ],
theme: {
colors: {
primary: {
DEFAULT: 'oklch(62% 0.20 var(--theme-colors-hue))',
50: 'color-mix(in srgb, oklch(95% 0.10 var(--theme-colors-hue)) 30%, oklch(100% 0 360))',
100: 'color-mix(in srgb, oklch(95% 0.10 var(--theme-colors-hue)) 80%, oklch(100% 0 360))',
200: 'oklch(90% 0.12 var(--theme-colors-hue))',
300: 'oklch(85% 0.20 var(--theme-colors-hue))',
400: 'oklch(74% 0.20 var(--theme-colors-hue))',
500: 'oklch(62% 0.20 var(--theme-colors-hue))',
600: 'oklch(54% 0.23 var(--theme-colors-hue))',
700: 'oklch(49% 0.21 var(--theme-colors-hue))',
800: 'oklch(42% 0.17 var(--theme-colors-hue))',
900: 'oklch(37% 0.14 var(--theme-colors-hue))',
950: 'oklch(29% 0.09 var(--theme-colors-hue))',
960: 'oklch(23% 0.09 var(--theme-colors-hue))',
970: 'oklch(17% 0.09 var(--theme-colors-hue))',
980: 'oklch(11% 0.09 var(--theme-colors-hue))',
990: 'oklch(6% 0.09 var(--theme-colors-hue))',
},
},
},
}) })
@@ -252,11 +252,11 @@ onMounted(async () => {
bottom="20%" bottom="20%"
z="20" z="20"
rounded-2xl rounded-2xl
text="pink-600" text="primary-600"
px-2 py-2 px-2 py-2
transform="translate-x--1/2" transform="translate-x--1/2"
> >
<div bg="pink-50" rounded-xl px-10 py-6> <div bg="primary-50" rounded-xl px-10 py-6>
<div class="markdown-content" v-html="process(streamingMessage.content as string)" /> <div class="markdown-content" v-html="process(streamingMessage.content as string)" />
</div> </div>
</div> </div>
+6
View File
@@ -33,6 +33,10 @@ export const useSettings = defineStore('settings', () => {
const availableLive2dMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([]) const availableLive2dMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([])
const live2dMotionMap = useLocalStorage<Record<string, string>>('settings/live2d/motion-map', {}) const live2dMotionMap = useLocalStorage<Record<string, string>>('settings/live2d/motion-map', {})
const disableTransitions = useLocalStorage('settings/disable-transitions', false)
const themeColorsHue = useLocalStorage('settings/theme/colors/hue', 354.31)
watch(isAudioInputOn, (value) => { watch(isAudioInputOn, (value) => {
if (value === 'false') { if (value === 'false') {
selectedAudioDevice.value = undefined selectedAudioDevice.value = undefined
@@ -67,8 +71,10 @@ export const useSettings = defineStore('settings', () => {
availableLive2dMotions, availableLive2dMotions,
live2dMotionMap, live2dMotionMap,
loadingLive2dModel, loadingLive2dModel,
disableTransitions,
language, language,
stageView, stageView,
themeColorsHue,
isAudioInputOn, isAudioInputOn,
selectedAudioDevice, selectedAudioDevice,
selectedAudioDeviceId, selectedAudioDeviceId,
@@ -16,6 +16,7 @@ const props = defineProps<{
tertiaryColor?: string tertiaryColor?: string
colors?: string[] colors?: string[]
zIndex?: number zIndex?: number
disableTransitions?: boolean
}>() }>()
interface StageTransitionCommonParams { interface StageTransitionCommonParams {
@@ -268,6 +269,11 @@ router.beforeEach((to, _from, next) => {
stageTransition.zIndex = props.zIndex stageTransition.zIndex = props.zIndex
} }
if (props.disableTransitions) {
next()
return
}
triggerTransition(stageTransition, next) triggerTransition(stageTransition, next)
}) })
</script> </script>