Explorar o código

小世AI助手-第二阶段-外部理解技术交底书

zhuliu hai 5 meses
pai
achega
da5553565c

+ 3 - 1
src/api/index.js

@@ -25,6 +25,7 @@ import examine from "./newApi/examine";
 import IPREmail from "./newApi/IPREmail";
 import litigation from "./newApi/litigation";
 import reportAffair from "./newApi/reportAffair";
+import technicalDisclosure from './newApi/technicalDisclosure'
 
 export default {
   ...client,
@@ -50,5 +51,6 @@ export default {
   ...examine,
   ...IPREmail,
   ...litigation,
-  ...reportAffair
+  ...reportAffair,
+  ...technicalDisclosure
 }

+ 1 - 28
src/api/newApi/noveltySearch.js

@@ -122,32 +122,5 @@ export default {
     },
 
 
-    //上传技术交底书
-    addConfession(data){
-        return axios.post('/xiaoshi/noveltyProject/addConfession', data)
-    },
-    //查询技术交底书
-    queryConfession(params){
-        return axios.get('/xiaoshi/noveltyProject/queryConfession', {params})
-    },
-    //查询会话id
-    querySessionId(params){
-        return axios.get('/xiaoshi/dify/querySessionId', {params})
-    },
-    //查询历史会话
-    queryHistoryMessage(data){
-        return axios.post('/xiaoshi/dify/queryHistoryMessage', data)
-    },
-    //发送消息-技术交底书
-    chatMessage(data){
-        return axios.post('/xiaoshi/dify/chatMessage', data)
-    },
-    //停止响应
-    stopMessage(params){
-        return axios.get('/xiaoshi/dify/stopMessage', {params})
-    },
-    //生成发明点
-    generateInventionPoint(data){
-        return axios.post('/xiaoshi/dify/generateInventionPoint', data)
-    },
+   
 };

+ 48 - 0
src/api/newApi/technicalDisclosure.js

@@ -0,0 +1,48 @@
+import axios from "@/utils/axios";
+export default {
+     //上传技术交底书
+     addConfession(data){
+        return axios.post('/xiaoshi/noveltyProject/addConfession', data)
+    },
+    //查询技术交底书
+    queryConfession(params){
+        return axios.get('/xiaoshi/noveltyProject/queryConfession', {params})
+    },
+    //查询会话id
+    querySessionId(params){
+        return axios.get('/xiaoshi/dify/querySessionId', {params})
+    },
+    //查询历史会话
+    queryHistoryMessage(data){
+        return axios.post('/xiaoshi/dify/queryHistoryMessage', data)
+    },
+    //发送消息-技术交底书
+    chatMessage(data){
+        return axios.post('/xiaoshi/dify/chatMessage', data)
+    },
+    //停止响应
+    stopMessage(params){
+        return axios.get('/xiaoshi/dify/stopMessage', {params})
+    },
+    //生成发明点
+    generateInventionPoint(data){
+        return axios.post('/xiaoshi/dify/generateInventionPoint', data)
+    },
+
+    //生成技术交底书会话记录
+    addConfessionSession(data){
+        return axios.post('/xiaoshi/confessionSession/addConfessionSession', data)
+    },
+    //更新交底书会话记录
+    updateConfessionSession(data){
+        return axios.post('/xiaoshi/confessionSession/updateConfessionSession', data)
+    },
+    //查询交底书会话记录
+    queryConfessionSession(data){
+        return axios.post('/xiaoshi/confessionSession/queryConfessionSession', data)
+    },
+    //删除交底书会话记录
+    deleteConfessionSessions(data){
+        return axios.post('/xiaoshi/confessionSession/deleteConfessionSessions', data)
+    },
+}

