ui: chat sidebar ui

This commit is contained in:
2024-03-25 18:00:39 +08:00
parent ca05296317
commit ef1ef50fd5
11 changed files with 212 additions and 18 deletions

View File

@@ -5,5 +5,6 @@
@layer base {
html {
font-family: 'Rubik', 'Noto Sans SC', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
box-sizing: border-box;
}
}

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import type {ChatSession} from "~/components/aigc/chat/index";
import type {PropType} from "vue";
const props = defineProps({
active: {
type: Boolean,
default: false
},
chatSession: {
type: Object as PropType<ChatSession>,
required: true
},
})
const emit = defineEmits<{
(e: 'remove', session: ChatSession): void
}>()
const dayjs = useDayjs()
</script>
<template>
<div
class="chat-card group"
:class="{'active': active}"
:title="chatSession.subject"
>
<div class="chat-card-title">
{{ chatSession.subject }}
</div>
<div class="chat-card-meta">
<div>{{ chatSession.messages.length }}条对话</div>
<div>{{ dayjs(chatSession.create_at * 1000).format('YYYY-MM-DD HH:mm:ss') }}</div>
</div>
<div
@click="emit('remove', chatSession)"
class="chat-card-remove-btn text-neutral-400 group-hover:opacity-100 group-hover:-translate-x-0.5"
>
<UIcon name="i-tabler-trash"/>
</div>
</div>
</template>
<style lang="scss" scoped>
.chat-card {
@apply flex flex-col gap-2 bg-white dark:bg-neutral-800 px-4 py-3 rounded-lg relative border-2 border-transparent shadow-card;
@apply transition duration-150 hover:bg-cyan-300/5;
@apply select-none;
&.active {
@apply border-cyan-500;
}
&-title {
@apply w-[calc(100%-16px)] text-sm font-medium text-ellipsis text-nowrap overflow-x-hidden;
}
&-meta {
@apply flex justify-between items-center text-xs text-neutral-400;
}
&-remove-btn {
@apply absolute top-0.5 right-0 opacity-0;
@apply transition duration-300 hover:text-red-400;
@apply cursor-pointer;
}
}
</style>

19
components/aigc/chat/index.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
export type ChatSessionId = string
export type ChatMessageId = string
export interface ChatSession {
id: ChatSessionId
subject: string
create_at: number
messages: ChatMessage[]
}
export type MessageRole = 'user' | 'assistant' | 'system'
export interface ChatMessage {
id: ChatMessageId
role: MessageRole
content: string
create_at: number
interrupted?: boolean
}

View File

@@ -38,7 +38,7 @@ onMounted(async () => {
const res = await get(props.fid) as string[] || []
if (res.length === cachedImages.value.length) return
cachedImages.value = res
}, 1000)
}, 200)
})
onUnmounted(() => {

View File

@@ -119,6 +119,24 @@ body {
@apply bg-[url('~/assets/background-pattern.svg')] dark:bg-[url('~/assets/background-pattern-dark.svg')];
}
::-webkit-scrollbar {
--bar-width: 5px;
width: var(--bar-width);
height: var(--bar-width)
}
::-webkit-scrollbar-track {
background-color: transparent
}
::-webkit-scrollbar-thumb {
--bar-color: rgba(0, 0, 0, .1);
background-color: var(--bar-color);
border-radius: 20px;
background-clip: content-box;
border: 1px solid transparent
}
/*
*::-webkit-scrollbar {
@apply w-1.5 h-1.5;
}
@@ -132,6 +150,7 @@ body {
@apply bg-neutral-300 dark:bg-neutral-700;
@apply hover:bg-neutral-400 hover:dark:bg-neutral-600;
}
*/
</style>
<style scoped>

View File

@@ -27,6 +27,7 @@
"@pinia-plugin-persistedstate/nuxt": "^1.2.0",
"@pinia/nuxt": "^0.5.1",
"@vite-pwa/nuxt": "^0.5.0",
"dayjs-nuxt": "^2.1.9"
"dayjs-nuxt": "^2.1.9",
"sass": "^1.72.0"
}
}

View File

@@ -1,13 +0,0 @@
<script setup lang="ts">
useHead({
title: '聊天 | XSH AI'
})
</script>
<template>
ChatGPT
</template>
<style scoped>
</style>

76
pages/aigc/chat/index.vue Normal file
View File

