Ver Fonte

新增:知识库下发及下发记录查询

范志成 há 4 meses atrás
pai
commit
aaa0a9c4cb
21 ficheiros alterados com 649 adições e 22 exclusões
  1. 11 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDifyDatasetsController.java
  2. 10 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDifyDatasetsQuestionController.java
  3. 56 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/SmsbDatasetsQuestionRel.java
  4. 39 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/bo/SmsbDatasetsQuestionRelBo.java
  5. 53 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDatasetsQuestionRelVo.java
  6. 15 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDatasetsQuestionRelMapper.java
  7. 13 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDifyDatasetsQuestionMapper.java
  8. 9 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDifyDatasetsQuestionService.java
  9. 7 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDifyDatasetsService.java
  10. 11 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDifyDatasetsQuestionServiceImpl.java
  11. 50 8
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDifyDatasetsServiceImpl.java
  12. 16 0
      smsb-modules/smsb-device/src/main/resources/mapper/device/SmsbDifyDatasetsQuestionMapper.xml
  13. 10 1
      smsb-plus-ui/src/api/smsb/device/datasets.ts
  14. 1 1
      smsb-plus-ui/src/api/smsb/device/datasets_push_record.ts
  15. 0 0
      smsb-plus-ui/src/api/smsb/device/datasets_push_record_types.ts
  16. 8 0
      smsb-plus-ui/src/api/smsb/device/difyDatasetsQuestion_index.ts
  17. 2 2
      smsb-plus-ui/src/views/smsb/datasets/product.vue
  18. 77 2
      smsb-plus-ui/src/views/smsb/datasets/question.vue
  19. 5 5
      smsb-plus-ui/src/views/smsb/datasetsPushRecord/product.vue
  20. 251 0
      smsb-plus-ui/src/views/smsb/datasetsPushRecord/question.vue
  21. 5 3
      smsb-plus-ui/src/views/smsb/difyDatasetsProduct/index.vue

+ 11 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDifyDatasetsController.java

@@ -97,6 +97,17 @@ public class SmsbDifyDatasetsController extends BaseController {
         return toAjax(smsbDifyDatasetsService.pushProduct(bo));
     }
 
+    /**
+     * 知识条目下发
+     */
+    @SaCheckPermission("device:datasets:edit")
+    @Log(title = "下发问答库", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PostMapping("/push/question")
+    public R<Void> pushQuestion(@RequestBody SmsbDifyDatasetsBo bo) throws IOException {
+        return toAjax(smsbDifyDatasetsService.pushQuestion(bo));
+    }
+
     /**
      * 修改知识库管理
      */

+ 10 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDifyDatasetsQuestionController.java

@@ -2,6 +2,7 @@ package com.inspur.device.controller;
 
 import java.util.List;
 
+import com.inspur.device.domain.vo.SmsbDifyDatasetsProductVo;
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
@@ -90,6 +91,15 @@ public class SmsbDifyDatasetsQuestionController extends BaseController {
         return toAjax(smsbDifyDatasetsQuestionService.updateByBo(bo));
     }
 
+    /**
+     * 查询知识库下发记录
+     */
+    @SaCheckPermission("device:difyDatasetsProduct:list")
+    @GetMapping("/listQuestionByRidAndDid/{recordId}/{datasetsId}")
+    public R<List<SmsbDifyDatasetsQuestionVo>> listProductByRidAndDid(@PathVariable("recordId") Long recordId, @PathVariable("datasetsId") Long datasetsId) {
+        return R.ok(smsbDifyDatasetsQuestionService.listQuestionByRidAndDid(recordId,datasetsId));
+    }
+
     /**
      * 删除Dify知识问答
      *

+ 56 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/SmsbDatasetsQuestionRel.java

@@ -0,0 +1,56 @@
+package com.inspur.device.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 问答库知识库关联对象 smsb_datasets_question_rel
+ *
+ * @author ZhiCheng Fan
+ * @date 2025-06-19
+ */
+@Data
+@TableName("smsb_datasets_question_rel")
+public class SmsbDatasetsQuestionRel {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 知识库ID
+     */
+    private Long datasetsId;
+
+    /**
+     * 问答库ID
+     */
+    private Long questionId;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /**
+     * 下发记录ID
+     */
+    private Long recordId;
+}

+ 39 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/bo/SmsbDatasetsQuestionRelBo.java

@@ -0,0 +1,39 @@
+package com.inspur.device.domain.bo;
+
+import com.inspur.device.domain.SmsbDatasetsQuestionRel;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+/**
+ * 产品库产品关联业务对象 smsb_datasets_question_rel
+ *
+ * @author Hao Li
+ * @date 2025-06-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SmsbDatasetsQuestionRel.class, reverseConvertGenerate = false)
+public class SmsbDatasetsQuestionRelBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 知识库ID
+     */
+    private Long datasetsId;
+
+    /**
+     * 问答库ID
+     */
+    private Long questionId;
+
+    /**
+     * 下发记录ID
+     */
+    private Long recordId;
+}

