Skip to content

Commit

Permalink
feat: added responsive layout support for chat page
Browse files Browse the repository at this point in the history
  • Loading branch information
satrong committed Jun 3, 2024
1 parent 5752a35 commit 36d4f4c
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 24 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"groq",
"jina",
"knowledgebase",
"nuxt"
"nuxt",
"Slideover"
],
"i18n-ally.localesPaths": [
"locales"
Expand Down
1 change: 1 addition & 0 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<USlideovers />
<UModals />
</div>
</template>
2 changes: 2 additions & 0 deletions assets/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ body {
samp {
font-family: inherit;
}

--top-height: 48px;
}

.md-body {
Expand Down
8 changes: 5 additions & 3 deletions components/Chat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,16 @@ async function saveMessage(data: Omit<ChatHistory, 'sessionId'>) {
</script>

<template>
<div class="flex flex-col box-border dark:text-gray-300 -mx-4">
<div class="flex flex-col box-border dark:text-gray-300 md:-mx-4">
<div class="px-4 border-b border-gray-200 dark:border-gray-700 box-border h-[57px] flex items-center">
<slot name="left-menu-btn"></slot>
<ChatConfigInfo v-if="instructionInfo" icon="i-iconoir-terminal"
:title="instructionInfo.name"
:description="instructionInfo.instruction" />
:description="instructionInfo.instruction"
class="hidden md:block" />
<ChatConfigInfo v-if="knowledgeBaseInfo" icon="i-heroicons-book-open"
:title="knowledgeBaseInfo.name"
class="mx-2" />
class="mx-2 hidden md:block" />
<div class="mx-auto px-4 text-center">
<h2 class="line-clamp-1">{{ sessionInfo?.title || t('chat.untitled') }}</h2>
<div class="text-xs text-muted line-clamp-1">{{ instructionInfo?.name }}</div>
Expand Down
10 changes: 6 additions & 4 deletions components/ChatInputBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ const emits = defineEmits<{
stop: []
}>()
const { isMobile } = useMediaBreakpoints()
const { t } = useI18n()
const submitMode = useStorage<SubmitMode>('sendMode', 'enter')
const state = reactive<ChatBoxFormData>({
content: '',
})
const tip = computed(() => {
const s = sendModeList.value[0].find(el => el.value === submitMode.value)?.label || ''
return `(${s})`
return ` (${s})`
})
const isFocus = ref(false)
const sendModeList = computed(() => {
Expand All @@ -37,6 +38,7 @@ const sendModeList = computed(() => {
const disabledBtn = computed(() => {
return props.disabled || (!props.loading && !state.content.trim())
})
const btnTip = computed(() => props.loading ? t('chat.stop') : (isMobile.value ? '' : tip.value))
defineExpose({
reset: onReset
Expand Down Expand Up @@ -76,11 +78,11 @@ function onReset() {
<slot></slot>
<div class="flex items-center ml-auto">
<ClientOnly>
<UButton type="submit" :disabled="disabledBtn" class="send-btn"
<UButton type="submit" :disabled="disabledBtn" :class="{ 'send-btn': !isMobile }"
:icon="loading ? 'i-iconoir-square' : 'i-iconoir-send-diagonal'" @click="onStop">
<span class="text-xs tip-text">{{ loading ? ' Stop' : tip }}</span>
<span class="text-xs tip-text" v-show="btnTip">{{ btnTip }}</span>
</UButton>
<UDropdown :items="sendModeList" :popper="{ placement: 'top-end' }">
<UDropdown v-if="!isMobile" :items="sendModeList" :popper="{ placement: 'top-end' }">
<UButton trailing-icon="i-heroicons-chevron-down-20-solid" class="arrow-btn" />
</UDropdown>
</ClientOnly>
Expand Down
15 changes: 10 additions & 5 deletions components/ChatSessionList.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<script lang="ts" setup>
import { useStorage } from '@vueuse/core'
import { USlideover } from '#components'
const emits = defineEmits<{
select: [sessionId: number]
closePanel: []
}>()
const { t } = useI18n()
const createChatSession = useCreateChatSession()
const { isMobile } = useMediaBreakpoints()
const sessionList = ref<ChatSession[]>([])
const currentSessionId = useStorage<number>('currentSessionId', 0)
Expand Down Expand Up @@ -105,18 +108,20 @@ async function updateSessionInfo(data: Partial<Omit<ChatSession, 'id' | 'createT
</script>

<template>
<div class="h-full box-border border-r dark:border-gray-800">
<Component :is="isMobile ? USlideover : 'div'"
:class="isMobile ? 'w-[80vw] max-w-[400px] h-full' : 'border-r dark:border-gray-800'"
class="h-full box-border">
<div class="p-3 border-b border-primary-400/30 flex items-center">
<h3 class="text-primary-600 dark:text-primary-300 mr-auto">{{ t("chat.allChats") }} ({{ sessionList.length }})</h3>
<UTooltip :text="t('chat.newChat')" :popper="{ placement: 'top' }">
<UButton icon="i-material-symbols-add" color="primary" square @click="onNewChat"></UButton>
</UTooltip>
<UButton icon="i-material-symbols-close-rounded" color="gray" class="md:hidden ml-4" @click="emits('closePanel')"></UButton>
</div>
<TransitionGroup tag="div" name="list" class="h-[calc(100%-57px)] overflow-auto">
<div v-for="item in sessionList" :key="item.id"
class="session-item relative box-border p-2 cursor-pointer dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700/30 border-b border-gray-100 dark:border-gray-900"

:class="{ '!bg-primary-300/10': item.isTop }"
class="session-item relative box-border p-2 cursor-pointer dark:text-gray-300 border-b border-gray-100 dark:border-gray-100/5"
:class="item.isTop ? 'bg-primary-300/10 dark:bg-primary-800/10' : 'hover:bg-gray-50 dark:hover:bg-gray-700/30'"
@click="onSelectChat(item.id!)">
<div class="w-full flex items-center text-sm h-[32px]">
<div class="line-clamp-1 grow opacity-80"
Expand All @@ -130,7 +135,7 @@ async function updateSessionInfo(data: Partial<Omit<ChatSession, 'id' | 'createT
<div v-if="item.isTop" class="triangle"></div>
</div>
</TransitionGroup>
</div>
</Component>
</template>

<style lang="scss" scoped>
Expand Down
9 changes: 9 additions & 0 deletions composables/useMediaBreakpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'

export function useMediaBreakpoints() {
const breakpoints = useBreakpoints(breakpointsTailwind)

const isMobile = computed(() => breakpoints.smaller('md').value)

return { isMobile }
}
6 changes: 3 additions & 3 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ watch(() => route.path, () => {
</script>

<template>
<div class="border-b border-gray-200 dark:border-gray-700" style="--top-height: 48px;">
<div class="flex items-center justify-between max-w-6xl mx-auto px-4 h-[var(--top-height)]">
<div class="border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between max-w-6xl mx-auto px-4 box-border h-[var(--top-height)]">
<h1 class="flex flex-row items-center mr-2">
<TheLogo class="w-[32px] h-[32px] mr-2" />
<span class="text-primary font-semibold text-lg">{{ $config.public.appName }}</span>
Expand Down Expand Up @@ -42,7 +42,7 @@ watch(() => route.path, () => {
</div>
</div>
</div>
<div id="main" class="p-4 box-border overflow-auto" style="height: calc(100% - 61px)">
<div id="main" class="p-2 md:p-4 box-border overflow-auto" style="height: calc(100% - var(--top-height) - 1px)">
<slot />
</div>
<UNotifications />
Expand Down
3 changes: 2 additions & 1 deletion locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"clearBtn": "Clear history messages",
"clearConfirmTip": "Are you sure you want to clear all history messages for this conversation?",
"pin": "Pin",
"unpin": "Unpin"
"unpin": "Unpin",
"stop": "Stop"
},
"settings": {
"ollamaServer": "Ollama Server",
Expand Down
3 changes: 2 additions & 1 deletion locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
"clearBtn": "清空历史消息",
"clearConfirmTip": "确定要清空该对话的所有历史消息吗?",
"pin": "置顶",
"unpin": "取消置顶"
"unpin": "取消置顶",
"stop": "停止"
},
"settings": {
"ollamaServer": "Ollama 服务",
Expand Down
38 changes: 32 additions & 6 deletions pages/chat/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ export interface ChatSessionSettings extends Partial<Omit<ChatSession, 'id' | 'c
const { t } = useI18n()
const chatSessionListRef = shallowRef<ComponentInstance<typeof ChatSessionList>>()
const chatRef = shallowRef<ComponentInstance<typeof Chat>>()
const slideover = useSlideover()
const { isMobile } = useMediaBreakpoints()
const sessionId = ref(0)
const latestMessageId = ref(0)
watch(isMobile, val => {
if (!val) {
slideover.close()
}
})
function onChangeSettings(data: ChatSessionSettings) {
chatSessionListRef.value?.updateSessionInfo({ ...data, forceUpdateTitle: true })
}
Expand All @@ -39,19 +47,37 @@ function onNewChat() {
async function onChangeChatSession(id: number) {
sessionId.value = id
}
function onOpenSideMenu() {
slideover.open(ChatSessionList, {
ref: chatSessionListRef,
onSelect: onChangeChatSession,
onClosePanel: () => {
slideover.close()
},
side: 'left',
preventClose: true,
})
}
</script>

<template>
<div class="h-full max-w-6xl mx-auto flex flex-1 border border-gray-200 dark:border-gray-800 rounded-md shadow-md"
style="--chat-side-width:240px">
<ChatSessionList ref="chatSessionListRef"
class="shrink-0 w-[var(--chat-side-width)]"
@select="onChangeChatSession" />
<chat ref="chatRef" v-if="sessionId > 0"
class="flex-1 px-4 pb-4 box-border w-[calc(100%-var(--chat-side-width))]"
<ClientOnly>
<ChatSessionList ref="chatSessionListRef"
class="shrink-0 w-[var(--chat-side-width)] hidden md:block"
@select="onChangeChatSession" />
</ClientOnly>
<Chat ref="chatRef" v-if="sessionId > 0"
class="flex-1 md:px-4 pb-4 box-border w-full md:w-[calc(100%-var(--chat-side-width))]"
:session-id="sessionId"
@change-settings="onChangeSettings"
@message="onMessage" />
@message="onMessage">
<template #left-menu-btn>
<UButton icon="i-material-symbols-lists-rounded" color="gray" class="mr-4 md:hidden rotate-180" @click="onOpenSideMenu"></UButton>
</template>
</Chat>
<div v-else class="grow h-full flex justify-center items-center">
<UButton icon="i-material-symbols-add" color="primary" square @click="onNewChat">{{ t("chat.newChat") }}</UButton>
</div>
Expand Down

0 comments on commit 36d4f4c

Please sign in to comment.