Explorar el Código

Merge branch 'dev' into dev_g

AmandaG hace 6 meses
padre
commit
b75270b449

+ 3 - 3
src/components/AIRobot/src/AIRobot.vue

@@ -14,7 +14,7 @@ const DeQuestions = ref([])
 const itemGroups = ref([])
 
 const AIRobotInit = () => {
-  $api.AIRobotInit({}).then((res:any) => {
+  $api.AIRobotInit({}).then((res: any) => {
     DeQuestions.value = res.data.fixed_question
     prepareGroups()
   })
@@ -235,7 +235,7 @@ defineExpose({
 }
 .AIRobot-top {
   position: absolute;
-  z-index: 2013;
+  z-index: 1999;
   right: 35px;
   bottom: 188px;
 }
@@ -277,7 +277,7 @@ defineExpose({
   background: var(--color-dialogue-icon-bg);
   position: absolute;
   box-shadow: -2px 2px 12px rgba(0, 0, 0, 15%);
-  z-index: 2013;
+  z-index: 1999;
   right: 10px;
   bottom: 130px;
   img {

+ 15 - 0
src/components/NotificationMessageCard/src/NotificationMessageCard.vue

@@ -6,6 +6,7 @@ import { useNotificationMessage } from '@/stores/modules/notificationMessage'
 import { cloneDeep } from 'lodash'
 import { DynamicScroller, DynamicScrollerItem } from 'vue3-virtual-scroller'
 import 'vue3-virtual-scroller/dist/vue3-virtual-scroller.css'
+import { formatTimezone } from '@/utils/tools'
 
 const notificationMsgStore = useNotificationMessage()
 const props = withDefaults(
@@ -15,6 +16,7 @@ const props = withDefaults(
     isScrollPadding?: boolean // 是否有padding
     updateReadCardsOnChange?: boolean // 是否在数据变化时请求接口更新已读卡片
     topOffset?: number // 应减去高度
+    isShowInsertionTime?: boolean // 是否显示插入时间
   }>(),
   {
     isObserver: true,
@@ -228,6 +230,9 @@ const scrollParentBoxStyle = computed(() => {
             :data="item.info"
           />
           <slot></slot>
+          <div class="insertion-time" v-if="isShowInsertionTime">
+            {{ formatTimezone(item.info.first_notifiation_date) }}
+          </div>
         </div>
       </DynamicScrollerItem>
     </template>
@@ -242,4 +247,14 @@ const scrollParentBoxStyle = computed(() => {
     padding: 0px 140px 0px 16px;
   }
 }
+.notification-message-card {
+  position: relative;
+  .insertion-time {
+    position: absolute;
+    right: 0;
+    top: 4px;
+    font-size: 12px;
+    color: var(--color-neutral-2);
+  }
+}
 </style>

+ 1 - 1
src/components/ScoringGrade/src/ScoringGrade.vue

@@ -412,7 +412,7 @@ div.scoring {
   background-color: var(--management-bg-color);
   position: absolute;
   box-shadow: -2px 2px 12px rgba(0, 0, 0, 15%);
-  z-index: 2013;
+  z-index: 1999;
   right: 10px;
   bottom: 64px;
   display: flex;

+ 1 - 1
src/components/VLoading/src/VLoading.vue

@@ -36,7 +36,7 @@ const props = withDefaults(defineProps<internalProps>(), {
 <style scoped>
 .v-loading-mask {
   position: absolute;
-  z-index: 2000;
+  z-index: 1499;
   margin: 0;
   top: 0;
   right: 0;

+ 0 - 9
src/styles/index.scss

@@ -56,12 +56,3 @@
 .icon_dark {
   fill: var(--color-neutral-1);
 }
-div.markdown-body {
-  background: transparent;
-  table {
-    th,
-    td {
-      white-space: nowrap;
-    }
-  }
-}

+ 38 - 4
src/styles/reset.scss

@@ -134,6 +134,23 @@ div {
     margin: 0;
   }
 }
+div.markdown-body {
+  background: transparent;
+  table {
+    th,
+    td {
+      white-space: nowrap;
+    }
+  }
+}
+
+.query-style {
+  .markdown-body {
+    p {
+      color: #b5b9bf;
+    }
+  }
+}
 
 .markdown-body {
   display: inline-block;
@@ -153,6 +170,7 @@ div {
   font-weight: bold;
 }
 .markdown-body p {
+  font-size: 14px;
   margin: 1em 0;
 }
 
@@ -162,12 +180,16 @@ div {
   padding-left: 1em;
 }
 
-.markdown-body ul {
+div.markdown-body ul {
   list-style-type: disc;
+  margin-left: 8px;
+  padding-left: 16px;
 }
 
 .markdown-body ol {
   list-style-type: decimal;
+  margin-left: 8px;
+  padding-left: 16px;
 }
 
 .markdown-body li {
@@ -181,9 +203,21 @@ div {
   margin: 1em 0;
 }
 
-.markdown-body a {
-  color: #0366d6;
-  text-decoration: underline;
+div.markdown-body a {
+  color: var(--color-theme);
+  text-decoration: none;
+  & + a {
+    margin-left: 6px;
+  }
+}
+
+.markdown-body {
+  table {
+    td,
+    th {
+      text-align: left;
+    }
+  }
 }
 
 .markdown-body code {

+ 140 - 45
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -20,7 +20,6 @@ const md = new MarkdownIt({
 })
 
 const renderedMessage = (content) => {
-  // console.log('content', content)
   if (!content) {
     return ''
   }
@@ -69,9 +68,9 @@ const messages = ref<MessageItem[]>([
   }
 ])
 messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
-onMounted(() => {
+const handleOpen = () => {
   scrollToBottom()
-})
+}
 
 watch(
   () => messages.value,
@@ -97,9 +96,14 @@ const progressStatus = {
 
 const isShowTips = ref(false) // 是否展示提示信息
 
+const isShowLoadingDots = ref(false) // 是否展示加载点
 const parseHtmlString = (data) => {
+  if (!data) {
+    isShowLoadingDots.value = false // 停止显示加载点
+    return
+  }
   const lines = data.split('\n')
-
+  isShowLoadingDots.value = true // 开始显示加载点
   function streamMarkdown() {
     const lastMsg: any = messages.value[messages.value.length - 1]
     let index = 0
@@ -111,6 +115,7 @@ const parseHtmlString = (data) => {
         index++
         scrollToBottom() // 滚动到底部
       } else {
+        isShowLoadingDots.value = false // 停止显示加载点
         clearInterval(timer)
       }
     }, 150)
@@ -121,22 +126,35 @@ const parseHtmlString = (data) => {
 
 const progressInterval = ref()
 const serial_no = ref()
+const is_FixedAnswer = ref(true) // 是否为预设问题 true是自由问题 false是预设问题
 const aiChat = (question, isPresetQuestion) => {
   serial_no.value = userStore.userInfo?.uname + Date.now().toString()
+  let fixed_faq = ''
+  if (!is_FixedAnswer.value) {
+    fixed_faq = messages.value[messages.value.length - 3].content
+  } else if (isPresetQuestion) {
+    fixed_faq = question
+  }
   $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
+      question_type:
+        isPresetQuestion || !is_FixedAnswer.value ? 'Predefined Question' : 'Free Question',
+      question_content: question,
+      fixed_faq: fixed_faq
     })
     .then((res) => {
-      if (isPause.value) {
+      if (isPause.value || queryTime.value === -1) {
         return
       }
       if (res.code === 200) {
         clearInterval(progressInterval.value)
+
+        is_FixedAnswer.value =
+          res.data.is_fixedAnswer_end !== null || res.data.is_fixedAnswer_end !== undefined
+            ? res.data.is_fixedAnswer_end
+            : true
         const { data } = res.data
         messages.value[messages.value.length - 1] = {
           id: serial_no.value,
@@ -154,6 +172,9 @@ const aiChat = (question, isPresetQuestion) => {
       } else {
         loadingAnswer.value = false
         messages.value[messages.value.length - 1].isError = true
+        messages.value[messages.value.length - 1].content = progressStatus[120]
+        clearInterval(progressInterval.value)
+        queryTime.value = -1
       }
     })
 }
@@ -175,15 +196,16 @@ const handleSend = (question, isPresetQuestion = true, isExternal = false) => {
   }
   isPause.value = false
   loadingAnswer.value = true
-
-  aiChat(question, isPresetQuestion)
   // 将用户内容添加到消息列表
   messages.value.push({
     type: 'user',
-    content: question
+    content: question,
+    isAnswer: true
   })
   !isPresetQuestion ? (userQuestion.value = '') : ''
   queryTime.value = 0
+
+  aiChat(question, isPresetQuestion)
   messages.value.push({
     type: 'robot',
     content: progressStatus[0]
@@ -214,6 +236,7 @@ const handleSend = (question, isPresetQuestion = true, isExternal = false) => {
     }
   }, 1000)
 }
+
 const showScrollButton = ref(false) // 控制按钮显示
 const messagesRef = ref()
 const autoScroll = ref(true)
@@ -232,7 +255,8 @@ function handleScroll() {
 
   isShowHeaderShadow.value = !!messagesRef.value?.scrollTop
 }
-function scrollToBottom() {
+const scrollToBottom = (isScroll = false) => {
+  if (!isScroll && (!autoScroll.value || !messagesRef.value)) return
   nextTick(() => {
     if (messagesRef.value) {
       messagesRef.value.scrollTop = messagesRef.value.scrollHeight
@@ -250,6 +274,7 @@ const handlePause = () => {
     .then((res) => {
       if (res.code === 200) {
         clearInterval(progressInterval.value)
+        is_FixedAnswer.value = true
         queryTime.value = -1
         messages.value[messages.value.length - 1].isCancel = true
         messages.value[messages.value.length - 1].content = progressStatus.cancel
@@ -279,12 +304,31 @@ const handleFeedback = (index, feedback) => {
 const emit = defineEmits(['close'])
 // 关闭聊天窗口
 const handleClose = () => {
-  progressInterval.value && clearInterval(progressInterval.value)
+  // progressInterval.value && clearInterval(progressInterval.value)
   emit('close')
 }
 
+const clearData = () => {
+  messages.value = [
+    {
+      type: 'robot',
+      isShowFeedback: false,
+      content: 'You can click on Frequently Asked Questions above or type your own question'
+    }
+  ]
+  progressInterval.value && clearInterval(progressInterval.value)
+  sessionStorage.removeItem('AIChat')
+}
+
+const liabilityExeDialog = ref(true) // 免责声明弹窗
+const handleLiabilityExeDialog = () => {
+  liabilityExeDialog.value = true
+}
+
 defineExpose({
-  handleSend
+  handleSend,
+  handleOpen,
+  clearData
 })
 </script>
 
@@ -294,17 +338,23 @@ defineExpose({
       <div class="header">
         <span class="welcome">Hi! I'm your Freight Assistant</span>
         <div class="option-icon">
-          <span
-            v-if="modalSize === 'large'"
-            class="font_family icon-icon_sidebar__window_b"
-            @click="modalSize = 'small'"
-          ></span>
-          <span
-            v-else-if="modalSize !== 'large'"
-            class="font_family icon-icon_maximized__window_b"
-            @click="modalSize = 'large'"
-          ></span>
-          <span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
+          <el-tooltip v-if="modalSize === 'large'" trigger="hover" content="Sidebar Window">
+            <span
+              class="font_family icon-icon_sidebar__window_b"
+              @click="modalSize = 'small'"
+            ></span>
+          </el-tooltip>
+
+          <el-tooltip v-else-if="modalSize !== 'large'" trigger="hover" content="Maximized Window"
+            ><span
+              class="font_family icon-icon_maximized__window_b"
+              @click="modalSize = 'large'"
+            ></span>
+          </el-tooltip>
+
+          <el-tooltip trigger="hover" content="Collapsed to Widget"
+            ><span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
+          </el-tooltip>
         </div>
       </div>
       <AIQuestions :modalSize="modalSize" @question="handleSend"></AIQuestions>
@@ -347,10 +397,15 @@ defineExpose({
           <div v-html="msg.html || renderedMessage(msg.content)" class="markdown-body"></div>
         </div>
         <LoadingDots
-          v-if="index === messages.length - 1 && msg.isAnswer && loadingAnswer"
+          v-if="
+            index === messages.length - 1 &&
+            msg.isAnswer &&
+            isShowLoadingDots &&
+            msg.type === 'robot'
+          "
         ></LoadingDots>
         <!-- 评价  -->
-        <div class="review" v-if="msg.isShowFeedback && msg.isAnswer">
+        <div class="review" v-if="msg.isShowFeedback && msg.isAnswer && msg.type === 'robot'">
           <el-button
             v-if="msg.feedback !== 'Cood'"
             class="el-button--text"
@@ -382,16 +437,18 @@ defineExpose({
         <img class="robot-bubble-img" v-if="msg.type === 'robot'" :src="robotBubbleImg" alt="" />
         <img class="user-bubble-img" v-else-if="msg.type === 'user'" :src="userBubbleImg" alt="" />
         <!-- 暂停回答 icon -->
-        <div
-          class="pause-btn"
+        <el-tooltip
           v-if="index === messages.length - 1 && queryTime > 29 && queryTime < 120"
-          @click="handlePause"
-        >
-          <div class="dot"></div>
-        </div>
+          content="Cancel Answer"
+          placement="bottom-start"
+          effect="dark"
+          ><div class="pause-btn" @click="handlePause">
+            <div class="dot"></div>
+          </div>
+        </el-tooltip>
       </div>
       <!-- 滚动到底部icon -->
-      <div v-if="showScrollButton" class="scroll-to-bottom-btn" @click="scrollToBottom">
+      <div v-if="showScrollButton" class="scroll-to-bottom-btn" @click="scrollToBottom(true)">
         <span class="font_family icon-icon_movedown_b"></span>
       </div>
     </div>
@@ -416,17 +473,41 @@ defineExpose({
           <span class="font_family icon-icon_send_b"></span>
         </div>
       </div>
+      <div class="liability-exemption">
+        <span> Content is generated by Al, please check carefully!</span>
+        <span class="liability-exemption-btn" @click="handleLiabilityExeDialog">Disclaimer</span>
+      </div>
     </div>
+    <el-dialog
+      class="liability-exemption-dialog"
+      v-model="liabilityExeDialog"
+      title="Disclaimer"
+      width="800"
+    >
+      <p style="line-height: 21px">
+        This feature generates automated answers summarised from FT articles. By using it, you are
+        interacting with an Al system (provided byAnthropic), not a human. Al can make mistakes.
+        Please refer to the source articles for verification and cifation purposes.
+      </p>
+      <p style="line-height: 21px">
+        Our staff do not check or moderate Ask FT is answers in advance, but may review some later.
+        Our journalists continue to write and edit all thearticles Ask FT uses as sources. Learn
+        more about our editorial principles for generative Al To helo us understand how you are
+        usina this feafure and how we can improve it, we use your data. This includes your query and
+        any personadata you have shared with us, like your job title. For more information about how
+        we use your data, please read our Privacy Policy.
+      </p>
+    </el-dialog>
   </div>
 </template>
 
 <style lang="scss" scoped>
 .ai-robot {
   position: absolute;
-  top: 74px;
+  top: 24px;
   right: 24px;
-  height: calc(100% - 98px);
-  z-index: 4000;
+  height: calc(100% - 48px);
+  z-index: 2000;
   display: flex;
   flex-direction: column;
   border-radius: 12px;
@@ -585,7 +666,7 @@ defineExpose({
         height: 16px;
         width: 16px;
         border-radius: 50%;
-        background-color: var(--color-customize-column-right-section-bg);
+        background-color: var(--color-pause-btn-bg);
         .dot {
           height: 5px;
           width: 5px;
@@ -594,11 +675,7 @@ defineExpose({
         }
       }
     }
-    .query-style {
-      span {
-        color: #b5b9bf;
-      }
-    }
+
     .robot-bubble {
       background: var(--scoring-bg-color);
       align-self: flex-start;
@@ -625,7 +702,7 @@ defineExpose({
       position: absolute;
       right: 50%;
       transform: translateX(50%);
-      bottom: 58px;
+      bottom: 76px;
       display: flex;
       justify-content: center;
       align-items: center;
@@ -635,7 +712,7 @@ defineExpose({
       // background-color: #f5f4f4;
       background: rgba(255, 255, 255, 0.6); /* 半透明背景色 */
       box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.3);
-      backdrop-filter: blur(1px); /* 应用10px的模糊效果 */
+      backdrop-filter: blur(1.7px); /* 应用10px的模糊效果 */
       span {
         color: #2b2f36;
       }
@@ -692,6 +769,18 @@ defineExpose({
       }
     }
   }
+  .liability-exemption {
+    margin-top: 8px;
+    font-size: 12px;
+    color: var(--color-neutral-3);
+    text-align: center;
+  }
+  .liability-exemption-btn {
+    margin-left: 2px;
+    text-decoration: underline;
+    color: var(--color-theme);
+    cursor: pointer;
+  }
 
   @keyframes loading-rotate {
     0% {
@@ -703,4 +792,10 @@ defineExpose({
     }
   }
 }
+:deep(.liability-exemption-dialog) {
+  padding-bottom: 0;
+  .el-dialog__body {
+    padding-top: 8px;
+  }
+}
 </style>

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

@@ -7,7 +7,9 @@ import ScoringGrade from '@/components/ScoringGrade'
 import AIRobot from '@/components/AIRobot'
 import AIRobotChat from '@/views/AIRobotChat/src/AIRobotChat.vue'
 import LogoMenu from './images/logo_menu.png'
+import { useUserStore } from '@/stores/modules/user'
 
+const userStore = useUserStore()
 const leftAsideWidth = ref('232px')
 const isCollapse = ref(false)
 const handleMenuCollapse = (val: boolean) => {
@@ -18,10 +20,12 @@ const handleMenuCollapse = (val: boolean) => {
 const isShowAIRobotChat = ref(false)
 const handleColseRobotChat = () => {
   isShowAIRobotChat.value = false
+  AIRobotChatref.value?.handleOpen()
 }
 
 const AvatarClick = () => {
   isShowAIRobotChat.value = true
+  AIRobotChatref.value?.handleOpen()
 }
 
 const AIRobotChatref = ref()
@@ -34,6 +38,16 @@ const handelClickAIDefault = async (item: any) => {
   }
 }
 
+watch(
+  () => userStore.isLogin,
+  (newVal) => {
+    if (newVal) {
+      getPrompt()
+    } else {
+      AIRobotChatref.value?.clearData()
+    }
+  }
+)
 const getPrompt = () => {
   $api.getPrompt().then((res) => {
     if (res.code === 200) {
@@ -41,9 +55,6 @@ const getPrompt = () => {
     }
   })
 }
-onMounted(() => {
-  getPrompt()
-})
 </script>
 <template>
   <el-container class="layout-container">
@@ -76,7 +87,7 @@ onMounted(() => {
     <AIRobot @AvatarClick="AvatarClick" @handelClickAIDefault="handelClickAIDefault"></AIRobot>
     <AIRobotChat
       ref="AIRobotChatref"
-      v-if="isShowAIRobotChat"
+      v-show="isShowAIRobotChat"
       @close="handleColseRobotChat"
     ></AIRobotChat>
     <ScoringGrade></ScoringGrade>

+ 3 - 0
src/views/SystemMessage/src/SystemMessage.vue

@@ -176,6 +176,7 @@ onMounted(() => {
                 :data="notificationList"
                 @hasCardRead="changeCardRead"
                 :isScrollPadding="true"
+                :isShowInsertionTime="true"
                 :updateReadCardsOnChange="false"
               ></NotificationMessageCard>
             </div>
@@ -196,6 +197,7 @@ onMounted(() => {
                 v-if="activeTabName === 'Unread'"
                 :data="unreadNotificationList"
                 :isScrollPadding="true"
+                :isShowInsertionTime="true"
                 :updateReadCardsOnChange="false"
               ></NotificationMessageCard>
             </div>
@@ -207,6 +209,7 @@ onMounted(() => {
                 v-if="activeTabName === 'Read'"
                 :updateReadCardsOnChange="false"
                 :isScrollPadding="true"
+                :isShowInsertionTime="true"
                 :data="readNotificationList"
               >
               </NotificationMessageCard>