Skip to content

Commit e33e5ad

Browse files
author
Kerwin
committed
perf: 优化加载体验
1 parent c78d2f1 commit e33e5ad

File tree

4 files changed

+107
-100
lines changed

4 files changed

+107
-100
lines changed

docker-compose/docker-compose.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,19 @@ services:
6969
MONGO_INITDB_ROOT_PASSWORD: xxxx
7070
MONGO_INITDB_DATABASE: chatgpt
7171

72-
7372
mongo-gui:
74-
container_name: "mongo-gui"
73+
container_name: mongo-gui
7574
image: ugleiton/mongo-gui
7675
restart: always
7776
ports:
78-
- "4321:4321"
77+
- '4321:4321'
7978
environment:
8079
- MONGO_URL=mongodb://chatgpt:xxxx@database:27017
8180
links:
8281
- database
8382
depends_on:
8483
- database
85-
84+
8685
nginx:
8786
image: nginx:alpine
8887
container_name: chatgptweb-database

src/store/modules/chat/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,23 @@ export const useChatStore = defineStore('chat-store', {
3737
if (uuid == null)
3838
uuid = r.uuid
3939
this.chat.unshift({ uuid: r.uuid, data: [] })
40-
if (uuid === r.uuid)
41-
this.syncChat(r, callback)
4240
}
4341
if (uuid == null) {
4442
uuid = Date.now()
4543
this.addHistory({ title: 'New Chat', uuid: Date.now(), isEdit: false })
4644
}
4745
this.active = uuid
4846
this.reloadRoute(uuid)
47+
callback && callback()
4948
},
5049

5150
async syncChat(h: Chat.History, callback: () => void) {
5251
const chatIndex = this.chat.findIndex(item => item.uuid === h.uuid)
5352
if (chatIndex <= -1 || this.chat[chatIndex].data.length <= 0) {
5453
const chatData = (await fetchGetChatHistory(h.uuid)).data
5554
this.chat.unshift({ uuid: h.uuid, data: chatData })
56-
callback && callback()
5755
}
56+
callback && callback()
5857
},
5958

