zhengnaiwen_citu 1 settimana fa
parent
commit
f43071492f

+ 20 - 0
src/api/dataChart.js

@@ -75,3 +75,23 @@ export function getEmbeddingStats () {
 export function clearEmbeddingCache () {
   return http.post('/vanna/v0/embedding_cache_cleanup')
 }
+
+// 获取训练数据统计信息
+export function getTrainingData () {
+  return http.get('/vanna/v0/training_data/stats')
+}
+
+// 分页查询训练数据,支持筛选和搜索
+export function getTrainingDataList (param) {
+  return http.post('/vanna/v0/training_data/query', param)
+}
+
+// 创建训练数据,支持单条和批量操作
+export function createTrainingData (param) {
+  return http.post('/vanna/v0/training_data/create', param)
+}
+
+// 删除训练数据,支持批量操作
+export function deleteTrainingData (param) {
+  return http.post('/vanna/v0/training_data/delete', param)
+}

+ 23 - 0
src/router/routes.js

@@ -2453,6 +2453,29 @@ export default {
           },
           name: 'model-history',
           alwaysShow: 0
+        },
+        {
+          hidden: 0,
+          icon: '',
+          type: 1,
+          title: '训练数据管理',
+          path: '/model-system/model-train',
+          children: [],
+          enName: 'model-train',
+          redirect: '',
+          active: '',
+          label: '训练数据管理',
+          sort: 10,
+          component: 'modelSystem/modelTrain',
+          meta: {
+            roles: [],
+            enName: 'model-train',
+            icon: '',
+            title: '训练数据管理',
+            fullScreen: false
+          },
+          name: 'model-train',
+          alwaysShow: 0
         }
       ],
       name: 'model-system',

+ 6 - 2
src/views/modelSystem/modelHistory/index.vue

@@ -9,6 +9,7 @@
             color="primary"
             @click="getConversationsStatus"
           >
+            <v-icon left>mdi-refresh</v-icon>
             刷新
           </v-btn>
           <v-btn
@@ -16,6 +17,7 @@
             color="deep-purple accent-4"
             @click="onClearRedisCache"
           >
+            <v-icon left>mdi-trash-can-outline</v-icon>
             清除对话缓存
           </v-btn>
         </template>
@@ -30,7 +32,7 @@
             <v-card
               class="pa-2"
               tile
-              height="200"
+              height="150"
             >
               <div class="d-flex align-center justify-center flex-column" style="height: 100%;">
                 <div class="text-h3" :class="`${elevation.textColor}--text`">{{ itemData[elevation.value] }}</div>
@@ -50,6 +52,7 @@
             color="primary"
             @click="getEmbeddingStats"
           >
+            <v-icon left>mdi-refresh</v-icon>
             刷新
           </v-btn>
           <v-btn
@@ -57,6 +60,7 @@
             color="deep-purple accent-4"
             @click="onClearEmbeddingCache"
           >
+            <v-icon left>mdi-trash-can-outline</v-icon>
             清除对话缓存
           </v-btn>
         </template>
@@ -71,7 +75,7 @@
             <v-card
               class="pa-2"
               tile
-              height="200"
+              height="150"
             >
               <div class="d-flex align-center justify-center flex-column" style="height: 100%;">
                 <div class="text-h3" :class="`${elevation.textColor}--text`">{{ embeddingData[elevation.value] }}</div>

+ 20 - 3
src/views/modelSystem/modelStatistics/index.vue

@@ -1,6 +1,19 @@
 <template>
   <div class="content white">
-    <v-container>
+    <v-banner class="mb-3">
+      反馈统计
+      <template v-slot:actions>
+        <v-btn
+          text
+          color="primary"
+          @click="init"
+        >
+          <v-icon left>mdi-refresh</v-icon>
+          刷新
+        </v-btn>
+      </template>
+    </v-banner>
+    <v-container v-loading="loading">
       <v-row >
         <v-col
           v-for="elevation in elevations"
@@ -10,7 +23,7 @@
           <v-card
             class="pa-2"
             tile
-            height="200"
+            height="150"
           >
             <div class="d-flex align-center justify-center flex-column" style="height: 100%;">
               <div class="text-h3" :class="`${elevation.textColor}--text`">{{ itemData[elevation.value] }}</div>
@@ -41,7 +54,8 @@ export default {
         { text: '正向反馈率(%)', value: 'positive_rate', textColor: 'primary' },
         { text: '训练覆盖率(%)', value: 'training_rate', textColor: 'primary' }
       ],
