ui: chat sidebar ui
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
68
components/aigc/chat/ChatItem.vue
Normal file
68
components/aigc/chat/ChatItem.vue
Normal 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
19
components/aigc/chat/index.d.ts
vendored
Normal 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
|
||||
}
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
76
pages/aigc/chat/index.vue
Normal 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>
|
||||
@@ -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="负面提示词">
|
||||
|
||||
@@ -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)',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
21
yarn.lock
21
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user