6059
setUsingContext(context: boolean) {

src/views/chat/index.vue

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Ref } from 'vue'
33
import { computed, onMounted, onUnmounted, ref } from 'vue'
44
import { useRoute } from 'vue-router'
55
import { storeToRefs } from 'pinia'
6-
import { NAutoComplete, NButton, NInput, useDialog, useMessage } from 'naive-ui'
6+
import { NAutoComplete, NButton, NInput, NSpin, useDialog, useMessage } from 'naive-ui'
77
import html2canvas from 'html2canvas'
88
import { Message } from './components'
99
import { useScroll } from './hooks/useScroll'
@@ -16,6 +16,7 @@ import { useBasicLayout } from '@/hooks/useBasicLayout'
1616
import { useChatStore, usePromptStore } from '@/store'
1717
import { fetchChatAPIProcess } from '@/api'
1818
import { t } from '@/locales'
19+
import { debounce } from '@/utils/functions/debounce'
1920
2021
let controller = new AbortController()
2122
@@ -40,6 +41,7 @@ const dataSources = computed(() => chatStore.getChatByUuid(+uuid))
4041
const conversationList = computed(() => dataSources.value.filter(item => (!item.inversion && !item.error)))
4142
4243
const prompt = ref<string>('')
44+
const firstLoading = ref<boolean>(false)
4345
const loading = ref<boolean>(false)
4446
const inputRef = ref<Ref | null>(null)
4547
@@ -461,9 +463,16 @@ const footerClass = computed(() => {
461463
})
462464
463465
onMounted(() => {
464-
scrollToBottom()
465-
if (inputRef.value && !isMobile.value)
466-
inputRef.value?.focus()
466+
firstLoading.value = true
467+
debounce(() => {
468+
// 直接刷 极小概率不请求
469+
chatStore.syncChat({ uuid: Number(uuid) } as Chat.History, () => {
470+
firstLoading.value = false
471+
scrollToBottom()
472+
if (inputRef.value && !isMobile.value)
473+
inputRef.value?.focus()
474+
})
475+
}, 100)()
467476
})
468477
469478
onUnmounted(() => {
@@ -491,35 +500,37 @@ onUnmounted(() => {
491500
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
492501
:class="[isMobile ? 'p-2' : 'p-4']"
493502
>
494-
<template v-if="!dataSources.length">
495-
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
496-
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
497-
<span>Aha~</span>
498-
</div>
499-
</template>
500-
<template v-else>
501-
<div>
502-
<Message
503-
v-for="(item, index) of dataSources"
504-
:key="index"
505-
:date-time="item.dateTime"
506-
:text="item.text"
507-
:inversion="item.inversion"
508-
:error="item.error"
509-
:loading="item.loading"
510-
@regenerate="onRegenerate(index)"
511-
@delete="handleDelete(index)"
512-
/>
513-
<div class="sticky bottom-0 left-0 flex justify-center">
514-
<NButton v-if="loading" type="warning" @click="handleStop">
515-
<template #icon>
516-
<SvgIcon icon="ri:stop-circle-line" />
517-
</template>
518-
Stop Responding
519-
</NButton>
503+
<NSpin :show="firstLoading">
504+
<template v-if="!dataSources.length">
505+
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
506+
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
507+
<span>Aha~</span>
520508
</div>
521-
</div>
522-
</template>
509+
</template>
510+
<template v-else>
511+
<div>
512+
<Message
513+
v-for="(item, index) of dataSources"
514+
:key="index"
515+
:date-time="item.dateTime"
516+
:text="item.text"
517+
:inversion="item.inversion"
518+
:error="item.error"
519+
:loading="item.loading"
520+
@regenerate="onRegenerate(index)"
521+
@delete="handleDelete(index)"
522+
/>
523+
<div class="sticky bottom-0 left-0 flex justify-center">
524+
<NButton v-if="loading" type="warning" @click="handleStop">
525+
<template #icon>
526+
<SvgIcon icon="ri:stop-circle-line" />
527+
</template>
528+
Stop Responding
529+
</NButton>
530+
</div>
531+
</div>
532+
</template>
533+
</NSpin>
523534
</div>
524535
</div>
525536
</main>
Lines changed: 59 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang='ts'>
2-
import { computed, nextTick, onMounted } from 'vue'
3-
import { NInput, NPopconfirm, NScrollbar } from 'naive-ui'
2+
import { computed, onMounted, ref } from 'vue'
3+
import { NInput, NPopconfirm, NScrollbar, NSpin } from 'naive-ui'
44
import { SvgIcon } from '@/components/common'
55
import { useAppStore, useChatStore } from '@/store'
66
import { useBasicLayout } from '@/hooks/useBasicLayout'
@@ -13,6 +13,8 @@ const appStore = useAppStore()
1313
const chatStore = useChatStore()
1414
const authStore = useAuthStoreWithout()
1515
16+
const loadingRoom = ref(false)
17+
1618
const dataSources = computed(() => chatStore.history)
1719
1820
onMounted(async () => {
@@ -23,10 +25,9 @@ onMounted(async () => {
2325
async function handleSyncChatRoom() {
2426
// if (chatStore.history.length == 1 && chatStore.history[0].title == 'New Chat'
2527
// && chatStore.chat[0].data.length <= 0)
28+
loadingRoom.value = true
2629
chatStore.syncHistory(() => {
27-
const scrollRef = document.querySelector('#scrollRef')
28-
if (scrollRef)
29-
nextTick(() => scrollRef.scrollTop = scrollRef.scrollHeight)
30+
loadingRoom.value = false
3031
})
3132
}
3233
@@ -37,11 +38,6 @@ async function handleSelect({ uuid }: Chat.History) {
3738
// 这里不需要 不然每次切换都rename
3839
// if (chatStore.active)
3940
// chatStore.updateHistory(chatStore.active, { isEdit: false })
40-
chatStore.syncChat({ uuid } as Chat.History, () => {
41-
const scrollRef = document.querySelector('#scrollRef')
42-
if (scrollRef)
43-
nextTick(() => scrollRef.scrollTop = scrollRef.scrollHeight)
44-
})
4541
await chatStore.setActive(uuid)
4642
4743
if (isMobile.value)
@@ -74,55 +70,57 @@ function isActive(uuid: number) {
7470
</script>
7571

7672
<template>
77-
<NScrollbar class="px-4">
78-
<div class="flex flex-col gap-2 text-sm">
79-
<template v-if="!dataSources.length">
80-
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
81-
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
82-
<span>{{ $t('common.noData') }}</span>
83-
</div>
84-
</template>
85-
<template v-else>
86-
<div v-for="(item, index) of dataSources" :key="index">
87-
<a
88-
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
89-
:class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
90-
@click="handleSelect(item)"
91-
>
92-
<span>
93-
<SvgIcon icon="ri:message-3-line" />
94-
</span>
95-
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
96-
<NInput
97-
v-if="item.isEdit"
98-
v-model:value="item.title" size="tiny"
99-
@keypress="handleEnter(item, false, $event)"
100-
/>
101-
<span v-else>{{ item.title }}</span>
102-
</div>
103-
<div v-if="isActive(item.uuid)" class="absolute z-10 flex visible right-1">
104-
<template v-if="item.isEdit">
105-
<button class="p-1" @click="handleEdit(item, false, $event)">
106-
<SvgIcon icon="ri:save-line" />
107-
</button>
108-
</template>
109-
<template v-else>
110-
<button class="p-1">
111-
<SvgIcon icon="ri:edit-line" @click="handleEdit(item, true, $event)" />
112-
</button>
113-
<NPopconfirm placement="bottom" @positive-click="handleDeleteDebounce(index, $event)">
114-
<template #trigger>
115-
<button class="p-1">
116-
<SvgIcon icon="ri:delete-bin-line" />
117-
</button>
118-
</template>
119-
{{ $t('chat.deleteHistoryConfirm') }}
120-
</NPopconfirm>
121-
</template>
122-
</div>
123-
</a>
124-
</div>
125-
</template>
126-
</div>
127-
</NScrollbar>
73+
<NSpin :show="loadingRoom">
74+
<NScrollbar class="px-4">
75+
<div class="flex flex-col gap-2 text-sm">
76+
<template v-if="!dataSources.length">
77+
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
78+
<SvgIcon icon="ri:inbox-line" class="mb-2 text-3xl" />
79+
<span>{{ $t('common.noData') }}</span>
80+
</div>
81+
</template>
82+
<template v-else>
83+
<div v-for="(item, index) of dataSources" :key="index">
84+
<a
85+
class="relative flex items-center gap-3 px-3 py-3 break-all border rounded-md cursor-pointer hover:bg-neutral-100 group dark:border-neutral-800 dark:hover:bg-[#24272e]"
86+
:class="isActive(item.uuid) && ['border-[#4b9e5f]', 'bg-neutral-100', 'text-[#4b9e5f]', 'dark:bg-[#24272e]', 'dark:border-[#4b9e5f]', 'pr-14']"
87+
@click="handleSelect(item)"
88+
>
89+
<span>
90+
<SvgIcon icon="ri:message-3-line" />
91+
</span>
92+
<div class="relative flex-1 overflow-hidden break-all text-ellipsis whitespace-nowrap">
93+
<NInput
94+
v-if="item.isEdit"
95+
v-model:value="item.title" size="tiny"
96+
@keypress="handleEnter(item, false, $event)"
97+
/>
98+
<span v-else>{{ item.title }}</span>
99+
</div>
100+
<div v-if="isActive(item.uuid)" class="absolute z-10 flex visible right-1">
101+
<template v-if="item.isEdit">
102+
<button class="p-1" @click="handleEdit(item, false, $event)">
103+
<SvgIcon icon="ri:save-line" />
104+
</button>
105+
</template>
106+
<template v-else>
107+
<button class="p-1">
108+
<SvgIcon icon="ri:edit-line" @click="handleEdit(item, true, $event)" />
109+
</button>
110+
<NPopconfirm placement="bottom" @positive-click="handleDeleteDebounce(index, $event)">
111+
<template #trigger>
112+
<button class="p-1">
113+
<SvgIcon icon="ri:delete-bin-line" />
114+
</button>
115+
</template>
116+
{{ $t('chat.deleteHistoryConfirm') }}
117+
</NPopconfirm>
118+
</template>
119+
</div>
120+
</a>
121+
</div>
122+
</template>
123+
</div>
124+
</NScrollbar>
125+
</NSpin>
128126
</template>

0 commit comments

Comments
 (0)