+ 53 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDatasetsQuestionRelVo.java

@@ -0,0 +1,53 @@
+package com.inspur.device.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.inspur.device.domain.SmsbDatasetsProductRel;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 产品库产品关联视图对象 smsb_datasets_product_rel
+ *
+ * @author ZhiCheng Fan
+ * @date 2025-06-19
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SmsbDatasetsProductRel.class)
+public class SmsbDatasetsQuestionRelVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 知识库ID
+     */
+    private Long datasetsId;
+
+    /**
+     * 问答库ID
+     */
+    private Long questionId;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 下发记录ID
+     */
+    private Long recordId;
+
+
+}

+ 15 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDatasetsQuestionRelMapper.java

@@ -0,0 +1,15 @@
+package com.inspur.device.mapper;
+
+import com.inspur.device.domain.SmsbDatasetsQuestionRel;
+import com.inspur.device.domain.vo.SmsbDatasetsQuestionRelVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 问答库知识库关联对象 smsb_datasets_question_rel
+ *
+ * @author ZhiCheng Fan
+ * @date 2025-06-19
+ */
+public interface SmsbDatasetsQuestionRelMapper extends BaseMapperPlus<SmsbDatasetsQuestionRel, SmsbDatasetsQuestionRelVo> {
+
+}

+ 13 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDifyDatasetsQuestionMapper.java

@@ -2,8 +2,11 @@ package com.inspur.device.mapper;
 
 import com.inspur.device.domain.SmsbDifyDatasetsQuestion;
 import com.inspur.device.domain.vo.SmsbDifyDatasetsQuestionVo;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
+import java.util.List;
+
 /**
  * Dify知识问答Mapper接口
  *
@@ -12,4 +15,14 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  */
 public interface SmsbDifyDatasetsQuestionMapper extends BaseMapperPlus<SmsbDifyDatasetsQuestion, SmsbDifyDatasetsQuestionVo> {
 
+    List<SmsbDifyDatasetsQuestionVo> selectVoListByIds(@Param("ids") List<String> ids);
+
+    /**
+     * 根据recordId+datasetsId 获取当前知识库下发的内容
+     *
+     * @param recordId
+     * @param datasetsId
+     * @return
+     */
+    List<SmsbDifyDatasetsQuestionVo> selectVoListByRIdAndDid(@Param("recordId") Long recordId, @Param("datasetsId") Long datasetsId);
 }

+ 9 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDifyDatasetsQuestionService.java

@@ -1,5 +1,6 @@
 package com.inspur.device.service;
 
+import com.inspur.device.domain.vo.SmsbDifyDatasetsProductVo;
 import com.inspur.device.domain.vo.SmsbDifyDatasetsQuestionVo;
 import com.inspur.device.domain.bo.SmsbDifyDatasetsQuestionBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -65,4 +66,12 @@ public interface ISmsbDifyDatasetsQuestionService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据知识库ID、下发记录ID 查询历史下发的问答内容