@@ -0,0 +1,76 @@
<script setup lang="ts">
import ChatItem from "~/components/aigc/chat/ChatItem.vue";
import type {ChatSession, ChatSessionId} from "~/components/aigc/chat";
useHead({
title: '聊天 | XSH AI'
})
const dayjs = useDayjs()
const sessions = ref<ChatSession[]>([
{
id: Math.random().toString(36).slice(2),
subject: '测试聊天',
messages: [
{
id: Math.random().toString(36).slice(2),
role: 'user',
content: '你好',
create_at: dayjs().unix(),
}
],
create_at: dayjs().unix(),
},
{
id: Math.random().toString(36).slice(2),
subject: '测试聊天2',
messages: [],
create_at: dayjs().unix(),
},
])
const currentSessionId = ref<ChatSessionId | null>(null)
onMounted(() => {
if (sessions.value.length > 0) {
currentSessionId.value = sessions.value[0].id
}
})
</script>
<template>
<div class="w-full flex relative">
<div class="h-[calc(100vh-4rem)] bg-neutral-100 dark:bg-neutral-900 p-4 flex flex-col w-[300px] shadow-sidebar">
<div class="flex-1 flex flex-col overflow-auto overflow-x-hidden">
<!-- list -->
<div class="flex flex-col gap-3">
<!-- ClientOnly avoids hydrate exception -->
<ClientOnly>
<ChatItem
v-for="(session, i) in sessions"
:chat-session="session" :key="i"
:active="session.id === currentSessionId"
@click="currentSessionId = session.id"
@remove="sessions.splice(sessions.findIndex(s => s.id === session.id), 1)"
/>
</ClientOnly>
</div>
</div>
<div class="flex justify-between items-center">
<div></div>
<div>
<UButton color="white" variant="solid" icon="i-tabler-message-circle-plus">
新建聊天
</UButton>
</div>
</div>
</div>
<div>
content
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -306,7 +306,7 @@ const onDefaultFormSubmit = (event: FormSubmitEvent<DefaultFormSchema>) => {
class="w-[1px] h-full bg-neutral-300 dark:bg-neutral-700 group-hover:bg-indigo-300 dark:group-hover:bg-indigo-700 group-hover:w-[3px] transition-all group-hover:delay-500 translate-x-1"></span>
</div>
<div
class="absolute bottom-28 -right-12 w-12 h-12 z-10 bg-neutral-100 dark:bg-neutral-800 rounded-r-lg shadow-lg flex md:hidden justify-center items-center">
class="absolute bottom-28 -right-12 w-12 h-12 z-10 bg-neutral-100 dark:bg-neutral-900 rounded-r-lg shadow-lg flex md:hidden justify-center items-center">
<UButton color="black" icon="i-tabler-brush" size="lg" square @click="showSidebar = !showSidebar"></UButton>
</div>
<div class="h-full flex flex-col overflow-y-auto">
@@ -315,7 +315,7 @@ const onDefaultFormSubmit = (event: FormSubmitEvent<DefaultFormSchema>) => {
<OptionBlock comment="Prompts" icon="i-tabler-article" label="提示词">
<UFormGroup name="prompt">
<UTextarea v-model="defaultFormState.prompt" :rows="2" autoresize
placeholder="请输入英文提示词,每个提示词之间用英文逗号隔开" resize/>
placeholder="请输入提示词,每个提示词之间用英文逗号隔开" resize/>
</UFormGroup>
</OptionBlock>
<OptionBlock comment="Negative Prompts" icon="i-tabler-article-off" label="负面提示词">

View File

@@ -12,6 +12,10 @@ export default <Partial<Config>>{
square: '1 / 1',
video: '16 / 9',
},
boxShadow: {
card: '0 2px 4px 0 rgba(0, 0, 0, .05)',
sidebar: 'inset -2px 0 2px 0 rgba(0, 0, 0, .05)',
}
},
},
}

View File

@@ -3061,7 +3061,7 @@ chalk@^5.3.0:
resolved "https://registry.npmmirror.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
chokidar@^3.5.1, chokidar@^3.5.3, chokidar@^3.6.0:
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1, chokidar@^3.5.3, chokidar@^3.6.0:
version "3.6.0"
resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -4517,6 +4517,11 @@ image-meta@^0.2.0:
resolved "https://registry.npmmirror.com/image-meta/-/image-meta-0.2.0.tgz#ea28d05d52f5ad35f75b14f46278a44d626f48bc"
integrity sha512-ZBGjl0ZMEMeOC3Ns0wUF/5UdUmr3qQhBSCniT0LxOgGGIRHiNFOkMtIHB7EOznRU47V2AxPgiVP+s+0/UCU0Hg==
immutable@^4.0.0:
version "4.3.5"
resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0"
integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -6808,6 +6813,15 @@ safe-regex-test@^1.0.3:
resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@^1.72.0:
version "1.72.0"
resolved "https://registry.npmmirror.com/sass/-/sass-1.72.0.tgz#5b9978943fcfb32b25a6a5acb102fc9dabbbf41c"
integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
scule@^1.0.0, scule@^1.1.0, scule@^1.1.1, scule@^1.2.0, scule@^1.3.0:
version "1.3.0"
resolved "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3"
@@ -7021,6 +7035,11 @@ socks@^2.7.1:
ip-address "^9.0.5"
smart-buffer "^4.2.0"
"source-map-js@>=0.6.2 <2.0.0":
version "1.2.0"
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"