feat(系统设置): DataEase Copilot需求

This commit is contained in:
dataeaseShu 2024-07-09 16:39:52 +08:00
parent 97716ed8ac
commit e33217571d
8 changed files with 426 additions and 84 deletions

View File

@ -310,14 +310,14 @@ export const copilotChat = async (data): Promise<IResponse> => {
})
}
export const getListCopilot = async (data): Promise<IResponse> => {
return request.post({ url: '/copilot/getList/' + data }).then(res => {
export const getListCopilot = async (): Promise<IResponse> => {
return request.post({ url: '/copilot/getList' }).then(res => {
return res?.data
})
}
export const clearAllCopilot = async (data): Promise<IResponse> => {
return request.post({ url: '/copilot/clearAll/' + data }).then(res => {
export const clearAllCopilot = async (): Promise<IResponse> => {
return request.post({ url: '/copilot/clearAll' }).then(res => {
return res?.data
})
}

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.66668 17.3333V11.697C5.66668 11.312 5.98648 11 6.38097 11H9.33335V6.03508C9.33335 5.64751 9.67446 5.33333 10.0953 5.33333H13.9048C14.3256 5.33333 14.6667 5.64751 14.6667 6.03508V9.33333H17.6191C18.0136 9.33333 18.3333 9.63181 18.3333 10V17.3333H18.9999C19.184 17.3333 19.3332 17.4826 19.3332 17.6667V18.3333C19.3332 18.5174 19.184 18.6667 18.9999 18.6667H4.9999C4.8158 18.6667 4.66656 18.5174 4.66656 18.3333V17.6667C4.66656 17.4826 4.8158 17.3333 4.9999 17.3333H5.66668ZM17 17.3333V10.6667H14.6667V17.3333H17ZM13.3333 17.3333V6.66666H10.6667V17.3333H13.3333ZM9.33335 17.3333V12.3333H7.00001V17.3333H9.33335Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 16.3333V18C18 18.3682 17.7015 18.6667 17.3333 18.6667H6.66667C6.29848 18.6667 6 18.3682 6 18V16.3333C6 16.1492 6.14924 16 6.33333 16H7C7.18409 16 7.33333 16.1492 7.33333 16.3333V17.3333H16.6667V16.3333C16.6667 16.1492 16.8159 16 17 16H17.6667C17.8508 16 18 16.1492 18 16.3333ZM12.6667 13.3571L14.6736 11.3502C14.8038 11.22 15.0149 11.22 15.1451 11.3502L15.6165 11.8216C15.7466 11.9517 15.7466 12.1628 15.6165 12.293L12.3166 15.5928C12.2515 15.6579 12.1662 15.6904 12.0809 15.6904C11.9956 15.6904 11.9103 15.6579 11.8452 15.5928L8.54539 12.293C8.41521 12.1628 8.41521 11.9517 8.54539 11.8216L9.01679 11.3502C9.14697 11.22 9.35802 11.22 9.4882 11.3502L11.3333 13.1953V5.99999C11.3333 5.81589 11.4826 5.66666 11.6667 5.66666H12.3333C12.5174 5.66666 12.6667 5.81589 12.6667 5.99999V13.3571Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 921 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.68833 5.68831C5.91562 5.46102 6.2239 5.33333 6.54534 5.33333H17.4547C17.7761 5.33333 18.0844 5.46102 18.3117 5.68831C18.539 5.91561 18.6667 6.22389 18.6667 6.54533V17.4547C18.6667 17.7761 18.539 18.0844 18.3117 18.3117C18.0844 18.539 17.7761 18.6667 17.4547 18.6667H6.54534C5.87601 18.6667 5.33334 18.124 5.33334 17.4547V6.54533C5.33334 6.22389 5.46104 5.91561 5.68833 5.68831ZM6.66668 10.6667V13.3333H9.33334V10.6667H6.66668ZM6.66668 14.6667V17.3333H9.33334V14.6667H6.66668ZM10.6667 17.3333H13.3333V14.6667H10.6667V17.3333ZM14.6667 17.3333H17.3333V14.6667H14.6667V17.3333ZM17.3333 13.3333V10.6667H14.6667V13.3333H17.3333ZM13.3333 10.6667H10.6667V13.3333H13.3333V10.6667ZM17.3333 6.66666H6.66668V9.33333H17.3333V6.66666Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 894 B

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.713135 21.4739V1.80273C0.713135 1.52659 0.936992 1.30273 1.21313 1.30273H2.26008C2.53623 1.30273 2.76008 1.52659 2.76008 1.80273V20.5523H22.5686C22.8447 20.5523 23.0686 20.7761 23.0686 21.0523V22.0992C23.0686 22.3754 22.8447 22.5992 22.5686 22.5992H1.83907C1.54045 22.5992 1.25407 22.4806 1.04291 22.2694C0.83176 22.0583 0.713135 21.7725 0.713135 21.4739Z" fill="#646A73"/>
<path d="M4.19544 18.3911C3.96872 18.2313 3.91539 17.9175 4.07659 17.6918L8.90155 10.9358C8.97757 10.8235 9.07516 10.7274 9.18865 10.6531C9.30213 10.5788 9.42925 10.5278 9.56262 10.5031C9.69599 10.4784 9.83293 10.4804 9.96551 10.5091C10.0981 10.5377 10.2236 10.5925 10.3349 10.6701L15.7191 14.2624L20.9481 8.16625C21.1291 7.95528 21.4474 7.93239 21.6567 8.11531L22.4497 8.80844C22.6562 8.98892 22.6788 9.30208 22.5003 9.51031L16.6806 16.2998C16.5179 16.4883 16.2919 16.611 16.0452 16.6449C15.7984 16.6788 15.5478 16.6215 15.3402 16.4839L10.0275 12.9315L5.77196 18.8941C5.61221 19.118 5.30171 19.1708 5.07694 19.0124L4.19544 18.3911Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.29008 5.22068L5.29008 5.22068C3.5148 6.99712 2.49805 9.40416 2.49805 11.97C2.49805 17.2342 6.75182 21.5024 12.0004 21.5024C17.249 21.5024 21.5027 17.2342 21.5027 11.97C21.5027 11.6777 21.3866 11.3973 21.1799 11.1906C20.9731 10.9838 20.6928 10.8677 20.4004 10.8677C20.108 10.8677 19.8276 10.9838 19.6209 11.1906C19.4142 11.3973 19.298 11.6777 19.298 11.97C19.298 16.0178 16.0303 19.2977 12.0004 19.2977C7.97048 19.2977 4.70273 16.0178 4.70273 11.97M5.29008 5.22068L4.80039 11.97M5.29008 5.22068C5.39243 5.11826 5.51394 5.037 5.64769 4.98154C5.78145 4.92608 5.92481 4.89751 6.0696 4.89746C6.2144 4.89741 6.35778 4.92588 6.49157 4.98124C6.62536 5.0366 6.74694 5.11777 6.84936 5.22012C6.95178 5.32247 7.03304 5.44398 7.0885 5.57774C7.14396 5.71149 7.17253 5.85485 7.17258 5.99965C7.17263 6.14444 7.14417 6.28782 7.0888 6.42161C7.03344 6.55541 6.95227 6.67698 6.84992 6.7794M5.29008 5.22068L6.919 6.84843M4.70273 11.97H4.80039M4.70273 11.97C4.70273 11.9701 4.70273 11.9702 4.70273 11.9702L4.80039 11.97M4.70273 11.97C4.701 11.0061 4.88979 10.0512 5.25827 9.16045C5.62676 8.26965 6.16768 7.46047 6.84992 6.7794M4.80039 11.97C4.79865 11.0189 4.98493 10.0767 5.34851 9.19778C5.7121 8.31884 6.24582 7.52042 6.919 6.84843M6.84992 6.7794L6.919 6.84843M6.84992 6.7794C6.84995 6.77937 6.84998 6.77935 6.85001 6.77932L6.919 6.84843" fill="#3370FF" stroke="#3370FF" stroke-width="0.195312"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,11 +1,15 @@
<script lang="ts" setup>
import { PropType, computed } from 'vue'
import { PropType, computed, onMounted, shallowRef, ref, nextTick } from 'vue'
import { Column, Line } from '@antv/g2plot'
import { downloadCanvas } from '@/utils/imgUtils'
import ExcelJS from 'exceljs'
interface Copilot {
msgType: string
question: string
chart: object
chartData: object
msgStatus: number
id: string
}
const props = defineProps({
copilotInfo: {
@ -13,6 +17,7 @@ const props = defineProps({
default: () => ({
msgType: 'api',
chart: {},
id: '',
question: '',
chartData: {
data: {},
@ -23,9 +28,141 @@ const props = defineProps({
},
isWelcome: {
type: Boolean
},
isAnswer: {
type: Boolean
}
})
const content = ref()
const chartTypeList = ref()
let columnPlot = null
onMounted(() => {
const { chart, msgType, msgStatus, chartData, id } = props.copilotInfo
if (msgStatus === 1 && msgType === 'api' && chartData) {
if (['bar', 'line'].includes(chart.type)) {
isLine.value = chart.type === 'line'
const chartType = chart.type === 'bar' ? Column : Line
columnPlot = new chartType(`de-${id}-ed`, {
data: chartData.data.data,
xField: chart.axis.x,
yField: chart.axis.y,
legend: {
layout: 'horizontal',
position: 'left'
}
})
columnPlot.render()
} else {
columns.value = chartData.data.fields.map(_ => ({
key: `${_.originName}`,
dataKey: `${_.originName}`,
title: `${_.originName}`,
width: 150
}))
data.value = chartData.data.data.map((ele, index) => {
return {
...ele,
id: index + 'row'
}
})
renderTableLocal.value = true
}
}
nextTick(() => {
;(chartTypeList.value || content.value).scrollIntoView({
block: 'end',
inline: 'nearest',
behavior: 'smooth'
})
})
})
const exportExcel = () => {
const { chartData, chart } = props.copilotInfo
const workbook = new ExcelJS.Workbook()
const worksheet = workbook.addWorksheet('Sheet1')
//
worksheet.columns = chartData.data.fields.map(ele => {
return { header: ele.originName, key: ele.originName }
})
const arr = chartData.data.fields.map(ele => ele.originName)
chartData.data.data.forEach(item => {
worksheet.addRow(arr.map(ele => item[ele]))
})
// excel
workbook.xlsx.writeBuffer().then(buffer => {
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = chart.title + '.xlsx'
link.click()
window.URL.revokeObjectURL(url)
})
}
const renderTableLocal = ref(false)
const switchChartType = type => {
columnPlot?.destroy()
isLine.value = type === 'line'
const { chart, msgType, msgStatus, chartData, id } = props.copilotInfo
renderTableLocal.value = false
if (msgStatus === 1 && msgType === 'api' && chartData) {
if (['bar', 'line'].includes(type)) {
const chartType = type === 'bar' ? Column : Line
const columnPlot = new chartType(`de-${id}-ed`, {
data: chartData.data.data,
xField: chart.axis.x,
yField: chart.axis.y,
legend: {
layout: 'horizontal',
position: 'left'
}
})
columnPlot.render()
return
}
columns.value = chartData.data.fields.map(_ => ({
key: `${_.originName}`,
dataKey: `${_.originName}`,
title: `${_.originName}`,
width: 150
}))
data.value = chartData.data.data.map((ele, index) => {
return {
...ele,
id: index + 'row'
}
})
renderTableLocal.value = true
}
}
const chartTypeRef = ref()
const downloadChart = () => {
if (renderTableLocal.value) {
exportExcel()
return
}
downloadCanvas('img', chartTypeRef.value, '图表')
}
const renderTable = computed(() => {
const { chart, msgType, msgStatus, chartData } = props.copilotInfo
return (
msgType === 'api' && msgStatus === 1 && !['bar', 'line'].includes(chart?.type) && chartData.data
)
})
const isLine = ref(false)
const columns = shallowRef([])
const data = shallowRef([])
const tips = computed(() => {
const { chart, msgType, question, msgStatus } = props.copilotInfo
if (msgType === 'api' && msgStatus === 1) {
@ -43,20 +180,73 @@ const tips = computed(() => {
<template>
<div
class="dialogue-chart"
:class="copilotInfo.msgType === 'user' ? 'user-dialogue' : 'api-dialogue'"
:class="[
copilotInfo.msgType === 'user' ? 'user-dialogue' : 'api-dialogue',
copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1 && 'chart-dialogue'
]"
>
<el-icon style="font-size: 32px" class="dialogue-chart_icon">
<Icon :name="copilotInfo.msgType === 'api' ? 'copilot' : 'default_avatar'" />
</el-icon>
<div class="content">
<div ref="content" class="content">
<div v-if="isWelcome" class="question-or-title" style="font-size: 16px; font-weight: 500">
您好我是 Copilot很高兴为你服务
</div>
<div v-else-if="isAnswer" class="question-or-title" style="font-size: 16px; font-weight: 500">
回答中<span class="dot">...</span>
</div>
<div v-else class="question-or-title">
{{ tips }}
</div>
<div v-if="isWelcome" class="is-welcome">这是一句 Copilot 的功能描述</div>
<div v-else-if="copilotInfo.msgType === 'api'" class="chart-type"></div>
<div
v-else-if="copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1"
class="chart-type"
ref="chartTypeRef"
>
<div class="column-plot_de" :id="`de-${copilotInfo.id}-ed`">
<el-table-v2
v-if="renderTable || renderTableLocal"
:columns="columns"
:data="data"
:width="718"
:height="335"
fixed
/>
</div>
</div>
</div>
<div
ref="chartTypeList"
class="chart-type_list"
v-if="copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1"
>
<el-tooltip effect="dark" content="切换至柱状图" placement="top">
<el-icon v-show="isLine" @click="switchChartType('bar')">
<Icon name="chart-bar" />
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" content="切换至折线图" placement="top">
<el-icon style="font-size: 16px" v-show="!isLine" @click="switchChartType('line')">
<Icon name="icon_chart-line" />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="切换至明细表" placement="top">
<el-icon @click="switchChartType('table')">
<Icon name="chart-table" />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="下载" placement="top">
<el-icon @click="downloadChart">
<Icon name="chart-download" />
</el-icon>
</el-tooltip>
</div>
</div>
</template>
@ -64,8 +254,47 @@ const tips = computed(() => {
<style lang="less" scoped>
.dialogue-chart {
display: flex;
& + & {
margin-bottom: 24px;
margin-top: 24px;
position: relative;
.chart-type_list {
position: absolute;
bottom: -36px;
right: 0;
display: flex;
align-items: center;
font-size: 24px;
.ed-icon {
position: relative;
cursor: pointer;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 24px;
height: 24px;
background: #1f23291a;
transform: translate(-50%, -50%);
display: none;
border-radius: 4px;
}
&:hover {
&::after {
display: block;
}
}
}
.ed-divider--vertical {
border-color: #1f232926;
height: 14px;
margin: 0 6px;
}
}
&.chart-dialogue {
margin-bottom: 52px;
}
&.user-dialogue {
@ -81,6 +310,11 @@ const tips = computed(() => {
}
}
.column-plot_de {
width: 718px;
height: 335px;
}
.content {
flex: 1;
margin-left: 8px;
@ -91,11 +325,32 @@ const tips = computed(() => {
font-weight: 400;
line-height: 22px;
padding: 12px 16px;
@keyframes identifier {
0% {
width: 0px;
}
33% {
width: 10px;
}
100% {
width: 22px;
}
}
.dot {
overflow: hidden;
display: inline-block;
animation: identifier 1s infinite;
}
}
.chart-type {
height: 360px;
border-top: 1px solid #1f232926;
padding: 12px 16px;
}
.is-welcome {

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { ref, shallowRef, computed, watch } from 'vue'
import { ElMessageBox } from 'element-plus-secondary'
import {
getDatasetTree,
clearAllCopilot,
@ -11,6 +12,7 @@ import { useElementSize } from '@vueuse/core'
import { fieldType } from '@/utils/attr'
import DialogueChart from '@/views/copilot/DialogueChart.vue'
import { type Tree } from '@/views/visualized/data/dataset/form/CreatDsGroup.vue'
import { cloneDeep } from 'lodash-es'
const quota = shallowRef([])
const dimensions = shallowRef([])
const datasetTree = shallowRef([])
@ -55,10 +57,35 @@ const initDataset = () => {
datasetTree.value = (res as unknown as Tree[]) || []
})
}
const handleDatasetChange = () => {
getOptions(datasetId.value)
}
const treeSelectRef = ref()
let oldId = ''
let currentId = ''
let oldName = ''
const handleDatasetChange = () => {
if (!!oldId) {
currentId = datasetId.value
datasetId.value = oldId
const msg = `当前数据集为【${oldName}】,切换数据集将清空当前会话。`
ElMessageBox.confirm('确定要切换数据集吗?', {
confirmButtonType: 'primary',
type: 'warning',
tip: msg,
autofocus: false,
showClose: false
}).then(() => {
datasetId.value = currentId
oldId = datasetId.value
oldName = treeSelectRef.value.getCurrentNode().name
getOptions(datasetId.value)
clearAllCopilot()
})
} else {
oldId = datasetId.value
oldName = treeSelectRef.value.getCurrentNode().name
getOptions(datasetId.value)
}
}
const getOptions = id => {
copilotFields(id).then(res => {
dimensions.value = res.data?.dimensionList || []
@ -66,6 +93,11 @@ const getOptions = id => {
})
}
initDataset()
let historyBack = []
getListCopilot().then(res => {
historyBack = (res as unknown as string[]) || []
historyArr.value = cloneDeep(historyBack)
})
const questionInputRef = ref()
const overHeight = ref(false)
const { height } = useElementSize(questionInputRef)
@ -75,14 +107,33 @@ watch(
overHeight.value = val > 48
}
)
const copilotChatLoading = ref(false)
const queryAnswer = () => {
if (!isActive.value) return
if (!isActive.value || copilotChatLoading.value) return
historyArr.value.push({
msgType: 'user',
chart: {},
id: `${+new Date()}`,
question: questionInput.value,
chartData: {
data: {},
title: ''
},
msgStatus: 1
})
copilotChatLoading.value = true
copilotChat({
datasetGroupId: datasetId.value,
question: questionInput.value,
history: historyArr.value
history: historyBack
})
.then(res => {
historyArr.value.push(res)
historyBack = res.history || []
})
.finally(() => {
copilotChatLoading.value = false
})
}
</script>
@ -96,21 +147,25 @@ const queryAnswer = () => {
</div>
<div class="copilot-service">
<div class="dialogue">
<div class="copilot-dialogue">
<div class="copilot-dialogue" :style="{ height: `calc(100vh - ${height + 152}px)` }">
<DialogueChart key="isWelcome" isWelcome></DialogueChart>
<DialogueChart :copilotInfo="ele" v-for="ele in historyArr" :key="ele.id"></DialogueChart>
<div class="question-input" :class="overHeight && 'over-height'" ref="questionInputRef">
<el-input
v-model="questionInput"
:autosize="{ minRows: 1, maxRows: 8 }"
type="textarea"
:placeholder="$t('common.inputText')"
>
</el-input>
<el-icon class="copilot-icon" @click="queryAnswer" :class="isActive && 'active'">
<Icon :name="isActive ? 'active-btn_copilot' : 'btn_copilot'"></Icon>
</el-icon>
</div>
<DialogueChart v-if="copilotChatLoading" key="isAnswer" isAnswer></DialogueChart>
</div>
<div class="question-input" :class="overHeight && 'over-height'" ref="questionInputRef">
<el-input
v-model="questionInput"
:autosize="{ minRows: 1, maxRows: 8 }"
type="textarea"
:placeholder="$t('common.inputText')"
>
</el-input>
<el-icon v-if="copilotChatLoading" class="copilot-icon circular-input_icon">
<Icon name="icon_loading_outlined"></Icon>
</el-icon>
<el-icon v-else class="copilot-icon" @click="queryAnswer" :class="isActive && 'active'">
<Icon :name="isActive ? 'active-btn_copilot' : 'btn_copilot'"></Icon>
</el-icon>
</div>
</div>
<div class="dataset-select" :style="{ width: showLeft ? 0 : '280px' }">
@ -138,6 +193,7 @@ const queryAnswer = () => {
@change="handleDatasetChange"
:props="dsSelectProps"
style="width: 100%"
ref="treeSelectRef"
placement="bottom"
:render-after-expand="false"
filterable
@ -232,78 +288,93 @@ const queryAnswer = () => {
.dialogue {
flex: 1;
padding: 0 160px;
position: relative;
.copilot-dialogue {
height: calc(100vh - 113px);
padding-top: 24px;
position: relative;
overflow-y: auto;
padding-bottom: 25px;
}
.question-input {
min-height: 47px;
width: calc(100% - 400px);
margin-left: 20px;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
border: 1px solid #fff;
bottom: 25px;
border-radius: 8px;
left: 180px;
box-sizing: border-box;
background: #fff;
box-shadow: 0px 6px 24px 0px #1f232914;
&:hover {
border: 1px solid var(--ed-color-primary);
}
.question-input {
width: calc(100% - 40px);
min-height: 47px;
margin-left: 20px;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
border: 1px solid #fff;
bottom: 24px;
border-radius: 8px;
left: 0;
box-sizing: border-box;
background: #fff;
box-shadow: 0px 6px 24px 0px #1f232914;
&:hover {
border: 1px solid var(--ed-color-primary);
}
&:has(.ed-textarea__inner:focus) {
border: 1px solid var(--ed-color-primary);
}
&:has(.ed-textarea__inner:focus) {
border: 1px solid var(--ed-color-primary);
}
:deep(.ed-textarea__inner) {
border-radius: 8px !important;
box-shadow: none;
resize: none;
padding: 12px 16px;
max-height: 200px;
}
:deep(.ed-textarea__inner) {
border-radius: 8px !important;
box-shadow: none;
resize: none;
padding: 12px 16px;
max-height: 200px;
}
&.over-height :deep(.ed-textarea__inner) {
padding-bottom: 40px;
}
&.over-height :deep(.ed-textarea__inner) {
padding-bottom: 40px;
}
.copilot-icon {
position: absolute !important;
bottom: 12px;
right: 16px;
font-size: 24px;
cursor: not-allowed;
position: relative;
&.active {
cursor: pointer;
.copilot-icon {
position: absolute !important;
bottom: 12px;
right: 16px;
font-size: 24px;
cursor: not-allowed;
position: relative;
&.active {
cursor: pointer;
&::after {
content: '';
position: absolute;
height: 32px;
width: 32px;
border-radius: 8px;
display: none;
background: #3370ff1a;
}
&:hover {
&::after {
content: '';
position: absolute;
height: 32px;
width: 32px;
border-radius: 8px;
display: none;
background: #3370ff1a;
}
&:hover {
&::after {
display: block;
}
display: block;
}
}
}
}
@keyframes circular {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.circular-input_icon {
animation: circular 1s infinite;
}
}
}
.dataset-select {
width: 280px;
height: 100%;
height: calc(100vh - 115px);
background: #fff;
border-left: 1px solid #1f232926;
position: relative;