Эх сурвалжийг харах

feat: 联调AI聊天功能接口

zhouyuhao 6 сар өмнө
parent
commit
77cd5d88a2

+ 61 - 1
src/api/module/AIRobot.ts

@@ -57,4 +57,64 @@ export const PromptAITest = (params: any, config: any) => {
     },
     config
   )
-}
+}
+
+/**
+ * 获取prompt提示器
+ */
+export const getPrompt = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot_chat',
+      operate: 'ai_chat_prompt',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * ai聊天
+ */
+export const aiChat = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot_chat',
+      operate: 'ai_chat',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 暂停ai聊天
+ */
+export const pauseAiChat = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot_chat',
+      operate: 'ai_chat_stop',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 评价ai聊天
+ */
+export const feedbackAiChat = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot_chat',
+      operate: 'ai_chat_answer_mark',
+      ...params
+    },
+    config
+  )
+}

+ 1 - 0
src/styles/reset.scss

@@ -137,6 +137,7 @@ div {
 
 .markdown-body {
   display: inline-block;
+  width: 100%;
   line-height: 1.6;
   font-size: 16px;
   color: #333;

+ 94 - 63
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -7,9 +7,11 @@ import userBubbleDark from './image/userBubbleDark.png'
 import robotBubbleLight from './image/robotBubbleLight.png'
 import robotBubbleDark from './image/robotBubbleDark.png'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useUserStore } from '@/stores/modules/user'
 import MarkdownIt from 'markdown-it'
 import 'github-markdown-css/github-markdown.css'
 
+const userStore = useUserStore()
 const md = new MarkdownIt({
   html: true,
   linkify: true,
@@ -18,6 +20,10 @@ const md = new MarkdownIt({
 })
 
 const renderedMessage = (content) => {
+  // console.log('content', content)
+  if (!content) {
+    return ''
+  }
   const normalized = content
     .replace(/\\n/g, '\n') // 粘贴带来的 \\n 处理
     .replace(/\r\n/g, '\n') // Windows 换行标准化
@@ -45,9 +51,10 @@ const isShowHeaderShadow = ref(false)
 
 const loadingAnswer = ref(false) // 是否正在加载答案
 interface MessageItem {
+  id?: string // 唯一标识
   type: 'robot' | 'user'
   content: string
-  feedback?: 'good' | 'noGood' | '' // 反馈结果
+  feedback?: 'Cood' | 'Not Good' | '' // 反馈结果
   isShowFeedback?: boolean // 是否展示反馈样式
   isAnswer?: boolean // 是否为用户问题的答案,是则才能展示反馈组件
   isError?: boolean // 是否为错误消息
@@ -58,23 +65,6 @@ const messages = ref<MessageItem[]>([
     type: 'robot',
     isShowFeedback: false,
     content: 'You can click on Frequently Asked Questions above or type your own question'
-  },
-  {
-    type: 'user',
-    content:
-      'Hi! I am your Freight Assistant. How can I help you?Of course! Please provide me with the details of your shipment.Of course! Please provide me with the details of your shipment.'
-  },
-  {
-    type: 'robot',
-    feedback: '',
-    isShowFeedback: false,
-    isAnswer: true,
-    content: 'Can you help me with my shipment?'
-  },
-  {
-    type: 'user',
-    content:
-      'Of course! Please provide me with the details of your shipment.Of course! Please provide me with the details of your shipment.'
   }
 ])
 messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
@@ -107,9 +97,51 @@ const progressStatus = {
 const isShowTips = ref(false) // 是否展示提示信息
 
 const progressInterval = ref()
-const handleSend = (question, isPresetQuestion = true) => {
-  if (!question) return
+const serial_no = ref()
+const aiChat = (question, isPresetQuestion) => {
+  serial_no.value = userStore.userInfo?.uname + Date.now().toString()
+  $api
+    .aiChat({
+      serial_no: serial_no.value,
+      prompt: sessionStorage.getItem('prompt'),
+      // question_type: isPresetQuestion ? 'Predefined Question' : 'Free Question',
+      question_type: 'Free Question',
+      question_content: question
+    })
+    .then((res) => {
+      if (isPause.value) {
+        return
+      }
+      if (res.code === 200) {
+        clearInterval(progressInterval.value)
+        const { data } = res.data
+        messages.value[messages.value.length - 1] = {
+          id: serial_no.value,
+          type: 'robot',
+          content: data,
+          feedback: '',
+          isShowFeedback: false,
+          isAnswer: true
+        }
 
+        scrollToBottom()
+        loadingAnswer.value = false
+        queryTime.value = -1
+      } else {
+        loadingAnswer.value = false
+        messages.value[messages.value.length - 1].isError = true
+      }
+    })
+}
+const handleSend = (question, isPresetQuestion = true, isExternal = false) => {
+  if (!question) return
+  if (
+    isExternal &&
+    messages.value.length === 1 &&
+    messages.value[0].content === progressStatus.init
+  ) {
+    messages.value.pop()
+  }
   if (loadingAnswer.value) {
     isShowTips.value = true
     setTimeout(() => {
@@ -117,8 +149,11 @@ const handleSend = (question, isPresetQuestion = true) => {
     }, 2000)
     return
   }
+  isPause.value = false
   loadingAnswer.value = true
 
+  aiChat(question, isPresetQuestion)
+  // 将用户内容添加到消息列表
   messages.value.push({
     type: 'user',
     content: question
@@ -181,45 +216,40 @@ function scrollToBottom() {
   })
 }
 
-const simulateStreamingMarkdown = () => {
-  loadingAnswer.value = true
-  const chunks: any = [userQuestion.value]
-
-  messages.value.push({
-    type: 'robot',
-    content: '',
-    isAnswer: true,
-    feedback: '',
-    isShowFeedback: false
-  })
-
-  let index = 0
-
-  autoScroll.value = true
-  scrollToBottom()
-
-  const interval = setInterval(() => {
-    index === chunks.length - 1 ? (loadingAnswer.value = false) : ''
-    if (index < chunks.length) {
-      messages.value[messages.value.length - 1].content += chunks[index]
-      index++
-
-      if (autoScroll.value) {
+const isPause = ref(false) // 是否暂停回答
+// 暂停回答
+const handlePause = () => {
+  $api
+    .pauseAiChat({
+      serial_no: serial_no.value
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        clearInterval(progressInterval.value)
+        queryTime.value = -1
+        messages.value[messages.value.length - 1].isCancel = true
+        messages.value[messages.value.length - 1].content = progressStatus.cancel
+        isPause.value = true
+        loadingAnswer.value = false
         scrollToBottom()
       }
-    } else {
-      clearInterval(interval)
-      userQuestion.value = ''
-    }
-  }, 200) // 每500ms追加一段
+    })
 }
 
-// 暂停回答
-const handlePause = () => {
-  clearInterval(progressInterval.value)
-  queryTime.value = -1
-  messages.value[messages.value.length - 1].isCancel = true
-  messages.value[messages.value.length - 1].content = progressStatus.cancel
+// 评价
+const handleFeedback = (index, feedback) => {
+  const message = messages.value[index]
+  message.feedback = feedback
+  $api
+    .feedbackAiChat({
+      serial_no: message.id,
+      feedback: feedback
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        messages.value[index].isShowFeedback = false
+      }
+    })
 }
 
 const emit = defineEmits(['close'])
@@ -289,33 +319,35 @@ defineExpose({
           src="./image/icon_loading.png"
           alt=""
         />
-        <div v-html="renderedMessage(msg.content)" class="markdown-body"></div>
+        <div style="display: inline-block">
+          <div v-html="renderedMessage(msg.content)" class="markdown-body"></div>
+        </div>
         <LoadingDots
           v-if="index === messages.length - 1 && msg.isAnswer && loadingAnswer"
         ></LoadingDots>
         <!-- 评价  -->
         <div class="review" v-if="msg.isShowFeedback && msg.isAnswer">
           <el-button
-            v-if="msg.feedback !== 'good'"
+            v-if="msg.feedback !== 'Cood'"
             class="el-button--text"
-            @click="msg.feedback = 'good'"
+            @click="handleFeedback(index, 'Cood')"
           >
             <span class="font_family icon-icon_good_b"></span>
           </el-button>
-          <div v-if="msg.feedback === 'good'" style="width: 16px; text-align: center">
+          <div v-if="msg.feedback === 'Cood'" style="width: 16px; text-align: center">
             <span
               style="color: var(--color-theme); font-size: 14px"
               class="font_family icon-icon_good__filled_b"
             ></span>
           </div>
           <el-button
-            v-if="msg.feedback !== 'noGood'"
+            v-if="msg.feedback !== 'Not Good'"
             class="el-button--text"
-            @click="msg.feedback = 'noGood'"
+            @click="handleFeedback(index, 'Not Good')"
           >
             <span class="font_family icon-icon_notgood_b"></span>
           </el-button>
-          <div v-if="msg.feedback === 'noGood'" style="width: 16px; text-align: center">
+          <div v-if="msg.feedback === 'Not Good'" style="width: 16px; text-align: center">
             <span
               style="color: var(--color-theme); font-size: 14px"
               class="font_family icon-icon_notgood__filled_b"
@@ -352,7 +384,6 @@ defineExpose({
           @focus="isFooterInputFocus = true"
           @blur="isFooterInputFocus = false"
         />
-        <el-button @click="simulateStreamingMarkdown">测试</el-button>
         <div
           class="input-icon"
           :class="[!userQuestion || queryTime !== -1 ? 'disable' : '']"

+ 13 - 4
src/views/Layout/src/LayoutView.vue

@@ -19,9 +19,7 @@ const isShowAIRobotChat = ref(false)
 const handleColseRobotChat = () => {
   isShowAIRobotChat.value = false
 }
-const onClick = () => {
-  isShowAIRobotChat.value = true
-}
+
 const AvatarClick = () => {
   isShowAIRobotChat.value = true
 }
@@ -32,9 +30,20 @@ const handelClickAIDefault = async (item: any) => {
   isShowAIRobotChat.value = true
   await nextTick()
   if (AIRobotChatref.value) {
-    AIRobotChatref.value.handleSend(item)
+    AIRobotChatref.value.handleSend(item, true, true)
   }
 }
+
+const getPrompt = () => {
+  $api.getPrompt().then((res) => {
+    if (res.code === 200) {
+      sessionStorage.setItem('prompt', JSON.stringify(res.data.prompt))
+    }
+  })
+}
+onMounted(() => {
+  getPrompt()
+})
 </script>
 <template>
   <el-container class="layout-container">