+ 186 - 0
src/components/xiaoshi_AI/CodeBlock.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="code-block">
+    <div class="code-block-header">
+      <span class="language-label">{{ displayLanguage }}</span>
+      <button class="copy-button" @click="copyCode">
+        {{ copied ? '已复制' : '复制' }}
+      </button>
+    </div>
+    <pre><code :class="['hljs', language]" v-html="highlightedCode" ref="codeEl"></code></pre>
+  </div>
+</template>
+
+<script>
+import hljs from 'highlight.js';
+
+export default {
+  name: 'CodeBlock',
+  props: {
+    code: {
+      type: String,
+      required: true,
+      default: ''
+    },
+    language: {
+      type: String,
+      default: ''
+    }
+  },
+  
+  data() {
+    return {
+      copied: false
+    }
+  },
+
+  computed: {
+    displayLanguage() {
+      return this.language || '文本';
+    },
+
+    highlightedCode() {
+      if (this.language && hljs.getLanguage(this.language)) {
+        try {
+          return hljs.highlight(this.code, { language: this.language }).value;
+        } catch (e) {
+          console.error('代码高亮失败:', e);
+        }
+      }
+      // 如果没有指定语言或高亮失败,返回转义后的代码
+      return hljs.highlightAuto(this.code).value;
+    }
+  },
+
+  methods: {
+    async copyCode() {
+      try {
+        // await navigator.clipboard.writeText(this.code);
+        await this.copyToClipboard(this.code);
+        this.copied = true;
+        setTimeout(() => {
+          this.copied = false;
+        }, 2000);
+      } catch (err) {
+        console.error('复制失败:', err);
+        this.fallbackCopy();
+      }
+    },
+    async copyToClipboard(text) {
+      if (navigator.clipboard && window.isSecureContext) {
+        // 使用现代 Clipboard API
+        return navigator.clipboard.writeText(text);
+      } else {
+        // 使用传统方法
+        return this.fallbackCopy();
+      }
+    },
+
+    fallbackCopy() {
+      try {
+        // 创建临时文本区域
+        const textArea = document.createElement('textarea');
+        textArea.value = this.code;
+        
+        // 确保文本区域不可见
+        textArea.style.position = 'fixed';
+        textArea.style.left = '-999999px';
+        textArea.style.top = '-999999px';
+        document.body.appendChild(textArea);
+        
+        // 选择并复制文本
+        textArea.select();
+        document.execCommand('copy');
+        
+        // 清理
+        document.body.removeChild(textArea);
+        
+        // 更新状态
+        this.copied = true;
+        setTimeout(() => {
+          this.copied = false;
+        }, 2000);
+
+        return Promise.resolve();
+      } catch (err) {
+        return Promise.reject(err);
+      }
+    },
+
+    // 解码 HTML 实体
+    decodeHtml(html) {
+      const txt = document.createElement('textarea');
+      txt.innerHTML = html;
+      return txt.value;
+    }
+  },
+
+  created() {
+    // 解码传入的代码(如果包含 HTML 实体)
+    // this.code = this.decodeHtml(this.code);
+  }
+}
+</script>
+
+<style scoped>
+.code-block {
+  margin: 1em 0;
+  border-radius: 6px;
+  overflow: hidden;
+  background: #f6f8fa;
+  border: 1px solid #e1e4e8;
+}
+
+.code-block-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 16px;
+  background: #f1f1f1;
+  border-bottom: 1px solid #e1e4e8;
+}
+
+.language-label {
+  font-size: 12px;
+  color: #666;
+  text-transform: uppercase;
+}
+
+.copy-button {
+  padding: 4px 8px;
+  font-size: 12px;
+  color: #666;
+  background: transparent;
+  border: 1px solid #ddd;
+  border-radius: 3px;
+  cursor: pointer;
+  transition: all 0.2s;
+  position: relative;
+}
+
+.copy-button:hover {
+  background: #e9e9e9;
+}
+.copy-button.copied {
+  background: #28a745;
+  color: white;
+  border-color: #28a745;
+}
+
+.copy-button:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.code-block pre {
+  margin: 0;
+  padding: 16px;
+  overflow-x: auto;
+}
+
+.code-block code {
+  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+  font-size: 13px;
+  line-height: 1.45;
+  display: block;
+}
+</style> 

+ 91 - 4
src/components/xiaoshi_AI/index.vue

@@ -26,7 +26,14 @@
                     <div class="message_wrapper">
                         <div class="message">
                             <div v-for="(item,index) in formateAIAnswer(chat)" :key="index">
-                                <div :id="'answer_'+chat.id" v-if="item.type == 'text'" v-html="renderMarkdown(item.content)"></div>
+                                <div :id="'answer_'+chat.id" v-if="item.type == 'text'" >
+                                    <!-- v-html="renderMarkdown(item.content)" -->
+                                    <div v-for="(item1,inde) in parseMessage(item.content)" :key="`n_${inde}`">
+                                        <div v-if="item1.type == 'text'" v-html="renderMarkdown(item1.content)"></div>
+                                        <code-block v-if="item1.type == 'code'" :language="item1.language" :code="item1.content"></code-block>
+                                    </div>
+                                </div>
+                            
                                 <div v-if="item.type == 'think'" class="think" :id="'think_'+chat.id">
                                     <div>
                                         <span @click="changeShowThink(chat,item)">
@@ -35,7 +42,13 @@
                                         </span>
                                         think...
                                     </div>
-                                    <div class="thinkContent" v-show="item.show" v-html="renderMarkdown(item.content)"></div>
+                                    <div class="thinkContent" v-show="item.show">
+                                        <!-- v-html="renderMarkdown(item.content)" -->
+                                        <div v-for="(item1,inde) in parseMessage(item.content)" :key="`n_${inde}`">
+                                            <div v-if="item1.type == 'text'" v-html="renderMarkdown(item1.content)"></div>
+                                            <code-block v-if="item1.type == 'code'" :language="item1.language" :code="item1.content"></code-block>
+                                        </div>
+                                    </div>
                                 </div>
                             </div>
                         </div>
@@ -75,11 +88,15 @@
 </template>
 
 <script>
+let StartCode = false
 import 'highlight.js/styles/github.css'; // 或其他主题样式
 import 'github-markdown-css/github-markdown.css';
 import {renderMarkdown} from '@/utils/markdown'
