fix(copilot): 删除copilot相关代码

This commit is contained in:
dataeaseShu
2025-09-05 14:19:21 +08:00
committed by dataeaseShu
parent 20ecee9507
commit c2301389c3
8 changed files with 0 additions and 1338 deletions

View File

@@ -1,92 +0,0 @@
<script lang="ts" setup>
import copilot from '@/assets/svg/copilot.svg'
import { onMounted, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const visible = ref(true)
const emits = defineEmits(['confirm'])
const confirm = () => {
emits('confirm')
}
onMounted(() => {
// do
})
</script>
<template>
<el-popover
:visible="visible"
placement="bottom"
popper-class="copilot-popper-tips"
:width="288"
show-arrow
>
<div class="copilot-popper-tips-content">
<p class="title">{{ t('copilot.talking_analysis') }}</p>
<p class="constant">
{{ t('copilot.hello') }}
<br />{{ t('copilot.click_talk') }}<br />&nbsp;
</p>
<div class="bottom">
<el-button size="middle" @click="confirm"> {{ t('copilot.know') }} </el-button>
</div>
</div>
<template #reference>
<div class="copilot-popper-tips-icon">
<el-icon style="margin: 2px" class="ai-icon">
<Icon name="copilot"><copilot class="svg-icon" /></Icon>
</el-icon>
</div>
</template>
</el-popover>
</template>
<style lang="less">
.copilot-popper-tips {
z-index: 10001 !important;
padding: 24px !important;
box-shadow: none !important;
border: 0px !important;
background: var(--ed-color-primary) !important;
.ed-popper__arrow::before {
border: 1px solid var(--ed-color-primary) !important;
background: var(--ed-color-primary) !important;
}
}
.copilot-popper-tips-content {
color: rgba(255, 255, 255, 1);
.title {
font-family: var(--de-custom_font, 'PingFang');
font-size: 20px;
font-weight: 500;
line-height: 28px;
}
.content {
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-weight: 500;
line-height: 22px;
text-align: left;
}
.bottom {
line-height: 22px;
text-align: right;
button {
border: 0px !important;
border-color: #ffffff !important;
font-weight: 500;
color: var(--ed-color-primary, rgba(51, 112, 255, 1)) !important;
}
}
}
.copilot-popper-tips-icon {
margin: 0 8px;
z-index: 10003;
border-radius: 50%;
background: #ffffff;
width: 28px;
height: 28px;
}
</style>

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import iconSetting from '@/assets/svg/icon-setting.svg'
import copilot from '@/assets/svg/copilot.svg'
import LangSelector from '@/layout/components/LangSelector.vue'
import { useRouter } from 'vue-router_2'
import TopDesktopCard from './TopDesktopCard.vue'
@@ -47,10 +46,6 @@ const initAiBase = async () => {
})
}
const handleCopilotClick = () => {
push('/copilot/index')
}
const handleAiClick = () => {
useEmitt().emitter.emit('aiComponentChange')
}
@@ -109,14 +104,6 @@ onMounted(() => {
name: $t('commons.assistant')
}"
></TopDesktopCard>
<TopDesktopCard
v-if="appearanceStore.getShowCopilot"
@openBlank="handleCopilotClick"
:cardInfo="{
icon: copilot,
name: 'Copilot'
}"
></TopDesktopCard>
</div>
<div class="border-top">
<el-popover

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import logo from '@/assets/svg/logo.svg'
import copilot from '@/assets/svg/copilot.svg'
import msgNotice from '@/assets/svg/icon_notification_outlined.svg'
import dvAi from '@/assets/svg/dv-ai.svg'
import dvPreviewDownload from '@/assets/svg/icon_download_outlined.svg'
@@ -22,7 +21,6 @@ import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import AiComponent from '@/layout/components/AiComponent.vue'
import { findBaseParams } from '@/api/aiComponent'
import AiTips from '@/layout/components/AiTips.vue'
import CopilotCom from '@/layout/components/Copilot.vue'
import DesktopSetting from './DesktopSetting.vue'
const appearanceStore = useAppearanceStoreWithOut()
@@ -42,9 +40,6 @@ const handleAiClick = () => {
useEmitt().emitter.emit('aiComponentChange')
}
const { t } = useI18n()
const handleCopilotClick = () => {
push('/copilot/index')
}
const desktop = isDesktop()
const activeIndex = computed(() => {
@@ -111,10 +106,6 @@ const msgNoticePush = () => {
push('/msg/msg-fill')
}
const copilotConfirm = () => {
wsCache.set('DE-COPILOT-TIPS-CHECK', 'CHECKED')
showOverlayCopilot.value = false
}
const badgeCount = ref('0')
onMounted(() => {
@@ -146,20 +137,6 @@ onMounted(() => {
</el-menu>
<div class="operate-setting" v-if="!desktop">
<XpackComponent jsname="c3dpdGNoZXI=" />
<el-tooltip effect="dark" content="Copilot" placement="bottom">
<el-icon
style="margin: 0 10px"
class="ai-icon copilot-icon"
v-if="!showOverlayCopilot && appearanceStore.getShowCopilot"
>
<Icon name="copilot"><copilot @click="handleCopilotClick" class="svg-icon" /></Icon>
</el-icon>
</el-tooltip>
<CopilotCom
@confirm="copilotConfirm"
v-if="showOverlayCopilot && appearanceStore.getShowCopilot"
class="copilot-icon-tips"
/>
<el-tooltip effect="dark" :content="t('commons.assistant')" placement="bottom">
<el-icon
style="margin: 0 10px"
@@ -215,7 +192,6 @@ onMounted(() => {
:base-url="aiBaseUrl"
></ai-component>
<div v-if="showOverlay && appearanceStore.getShowAi" class="overlay"></div>
<div v-if="showOverlayCopilot && appearanceStore.getShowCopilot" class="overlay"></div>
</div>
<div v-else class="operate-setting">
<desktop-setting />

View File

@@ -23,7 +23,6 @@ const DashboardPanel = defineAsyncComponent(
() => import('@/views/dashboard/DashboardPreviewShow.vue')
)
const Copilot = defineAsyncComponent(() => import('@/views/copilot/index.vue'))
const TemplateManage = defineAsyncComponent(() => import('@/views/template/indexInject.vue'))
const Preview = defineAsyncComponent(() => import('@/views/data-visualization/PreviewCanvas.vue'))
@@ -49,7 +48,6 @@ const componentMap = {
DashboardPanel,
DatasetEditor,
DashboardEmpty,
Copilot,
TemplateManage
}

View File

@@ -20,22 +20,6 @@ export const routes: AppRouteRecordRaw[] = [
}
]
},
{
path: '/copilot',
name: 'copilot',
component: () => import('@/layout/index.vue'),
hidden: true,
meta: {},
children: [
{
path: 'index',
name: 'cpt',
hidden: true,
component: () => import('@/views/copilot/index.vue'),
meta: { hidden: true }
}
]
},
{
path: '/login',
name: 'login',

View File

@@ -28,7 +28,6 @@ const ScreenPanel = defineAsyncComponent(() => import('@/views/data-visualizatio
const DashboardPanel = defineAsyncComponent(
() => import('@/views/dashboard/DashboardPreviewShow.vue')
)
const Copilot = defineAsyncComponent(() => import('@/views/copilot/index.vue'))
const TemplateManage = defineAsyncComponent(() => import('@/views/template/indexInject.vue'))
const AsyncXpackComponent = defineAsyncComponent(() => import('@/components/plugin/src/index.vue'))
@@ -43,7 +42,6 @@ const componentMap = {
Datasource,
ScreenPanel,
DashboardPanel,
Copilot,
TemplateManage
}
const iframeStyle = ref(null)

View File

@@ -1,547 +0,0 @@
<script lang="ts" setup>
import icon_chartLineC from '@/assets/svg/icon_chart-line-c.svg'
import icon_dashboard_outlinedC from '@/assets/svg/icon_dashboard_outlined-c.svg'
import icon_pie_outlinedC from '@/assets/svg/icon_pie_outlined-c.svg'
import default_avatar from '@/assets/svg/default_avatar.svg'
import copilot from '@/assets/svg/copilot.svg'
import chartTable from '@/assets/svg/chart-table.svg'
import chartDownload from '@/assets/svg/chart-download.svg'
import { PropType, computed, onMounted, shallowRef, ref, nextTick, watch } from 'vue'
import { useElementSize } from '@vueuse/core'
import { downloadCanvas } from '@/utils/imgUtils'
import ExcelJS from 'exceljs'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
interface Copilot {
msgType: string
question: string
chart: object
loading: boolean
chartData: object
msgStatus: number
id: string
}
const props = defineProps({
copilotInfo: {
type: Object as PropType<Copilot>,
default: () => ({
msgType: 'api',
chart: {},
id: '',
loading: false,
question: '',
chartData: {
data: {},
title: ''
},
msgStatus: 0
})
},
isWelcome: {
type: Boolean
},
isAnswer: {
type: Boolean
}
})
const content = ref()
const chartTypeList = ref()
let columnPlot = null
const chartRef = ref()
const { width } = useElementSize(chartRef)
onMounted(() => {
const { chart, msgType, msgStatus, chartData, id } = props.copilotInfo
/* if (msgStatus === 1 && msgType === 'api' && chartData) {
if (['bar', 'line'].includes(chart.type)) {
activeCommand.value = chart.type
const chartType = chart.type === 'bar' ? Column : Line
columnPlot = new chartType(`de-${id}-ed`, {
data: chartData.data.data,
xField: chart.axis?.x?.value,
yField: chart.axis?.y?.value,
legend: {
layout: 'horizontal',
position: 'left'
}
})
columnPlot.render()
} else if (chart.type === 'pie') {
activeCommand.value = chart.type
columnPlot = new Pie(`de-${id}-ed`, {
appendPadding: 10,
data: chartData.data.data,
angleField: chart.axis?.y?.value,
colorField: chart.axis?.x?.value,
radius: 0.9,
interactions: [{ type: 'element-active' }]
})
columnPlot.render()
} else {
columns.value = chart.columns.map(_ => ({
key: `${_.value}`,
dataKey: `${_.value}`,
title: `${_.name}`,
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 = chart.columns.map(ele => {
return { header: ele.name, key: ele.value }
})
const arr = chart.columns.map(ele => ele.value)
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 changeChartType = () => {
switchChartType(activeCommand.value)
}
const switchChartType = type => {
renderTableLocal.value = false
/* nextTick(() => {
if (columnPlot?.chart && !columnPlot.chart.wrapperElement) {
columnPlot.chart.wrapperElement = document.querySelector(
`#de-${props.copilotInfo.id}-ed > div`
)
}
if (columnPlot?.chart?.wrapperElement) {
columnPlot.destroy()
}
if (['bar', 'line', 'pie'].includes(type)) {
activeCommand.value = type
}
const { chart, msgType, msgStatus, chartData, id } = props.copilotInfo
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?.value,
yField: chart.axis?.y?.value,
legend: {
layout: 'horizontal',
position: 'left'
}
})
columnPlot.render()
return
}
if (type === 'pie') {
columnPlot = new Pie(`de-${id}-ed`, {
appendPadding: 10,
data: chartData.data.data,
angleField: chart.axis?.y?.value,
colorField: chart.axis?.x?.value,
radius: 0.9,
interactions: [{ type: 'element-active' }]
})
columnPlot.render()
return
}
columns.value = chart.columns.map(_ => ({
key: `${_.value}`,
dataKey: `${_.value}`,
title: `${_.name}`,
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, t('copilot.chart'))
}
watch(
() => props.copilotInfo.loading,
val => {
if (!val) {
switchChartType(renderTableLocal.value ? 'table' : activeCommand.value)
}
}
)
const renderTable = computed(() => {
const { chart, msgType, msgStatus, chartData } = props.copilotInfo
return (
msgType === 'api' &&
msgStatus === 1 &&
!['bar', 'line', 'pie'].includes(chart?.type) &&
chartData.data
)
})
const activeCommand = ref('')
const curTypeList = [
{
label: t('copilot.line'),
value: 'line',
icon: icon_chartLineC
},
{
label: t('copilot.bar'),
icon: icon_dashboard_outlinedC,
value: 'bar'
},
{
label: t('copilot.pie'),
icon: icon_pie_outlinedC,
value: 'pie'
}
]
const columns = shallowRef([])
const data = shallowRef([])
const tips = computed(() => {
const { chart, msgType, question, msgStatus } = props.copilotInfo
if (msgType === 'api' && msgStatus === 1) {
return chart.title
}
if (msgStatus === 0) {
return t('copilot.sorry')
} else if (msgType === 'user') {
return question
}
return ''
})
</script>
<template>
<div
class="dialogue-chart"
: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
><component
class="svg-icon"
:is="copilotInfo.msgType === 'api' ? copilot : default_avatar"
></component
></Icon>
</el-icon>
<div ref="content" class="content">
<div v-if="isWelcome" class="question-or-title" style="font-size: 16px; font-weight: 500">
{{ t('copilot.hello1') }}
</div>
<div v-else-if="isAnswer" class="question-or-title" style="font-size: 16px; font-weight: 500">
{{ t('copilot.answer') }}<span class="dot">...</span>
</div>
<div v-else class="question-or-title">
{{ tips }}
</div>
<div v-if="isWelcome" class="is-welcome">{{ t('copilot.example') }}</div>
<div
v-else-if="copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1"
class="chart-type"
ref="chartTypeRef"
>
<div
v-if="!copilotInfo.loading"
ref="chartRef"
class="column-plot_de"
:id="`de-${copilotInfo.id}-ed`"
>
<el-table-v2
v-if="renderTable || renderTableLocal"
:columns="columns"
:data="data"
:width="width"
:height="335"
fixed
/>
</div>
</div>
</div>
<div
ref="chartTypeList"
class="chart-type_list"
v-if="copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1"
>
<template v-if="!renderTable">
<el-icon
:class="!(renderTable || renderTableLocal) && 'active'"
v-if="activeCommand"
class="select-prefix"
>
<Icon
><component
class="svg-icon"
:is="curTypeList.find(ele => ele.value === activeCommand).icon"
></component
></Icon>
</el-icon>
<el-tooltip effect="dark" :content="t('copilot.switch_chart')" placement="top">
<div
v-show="renderTable || renderTableLocal"
@click="switchChartType(activeCommand)"
class="fake-mask_select"
></div>
</el-tooltip>
<el-tooltip effect="dark" :content="t('copilot.switch_chart')" placement="top">
<el-select
popper-class="copilot-select_popper"
class="select-copilot-list"
:class="!(renderTable || renderTableLocal) && 'active'"
v-model="activeCommand"
size="small"
@change="changeChartType"
>
<el-option
v-for="item in curTypeList"
:key="item.label"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-tooltip>
<el-divider direction="vertical" />
</template>
<el-tooltip effect="dark" :content="t('copilot.switch_table')" placement="top">
<el-icon
:class="(renderTable || renderTableLocal) && 'active'"
class="ed-icon_chart"
@click="switchChartType('table')"
>
<Icon name="chart-table"><chartTable class="svg-icon" /></Icon>
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" :content="t('copilot.download')" placement="top">
<el-icon class="ed-icon_chart" @click="downloadChart">
<Icon name="chart-download"><chartDownload class="svg-icon" /></Icon>
</el-icon>
</el-tooltip>
</div>
</div>
</template>
<style lang="less" scoped>
.dialogue-chart {
display: flex;
margin-top: 24px;
position: relative;
.select-copilot-list {
width: 40px;
position: relative;
z-index: 100;
:deep(.ed-input__wrapper) {
background: transparent;
box-shadow: none !important;
padding-right: 4px;
.ed-input__inner {
visibility: hidden;
}
}
&.active {
:deep(.ed-input__wrapper) {
background: #3370ff1a;
}
}
}
.chart-type_list {
position: absolute;
bottom: -36px;
right: 0;
display: flex;
align-items: center;
font-size: 24px;
.fake-mask_select {
width: 40px;
height: 24px;
cursor: pointer;
border-radius: 4px;
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
z-index: 101;
&:hover {
background: #1f23291a;
}
}
.ed-icon_chart {
position: relative;
cursor: pointer;
color: #646a73;
&::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;
}
}
&.active {
color: var(--ed-color-primary, #3370ff);
&::after {
display: block;
background: #3370ff1a;
}
}
}
.select-prefix {
position: absolute;
left: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
color: #646a73;
&.active {
color: var(--ed-color-primary, #3370ff);
}
}
.ed-divider--vertical {
border-color: #1f232926;
height: 14px;
margin: 0 6px;
}
}
&.chart-dialogue {
margin-bottom: 52px;
}
&.user-dialogue {
.content {
background: #d6e2ff;
}
}
&.api-dialogue {
.content {
background: #fff;
box-shadow: 0px 4px 8px 0px #1f23291a;
}
}
.column-plot_de {
width: calc(100% - 32px);
height: 335px;
}
.content {
flex: 1;
margin-left: 8px;
border-radius: 8px;
.question-or-title {
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
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 {
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-weight: 400;
line-height: 22px;
text-align: left;
color: #646a73;
margin: -8px 16px 12px 16px;
}
}
}
</style>
<style lang="less">
.copilot-select_popper {
width: 128px;
}
</style>

View File

@@ -1,642 +0,0 @@
<script lang="ts" setup>
import activeBtn_copilot from '@/assets/svg/active-btn_copilot.svg'
import btn_copilot from '@/assets/svg/btn_copilot.svg'
import copilot from '@/assets/svg/copilot.svg'
import icon_loading_outlined from '@/assets/svg/icon_loading_outlined.svg'
import treeSort from '@/utils/treeSortUtils'
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
import { useCache } from '@/hooks/web/useCache'
import { useEmbedded } from '@/store/modules/embedded'
import dvFolder from '@/assets/svg/dv-folder.svg'
import icon_dataset from '@/assets/svg/icon_dataset.svg'
import icon_expandRight_filled from '@/assets/svg/icon_expand-right_filled.svg'
import { ref, shallowRef, computed, watch, nextTick, unref } from 'vue'
import { ElMessageBox } from 'element-plus-secondary'
import {
getDatasetTree,
clearAllCopilot,
copilotFields,
getListCopilot,
copilotChat
} from '@/api/dataset'
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'
import { iconFieldMap } from '@/components/icon-group/field-list'
import { useI18n } from '@/hooks/web/useI18n'
const embeddedStore = useEmbedded()
const { wsCache } = useCache()
const { t } = useI18n()
const quota = shallowRef([])
const dimensions = shallowRef([])
const datasetTree = shallowRef([])
const historyArr = ref([])
const datasetId = ref('')
const questionInput = ref('')
const showLeft = ref(false)
const defaultProps = {
children: 'children',
label: 'name'
}
const dsSelectProps = {
label: 'name',
children: 'children',
value: 'id',
isLeaf: node => !node.children?.length
}
const expandedD = ref(true)
const expandedQ = ref(true)
const dfs = arr => {
return arr.filter(ele => {
if (!!ele.children?.length && !ele.leaf) {
ele.children = dfs(ele.children)
return !!ele.children?.length
}
return ele.leaf
})
}
const originResourceTree = ref([])
const computedTree = computed(() => {
if (datasetTree.value[0]?.id === '0') {
return dfs(datasetTree.value[0].children)
}
return dfs(datasetTree.value)
})
const isActive = computed(() => {
return questionInput.value.trim().length && !!datasetId.value
})
const sortList = ['time_asc', 'time_desc', 'name_asc', 'name_desc']
const initDataset = async () => {
await getDatasetTree({}).then(res => {
datasetTree.value = (res as unknown as Tree[]) || []
let curSortType = sortList[Number(wsCache.get('TreeSort-backend')) ?? 1]
curSortType = wsCache.get('TreeSort-dataset') ?? curSortType
originResourceTree.value = cloneDeep(unref(datasetTree))
datasetTree.value = treeSort(originResourceTree.value, curSortType)
})
getListCopilot().then(res => {
const allList = (res as unknown as { history: object }[]) || []
historyBack = allList[allList.length - 1]?.history || []
historyArr.value = cloneDeep(allList).map(ele => ({ ...ele, loading: false }))
if (!!allList.length) {
datasetId.value = allList[0].datasetGroupId
oldId = datasetId.value
datasetId.value && getOptions(datasetId.value)
if (oldId && !oldName) {
nextTick(() => {
dfsName(computedTree.value)
})
}
}
})
}
const treeSelectRef = ref()
let oldId = ''
let currentId = ''
let oldName = ''
const copilotDialogueComputed = computed(() => {
if (embeddedStore.baseUrl) {
return { height: `100%` }
}
return { height: `calc(100vh - ${height + 152}px)` }
})
const dfsName = arr => {
return arr.filter(ele => {
if (ele.id === oldId) {
oldName = ele.name
}
if (!!ele.children?.length && !ele.leaf) {
ele.children = dfsName(ele.children)
return !!ele.children?.length
}
return ele.leaf
})
}
const handleDatasetChange = () => {
if (!!oldId && !!historyArr.value.length) {
currentId = datasetId.value
datasetId.value = oldId
const msg = t('copilot.ds_prefix') + oldName + t('copilot.ds_suffix')
ElMessageBox.confirm(t('copilot.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().then(() => {
historyArr.value = []
historyBack = []
})
})
} else {
oldId = datasetId.value
oldName = treeSelectRef.value.getCurrentNode().name
getOptions(datasetId.value)
}
}
const getOptions = id => {
copilotFields(id).then(res => {
dimensions.value = res.data?.dimensionList || []
quota.value = res.data?.quotaList || []
})
}
initDataset()
let historyBack = []
const questionInputRef = ref()
const overHeight = ref(false)
const { height } = useElementSize(questionInputRef)
watch(
() => height.value,
val => {
overHeight.value = val > 48
}
)
const handleShowLeft = val => {
showLeft.value = val
historyArr.value.forEach(ele => {
ele.loading = true
})
setTimeout(() => {
historyArr.value.forEach(ele => {
ele.loading = false
})
}, 300)
}
const copilotChatLoading = ref(false)
const inputRef = ref()
const queryAnswer = (event?: KeyboardEvent) => {
if (event?.keyCode === 229) {
return
}
if (event?.keyCode === 13) {
event.preventDefault()
}
let copyAuestionInput = questionInput.value
if (!isActive.value || copilotChatLoading.value) return
questionInput.value = ''
inputRef.value.blur()
nextTick(() => {
overHeight.value = false
})
historyArr.value.push({
msgType: 'user',
chart: {},
id: `${+new Date()}`,
question: copyAuestionInput,
chartData: {
data: {},
title: ''
},
msgStatus: 1
})
copilotChatLoading.value = true
copilotChat({
datasetGroupId: datasetId.value,
question: copyAuestionInput,
history: historyBack
})
.then(res => {
historyArr.value.push({ ...(res || {}), loading: false })
historyBack = res.history || []
})
.finally(() => {
copilotChatLoading.value = false
})
}
</script>
<template>
<div class="copilot">
<div class="copilot-analysis">
<el-icon style="margin-right: 8px; font-size: 24px">
<Icon name="copilot"><copilot class="svg-icon" /></Icon>
</el-icon>
{{ t('copilot.talking_analysis') }}
</div>
<div class="copilot-service">
<div class="dialogue">
<div class="copilot-dialogue" :style="copilotDialogueComputed">
<DialogueChart key="isWelcome" isWelcome></DialogueChart>
<DialogueChart :copilotInfo="ele" v-for="ele in historyArr" :key="ele.id"></DialogueChart>
<DialogueChart v-if="copilotChatLoading" key="isAnswer" isAnswer></DialogueChart>
</div>
<div class="question-input" :class="overHeight && 'over-height'" ref="questionInputRef">
<el-input
v-model="questionInput"
@keydown.stop
ref="inputRef"
@keydown.enter="queryAnswer"
: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_loading_outlined class="svg-icon" /></Icon>
</el-icon>
<el-icon v-else class="copilot-icon" @click="queryAnswer" :class="isActive && 'active'">
<Icon
><component
class="svg-icon"
:is="isActive ? activeBtn_copilot : btn_copilot"
></component
></Icon>
</el-icon>
</div>
</div>
<div class="dataset-select" :style="{ width: showLeft ? 0 : '280px' }">
<el-tooltip effect="dark" :content="t('relation.retract')" placement="left">
<p v-show="!showLeft" class="arrow-right" @click="handleShowLeft(true)">
<el-icon>
<Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon>
</el-icon>
</p>
</el-tooltip>
<el-tooltip effect="dark" :content="t('relation.expand')" placement="left">
<p v-show="showLeft" class="left-outlined" @click="handleShowLeft(false)">
<el-icon>
<Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon>
</el-icon>
</p>
</el-tooltip>
<div style="width: 100%; height: calc(100% - 16px)" v-show="!showLeft">
<div class="title-dataset_select">{{ t('copilot.choose_dataset') }}</div>
<div style="margin: 0 16px" class="tree-select">
<el-tree-select
v-model="datasetId"
:data="computedTree"
:placeholder="t('copilot.pls_choose_dataset')"
@change="handleDatasetChange"
:props="dsSelectProps"
style="width: 100%"
node-key="id"
ref="treeSelectRef"
placement="bottom"
:render-after-expand="false"
filterable
popper-class="dataset-tree"
>
<template #default="{ node, data }">
<div class="content">
<el-icon size="18px" v-if="!data.leaf">
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
</el-icon>
<el-icon size="18px" v-if="data.leaf">
<Icon name="icon_dataset"><icon_dataset class="svg-icon" /></Icon>
</el-icon>
<span class="label ellipsis" style="margin-left: 8px" :title="node.label">{{
node.label
}}</span>
</div>
</template>
</el-tree-select>
</div>
<div class="preview-field">
<div :class="['field-d', { open: expandedD }]">
<div :class="['title', { expanded: expandedD }]" @click="expandedD = !expandedD">
<ElIcon class="expand">
<Icon><icon_expandRight_filled class="svg-icon" /></Icon>
</ElIcon>
&nbsp;{{ $t('chart.dimension') }}
</div>
<el-tree v-if="expandedD" :data="dimensions" :props="defaultProps">
<template #default="{ data }">
<span class="custom-tree-node father">
<el-icon>
<Icon
><component
class="svg-icon"
:class="`field-icon-${
fieldType[[2, 3].includes(data.deExtractType) ? 2 : 0]
}`"
:is="iconFieldMap[fieldType[data.deExtractType]]"
></component
></Icon>
</el-icon>
<span :title="data.name" class="label-tooltip">{{ data.name }}</span>
</span>
</template>
</el-tree>
</div>
<div class="line-bottom"></div>
<div :class="['field-q', { open: expandedQ }]">
<div :class="['title', { expanded: expandedQ }]" @click="expandedQ = !expandedQ">
<ElIcon class="expand">
<Icon name="icon_expand-right_filled"
><icon_expandRight_filled class="svg-icon"
/></Icon>
</ElIcon>
&nbsp;{{ $t('chart.quota') }}
</div>
<el-tree v-if="expandedQ" :data="quota" :props="defaultProps">
<template #default="{ data }">
<span class="custom-tree-node father">
<el-icon>
<Icon
><component
class="svg-icon"
:class="`field-icon-${
fieldType[[2, 3].includes(data.deExtractType) ? 2 : 0]
}`"
:is="iconFieldMap[fieldType[data.deExtractType]]"
></component
></Icon>
</el-icon>
<span :title="data.name" class="label-tooltip">{{ data.name }}</span>
</span>
</template>
</el-tree>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.copilot {
width: 100%;
height: 100%;
max-height: calc(100vh - 56px);
.copilot-analysis {
background-color: #fff;
padding: 16px 24px;
display: flex;
align-items: center;
font-weight: 500;
border-bottom: 1px solid #1f232926;
}
.copilot-service {
width: 100%;
height: calc(100% - 58px);
display: flex;
overflow-y: auto;
.dialogue {
flex: 1;
position: relative;
max-height: 100%;
.copilot-dialogue {
padding: 0 160px;
padding-top: 24px;
position: relative;
overflow-y: auto;
padding-bottom: 60px;
max-height: calc(100% - 75px);
}
.question-input {
min-height: 47px;
width: calc(100% - 360px);
margin-left: 20px;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
border: 1px solid #fff;
bottom: 25px;
border-radius: 8px;
left: 180px;
z-index: 100;
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);
}
: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;
}
.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 {
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%;
background: #fff;
border-left: 1px solid #1f232926;
position: relative;
.left-outlined {
position: absolute;
font-size: 12px;
cursor: pointer;
left: -20px;
top: 16px;
width: 20px;
height: 24px;
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
box-shadow: 0px 4px 8px 0px #0000001a;
border: 1px solid #dee0e3;
background: #fff;
display: flex;
align-items: center;
& > .ed-icon {
margin-left: 6px;
}
&:hover {
width: 24px;
left: -24px;
color: var(--ed-color-primary, #3370ff);
& > .ed-icon {
margin-left: 8px;
}
}
}
.arrow-right {
position: absolute;
top: 16px;
z-index: 2;
cursor: pointer;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
left: -12px;
height: 24px;
width: 24px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #dee0e3;
background: #fff;
font-size: 12px;
border-radius: 50%;
&:hover {
color: var(--ed-color-primary, #3370ff);
}
}
.title-dataset_select {
width: 100%;
margin: 16px 16px 12px 0;
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-weight: 500;
line-height: 22px;
padding-left: 16px;
}
.preview-field {
padding: 0 8px;
width: 100%;
height: calc(100% - 100px);
position: relative;
overflow-y: auto;
:deep(.ed-tree-node__content) {
border-radius: 4px;
&:hover {
background: #1f23291a;
}
}
:deep(.ed-tree-node.is-current > .ed-tree-node__content:not(.is-menu):after) {
display: none;
}
.custom-tree-node {
width: calc(100% - 32px);
display: flex;
align-items: center;
padding-right: 8px;
box-sizing: content-box;
.label-tooltip {
margin-left: 5.33px;
width: 70%;
font-size: 14px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.field-d,
.field-q {
position: relative;
height: 49px;
padding: 0 8px;
&.open {
max-height: 50%;
height: 50%;
}
.title {
cursor: pointer;
position: sticky;
top: 0px;
height: 49px;
font-family: var(--de-custom_font, 'PingFang');
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 22px;
color: #1f2329;
display: flex;
align-items: center;
z-index: 9;
background-color: #fff;
.expand {
font-size: 10px;
color: #646a73;
}
&.expanded {
.expand {
transform: rotate(90deg);
}
}
}
overflow-y: auto;
}
.field-d {
max-height: calc(100% - 52px);
}
.line-bottom {
background: rgba(31, 35, 41, 0.15);
height: 1px;
width: calc(100% - 16px);
margin-left: 8px;
}
}
}
}
}
</style>