+     * @param datasetsId
+     * @param recordId
+     * @return
+     */
+    List<SmsbDifyDatasetsQuestionVo> listQuestionByRidAndDid(Long recordId, Long datasetsId);
 }

+ 7 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDifyDatasetsService.java

@@ -79,4 +79,11 @@ public interface ISmsbDifyDatasetsService {
      * @return
      */
     boolean pushProduct(SmsbDifyDatasetsBo bo) throws IOException;
+
+    /**
+     * 知识条目下发
+     * @param bo
+     * @return
+     */
+    boolean pushQuestion(SmsbDifyDatasetsBo bo) throws IOException;
 }

+ 11 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDifyDatasetsQuestionServiceImpl.java

@@ -131,4 +131,15 @@ public class SmsbDifyDatasetsQuestionServiceImpl implements ISmsbDifyDatasetsQue
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 根据知识库ID、下发记录ID 查询历史下发的问答内容
+     * @param datasetsId
+     * @param recordId
+     * @return
+     */
+    @Override
+    public List<SmsbDifyDatasetsQuestionVo> listQuestionByRidAndDid(Long recordId, Long datasetsId) {
+        return baseMapper.selectVoListByRIdAndDid(recordId,datasetsId);
+    }
 }

+ 50 - 8
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDifyDatasetsServiceImpl.java

@@ -9,15 +9,9 @@ import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.inspur.device.domain.SmsbDatasetsProductRel;
-import com.inspur.device.domain.SmsbDatasetsPushRecord;
-import com.inspur.device.domain.SmsbDifyDatasets;
-import com.inspur.device.domain.SmsbDifyDatasetsFile;
+import com.inspur.device.domain.*;
 import com.inspur.device.domain.bo.SmsbDifyDatasetsBo;
-import com.inspur.device.domain.vo.DifyDatasetsRspData;
-import com.inspur.device.domain.vo.SmsbDifyDatasetsFileVo;
-import com.inspur.device.domain.vo.SmsbDifyDatasetsProductVo;
-import com.inspur.device.domain.vo.SmsbDifyDatasetsVo;
+import com.inspur.device.domain.vo.*;
 import com.inspur.device.mapper.*;
 import com.inspur.device.service.ISmsbDifyDatasetsFileService;
 import com.inspur.device.service.ISmsbDifyDatasetsService;
