style(stage-web): improve mobile chat ui
This commit is contained in:
@@ -21,6 +21,7 @@ const { activeProvider, activeModel } = storeToRefs(useConsciousnessStore())
|
||||
// const { selectedAudioDevice, isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
|
||||
const { isAudioInputOn, selectedAudioDeviceId, themeColorsHueDynamic } = storeToRefs(useSettings())
|
||||
const { send, onAfterSend } = useChatStore()
|
||||
const { messages } = storeToRefs(useChatStore())
|
||||
const { t } = useI18n()
|
||||
|
||||
async function handleSend() {
|
||||
@@ -28,13 +29,22 @@ async function handleSend() {
|
||||
return
|
||||
}
|
||||
|
||||
const providerConfig = providersStore.getProviderConfig(activeProvider.value)
|
||||
try {
|
||||
const providerConfig = providersStore.getProviderConfig(activeProvider.value)
|
||||
|
||||
await send(messageInput.value, {
|
||||
chatProvider: providersStore.getProviderInstance(activeProvider.value) as ChatProvider,
|
||||
model: activeModel.value,
|
||||
providerConfig,
|
||||
})
|
||||
await send(messageInput.value, {
|
||||
chatProvider: providersStore.getProviderInstance(activeProvider.value) as ChatProvider,
|
||||
model: activeModel.value,
|
||||
providerConfig,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
messages.value.pop()
|
||||
messages.value.push({
|
||||
role: 'error',
|
||||
content: (error as Error).message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const { destroy, start } = useMicVAD(selectedAudioDeviceId, {
|
||||
@@ -85,16 +95,16 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div fixed bottom-0 w-full flex gap-1 max-w="[calc(100dvw-1rem)]">
|
||||
<MobileChatHistory absolute left-0 top-0 transform="translate-y-[-100%]" w-full />
|
||||
<MobileChatHistory absolute left-0 top-0 transform="translate-y-[-100%]" h="80dvh" w-full />
|
||||
<div flex flex-1>
|
||||
<BasicTextarea
|
||||
v-model="messageInput"
|
||||
:placeholder="t('stage.message')"
|
||||
border="solid 2 primary-200 dark:primary-400/20"
|
||||
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="primary-50 dark:primary-400/20"
|
||||
max-h="[10lh]" min-h="[1lh]"
|
||||
w-full resize-none overflow-y-scroll rounded-xl p-2 font-medium outline-none
|
||||
border="solid 2 primary-50 dark:primary-950/10"
|
||||
text="primary-500 hover:primary-600 dark:primary-100 dark:hover:primary-200 placeholder:primary-400 placeholder:hover:primary-500 placeholder:dark:primary-300 placeholder:dark:hover:primary-400"
|
||||
bg="primary-50/50 dark:primary-950/70"
|
||||
max-h="[10lh]" min-h="[2lh]"
|
||||
w-full resize-none overflow-y-scroll rounded-xl p-3 font-medium outline-none backdrop-blur-md
|
||||
transition="all duration-250 ease-in-out placeholder:all placeholder:duration-250 placeholder:ease-in-out"
|
||||
:class="{ 'transition-colors-none placeholder:transition-colors-none': themeColorsHueDynamic }"
|
||||
@submit="handleSend"
|
||||
|
||||
@@ -8,11 +8,11 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
const chatHistoryRef = ref<HTMLDivElement>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { messages, sending } = storeToRefs(useChatStore())
|
||||
const bounding = useElementBounding(chatHistoryRef, { immediate: true, windowScroll: true, windowResize: true })
|
||||
const { y: chatHistoryContainerY } = useScroll(chatHistoryRef)
|
||||
|
||||
const { t } = useI18n()
|
||||
const { process } = useMarkdown()
|
||||
const { onBeforeMessageComposed, onTokenLiteral } = useChatStore()
|
||||
|
||||
|
||||
@@ -36,21 +36,39 @@ onTokenLiteral(async () => {
|
||||
<template>
|
||||
<div py="1" flex="~ col" rounded="lg" overflow-hidden>
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
<div ref="chatHistoryRef" v-auto-animate h-full w-full max-h="30vh" flex="~ col" overflow-scroll class="chat-history">
|
||||
<div ref="chatHistoryRef" v-auto-animate h-full w-full max-h="40%" flex="~ col" overflow-scroll class="chat-history">
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
<div v-for="(message, index) in messages" :key="index" mb-2>
|
||||
<div v-if="message.role === 'error'" flex mr="12">
|
||||
<div
|
||||
flex="~ col"
|
||||
shadow="md violet-200/20 dark:none"
|
||||
min-w-20 rounded-lg px-3 py-2
|
||||
h="unset <sm:fit"
|
||||
bg="violet-500 dark:violet-800"
|
||||
backdrop-blur-sm
|
||||
>
|
||||
<div flex="~ row" gap-2>
|
||||
<div flex-1>
|
||||
<span text-xs text="violet-400/90 dark:violet-600/90" font-normal class="inline <sm:hidden">{{ t('stage.chat.message.character-name.core-system') }}</span>
|
||||
</div>
|
||||
<div i-solar:danger-triangle-bold-duotone text-violet-500 />
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div v-if="message.role === 'assistant'" flex mr="12">
|
||||
<div
|
||||
|
||||
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 backdrop-blur-sm
|
||||
shadow="md primary-200/20 dark:none"
|
||||
min-w-20 rounded-lg px-3 py-2
|
||||
h="unset <sm:fit"
|
||||
bg="<md:primary-500/25"
|
||||
bg="primary-100 dark:primary-800"
|
||||
backdrop-blur-md
|
||||
>
|
||||
<div>
|
||||
<span text-xs text="primary-400/90 dark:primary-600/90" font-normal class="inline <sm:hidden">{{ t('stage.chat.message.character-name.airi') }}</span>
|
||||
<span text="primary-400/90 dark:primary-600/90" text-xs font-normal class="inline <sm:hidden">{{ t('stage.chat.message.character-name.airi') }}</span>
|
||||
</div>
|
||||
<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 />
|
||||
@@ -59,11 +77,11 @@ onTokenLiteral(async () => {
|
||||
<div v-else-if="message.role === 'user'" flex="~">
|
||||
<div
|
||||
flex="~ col"
|
||||
border="4 solid cyan-200/50 dark:cyan-500/50"
|
||||
shadow="md cyan-200/50 dark:none"
|
||||
shadow="md cyan-200/20 dark:none"
|
||||
px="2"
|
||||
h="unset <sm:fit" min-w-20 rounded-lg px-2 py-1 backdrop-blur-sm
|
||||
bg="<md:cyan-500/25"
|
||||
h="unset <sm:fit" min-w-20 rounded-lg px-3 py-2
|
||||
bg="white dark:neutral-800"
|
||||
backdrop-blur-md
|
||||
>
|
||||
<div>
|
||||
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-normal class="inline <sm:hidden">{{ t('stage.chat.message.character-name.you') }}</span>
|
||||
@@ -78,8 +96,14 @@ onTokenLiteral(async () => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/*
|
||||
DO NOT ATTEMPT TO USE backdrop-filter TOGETHER WITH mask-image.
|
||||
|
||||
html - Why doesn't blur backdrop-filter work together with mask-image? - Stack Overflow
|
||||
https://stackoverflow.com/questions/72780266/why-doesnt-blur-backdrop-filter-work-together-with-mask-image
|
||||
*/
|
||||
.chat-history {
|
||||
--gradient: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1));
|
||||
--gradient: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 20%);
|
||||
-webkit-mask-image: var(--gradient);
|
||||
mask-image: var(--gradient);
|
||||
-webkit-mask-size: 100% 100%;
|
||||
|
||||
@@ -32,7 +32,7 @@ watch(live2dModelRef, (model) => {
|
||||
|
||||
function handleModelLoaded() {
|
||||
if (live2dModelRef.value) {
|
||||
live2dModelRef.value.setMotion(motion.value)
|
||||
live2dModelRef.value?.setMotion(motion.value)
|
||||
motionGroupsList.value = live2dModelRef.value.listMotionGroups()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { WidgetStage } from '@proj-airi/stage-ui/components/scenes'
|
||||
import { useDark, useMouse } from '@vueuse/core'
|
||||
import { breakpointsTailwind, useBreakpoints, useDark, useMouse } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Cross from '../components/Backgrounds/Cross.vue'
|
||||
@@ -17,6 +17,8 @@ function handleSettingsOpen(open: boolean) {
|
||||
}
|
||||
|
||||
const positionCursor = useMouse()
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
const isMobile = breakpoints.smaller('md')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,10 +30,7 @@ const positionCursor = useMouse()
|
||||
>
|
||||
<div relative flex="~ col" z-2 h-100dvh w-100vw of-hidden>
|
||||
<!-- header -->
|
||||
<div
|
||||
class="px-0 py-1 hidden sm:block md:px-3 md:py-3"
|
||||
w-full gap-2
|
||||
>
|
||||
<div class="px-0 py-1 md:px-3 md:py-3" w-full gap-2>
|
||||
<Header class="flex" />
|
||||
</div>
|
||||
<!-- page -->
|
||||
@@ -43,7 +42,7 @@ const positionCursor = useMouse()
|
||||
x: positionCursor.x.value,
|
||||
y: positionCursor.y.value,
|
||||
}"
|
||||
x-offset="-10%"
|
||||
:x-offset="isMobile ? undefined : '-10%'"
|
||||
/>
|
||||
<InteractiveArea class="flex <md:hidden" absolute h="85dvh" right-4 flex-1 max-w="500px" min-w="30%" />
|
||||
<MobileInteractiveArea class="hidden <md:block" mx2 mb2 @settings-open="handleSettingsOpen" />
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"unified": "^11.0.5",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unspeech": "^0.1.7",
|
||||
"vaul-vue": "^0.4.1",
|
||||
"vue-i18n": "^11.1.9",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue-tsc": "^2.2.12",
|
||||
|
||||
@@ -230,7 +230,8 @@ async function initLive2DPixiStage() {
|
||||
}
|
||||
|
||||
async function setMotion(motionName: string, index?: number) {
|
||||
await model.value!.motion(motionName, index, MotionPriority.FORCE)
|
||||
// TODO: motion? Not every Live2D model has motion, we do need to help users to set motion
|
||||
await model.value?.motion(motionName, index, MotionPriority.FORCE)
|
||||
}
|
||||
|
||||
const handleResize = useDebounceFn(() => {
|
||||
|
||||
Generated
+3
@@ -1168,6 +1168,9 @@ importers:
|
||||
unspeech:
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7
|
||||
vaul-vue:
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1(reka-ui@2.3.2(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))
|
||||
vue-i18n:
|
||||
specifier: ^11.1.9
|
||||
version: 11.1.9(vue@3.5.17(typescript@5.8.3))
|
||||
|
||||
Reference in New Issue
Block a user