-      itemData: {}
+      itemData: {},
+      loading: false
     }
   },
   created () {
@@ -49,11 +63,14 @@ export default {
   },
   methods: {
     async init () {
+      this.loading = true
       try {
         const { data } = await getFeedbackStats()
         this.itemData = data
       } catch (error) {
         this.$snackbar.error(error)
+      } finally {
+        this.loading = false
       }
     }
   }

+ 242 - 0
src/views/modelSystem/modelTrain/index.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="content white">
+    <v-banner class="mb-3">
+      训练数据统计
+      <template v-slot:actions>
+        <v-btn
+          text
+          color="primary"
+          @click="getStatistics"
+        >
+          <v-icon left>mdi-refresh</v-icon>
+          刷新
+        </v-btn>
+        <!-- <v-btn
+          text
+          color="deep-purple accent-4"
+          @click="onClearRedisCache"
+        >
+          清除对话缓存
+        </v-btn> -->
+      </template>
+    </v-banner>
+    <v-container v-loading="loading">
+      <v-row >
+        <v-col
+          v-for="elevation in elevations"
+          :key="elevation.value"
+        >
+          <v-card
+            class="pa-2"
+            tile
+            height="150"
+          >
+            <div class="d-flex align-center justify-center flex-column" style="height: 100%;">
+              <div class="text-h3" :class="`${elevation.textColor}--text`">{{ itemData[elevation.value] }}</div>
+              <div>
+                {{ elevation.text }}
+                <span class="success--text">
+                  {{ itemData.type_percentages?.[elevation.value] ? `( ${itemData.type_percentages?.[elevation.value]}% )` : null }}
+                </span>
+              </div>
+            </div>
+          </v-card>
+        </v-col>
+      </v-row>
+
+    </v-container>
+    <v-divider class="my-3"></v-divider>
+    <div class="pa-3">
+      <MSearch class="mb-3" :option="filter" @search="handleSearch"></MSearch>
+      <MTable
+        :headers="headers"
+        :items="items"
+        :loading="tableLoading"
+        :total="total"
+        :page-info="pageInfo"
+        @edit="handleEdit"
+        @delete="handleDelete"
+        @pageHandleChange="pageHandleChange"
+        @sort="handleSort"
+        @add="handleAdd"
+      >
+        <template #question="{ item }">
+          <span style="max-width: 300px;" class="d-inline-block text-truncate">{{ item.question }}</span>
+        </template>
+        <template #content="{ item }">
+          <!-- <tooltip width="300" :text="item.content" /> -->
+          <span style="max-width: 300px;" class="d-inline-block text-truncate">{{ item.content }}</span>
+        </template>
+        <template #actions="{ item }">
+          <v-btn text color="primary" @click="handleDetails(item)">查看</v-btn>
+          <!-- <v-btn text color="primary" @click="handleEdit(item)">编辑</v-btn> -->
+          <v-btn text color="error" @click="handleDelete([item.id])">删除</v-btn>
+        </template>
+      </MTable>
+    </div>
+    <ModelTrainEdit ref="modelTrainEditRefs" @success="onOpen"></ModelTrainEdit>
+  </div>
+</template>
+
+<script>
+import {
+  getTrainingData,
+  getTrainingDataList,
+  deleteTrainingData
+} from '@/api/dataChart'
+import MTable from '@/components/List/table.vue'
+import MSearch from '@/components/Filter'
+import ModelTrainEdit from './modelTrainEdit.vue'
+export default {
+  name: 'ModelTrain',
+  components: {
+    MTable,
+    MSearch,
+    ModelTrainEdit
+  },
+  data () {
+    return {
+      searchValues: {},
+      filter: {
+        list: [
+          { type: 'textField', value: null, label: '关键词', key: 'search_keyword', placeholder: '请输入关键词' },
+          {
+            type: 'autocomplete',
+            key: 'training_data_type',
+            value: null,
+            label: '类型 *',
+            placeholder: '请选择类型',
+            items: [
+              { label: 'sql', value: 'sql' },
+              { label: 'documentation', value: 'documentation' },
+              { label: 'ddl', value: 'ddl' },
+              { label: 'error_sql', value: 'error_sql' }
+            ]
+          }
+        ]
+      },
+      loading: false,
+      elevations: [
+        { text: '总数据量', value: 'total_count', textColor: 'primary' },
+        { text: 'DDL类型', value: 'ddl', textColor: 'info' },
+        { text: 'SQL类型', value: 'sql', textColor: 'info' },
+        { text: '文档类型', value: 'documentation', textColor: 'info' },
+        { text: '错误SQL类型', value: 'error_sql', textColor: 'error' }
+      ],
+      itemData: {},
+      tableLoading: false,
+      headers: [
+        { text: 'ID', value: 'id' },
+        { text: '问题', value: 'question' },
+        { text: '类型', value: 'training_data_type' },
+        { text: '内容', value: 'content' },
+        { text: '操作', value: 'actions' }
+      ],
+      items: [],
+      total: 0,
+      orders: [],
+      pageInfo: {
+        size: 5,
+        current: 1
+      }
+    }
+  },
+  created () {
+    this.onOpen()
+  },
+  methods: {
+    onOpen () {
+      this.init()
+      this.getStatistics()
+    },
+    async getStatistics () {
+      this.loading = true
+      try {
+        const { data } = await getTrainingData()
+        this.itemData = {
+          ...data,
+          ...data.type_breakdown
+        }
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.loading = false
+      }
+    },
+    async init () {
+      this.tableLoading = true
+      const orders = {
+        sort_by: undefined,
+        sort_order: undefined
+      }
+      if (this.orders.length) {
+        console.log(this.orders[0])
+        Object.assign(orders, {
+          sort_by: this.orders[0].column.replace(/`/g, ''),
+          sort_order: this.orders[0].asc ? 'asc' : 'desc'
+        })
+      }
+      try {
+        const { data } = await getTrainingDataList({
+          page: this.pageInfo.current,
+          page_size: this.pageInfo.size,
+          ...this.searchValues,
+          ...orders
+        })
+        this.items = data.records
+        this.total = data.pagination.total
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.tableLoading = false
+      }
+    },
+    handleSearch (v) {
+      this.searchValues = v
+      this.pageInfo.current = 1
+      this.init()
+    },
+    handleAdd () {
+      this.$refs.modelTrainEditRefs.open()
+    },
+    handleDetails (item) {
+      this.$refs.modelTrainEditRefs.open(item, true)
+    },
+    pageHandleChange (index) {
+      this.pageInfo.current = index
+      this.init()
+    },
+    handleSort (v) {
+      this.orders = v
+      this.init()
+    },
+    handleEdit (item) {
+      this.$refs.modelTrainEditRefs.open(item)
+    },
+    handleDelete (ids) {
+      if (Array.isArray(ids) && !ids.length) {
+        this.$snackbar.warning('请选择要删除的数据')
+        return
+      }
+      this.$confirm('提示', '确定删除吗?').then(async () => {
+        try {
+          await deleteTrainingData({
+            ids,
+            confirm: true
+          })
+          this.$snackbar.success('删除成功')
+          this.onOpen()
+        } catch (error) {
+          this.$snackbar.error(error)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.content {
+  font-size: 16px;
+}
+</style>

+ 92 - 0
src/views/modelSystem/modelTrain/modelTrainEdit.vue

@@ -0,0 +1,92 @@
+<template>
+  <MDialog title="模型训练" width="600px" :visible.sync="visible" :footer="!readonly" @submit="onSubmit">
+    <MForm ref="form" :items="formItems" v-model="formValues"></MForm>
+  </MDialog>
+</template>
+
+<script>
+import MDialog from '@/components/Dialog'
+import MForm from '@/components/MForm'
+import {
+  createTrainingData
+} from '@/api/dataChart'
+export default {
+  name: 'modelTrainEdit',
+  components: {
+    MDialog,
+    MForm
+  },
+  data () {
+    return {
+      visible: false,
+      readonly: false,
+      formValues: {}
+    }
+  },
+  computed: {
+    formItems () {
+      return [
+        {
+          label: '提问内容 *',
+          key: 'question',
+          readonly: this.readonly,
+          type: 'textarea',
+          rules: [v => this.readonly || !!v || '请输入提问内容']
+        },
+        {
+          label: '数据类型',
+          key: 'training_data_type',
+          type: 'autocomplete',
+          readonly: this.readonly,
+          items: [
+            { label: 'sql', value: 'sql' },
+            { label: 'documentation', value: 'documentation' },
+            { label: 'ddl', value: 'ddl' },
+            { label: 'error_sql', value: 'error_sql' }
+          ],
+          rules: [v => this.readonly || !!v || '请选择数据类型']
+        },
+        {
+          label: '回答内容 *',
+          key: 'content',
+          readonly: this.readonly,
+          rows: 5,
+          type: 'textarea',
+          rules: [v => this.readonly || !!v || '请输入回答内容']
+        }
+      ]
+    }
+  },
+  methods: {
+    open (item, readonly = false) {
+      this.readonly = readonly
+      this.visible = true
+      if (!item) {
+        this.formValues = {}
+        this.$nextTick(() => {
+          this.$refs.form.resetValidation()
+        })
+        return
+      }
+      this.formValues = { ...item }
+      this.$refs.form.resetValidation()
+    },
+    async onSubmit () {
+      try {
+        await createTrainingData({
+          data: this.formValues
+        })
+        this.$snackbar.success('创建成功')
+        this.visible = false
+        this.$emit('success')
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>