IntelliClass_FE/components/ai/Conversation.vue
HoshinoSuzumi 20471bfbe3
Some checks failed
CI / lint (push) Failing after 59s
CI / test (push) Failing after 47s
feat: 完成 AIGC Conversation 组件
2025-04-26 21:54:10 +08:00

223 lines
6.5 KiB
Vue

<!-- eslint-disable @typescript-eslint/no-explicit-any -->
<script lang="ts" setup>
import type { FormContext } from 'vee-validate'
import type { AnyZodObject } from 'zod'
import dayjs from 'dayjs'
import type { LLMConversation } from '.'
const props = defineProps<{
formSchema: AnyZodObject
form: FormContext<any>
formFieldConfig?: {
[key: string]: {
component: string
props?: Record<string, any>
[key: string]: any
}
}
conversations?: LLMConversation[]
activeConversationId?: string | null
disableUserInput?: boolean
}>()
const messages = computed(() => {
const { conversations, activeConversationId } = props
if (conversations && activeConversationId) {
const conversation = conversations.find(
(m) => m.id === activeConversationId,
)
return conversation ? conversation.messages : []
}
return []
})
const emit = defineEmits<{
(e: 'submit', values: FormContext<any>['values']): void
(e: 'update:conversationId', conversationId: string | null): void
(e: 'deleteConversation', conversationId: string): void
}>()
const onDeleteConversation = (conversationId: string) => {
emit('deleteConversation', conversationId)
if (conversationId === props.activeConversationId) {
emit('update:conversationId', null)
}
}
</script>
<template>
<div class="h-full flex flex-col gap-4">
<div class="flex justify-between items-start">
<div>
<Button
v-if="activeConversationId"
variant="link"
size="sm"
@click="$emit('update:conversationId', null)"
>
<Icon name="tabler:arrow-back" />
返回
</Button>
</div>
<div>
<!-- Histories -->
<Popover>
<PopoverTrigger>
<Button
variant="outline"
size="sm"
>
<Icon name="tabler:history" />
历史记录
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
class="p-0"
>
<ScrollArea
v-if="conversations?.length"
class="flex flex-col gap-2 p-3 h-[320px]"
>
<div
v-for="(conversation, i) in props.conversations"
:key="i"
class="flex items-start gap-2 p-2 rounded-md cursor-pointer relative"
:class="`${conversation.id === activeConversationId ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'}`"
@click="$emit('update:conversationId', conversation.id)"
>
<Button
variant="ghost"
size="icon"
class="absolute top-1.5 right-2"
@click.stop="onDeleteConversation(conversation.id)"
>
<Icon name="tabler:trash" />
</Button>
<Icon
name="tabler:history"
class="mt-0.5"
/>
<div class="flex-1 text-sm font-medium flex flex-col">
<span class="w-2/3 text-ellipsis line-clamp-1">
{{ conversation.title || '历史对话' }}
</span>
<span
class="text-xs"
:class="`${conversation.id === activeConversationId ? 'text-primary-foreground/60' : 'text-muted-foreground'}`"
>
{{
dayjs((conversation.created_at || 0) * 1000).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</span>
</div>
</div>
<p class="text-xs text-muted-foreground/60 font-medium text-center pt-2">
到底了
</p>
</ScrollArea>
<div
v-else
class="flex flex-col items-center justify-center gap-2 h-[320px]"
>
<Icon
name="tabler:history"
class="text-3xl text-muted-foreground"
/>
<p class="text-sm text-muted-foreground">暂无历史记录</p>
</div>
</PopoverContent>
</Popover>
</div>
</div>
<hr />
<!-- 消息区域调整 -->
<div
v-if="activeConversationId"
class="flex flex-col flex-1 gap-4 max-h-[calc(100vh-280px)]"
>
<div
class="flex-1 flex flex-col gap-6 overflow-y-auto scroll-smooth px-2 pb-4"
>
<div
v-for="(message, i) in messages"
:key="i"
class="w-full flex"
:class="`${message.role == 'user' ? 'justify-end' : 'justify-start'}`"
>
<div
class="w-fit px-4 py-3 rounded-lg bg-white dark:bg-gray-800 shadow max-w-prose border"
:class="`${message.role == 'user' ? 'rounded-br-none' : 'rounded-bl-none'}`"
>
<MarkdownRenderer
v-if="!!message.content"
:source="message.content"
/>
<div
v-else
class="flex items-center gap-2 text-foreground/60 text-sm font-medium"
>
<Icon
name="svg-spinners:270-ring-with-bg"
class="text-lg"
/>
思考中
</div>
</div>
</div>
</div>
<div
v-if="!disableUserInput"
class="relative"
>
<Textarea
class="w-full h-12 pr-24 shadow-lg"
placeholder="请告诉我额外的补充需求,我会尽量满足您的要求"
/>
<div class="absolute right-2 bottom-2">
<Button
type="submit"
size="sm"
>
<Icon name="tabler:send" />
发送
</Button>
</div>
</div>
</div>
<!-- 表单区域不变 -->
<div
v-else
class="rounded-lg p-6 bg-primary/5"
>
<div class="flex flex-col gap-4">
<AutoForm
v-if="formSchema && form"
:schema="formSchema"
:form="form"
:field-config="formFieldConfig"
class="space-y-2"
@submit="(values) => $emit('submit', values)"
>
<div class="w-full flex justify-center gap-2 pt-4">
<Button
type="submit"
size="lg"
>
<Icon name="mage:stars-c-fill" />
一键生成
</Button>
</div>
</AutoForm>
</div>
</div>
</div>
</template>
<style scoped></style>