feat: mobile chat
This commit is contained in:
@@ -10,6 +10,7 @@ import { useMicVAD } from '../../composables/micvad'
|
||||
// import { useAudioContext } from '../../stores/audio'
|
||||
import { useChatStore } from '../../stores/chat'
|
||||
import { useSettings } from '../../stores/settings'
|
||||
import MobileChatHistory from '../Widgets/MobileChatHistory.vue'
|
||||
import MobileSettings from '../Widgets/MobileSettings.vue'
|
||||
|
||||
const messageInput = ref('')
|
||||
@@ -87,39 +88,42 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div gap-1>
|
||||
<div flex flex-1>
|
||||
<BasicTextarea
|
||||
v-model="messageInput"
|
||||
:placeholder="t('stage.message')"
|
||||
border="solid 2 pink-100 dark:pink-400/20"
|
||||
text="pink-400 hover:pink-600 dark:pink-400/50 dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:pink-400/50 placeholder:dark:hover:pink-600"
|
||||
bg="pink-50 dark:pink-400/20" max-h="[10lh]" min-h="[1lh]"
|
||||
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"
|
||||
@submit="handleSend"
|
||||
/>
|
||||
</div>
|
||||
<DrawerRoot should-scale-background>
|
||||
<DrawerTrigger
|
||||
class="px-4 py-2.5"
|
||||
border="solid 2 pink-100 dark:pink-400/20"
|
||||
text="lg pink-400 hover:pink-600 dark:pink-400/50 dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:pink-400/50 placeholder:dark:hover:pink-600"
|
||||
bg="pink-50 dark:pink-400/20" max-h="[10lh]" min-h="[1lh]" rounded-r-xl
|
||||
>
|
||||
<div i-solar:settings-bold-duotone />
|
||||
</DrawerTrigger>
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay class="fixed inset-0 z-50 bg-black/40" />
|
||||
<DrawerContent
|
||||
max-h="[75%]"
|
||||
fixed bottom-0 left-0 right-0 z-50 mt-24 h-full flex flex-col rounded-t-lg bg="[#fffbff] dark:[#1f1a1d]"
|
||||
<div>
|
||||
<div relative w-full flex gap-1>
|
||||
<MobileChatHistory absolute left-0 top-0 transform="translate-y-[-100%]" w-full />
|
||||
<div flex flex-1>
|
||||
<BasicTextarea
|
||||
v-model="messageInput"
|
||||
:placeholder="t('stage.message')"
|
||||
border="solid 2 pink-100 dark:pink-400/20"
|
||||
text="pink-400 hover:pink-600 dark:pink-400/50 dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:pink-400/50 placeholder:dark:hover:pink-600"
|
||||
bg="pink-50 dark:pink-400/20" max-h="[10lh]" min-h="[1lh]"
|
||||
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"
|
||||
@submit="handleSend"
|
||||
/>
|
||||
</div>
|
||||
<DrawerRoot should-scale-background>
|
||||
<DrawerTrigger
|
||||
class="px-4 py-2.5"
|
||||
border="solid 2 pink-100 dark:pink-400/20"
|
||||
text="lg pink-400 hover:pink-600 dark:pink-400/50 dark:hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600 placeholder:dark:pink-400/50 placeholder:dark:hover:pink-600"
|
||||
bg="pink-50 dark:pink-400/20" max-h="[10lh]" min-h="[1lh]" rounded-r-xl
|
||||
>
|
||||
<div class="flex flex-1 flex-col rounded-t-lg p-5" bg="[#fffbff] dark:[#1f1a1d]" gap-2>
|
||||
<MobileSettings />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</DrawerRoot>
|
||||
<div i-solar:settings-bold-duotone />
|
||||
</DrawerTrigger>
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay class="fixed inset-0 z-50 bg-black/40" />
|
||||
<DrawerContent
|
||||
max-h="[75%]"
|
||||
fixed bottom-0 left-0 right-0 z-50 mt-24 h-full flex flex-col rounded-t-lg bg="[#fffbff] dark:[#1f1a1d]"
|
||||
>
|
||||
<div class="flex flex-1 flex-col rounded-t-lg p-5" bg="[#fffbff] dark:[#1f1a1d]" gap-2>
|
||||
<MobileSettings />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</DrawerRoot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -36,9 +36,7 @@ onTokenLiteral(async () => {
|
||||
<div
|
||||
relative
|
||||
class="<md:(absolute left-0 top-0 from-zinc-100/80 to-zinc-800/0 bg-gradient-to-t p-2 dark:from-zinc-800/80)"
|
||||
px="<sm:2" py="<sm:2" flex="~ col"
|
||||
rounded="lg"
|
||||
overflow-hidden
|
||||
px="<sm:2" py="<sm:2" flex="~ col" rounded="lg" overflow-hidden
|
||||
>
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
<div ref="chatHistoryRef" v-auto-animate h-full w-full flex="~ col" overflow-scroll>
|
||||
|
||||
@@ -33,15 +33,9 @@ onTokenLiteral(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
relative
|
||||
class="<md:(absolute left-0 top-0 from-zinc-100/80 to-zinc-800/0 bg-gradient-to-t p-2 dark:from-zinc-800/80)"
|
||||
px="<sm:2" py="<sm:2" flex="~ col"
|
||||
rounded="lg"
|
||||
overflow-hidden
|
||||
>
|
||||
<div py="1" flex="~ col" rounded="lg" overflow-hidden>
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
<div ref="chatHistoryRef" v-auto-animate h-full w-full flex="~ col" overflow-scroll>
|
||||
<div ref="chatHistoryRef" v-auto-animate h-full w-full max-h="30vh" flex="~ col" overflow-scroll>
|
||||
<div flex-1 /> <!-- spacer -->
|
||||
<div v-for="(message, index) in messages" :key="index" mb-2>
|
||||
<div v-if="message.role === 'assistant'" flex mr="12">
|
||||
@@ -60,7 +54,7 @@ onTokenLiteral(async () => {
|
||||
<div v-else i-eos-icons:three-dots-loading />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="message.role === 'user'" flex="~ row-reverse" ml="12">
|
||||
<div v-else-if="message.role === 'user'" flex="~">
|
||||
<div
|
||||
flex="~ col"
|
||||
border="4 solid teal-200/50 dark:teal-500/50"
|
||||
|
||||
@@ -15,7 +15,7 @@ import Stage from '../components/Widgets/Stage.vue'
|
||||
<div flex="~ 1 row <md:col" relative h-full w-full items-end gap-2>
|
||||
<Stage h-full w-full flex-1 mb="<md:18" min-w="50%" />
|
||||
<InteractiveArea w-full flex-1 h="full <md:40%" max-w="30% <md:100%" class="flex <md:hidden" />
|
||||
<MobileInteractiveArea w="full" class="md:hidden <md:flex" absolute bottom-0 w-full />
|
||||
<MobileInteractiveArea class="<md:block md:hidden" absolute bottom-0 w-full />
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedBackground>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { assistant, type AssistantMessage, type Message, user } from '@xsai/shared-chat'
|
||||
import type { AssistantMessage, Message } from '@xsai/shared-chat'
|
||||
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
@@ -55,8 +55,6 @@ export const useChatStore = defineStore('chat', () => {
|
||||
t('prompt.prefix'),
|
||||
t('prompt.suffix'),
|
||||
),
|
||||
user('abcd'),
|
||||
assistant('efgh'),
|
||||
])
|
||||
|
||||
const streamingMessage = ref<AssistantMessage>({ role: 'assistant', content: '' })
|
||||
|
||||
Vendored
+9
@@ -18,5 +18,14 @@ declare module 'vue-router/auto-routes' {
|
||||
* Route name map generated by unplugin-vue-router
|
||||
*/
|
||||
export interface RouteNamedMap {
|
||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
|
||||
'/audio': RouteRecordInfo<'/audio', '/audio', Record<never, never>, Record<never, never>>,
|
||||
'/devtools/image': RouteRecordInfo<'/devtools/image', '/devtools/image', Record<never, never>, Record<never, never>>,
|
||||
'/queue': RouteRecordInfo<'/queue', '/queue', Record<never, never>, Record<never, never>>,
|
||||
'/test/filter-message': RouteRecordInfo<'/test/filter-message', '/test/filter-message', Record<never, never>, Record<never, never>>,
|
||||
'/test/queues/delays': RouteRecordInfo<'/test/queues/delays', '/test/queues/delays', Record<never, never>, Record<never, never>>,
|
||||
'/test/queues/emotions': RouteRecordInfo<'/test/queues/emotions', '/test/queues/emotions', Record<never, never>, Record<never, never>>,
|
||||
'/test/queues/messages': RouteRecordInfo<'/test/queues/messages', '/test/queues/messages', Record<never, never>, Record<never, never>>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"vueCompilerOptions": {
|
||||
|
||||
Reference in New Issue
Block a user