@@ -64,9 +58,11 @@ public class SmsbDifyDatasetsServiceImpl implements ISmsbDifyDatasetsService {
     private final static String API_DATASETS_COMMON = "/v1/datasets";
     private final SmsbDifyDatasetsMapper baseMapper;
     private final SmsbDifyDatasetsProductMapper smsbDifyDatasetsProductMapper;
+    private final SmsbDifyDatasetsQuestionMapper smsbDifyDatasetsQuestionMapper;
     private final SmsbDifyDatasetsFileMapper smsbDifyDatasetsFileMapper;
     private final ISmsbDifyDatasetsFileService smsbDifyDatasetsFileService;
     private final SmsbDatasetsProductRelMapper smsbDatasetsProductRelMapper;
+    private final SmsbDatasetsQuestionRelMapper smsbDatasetsQuestionRelMapper;
     private final SmsbDatasetsPushRecordMapper smsbDatasetsPushRecordMapper;
 
 
@@ -171,6 +167,52 @@ public class SmsbDifyDatasetsServiceImpl implements ISmsbDifyDatasetsService {
         return uploadResult;
     }
 
+    @Override
+    public boolean pushQuestion(SmsbDifyDatasetsBo bo) throws IOException {
+        Long datasetsId = bo.getId();
+        SmsbDifyDatasetsVo datasetsVo = baseMapper.selectVoById(datasetsId);
+        List<String> questionIds = bo.getEntryIds();
+        if (CollectionUtils.isEmpty(questionIds)) {
+            throw new ServiceException("知识条目ID不能为空!");
+        }
+        List<SmsbDifyDatasetsQuestionVo> questionVoList = smsbDifyDatasetsQuestionMapper.selectVoListByIds(questionIds);
+        // 1 生成新的内容
+        StringBuffer sb = new StringBuffer();
+        List<SmsbDatasetsQuestionRel> datasetsQuestionRelList = new ArrayList<>();
+        for (SmsbDifyDatasetsQuestionVo questionVo : questionVoList) {
+            sb.append("###");
+            sb.append("问题:" + questionVo.getQuestion()).append("\n");
+            sb.append("回答:" + questionVo.getAnswer()).append("\n");
+//            sb.append("产品图片:" + questionVo.getImgUrl()).append("\n");
+            SmsbDatasetsQuestionRel datasetsQuestionRel = new SmsbDatasetsQuestionRel();
+            datasetsQuestionRel.setQuestionId(questionVo.getId());
+            datasetsQuestionRel.setDatasetsId(datasetsId);
+            datasetsQuestionRelList.add(datasetsQuestionRel);
+        }
+        // 2 查询当前产品库关联的文件
+        SmsbDifyDatasetsFileVo difyDatasetsFileVo = smsbDifyDatasetsFileMapper.selectVoOne(new LambdaQueryWrapper<SmsbDifyDatasetsFile>()
+            .eq(SmsbDifyDatasetsFile::getDatasetsId,datasetsId).last("limit 1"));
+        // 3 根据记录先删除dify平台的文件
+        if (null != difyDatasetsFileVo) {
+            List<Long> ids = new ArrayList<>();
+            ids.add(difyDatasetsFileVo.getId());
+            smsbDifyDatasetsFileService.deleteWithValidByIds(ids,false);
+        }
+        // 4 生成新的文件上传至dify平台
+        String filePath = createTempFile(sb.toString());
+        boolean uploadResult = upload2Dify(datasetsVo,filePath);
+        // 5 生成下发记录
+        SmsbDatasetsPushRecord pushRecord = createNewPushRecord(datasetsVo,1,questionVoList.size());
+        Long recordId = pushRecord.getId();
+        // 6 记录当前产品库关联的产品
+        datasetsQuestionRelList.forEach(rel -> {
+            rel.setRecordId(recordId);
+        });
+        smsbDatasetsQuestionRelMapper.insertBatch(datasetsQuestionRelList);
+
+        return uploadResult;
+    }
+
     private SmsbDatasetsPushRecord createNewPushRecord(SmsbDifyDatasetsVo datasetsVo,Integer datasetsType,Integer entryNum) {
         SmsbDatasetsPushRecord pushRecord = new SmsbDatasetsPushRecord();
         pushRecord.setDatasetsId(datasetsVo.getId());

+ 16 - 0
smsb-modules/smsb-device/src/main/resources/mapper/device/SmsbDifyDatasetsQuestionMapper.xml

@@ -3,5 +3,21 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.inspur.device.mapper.SmsbDifyDatasetsQuestionMapper">
+    <select id="selectVoListByIds" resultType="com.inspur.device.domain.vo.SmsbDifyDatasetsQuestionVo">
+        select * from smsb_dify_datasets_question where id in
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
 
+    <select id="selectVoListByRIdAndDid" resultType="com.inspur.device.domain.vo.SmsbDifyDatasetsQuestionVo">
+        SELECT
+            sddq.*
+        FROM
+            smsb_datasets_question_rel sdqr
+                LEFT JOIN smsb_dify_datasets_question sddq ON sdqr.question_id = sddq.id
+        WHERE
+            sdqr.datasets_id = #{datasetsId}
+          AND sdqr.record_id = #{recordId}
+    </select>
 </mapper>

+ 10 - 1
smsb-plus-ui/src/api/smsb/device/datasets.ts

@@ -1,7 +1,7 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
 import { DifyDatasetsVO, DifyDatasetsForm, DifyDatasetsQuery } from '@/api/smsb/device/datasets_type';
-import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct/types";
+import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct_types";
 
 /**
  * 查询知识库管理列表
@@ -54,6 +54,15 @@ export const pushProductEntry = (data: any) => {
     data: data
   });
 };
+
+export const pushQuestionEntry = (data: any) => {
+  return request({
+    url: '/smsb/device/datasets/push/question',
+    method: 'post',
+    data: data
+  });
+};
+
 /**
  * 修改知识库管理
  * @param data

+ 1 - 1
smsb-plus-ui/src/api/smsb/device/datasetsPushRecord/api.ts → smsb-plus-ui/src/api/smsb/device/datasets_push_record.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { DatasetsPushRecordVO, DatasetsPushRecordForm, DatasetsPushRecordQuery } from '@/api/smsb/device/datasetsPushRecord/types';
+import { DatasetsPushRecordVO, DatasetsPushRecordForm, DatasetsPushRecordQuery } from '@/api/smsb/device/datasets_push_record_types';
 
 /**
  * 查询知识库下发记录列表

+ 0 - 0
smsb-plus-ui/src/api/smsb/device/datasetsPushRecord/types.ts → smsb-plus-ui/src/api/smsb/device/datasets_push_record_types.ts


+ 8 - 0
smsb-plus-ui/src/api/smsb/device/difyDatasetsQuestion_index.ts

@@ -1,6 +1,7 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
 import { DifyDatasetsQuestionVO, DifyDatasetsQuestionForm, DifyDatasetsQuestionQuery } from '@/api/smsb/device/difyDatasetsQuestion_types';
+import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct_types";
 
 /**
  * 查询Dify知识问答列表
@@ -16,6 +17,13 @@ export const listDifyDatasetsQuestion = (query?: DifyDatasetsQuestionQuery): Axi
   });
 };
 
+export const listQuestionByRidAndDid= (recordId:string | number ,datasetsId: string | number): AxiosPromise<DifyDatasetsProductVO[]> => {
+  return request({
+    url: '/device/difyDatasetsQuestion/listQuestionByRidAndDid/' + recordId + "/" + datasetsId,
+    method: 'get'
+  });
+};
+
 /**
  * 查询Dify知识问答详细
  * @param id

+ 2 - 2
smsb-plus-ui/src/views/smsb/datasets/product.vue

@@ -155,8 +155,8 @@ import {
   updateDifyDatasets
 } from '@/api/smsb/device/datasets';
 import {DifyDatasetsForm, DifyDatasetsQuery, DifyDatasetsVO} from '@/api/smsb/device/datasets_type';
-import {listAllProduct, listProductByDatasetsId} from "@/api/smsb/device/difyDatasetsProduct";
-import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct/types";
+import {listAllProduct, listProductByDatasetsId} from "@/api/smsb/device/difyDatasetsProduct_index";
+import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct_types";
 
 const {proxy} = getCurrentInstance() as ComponentInternalInstance;
 

+ 77 - 2
smsb-plus-ui/src/views/smsb/datasets/question.vue

@@ -51,8 +51,12 @@
           <el-table-column label="APP数量" align="center" prop="appCount" width="80"/>
           <el-table-column label="创建时间" align="left" prop="createTime" width="160"/>
           <el-table-column label="更新时间" align="left" prop="updateTime" width="160"/>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100">
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
             <template #default="scope">
+              <el-tooltip content="下发" placement="top">
+                <el-button link type="primary" icon="Position" @click="handlePush(scope.row)"
+                           v-hasPermi="['device:datasets:edit']"></el-button>
+              </el-tooltip>
               <el-tooltip content="文件列表" placement="top">
                 <el-button link type="primary" icon="Document" @click="handleToFileList(scope.row)"
                            v-hasPermi="['device:datasetsFile:list']"></el-button>
@@ -86,6 +90,28 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 知识条目下发 -->
+    <el-dialog :title="pushDialog.title" v-model="pushDialog.visible" width="900px" append-to-body>
+      <div>
+        <el-transfer
+          v-model="selectedEntry"
+          filterable
+          :filter-method="filterEntry"
+          filter-placeholder="请输入知识条目名称"
+          :data="entryList"
+          :props="{ key: 'key', label: 'label' }"
+          :titles="['可选条目', '已选条目']"
+          :button-texts="['移除', '添加']"
+        />
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitEntry">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -94,10 +120,12 @@ import {
   addDifyDatasets,
   delDifyDatasets,
   getDifyDatasets,
-  listDifyDatasets, syncDifyDatasets,
+  listDifyDatasets, pushProductEntry, pushQuestionEntry, syncDifyDatasets,
   updateDifyDatasets
 } from '@/api/smsb/device/datasets';
 import {DifyDatasetsForm, DifyDatasetsQuery, DifyDatasetsVO} from '@/api/smsb/device/datasets_type';
+import {listAllProduct} from "@/api/smsb/device/difyDatasetsProduct_index";
+import {listAllQuestion, listDifyDatasetsQuestion} from "@/api/smsb/device/difyDatasetsQuestion_index";
 
 const {proxy} = getCurrentInstance() as ComponentInternalInstance;
 
@@ -109,6 +137,14 @@ const ids = ref<Array<string | number>>([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
+const entryList = ref([]);
+const selectedEntry = ref([]);
+const datasetsId = ref<number | string>();
+
+const pushDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
 
 const queryFormRef = ref<ElFormInstance>();
 const difyDatasetsFormRef = ref<ElFormInstance>();
@@ -215,6 +251,45 @@ const handleUpdate = async (row?: DifyDatasetsVO) => {
   dialog.title = "修改问答库";
 }
 
+/** 知识库下发 */
+const handlePush = async (row?: DifyDatasetsVO) => {
+  datasetsId.value = row.id;
+  console.log("datasetsId1 = " + datasetsId.value);
+
+  pushDialog.visible = true;
+  pushDialog.title = "知识条目下发";
+  const res = await listDifyDatasetsQuestion();
+  const rspList = res.rows || [];
+  // 清空现有数据
+  entryList.value = [];
+  selectedEntry.value = [];
+  // 使用map更简洁地转换数据格式
+  entryList.value = rspList.map(item => ({
+    key: item.id,
+    label: item.question,
+    disabled: false // 如果需要禁用某些项,可以在这里设置
+  }));
+}
+const filterEntry = (query, item) => {
+  if (!query) return true; // 没有查询条件时返回所有项
+  return item.label.toLowerCase().includes(query.toLowerCase());
+}
+const submitEntry = async () => {
+  buttonLoading.value = true;
+  console.log("datasetsId = " + datasetsId.value);
+  console.log("selectedEntry = " + selectedEntry.value)
+  if (selectedEntry.value.length < 1) {
+    proxy?.$modal.msgError("请至少选择一个知识条目!");
+  }
+  const pushQuestionParam = {
+    id: datasetsId.value,
+    entryIds: selectedEntry.value
+  }
+  await pushQuestionEntry(pushQuestionParam).finally(() => buttonLoading.value = false);
+  proxy?.$modal.msgSuccess("操作成功");
+  pushDialog.visible = false;
+}
+
 /** 跳转至文件列表页面  */
 const handleToFileList = (row?: DifyDatasetsVO) =>{
   proxy.$router.push('/device/datasetsFile?datasetsId=' + row.difyId);

+ 5 - 5
smsb-plus-ui/src/views/smsb/datasetsPushRecord/product.vue

@@ -96,14 +96,14 @@ import {
   getDatasetsPushRecord,
   listDatasetsPushRecord,
   updateDatasetsPushRecord
-} from '@/api/smsb/device/datasetsPushRecord/api';
+} from '@/api/smsb/device/datasets_push_record';
 import {
   DatasetsPushRecordForm,
   DatasetsPushRecordQuery,
   DatasetsPushRecordVO
-} from '@/api/smsb/device/datasetsPushRecord/types';
-import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct/types";
-import {listProductByRidAndDid} from "@/api/smsb/device/difyDatasetsProduct";
+} from '@/api/smsb/device/datasets_push_record_types';
+import {DifyDatasetsProductVO} from "@/api/smsb/device/difyDatasetsProduct_types";
+import {listProductByRidAndDid} from "@/api/smsb/device/difyDatasetsProduct_index";
 
 const {proxy} = getCurrentInstance() as ComponentInternalInstance;
 
@@ -140,7 +140,7 @@ const data = reactive<PageData<DatasetsPushRecordForm, DatasetsPushRecordQuery>>
     pageNum: 1,
     pageSize: 10,
     datasetsId: undefined,
-    datasetsType: undefined,
+    datasetsType: 2,
     datasetsName: undefined,
     createUser: undefined,
     params: {}

+ 251 - 0
smsb-plus-ui/src/views/smsb/datasetsPushRecord/question.vue

@@ -0,0 +1,251 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+                :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover" :style="{ marginTop: '10px', height: '60px' }">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="55px">
+            <el-form-item label="知识库" prop="datasetsName">
+              <el-input v-model="queryParams.datasetsName" placeholder="请输入知识库名称" clearable @keyup.enter="handleQuery"/>
+            </el-form-item>
+            <el-form-item label="下发人" prop="createUser">
+              <el-input v-model="queryParams.createUser" placeholder="请输入下发人名称" clearable @keyup.enter="handleQuery"/>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <!--      <template #header>
+              <el-row :gutter="10" class="mb8">
+                <el-col :span="1.5">
+                  <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['device:datasetsPushRecord:add']">新增</el-button>
+                </el-col>
+                <el-col :span="1.5">
+                  <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['device:datasetsPushRecord:edit']">修改</el-button>
+                </el-col>
+                <el-col :span="1.5">
+                  <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['device:datasetsPushRecord:remove']">删除</el-button>
+                </el-col>
+                <el-col :span="1.5">
+                  <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['device:datasetsPushRecord:export']">导出</el-button>
+                </el-col>
+                <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+              </el-row>
+            </template>-->
+      <div class="table-content">
+        <el-table v-loading="loading" :data="datasetsPushRecordList" @selection-change="handleSelectionChange">
+          <el-table-column label="" align="left" prop="" width="10"/>
+          <el-table-column label="ID" align="left" prop="id" v-if="true" width="180"/>
+          <el-table-column label="知识库 ID" align="left" prop="datasetsId" width="180"/>
+          <el-table-column label="知识库名称" align="left" prop="datasetsName"/>
+          <el-table-column label="条目数量" align="center" prop="entryNum" width="120"/>
+          <el-table-column label="创建人名称" align="center" prop="createUser" width="120"/>
+          <el-table-column label="创建时间" align="left" prop="createTime" width="180">
+          </el-table-column>
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100">
+            <template #default="scope">
+              <el-tooltip content="知识条目" placement="top">
+                <el-button link type="primary" icon="View" @click="handleEntryList(scope.row)"
+                           v-hasPermi="['device:datasets:query']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
+                           v-hasPermi="['device:datasetsPushRecord:remove']"></el-button>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                  v-model:limit="queryParams.pageSize" @pagination="getList"/>
+    </el-card>
+    <!-- 下发详情 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="1200px" append-to-body>
+      <div class="table-content">
+        <el-table v-loading="entryLoading" :data="questionList">
+          <el-table-column label="问题" align="left" prop="question" width="250" :show-overflow-tooltip="true"/>
+          <el-table-column label="回答" align="left" prop="answer" :show-overflow-tooltip="true"/>
+          <el-table-column label="创建人" align="left" prop="createUser" width="100" :show-overflow-tooltip="true"/>
+        </el-table>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="DatasetsPushRecord" lang="ts">
+import {
+  addDatasetsPushRecord,
+  delDatasetsPushRecord,
+  getDatasetsPushRecord,
+  listDatasetsPushRecord,
+  updateDatasetsPushRecord
+} from '@/api/smsb/device/datasets_push_record';
+import {
+  DatasetsPushRecordForm,
+  DatasetsPushRecordQuery,
+  DatasetsPushRecordVO
+} from '@/api/smsb/device/datasets_push_record_types';
+import {DifyDatasetsQuestionVO} from "@/api/smsb/device/difyDatasetsQuestion_types";
+import {listQuestionByRidAndDid} from "@/api/smsb/device/difyDatasetsQuestion_index";
+
+const {proxy} = getCurrentInstance() as ComponentInternalInstance;
+
+const datasetsPushRecordList = ref<DatasetsPushRecordVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const entryLoading = ref(true);
+const questionList = ref<DifyDatasetsQuestionVO>([]);
+
+const queryFormRef = ref<ElFormInstance>();
+const datasetsPushRecordFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: DatasetsPushRecordForm = {
+  id: undefined,
+  datasetsId: undefined,
+  datasetsType: undefined,
+  datasetsName: undefined,
+  entryNum: undefined,
+  createUser: undefined,
+}
+const data = reactive<PageData<DatasetsPushRecordForm, DatasetsPushRecordQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    datasetsId: undefined,
+    datasetsType: 1,
+    datasetsName: undefined,
+    createUser: undefined,
+    params: {}
+  },
+  rules: {}
+});
+
+const {queryParams, form, rules} = toRefs(data);
+
+/** 查询知识库下发记录列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listDatasetsPushRecord(queryParams.value);
+  datasetsPushRecordList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  datasetsPushRecordFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: DatasetsPushRecordVO[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加知识库下发记录";
+}
+const handleEntryList = async (row?: DatasetsPushRecordVO) => {
+  entryLoading.value = true;
+  dialog.visible = true;
+  dialog.title = '知识条目';
+  const datasetsId = row.datasetsId;
+  const recordId = row.id;
+  const res = await listQuestionByRidAndDid(recordId,datasetsId);
+  questionList.value = res.data;
+  entryLoading.value = false;
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: DatasetsPushRecordVO) => {
+  reset();
+  const _id = row?.id || ids.value[0]
+  const res = await getDatasetsPushRecord(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改知识库下发记录";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  datasetsPushRecordFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateDatasetsPushRecord(form.value).finally(() => buttonLoading.value = false);
+      } else {
+        await addDatasetsPushRecord(form.value).finally(() => buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: DatasetsPushRecordVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除知识库下发记录编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await delDatasetsPushRecord(_ids);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('device/datasetsPushRecord/export', {
+    ...queryParams.value
+  }, `datasetsPushRecord_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 5 - 3
smsb-plus-ui/src/views/smsb/difyDatasetsProduct/index.vue

@@ -87,7 +87,7 @@
                              :max="100000" style="width: 180px" :maxlength="6"/>
           </el-form-item>
         </el-row>
-        <el-form-item label="产品关键词" prop="keyword">
+        <el-form-item label="关键词" prop="keyword">
           <el-input v-model="form.keyword" placeholder="请输入关键词"/>
         </el-form-item>
         <el-form-item label="产品属性" prop="property">
@@ -97,13 +97,15 @@
           </el-select>
         </el-form-item>
         <el-form-item label="产品价格" prop="price">
-          <el-input v-model="form.price" placeholder="请输入价格"/>
+<!--          -->
+          <el-input-number v-model="form.price" placeholder="请输入价格" controls-position="right" style="width: 300px"/>
+<!--          <el-input v-model="form.price" placeholder="请输入价格"/>-->
         </el-form-item>
         <el-form-item label="产品图片" prop="imgUrl">
           <component-upload-image :limit=1 v-model="form.imgUrl"></component-upload-image>
         </el-form-item>
         <el-form-item label="产品视频" prop="videoUrl">
-          <component-upload-image :limit=1 v-model="form.videoUrl"></component-upload-image>
+          <el-input v-model="form.videoUrl" placeholder="请输入视频地址"/>
         </el-form-item>
         <el-form-item label="产品简介" prop="note">
           <el-input v-model="form.note" type="textarea" :rows="5" placeholder="请输入内容" :maxlength="500"/>