|
@@ -5,7 +5,7 @@
|
|
<v-tab>AI 取数</v-tab>
|
|
<v-tab>AI 取数</v-tab>
|
|
</v-tabs>
|
|
</v-tabs>
|
|
</div>
|
|
</div>
|
|
- <div class="chart-content-chat-box overflow-y-auto" ref="chatBox">
|
|
|
|
|
|
+ <div class="chart-content-chat-box overflow-y-auto position-relative" ref="chatBox">
|
|
<div class="pa-3">
|
|
<div class="pa-3">
|
|
<div
|
|
<div
|
|
v-for="(item, index) in items"
|
|
v-for="(item, index) in items"
|
|
@@ -15,8 +15,24 @@
|
|
<v-avatar color="indigo" size="36">
|
|
<v-avatar color="indigo" size="36">
|
|
<span class="white--text">{{ item.type === 1 ? 'AI' : 'T' }}</span>
|
|
<span class="white--text">{{ item.type === 1 ? 'AI' : 'T' }}</span>
|
|
</v-avatar>
|
|
</v-avatar>
|
|
- <div :class="[item.type === 1 ? 'ml-3' : 'mr-3 box-length-70']">
|
|
|
|
- <div :class="`text-${item.type === 1 ? 'left' : 'right'}`">{{ item.type === 1 ? 'AI助手' : '游客' }}</div>
|
|
|
|
|
|
+ <div :class="[item.type === 1 ? 'ml-3 flex-grow-1 flex-shrink-1' : 'mr-3 box-length-70']">
|
|
|
|
+ <div
|
|
|
|
+ :class="['d-flex align-center', `justify-${item.type === 1 ? 'start' : 'end'}`]"
|
|
|
|
+ >
|
|
|
|
+ {{ item.type === 1 ? 'AI助手' : '游客' }}
|
|
|
|
+ <template v-if="item.type === 1">
|
|
|
|
+ <v-btn
|
|
|
|
+ v-if="item.content?.sql"
|
|
|
|
+ class="ml-3"
|
|
|
|
+ small
|
|
|
|
+ elevation="0"
|
|
|
|
+ depressed
|
|
|
|
+ @click="item.showSnackbar = !item.showSnackbar"
|
|
|
|
+ >
|
|
|
|
+ {{ !item.showSnackbar ? '查看SQL' : '收起SQL'}}
|
|
|
|
+ </v-btn>
|
|
|
|
+ </template>
|
|
|
|
+ </div>
|
|
<div class="mt-2" :class="{ 'indigo lighten-5 pa-3 rounded': item.type !== 1 }">
|
|
<div class="mt-2" :class="{ 'indigo lighten-5 pa-3 rounded': item.type !== 1 }">
|
|
<template v-if="typeof item.content === 'string'">
|
|
<template v-if="typeof item.content === 'string'">
|
|
{{ item.content }}
|
|
{{ item.content }}
|
|
@@ -35,27 +51,115 @@
|
|
</template>
|
|
</template>
|
|
<template v-else>
|
|
<template v-else>
|
|
<div>
|
|
<div>
|
|
|
|
+ {{ item.content.summary }}
|
|
|
|
+ </div>
|
|
|
|
+ <div v-if="item.showSnackbar" class="pa-3 blue-grey lighten-3">
|
|
{{ item.content.sql }}
|
|
{{ item.content.sql }}
|
|
</div>
|
|
</div>
|
|
- <div class="mt-3">
|
|
|
|
- <!-- <m-table
|
|
|
|
- clearHeader
|
|
|
|
- size="small"
|
|
|
|
- shadow="never"
|
|
|
|
- :headers="item.content.columns"
|
|
|
|
- :items="item.content.rows"
|
|
|
|
- ></m-table>
|
|
|
|
- <el-popover
|
|
|
|
- placement="bottom"
|
|
|
|
- width="200"
|
|
|
|
- trigger="click"
|
|
|
|
- >
|
|
|
|
- <m-form :ref="`form${i}`" label-width="60px" :items="formItems(item.content.columns)" v-model="item.model"></m-form>
|
|
|
|
- <div style="text-align: right; margin: 0">
|
|
|
|
- <m-button type="primary" size="mini" @click="onRender($refs[`form${i}`], item)">生成图表</m-button>
|
|
|
|
|
|
+ <div class="mt-3" v-if="item.content.columns.length">
|
|
|
|
+ <div>
|
|
|
|
+ <v-menu
|
|
|
|
+ :close-on-content-click="false"
|
|
|
|
+ max-width="300"
|
|
|
|
+ attach=".chart-content-chat-box"
|
|
|
|
+ >
|
|
|
|
+ <template v-slot:activator="{ on, attrs }">
|
|
|
|
+ <v-btn
|
|
|
|
+ v-bind="attrs"
|
|
|
|
+ v-on="on"
|
|
|
|
+ text
|
|
|
|
+ color="primary"
|
|
|
|
+ >我要画图</v-btn>
|
|
|
|
+ </template>
|
|
|
|
+ <div class="white">
|
|
|
|
+ <v-banner>画图配置</v-banner>
|
|
|
|
+ <div class="pa-3">
|
|
|
|
+ <v-autocomplete
|
|
|
|
+ v-model="item.model.typeAxis"
|
|
|
|
+ :items="item.content.columns"
|
|
|
|
+ class="mb-3"
|
|
|
|
+ outlined
|
|
|
|
+ dense
|
|
|
|
+ hide-details
|
|
|
|
+ label="类型轴"
|
|
|
|
+ ></v-autocomplete>
|
|
|
|
+
|
|
|
|
+ <v-autocomplete
|
|
|
|
+ v-model="item.model.dataAxis"
|
|
|
|
+ :items="item.content.columns"
|
|
|
|
+ class="mb-3"
|
|
|
|
+ outlined
|
|
|
|
+ dense
|
|
|
|
+ hide-details
|
|
|
|
+ label="数据轴"
|
|
|
|
+ multiple
|
|
|
|
+ chips
|
|
|
|
+ small-chips
|
|
|
|
+ ></v-autocomplete>
|
|
|
|
+ <div class="text-right">
|
|
|
|
+ <v-btn small color="primary" @click="onRender(item)">图表预览</v-btn>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </v-menu>
|
|
|
|
+ </div>
|
|
|
|
+ <v-card flat outlined height="324">
|
|
|
|
+ <div class="pa-3">
|
|
|
|
+ <v-simple-table
|
|
|
|
+ fixed-header
|
|
|
|
+ dense
|
|
|
|
+ height="300px"
|
|
|
|
+ >
|
|
|
|
+ <template v-slot:default>
|
|
|
|
+ <thead>
|
|
|
|
+ <tr>
|
|
|
|
+ <th
|
|
|
|
+ v-for="header in item.content.columns"
|
|
|
|
+ :key="header"
|
|
|
|
+ class="text-left"
|
|
|
|
+ >{{ header }}</th>
|
|
|
|
+ </tr>
|
|
|
|
+ </thead>
|
|
|
|
+ <tbody>
|
|
|
|
+ <tr
|
|
|
|
+ v-for="(row, index) in item.content.rows"
|
|
|
|
+ :key="index"
|
|
|
|
+ >
|
|
|
|
+ <td
|
|
|
|
+ v-for="header in item.content.columns"
|
|
|
|
+ :key="header"
|
|
|
|
+ class="text-left"
|
|
|
|
+ >{{ row[header] }}</td>
|
|
|
|
+ </tr>
|
|
|
|
+ </tbody>
|
|
|
|
+ </template>
|
|
|
|
+ </v-simple-table>
|
|
</div>
|
|
</div>
|
|
- <m-button type="primary" text slot="reference">我要作图</m-button>
|
|
|
|
- </el-popover> -->
|
|
|
|
|
|
+ </v-card>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="d-flex align-center" v-if="!item.dataValidation">
|
|
|
|
+ 您认为结果是否正确
|
|
|
|
+ <v-btn
|
|
|
|
+ class="ma-2"
|
|
|
|
+ text
|
|
|
|
+ icon
|
|
|
|
+ small
|
|
|
|
+ color="blue lighten-2"
|
|
|
|
+ @click="onAddTrain(item, true)"
|
|
|
|
+ >
|
|
|
|
+ <v-icon>mdi-thumb-up</v-icon>
|
|
|
|
+ </v-btn>
|
|
|
|
+
|
|
|
|
+ <v-btn
|
|
|
|
+ class="ma-2"
|
|
|
|
+ text
|
|
|
|
+ icon
|
|
|
|
+ small
|
|
|
|
+ color="red lighten-2"
|
|
|
|
+ @click="onAddTrain(item, false)"
|
|
|
|
+ >
|
|
|
|
+ <v-icon>mdi-thumb-down</v-icon>
|
|
|
|
+ </v-btn>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
@@ -78,35 +182,70 @@
|
|
@keydown.enter="handleKeyCode($event)"
|
|
@keydown.enter="handleKeyCode($event)"
|
|
>
|
|
>
|
|
</v-textarea>
|
|
</v-textarea>
|
|
- <v-btn icon color="primary" class="btn" :disabled="!question" @click="handleSendMsg">
|
|
|
|
|
|
+ <v-btn icon color="primary" class="btn" :disabled="!question || disabled" @click="handleSendMsg">
|
|
<v-icon>mdi-send</v-icon>
|
|
<v-icon>mdi-send</v-icon>
|
|
</v-btn>
|
|
</v-btn>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
+ <!-- <v-dialog
|
|
|
|
+ v-model="show"
|
|
|
|
+ persistent
|
|
|
|
+ max-width="290"
|
|
|
|
+ >
|
|
|
|
+ <v-card>
|
|
|
|
+ <v-card-title class="text-h5">
|
|
|
|
+ 提示
|
|
|
|
+ </v-card-title>
|
|
|
|
+ <v-card-text>是否添加到训练集</v-card-text>
|
|
|
|
+ <v-card-actions>
|
|
|
|
+ <v-spacer></v-spacer>
|
|
|
|
+ <v-btn
|
|
|
|
+ text
|
|
|
|
+ @click="show = false"
|
|
|
|
+ >
|
|
|
|
+ 取消
|
|
|
|
+ </v-btn>
|
|
|
|
+ <v-btn
|
|
|
|
+ color="error darken-1"
|
|
|
|
+ text
|
|
|
|
+ @click="onSure(false)"
|
|
|
|
+ >
|
|
|
|
+ 不添加
|
|
|
|
+ </v-btn>
|
|
|
|
+ <v-btn
|
|
|
|
+ color="green darken-1"
|
|
|
|
+ text
|
|
|
|
+ @click="onSure(true)"
|
|
|
|
+ >
|
|
|
|
+ 添加
|
|
|
|
+ </v-btn>
|
|
|
|
+ </v-card-actions>
|
|
|
|
+ </v-card>
|
|
|
|
+ </v-dialog> -->
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script>
|
|
<script>
|
|
import {
|
|
import {
|
|
- getAsk
|
|
|
|
|
|
+ getAsk,
|
|
|
|
+ submitTrainingCorrect,
|
|
|
|
+ submitTrainingError
|
|
} from '@/api/dataChart'
|
|
} from '@/api/dataChart'
|
|
export default {
|
|
export default {
|
|
name: 'dataChartEditChat',
|
|
name: 'dataChartEditChat',
|
|
data () {
|
|
data () {
|
|
return {
|
|
return {
|
|
- icons: 1,
|
|
|
|
|
|
+ // show: false,
|
|
|
|
+ disabled: false,
|
|
question: '',
|
|
question: '',
|
|
items: [
|
|
items: [
|
|
{
|
|
{
|
|
type: 1,
|
|
type: 1,
|
|
content: '您好,我是AI助手,请问有什么可以帮助您的吗?'
|
|
content: '您好,我是AI助手,请问有什么可以帮助您的吗?'
|
|
- },
|
|
|
|
- {
|
|
|
|
- type: 2,
|
|
|
|
- content: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sed deserunt explicabo corrupti iure nesciunt autem quaerat unde, fugit, ipsa magni consequatur beatae vero ea culpa nisi aliquid aliquam consectetur aut.'
|
|
|
|
}
|
|
}
|
|
]
|
|
]
|
|
|
|
+ // trueData: false
|
|
}
|
|
}
|
|
},
|
|
},
|
|
methods: {
|
|
methods: {
|
|
@@ -121,40 +260,75 @@ export default {
|
|
}
|
|
}
|
|
},
|
|
},
|
|
async handleSendMsg () {
|
|
async handleSendMsg () {
|
|
- if (!this.question) {
|
|
|
|
|
|
+ if (!this.question || this.disabled) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+ this.disabled = true
|
|
|
|
+
|
|
|
|
+ const question = this.question
|
|
this.items.push({
|
|
this.items.push({
|
|
type: 2,
|
|
type: 2,
|
|
user: '游客',
|
|
user: '游客',
|
|
- content: this.question
|
|
|
|
|
|
+ content: question
|
|
})
|
|
})
|
|
|
|
+ this.scrollToBottom()
|
|
const ask = {
|
|
const ask = {
|
|
type: 1,
|
|
type: 1,
|
|
content: {},
|
|
content: {},
|
|
|
|
+ showSnackbar: false,
|
|
|
|
+ dataValidation: false,
|
|
|
|
+ question, // 记录当前问题
|
|
|
|
+ showMenu: false,
|
|
model: {
|
|
model: {
|
|
dataAxis: null,
|
|
dataAxis: null,
|
|
typeAxis: null
|
|
typeAxis: null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.items.push(ask)
|
|
this.items.push(ask)
|
|
|
|
+ this.question = ''
|
|
try {
|
|
try {
|
|
- const { data } = await getAsk({
|
|
|
|
- question: this.question
|
|
|
|
- })
|
|
|
|
- this.question = ''
|
|
|
|
- const { columns, ...obj } = data
|
|
|
|
- ask.content = {
|
|
|
|
- ...obj,
|
|
|
|
- columns: columns.map(e => ({ label: e, prop: e, value: e }))
|
|
|
|
|
|
+ const { data } = await getAsk({ question })
|
|
|
|
+ ask.content = data
|
|
|
|
+ this.scrollToBottom()
|
|
|
|
+ } catch (error) {
|
|
|
|
+ ask.content = error.message
|
|
|
|
+ } finally {
|
|
|
|
+ this.disabled = false
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ scrollToBottom () {
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
+ const box = this.$refs.chatBox
|
|
|
|
+ if (!box) {
|
|
|
|
+ return
|
|
}
|
|
}
|
|
- this.$nextTick(() => {
|
|
|
|
- const box = this.$refs.chatBox
|
|
|
|
- box.scrollTop = box.scrollHeight
|
|
|
|
|
|
+ box.scrollTop = box.scrollHeight
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ async onAddTrain (item, bool) {
|
|
|
|
+ const subApi = bool ? submitTrainingCorrect : submitTrainingError
|
|
|
|
+ try {
|
|
|
|
+ await subApi({
|
|
|
|
+ question: item.question,
|
|
|
|
+ sql: item.content.sql
|
|
})
|
|
})
|
|
|
|
+ item.dataValidation = true
|
|
|
|
+ // this.$snackbar.success('操作成功')
|
|
} catch (error) {
|
|
} catch (error) {
|
|
this.$snackbar.error(error)
|
|
this.$snackbar.error(error)
|
|
}
|
|
}
|
|
|
|
+ },
|
|
|
|
+ onRender ({ model, content }) {
|
|
|
|
+ if (!model.dataAxis || !model.typeAxis) {
|
|
|
|
+ this.$snackbar.error('请选择数据轴和类型轴')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ const { typeAxis, dataAxis } = model
|
|
|
|
+ const data = {
|
|
|
|
+ type: typeAxis ? content.rows.map(e => e[typeAxis]) : [],
|
|
|
|
+ data: dataAxis ? dataAxis.map(e => content.rows.map(r => r[e])) : []
|
|
|
|
+ }
|
|
|
|
+ this.$emit('render', data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -207,4 +381,10 @@ export default {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+.position {
|
|
|
|
+ &-relative {
|
|
|
|
+ position: relative;
|
|
|
|
+ }
|
|
|
|
+}
|
|
</style>
|
|
</style>
|