feat(assistant): assistant

This commit is contained in:
dataeaseShu
2025-07-23 16:19:15 +08:00
parent 80c45c4d5f
commit 4ee583030a
16 changed files with 621 additions and 282 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.76027 7.79265L1.11652 3.24995C1.01715 3.1262 1.01715 2.92537 1.11652 2.80142C1.16423 2.74183 1.22902 2.7085 1.29631 2.7085H8.70444C8.84485 2.7085 8.95882 2.85058 8.95882 3.02578C8.95882 3.10995 8.93194 3.19037 8.88423 3.24995L5.24048 7.79287C5.10798 7.95808 4.89298 7.95808 4.76048 7.79287L4.76027 7.79265Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 440 B

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 1.25C14.8325 1.25 18.75 5.16751 18.75 10C18.75 14.8325 14.8325 18.75 10 18.75H2.08333C1.6231 18.75 1.25 18.3769 1.25 17.9167V10C1.25 5.16751 5.16751 1.25 10 1.25ZM2.91667 17.0833H10C13.912 17.0833 17.0833 13.912 17.0833 9.99998C17.0833 6.08796 13.912 2.91665 10 2.91665C6.08798 2.91665 2.91667 6.08796 2.91667 9.99998V17.0833Z" fill="#1F2329"/>
<path d="M9.58333 6.25C9.35321 6.25 9.16667 6.43655 9.16667 6.66667V9.16667H6.66667C6.43655 9.16667 6.25 9.35321 6.25 9.58333V10.4167C6.25 10.6468 6.43655 10.8333 6.66667 10.8333H9.16667V13.3333C9.16667 13.5635 9.35321 13.75 9.58333 13.75H10.4167C10.6468 13.75 10.8333 13.5635 10.8333 13.3333V10.8333H13.3333C13.5635 10.8333 13.75 10.6468 13.75 10.4167V9.58333C13.75 9.35321 13.5635 9.16667 13.3333 9.16667H10.8333V6.66667C10.8333 6.43655 10.6468 6.25 10.4167 6.25H9.58333Z" fill="#1F2329"/>
</svg>

After

Width:  |  Height:  |  Size: 953 B

@@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1.125C13.3492 1.125 16.875 4.65076 16.875 9C16.875 13.3492 13.3492 16.875 9 16.875H1.875C1.46079 16.875 1.125 16.5392 1.125 16.125V9C1.125 4.65076 4.65076 1.125 9 1.125ZM2.625 15.375H9C12.5208 15.375 15.375 12.5208 15.375 8.99998C15.375 5.47917 12.5208 2.62498 9 2.62498C5.47918 2.62498 2.625 5.47917 2.625 8.99998V15.375Z" fill="#1CBA90"/>
<path d="M8.625 5.625C8.41789 5.625 8.25 5.79289 8.25 6V8.25H6C5.79289 8.25 5.625 8.41789 5.625 8.625V9.375C5.625 9.58211 5.79289 9.75 6 9.75H8.25V12C8.25 12.2071 8.41789 12.375 8.625 12.375H9.375C9.58211 12.375 9.75 12.2071 9.75 12V9.75H12C12.2071 9.75 12.375 9.58211 12.375 9.375V8.625C12.375 8.41789 12.2071 8.25 12 8.25H9.75V6C9.75 5.79289 9.58211 5.625 9.375 5.625H8.625Z" fill="#1CBA90"/>
</svg>

After

Width:  |  Height:  |  Size: 851 B

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.35433 11.2487H7.625L14.379 4.49447C14.6111 4.26241 14.6116 3.88631 14.3801 3.65363L12.3345 1.59723C12.102 1.36473 11.7251 1.36473 11.4926 1.59723L11.0727 2.01709L11.0743 2.01875L4.75898 8.38268V10.6534C4.75898 10.9822 5.02553 11.2487 5.35433 11.2487ZM11.8947 2.84338L13.1003 4.0554L12.0801 5.07666L10.8806 3.87724L11.8947 2.84338ZM11.2385 5.91901L7.12283 10.0386H7.12102L5.96904 8.88486L10.0468 4.72734L11.2385 5.91901Z" fill="#646A73"/>
<path d="M8.76346 2.31588H2.44373C2.07839 2.31588 1.78223 2.61204 1.78223 2.97737V13.5614C1.78223 13.9267 2.07839 14.2229 2.44373 14.2229H13.0277C13.393 14.2229 13.6892 13.9267 13.6892 13.5614V7.16128L12.3579 8.5006V12.898H3.11347V3.64068H7.46273L8.76346 2.31588Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 835 B