+import CodeBlock from './CodeBlock.vue';
 export default {
-  components: {},
+  components: {
+    'code-block':CodeBlock
+  },
   props: {
     params:{
         type:Object,
@@ -126,6 +143,8 @@ export default {
         if(val){
             this.queryChatHistory.has_more = true
             this.getChatHistory(1)
+        }else{
+            this.chatHistory = []
         }
     }
   },
@@ -224,6 +243,7 @@ export default {
         if (event.keyCode == 13) {
             if (!event.ctrlKey) {
                 event.preventDefault();
+                event.stopPropagation();
                 this.sendMessage()
             } else {
                 this.userInput += "\n";
@@ -291,6 +311,7 @@ export default {
             conversationId:this.conversationId,
             ...this.params
         }
+        this.userInput = ''
         const response = await fetch('/api/xiaoshi/dify/chatMessage', {
           method: 'POST',
           headers: {
@@ -457,6 +478,71 @@ export default {
             })
         }
     },
+    //获取代码文本
+    parseMessage(text) {
+      const blocks = [];
+      
+      // 使用正则表达式匹配代码块
+      let regex = /```(\s*|\w*)\n([\s\S]+?)```/g;
+      let lastIndex = 0;
+      let match;
+      // console.log(regex.exec(text))
+      while ((match = regex.exec(text)) !== null) {
+        StartCode = false
+        // 添加代码块之前的文本
+        if (match.index > lastIndex) {
+          blocks.push({
+            type: 'text',
+            content: text.slice(lastIndex, match.index),
+            isComplete: false
+          });
+        }
+        
+        // 添加代码块
+        blocks.push({
+          type: 'code',
+          language: match[1] || '',
+          content: match[2].trim(),
+          isComplete: true  // 代码块总是完整显示
+        });
+        
+        lastIndex = regex.lastIndex;
+      }
+      
+      // 添加剩余的文本
+      if (lastIndex < text.length) {
+        let lastContent = text.slice(lastIndex)
+        let regex2 = /```(\w*)\n([\s\S]+)/g;
+        let lastResult
+        let index1 = 0
+        while((lastResult = regex2.exec(lastContent)) !== null){
+          StartCode = true
+          if(lastResult.index > index1){
+            blocks.push({
+              type: 'text',
+              content: lastContent.slice(index1, lastResult.index),
+              isComplete: false
+            });
+          }
+          blocks.push({
+            type: 'code',
+            language: lastResult[1] || '',
+            content: lastResult[2].trim(),
+            isComplete: true  // 代码块总是完整显示
+          });
+          index1 = regex2.lastIndex;
+        }
+        lastIndex = lastIndex + index1
+      }
+      if(lastIndex < text.length && !StartCode){
+        blocks.push({
+            type: 'text',
+            content: text.slice(lastIndex),
+            isComplete: false
+          });
+      }
+      return blocks;
+    },
     //格式化AI答复
     formateAIAnswer(chat){
         let answer = chat.answer
@@ -618,7 +704,8 @@ export default {
                 }
             }
             .message{
-                width:fit-content;
+                width: fit-content;
+                max-width: calc(100% - 1px);
                 border-radius: 1rem;
                 padding-top: .75rem;
                 padding-bottom: .75rem;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/icons/svg/ai.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 270 - 0
src/utils/model/AIHelper/index copy.vue


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 128 - 16
src/utils/model/AIHelper/index.vue


+ 65 - 0
src/views/noveltySearch/components/technicalDisclosure/dialog/renameConversation.vue

@@ -0,0 +1,65 @@
+<template>
+    <div class="responseDialog">
+      <el-dialog title="重命名会话" :visible.sync="showDialog" width="500px" :close-on-click-modal="false"  :before-close="handleClose" append-to-body>
+        
+        <div>
+            <p>会话名称</p>
+            <el-input v-model="form.conversationName" placeholder="请输入会话名称"></el-input>
+        </div>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="handleClose">取 消</el-button>
+          <el-button type="primary" @click="updateConfessionSession" >确 定</el-button>
+        </span>
+      </el-dialog>
+    </div>
+</template>
+  
+<script>
+  export default {
+    props:{},
+    data() {
+      return {
+        showDialog:false,
+        form:{}
+      }
+    },
+    watch: {
+    },
+    mounted() {
+  
+    },
+    methods: {
+        open(row){
+            this.form = JSON.parse(JSON.stringify(row))
+            this.showDialog = true
+        },
+        handleClose(){
+            this.showDialog = false
+        },
+        //更新技术交底书会话记录
+        updateConfessionSession(){
+            var params = {
+                confessionSessionId:this.form.id,
+                inventionPoint:this.form.inventionPoint,
+                conversationName:this.form.conversationName
+            }
+            this.$api.updateConfessionSession(params).then(response=>{
+                if(response.code == 200){
+                    this.$emit('rename',this.form)
+                    this.handleClose()
+                }
+            }).catch(error=>{
+                this.$message.error('更新失败')
+            })
+        },
+    },
+  }
+</script>
+  
+<style lang="scss">
+  .responseDialog{
+    .el-dialog__body{
+      padding-bottom: 0px;
+    }
+  }
+</style>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 497 - 36
src/views/noveltySearch/components/technicalDisclosure/technicalDisclosure.vue