@@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.4997 15.8333V17.5H2.49967V15.8333H17.4997ZM17.4997 4.16667H2.49967V17.5L2.32959 17.491C1.54496 17.4115 0.921507 16.788 0.84196 16.0034L0.833008 15.8333V4.16667C0.833008 3.24619 1.5792 2.5 2.49967 2.5H17.4997C18.4201 2.5 19.1663 3.24619 19.1663 4.16667V15.8333C19.1663 16.7538 18.4201 17.5 17.4997 17.5V4.16667Z" fill="#1F2329"/>
<path d="M6.24967 5.83333H3.74967C3.63917 5.83333 3.53319 5.87723 3.45505 5.95537C3.37691 6.03351 3.33301 6.13949 3.33301 6.25V7.08333C3.33301 7.19384 3.37691 7.29982 3.45505 7.37796C3.53319 7.4561 3.63917 7.5 3.74967 7.5H6.24967C6.30439 7.5 6.35857 7.48922 6.40913 7.46828C6.45968 7.44734 6.50561 7.41665 6.5443 7.37796C6.58299 7.33927 6.61369 7.29334 6.63462 7.24278C6.65556 7.19223 6.66634 7.13805 6.66634 7.08333V6.25C6.66634 6.19528 6.65556 6.1411 6.63462 6.09055C6.61369 6.04 6.58299 5.99406 6.5443 5.95537C6.50561 5.91668 6.45968 5.88599 6.40913 5.86505C6.35857 5.84411 6.30439 5.83333 6.24967 5.83333Z" fill="#1F2329"/>
<path d="M6.24967 8.33333H3.74967C3.63917 8.33333 3.53319 8.37723 3.45505 8.45537C3.37691 8.53351 3.33301 8.63949 3.33301 8.75V9.58333C3.33301 9.69384 3.37691 9.79982 3.45505 9.87796C3.53319 9.9561 3.63917 10 3.74967 10H6.24967C6.30439 10 6.35857 9.98922 6.40913 9.96828C6.45968 9.94734 6.50561 9.91665 6.5443 9.87796C6.58299 9.83927 6.61369 9.79334 6.63462 9.74278C6.65556 9.69223 6.66634 9.63805 6.66634 9.58333V8.75C6.66634 8.69528 6.65556 8.6411 6.63462 8.59055C6.61369 8.54 6.58299 8.49406 6.5443 8.45537C6.50561 8.41668 6.45968 8.38599 6.40913 8.36505C6.35857 8.34411 6.30439 8.33333 6.24967 8.33333Z" fill="#1F2329"/>
<path d="M7.49967 4.16667H9.16634V15.8333H7.49967V4.16667Z" fill="#1F2329"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+10 -2
View File
@@ -395,7 +395,14 @@
"filter_like": "Contains",
"filter_not_like": "Does not contain",
"filter_in": "Belongs to",
"filter_not_in": "Does not belong to"
"filter_not_in": "Does not belong to",
"enter_a_question": "Please enter a question",
"new_conversation": "New Conversation",
"send": "Send",
"stop_replying": "Stop replying",
"i_am_sqlbot": "Hello, I am SQLBot",
"predict_data_etc": "I can query data, generate charts, detect data anomalies, predict data, etc.",
"intelligent_data_query": "Hurry up and start intelligent data query~"
},
"embedded": {
"embedded_management": "Embedded management",
@@ -441,7 +448,8 @@
"edit_interface_credentials": "Edit interface credentials",
"duplicate_name": "Duplicate name",
"interface_credentials": "Interface credentials",
"no_credentials_yet": "No credentials yet"
"no_credentials_yet": "No credentials yet",
"intelligent_customer_service": "SQLBot Intelligent Customer Service"
},
"chat": {
"type": "Chart Type",
+9 -1
View File
@@ -453,7 +453,15 @@
"edit_interface_credentials": "编辑接口凭证",
"duplicate_name": "重复名称",
"interface_credentials": "接口凭证",
"no_credentials_yet": "暂无凭证"
"no_credentials_yet": "暂无凭证",
"intelligent_customer_service": "SQLBot 智能客服",
"enter_a_question": "请输入问题",
"new_conversation": "新建对话",
"send": "发送",
"stop_replying": "停止回答",
"i_am_sqlbot": "你好,我是 SQLBot",
"predict_data_etc": "我可以查询数据、生成图表、检测数据异常、预测数据等",
"intelligent_data_query": "赶快开启智能问数吧~"
},
"chat": {
"type": "图表类型",
+130 -26
View File
@@ -1,7 +1,10 @@
<script setup lang="ts">
import { Delete, EditPen, MoreFilled } from '@element-plus/icons-vue'
import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg'
import icon_expand_down_filled from '@/assets/embedded/icon_expand-down_filled.svg'
import rename from '@/assets/svg/icon_rename_outlined.svg'
import delIcon from '@/assets/svg/icon_delete.svg'
import { type Chat, chatApi } from '@/api/chat.ts'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import dayjs from 'dayjs'
import { getDate } from '@/utils/utils.ts'
import { groupBy } from 'lodash-es'
@@ -48,6 +51,13 @@ const computedChatGroup = computed(() => {
return groupBy(props.chatList, groupByDate)
})
const expandMap = ref({
[t('qa.today')]: true,
[t('qa.week')]: true,
[t('qa.earlier')]: true,
[t('qa.no_time')]: true,
})
const computedChatList = computed(() => {
const _list = []
if (computedChatGroup.value[t('qa.today')]) {
@@ -169,27 +179,44 @@ function handleCommand(command: string | number | object, chat: Chat) {
<el-scrollbar ref="chatListRef">
<div class="chat-list-inner">
<div v-for="group in computedChatList" :key="group.key" class="group">
<div class="group-title">{{ group.key }}</div>
<div
class="group-title"
style="cursor: pointer"
@click="expandMap[group.key] = !expandMap[group.key]"
>
<el-icon :class="!expandMap[group.key] && 'expand'" style="margin-right: 8px" size="10">
<icon_expand_down_filled></icon_expand_down_filled>
</el-icon>
{{ group.key }}
</div>
<template v-for="chat in group.list" :key="chat.id">
<div
class="chat-list-item"
:class="{ active: currentChatId === chat.id }"
:class="{ active: currentChatId === chat.id, hide: !expandMap[group.key] }"
@click="onClickHistory(chat)"
>
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
<el-button class="icon-more" link type="primary" @click.stop>
<el-dropdown trigger="click" @command="(cmd: any) => handleCommand(cmd, chat)">
<el-icon>
<MoreFilled />
<el-popover :teleported="false" popper-class="popover-card" placement="bottom">
<template #reference>
<el-icon class="more" size="16" style="margin-left: auto" @click.stop>
<icon_more_outlined></icon_more_outlined>
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="EditPen" command="rename">Rename</el-dropdown-item>
<el-dropdown-item :icon="Delete" command="delete">Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-button>
</template>
<div class="content">
<div class="item" @click.stop="handleCommand('rename', chat)">
<el-icon size="16">
<rename></rename>
</el-icon>
{{ $t('dashboard.rename') }}
</div>
<div class="item" @click.stop="handleCommand('delete', chat)">
<el-icon size="16">
<delIcon></delIcon>
</el-icon>
{{ $t('dashboard.delete') }}
</div>
</div>
</el-popover>
</div>
</template>
</div>
@@ -223,6 +250,10 @@ function handleCommand(command: string | number | object, chat: Chat) {
font-size: 12px;
margin-bottom: 4px;
.expand {
transform: rotate(-90deg);
}
}
}
@@ -251,23 +282,39 @@ function handleCommand(command: string | number | object, chat: Chat) {
white-space: nowrap;
}
.icon-more {
margin-left: auto;
.more {
display: none;
&.ed-icon {
position: relative;
cursor: pointer;
margin-top: 4px;
min-width: unset;
&::after {
content: '';
background-color: #1f23291a;
position: absolute;
border-radius: 6px;
width: 24px;
height: 24px;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
display: none;
}
&:hover {
&::after {
display: block;
}
}
}
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
.icon-more {
display: inline-flex;
color: rgba(100, 106, 115, 1);
&:hover {
background-color: rgba(31, 35, 41, 0.1);
}
.more {
display: block;
}
}
@@ -275,6 +322,63 @@ function handleCommand(command: string | number | object, chat: Chat) {
background-color: rgba(255, 255, 255, 1);
font-weight: 500;
}
&.hide {
display: none;
}
}
}
</style>
<style lang="less">
.popover-card.popover-card.popover-card {
box-shadow: 0px 4px 8px 0px #1f23291a;
border-radius: 4px;
border: 1px solid #dee0e3;
width: 120px !important;
min-width: 120px !important;
padding: 0;
.content {
position: relative;
&::after {
position: absolute;
content: '';
top: 40px;
left: 0;
width: 100%;
height: 1px;
background: #dee0e3;
}
.item {
position: relative;
padding-left: 12px;
height: 40px;
display: flex;
align-items: center;
cursor: pointer;
.ed-icon {
margin-right: 8px;
color: #646a73;
}
&:hover {
&::after {
display: block;
}
}
&::after {
content: '';
width: 112px;
height: 32px;
border-radius: 4px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #1f23291a;
display: none;
}
}
}
}
</style>
+186 -252
View File
@@ -1,264 +1,198 @@
<template>
<div class="sqlbot-assistant-container">
<div class="head">
<div class="flex align-center">
<div class="head-img-div">
<img :src="AssistantGif" />
</div>
<h4>SQLBot 小助手</h4>
</div>
</div>
<chat-component v-if="!loading" ref="chatRef" class="sqlbot-chat-container" />
</div>
<div class="sqlbot-top-btn">
<el-icon style="cursor: pointer" @click="createChat">
<icon_new_chat_outlined />
</el-icon>
<el-icon style="cursor: pointer" @click="openHistory">
<history></history>
</el-icon>
</div>
<div class="sqlbot-history-container">
<el-drawer
v-model="drawer"
:append-to-body="false"
class="sqlbot-history-drawer"
modal-class="sqlbot-history-modal"
title=""
direction="ttb"
:with-header="false"
>
<div class="sqlbot-history-title">
<span>历史记录</span>
</div>
<div class="sqlbot-history-content">
<div v-if="!chatList.length" class="history-empty">
<span>暂无历史记录</span>
</div>
<div v-else class="sqlbot-chat-list-inner">
<el-scrollbar max-height="350px" class="sqlbot-chat-scroller">
<template v-for="chat in chatList" :key="chat.id">
<div
class="chat-list-item"
:class="{ 'is-active-chat': currentId === chat.id }"
@click="onClickHistory(chat)"
>
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
<!-- <div class="history-operate">
<el-icon @click="EditPen(chat)"><IconOpeEdit /></el-icon>
<el-icon @click="Delete(chat)"><IconOpeDelete /></el-icon>
</div> -->
</div>
</template>
</el-scrollbar>
</div>
</div>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
import ChatComponent from '@/views/chat/index.vue'
import AssistantGif from '@/assets/img/assistant.gif'
import history from '@/assets/svg/chart/history.svg'
import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg'
/* import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg' */
import { useRoute } from 'vue-router'
import { assistantApi } from '@/api/assistant'
import { useAssistantStore } from '@/stores/assistant'
<script lang="ts" setup>
import { ref } from 'vue'
import icon_sidebar_outlined from '@/assets/embedded/icon_sidebar_outlined.svg'
import icon_new_chat_outlined from '@/assets/embedded/icon_new-chat_outlined.svg'
import LOGO from '@/assets/embedded/LOGO.png'
import answer from '@/assets/embedded/answer.png'
import disable_answer from '@/assets/embedded/disable.png'
import loading_answer from '@/assets/embedded/loading.png'
import send_answer from '@/assets/embedded/send.png'
const assistantStore = useAssistantStore()
const route = useRoute()
const chatRef = ref()
const chatList = ref<Array<any>>([])
const drawer = ref(false)
const currentId = ref()
const createChat = () => {
chatRef.value?.createNewChat()
}
const openHistory = () => {
chatList.value = chatRef.value?.getHistoryList()
currentId.value = chatRef.value?.getCurrentChatId()
drawer.value = true
}
const onClickHistory = (chat: any) => {
chatRef.value?.toAssistantHistory(chat)
}
/* const EditPen = (chat: any) => {
chatRef.value?.onChatRenamed(chat)
}
const Delete = (chat: any) => {
chatRef.value?.onChatDeleted(chat.id)
} */
const validator = ref({
id: '',
valid: false,
id_match: false,
token: '',
})
const loading = ref(true)
const eventName = 'sqlbot_assistant_event'
const communicationCb = async (event: any) => {
if (event.data?.eventName === eventName) {
if (event.data?.messageId !== route.query.id) {
return
}
if (event.data?.busi == 'certificate') {
const certificate = event.data['certificate']
assistantStore.setType(1)
assistantStore.setCertificate(certificate)
// store certificate to pinia
}
}
}
onBeforeMount(async () => {
const assistantId = route.query.id
const online = route.query.online
console.log(online)
const now = Date.now()
assistantStore.setFlag(now)
const param = {
id: assistantId,
virtual: assistantStore.getFlag,
online,
}
validator.value = await assistantApi.validate(param)
assistantStore.setToken(validator.value.token)
assistantStore.setAssistant(true)
loading.value = false
window.addEventListener('message', communicationCb)
const readyData = {
eventName: 'sqlbot_assistant_event',
busi: 'ready',
ready: true,
messageId: assistantId,
}
window.parent.postMessage(readyData, '*')
})
onBeforeUnmount(() => {
window.removeEventListener('message', communicationCb)
})
const textareaVal = ref('')
</script>
<template>
<div class="assistant-window">
<div class="header">
<el-icon size="20">
<icon_sidebar_outlined></icon_sidebar_outlined>
</el-icon>
<img :src="LOGO" class="logo" width="30px" height="30px" alt="" />
<span class="tite">{{ $t('embedded.intelligent_customer_service') }}</span>
<el-tooltip effect="dark" :content="$t('embedded.new_conversation')" placement="top">
<el-icon class="new-chat" size="20">
<icon_new_chat_outlined></icon_new_chat_outlined>
</el-icon>
</el-tooltip>
</div>
<div class="center">
<img :src="LOGO" class="logo" width="30px" height="30px" alt="" />
<div class="i-am">{{ $t('embedded.i_am_sqlbot') }}</div>
<div class="i-can">{{ $t('embedded.predict_data_etc') }}</div>
<div class="data-query">{{ $t('embedded.intelligent_data_query') }}</div>
</div>
<div class="content">
<div class="textarea-send">
<el-input
v-model="textareaVal"
:autosize="{ minRows: 1, maxRows: 8.5848 }"
type="textarea"
:placeholder="$t('embedded.enter_a_question')"
/>
<el-tooltip :offset="10" effect="dark" :content="$t('embedded.send')" placement="top">
<img :src="disable_answer" width="32px" height="32px" alt="" />
</el-tooltip>
<el-tooltip
:offset="10"
effect="dark"
:content="$t('embedded.stop_replying')"
placement="top"
>
<img :src="answer" width="32px" height="32px" alt="" />
</el-tooltip>
<img :src="loading_answer" width="32px" height="32px" alt="" />
<img :src="send_answer" width="32px" height="32px" alt="" />
</div>
</div>
<div class="drawer-assistant"></div>
</div>
</template>
<style lang="less" scoped>
.sqlbot-assistant-container {
height: 100%;
width: 100%;
color: rgb(31, 35, 41);
.flex {
.assistant-window {
width: 460px;
height: 640px;
border-radius: 12px;
border: 1px solid #dee0e3;
box-shadow: 0px 6px 24px 0px #1f232914;
background-color: #fff;
position: relative;
.header {
background: #1cba9014;
height: 56px;
padding: 0 16px;
display: flex;
align-items: center;
position: relative;
.logo {
margin: 0 8px 0 16px;
}
.title {
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
.ed-icon {
position: relative;
cursor: pointer;
&::after {
content: '';
background-color: #1f23291a;
position: absolute;
border-radius: 6px;
width: 28px;
height: 28px;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
display: none;
}
&:hover {
&::after {
display: block;
}
}
}
.new-chat {
position: absolute;
right: 88px;
top: 17px;
}
}
.head {
background: linear-gradient(90deg, #ebf1ff 24.34%, #e5fbf8 56.18%, #f2ebfe 90.18%);
position: fixed;
.center {
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
flex-direction: column;
.i-am {
font-weight: 600;
font-size: 24px;
line-height: 32px;
margin: 16px 0;
}
.i-can {
margin-bottom: 4px;
}
.data-query,
.i-can {
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #646a73;
}
}
.content {
width: calc(100% - 32px);
.textarea-send {
width: 100%;
position: relative;
.ed-textarea {
width: 100%;
}
:deep(.ed-textarea__inner) {
padding: 12px 12px 52px 12px;
font-weight: 400;
font-size: 16px;
line-height: 24px;
border-radius: 16px;
&::placeholder {
color: #8f959e;
}
}
img {
cursor: pointer;
position: absolute;
right: 12px;
bottom: 12px;
}
}
position: absolute;
bottom: 16px;
left: 16px;
}
.drawer-assistant {
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 100;
height: 56px;
line-height: 56px;
box-sizing: border-box;
border-bottom: 1px solid #dee0e3;
.align-center {
align-items: center;
.head-img-div {
display: flex;
margin-left: 24px;
img {
width: 32px;
height: 32px;
}
}
}
}
.sqlbot-chat-container {
padding-top: 56px;
:deep(.ed-aside) {
display: none;
}
}
}
.sqlbot-top-btn {
right: 85px;
z-index: 2009;
position: fixed;
top: 16px;
display: flex;
column-gap: 16px;
// right: 16px;
font-size: 22px;
i:first-child {
color: var(--primary-color);
}
}
.sqlbot-history-container {
width: 100%;
:deep(.sqlbot-history-modal) {
top: 56px !important;
}
:deep(.sqlbot-history-title) {
border-bottom: 1px solid #dee0e3;
padding: 16px 24px;
font-size: 14px;
font-weight: 500;
color: #1f2329;
}
:deep(.ed-drawer__body) {
padding: 0 !important;
}
:deep(.sqlbot-history-drawer) {
height: fit-content !important;
.sqlbot-history-content {
padding: 16px;
.history-empty {
height: 60px;
color: #8f959e !important;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.is-active-chat {
background-color: #ebf1ff;
color: #3370ff;
font-weight: 500;
}
.chat-list-item {
display: flex;
align-items: center;
padding: 10px 8px;
font-size: 14px;
color: #1f2329;
justify-content: space-between;
.history-operate {
display: none;
}
&:hover {
cursor: pointer;
border-radius: 4px;
background: rgba(31, 35, 41, 0.1);
.history-operate {
padding-right: 16px;
display: flex;
column-gap: 16px;
align-items: center;
font-size: 18px;
i {
color: #8f959e;
&:hover {
color: #8f959e99;
}
}
}
}
}
}
width: 50%;
background: #f5f6f7;
box-shadow: 0px 6px 24px 0px #1f232914;
padding: 16px;
border-right: 1px solid #dee0e3;
display: none;
}
}
</style>
+264
View File
@@ -0,0 +1,264 @@
<template>
<div class="sqlbot-assistant-container">
<div class="head">
<div class="flex align-center">
<div class="head-img-div">
<img :src="AssistantGif" />
</div>
<h4>SQLBot 小助手</h4>
</div>
</div>
<chat-component v-if="!loading" ref="chatRef" class="sqlbot-chat-container" />
</div>
<div class="sqlbot-top-btn">
<el-icon style="cursor: pointer" @click="createChat">
<icon_new_chat_outlined />
</el-icon>
<el-icon style="cursor: pointer" @click="openHistory">
<history></history>
</el-icon>
</div>
<div class="sqlbot-history-container">
<el-drawer
v-model="drawer"
:append-to-body="false"
class="sqlbot-history-drawer"
modal-class="sqlbot-history-modal"
title=""
direction="ttb"
:with-header="false"
>
<div class="sqlbot-history-title">
<span>历史记录</span>
</div>
<div class="sqlbot-history-content">
<div v-if="!chatList.length" class="history-empty">
<span>暂无历史记录</span>
</div>
<div v-else class="sqlbot-chat-list-inner">
<el-scrollbar max-height="350px" class="sqlbot-chat-scroller">
<template v-for="chat in chatList" :key="chat.id">
<div
class="chat-list-item"
:class="{ 'is-active-chat': currentId === chat.id }"
@click="onClickHistory(chat)"
>
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
<!-- <div class="history-operate">
<el-icon @click="EditPen(chat)"><IconOpeEdit /></el-icon>
<el-icon @click="Delete(chat)"><IconOpeDelete /></el-icon>
</div> -->
</div>
</template>
</el-scrollbar>
</div>
</div>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
import ChatComponent from '@/views/chat/index.vue'
import AssistantGif from '@/assets/img/assistant.gif'
import history from '@/assets/svg/chart/history.svg'
import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg'
/* import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg' */
import { useRoute } from 'vue-router'
import { assistantApi } from '@/api/assistant'
import { useAssistantStore } from '@/stores/assistant'
const assistantStore = useAssistantStore()
const route = useRoute()
const chatRef = ref()
const chatList = ref<Array<any>>([])
const drawer = ref(false)
const currentId = ref()
const createChat = () => {
chatRef.value?.createNewChat()
}
const openHistory = () => {
chatList.value = chatRef.value?.getHistoryList()
currentId.value = chatRef.value?.getCurrentChatId()
drawer.value = true
}
const onClickHistory = (chat: any) => {
chatRef.value?.toAssistantHistory(chat)
}
/* const EditPen = (chat: any) => {
chatRef.value?.onChatRenamed(chat)
}
const Delete = (chat: any) => {
chatRef.value?.onChatDeleted(chat.id)
} */
const validator = ref({
id: '',
valid: false,
id_match: false,
token: '',
})
const loading = ref(true)
const eventName = 'sqlbot_assistant_event'
const communicationCb = async (event: any) => {
if (event.data?.eventName === eventName) {
if (event.data?.messageId !== route.query.id) {
return
}
if (event.data?.busi == 'certificate') {
const certificate = event.data['certificate']
assistantStore.setType(1)
assistantStore.setCertificate(certificate)
// store certificate to pinia
}
}
}
onBeforeMount(async () => {
const assistantId = route.query.id
const online = route.query.online
console.log(online)
const now = Date.now()
assistantStore.setFlag(now)
const param = {
id: assistantId,
virtual: assistantStore.getFlag,
online,
}
validator.value = await assistantApi.validate(param)
assistantStore.setToken(validator.value.token)
assistantStore.setAssistant(true)
loading.value = false
window.addEventListener('message', communicationCb)
const readyData = {
eventName: 'sqlbot_assistant_event',
busi: 'ready',
ready: true,
messageId: assistantId,
}
window.parent.postMessage(readyData, '*')
})
onBeforeUnmount(() => {
window.removeEventListener('message', communicationCb)
})
</script>
<style lang="less" scoped>
.sqlbot-assistant-container {
height: 100%;
width: 100%;
color: rgb(31, 35, 41);
.flex {
display: flex;
}
.head {
background: linear-gradient(90deg, #ebf1ff 24.34%, #e5fbf8 56.18%, #f2ebfe 90.18%);
position: fixed;
width: 100%;
left: 0;
top: 0;
z-index: 100;
height: 56px;
line-height: 56px;
box-sizing: border-box;
border-bottom: 1px solid #dee0e3;
.align-center {
align-items: center;
.head-img-div {
display: flex;
margin-left: 24px;
img {
width: 32px;
height: 32px;
}
}
}
}
.sqlbot-chat-container {
padding-top: 56px;
:deep(.ed-aside) {
display: none;
}
}
}
.sqlbot-top-btn {
right: 85px;
z-index: 2009;
position: fixed;
top: 16px;
display: flex;
column-gap: 16px;
// right: 16px;
font-size: 22px;
i:first-child {
color: var(--primary-color);
}
}
.sqlbot-history-container {
width: 100%;
:deep(.sqlbot-history-modal) {
top: 56px !important;
}
:deep(.sqlbot-history-title) {
border-bottom: 1px solid #dee0e3;
padding: 16px 24px;
font-size: 14px;
font-weight: 500;
color: #1f2329;
}
:deep(.ed-drawer__body) {
padding: 0 !important;
}
:deep(.sqlbot-history-drawer) {
height: fit-content !important;
.sqlbot-history-content {
padding: 16px;
.history-empty {
height: 60px;
color: #8f959e !important;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.is-active-chat {
background-color: #ebf1ff;
color: #3370ff;
font-weight: 500;
}
.chat-list-item {
display: flex;
align-items: center;
padding: 10px 8px;
font-size: 14px;
color: #1f2329;
justify-content: space-between;
.history-operate {
display: none;
}
&:hover {
cursor: pointer;
border-radius: 4px;
background: rgba(31, 35, 41, 0.1);
.history-operate {
padding-right: 16px;
display: flex;
column-gap: 16px;
align-items: center;
font-size: 18px;
i {
color: #8f959e;
&:hover {
color: #8f959e99;
}
}
}
}
}
}
}
}
</style>
@@ -291,10 +291,10 @@ defineExpose({
</div>
<div v-if="advancedSetting.length > 5 && configExpand" class="params-table_pagination">
<el-pagination
@current-change="handelCurrentChange"
:default-page-size="5"
layout="prev, pager, next"
:total="advancedSetting.length"
@current-change="handelCurrentChange"
/>
</div>
</div>