Quellcode durchsuchen

Merge branch 'dev' of United_Software/k_online_ui into test

Jack Zhou vor 6 Monaten
Ursprung
Commit
1542eb46b6
76 geänderte Dateien mit 5109 neuen und 100 gelöschten Zeilen
  1. 3 0
      package.json
  2. 3 1
      src/api/index.ts
  3. 60 0
      src/api/module/AIRobot.ts
  4. 1 2
      src/auto-imports.d.ts
  5. BIN
      src/components/AIRobot/image/icon_ai_robot24_b@2x.png
  6. BIN
      src/components/AIRobot/image/icon_ai_robot36_b@2x.png
  7. BIN
      src/components/AIRobot/image/icon_ai_robot48_b@2x.png
  8. BIN
      src/components/AIRobot/image/icon_cancel_query_b@2x.png
  9. BIN
      src/components/AIRobot/image/icon_faq_b@2x.png
  10. BIN
      src/components/AIRobot/image/score_normal.png
  11. 275 54
      src/components/AIRobot/src/AIRobot.vue
  12. 1 2
      src/components/NotificationMessageCard/src/NotificationMessageCard.vue
  13. 16 1
      src/components/ScoringGrade/src/ScoringGrade.vue
  14. 15 0
      src/router/index.ts
  15. 84 2
      src/styles/elementui.scss
  16. 64 4
      src/styles/icons/iconfont.css
  17. 0 0
      src/styles/icons/iconfont.js
  18. 30 0
      src/styles/icons/iconfont.svg
  19. BIN
      src/styles/icons/iconfont.ttf
  20. BIN
      src/styles/icons/iconfont.woff
  21. BIN
      src/styles/icons/iconfont.woff2
  22. 65 7
      src/styles/reset.scss
  23. 13 0
      src/styles/theme-g.scss
  24. 41 2
      src/styles/theme.scss
  25. 1 0
      src/views/AIApiLog/index.ts
  26. 189 0
      src/views/AIApiLog/src/AIApiLog.vue
  27. 81 0
      src/views/AIApiLog/src/components/LogDialog.vue
  28. 1 0
      src/views/AIApiLog/src/components/TableView/index.ts
  29. 458 0
      src/views/AIApiLog/src/components/TableView/src/TableView.vue
  30. 190 0
      src/views/AIApiLog/src/components/TableView/src/components/DownloadDialog.vue
  31. 1 0
      src/views/AIRobotChat/index.ts
  32. 654 0
      src/views/AIRobotChat/src/AIRobotChat.vue
  33. 289 0
      src/views/AIRobotChat/src/components/AIQuestions.vue
  34. 70 0
      src/views/AIRobotChat/src/components/AutoResizeTextarea.vue
  35. 46 0
      src/views/AIRobotChat/src/components/LoadingDots.vue
  36. BIN
      src/views/AIRobotChat/src/image/icon_ai_robot36_b@2x.png
  37. BIN
      src/views/AIRobotChat/src/image/icon_ai_robot48_b@2x.png
  38. BIN
      src/views/AIRobotChat/src/image/icon_faq_b@2x.png
  39. BIN
      src/views/AIRobotChat/src/image/icon_loading.png
  40. BIN
      src/views/AIRobotChat/src/image/robotBubbleDark.png
  41. BIN
      src/views/AIRobotChat/src/image/robotBubbleLight.png
  42. BIN
      src/views/AIRobotChat/src/image/userBubbleDark.png
  43. BIN
      src/views/AIRobotChat/src/image/userBubbleLight.png
  44. 0 1
      src/views/Booking/src/components/BookingDetail/src/components/AddReferenceDialog.vue
  45. 0 1
      src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue
  46. 0 1
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  47. 1 0
      src/views/ChatLog/index.ts
  48. 268 0
      src/views/ChatLog/src/ChatLog.vue
  49. 1 0
      src/views/ChatLog/src/components/TableView/index.ts
  50. 435 0
      src/views/ChatLog/src/components/TableView/src/TableView.vue
  51. 190 0
      src/views/ChatLog/src/components/TableView/src/components/DownloadDialog.vue
  52. 28 1
      src/views/Layout/src/LayoutView.vue
  53. 2 0
      src/views/Layout/src/components/Header/components/LogoutDialog.vue
  54. 11 5
      src/views/Layout/src/components/Menu/MenuView.vue
  55. 1 0
      src/views/Login/src/loginView.vue
  56. 1 0
      src/views/PromptConfiguration/index.ts
  57. 474 0
      src/views/PromptConfiguration/src/PromptConfiguration.vue
  58. 92 0
      src/views/PromptConfiguration/src/components/CodeBlock.vue
  59. 341 0
      src/views/PromptConfiguration/src/components/OutputConfiguration.vue
  60. 263 0
      src/views/PromptConfiguration/src/components/PreviewTesting.vue
  61. 137 0
      src/views/PromptConfiguration/src/components/RespnseConfiguration.vue
  62. 201 0
      src/views/PromptConfiguration/src/components/TableConfiguration.vue
  63. BIN
      src/views/PromptConfiguration/src/images/default_add@2x.png
  64. BIN
      src/views/PromptConfiguration/src/images/icon_ai_test@2x.png
  65. BIN
      src/views/PromptConfiguration/src/images/icon_success_big@2x.png
  66. BIN
      src/views/PromptConfiguration/src/images/submit_successful.png
  67. 7 2
      src/views/SystemMessage/src/SystemMessage.vue
  68. 0 1
      src/views/Tracking/src/components/PublicTracking/src/components/MilestonesTable.vue
  69. 5 5
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  70. 0 2
      src/views/Tracking/src/components/TrackingDetail/src/components/AMS&ISF.vue
  71. 0 1
      src/views/Tracking/src/components/TrackingDetail/src/components/AddReferenceDialog.vue
  72. 0 1
      src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue
  73. 0 1
      src/views/Tracking/src/components/TrackingDetail/src/components/ContainersView.vue
  74. 0 1
      src/views/Tracking/src/components/TrackingDetail/src/components/MilestonesTable.vue
  75. 0 1
      src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue
  76. 0 1
      src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

+ 3 - 0
package.json

@@ -30,8 +30,11 @@
     "echarts": "^5.5.1",
     "element-plus": "^2.8.1",
     "exceljs": "^4.4.0",
+    "github-markdown-css": "^5.8.1",
+    "highlight.js": "^11.11.1",
     "leaflet": "^1.9.4",
     "lodash": "^4.17.21",
+    "markdown-it": "^14.1.0",
     "mitt": "^3.0.1",
     "moment": "^2.30.1",
     "moment-timezone": "^0.5.46",

+ 3 - 1
src/api/index.ts

@@ -5,6 +5,7 @@ import * as login from './module/login'
 import * as other from './module/other'
 import * as notificationMessage from './module/notificationMessage'
 import * as system from './module/system'
+import * as AIRobot from './module/AIRobot'
 /**
  * api 对象接口定义
  */
@@ -23,7 +24,8 @@ const apis = generateApiMap({
   ...login,
   ...other,
   ...notificationMessage,
-  ...system
+  ...system,
+  ...AIRobot
 })
 export default {
   ...apis // 取出所有可遍历属性赋值在新的对象上

+ 60 - 0
src/api/module/AIRobot.ts

@@ -0,0 +1,60 @@
+import HttpAxios from '@/utils/axios'
+
+const base = import.meta.env.VITE_API_HOST
+const baseUrl = `${base}/main_new_version.php`
+
+/**
+ * Prompt Configuration数据
+ */
+export const getPromptConfiguration = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 保存Prompt Configuration数据
+ */
+export const SavePromptConfiguration = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot',
+      operate: 'save',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 编辑返回prompt数据
+ */
+export const EditPrompt = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot',
+      operate: 'preview_propmpt_witout_save',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * AI测试问题
+ */
+export const PromptAITest = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'robot',
+      operate: 'test_with_ds_claude',
+      ...params
+    },
+    config
+  )
+}

+ 1 - 2
src/auto-imports.d.ts

@@ -3,7 +3,6 @@
 // @ts-nocheck
 // noinspection JSUnusedGlobalSymbols
 // Generated by unplugin-auto-import
-// biome-ignore lint: disable
 export {}
 declare global {
   const $api: typeof import('@/api/index')['default']
@@ -69,6 +68,6 @@ declare global {
 // for type re-export
 declare global {
   // @ts-ignore
-  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
+  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
   import('vue')
 }

BIN
src/components/AIRobot/image/icon_ai_robot24_b@2x.png


BIN
src/components/AIRobot/image/icon_ai_robot36_b@2x.png


BIN
src/components/AIRobot/image/icon_ai_robot48_b@2x.png


BIN
src/components/AIRobot/image/icon_cancel_query_b@2x.png


BIN
src/components/AIRobot/image/icon_faq_b@2x.png


BIN
src/components/AIRobot/image/score_normal.png


+ 275 - 54
src/components/AIRobot/src/AIRobot.vue

@@ -1,92 +1,259 @@
 <script setup lang="ts">
-import { ref } from 'vue'
-import normalPng from '../image/score_normal.png'
+import { ref, onMounted } from 'vue'
+import normalPng from '../image/icon_ai_robot24_b@2x.png'
+import emitter from '@/utils/bus'
 
 const clickSrc = ref(normalPng)
 const visible = ref(false)
 const AIRobotHoverVisible = ref(false)
-const AIRobotClickVisible = ref(false)
 const clicked = ref(false)
+const isShowDefault = ref(false)
+const isShowAIRobotTop = ref(false)
+const AIIconVisible = ref(false)
+const DeQuestions = ref([
+  {
+    label: 'List shipments with ETA changes in the last 30 days.',
+    value: 'List shipments with ETA changes in the last 30 days.',
+    isLong: false
+  },
+  {
+    label: 'Shipments arriving in the next 7 days.',
+    value: 'Shipments arriving in the next 7 days.',
+    isLong: false
+  },
+  {
+    label: 'Show shipments delayed in the last 30 days.',
+    value: 'Show shipments delayed in the last 30 days.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone updates in the last 7 days.',
+    value: 'List shipments with milestone updates in the last 7 days.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone in the last 7 days2sdsa.',
+    value: 'List shipments with milestone in the last 7 days2sdsa.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone updates 7 days3fef.',
+    value: 'List shipments with milestone updates 7 days3fef.',
+    isLong: false
+  },
+  {
+    label:
+      'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    value:
+      'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    isLong: true
+  },
+  {
+    label:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111122.',
+    value:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111122.',
+    isLong: true
+  },
+  {
+    label:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111133.',
+    value:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111133.',
+    isLong: true
+  }
+])
+const itemGroups = ref([])
+
 // 鼠标hover AIRobot图标
 const AvatarMouseEnter = () => {
-  if (!clicked.value) {
+  if (clicked.value) {
     AIRobotHoverVisible.value = true
     visible.value = true
   }
 }
 // 鼠标move AIRobot图标
 const AvatarMouseLeave = () => {
-  if (!clicked.value) {
+  if (clicked.value) {
     visible.value = false
     AIRobotHoverVisible.value = false
   }
 }
+const emit = defineEmits(['AvatarClick', 'handelClickAIDefault'])
 // 点击AIRobot图标
 const AvatarClick = () => {
+  clicked.value = true
   AIRobotHoverVisible.value = false
-  if(AIRobotClickVisible.value == true && visible.value) {
-    AIRobotClickVisible.value = false
-    visible.value = false
-    clicked.value = false
+  isShowDefault.value = false
+  isShowAIRobotTop.value = false
+  emit('AvatarClick')
+}
+// 隐藏上方弹窗
+const HideAIRobotTop = () => {
+  isShowDefault.value = false
+}
+
+// 隐藏上方弹窗
+const HideAIRobotTopTwo = () => {
+  isShowAIRobotTop.value = false
+}
+
+// 随机显示方法
+const prepareGroups = () => {
+  const groups = []
+  let currentGroup = []
+  let currentHeight = 0
+
+  DeQuestions.value.forEach((item) => {
+    const itemHeight = item.isLong ? 2 : 1
+
+    if (currentHeight + itemHeight > 4) {
+      groups.push(currentGroup)
+      currentGroup = []
+      currentHeight = 0
+    }
+
+    currentGroup.push(item)
+    currentHeight += itemHeight
+  })
+
+  // 添加最后一组
+  if (currentGroup.length > 0) {
+    groups.push(currentGroup)
+  }
+
+  itemGroups.value = groups
+}
+const isShowLogin = () => {
+  const LoginDays = 0
+  let settimeouttime = 0
+  AIIconVisible.value = true
+  isShowDefault.value = true
+  if (LoginDays == 0) {
+    settimeouttime = 45000
+  } else if (LoginDays == 2) {
+    settimeouttime = 15000
   } else {
-    visible.value = true 
-    AIRobotClickVisible.value = true
-    clicked.value = true
+    settimeouttime = 10000
   }
+  setTimeout(() => {
+    isShowDefault.value = false
+    isShowAIRobotTop.value = true
+  }, settimeouttime)
+}
+
+// 退出登录后隐藏icon
+const Logout = () => {
+  AIIconVisible.value = false
+  AIRobotHoverVisible.value = false
+  isShowDefault.value = false
+  isShowAIRobotTop.value = false
+}
+
+// 退出登录后隐藏icon
+const checknoPrompt = () => {
+  AIIconVisible.value = true
 }
-const handlePopoverHide = () => {
-  clicked.value = false
+
+// 点击问题
+const handelClick = (item: any) => {
+  emit('handelClickAIDefault', item.value)
+  clicked.value = true
   AIRobotHoverVisible.value = false
-  AIRobotClickVisible.value = false
+  isShowDefault.value = false
+  isShowAIRobotTop.value = false
 }
+
+onMounted(() => {
+  prepareGroups()
+  emitter.on('login-success', isShowLogin)
+  emitter.on('login-out', Logout)
+  emitter.on('checkPrompt', Logout)
+  emitter.on('checknoPrompt', checknoPrompt)
+})
+
+defineExpose({
+  isShowLogin
+})
 </script>
 <template>
   <!-- 上方显示的弹窗 -->
-  <div class="AIRobot-top">
-    <div class="flex_end">
-      <div class="icon flex_center">
-        <span class="iconfont_icon icon_dark">
-            <svg class="iconfont" aria-hidden="true">
-              <use xlink:href="#icon-icon_reject_b"></use>
-            </svg>
-          </span>
+  <div class="AIRobot-top" v-if="isShowDefault">
+    <div class="flex_end" @click="HideAIRobotTop">
+      <div class="icon flex_center icon-AI">
+        <span class="iconfont_icon icon_dark AI_icon">
+          <svg class="iconfont" aria-hidden="true">
+            <use xlink:href="#icon-icon_reject_b"></use>
+          </svg>
+        </span>
       </div>
     </div>
-    <div class="dialogue_title">
-      Hi! I'm your Freight Assistant, always on call
+    <div class="flex_title">
+      <div class="AIAvator">
+        <img width="40px" src="../image/icon_ai_robot36_b@2x.png" />
+      </div>
+      <div class="dialogue_title">Hi! I'm your Freight Assistant, always on call</div>
     </div>
-    <div class="dialogue_content">
-      <div class="dialogue_content_title">
-        <div class="dialogue_title_left">
-          Frequently Asked Questions
-        </div>
-        <div class="dialogue_title_right">
-          Refresh Questions
+    <div class="flex_end">
+      <div class="dialogue_content">
+        <div class="dialogue_content_title">
+          <div class="dialogue_title_left">
+            <img src="../image/icon_faq_b@2x.png" width="24px" />
+            Frequently Asked Questions
+          </div>
         </div>
+        <el-carousel class="carousel" :autoplay="false" height="190px" style="width: 452px">
+          <el-carousel-item v-for="(group, index) in itemGroups" :key="index">
+            <div class="dialogue_container">
+              <div
+                class="dialogue_content_item"
+                @click="handelClick(item)"
+                v-for="item in group"
+                :key="item.label"
+                :class="{ long_item: item.isLong }"
+              >
+                {{ item.label }}
+              </div>
+            </div>
+          </el-carousel-item>
+        </el-carousel>
       </div>
     </div>
   </div>
+  <div class="AIRobot-top" v-if="isShowAIRobotTop">
+    <div class="flex_end" @click="HideAIRobotTopTwo">
+      <div class="icon flex_center icon-AI">
+        <span class="iconfont_icon icon_dark AI_icon">
+          <svg class="iconfont" aria-hidden="true">
+            <use xlink:href="#icon-icon_reject_b"></use>
+          </svg>
+        </span>
+      </div>
+    </div>
+    <div class="dialogue_title">Hi! I'm your Freight Assistant, always on call</div>
+  </div>
   <!-- 悬浮icon -->
-  <div class="AIRobot flex_center">
-    <el-popover
-      :visible="visible"
-      placement="top-end"
-      width="auto"
-      @hide="handlePopoverHide"
-    >
+  <div class="AIRobot flex_center" v-if="AIIconVisible">
+    <el-popover :visible="visible" placement="top-end" width="auto">
       <template #reference>
-        <el-avatar @click="AvatarClick" @mouseenter="AvatarMouseEnter" @mouseleave="AvatarMouseLeave" :size="46" shape="square" :src="clickSrc" />
+        <el-avatar
+          @click="AvatarClick"
+          @mouseenter="AvatarMouseEnter"
+          @mouseleave="AvatarMouseLeave"
+          :size="46"
+          shape="square"
+          class="avatar_bg"
+          :src="clickSrc"
+        />
       </template>
       <!-- hover时显示的对话框 -->
       <div v-if="AIRobotHoverVisible" class="AIRobot_dialog">Continue the conversation</div>
-      <!-- click时显示的对话框 -->
-      <div v-if="AIRobotClickVisible" class="AIRobot_dialog">Continue the conversation  156468482</div>
     </el-popover>
   </div>
 </template>
 
 <style lang="scss">
-.iconfont_icon {
+.AI_icon {
   margin-right: 0;
 }
 .flex_center {
@@ -98,38 +265,53 @@ const handlePopoverHide = () => {
   display: flex;
   justify-content: end;
 }
+.flex_title {
+  display: flex;
+  align-items: center;
+  width: 500px;
+}
 .AIRobot-top {
   position: absolute;
   z-index: 2013;
-  right: 20px;
-  bottom: 250px;
+  right: 50px;
+  bottom: 230px;
 }
 .dialogue_content_title {
   display: flex;
   align-items: center;
   justify-content: space-between;
+  padding-left: 8px;
 }
 .dialogue_title_left {
-  margin-right: 80px;
-  background: linear-gradient(90deg, #A71549 1.77%, #06256E 46.77%);
+  background: var(--color-dialogue_title);
   -webkit-background-clip: text;
   background-clip: text;
   color: transparent;
   font-size: 14px;
   font-weight: 700;
 }
-.icon {
+.dialogue_title_right {
+  color: var(--color-theme);
+  cursor: pointer;
+  user-select: none;
+}
+.icon_theme {
+  fill: var(--color-theme);
+  cursor: pointer;
+}
+.icon-AI {
   width: 24px;
   height: 24px;
   background-color: var(--management-bg-color);
   box-shadow: -2px 2px 12px rgba(0, 0, 0, 15%);
   border-radius: 6px;
+  cursor: pointer;
 }
 .AIRobot {
   width: 64px;
   height: 64px;
   border-radius: 12px;
-  background: radial-gradient(50% 43% at 50% 54.79%, #D5B4F3 0%, #FFF9FC 100%);
+  background: var(--color-dialogue-icon-bg);
   position: absolute;
   box-shadow: -2px 2px 12px rgba(0, 0, 0, 15%);
   z-index: 2013;
@@ -137,21 +319,60 @@ const handlePopoverHide = () => {
   bottom: 164px;
 }
 .AIRobot_dialog {
-  background: linear-gradient(92deg, #EAECFF 1.33%, #F1E3FB 99.63%);
+  background: var(--color-dialogue-text-bg);
   box-shadow: -10px 10px 24px 0px rgba(58, 0, 78, 0.15);
   padding: 5px 8px;
   border-radius: 3px;
 }
 .dialogue_title {
-  background: linear-gradient(92deg, #EAECFF 1.33%, #F1E3FB 99.63%);
+  background: var(--color-dialogue-text-bg);
   padding: 8px;
   border-radius: 12px;
   margin: 10px 0;
   width: 294px;
 }
 .dialogue_content {
-  background: linear-gradient(92deg, #EAECFF 1.33%, #F1E3FB 99.63%);
-  padding: 8px;
+  background: var(--color-dialogue-text-bg);
+  padding: 8px 0 0 0;
   border-radius: 12px;
 }
-</style>
+.AIAvator {
+  width: 40px;
+  height: 40px;
+  background: var(--color-dialogue-icon-bg);
+  border-radius: 12px;
+  margin-right: 8px;
+}
+.dialogue_container {
+  width: 420px;
+  height: 156px;
+  border-radius: 12px;
+  border: 1px solid var(--color-dialogue_container-border);
+  padding: 8px;
+  background: var(--color-dialogue_container-bg);
+}
+.dialogue_content_item {
+  width: 404px;
+  height: 32px;
+  padding: 5.5px 8px;
+  border-radius: 6px;
+  background-color: var(--color-dialogue-bg);
+  margin-bottom: 4px;
+  display: flex;
+  align-items: center;
+  word-break: break-word;
+  overflow: hidden;
+  transition: all 0.3s ease;
+  cursor: pointer;
+}
+.dialogue_content_item:hover {
+  background-color: var(--color-arrow-hoverL);
+  color: var(--color-theme);
+}
+.itemLable {
+  width: 100%;
+}
+.long_item {
+  height: 64px;
+}
+</style>

+ 1 - 2
src/components/NotificationMessageCard/src/NotificationMessageCard.vue

@@ -191,7 +191,6 @@ const parentHeight = computed(() => {
   return (window.innerHeight || document.documentElement.clientHeight) - props.topOffset
 })
 const scrollParentBoxStyle = computed(() => {
-  console.log(props.topOffset, 'value')
   return props.topOffset ? { height: `${parentHeight.value}px` } : {}
 })
 </script>
@@ -240,7 +239,7 @@ const scrollParentBoxStyle = computed(() => {
   width: 100%;
   overflow-y: auto;
   .scroll-padding {
-    padding: 10px 140px 0px 16px;
+    padding: 0px 140px 0px 16px;
   }
 }
 </style>

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

@@ -15,11 +15,13 @@ import happyPng2 from '../image/happy_2.png'
 import normalPng from '../image/score_normal.png'
 import submitsucessful from '../image/submit_successful.png'
 import { useUserStore } from '@/stores/modules/user'
+import emitter from '@/utils/bus'
 
 const userStore = useUserStore()
 
 // const isShow = ref(true)
 const visible = ref(false)
+const isShowScoring = ref(true)
 const isLoaded = ref(false)
 const isShowAngry = ref(false)
 const isShowHappy = ref(false)
@@ -273,9 +275,22 @@ const mouseout = (item: any) => {
   item.src = item.src1
 }
 const SubmitText = ref()
+
+// 切换隐藏icon
+const Logout = () => {
+  isShowScoring.value = false
+}
+// 切换隐藏icon
+const checknoPrompt = () => {
+  isShowScoring.value = true
+}
+onMounted(() => {
+  emitter.on('checkPrompt', Logout)
+  emitter.on('checknoPrompt', checknoPrompt)
+})
 </script>
 <template>
-  <div class="scoring">
+  <div class="scoring" v-if="isShowScoring">
     <el-popover
       :visible="visible"
       placement="left"

+ 15 - 0
src/router/index.ts

@@ -96,6 +96,21 @@ const router = createRouter({
           name: 'Operationlog',
           component: () => import('../views/OperationLog')
         },
+        {
+          path: '/chat-log',
+          name: 'Chat Log',
+          component: () => import('../views/ChatLog')
+        },
+        {
+          path: '/ai-api-log',
+          name: 'AI API Log',
+          component: () => import('../views/AIApiLog')
+        },
+        {
+          path: '/PromptConfiguration',
+          name: 'PromptConfiguration',
+          component: () => import('../views/PromptConfiguration')
+        },
         {
           path: '/system-message',
           name: 'System Message',

+ 84 - 2
src/styles/elementui.scss

@@ -15,6 +15,21 @@
   }
 }
 
+.el-button.el-button--noborder--configuration {
+  border: none;
+  span {
+    color: var(--color-theme);
+  }
+  &:hover {
+    border-color: var(--color-btn-default-bg-hover);
+    background-color: var(--color-btn-default-bg-hover);
+    fill: var(--color-theme);
+    span {
+      color: var(--color-theme);
+    }
+  }
+}
+
 button.el-button.el-button--text {
   height: 24px;
   padding: 4px 8px;
@@ -171,7 +186,7 @@ button.el-button.el-button--icon {
   }
 }
 // 初始为黑色
-.el-button.el-button--dark {
+button.el-button.el-button--dark {
   background-color: var(--color-btn-default-dark-bg);
   fill: var(--color-white);
   border: none;
@@ -183,7 +198,17 @@ button.el-button.el-button--icon {
     background-color: var(--color-btn-default-dark-hover-bg);
     fill: var(--color-btn-default-dark-hover-bg);
     span {
-      color: var(--color-btn-default-dark-hover) !important;
+      color: var(--color-btn-default-dark-hover);
+    }
+  }
+}
+button.el-button.el-button--dark.is-disabled {
+  opacity: 0.3;
+  &:hover {
+    background-color: var(--color-btn-default-dark-bg);
+    fill: var(--color-white);
+    span {
+      color: var(--color-white);
     }
   }
 }
@@ -785,3 +810,60 @@ div .DaterangeClass {
 div .el-radio__label {
   width: 100%;
 }
+
+div .avatar_bg {
+  background-color: transparent !important;
+}
+div .carousel .el-carousel__indicator--horizontal {
+  padding: 4px 8px;
+}
+div .carousel .el-carousel__button {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background-color: var(--color-system-card-bg);
+  opacity: 1;
+}
+div .carousel .el-carousel__indicator.is-active button {
+  background-color: var(--color-theme);
+}
+div .carousel .el-carousel__item--card, .el-carousel__item.is-animating {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding-bottom: 15px;
+}
+div .carousel .el-carousel__arrow {
+  opacity: 1;
+  background-color: var(--color-carousel-card-bg);
+  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.10);
+}
+div .carousel .el-carousel__arrow--left {
+  left: 0;
+}
+div .carousel .el-carousel__arrow--right {
+  right: 0;
+}
+div .carousel .el-icon {
+  fill: black;
+  color: black;
+}
+div .carousel .el-carousel__arrow:hover {
+  background-color: var(--color-arrow-hoverL);
+  .el-icon {
+    fill: var(--color-theme);
+    color: var(--color-theme);
+  }
+}
+div .prompt-dialog {
+  min-height: 800px ;
+}
+div .prompt-dialog-inner .el-dialog__header {
+  padding: 0;
+  height: 48px;
+}
+div .prompt-dialog-inner .el-dialog__body {
+  max-height: 720px;
+  overflow-y: scroll;
+  line-height: 21px; 
+}

+ 64 - 4
src/styles/icons/iconfont.css

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "font_family"; /* Project id 4672385 */
-  src: url('iconfont.woff2?t=1740548496100') format('woff2'),
-       url('iconfont.woff?t=1740548496100') format('woff'),
-       url('iconfont.ttf?t=1740548496100') format('truetype'),
-       url('iconfont.svg?t=1740548496100#font_family') format('svg');
+  src: url('iconfont.woff2?t=1745286986564') format('woff2'),
+       url('iconfont.woff?t=1745286986564') format('woff'),
+       url('iconfont.ttf?t=1745286986564') format('truetype'),
+       url('iconfont.svg?t=1745286986564#font_family') format('svg');
 }
 
 .font_family {
@@ -14,6 +14,66 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-icon_good_b:before {
+  content: "\e713";
+}
+
+.icon-icon_notgood__filled_b:before {
+  content: "\e714";
+}
+
+.icon-icon_good__filled_b:before {
+  content: "\e715";
+}
+
+.icon-icon_notgood_b:before {
+  content: "\e716";
+}
+
+.icon-icon_send_b:before {
+  content: "\e712";
+}
+
+.icon-icon_sidebar__window_b:before {
+  content: "\e70f";
+}
+
+.icon-icon_maximized__window_b:before {
+  content: "\e710";
+}
+
+.icon-icon_collapsed__to_widget_b:before {
+  content: "\e711";
+}
+
+.icon-icon_theme_colors_b:before {
+  content: "\e70e";
+}
+
+.icon-icon_delay_b1:before {
+  content: "\e70b";
+}
+
+.icon-icon_collapse_b1:before {
+  content: "\e70c";
+}
+
+.icon-icon_cancel_b2:before {
+  content: "\e70d";
+}
+
+.icon-icon_ai_api_log_b:before {
+  content: "\e70a";
+}
+
+.icon-icon_collapse__sidebar_b:before {
+  content: "\e708";
+}
+
+.icon-icon_dashboard_title_b:before {
+  content: "\e709";
+}
+
 .icon-icon_collapse_b:before {
   content: "\e707";
 }

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
src/styles/icons/iconfont.js


+ 30 - 0
src/styles/icons/iconfont.svg

@@ -14,6 +14,36 @@
     />
       <missing-glyph />
       
+      <glyph glyph-name="icon_good_b" unicode="&#59155;" d="M549.376 780.48c-38.272 0-73.6-20.608-92.352-53.888L297.408 444.032H135.04a32 32 0 0 1-32-32v-392.384a32 32 0 0 1 32-32h98.56v-0.128h64v0.128h430.72a96 96 0 0 1 92.352 69.952l90.24 320.256a96 96 0 0 1-92.352 122.048h-162.944V674.368c0 58.624-47.552 106.112-106.176 106.112zM297.6 51.648v328.32h18.56a32 32 0 0 1 27.776 16.32L512.704 695.04a42.112 42.112 0 0 0 78.848-20.736v-206.464a32 32 0 0 1 32-32h194.944a32 32 0 0 0 30.72-40.64l-90.24-320.32a32 32 0 0 0-30.72-23.296h-430.72z m-64 328.32v-328.32H167.04v328.32h66.56z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_notgood__filled_b" unicode="&#59156;" d="M488.576-69.568c38.272 0 73.6 20.608 92.416 53.952l159.36 282.176V723.328H309.76a96 96 0 0 1-92.352-70.016l-90.24-320.256a96 96 0 0 1 92.352-122.048h162.944v-174.464c0-58.624 47.552-106.112 106.112-106.112z m315.776 792.896V266.88h98.624a32 32 0 0 1 32 32V691.2a32 32 0 0 1-32 32h-98.56z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_good__filled_b" unicode="&#59157;" d="M574.4 780.48c-38.272 0-73.6-20.608-92.416-53.888L322.432 444.032H160a32 32 0 0 1-32-32v-392.384a32 32 0 0 1 32-32h98.56v456.32h64v-456.32h430.656a96 96 0 0 1 92.416 69.952l90.24 320.256a96 96 0 0 1-92.416 122.048h-162.944V674.368c0 58.624-47.488 106.112-106.112 106.112z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_notgood_b" unicode="&#59158;" d="M488.576-69.568c38.272 0 73.6 20.608 92.416 53.952l159.616 282.56h162.368a32 32 0 0 1 32 32V691.2a32 32 0 0 1-32 32H309.76a96 96 0 0 1-92.352-69.952l-90.24-320.256a96 96 0 0 1 92.352-122.048h162.944v-174.464c0-58.624 47.552-106.112 106.112-106.112z m36.672 85.44a42.112 42.112 0 0 0-78.72 20.672v206.464a32 32 0 0 1-32 32H219.52a32 32 0 0 0-30.784 40.704l90.24 320.256a32 32 0 0 0 30.784 23.36h430.656V330.88h-18.56a32 32 0 0 1-27.84-16.256l-168.768-298.88zM804.48 330.88V659.2h66.56v-328.32h-66.56z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_send_b" unicode="&#59154;" d="M678.272 424.256l-460.928 225.92 56.32-225.92h404.608z m-403.712-76.8l-57.216-229.568 468.352 229.568H274.56z m601.664 65.28a32 32 0 0 0 0-57.472L164.672 6.592a32 32 0 0 0-45.184 36.48l83.072 333.184a32 32 0 0 1 0 15.488L119.488 724.992a32 32 0 0 0 45.184 36.48L876.16 412.736z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_sidebar__window_b" unicode="&#59151;" d="M740.352 809.28h132.16v-76.8h-132.16v76.8z m-72.448-102.592a25.6 25.6 0 0 0 25.6 25.6v76.8H119.04a102.4 102.4 0 0 1-102.4-102.4v-645.376a102.4 102.4 0 0 1 102.4-102.4H693.504v76.8a25.6 25.6 0 0 0-25.6 25.6v80.64h-76.8v-80.64c0-8.832 1.088-17.408 3.2-25.6H119.04a25.6 25.6 0 0 0-25.6 25.6V706.688c0 14.08 11.456 25.6 25.6 25.6h475.264c-2.112-8.192-3.2-16.768-3.2-25.6v-80.64h76.8v80.64z m251.072 25.6a25.6 25.6 0 0 0 25.6-25.6v-80.64h76.8v80.64a102.4 102.4 0 0 1-102.4 102.4v-76.8zM667.904 410.88V572.16h-76.8v-161.28h76.8z m276.672 161.28v-161.28h76.8V572.16h-76.8z m-276.672-376.384v161.28h-76.8v-161.28h76.8z m276.672 161.28v-161.28h76.8v161.28h-76.8z m0-215.04v-80.64a25.6 25.6 0 0 0-25.6-25.6v-76.8a102.4 102.4 0 0 1 102.4 102.4v80.64h-76.8zM541.632 406.592L433.28 514.944l-45.248-45.248 53.76-53.76H186.88v-64h254.784l-53.696-53.696 45.248-45.248 108.352 108.352a32 32 0 0 1 0 45.248z m330.88-370.88h-132.16v-76.8h132.16v76.8z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_maximized__window_b" unicode="&#59152;" d="M944.64 706.688a25.6 25.6 0 0 1-25.6 25.6h-224.96a25.6 25.6 0 0 1-25.6-25.6v-645.376c0-14.08 11.52-25.6 25.6-25.6h224.896a25.6 25.6 0 0 1 25.6 25.6V706.688z m-25.6 102.4a102.4 102.4 0 0 0 102.4-102.4v-645.376a102.4 102.4 0 0 0-102.4-102.4h-224.96a102.4 102.4 0 0 0-102.4 102.4V706.688a102.4 102.4 0 0 0 102.4 102.4h224.896z m-490.624-76.8h148.352v76.8H428.416v-76.8z m-220.48 0h162.944v76.8H207.936v-76.8z m-88.96 0h36.544v76.8h-36.48a102.4 102.4 0 0 1-102.4-102.4v-80.64h76.8v80.64c0 14.08 11.456 25.6 25.6 25.6z m-25.6-321.408V572.16H16.64v-161.28h76.8z m0-215.104v161.28H16.64v-161.28h76.8z m0-134.4v80.64H16.64v-80.64a102.4 102.4 0 0 1 102.4-102.4h36.544v76.8h-36.48a25.6 25.6 0 0 0-25.6 25.6z m483.392-25.6H428.416v-76.8h148.352v76.8z m-205.952 0H208v-76.8h162.944v76.8zM164.352 406.528L272.64 514.944l45.248-45.248-53.696-53.76h254.784v-64H264.192l53.696-53.696-45.248-45.248-108.352 108.352a32 32 0 0 0 0 45.248z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_collapsed__to_widget_b" unicode="&#59153;" d="M928.768 813.504c55.488 0 100.48-44.992 100.48-100.48v-327.808h-75.392V712.96a25.152 25.152 0 0 1-25.088 25.152H111.744a25.152 25.152 0 0 1-25.152-25.152v-659.84c0-13.888 11.264-25.152 25.152-25.152h488.768v-75.456H111.744A100.544 100.544 0 0 0 11.2 53.12V712.96c0 55.552 44.992 100.544 100.544 100.544h817.024z m-216.832-571.84c0 15.616 12.672 28.288 28.288 28.288h62.848v69.12h-62.848c-53.76 0-97.408-43.648-97.408-97.408v-62.848h69.12v62.848z m153.984 28.288h62.848c15.616 0 28.224-12.672 28.224-28.288v-62.848h69.12v62.848c0 53.76-43.52 97.408-97.344 97.408h-62.848v-69.12z m-153.984-216.832v62.848h-69.12v-62.848c0-53.76 43.584-97.408 97.408-97.408h62.848v69.12h-62.848a28.288 28.288 0 0 0-28.288 28.288z m245.12 62.848v-62.848a28.288 28.288 0 0 0-28.288-28.288h-62.848v-69.12h62.848c53.76 0 97.408 43.584 97.408 97.408v62.848h-69.12z m-418.304 258.56a31.36 31.36 0 0 1-0.256 2.56l-19.2 154.048-62.4-7.808 9.92-79.104L260.416 604.8l-38.592-49.6L428.16 394.56l-79.104-9.856 7.808-62.4 152.96 19.136a31.36 31.36 0 0 1 28.928 32.96z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_theme_colors_b" unicode="&#59150;" d="M454.4 722.432L388.096 788.8l54.336 54.336 492.288-492.288-425.92-425.92-425.856 425.92L454.4 722.432z m54.4-54.336L191.424 350.848l317.248-317.312 317.312 317.312-317.312 317.248zM880.32 350.848L508.8-20.736 137.216 350.848l53.056 53.12h636.992l53.12-53.12zM1033.152 166.592a76.416 76.416 0 1 0-152.832 0c0 42.24 76.416 131.584 76.416 131.584s76.416-89.408 76.416-131.584z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_delay_b1" unicode="&#59147;" d="M487.04 676.032a64 64 0 0 0 108.224-0.192l324.544-515.456a64 64 0 0 0-54.144-98.112H213.76a64 64 0 0 0-54.016 98.304l327.296 515.456z m54.08-34.304L213.76 126.272h651.84L541.12 641.728z m30.528-165.12v-203.456h-64v203.52h64z m0-309.376v52.992h-64v-52.992h64z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_collapse_b1" unicode="&#59148;" d="M160 769.088a102.4 102.4 0 0 1-102.4-102.4v-565.376a102.4 102.4 0 0 1 102.4-102.4h704a102.4 102.4 0 0 1 102.4 102.4V666.688a102.4 102.4 0 0 1-102.4 102.4h-704z m-25.6-102.4c0 14.08 11.52 25.6 25.6 25.6h244.288v-616.576H160a25.6 25.6 0 0 0-25.6 25.6V666.688z m346.688-590.976V692.288H864c14.08 0 25.6-11.52 25.6-25.6v-565.376a25.6 25.6 0 0 0-25.6-25.6H481.088z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_cancel_b2" unicode="&#59149;" d="M201.152 743.936c0 14.08 11.52 25.6 25.6 25.6h554.56c14.08 0 25.6-11.52 25.6-25.6v-458.24h76.8v458.24a102.4 102.4 0 0 1-102.4 102.4H226.752a102.4 102.4 0 0 1-102.4-102.4v-704a102.4 102.4 0 0 1 102.4-102.4h277.312v76.8H226.752a25.6 25.6 0 0 0-25.6 25.6v704z m428.224-497.728l107.968-107.968 107.968 107.904 54.336-54.272-107.968-107.968 107.968-107.968-54.336-54.272-107.968 107.968-107.968-108.032-54.336 54.336 108.032 107.968-108.032 107.968 54.336 54.336z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_ai_api_log_b" unicode="&#59146;" d="M305.28 659.84h429.952v76.8H305.28v-76.8z m-51.84-513.28h532.544V543.36H253.44v-396.864zM176.64 556.16a64 64 0 0 0 64 64h558.144a64 64 0 0 0 64-64v-422.464a64 64 0 0 0-64-64H240.64a64 64 0 0 0-64 64V556.16z m164.8-262.4v102.4h128v-102.4h-128z m241.728 102.4v-102.4h128v102.4h-128z m313.408-136V430.272h76.8v-170.176h-76.8z m-832 170.112v-170.176h76.8V430.336h-76.8z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_collapse__sidebar_b" unicode="&#59144;" d="M142.08 769.088a38.4 38.4 0 0 1-38.4-38.4v-693.376a38.4 38.4 0 0 1 38.4-38.4h832a38.4 38.4 0 0 1 38.4 38.4V730.688a38.4 38.4 0 0 1-38.4 38.4h-832z m38.4-693.376V692.288h269.824v-616.576H180.48z m346.624 0V692.288h408.512v-616.576H527.104z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_dashboard_title_b" unicode="&#59145;" d="M863.2064 384c0-183.808-148.992-332.8-332.8-332.8s-332.8 148.992-332.8 332.8 148.992 332.8 332.8 332.8 332.8-148.992 332.8-332.8z m-332.8-1.6128l262.3232 60.4416C765.952 562.9952 658.6624 652.8 530.432 652.8v-270.4128z"  horiz-adv-x="1049" />
+      
       <glyph glyph-name="icon_collapse_b" unicode="&#59143;" d="M199.111111 726.300444a91.022222 91.022222 0 0 1-91.022222-91.022222v-502.556444a91.022222 91.022222 0 0 1 91.022222-91.022222h625.777778a91.022222 91.022222 0 0 1 91.022222 91.022222V635.278222a91.022222 91.022222 0 0 1-91.022222 91.022222h-625.777778z m-22.755555-91.022222c0 12.515556 10.24 22.755556 22.755555 22.755556h217.144889v-548.067556H199.111111a22.755556 22.755556 0 0 0-22.755555 22.755556V635.278222z m308.167111-525.312V658.033778H824.888889c12.515556 0 22.755556-10.24 22.755555-22.755556v-502.556444a22.755556 22.755556 0 0 0-22.755555-22.755556H484.522667z"  horiz-adv-x="1024" />
       
       <glyph glyph-name="icon_cancel_b1" unicode="&#59142;" d="M235.690667 704c0 12.515556 10.24 22.755556 22.755555 22.755556h492.942222c12.515556 0 22.755556-10.24 22.755556-22.755556v-407.324444h68.266667v407.324444a91.022222 91.022222 0 0 1-91.022223 91.022222H258.446222a91.022222 91.022222 0 0 1-91.022222-91.022222v-625.777778a91.022222 91.022222 0 0 1 91.022222-91.022222h246.499556v68.266667H258.446222a22.755556 22.755556 0 0 0-22.755555 22.755555v625.777778z m380.643555-442.424889l95.971556-96.028444 95.971555 95.971555 48.241778-48.241778-95.914667-95.971555 95.971556-95.971556-48.298667-48.241777-95.971555 95.914666-95.971556-95.971555-48.298666 48.298666 96.028444 95.971556-96.028444 95.971555 48.298666 48.298667z"  horiz-adv-x="1024" />

BIN
src/styles/icons/iconfont.ttf


BIN
src/styles/icons/iconfont.woff


BIN
src/styles/icons/iconfont.woff2


+ 65 - 7
src/styles/reset.scss

@@ -8,12 +8,6 @@ span,
 applet,
 object,
 iframe,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
 p,
 blockquote,
 pre,
@@ -26,7 +20,6 @@ cite,
 code,
 del,
 dfn,
-em,
 img,
 ins,
 kbd,
@@ -141,3 +134,68 @@ div {
     margin: 0;
   }
 }
+
+.markdown-body {
+  line-height: 1.6;
+  font-size: 16px;
+  color: #333;
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+  margin: 1em 0 0.5em;
+  font-weight: bold;
+}
+
+.markdown-body p {
+  margin: 1em 0;
+}
+
+.markdown-body ul,
+.markdown-body ol {
+  margin-left: 1.5em;
+  padding-left: 1em;
+}
+
+.markdown-body ul {
+  list-style-type: disc;
+}
+
+.markdown-body ol {
+  list-style-type: decimal;
+}
+
+.markdown-body li {
+  margin: 0.5em 0;
+}
+
+.markdown-body blockquote {
+  padding-left: 1em;
+  border-left: 4px solid #ddd;
+  color: #555;
+  margin: 1em 0;
+}
+
+.markdown-body a {
+  color: #0366d6;
+  text-decoration: underline;
+}
+
+.markdown-body code {
+  background: #f5f5f5;
+  padding: 0.2em 0.4em;
+  border-radius: 4px;
+  font-family: monospace;
+  font-size: 0.9em;
+}
+
+.markdown-body pre code {
+  display: block;
+  padding: 1em;
+  background: #f6f8fa;
+  overflow-x: auto;
+}

+ 13 - 0
src/styles/theme-g.scss

@@ -79,4 +79,17 @@
   --color-system-card-bg:#2B2F36;
   --color-system-border-1: #3F434A;
   --color-system-input-border: #656f7d;
+
+  
+  // AI Robot
+  --color-dialogue-bg: rgba(255,255,255,0.13);
+  --color-dialogue_container-border: rgba(255,255,255,0.20);
+  --color-carousel-card-bg: rgba(255,255,255,0.70);
+  --color-dialogue-icon-bg: linear-gradient(92deg, var(--1-gradient-ai-robot-0, #525CBA) 1.33%, var(--1-gradient-ai-robot-100, #724493) 99.63%);
+  --color-dialogue-text-bg: linear-gradient(92deg, var(--1-gradient-ai-robot-0, #525CBA) 1.33%, var(--1-gradient-ai-robot-100, #724493) 99.63%);
+  --color-dialogue_container-bg:rgba(255, 255, 255, 0.10);
+  --color-dialogue_title: linear-gradient(90deg, var(--1-gradient-ai-robot-faq-0, #FFA8C7) 1.77%, var(--1-gradient-ai-robot-faq-46, #5988f3) 46.77%);
+  --color-dialogue_content-bg:linear-gradient(117deg, var(--1-gradient-ai-robot-0, #525CBA) 4.31%, var(--1-gradient-ai-robot-15, #5A57B2) 14.24%, var(--1-gradient-ai-robot-38, #5F54AD) 29.71%, var(--1-gradient-ai-robot-59, #664EA2) 43.72%, var(--1-gradient-ai-robot-83, #694CA0) 59.35%, var(--1-gradient-ai-robot-100, #724493) 70.56%);
+  --color-arrow-hoverL: #FCEEE3;
+
 }

+ 41 - 2
src/styles/theme.scss

@@ -76,8 +76,6 @@
   --color-border: #eaebed;
   --color-select-border: #eaebed;
   --border-color-2: #eaebed;
-  --color-border-1: #e8eaee;
-  --color-border-2: #eaebed;
 
   --color-mune-active-bg: #fdf5f1;
 
@@ -282,6 +280,37 @@
   --color-system-border-1: #e8eaee;
   --color-system-input-border: #e8eaee;
   --color-personal-preference-bg: #f5f7fa;
+
+  // AI Robot
+  --color-dialogue-bg: #fff;
+  --color-dialogue_container-border: #fff;
+  --color-carousel-card-bg: #fff;
+  --color-dialogue-icon-bg: radial-gradient(50% 43% at 50% 54.79%, #d5b4f3 0%, #fff9fc 100%);
+  --color-dialogue-text-bg: linear-gradient(92deg, #eaecff 1.33%, #f1e3fb 99.63%);
+  --color-dialogue_title: linear-gradient(90deg, #a71549 1.77%, #06256e 46.77%);
+  --color-dialogue_container-bg: rgba(255, 255, 255, 0.5);
+  --color-dialogue_content-bg: linear-gradient(
+    117deg,
+    var(--1-gradient-ai-robot-0, #b5fef3) 4.31%,
+    var(--1-gradient-ai-robot-15, #f9eeec) 14.24%,
+    var(--1-gradient-ai-robot-38, #fbd3ee) 29.71%,
+    var(--1-gradient-ai-robot-59, #ddd5f7) 43.72%,
+    var(--1-gradient-ai-robot-83, #c8f4f3) 59.35%,
+    var(--1-gradient-ai-robot-100, #cadff8) 70.56%
+  );
+  --color-ai-chat-header-bg-gradient-begin: #eaecff;
+  --color-ai-chat-header-bg-gradient-end: #fefdff;
+  --color-ai-user-bubble-bg-gradient-begin: #ffede6;
+  --color-ai-user-bubble-bg-gradient-end: #f2f4f7;
+  --input-border: #eaebed;
+  --color-pause-btn-bg: #fff1e6;
+  --color-arrow-hoverL: #FCEEE3;
+
+  --color-output-type-bg: #4361ED;
+  --color-output-type-string-bg: #6C757E;
+  --color-output-select-text: #F9A725;
+  --color-loading-text: #b5b9bf;
+  --color-warning-tips-bg: #fff4d1;
 }
 
 :root.dark {
@@ -456,4 +485,14 @@
       fill: var(--color-white);
     }
   }
+
+  --color-ai-chat-header-bg-gradient-begin: #484f82;
+  --color-ai-chat-header-bg-gradient-end: #31363d;
+  --color-ai-user-bubble-bg-gradient-begin: #716763;
+  --color-ai-user-bubble-bg-gradient-end: #5c6a7d;
+  --input-border: #656f7d;
+  --color-pause-btn-bg: #453b36;
+  --color-loading-text: #818892;
+  --color-warning-tips-bg: #85681b;
+  --color-warning-tips-text: #edb82f;
 }

+ 1 - 0
src/views/AIApiLog/index.ts

@@ -0,0 +1 @@
+export { default } from './src/AIApiLog.vue'

+ 189 - 0
src/views/AIApiLog/src/AIApiLog.vue

@@ -0,0 +1,189 @@
+<script lang="ts" setup>
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import TableView from './components/TableView'
+
+const OperationSearch = ref()
+const filterRef: Ref<HTMLElement | null> = ref(null)
+const containerHeight = useCalculatingHeight(document.documentElement, 290, [filterRef])
+const searchData = ref({
+  inputModel: '',
+  startDate: '',
+  endDate: '',
+  aiModel: '',
+  comparator: 'thanOrEqual',
+  responseDuration: 0
+})
+
+const aiModelList = [
+  {
+    label: 'Deepseek-chat',
+    value: 'deepseekChat'
+  },
+  {
+    label: 'Deepseek-search',
+    value: 'deepseekSearch'
+  },
+  {
+    label: 'Claude 3.7 Sonnet',
+    value: 'claude'
+  }
+]
+
+const comparatorList = [
+  {
+    label: '>=',
+    value: 'thanOrEqual'
+  },
+  {
+    label: '=',
+    value: 'equal'
+  },
+  {
+    label: '<=',
+    value: 'lessOrEqual'
+  }
+]
+
+const tableRef = ref()
+
+const Search = () => {
+  tableRef.value.SearchOperationLog(searchData.value)
+}
+const DateChange = (date: any) => {
+  searchData.value.startDate = date[0]
+  searchData.value.endDate = date[1]
+  tableRef.value.SearchOperationLog(searchData.value)
+}
+</script>
+<template>
+  <div class="dashboard">
+    <div class="Title">AI API Log</div>
+    <div class="display">
+      <div class="heaer_top">
+        <div class="input-tips_filter">
+          <el-input
+            placeholder="Search Request ID、Question ID"
+            v-model="OperationSearch"
+            class="log_input"
+          >
+            <template #prefix>
+              <span class="iconfont_icon">
+                <svg class="iconfont icon_dark" aria-hidden="true">
+                  <use xlink:href="#icon-icon_search_b"></use>
+                </svg>
+              </span>
+            </template>
+          </el-input>
+        </div>
+
+        <div class="date-tips_filter">
+          <CalendarDate @DateChange="DateChange"></CalendarDate>
+        </div>
+        <div class="tips_filter">
+          <el-select v-model="searchData.aiModel" placeholder="AI Model">
+            <el-option
+              v-for="item in aiModelList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div class="comparator-tips_filter">
+          <span>Response Duration</span>
+          <el-select v-model="searchData.comparator" style="width: 70px; margin: 0 6px">
+            <el-option
+              v-for="item in comparatorList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            >
+            </el-option>
+          </el-select>
+          <el-input-number
+            v-model="searchData.responseDuration"
+            placeholder="s"
+            :controls="false"
+            :min="0"
+            style="width: 60px"
+          ></el-input-number>
+        </div>
+
+        <el-button class="el-button--dark" @click="Search">Search</el-button>
+      </div>
+    </div>
+    <TableView :height="containerHeight" ref="tableRef"></TableView>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding-left: 23.32px;
+  align-items: center;
+}
+.heaer_top {
+  margin-top: 6.57px;
+  margin-bottom: 8px;
+  padding-right: 8px;
+  display: flex;
+}
+
+.display {
+  border: 1px solid var(--color-border);
+  border-width: 0 0 1px 0;
+  padding-left: 23.52px;
+}
+:deep(.el-select__placeholder.is-transparent span) {
+  color: var(--tag-info-text-color) !important;
+}
+:deep(.ETD_title) {
+  margin-bottom: 0;
+}
+:deep(.ant-picker-range) {
+  width: 250px !important;
+  height: 32px;
+  background-color: var(--color-mode) !important;
+}
+.tips_filter {
+  flex: 1;
+  height: 30px;
+  max-width: 190px;
+  margin-right: 8px;
+}
+.input-tips_filter {
+  flex: 1;
+  max-width: 320px;
+  height: 32px;
+  margin-right: 8px;
+  :deep(.el-input__wrapper) {
+    height: 32px;
+  }
+}
+.date-tips_filter {
+  flex: 1;
+  max-width: 250px;
+  height: 32px;
+  margin-right: 8px;
+}
+.comparator-tips_filter {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  max-width: 260px;
+  height: 32px;
+  margin-right: 8px;
+}
+.dashboard {
+  position: relative;
+  background-color: var(--color-mode);
+}
+:deep(.log_input .el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-select-border);
+  border-radius: 6px;
+}
+</style>

+ 81 - 0
src/views/AIApiLog/src/components/LogDialog.vue

@@ -0,0 +1,81 @@
+<script setup lang="ts">
+const dialogVisible = ref(false)
+
+const openDialog = () => {
+  dialogVisible.value = true
+}
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog v-model="dialogVisible" class="log-dialog" title="AI API Log" width="1000" top="10vh">
+    <div class="request-section">
+      <div class="title">Request Content</div>
+      <div class="content">
+        Hello, I would like to check the status of my package. The tracking number is ABC123456789.
+      </div>
+    </div>
+    <el-divider style="margin: 16px 0" />
+    <div class="response-section">
+      <div class="title">Request Content</div>
+      <p class="content">
+        Hey there! For the package with tracking number ABC123456789, the latest update is: [insert
+        latest tracking status here, e.g., shipped, in transit, arrived at the delivery hub, out for
+        delivery, etc.]. You can see more details by clicking this link: [insert detailed tracking
+        link here].Hey there! For the package with tracking number ABC123456789, the latest update
+        is: [insert latest tracking status here, e.g., shipped, in transit, arrived at the delivery
+        hub, out for delivery, etc.]. You can see more details by clicking this link: [insert
+        detailed tracking link here].Hey there! For the package with tracking number ABC123456789,
+        the latest update is: [insert latest tracking status here, e.g., shipped, in transit,
+        arrived at the delivery hub, out for delivery, etc.]. You can see more details by clicking
+        this link: [insert detailed tracking link here].Hey there! For the package with tracking
+        number ABC123456789, the latest update is: [insert latest tracking status here, e.g.,
+        shipped, in transit, arrived at the delivery hub, out for delivery, etc.]. You can see more
+        details by clicking this link: [insert detailed tracking link here].Hey there! For the
+        package with tracking number ABC123456789, the latest update is: [insert latest tracking
+        status here, e.g., shipped, in transit, arrived at the delivery hub, out for delivery,
+        etc.]. You can see more details by clicking this link: [insert detailed tracking link
+        here].Hey there! For the package with tracking number ABC123456789, the latest update is:
+        [insert latest tracking status here, e.g., shipped, in transit, arrived at the delivery hub,
+        out for delivery, etc.]. You can see more details by clicking this link: [insert detailed
+        tracking link here].
+      </p>
+    </div>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.title {
+  margin-bottom: 8px;
+
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 22px;
+}
+.content {
+  line-height: 21px;
+}
+.request-section {
+  padding: 16px;
+  padding-bottom: 0;
+}
+.response-section {
+  padding: 16px;
+  padding-top: 0;
+  .content {
+    padding: 8px 16px 20px 16px;
+    border-radius: 6px;
+    background-color: var(--color-share-link-bg);
+  }
+}
+</style>
+<style>
+.log-dialog {
+  height: 80%;
+  .el-dialog__body {
+    padding: 0;
+  }
+}
+</style>

+ 1 - 0
src/views/AIApiLog/src/components/TableView/index.ts

@@ -0,0 +1 @@
+export { default } from './src/TableView.vue'

+ 458 - 0
src/views/AIApiLog/src/components/TableView/src/TableView.vue

@@ -0,0 +1,458 @@
+<script setup lang="ts">
+import { ref, nextTick, onMounted } from 'vue'
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import DownloadDialog from './components/DownloadDialog.vue'
+// import { autoWidth } from '@/utils/table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import dayjs from 'dayjs'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+import LogDialog from '../../LogDialog.vue'
+
+const props = defineProps({
+  height: {
+    type: Number,
+    default: 440
+  }
+})
+
+const tableOriginColumnsField = ref()
+const handleColumns = (columns: any, status?: string) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      sortable: true,
+      minWidth: 120,
+      showOverflow: true
+    }
+    // 设置插槽
+    if (item.type === 'status' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'status' }
+      }
+    } else if (item.type === 'link' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'link' }
+      }
+    } else if (item.type === 'mode' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'mode' },
+        formatter: ({ cellValue }: any) => {
+          return cellValue
+        }
+      }
+    }
+    // 格式化
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+
+// 获取表格列
+const getTableColumns = async () => {
+  tableLoadingColumn.value = true
+  await $api.getOperationTableColumns().then((res: any) => {
+    if (res.code === 200) {
+      tableData.value.columns = [
+        { type: 'checkbox', width: 50, fixed: 'left' },
+        ...handleColumns(res.data.OperationTableColumns),
+        { title: 'Action', width: 106, fixed: 'right', slots: { default: 'action' } }
+      ]
+      tableOriginColumnsField.value = res.data.OperationTableColumns
+    }
+  })
+  nextTick(() => {
+    // tableRef.value && autoWidth(tableData.value, tableRef.value)
+    tableLoadingColumn.value = false
+    selectedNumber.value = 0
+    selectedTableData.value = []
+  })
+}
+
+const pageInfo = ref({ pageNo: 1, pageSize: 20, total: 0 })
+const tempSearch = ref()
+// 获得表格数据后赋值
+const assignTableData = (data: any) => {
+  tableData.value.data = data.searchData || []
+  pageInfo.value.total = Number(data.rc) || 0
+  tempSearch.value = data.tmp_search
+  // 拥有所有字段的表格
+  setTimeout(() => {
+    allTable.value.columns = handleColumns(tableData.value.columns, 'all')
+    allTable.value.data = data.searchData || []
+    // 为了让导出的表格列宽度自适应
+    nextTick(() => {
+      // allTableRef.value && autoWidth(allTable.value, allTableRef.value)
+    })
+  }, 1000)
+}
+
+let searchdata: any = {}
+// 获取表格数据
+const getTableData = async (isPageChange?: boolean) => {
+  const rc = isPageChange ? pageInfo.value.total : -1
+  tableLoadingTableData.value = true
+  await $api
+    .SearchOperationLog({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc,
+      ...searchdata
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      selectedNumber.value = 0
+      selectedTableData.value = []
+      nextTick(() => {
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+const SearchOperationLog = (val: any) => {
+  searchdata = val
+  tableLoadingTableData.value = true
+  $api
+    .SearchOperationLog({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: -1,
+      ...val
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      selectedNumber.value = 0
+      selectedTableData.value = []
+      nextTick(() => {
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+onMounted(() => {
+  Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
+    nextTick(() => {
+      // tableRef.value && autoWidth(tableData.value, tableRef.value)
+    })
+  })
+})
+
+const tableRef = ref<VxeGridInstance>()
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  sortConfig: {
+    sortMethod: (params) => {
+      const { data, sortList } = params
+
+      // 如果没有排序条件,直接返回原数据
+      if (sortList.length === 0) return data
+
+      // 对数据进行多重排序
+      const sortedData = [...data].sort((a, b) => {
+        for (const { field, order } of sortList) {
+          const curColumn = tableOriginColumnsField.value.find((item: any) => item.field === field)
+          if (!curColumn) continue
+
+          const typeName = curColumn.type
+          const aValue = a[field]
+          const bValue = b[field]
+
+          const compareResult = (aValue: any, bValue: any) => {
+            // 如果 aValue 或 bValue 是 null 或 undefined,优先处理这些值
+            if (aValue == null && bValue == null) {
+              return 0 // 如果两个值都为 null 或 undefined,视为相等
+            } else if (aValue == null) {
+              return -1 // 如果 aValue 是 null,bValue 不是,则将 aValue 视为较小值
+            } else if (bValue == null) {
+              return 1 // 如果 bValue 是 null,aValue 不是,则将 bValue 视为较小值
+            }
+
+            if (typeName === 'datetime' || typeName === 'date' || typeName === 'time') {
+              return dayjs(aValue).unix() - dayjs(bValue).unix()
+            } else if (isNaN(Number(aValue)) || isNaN(Number(bValue))) {
+              return aValue.localeCompare(bValue)
+            } else {
+              return Number(aValue) - Number(bValue)
+            }
+          }
+
+          const result = compareResult(aValue, bValue)
+          if (result !== 0) {
+            return order === 'asc' ? result : -result
+          }
+        }
+
+        return 0 // 如果所有字段都相等
+      })
+
+      return sortedData
+    }
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+const allTableRef = ref<VxeGridInstance>()
+const allTable = ref<VxeGridProps<any>>({
+  columns: [],
+  data: [],
+  showHeaderOverflow: true,
+  showOverflow: true,
+  scrollY: { enabled: true, oSize: 5, gt: 2 },
+  scrollX: { enabled: true, gt: 2 },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const downloadDialogRef = ref()
+const handleDownload = () => {
+  const curSelectedColumns: string[] = []
+  tableRef.value?.columns?.forEach((item: any) => {
+    if (item.field) {
+      curSelectedColumns.push(item.title)
+    }
+  })
+  downloadDialogRef.value.openDialog(
+    curSelectedColumns,
+    selectedNumber.value || pageInfo.value.total
+  )
+}
+
+const exportLoading = ref(false)
+// 获取导出表格数据
+const getExportTableData = (status: number) => {
+  // 如果有选中表格行数据,那么只到处选中的数据
+  if (selectedNumber.value > 0) {
+    exportTable(status)
+    return
+  }
+  exportLoading.value = true
+  const buildColumnString = (columns: any[]): string => {
+    return columns
+      .filter((item) => item.field)
+      .map((item) => item.title)
+      .join(',')
+  }
+
+  let column = ''
+  if (status === 1) {
+    column = buildColumnString(tableData.value.columns)
+  } else {
+    column = buildColumnString(allTable.value.columns)
+  }
+  $api
+    .OperationLogDownload({
+      selected_fields: column,
+      tmp_search: tempSearch.value
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        allTable.value.data = res.data.Data || []
+        nextTick(() => {
+          exportLoading.value = false
+          // 导出数据
+          exportTable(status)
+        })
+      }
+    })
+    .finally(() => {
+      exportLoading.value = false
+    })
+}
+// 导出表格 status: 1 导出当前表格 2 导出所有字段表格
+const exportTable = (status: number) => {
+  const exportConfig: any = {
+    type: 'xlsx',
+    message: false,
+    filename: `Chat Log_${dayjs().format('YYYYMMDDHH[h]mm[m]ss[s]')}`
+  }
+  if (status === 1) {
+    exportConfig.columnFilterMethod = ({ column }: any) => {
+      const index = tableData.value.columns.findIndex((item: any) => item.field === column.field)
+      // 排除复选框列
+      return column.field && index !== -1
+    }
+    exportConfig.columns = tableData.value.columns
+  }
+  if (selectedNumber.value > 0) {
+    exportConfig.dataFilterMethod = ({ row }: any) => {
+      const index = selectedTableData.value.findIndex(
+        (item: any) => item._X_ROW_KEY === row._X_ROW_KEY
+      )
+      return index !== -1
+    }
+  }
+  allTableRef.value?.exportData(exportConfig)
+}
+
+const tableLoadingColumn = ref(false)
+const tableLoadingTableData = ref(false)
+
+const selectedNumber = ref(0)
+const selectedTableData = ref([])
+// 复选框选中事件
+const handleCheckboxChange = ({ records }: any) => {
+  selectedNumber.value = records.length
+  selectedTableData.value = records
+}
+const handleCheckAllChange = ({ records }: any) => {
+  selectedNumber.value = records.length
+  selectedTableData.value = records
+}
+
+const logDialogRef = ref()
+const handleLogDetail = (row) => {
+  logDialogRef.value.openDialog(row)
+}
+
+defineExpose({
+  SearchOperationLog
+})
+</script>
+
+<template>
+  <div
+    style="padding: 0px 20px"
+    class="table-box"
+    v-loading.fullscreen.lock="exportLoading"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
+    element-loading-background="rgb(43, 47, 54, 0.7)"
+  >
+    <div class="table-tools">
+      <div class="left-total-records">{{ selectedNumber }} Selected</div>
+      <div class="right-tools-btn">
+        <el-button class="el-button--main el-button--pain-theme" @click="handleDownload">
+          <span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
+          Download
+        </el-button>
+      </div>
+    </div>
+    <vxe-grid
+      ref="tableRef"
+      v-vloading="tableLoadingTableData || tableLoadingColumn"
+      :height="props.height"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+      @checkbox-change="handleCheckboxChange"
+      @checkbox-all="handleCheckAllChange"
+    >
+      <!-- 空数据时的插槽 -->
+      <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
+        <VEmpty></VEmpty>
+      </template>
+      <!-- action操作栏的插槽 -->
+      <template #action="{ row }">
+        <el-button
+          style="height: 24px; padding: 8px 4px; padding-left: 5px; font-size: 12px"
+          @click="handleLogDetail(row)"
+        >
+          <span
+            style="margin-right: 2px; font-size: 15px"
+            class="font_family icon-icon_ai_api_log_b"
+          ></span
+          >AI API Log
+        </el-button>
+      </template>
+    </vxe-grid>
+    <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
+    <div class="bottom-pagination">
+      <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
+      <div class="right-pagination">
+        <el-pagination
+          v-model:current-page="pageInfo.pageNo"
+          v-model:page-size="pageInfo.pageSize"
+          :page-sizes="[20, 50, 100, 150]"
+          :pager-count="3"
+          background
+          layout="sizes, prev, pager, next"
+          :total="pageInfo.total"
+          @size-change="getTableData(true)"
+          @current-change="getTableData(true)"
+        />
+      </div>
+    </div>
+    <DownloadDialog @export="getExportTableData" ref="downloadDialogRef" />
+    <LogDialog ref="logDialogRef" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.table-tools {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  height: 48px;
+  padding: 8px 0;
+
+  .left-total-records {
+    font-size: 16px;
+    font-weight: 700;
+    line-height: 32px;
+  }
+}
+
+.bottom-pagination {
+  display: flex;
+  justify-content: space-between;
+  height: 40px;
+  margin-top: -1px;
+  padding: 4px 8px;
+  border: 1px solid var(--color-border);
+  border-radius: 0 0 12px 12px;
+
+  .left-total-records {
+    line-height: 32px;
+  }
+
+  .right-pagination {
+    display: flex;
+    align-items: center;
+  }
+}
+.table-box {
+  position: relative;
+  overflow: hidden;
+
+  .all-table {
+    position: absolute;
+    top: -100000px;
+    width: 20px;
+  }
+}
+</style>

+ 190 - 0
src/views/AIApiLog/src/components/TableView/src/components/DownloadDialog.vue

@@ -0,0 +1,190 @@
+<script setup lang="ts">
+const dialogVisible = ref(false)
+
+const openDialog = (selectedColumns: string[], slectedDataNumber: number) => {
+  selectedDataNumber.value = slectedDataNumber
+  columns.value = selectedColumns
+  dialogVisible.value = true
+}
+
+const isShowSelectColumn = ref(false)
+
+const downloadFilter = ref(1)
+const selectedDataNumber = ref(0)
+
+const columns = ref()
+
+const emits = defineEmits<{ export: [number] }>()
+const handleDownload = () => {
+  emits('export', downloadFilter.value)
+}
+
+const clearData = () => {
+  isShowSelectColumn.value = false
+  downloadFilter.value = 1
+}
+
+defineExpose({
+  openDialog,
+  handleDownload
+})
+</script>
+
+<template>
+  <div>
+    <el-dialog @close="clearData" v-model="dialogVisible" title="Download File" width="540">
+      <div class="download-dialog">
+        <div class="select-data">
+          <div style="display: inline-block">
+            Select data on your Opeartion Log list:<span style="color: var(--color-theme)">{{
+              selectedDataNumber
+            }}</span>
+          </div>
+        </div>
+        <div class="download-filter">
+          <el-radio-group v-model="downloadFilter">
+            <el-radio :value="1"
+              >Download with selected columns
+              <span class="column-number">{{ columns.length }}</span>
+              <SeeAllIcon v-model="isShowSelectColumn" />
+            </el-radio>
+            <div
+              v-if="isShowSelectColumn"
+              class="select-columns"
+              :class="{ show: isShowSelectColumn }"
+            >
+              <div class="title">Selected columns</div>
+              <div class="content">
+                <div class="column-item" v-for="item in columns" :key="item">{{ item }}</div>
+              </div>
+            </div>
+            <el-radio :value="2">Download with all columns</el-radio>
+          </el-radio-group>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="cancel-btn" type="default" @click="dialogVisible = false"
+            >Cancel</el-button
+          >
+          <el-button class="download-btn el-button--dark" @click="handleDownload"
+            ><span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
+            Download</el-button
+          >
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.download-dialog {
+  color: var(--color-neutral-1);
+}
+
+.select-data {
+  font-weight: 700;
+}
+
+.data-filter {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  max-height: 120px;
+  margin-top: 8px;
+  overflow: auto;
+
+  .filter-item {
+    height: 22px;
+    padding: 0px 8px;
+    background-color: var(--color-download-file-filter-tag-bg);
+    border-radius: 12px;
+    line-height: 22px;
+    font-size: 12px;
+  }
+}
+
+.download-filter {
+  margin-top: 16px;
+
+  .el-radio-group {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+
+    .el-radio {
+      height: 40px;
+      align-items: center;
+    }
+
+    :deep(.el-radio__label) {
+      margin-top: 2px;
+      font-weight: 700;
+      color: var(--color-neutral-1);
+    }
+
+    .column-number {
+      padding: 3px 5px;
+      background-color: var(--color-theme);
+      border-radius: 12px;
+      font-size: 12px;
+      font-weight: 700;
+      color: #fff;
+    }
+
+    .see-all-btn {
+      margin-left: 8px;
+      color: var(--color-theme);
+      font-size: 12px;
+    }
+
+    .select-columns {
+      max-height: 350px;
+      padding: 8px;
+      margin-top: 8px;
+      background-color: var(--color-dialog-header-bg);
+      border-radius: 6px;
+      overflow: hidden;
+
+      &.show {
+        max-height: 500px;
+      }
+
+      .title {
+        font-size: 12px;
+        font-weight: 700;
+      }
+
+      .content {
+        display: flex;
+        flex-wrap: wrap;
+        margin-top: 8px;
+        gap: 8px;
+
+        .column-item {
+          height: 22px;
+          padding: 0px 8px;
+          background-color: var(--color-download-file-selected-column-tag-bg);
+          line-height: 22px;
+          border-radius: 12px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+
+.dialog-footer {
+  .el-button {
+    height: 40px;
+  }
+
+  .cancel-btn {
+    width: 115px;
+  }
+
+  .download-btn {
+    width: 136px;
+  }
+}
+</style>

+ 1 - 0
src/views/AIRobotChat/index.ts

@@ -0,0 +1 @@
+export { default } from './src/AIRobotChat.vue'

+ 654 - 0
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -0,0 +1,654 @@
+<script setup lang="ts">
+import AutoResizeTextarea from './components/AutoResizeTextarea.vue'
+import LoadingDots from './components/LoadingDots.vue'
+import AIQuestions from './components/AIQuestions.vue'
+import userBubbleLight from './image/userBubbleLight.png'
+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 MarkdownIt from 'markdown-it'
+import 'github-markdown-css/github-markdown.css'
+
+const md = new MarkdownIt({
+  html: true,
+  linkify: true,
+  typographer: true,
+  breaks: true
+})
+
+const renderedMessage = (content) => {
+  return md.render(content)
+}
+
+const themeStore = useThemeStore()
+const modalSize = ref('large')
+const userQuestion = ref()
+
+const isFooterInputFocus = ref(false)
+
+const userBubbleImg = computed(() => {
+  return themeStore.theme === 'light' ? userBubbleLight : userBubbleDark
+})
+const robotBubbleImg = computed(() => {
+  return themeStore.theme === 'light' ? robotBubbleLight : robotBubbleDark
+})
+
+// 是否显示footer的顶部阴影
+const isShowFooterShadow = ref(false)
+// 是否显示header的底部阴影
+const isShowHeaderShadow = ref(false)
+
+const loadingAnswer = ref(false) // 是否正在加载答案
+interface MessageItem {
+  type: 'robot' | 'user'
+  content: string
+  feedback?: 'good' | 'noGood' | '' // 反馈结果
+  isShowFeedback?: boolean // 是否展示反馈样式
+  isAnswer?: boolean // 是否为用户问题的答案,是则才能展示反馈组件
+  isError?: boolean // 是否为错误消息
+  isCancel?: boolean // 是否为取消消息
+}
+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?'
+  },
+  {
+    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.'
+  }
+])
+messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
+onMounted(() => {
+  scrollToBottom()
+})
+
+watch(
+  () => messages.value,
+  (newVal) => {
+    // 将消息存储到 sessionStorage 中
+    sessionStorage.setItem('AIChat', JSON.stringify(newVal))
+  },
+  { immediate: true, deep: true }
+)
+
+// 用户问题请求时间
+const queryTime = ref(-1)
+// 当前用户问题回复进度
+const progressStatus = {
+  init: 'You can click on Frequently Asked Questions above or type your own question',
+  '0': 'Thinking about your question...',
+  '15': 'Searching for relevant data, please wait...',
+  '30': 'This query is complex and may take more time',
+  '60': 'You may try simplifying your question or selecting a Frequently Asked Question',
+  '120': 'Sorry, the query failed. Please try again later or select a Frequently Asked Question',
+  cancel: 'You have stopped this answer'
+}
+
+const isShowTips = ref(false) // 是否展示提示信息
+
+const progressInterval = ref()
+const handleSend = (question, isPresetQuestion = true) => {
+  if (!question) return
+
+  if (loadingAnswer.value) {
+    isShowTips.value = true
+    setTimeout(() => {
+      isShowTips.value = false
+    }, 2000)
+    return
+  }
+  loadingAnswer.value = true
+
+  messages.value.push({
+    type: 'user',
+    content: question
+  })
+  !isPresetQuestion ? (userQuestion.value = '') : ''
+  queryTime.value = 0
+  messages.value.push({
+    type: 'robot',
+    content: progressStatus[0]
+  })
+  autoScroll.value = true
+  scrollToBottom()
+
+  progressInterval.value = setInterval(() => {
+    queryTime.value++
+    // 定义时间点与对应状态的映射
+    const timeToStatusMap = {
+      15: 15,
+      30: 30,
+      60: 60
+    }
+    // 获取最后一个消息对象
+    const lastMessage = messages.value[messages.value.length - 1]
+    if (queryTime.value === 120) {
+      clearInterval(progressInterval.value)
+      lastMessage.content = progressStatus[120]
+      lastMessage.isError = true
+      queryTime.value = -1
+      loadingAnswer.value = false
+      return
+    }
+    if (timeToStatusMap[queryTime.value] !== undefined) {
+      lastMessage.content = progressStatus[queryTime.value]
+    }
+  }, 1000)
+}
+const showScrollButton = ref(false) // 控制按钮显示
+const messagesRef = ref()
+const autoScroll = ref(true)
+
+function handleScroll() {
+  const el = messagesRef.value
+  const threshold = 50 // 到底部的距离阈值
+  const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold
+  autoScroll.value = atBottom
+
+  showScrollButton.value = el.scrollHeight > el.clientHeight && !autoScroll.value
+
+  isShowFooterShadow.value =
+    messagesRef.value?.scrollTop <
+    messagesRef.value?.scrollHeight - messagesRef.value?.clientHeight - 1
+
+  isShowHeaderShadow.value = !!messagesRef.value?.scrollTop
+}
+function scrollToBottom() {
+  nextTick(() => {
+    if (messagesRef.value) {
+      messagesRef.value.scrollTop = messagesRef.value.scrollHeight
+    }
+  })
+}
+
+const simulateStreamingMarkdown = () => {
+  loadingAnswer.value = true
+  const chunks = [
+    '# 欢迎使用这是一个用于测试的 Markdown 文本 Markdown\n\n',
+    '这是一个用于测试的这是一个用于测试的 Markdown 文本 **Markdown** 文本。\n\n',
+    '你可以使用这是一个用于测试的 Markdown 文本 *斜体* 来强调重点。\n\n',
+    '也可以使用这是一个用于测试的 Markdown 文本 [链接](https://example.com) 引导用户跳转。\n\n',
+    '- 支持无这是一个用于测试的 Markdown 文本这是一个用于测试的 Markdown 文本序列表\n',
+    '- 非常适合这是一个用于测试的 Markdown 文本这是一个用于测试的 Markdown 文本这是一个用于测试的 Markdown 文本逐行流式显示\n\n',
+    '这是一段这是一个用于测试的 Markdown 文本这是一个用于测试的 Markdown 文本这是一个用于测试的 Markdown 文本测试!\n'
+  ]
+
+  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) {
+        scrollToBottom()
+      }
+    } else {
+      clearInterval(interval)
+    }
+  }, 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 emit = defineEmits(['close'])
+// 关闭聊天窗口
+const handleClose = () => {
+  progressInterval.value && clearInterval(progressInterval.value)
+  emit('close')
+}
+
+defineExpose({
+  handleSend
+})
+</script>
+
+<template>
+  <div class="ai-robot" :style="{ width: modalSize === 'large' ? '1000px' : '484px' }">
+    <div class="top-section" :class="[isShowHeaderShadow ? 'box-shadow' : '']">
+      <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>
+        </div>
+      </div>
+      <AIQuestions :modalSize="modalSize" @question="handleSend"></AIQuestions>
+      <div class="warning-tips" v-if="isShowTips">
+        <div class="warning-bg">
+          <span class="warning-icon font_family icon-icon_warning_fill_b"></span>
+        </div>
+        <span>Answer in progress, please wait.</span>
+      </div>
+    </div>
+    <div class="chat-messages" ref="messagesRef" @scroll="handleScroll">
+      <div
+        class="message-item"
+        :class="[
+          msg.type === 'user' ? 'user-bubble' : 'robot-bubble',
+          (queryTime > -1 && queryTime < 120 && index === messages.length - 1) || msg.isCancel
+            ? 'query-style'
+            : ''
+        ]"
+        v-for="(msg, index) in messages"
+        :key="index"
+        @mouseenter="msg.isShowFeedback = true"
+        @mouseleave="msg.isShowFeedback = false"
+      >
+        <!-- 请求失败后的提示icon   -->
+        <div class="pause-bg" v-if="msg.isError">
+          <span class="pause-icon font_family icon-icon_warning_fill_b"></span>
+        </div>
+        <!-- loading icon -->
+        <img
+          class="loading-img"
+          v-if="queryTime > -1 && queryTime < 120 && index === messages.length - 1"
+          src="./image/icon_loading.png"
+          alt=""
+        />
+        <span v-if="!msg.isAnswer">{{ msg.content }}</span>
+        <div v-else>
+          <div v-html="renderedMessage(msg.content)" class="markdown-body"></div>
+          <LoadingDots
+            v-if="index === messages.length - 1 && msg.isAnswer && loadingAnswer"
+          ></LoadingDots>
+          <div></div>
+        </div>
+        <!-- 评价  -->
+        <div class="review" v-if="msg.isShowFeedback && msg.isAnswer">
+          <el-button
+            v-if="msg.feedback !== 'good'"
+            class="el-button--text"
+            @click="msg.feedback = 'good'"
+          >
+            <span class="font_family icon-icon_good_b"></span>
+          </el-button>
+          <div v-if="msg.feedback === 'good'" 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'"
+            class="el-button--text"
+            @click="msg.feedback = 'noGood'"
+          >
+            <span class="font_family icon-icon_notgood_b"></span>
+          </el-button>
+          <div v-if="msg.feedback === 'noGood'" style="width: 16px; text-align: center">
+            <span
+              style="color: var(--color-theme); font-size: 14px"
+              class="font_family icon-icon_notgood__filled_b"
+            ></span>
+          </div>
+        </div>
+
+        <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"
+          v-if="index === messages.length - 1 && queryTime > 29 && queryTime < 120"
+          @click="handlePause"
+        >
+          <div class="dot"></div>
+        </div>
+      </div>
+      <!-- 滚动到底部icon -->
+      <div v-if="showScrollButton" class="scroll-to-bottom-btn" @click="scrollToBottom">
+        <span class="font_family icon-icon_movedown_b"></span>
+      </div>
+    </div>
+
+    <div
+      class="footer"
+      :class="[isFooterInputFocus ? 'focus-style' : '', isShowFooterShadow ? 'box-shadow' : '']"
+    >
+      <div class="footer-input">
+        <AutoResizeTextarea
+          style="flex: 1"
+          v-model="userQuestion"
+          :placeholder="'Type your question here...'"
+          @focus="isFooterInputFocus = true"
+          @blur="isFooterInputFocus = false"
+        />
+        <el-button @click="simulateStreamingMarkdown">测试</el-button>
+        <div
+          class="input-icon"
+          :class="[!userQuestion || queryTime !== -1 ? 'disable' : '']"
+          @click="handleSend(userQuestion, false)"
+        >
+          <span class="font_family icon-icon_send_b"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.ai-robot {
+  position: absolute;
+  top: 74px;
+  right: 24px;
+  height: calc(100% - 98px);
+  z-index: 4000;
+  display: flex;
+  flex-direction: column;
+  border-radius: 12px;
+  border: 1px solid var(--color-border);
+  box-shadow: 4px 4px 32px 0px rgba(0, 0, 0, 0.2);
+  background-color: var(--color-dialog-body-bg);
+  overflow: hidden;
+  .top-section {
+    position: relative;
+    padding-bottom: 16px;
+    background: linear-gradient(
+      to bottom,
+      var(--color-ai-chat-header-bg-gradient-begin) 10%,
+      var(--color-ai-chat-header-bg-gradient-begin) 10%,
+      var(--color-ai-chat-header-bg-gradient-end) 100%
+    );
+    &.box-shadow {
+      box-shadow: -2px 0px 12px rgba(0, 0, 0, 0.1);
+    }
+    .header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      height: 64px;
+      margin-bottom: 6px;
+      padding: 0 16px;
+      .welcome {
+        font-size: 18px;
+        font-weight: 700;
+      }
+      .option-icon {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        .font_family {
+          font-size: 16px;
+          cursor: pointer;
+          &:hover {
+            color: var(--color-theme);
+          }
+        }
+      }
+    }
+    .warning-tips {
+      position: absolute;
+      top: 60px;
+      left: 17px;
+      width: calc(100% - 32px);
+      height: 40px;
+      padding-top: 14px;
+      background-color: var(--color-warning-tips-bg);
+      border-radius: 6px;
+      text-align: center;
+      .warning-bg {
+        position: relative;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        height: 12px;
+        width: 12px;
+        margin-right: 6px;
+        border-radius: 50%;
+        background-color: #fff;
+      }
+      .warning-icon {
+        position: absolute;
+        top: -3px;
+        border-radius: 50%;
+        border: none;
+      }
+      span {
+        color: #edb82f;
+      }
+    }
+  }
+
+  .chat-messages {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    padding: 0 16px 18px;
+    overflow: auto;
+    .message-item {
+      position: relative;
+      display: inline-block;
+      padding: 11px 8px;
+      margin-bottom: 7px;
+      border-radius: 12px;
+      background-color: var(--scoring-bg-color);
+      .review {
+        position: absolute;
+        bottom: -24px;
+        left: 0;
+        display: flex;
+        align-items: center;
+        gap: 13px;
+        width: 100%;
+        height: 30px;
+        margin-top: 10px;
+        padding-left: 30px;
+        padding-top: 5px;
+
+        button.el-button + .el-button {
+          margin-left: 0px;
+        }
+      }
+      .pause-bg {
+        position: relative;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        height: 12px;
+        width: 12px;
+        margin-right: 6px;
+        border-radius: 50%;
+        background-color: #fff;
+      }
+      .pause-icon {
+        position: absolute;
+        top: -2px;
+        color: #c9353f;
+        border-radius: 50%;
+        border: none;
+      }
+      .el-button--text {
+        height: 16px;
+        width: 16px;
+        span {
+          color: var(--color-neutral-2);
+          font-size: 14px;
+        }
+        &:hover {
+          span {
+            color: var(--color-theme);
+          }
+        }
+      }
+
+      .loading-img {
+        width: 16px;
+        height: 16px;
+        margin-top: -1px;
+        margin-right: 4px;
+        animation: loading-rotate 2s linear infinite;
+      }
+      .markdown-body {
+        background: transparent;
+      }
+      .pause-btn {
+        position: absolute;
+        right: -22px;
+        top: 13px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 16px;
+        width: 16px;
+        border-radius: 50%;
+        background-color: var(--color-customize-column-right-section-bg);
+        .dot {
+          height: 5px;
+          width: 5px;
+          border-radius: 1px;
+          background-color: var(--color-theme);
+        }
+      }
+    }
+    .query-style {
+      span {
+        color: #b5b9bf;
+      }
+    }
+    .robot-bubble {
+      background: var(--scoring-bg-color);
+      align-self: flex-start;
+      .robot-bubble-img {
+        position: absolute;
+        left: -1px;
+        bottom: -7px;
+      }
+    }
+    .user-bubble {
+      align-self: flex-end;
+      background: linear-gradient(
+        to right,
+        var(--color-ai-user-bubble-bg-gradient-begin),
+        var(--color-ai-user-bubble-bg-gradient-end)
+      );
+      .user-bubble-img {
+        position: absolute;
+        right: 0;
+        bottom: -7px;
+      }
+    }
+    .scroll-to-bottom-btn {
+      position: absolute;
+      right: 50%;
+      transform: translateX(50%);
+      bottom: 58px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 32px;
+      width: 32px;
+      border-radius: 50%;
+      // 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的模糊效果 */
+      span {
+        color: #2b2f36;
+      }
+      cursor: pointer;
+      &:hover {
+        background-color: #fceee3;
+        span {
+          color: var(--color-theme);
+        }
+      }
+    }
+  }
+  .footer {
+    padding: 12px 16px;
+    &.box-shadow {
+      box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.1);
+    }
+    &.focus-style {
+      .footer-input {
+        border: 1px solid var(--color-theme);
+      }
+      .input-icon {
+        background-color: var(--color-theme);
+        span {
+          color: #fff;
+        }
+        &:hover {
+          background-color: #d56200;
+        }
+      }
+    }
+  }
+  .footer-input {
+    display: flex;
+    align-items: flex-end;
+    gap: 12px;
+    padding: 4px 12px;
+    padding-right: 4px;
+    border: 1px solid var(--input-border);
+    border-radius: 20px;
+    box-sizing: border-box;
+
+    .input-icon {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 32px;
+      width: 32px;
+      padding: 1px 0 0 2px;
+      border-radius: 50%;
+      cursor: pointer;
+      &.disable {
+        cursor: not-allowed;
+      }
+    }
+  }
+
+  @keyframes loading-rotate {
+    0% {
+      transform: rotate(0deg);
+    }
+
+    100% {
+      transform: rotate(360deg);
+    }
+  }
+}
+</style>

+ 289 - 0
src/views/AIRobotChat/src/components/AIQuestions.vue

@@ -0,0 +1,289 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+
+const props = defineProps<{
+  modalSize: String
+}>()
+
+const DeQuestions = ref([
+  {
+    label: 'List shipments with ETA changes in the last 30 days.',
+    value: 'List shipments with ETA changes in the last 30 days.',
+    isLong: false
+  },
+  {
+    label: 'Shipments arriving in the next 7 days.',
+    value: 'Shipments arriving in the next 7 days.',
+    isLong: false
+  },
+  {
+    label: 'Show shipments delayed in the last 30 days.',
+    value: 'Show shipments delayed in the last 30 days.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone updates in the last 7 days.',
+    value: 'List shipments with milestone updates in the last 7 days.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone in the last 7 days2sdsa.',
+    value: 'List shipments with milestone in the last 7 days2sdsa.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone updates 7 days3fef.',
+    value: 'List shipments with milestone updates 7 days3fef.',
+    isLong: false
+  },
+  {
+    label:
+      'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    value:
+      'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    isLong: true
+  },
+  {
+    label:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111122.',
+    value:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111122.',
+    isLong: true
+  },
+  {
+    label:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111133.',
+    value:
+      'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111133.',
+    isLong: true
+  }
+])
+const itemGroups = ref([])
+const pages = ref([])
+// 随机显示方法
+const prepareGroups = () => {
+  const groups = []
+  let currentGroup = []
+  let currentHeight = 0
+
+  DeQuestions.value.forEach((item) => {
+    const itemHeight = item.isLong ? 2 : 1
+
+    if (currentHeight + itemHeight > 4) {
+      groups.push(currentGroup)
+      currentGroup = []
+      currentHeight = 0
+    }
+
+    currentGroup.push(item)
+    currentHeight += itemHeight
+  })
+
+  // 添加最后一组
+  if (currentGroup.length > 0) {
+    groups.push(currentGroup)
+  }
+
+  itemGroups.value = groups
+}
+// 智能分页算法
+const generatePages = () => {
+  const result = []
+  let currentPage = { row1: [], row2: [] }
+  let cursor = 0
+
+  while (cursor < DeQuestions.value.length) {
+    // 处理第一排
+    const currentItem = DeQuestions.value[cursor]
+    // 第一排逻辑
+    if (currentItem.isLong) {
+      // 长文本独占第一排
+      currentPage.row1.push(currentItem)
+      cursor++
+    } else {
+      // 尝试取两个短文本
+      const nextItem = DeQuestions.value[cursor + 1]
+      if (nextItem && !nextItem.isLong) {
+        currentPage.row1.push(currentItem, nextItem)
+        cursor += 2
+      } else {
+        currentPage.row1.push(currentItem)
+        cursor++
+      }
+    }
+
+    // 处理第二排
+    const remaining = DeQuestions.value.slice(cursor)
+    if (remaining.length > 0) {
+      const secondItem = remaining[0]
+
+      if (secondItem.isLong) {
+        currentPage.row2.push(secondItem)
+        cursor++
+      } else {
+        const nextSecondItem = remaining[1]
+        if (nextSecondItem && !nextSecondItem.isLong) {
+          currentPage.row2.push(secondItem, nextSecondItem)
+          cursor += 2
+        } else {
+          currentPage.row2.push(secondItem)
+          cursor++
+        }
+      }
+    }
+
+    // 保存当前页
+    result.push(currentPage)
+
+    // 重置页面
+    currentPage = { row1: [], row2: [] }
+  }
+  pages.value = result.filter((p) => p.row1.length > 0)
+}
+onMounted(() => {
+  prepareGroups()
+  generatePages()
+})
+
+const emit = defineEmits<{ question: [string] }>()
+const clickQuestion = (question) => {
+  emit('question', question)
+}
+</script>
+<template>
+  <div class="flex_center">
+    <div class="dialogue_content">
+      <div class="dialogue_content_title">
+        <div class="dialogue_title_left">
+          <img
+            class="dialogue_title_left_img"
+            src="../image/icon_ai_robot48_b@2x.png"
+            width="48px"
+          />
+          <div class="dialogue_title_left_text">Frequently Asked Questions</div>
+        </div>
+      </div>
+      <el-carousel
+        v-if="props.modalSize === 'large'"
+        class="carousel large_carousel"
+        :autoplay="false"
+        height="115px"
+      >
+        <el-carousel-item v-for="(page, index) in pages" :key="index">
+          <div class="dialogue_container dialogue_container_large">
+            <div class="double-row-layout">
+              <!-- 第一排 -->
+              <div class="row first-row">
+                <template v-for="item in page.row1" :key="item.id">
+                  <div
+                    @click="clickQuestion(item.label)"
+                    class="dialogue_content_item"
+                    :class="{ 'long-item': item.isLong }"
+                  >
+                    {{ item.label }}
+                  </div>
+                </template>
+              </div>
+              <!-- 第二排 -->
+              <div class="row second-row">
+                <template v-for="item in page.row2" :key="item.id">
+                  <div
+                    @click="clickQuestion(item.label)"
+                    class="dialogue_content_item"
+                    :class="{ 'long-item': item.isLong }"
+                  >
+                    {{ item.label }}
+                  </div>
+                </template>
+              </div>
+            </div>
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+      <el-carousel v-else class="carousel small_carousel" :autoplay="false" height="190px">
+        <el-carousel-item v-for="(group, index) in itemGroups" :key="index">
+          <div class="dialogue_container">
+            <div
+              class="dialogue_content_item"
+              v-for="item in group"
+              :key="item.label"
+              @click="clickQuestion(item.label)"
+              :class="{ 'long_item': item.isLong }"
+            >
+              {{ item.label }}
+            </div>
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.dialogue_content {
+  background: var(--color-dialogue_content-bg);
+}
+.dialogue_title_left {
+  position: relative;
+}
+.dialogue_title_left_img {
+  position: absolute;
+  top: -20px;
+}
+.dialogue_title_left_text {
+  background: var(--color-dialogue_title);
+  -webkit-background-clip: text;
+  background-clip: text;
+  color: transparent;
+  font-size: 14px;
+  font-weight: 700;
+  margin: 5px 0 0 55px;
+}
+.small_carousel {
+  width: 452px;
+}
+.large_carousel {
+  width: 968px;
+}
+.double-row-layout {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.row {
+  flex: 1;
+  display: flex;
+  min-height: 0; /* 防止内容溢出 */
+}
+.dialogue_content_item:hover {
+  background-color: var(--color-arrow-hoverL);
+  color: var(--color-theme);
+}
+.long-item {
+  grid-column: span 2;
+}
+.dialogue_container_large {
+  width: 936px;
+  height: 84px;
+}
+.dialogue_content_item {
+  width: 100%;
+  height: 32px;
+  padding: 5.5px 8px;
+  border-radius: 6px;
+  background-color: var(--color-dialogue-bg);
+  margin-right: 4px;
+  word-break: break-word;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: start;
+  text-align: center;
+  cursor: pointer;
+  min-width: 0; /* 防止文本溢出 */
+}
+:deep(.el-carousel__item--card, .el-carousel__item.is-animating) {
+  padding-bottom: 0 !important;
+}
+</style>

+ 70 - 0
src/views/AIRobotChat/src/components/AutoResizeTextarea.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+const inputVModel = defineModel({ type: String })
+const props = defineProps<{
+  placeholder?: string
+}>()
+
+watch(
+  () => inputVModel.value,
+  () => {
+    textareaRef.value.style.height = '30px' // 重置高度
+  }
+)
+const textareaRef = ref(null)
+// 实现自适应高度(最多 4 行)
+const resize = () => {
+  const el = textareaRef.value
+  if (!el) return
+
+  el.style.height = 'auto' // 先清空旧高度
+  const scrollHeight = el.scrollHeight
+
+  const maxHeight = 92 // 四行时高度
+
+  if (scrollHeight <= maxHeight) {
+    el.style.overflowY = 'hidden'
+    el.style.height = scrollHeight + 'px'
+  } else {
+    el.style.overflowY = 'auto'
+    el.style.height = maxHeight + 'px'
+  }
+}
+const emit = defineEmits(['focus', 'blur'])
+</script>
+
+<template>
+  <textarea
+    ref="textareaRef"
+    v-model="inputVModel"
+    class="input-area"
+    rows="1"
+    :placeholder="props.placeholder"
+    @input="resize"
+    @focus="emit('focus')"
+    @blur="emit('blur')"
+  />
+</template>
+
+<style lang="scss" scoped>
+.input-area {
+  width: 100%;
+  font-size: 14px;
+  line-height: 21px;
+  padding: 4px;
+  resize: none;
+  overflow-y: hidden; // 默认不显示滚动条
+  height: 30px; // 初始高度(1 行)
+  max-height: 92px; // 最多 4 行
+  box-sizing: border-box;
+  border: none;
+  outline-color: transparent;
+  outline: none;
+  background-color: transparent;
+  border-radius: 8px;
+  transition: height 0.1s ease;
+  &::placeholder {
+    color: #b5b9bf;
+    opacity: 1;
+  }
+}
+</style>

+ 46 - 0
src/views/AIRobotChat/src/components/LoadingDots.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="loading-dots">
+    <span class="dot"></span>
+    <span class="dot"></span>
+    <span class="dot"></span>
+  </div>
+</template>
+
+<style scoped>
+.loading-dots {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  gap: 6px;
+  height: 24px;
+}
+
+.dot {
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  background-color: var(--color-theme);
+  animation: bounce 1.4s infinite ease-in-out both;
+}
+
+.dot:nth-child(1) {
+  animation-delay: 0s;
+}
+.dot:nth-child(2) {
+  animation-delay: 0.2s;
+}
+.dot:nth-child(3) {
+  animation-delay: 0.4s;
+}
+
+@keyframes bounce {
+  0%,
+  80%,
+  100% {
+    transform: translateY(0);
+  }
+  40% {
+    transform: translateY(-4px);
+  }
+}
+</style>

BIN
src/views/AIRobotChat/src/image/icon_ai_robot36_b@2x.png


BIN
src/views/AIRobotChat/src/image/icon_ai_robot48_b@2x.png


BIN
src/views/AIRobotChat/src/image/icon_faq_b@2x.png


BIN
src/views/AIRobotChat/src/image/icon_loading.png


BIN
src/views/AIRobotChat/src/image/robotBubbleDark.png


BIN
src/views/AIRobotChat/src/image/robotBubbleLight.png


BIN
src/views/AIRobotChat/src/image/userBubbleDark.png


BIN
src/views/AIRobotChat/src/image/userBubbleLight.png


+ 0 - 1
src/views/Booking/src/components/BookingDetail/src/components/AddReferenceDialog.vue

@@ -45,7 +45,6 @@ const tableData = ref<VxeGridProps<any>>({
   ],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue

@@ -15,7 +15,6 @@ const tableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Booking/src/components/BookingTable/src/BookingTable.vue

@@ -188,7 +188,6 @@ const bookingTable = ref<VxeGridProps<any>>({
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 20, scrollToTopOnChange: true },
   scrollX: { enabled: true, gt: 10, scrollToLeftOnChange: true },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 1 - 0
src/views/ChatLog/index.ts

@@ -0,0 +1 @@
+export { default } from './src/ChatLog.vue'

+ 268 - 0
src/views/ChatLog/src/ChatLog.vue

@@ -0,0 +1,268 @@
+<script lang="ts" setup>
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import TableView from './components/TableView'
+import LogDialog from '@/views/AIApiLog/src/components/LogDialog.vue'
+
+const OperationSearch = ref()
+const filterRef: Ref<HTMLElement | null> = ref(null)
+const containerHeight = useCalculatingHeight(document.documentElement, 290, [filterRef])
+const searchData = ref({
+  inputModel: '',
+  startDate: '',
+  endDate: '',
+  userType: '',
+  questionType: '',
+  answerType: '',
+  answerSatisfaction: '',
+  comparator: 'thanOrEqual',
+  responseDuration: 0
+})
+
+const userTypeList = [
+  {
+    label: 'Customer',
+    value: 'customer'
+  },
+  {
+    label: 'Employee',
+    value: 'employee'
+  }
+]
+const questionTypeList = [
+  {
+    label: 'Predefined Question',
+    value: 'predefinedQuestion'
+  },
+  {
+    label: 'free text',
+    value: 'freeText'
+  }
+]
+const AnswerTypeList = [
+  {
+    label: 'Suspend',
+    value: 'suspend'
+  },
+  {
+    label: 'Timeout',
+    value: 'timeout'
+  },
+  {
+    label: 'Predefined Template',
+    value: 'predefinedTemplate'
+  },
+  {
+    label: 'AI Answer',
+    value: 'AIAnswer'
+  }
+]
+const answerSatisfactionList = [
+  {
+    label: 'Null',
+    value: 'null'
+  },
+  {
+    label: 'Good',
+    value: 'good'
+  },
+  {
+    label: 'Not Good',
+    value: 'notGood'
+  }
+]
+
+const comparatorList = [
+  {
+    label: '>=',
+    value: 'thanOrEqual'
+  },
+  {
+    label: '=',
+    value: 'equal'
+  },
+  {
+    label: '<=',
+    value: 'lessOrEqual'
+  }
+]
+const tableRef = ref()
+
+const Search = () => {
+  tableRef.value.SearchOperationLog(searchData.value)
+}
+const DateChange = (date: any) => {
+  searchData.value.startDate = date[0]
+  searchData.value.endDate = date[1]
+  tableRef.value.SearchOperationLog(searchData.value)
+}
+
+const logDialogRef = ref()
+const openDialog = () => {
+  logDialogRef.value.openDialog()
+}
+</script>
+<template>
+  <div class="dashboard">
+    <div class="Title">Chat Log</div>
+    <div class="display">
+      <div class="heaer_top">
+        <div class="input-tips_filter">
+          <el-input
+            placeholder="Search Question ID、User"
+            v-model="OperationSearch"
+            class="log_input"
+          >
+            <template #prefix>
+              <span class="iconfont_icon">
+                <svg class="iconfont icon_dark" aria-hidden="true">
+                  <use xlink:href="#icon-icon_search_b"></use>
+                </svg>
+              </span>
+            </template>
+          </el-input>
+        </div>
+
+        <div class="date-tips_filter">
+          <CalendarDate @DateChange="DateChange"></CalendarDate>
+        </div>
+        <div class="tips_filter">
+          <el-select v-model="searchData.userType" placeholder="User Type">
+            <el-option
+              v-for="item in userTypeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div class="tips_filter">
+          <el-select v-model="searchData.questionType" placeholder="Question Type">
+            <el-option
+              v-for="item in questionTypeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            >
+            </el-option>
+          </el-select>
+        </div>
+        <div class="tips_filter">
+          <el-select v-model="searchData.answerType" placeholder="Answer Type">
+            <el-option
+              v-for="item in AnswerTypeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            >
+            </el-option>
+          </el-select>
+        </div>
+        <div class="tips_filter">
+          <el-select v-model="searchData.answerSatisfaction" placeholder="Answer Satisfaction">
+            <el-option
+              v-for="item in answerSatisfactionList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            >
+            </el-option>
+          </el-select>
+        </div>
+        <div class="comparator-tips_filter">
+          <span>Response Duration</span>
+          <el-select v-model="searchData.comparator" style="width: 70px; margin: 0 6px">
+            <el-option
+              v-for="item in comparatorList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            >
+            </el-option>
+          </el-select>
+          <el-input-number
+            v-model="searchData.responseDuration"
+            placeholder="s"
+            :controls="false"
+            :min="0"
+            style="width: 60px"
+          ></el-input-number>
+        </div>
+        <el-button class="el-button--dark" @click="Search">Search</el-button>
+      </div>
+    </div>
+    <TableView :height="containerHeight" ref="tableRef"></TableView>
+    <LogDialog ref="logDialogRef" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding-left: 23.32px;
+  align-items: center;
+}
+.heaer_top {
+  margin-top: 6.57px;
+  margin-bottom: 8px;
+  padding-right: 8px;
+  display: flex;
+}
+
+.display {
+  border: 1px solid var(--color-border);
+  border-width: 0 0 1px 0;
+  padding-left: 23.52px;
+}
+:deep(.el-select__placeholder.is-transparent span) {
+  color: var(--tag-info-text-color) !important;
+}
+:deep(.ETD_title) {
+  margin-bottom: 0;
+}
+:deep(.ant-picker-range) {
+  width: 250px !important;
+  height: 32px;
+  background-color: var(--color-mode) !important;
+}
+.tips_filter {
+  flex: 1;
+  height: 30px;
+  max-width: 170px;
+  margin-right: 8px;
+}
+.input-tips_filter {
+  flex: 1;
+  max-width: 320px;
+  height: 32px;
+  margin-right: 8px;
+  :deep(.el-input__wrapper) {
+    height: 32px;
+  }
+}
+.date-tips_filter {
+  flex: 1;
+  max-width: 250px;
+  height: 32px;
+  margin-right: 8px;
+}
+.comparator-tips_filter {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  max-width: 260px;
+  height: 32px;
+  margin-right: 8px;
+}
+.dashboard {
+  position: relative;
+  background-color: var(--color-mode);
+}
+:deep(.log_input .el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-select-border);
+  border-radius: 6px;
+}
+</style>

+ 1 - 0
src/views/ChatLog/src/components/TableView/index.ts

@@ -0,0 +1 @@
+export { default } from './src/TableView.vue'

+ 435 - 0
src/views/ChatLog/src/components/TableView/src/TableView.vue

@@ -0,0 +1,435 @@
+<script setup lang="ts">
+import { ref, nextTick, onMounted } from 'vue'
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import DownloadDialog from './components/DownloadDialog.vue'
+// import { autoWidth } from '@/utils/table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import dayjs from 'dayjs'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+
+const props = defineProps({
+  height: {
+    type: Number,
+    default: 440
+  }
+})
+
+const tableOriginColumnsField = ref()
+const handleColumns = (columns: any, status?: string) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      sortable: true,
+      minWidth: 120
+    }
+    // 设置插槽
+    if (item.type === 'status' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'status' }
+      }
+    } else if (item.type === 'link' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'link' }
+      }
+    } else if (item.type === 'mode' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'mode' },
+        formatter: ({ cellValue }: any) => {
+          return cellValue
+        }
+      }
+    }
+    // 格式化
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+
+// 获取表格列
+const getTableColumns = async () => {
+  tableLoadingColumn.value = true
+  await $api.getOperationTableColumns().then((res: any) => {
+    if (res.code === 200) {
+      tableData.value.columns = [
+        { type: 'checkbox', width: 50, fixed: 'left' },
+        ...handleColumns(res.data.OperationTableColumns)
+      ]
+      tableOriginColumnsField.value = res.data.OperationTableColumns
+    }
+  })
+  nextTick(() => {
+    // tableRef.value && autoWidth(tableData.value, tableRef.value)
+    tableLoadingColumn.value = false
+    selectedNumber.value = 0
+    selectedTableData.value = []
+  })
+}
+
+const pageInfo = ref({ pageNo: 1, pageSize: 20, total: 0 })
+const tempSearch = ref()
+// 获得表格数据后赋值
+const assignTableData = (data: any) => {
+  tableData.value.data = data.searchData || []
+  pageInfo.value.total = Number(data.rc) || 0
+  tempSearch.value = data.tmp_search
+  // 拥有所有字段的表格
+  setTimeout(() => {
+    allTable.value.columns = handleColumns(tableData.value.columns, 'all')
+    allTable.value.data = data.searchData || []
+    // 为了让导出的表格列宽度自适应
+    nextTick(() => {
+      // allTableRef.value && autoWidth(allTable.value, allTableRef.value)
+    })
+  }, 1000)
+}
+
+let searchdata: any = {}
+// 获取表格数据
+const getTableData = async (isPageChange?: boolean) => {
+  const rc = isPageChange ? pageInfo.value.total : -1
+  tableLoadingTableData.value = true
+  await $api
+    .SearchOperationLog({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc,
+      ...searchdata
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      selectedNumber.value = 0
+      selectedTableData.value = []
+      nextTick(() => {
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+const SearchOperationLog = (val: any) => {
+  searchdata = val
+  tableLoadingTableData.value = true
+  $api
+    .SearchOperationLog({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: -1,
+      ...val
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      selectedNumber.value = 0
+      selectedTableData.value = []
+      nextTick(() => {
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+onMounted(() => {
+  Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
+    nextTick(() => {
+      // tableRef.value && autoWidth(tableData.value, tableRef.value)
+    })
+  })
+})
+
+const tableRef = ref<VxeGridInstance>()
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  sortConfig: {
+    sortMethod: (params) => {
+      const { data, sortList } = params
+
+      // 如果没有排序条件,直接返回原数据
+      if (sortList.length === 0) return data
+
+      // 对数据进行多重排序
+      const sortedData = [...data].sort((a, b) => {
+        for (const { field, order } of sortList) {
+          const curColumn = tableOriginColumnsField.value.find((item: any) => item.field === field)
+          if (!curColumn) continue
+
+          const typeName = curColumn.type
+          const aValue = a[field]
+          const bValue = b[field]
+
+          const compareResult = (aValue: any, bValue: any) => {
+            // 如果 aValue 或 bValue 是 null 或 undefined,优先处理这些值
+            if (aValue == null && bValue == null) {
+              return 0 // 如果两个值都为 null 或 undefined,视为相等
+            } else if (aValue == null) {
+              return -1 // 如果 aValue 是 null,bValue 不是,则将 aValue 视为较小值
+            } else if (bValue == null) {
+              return 1 // 如果 bValue 是 null,aValue 不是,则将 bValue 视为较小值
+            }
+
+            if (typeName === 'datetime' || typeName === 'date' || typeName === 'time') {
+              return dayjs(aValue).unix() - dayjs(bValue).unix()
+            } else if (isNaN(Number(aValue)) || isNaN(Number(bValue))) {
+              return aValue.localeCompare(bValue)
+            } else {
+              return Number(aValue) - Number(bValue)
+            }
+          }
+
+          const result = compareResult(aValue, bValue)
+          if (result !== 0) {
+            return order === 'asc' ? result : -result
+          }
+        }
+
+        return 0 // 如果所有字段都相等
+      })
+
+      return sortedData
+    }
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+const allTableRef = ref<VxeGridInstance>()
+const allTable = ref<VxeGridProps<any>>({
+  columns: [],
+  data: [],
+  showHeaderOverflow: true,
+  showOverflow: true,
+  scrollY: { enabled: true, oSize: 5, gt: 2 },
+  scrollX: { enabled: true, gt: 2 },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const downloadDialogRef = ref()
+const handleDownload = () => {
+  const curSelectedColumns: string[] = []
+  tableRef.value?.columns?.forEach((item: any) => {
+    if (item.field) {
+      curSelectedColumns.push(item.title)
+    }
+  })
+  downloadDialogRef.value.openDialog(
+    curSelectedColumns,
+    selectedNumber.value || pageInfo.value.total
+  )
+}
+
+const exportLoading = ref(false)
+// 获取导出表格数据
+const getExportTableData = (status: number) => {
+  // 如果有选中表格行数据,那么只到处选中的数据
+  if (selectedNumber.value > 0) {
+    exportTable(status)
+    return
+  }
+  exportLoading.value = true
+  const buildColumnString = (columns: any[]): string => {
+    return columns
+      .filter((item) => item.field)
+      .map((item) => item.title)
+      .join(',')
+  }
+
+  let column = ''
+  if (status === 1) {
+    column = buildColumnString(tableData.value.columns)
+  } else {
+    column = buildColumnString(allTable.value.columns)
+  }
+  $api
+    .OperationLogDownload({
+      selected_fields: column,
+      tmp_search: tempSearch.value
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        allTable.value.data = res.data.Data || []
+        nextTick(() => {
+          exportLoading.value = false
+          // 导出数据
+          exportTable(status)
+        })
+      }
+    })
+    .finally(() => {
+      exportLoading.value = false
+    })
+}
+// 导出表格 status: 1 导出当前表格 2 导出所有字段表格
+const exportTable = (status: number) => {
+  const exportConfig: any = {
+    type: 'xlsx',
+    message: false,
+    filename: `Chat Log_${dayjs().format('YYYYMMDDHH[h]mm[m]ss[s]')}`
+  }
+  if (status === 1) {
+    exportConfig.columnFilterMethod = ({ column }: any) => {
+      const index = tableData.value.columns.findIndex((item: any) => item.field === column.field)
+      // 排除复选框列
+      return column.field && index !== -1
+    }
+    exportConfig.columns = tableData.value.columns
+  }
+  if (selectedNumber.value > 0) {
+    exportConfig.dataFilterMethod = ({ row }: any) => {
+      const index = selectedTableData.value.findIndex(
+        (item: any) => item._X_ROW_KEY === row._X_ROW_KEY
+      )
+      return index !== -1
+    }
+  }
+  allTableRef.value?.exportData(exportConfig)
+}
+
+const tableLoadingColumn = ref(false)
+const tableLoadingTableData = ref(false)
+
+const selectedNumber = ref(0)
+const selectedTableData = ref([])
+// 复选框选中事件
+const handleCheckboxChange = ({ records }: any) => {
+  selectedNumber.value = records.length
+  selectedTableData.value = records
+}
+const handleCheckAllChange = ({ records }: any) => {
+  selectedNumber.value = records.length
+  selectedTableData.value = records
+}
+defineExpose({
+  SearchOperationLog
+})
+</script>
+
+<template>
+  <div
+    style="padding: 0px 20px"
+    class="table-box"
+    v-loading.fullscreen.lock="exportLoading"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
+    element-loading-background="rgb(43, 47, 54, 0.7)"
+  >
+    <div class="table-tools">
+      <div class="left-total-records">{{ selectedNumber }} Selected</div>
+      <div class="right-tools-btn">
+        <el-button class="el-button--main el-button--pain-theme" @click="handleDownload">
+          <span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
+          Download
+        </el-button>
+      </div>
+    </div>
+    <vxe-grid
+      ref="tableRef"
+      v-vloading="tableLoadingTableData || tableLoadingColumn"
+      :height="props.height"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+      @checkbox-change="handleCheckboxChange"
+      @checkbox-all="handleCheckAllChange"
+    >
+      <!-- 空数据时的插槽 -->
+      <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
+        <VEmpty> </VEmpty>
+      </template>
+    </vxe-grid>
+    <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
+    <div class="bottom-pagination">
+      <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
+      <div class="right-pagination">
+        <el-pagination
+          v-model:current-page="pageInfo.pageNo"
+          v-model:page-size="pageInfo.pageSize"
+          :page-sizes="[20, 50, 100, 150]"
+          :pager-count="3"
+          background
+          layout="sizes, prev, pager, next"
+          :total="pageInfo.total"
+          @size-change="getTableData(true)"
+          @current-change="getTableData(true)"
+        />
+      </div>
+    </div>
+    <DownloadDialog @export="getExportTableData" ref="downloadDialogRef" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.table-tools {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  height: 48px;
+  padding: 8px 0;
+
+  .left-total-records {
+    font-size: 16px;
+    font-weight: 700;
+    line-height: 32px;
+  }
+}
+
+.bottom-pagination {
+  display: flex;
+  justify-content: space-between;
+  height: 40px;
+  margin-top: -1px;
+  padding: 4px 8px;
+  border: 1px solid var(--color-border);
+  border-radius: 0 0 12px 12px;
+
+  .left-total-records {
+    line-height: 32px;
+  }
+
+  .right-pagination {
+    display: flex;
+    align-items: center;
+  }
+}
+.table-box {
+  position: relative;
+  overflow: hidden;
+
+  .all-table {
+    position: absolute;
+    top: -100000px;
+    width: 20px;
+  }
+}
+</style>

+ 190 - 0
src/views/ChatLog/src/components/TableView/src/components/DownloadDialog.vue

@@ -0,0 +1,190 @@
+<script setup lang="ts">
+const dialogVisible = ref(false)
+
+const openDialog = (selectedColumns: string[], slectedDataNumber: number) => {
+  selectedDataNumber.value = slectedDataNumber
+  columns.value = selectedColumns
+  dialogVisible.value = true
+}
+
+const isShowSelectColumn = ref(false)
+
+const downloadFilter = ref(1)
+const selectedDataNumber = ref(0)
+
+const columns = ref()
+
+const emits = defineEmits<{ export: [number] }>()
+const handleDownload = () => {
+  emits('export', downloadFilter.value)
+}
+
+const clearData = () => {
+  isShowSelectColumn.value = false
+  downloadFilter.value = 1
+}
+
+defineExpose({
+  openDialog,
+  handleDownload
+})
+</script>
+
+<template>
+  <div>
+    <el-dialog @close="clearData" v-model="dialogVisible" title="Download File" width="540">
+      <div class="download-dialog">
+        <div class="select-data">
+          <div style="display: inline-block">
+            Select data on your Opeartion Log list:<span style="color: var(--color-theme)">{{
+              selectedDataNumber
+            }}</span>
+          </div>
+        </div>
+        <div class="download-filter">
+          <el-radio-group v-model="downloadFilter">
+            <el-radio :value="1"
+              >Download with selected columns
+              <span class="column-number">{{ columns.length }}</span>
+              <SeeAllIcon v-model="isShowSelectColumn" />
+            </el-radio>
+            <div
+              v-if="isShowSelectColumn"
+              class="select-columns"
+              :class="{ show: isShowSelectColumn }"
+            >
+              <div class="title">Selected columns</div>
+              <div class="content">
+                <div class="column-item" v-for="item in columns" :key="item">{{ item }}</div>
+              </div>
+            </div>
+            <el-radio :value="2">Download with all columns</el-radio>
+          </el-radio-group>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="cancel-btn" type="default" @click="dialogVisible = false"
+            >Cancel</el-button
+          >
+          <el-button class="download-btn el-button--dark" @click="handleDownload"
+            ><span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
+            Download</el-button
+          >
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.download-dialog {
+  color: var(--color-neutral-1);
+}
+
+.select-data {
+  font-weight: 700;
+}
+
+.data-filter {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  max-height: 120px;
+  margin-top: 8px;
+  overflow: auto;
+
+  .filter-item {
+    height: 22px;
+    padding: 0px 8px;
+    background-color: var(--color-download-file-filter-tag-bg);
+    border-radius: 12px;
+    line-height: 22px;
+    font-size: 12px;
+  }
+}
+
+.download-filter {
+  margin-top: 16px;
+
+  .el-radio-group {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+
+    .el-radio {
+      height: 40px;
+      align-items: center;
+    }
+
+    :deep(.el-radio__label) {
+      margin-top: 2px;
+      font-weight: 700;
+      color: var(--color-neutral-1);
+    }
+
+    .column-number {
+      padding: 3px 5px;
+      background-color: var(--color-theme);
+      border-radius: 12px;
+      font-size: 12px;
+      font-weight: 700;
+      color: #fff;
+    }
+
+    .see-all-btn {
+      margin-left: 8px;
+      color: var(--color-theme);
+      font-size: 12px;
+    }
+
+    .select-columns {
+      max-height: 350px;
+      padding: 8px;
+      margin-top: 8px;
+      background-color: var(--color-dialog-header-bg);
+      border-radius: 6px;
+      overflow: hidden;
+
+      &.show {
+        max-height: 500px;
+      }
+
+      .title {
+        font-size: 12px;
+        font-weight: 700;
+      }
+
+      .content {
+        display: flex;
+        flex-wrap: wrap;
+        margin-top: 8px;
+        gap: 8px;
+
+        .column-item {
+          height: 22px;
+          padding: 0px 8px;
+          background-color: var(--color-download-file-selected-column-tag-bg);
+          line-height: 22px;
+          border-radius: 12px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+
+.dialog-footer {
+  .el-button {
+    height: 40px;
+  }
+
+  .cancel-btn {
+    width: 115px;
+  }
+
+  .download-btn {
+    width: 136px;
+  }
+}
+</style>

+ 28 - 1
src/views/Layout/src/LayoutView.vue

@@ -5,6 +5,7 @@ import Menu from './components/Menu/MenuView.vue'
 import Logo from './images/logo.png'
 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'
 
 const leftAsideWidth = ref('232px')
@@ -13,6 +14,27 @@ const handleMenuCollapse = (val: boolean) => {
   isCollapse.value = val
   val ? (leftAsideWidth.value = '64px') : (leftAsideWidth.value = '232px')
 }
+
+const isShowAIRobotChat = ref(false)
+const handleColseRobotChat = () => {
+  isShowAIRobotChat.value = false
+}
+const onClick = () => {
+  isShowAIRobotChat.value = true
+}
+const AvatarClick = () => {
+  isShowAIRobotChat.value = true
+}
+
+const AIRobotChatref = ref()
+// 点击问题
+const handelClickAIDefault = async (item: any) => {
+  isShowAIRobotChat.value = true
+  await nextTick()
+  if (AIRobotChatref.value) {
+    AIRobotChatref.value.handleSend(item)
+  }
+}
 </script>
 <template>
   <el-container class="layout-container">
@@ -42,7 +64,12 @@ const handleMenuCollapse = (val: boolean) => {
         <router-view />
       </el-main>
     </el-container>
-    <!-- <AIRobot></AIRobot> -->
+    <AIRobot @AvatarClick="AvatarClick" @handelClickAIDefault="handelClickAIDefault"></AIRobot>
+    <AIRobotChat
+      ref="AIRobotChatref"
+      v-if="isShowAIRobotChat"
+      @close="handleColseRobotChat"
+    ></AIRobotChat>
     <ScoringGrade></ScoringGrade>
   </el-container>
 </template>

+ 2 - 0
src/views/Layout/src/components/Header/components/LogoutDialog.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
+import emitter from '@/utils/bus'
 
 const router = useRouter()
 const dialogVisible = ref(false)
@@ -13,6 +14,7 @@ const handleLogout = () => {
   router.push('/login')
   sessionStorage.clear()
   localStorage.removeItem('user_type')
+  emitter.emit('login-out');
 }
 defineExpose({
   openDialog

+ 11 - 5
src/views/Layout/src/components/Menu/MenuView.vue

@@ -2,6 +2,7 @@
 import { useRoute, useRouter } from 'vue-router'
 import { useUserStore } from '@/stores/modules/user'
 import { CaretRight } from '@element-plus/icons-vue'
+import emitter from '@/utils/bus'
 
 const route = useRoute()
 const router = useRouter()
@@ -73,10 +74,14 @@ const isAllowJump = (path: any) => {
 router.afterEach(() => {
   activeMenu.value = (route.meta?.activeMenu as string) || route.path
 })
-const isVisible = ref(false)
-const emits = defineEmits(['changeVisible'])
+
 // 路由跳转函数
 const changeRouter = (path: any) => {
+  if (path == '/PromptConfiguration') {
+    emitter.emit('checkPrompt')
+  } else {
+    emitter.emit('checknoPrompt')
+  }
   if (sessionStorage.getItem('searchTableQeury')) {
     sessionStorage.removeItem('searchTableQeury')
   }
@@ -90,9 +95,10 @@ const changeRouter = (path: any) => {
   }
   sessionStorage.removeItem('trackingTablePageInfo')
   sessionStorage.removeItem('bookingTablePageInfo')
-  isVisible.value = false
-  emits('changeVisible', isVisible.value)
-  isVisible.value = false
+  if (localStorage.getItem('loginAI')) {
+    localStorage.removeItem('loginAI')
+    emitter.emit('login-success')
+  }
   let toPath = path
   if (path === '/tracking' && !userStore.userInfo?.uname) {
     toPath = '/public-tracking'

+ 1 - 0
src/views/Login/src/loginView.vue

@@ -212,6 +212,7 @@ const handleResult = (res: any) => {
       })
     }
     localStorage.setItem('user_type', res.data.user_info.user_type)
+    localStorage.setItem('loginAI', 'loginAI')
     userStore.setUserInfo(res.data?.user_info || {})
     router.push('/')
   } else if (res.code === 400) {

+ 1 - 0
src/views/PromptConfiguration/index.ts

@@ -0,0 +1 @@
+export { default } from './src/PromptConfiguration.vue'

+ 474 - 0
src/views/PromptConfiguration/src/PromptConfiguration.vue

@@ -0,0 +1,474 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import TableConfiguration from './components/TableConfiguration.vue'
+import RespnseConfiguration from './components/RespnseConfiguration.vue'
+import OutputConfiguration from './components/OutputConfiguration.vue'
+import PreviewTesting from './components/PreviewTesting.vue'
+import submitsucessful from './images/icon_success_big@2x.png'
+import moment from 'moment'
+
+const rolename = ref('')
+const tablename = ref('')
+const professionalfield = ref('')
+const tabledescription = ref('')
+const maintasks = ref('')
+const outputvalue = ref('')
+const editid = ref('')
+const prompttextvalue = ref('')
+const TableConfigurationref = ref()
+const RespnseConfigurationref = ref()
+const tableDataList = ref([])
+const stepData = ref([])
+const formatList = ref([])
+const promptValue = ref([])
+const PromptdialogVisible = ref(false)
+const PromptdialogInnerVisible = ref(false)
+const testquestion = ref(false)
+const UnableSaveVisible = ref(false)
+const SaveedVisible = ref(false)
+const ChangeLogList = ref([])
+const prompttext = ref('')
+
+const AddTableConfiguration = () => {
+  TableConfigurationref.value.addNewTableColumn()
+}
+
+const addstepdata = () => {
+  RespnseConfigurationref.value.addstepdata()
+}
+
+const getPromptConfiguration = () => {
+  $api.getPromptConfiguration().then((res) => {
+    if (res.code === 200) {
+      rolename.value = res.data.character_name
+      professionalfield.value = res.data.professional_field
+      maintasks.value = res.data.main_tasks
+      tablename.value = res.data.table_name
+      tabledescription.value = res.data.table_description
+      tableDataList.value = res.data.tableData
+      stepData.value = res.data.stepData
+      formatList.value = res.data.formatList
+      outputvalue.value = res.data.formatListType
+      prompttextvalue.value = res.data.complete_prompt
+      promptValue.value = res.data.prompt_summary
+      editid.value = res.data.id
+      ChangeLogList.value = res.data.prompt_log_record
+      if(rolename.value != '' && professionalfield.value != '' && maintasks.value != '' && professionalfield.value != '' && tableDataList.value.length != 0 && stepData.value.length != 0 &&  formatList.value.length != 0 &&  outputvalue.value != '' && prompttextvalue.value != '') {
+        testquestion.value = false
+      } else {
+        testquestion.value = true
+      }
+    }
+  })
+}
+// 保存
+const missingmessage = ref()
+const SavePromptConfiguration = () => {
+  missingmessage.value = ''
+  if(rolename.value != '' && professionalfield.value != '' && maintasks.value != '' && professionalfield.value != '' && tableDataList.value.length != 0 && stepData.value.length != 0 &&  formatList.value.length != 0 &&  outputvalue.value != '' && prompttextvalue.value != '') {
+    $api.SavePromptConfiguration({
+      role_name: rolename.value,
+      professional_field: professionalfield.value,
+      main_tasks: maintasks.value,
+      table_name: tablename.value,
+      table_description: tabledescription.value,
+      tableDataList: tableDataList.value,
+      stepData: stepData.value,
+      formatList: formatList.value,
+      outputvalue: outputvalue.value,
+      prompttextvalue: prompttextvalue.value,
+      id: editid.value
+    }).then((res) => {
+      if (res.code === 200) {
+        SaveedVisible.value = true
+        setTimeout(() => {
+          window.location.reload()
+        }, 3000)
+      }
+    })
+  } else {
+    if(rolename.value == '' || professionalfield.value == '' || maintasks.value == '') {
+      missingmessage.value += '系统角色配置, '
+    }
+    if(tablename.value == '' || tableDataList.value.length == 0) {
+      missingmessage.value += '表结构配置, '
+    }
+    if(stepData.value.length == 0) {
+      missingmessage.value += '响应规则配置, '
+    }
+    if(formatList.value.length == 0 || outputvalue.value == '') {
+      missingmessage.value += '输出格式配置, '
+    }
+    missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+    UnableSaveVisible.value = true
+  }
+}
+
+const EditPrompt = () => {
+  $api.EditPrompt({
+      role_name: rolename.value,
+      professional_field: professionalfield.value,
+      main_tasks: maintasks.value,
+      table_name: tablename.value,
+      table_description: tabledescription.value,
+      tableDataList: tableDataList.value,
+      stepData: stepData.value,
+      formatList: formatList.value,
+      outputvalue: outputvalue.value,
+      prompttextvalue: prompttextvalue.value,
+      id: editid.value
+    }).then((res) => {
+      if (res.code === 200) {
+        prompttextvalue.value = res.data.complete_prompt
+        promptValue.value = res.data.prompt_summary
+      }
+    })
+}
+
+// 导出为txt
+const exporttxt = () => {
+  // 创建 Blob 对象
+  const blob = new Blob([prompttext.value], { type: 'text/plain' })
+   // 创建下载链接
+  const link = document.createElement('a')
+  link.href = URL.createObjectURL(blob)
+  const currentDate = moment();
+  const formattedDate = currentDate.format('YYYY-MM-DD HH:mm');
+  link.download = `${formattedDate} Prompt.txt` // 自定义文件名
+  
+  // 触发下载
+  link.click()
+  
+  // 清理内存
+  URL.revokeObjectURL(link.href)
+}
+
+// 查看日志
+const ViewPrompt = (item: any) => {
+  PromptdialogInnerVisible.value = true
+  prompttext.value = item
+}
+
+// 返回上一级
+const Backprompt = () => {
+  PromptdialogInnerVisible.value = false
+}
+
+onMounted(() => {
+  getPromptConfiguration()
+})
+</script>
+
+<template>
+  <div class="Title">
+    <div>Prompt Configuration</div>
+    <div>
+      <el-button class="el-button--default title-button" style="margin-right: 8px;" @click="PromptdialogVisible = true"><span class="font_family icon-icon_dashboard_b icon_dark" style="margin-right: 5px;"></span>变更日志查看</el-button>
+      <el-button class="el-button--main title-button" @click="SavePromptConfiguration"><span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>保存生效</el-button>
+    </div>
+  </div>
+  <el-dialog
+    v-model="PromptdialogVisible"
+    width="1000px"
+    title="变更日志"
+    :modal="false"
+    align-center
+    class="prompt-dialog"
+    :close-on-click-modal="false"
+  > 
+    <div class="diaolog-body">
+      <div class="diaolog-content" v-for="(item, index) in ChangeLogList" :key="index">
+        <div class="diaolog-flex">
+          <div>
+            <div class="dialog-title">{{ item.time }}</div>
+            <div class="dialog-person">{{ item.person }}</div>
+          </div>
+          <div>
+            <el-button class="el-button--default" @click="ViewPrompt(item.text)"><span class="font_family icon-icon_view_b icon_dark" style="margin-right: 5px;" ></span>查看完整Prompt</el-button>
+          </div>
+        </div>
+        <div class="diaolog-text">
+          {{ item.text }}
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+  <el-dialog
+    v-model="PromptdialogInnerVisible"
+    width="1000px"
+    :modal="false"
+    align-center
+    class="prompt-dialog prompt-dialog-inner"
+    :close-on-click-modal="false"
+  > 
+    <template #header>
+      <div class="dialog-header">
+        <div style="width: 55%;display: flex;justify-content: space-between;">
+          <div class="Back" @click="Backprompt"><span class="font_family icon-icon_previous_b icon_dark"></span>Back</div>
+          <div style="display: flex;align-items: center;">查看完整Prmopt</div>
+        </div>
+        <el-button class="el-button--default" @click="exporttxt"><span class="font_family icon-icon_import_b icon_dark" style="margin-right: 5px;" ></span>导出完整日志</el-button>
+      </div>
+    </template>
+    <pre class="diaolog-content">
+      {{ prompttext }}
+    </pre>
+  </el-dialog>
+  <div class="prompt-content">
+    <!-- 系统角色设置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">系统角色设置</div>
+      <div class="flex">
+        <div style="width: 50%; margin-right: 8px;">
+          <div class="little-title">角色名称</div>
+          <el-input v-model="rolename" placeholder="为AI Robot创建一个角色名称" class="input-name"></el-input>
+        </div>
+        <div style="width: 50%;">
+          <div class="little-title">专业领域</div>
+          <el-input v-model="professionalfield" placeholder="为AI Robot设定专业领域" class="input-name"></el-input>
+        </div>
+      </div>
+      <div style="margin-top: 16px;">
+        <div class="little-title">主要任务</div>
+        <el-input v-model="maintasks" type="textarea" placeholder="简要描述AI Robot的主要任务" class="input-name-textarea" :rows="3"></el-input>
+      </div>
+    </div>
+    <!-- 表结构配置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">表结构配置</div>
+      <div class="flex">
+        <div style="width: 50%; margin-right: 8px;">
+          <div class="little-title"><span class="stars_red">*</span>表名</div>
+          <el-input v-model="tablename" placeholder="创建一个表名" class="input-name"></el-input>
+        </div>
+        <div style="width: 50%;">
+          <div class="little-title">表描述</div>
+          <el-input v-model="tabledescription" placeholder="简要描述表的用途" class="input-name"></el-input>
+        </div>
+      </div>
+      <div style="margin-top: 16px;">
+        <el-button class="el-button--noborder--configuration prompt-button" @click="AddTableConfiguration">+ 添加字段</el-button>
+        <TableConfiguration ref="TableConfigurationref" :tableDataList="tableDataList"></TableConfiguration>
+      </div>
+    </div>
+    <!-- 响应规则配置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">响应规则配置</div>
+      <div style="margin-top: 16px;">
+        <el-button class="el-button--noborder--configuration prompt-button" @click="addstepdata" v-if="stepData.length != 0">+ 添加步骤</el-button>
+        <RespnseConfiguration ref="RespnseConfigurationref" :stepDataprops="stepData"></RespnseConfiguration>
+      </div>
+    </div>
+    <!-- 输出格式配置 -->
+    <div class="prompt-border">
+      <div class="prompt-title">输出格式配置</div>
+      <div style="margin-top: 16px;">
+        <OutputConfiguration ref="OutputConfigurationref" :formatList="formatList" :outputvalue="outputvalue"></OutputConfiguration>
+      </div>
+    </div>
+    <!-- 预览与测试 -->
+     <div class="propmt-border-colorful">
+        <div class="prompt-title" style="padding: 0 14px;">预览与测试</div>
+        <div style="margin-top: 16px;">
+          <PreviewTesting ref="PreviewTestingref" :prompttext="prompttextvalue" :testquestion="testquestion" :promptValue="promptValue" @handleprompt="EditPrompt"></PreviewTesting>
+        </div>
+     </div>
+     <div class="propmt-save"><el-button class="el-button--main" @click="SavePromptConfiguration"><span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>保存生效</el-button></div>
+     <!-- 保存失败 -->
+    <el-dialog v-model="UnableSaveVisible" width="480">
+      <div>{{ missingmessage }} missing.</div>
+      <div>Please complete all required fields.</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--danger"
+            @click="UnableSaveVisible = false"
+            style="width: 100px"
+          >
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon iconfont_warning">
+            <svg class="iconfont icon_danger" aria-hidden="true">
+              <use xlink:href="#icon-icon_fail_fill_b"></use>
+            </svg>
+          </span>
+          Unable to Save
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 保存成功 -->
+    <el-dialog v-model="SaveedVisible" width="320" style="height: 212px">
+      <div style="text-align: center"><el-image :src="submitsucessful" style="width: 64px;" /></div>
+      <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  border-width: 1px 0 1px 0;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.prompt-content {
+  padding: 16px 24px;
+}
+.prompt-border {
+  border: 1px solid var(--color-system-border-1);
+  border-radius: 12px;
+  padding: 13px 16px;
+  margin-bottom: 8px;
+}
+.propmt-border-colorful {
+  border: 2px solid transparent;
+  border-radius: 12px;
+  background-clip: padding-box, border-box;
+  background-origin: padding-box, border-box;
+  background-image: linear-gradient(to bottom, #FFF, #FFF), linear-gradient(to bottom, #FF7500, #8112FF);
+  margin-bottom: 8px;
+  padding: 13px 0;
+}
+.prompt-button {
+  margin-bottom: 8px;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+.prompt-title {
+  font-size: 18px;
+  font-weight: 700;
+  margin-bottom: 21px;
+}
+.flex {
+  display: flex;
+}
+.little-title {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--color-neutral-2);
+  margin-bottom: 4px;
+}
+.input-name {
+  height: 40px;
+}
+.input-name-textarea {
+  height: 80px;
+}
+:deep(.el-textarea__inner) {
+  height: 80px;
+  line-height: 1.5; /* 多行文本的行高 */
+  padding: 11px 8px; /* 调整垂直居中 */
+  resize: none; /* 禁用拖拽调整大小(可选) */
+}
+:deep(.el-button, .el-button.is-round) {
+  padding: 4px 8px;
+}
+:deep(.el-textarea__inner) {
+  font-weight: 400;
+  font-size: 14px;
+  color: var(--color-neutral-1);
+}
+.propmt-save {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.el-button--main {
+  width: 400px;
+  height: 40px;
+}
+.title-button {
+  width: 128px;
+  height: 40px;
+}
+.diaolog-body {
+  max-height: 720px;
+  overflow-y: scroll;
+}
+.diaolog-content {
+  background-color: #F8F9FD;
+  padding: 16px;
+  margin-bottom: 8px;
+  border-radius: 6px;
+  line-height: 21px; 
+  white-space: break-spaces;
+}
+.diaolog-flex {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.el-button--default {
+  height: 40px;
+}
+.dialog-title {
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+}
+.dialog-person {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--color-neutral-2);
+}
+.diaolog-text {
+  font-size: 14px;
+  color: var(--color-neutral-2);
+  font-weight: 400;
+  margin-top: 16px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+.dialog-header {
+  height: 48px;
+  width: 95%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.Back {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 400;
+  display: flex;
+  align-items: end;
+  margin-left: 23px;
+  cursor: pointer;
+  padding: 8px;
+  border-radius: 6px;
+}
+.Back:hover {
+  border-color: var(--color-btn-default-bg-hover);
+  background-color: var(--color-btn-default-bg-hover);
+  color: var(--color-theme);
+    span {
+      color: var(--color-theme);
+    }
+}
+.cancel_header {
+  font-size: 18px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  display: flex;
+  align-items: center;
+}
+.icon_danger {
+  fill: var(--color-btn-danger-bg);
+}
+.iconfont_warning {
+  display: flex;
+  align-items: center;
+}
+</style>

+ 92 - 0
src/views/PromptConfiguration/src/components/CodeBlock.vue

@@ -0,0 +1,92 @@
+<template>
+  <pre><code class="json" ref="codeBlock">{{ formattedCode }}</code></pre>
+</template>
+
+<script setup>
+import { computed, onMounted, ref, watch } from 'vue'
+import hljs from 'highlight.js'
+
+const props = defineProps({
+  language: {
+    type: String,
+    default: 'javascript'
+  },
+  rawCode: {
+    type: String,
+    required: true
+  }
+})
+
+const codeBlock = ref(null)
+
+// 自动格式化 JSON
+const formattedCode = computed(() => {
+  if (props.language === 'json') {
+    try {
+      const parsed = JSON.parse(props.rawCode)
+      return JSON.stringify(parsed, null, 4) // 关键:2空格缩进
+    } catch (e) {
+      return props.rawCode // 解析失败时返回原始内容
+    }
+  }
+  return props.rawCode
+})
+
+// 自定义JSON高亮规则
+hljs.registerLanguage('json', function (hljs) {
+  return {
+    name: 'JSON',
+    contains: [
+      {
+        className: 'json-key', // 键名使用自定义类名
+        begin: /"(\\.|[^\\"\r\n])*"(?=\s*:)/, // 匹配键名(后跟冒号的字符串)
+        relevance: 0
+      },
+      hljs.QUOTE_STRING_MODE, // 默认的字符串规则(用于值中的字符串)
+      hljs.NUMBER_MODE,
+      {
+        className: 'literal',
+        begin: /\b(true|false|null)\b/
+      }
+      // 可根据需要添加其他规则
+    ]
+  }
+})
+
+// 高亮函数
+const highlightCode = () => {
+  if (codeBlock.value) {
+    hljs.highlightElement(codeBlock.value)
+  }
+}
+
+onMounted(highlightCode)
+watch(() => [props.language, formattedCode.value], highlightCode)
+</script>
+
+<style scoped>
+/* 新增换行保留样式 */
+pre {
+  white-space: pre-wrap; /* 允许自动换行 */
+  word-wrap: break-word; /* 长单词换行 */
+  width: 100%;
+  border-radius: 6px;
+  margin: 0;
+  padding: 8px 0 22px 16px;
+}
+:deep(.language-json) {
+  color: #f0f1f3;
+}
+:deep(.hljs-json-key) {
+  color: #f0f1f3;
+}
+:deep(.hljs-string) {
+  color: #42b0d5;
+}
+:deep(.hljs-number) {
+  color: #42b0d5;
+}
+:deep(.hljs-literal) {
+  color: #42b0d5;
+}
+</style>

+ 341 - 0
src/views/PromptConfiguration/src/components/OutputConfiguration.vue

@@ -0,0 +1,341 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import addimg from '../images/default_add@2x.png'
+
+const editdialogVisible = ref(false)
+const isEdit = ref(false)
+const UnableSaveVisible = ref(false)
+const outputoptions = ref([
+  {
+    label:'JSON',
+    value: 'JSON'
+  }
+])
+const isSelected = ref([
+  {
+    label: '必填字段',
+    value: '必填字段'
+  },
+  {
+    label: '有条件必填',
+    value: '有条件必填'
+  }
+])
+const EditDataType = ref('')
+const EditName = ref('')
+const Editdescribe = ref('')
+const isSelectedvalue = ref('')
+const Condition = ref('')
+
+interface TypeItem {
+  name: string
+  type: string
+  describe: string
+  selecttype: string
+  requirements: string
+}
+interface Props {
+  formatList: TypeItem []
+  outputvalue: String
+}
+const props = withDefaults(defineProps<Props>(), {})
+
+const formatList = ref(props.formatList)
+const outputvalue = ref(props.outputvalue)
+watch(
+  () => props.formatList,
+  (current) => {
+    formatList.value = current
+    if(formatList.value == null) {
+      formatList.value = []
+    }
+  }
+)
+watch(
+  () => props.outputvalue,
+  (current) => {
+    outputvalue.value = current
+  }
+)
+
+// 编辑字段
+const temporaryname = ref('')
+const handelclickedit = (item: any) => {
+  editdialogVisible.value = true
+  isEdit.value = true
+  EditName.value = item.name
+  temporaryname.value = item.name
+  EditDataType.value = item.type
+  Editdescribe.value = item.describe
+  isSelectedvalue.value = item.selecttype
+  Condition.value = item.requirements
+}
+
+// 添加字段
+const handleaddclick = () => {
+  editdialogVisible.value = true
+}
+
+// 取消保存
+const handleclickcancel = () => {
+  editdialogVisible.value = false
+  isEdit.value = false
+  EditName.value = ''
+  EditDataType.value = ''
+  Editdescribe.value = ''
+  isSelectedvalue.value = ''
+  Condition.value = ''
+  temporaryname.value = ''
+}
+
+// 保存字段
+const handleclicksave = () => {
+  if(isEdit.value) {
+    const target = formatList.value.find(obj => obj.name === temporaryname.value);
+    if(EditName.value == '' || EditDataType.value == '' || Editdescribe.value == '' || isSelectedvalue.value == '') {
+      UnableSaveVisible.value = true
+    } else {
+      if(isSelectedvalue.value == '有条件必填' && Condition.value == '') {
+        UnableSaveVisible.value = true
+      } else {
+        const newData = { name: EditName.value, type: EditDataType.value, describe: Editdescribe.value, selecttype: isSelectedvalue.value,requirements: Condition.value }
+        if (target) Object.assign(target, newData);
+        EditName.value = ''
+        EditDataType.value = ''
+        Editdescribe.value = ''
+        isSelectedvalue.value = ''
+        editdialogVisible.value = false
+        Condition.value = ''
+        temporaryname.value = ''
+      }
+    }
+  } else {
+      if(formatList.value.some(obj => Object.values(obj).includes(EditName.value))) {
+        const target = formatList.value.find(obj => obj.name === EditName.value);
+        if(EditName.value == '' || EditDataType.value == '' || Editdescribe.value == '' || isSelectedvalue.value == '') {
+        UnableSaveVisible.value = true
+      } else {
+        if(isSelectedvalue.value == '有条件必填' && Condition.value == '') {
+          UnableSaveVisible.value = true
+        } else {
+          const newData = { name: EditName.value, type: EditDataType.value, describe: Editdescribe.value, selecttype: isSelectedvalue.value,requirements: Condition.value }
+          if (target) Object.assign(target, newData);
+          EditName.value = ''
+          EditDataType.value = ''
+          Editdescribe.value = ''
+          isSelectedvalue.value = ''
+          editdialogVisible.value = false
+          Condition.value = ''
+          temporaryname.value = ''
+        }
+      }
+    } else {
+      if(EditName.value == '' || EditDataType.value == '' || Editdescribe.value == '' || isSelectedvalue.value == '') {
+        UnableSaveVisible.value = true
+      } else {
+        if(isSelectedvalue.value == '有条件必填' && Condition.value == '') {
+          UnableSaveVisible.value = true
+        } else {
+          formatList.value.push({
+            name: EditName.value,
+            type: EditDataType.value,
+            describe: Editdescribe.value,
+            selecttype: isSelectedvalue.value,
+            requirements: Condition.value
+          })
+          EditName.value = ''
+          EditDataType.value = ''
+          Editdescribe.value = ''
+          isSelectedvalue.value = ''
+          editdialogVisible.value = false
+          Condition.value = ''
+          temporaryname.value = ''
+        }
+      }
+    }
+  }
+}
+
+// 删除字段
+const handelclickdelete = (val: any) => {
+  formatList.value.splice(formatList.value.indexOf(val), 1)
+}
+</script>
+<template>
+  <div>
+    <div class="output-title">
+      <span class="stars_red">*</span>输出格式类型
+    </div>
+    <el-select
+      v-model="outputvalue"
+      placeholder="Select"
+      style="width: 480px;margin: 4px 0 16px 0;"
+    >
+      <el-option
+        v-for="item in outputoptions"
+        :key="item.value"
+        :label="item.label"
+        :value="item.value"
+      />
+    </el-select>
+    <div>
+      <el-button class="el-button--noborder--configuration prompt-button" @click="handleaddclick">+ 添加字段</el-button>
+    </div>
+    <div class="empty" v-if="formatList.length == 0 ">
+      <div>
+        <div><img :src="addimg" width="100" /></div>
+        <el-button @click="handleaddclick" class="el-button--main">+ 添加字段</el-button>
+      </div>
+    </div>
+    <div class="output-item" v-for="(item, index) in formatList" :key="index">
+      <div class="output-flex">
+        <div class="output-flex-left">
+          <div class="output-item-title">{{ item.name }}</div>
+          <div class="output-item-type">{{ item.type }}</div>
+        </div>
+        <div class="output-flex-left">
+          <el-button class="el-button--blue" style="height: 24px;width: 24px;" @click="handelclickedit(item)">
+            <span class="font_family icon-icon_edit_b icon_dark"></span>
+          </el-button>
+          <el-button @click="handelclickdelete(item)" class="el-button--blue" style="height: 24px;width: 24px;">
+            <span class="font_family icon-icon_delete_b icon_dark"></span>
+          </el-button>
+        </div>
+      </div>
+      <div class="output-describe">{{ item.describe }}</div>
+      <div class="output-select">{{ item.selecttype }}<span class="output-select" v-if="item.selecttype === '有条件必填'">({{item.requirements}})</span></div>
+    </div>
+    <el-dialog
+      v-model="editdialogVisible"
+      title="编辑字段"
+      :modal="false"
+      :show-close="false"
+      :close-on-click-modal="false"
+      class="dialog-edit"
+      width="480"
+    > 
+      <div class="dialog-title"><span class="stars_red">*</span>字段名称</div>
+      <el-input v-model="EditName" style="margin-bottom: 16px;"></el-input>
+      <div class="dialog-title"><span class="stars_red">*</span>数据类型</div>
+      <el-input v-model="EditDataType" style="margin-bottom: 16px;"></el-input>
+      <div class="dialog-title"><span class="stars_red">*</span>描述</div>
+      <el-input type="textarea" v-model="Editdescribe" style="margin-bottom: 16px;"></el-input>
+      <div class="dialog-title"><span class="stars_red">*</span>是否必填</div>
+      <el-select
+        v-model="isSelectedvalue"
+        placeholder="Select"
+        style="margin-bottom: 16px;"
+      >
+        <el-option
+          v-for="item in isSelected"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
+      </el-select>
+      <div v-if="isSelectedvalue === '有条件必填'" class="dialog-title"><span class="stars_red">*</span>条件要求</div>
+      <el-input v-if="isSelectedvalue === '有条件必填'"  v-model="Condition"></el-input>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="dialog_button" type="default" @click="handleclickcancel">
+            <span class="font_family icon-icon_return_b icon_dark" style="margin-right: 5px;"></span>Cancel
+          </el-button>
+          <el-button class="el-button--dark dialog_button" @click="handleclicksave">
+            <span class="font_family icon-icon_save_b icon_dark" style="margin-right: 5px;"></span>Save
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog v-model="UnableSaveVisible" width="480">
+      <div>Please complete all required fields.</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--danger"
+            @click="UnableSaveVisible = false"
+            style="width: 100px"
+          >
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon iconfont_warning">
+            <svg class="iconfont icon_danger" aria-hidden="true">
+              <use xlink:href="#icon-icon_fail_fill_b"></use>
+            </svg>
+          </span>
+          Unable to Save
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.stars_red {
+  color: var(--color-danger);
+}
+.empty {
+  display: flex;
+  justify-content: center;
+  height: 215px;
+}
+.output-title {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--color-neutral-2);
+}
+.prompt-button {
+  margin-bottom: 8px;
+}
+.output-item {
+  background-color: var(--color-personal-preference-bg);
+  padding: 8px;
+  border-radius: 12px;
+  margin-bottom: 8px;
+}
+.output-flex {
+  display: flex;
+  justify-content: space-between;
+}
+.output-flex-left {
+  display: flex;
+  align-items: center;
+}
+.output-item-title {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  margin-right: 8px;
+}
+.output-item-type {
+  color: var(--color-white);
+  background-color: var(--color-output-type-string-bg);
+  font-size: 12px;
+  border-radius: 3px;
+  padding: 1px 4px;
+}
+.output-describe {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  margin: 6px 0;
+}
+.output-select {
+  color:var(--color-output-select-text);
+  font-size: 12px;
+}
+:deep(.dialog_button) {
+  padding: 10px 34px !important;
+  height: 40px;
+}
+:deep(.el-dialog) {
+  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.25) ;
+}
+.dialog-title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+  margin-bottom: 8px;
+}
+</style>

+ 263 - 0
src/views/PromptConfiguration/src/components/PreviewTesting.vue

@@ -0,0 +1,263 @@
+<script setup lang="ts">
+import icon from '../images/icon_ai_test@2x.png'
+import CodeBlock from './CodeBlock.vue'
+import moment from 'moment'
+
+const PromptdialogVisible = ref(false)
+const testquestionvalue = ref('')
+
+const props = defineProps({
+  prompttext: {
+    type: String
+  },
+  testquestion: {
+    type: Boolean,
+    default: true
+  },
+  promptValue: {
+    type: Array
+  },
+  
+})
+
+const prompttext = ref(props.prompttext)
+const testquestion = ref(props.testquestion)
+watch(
+  () => props.prompttext,
+  (current) => {
+    prompttext.value = current
+  }
+)
+watch(
+  () => props.testquestion,
+  (current) => {
+    testquestion.value = current
+  }
+)
+
+const emits = defineEmits(['handleprompt'])
+
+const handleclickprompt = () => {
+  PromptdialogVisible.value = true
+  emits('handleprompt')
+}
+
+// 导出为txt
+const exporttxt = () => {
+  // 创建 Blob 对象
+  const blob = new Blob([prompttext.value], { type: 'text/plain' })
+   // 创建下载链接
+   const link = document.createElement('a')
+  link.href = URL.createObjectURL(blob)
+  const currentDate = moment();
+  const formattedDate = currentDate.format('YYYY-MM-DD HH:mm');
+  link.download = `${formattedDate} Prompt.txt` // 自定义文件名
+  
+  // 触发下载
+  link.click()
+  
+  // 清理内存
+  URL.revokeObjectURL(link.href)
+}
+
+const unformattedJson = ref({})
+
+const PromptAITest = (val:any) => {
+  if(testquestion.value == false) {
+    $api.PromptAITest({
+      test_ai_method: val,
+      test_question: testquestionvalue.value,
+      prompt: prompttext.value
+    }).then((res) => {
+      if (res.code === 200) {
+        unformattedJson.value = res.data
+      }
+    })
+  }
+}
+
+</script>
+
+<template>
+  <div>
+    <div class="preview">
+      <div class="preview_title">
+        新的prompt预览
+        <el-button class="el-button--default" @click="handleclickprompt"><span class="font_family icon-icon_view_b icon_dark" style="margin-right: 5px;" ></span>查看完整Prompt</el-button>
+      </div>
+      <el-dialog
+        v-model="PromptdialogVisible"
+        width="1000px"
+        :modal="false"
+        align-center
+        :close-on-click-modal="false"
+      > 
+        <template #header>
+          <div class="dialog-header">
+            <div style="width: 55%;display: flex;justify-content: end;">查看完整Prmopt</div>
+            <el-button class="el-button--default" @click="exporttxt"><span class="font_family icon-icon_import_b icon_dark" style="margin-right: 5px;" ></span>导出完整日志</el-button>
+          </div>
+        </template>
+        <pre class="diaolog-content">
+          {{ prompttext }}
+        </pre>
+      </el-dialog>
+      <div class="preview_content" v-if="promptValue.length == 0">暂无数据</div>
+      <div class="preview_content" v-else>
+        <div class="preview_content_title">提示词摘要</div>
+        <div class="preview_content_item" v-for="(item, index) in promptValue" :key="index">{{ item }}</div>
+      </div>
+    </div>
+    <div class="test">
+      <div class="test_title">
+        测试问题
+      </div>
+      <div class="test_diabled" v-if="testquestion">
+        请设置<span class="test_disabled_bold">系统角色+表结构+响应规则+输出格式</span>后再进行测试
+      </div>
+      <div v-else>
+        <el-input type="textarea" v-model="testquestionvalue"></el-input>
+      </div>
+      <div class="test_flex">
+        <div class="test_button" :class="{opacity: testquestion}" @click="PromptAITest('DS')">
+          <div class="test_button_color"><img :src="icon" width="16px" /> Test with DS</div>
+        </div>
+        <div class="test_button" :class="{opacity: testquestion}" @click="PromptAITest('Claude')">
+          <div class="test_button_color"><img :src="icon" width="16px" />Test with Claude</div>
+        </div>
+      </div>
+      <div class="test_title">
+        测试结果
+      </div>
+      <div class="test_result">
+        <div v-if="Object.keys(unformattedJson).length == 0" class="test_result_text">暂无数据</div>
+        <CodeBlock v-else language="json" :raw-code="JSON.stringify(unformattedJson)">
+        </CodeBlock>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.preview {
+  background: #FCF7FF;
+  padding: 16px;
+}
+.preview_title {
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  margin-bottom: 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.preview_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-3);
+}
+.test {
+  padding: 0 16px;
+}
+.test_title {
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  margin: 17px 0 8px 0;
+}
+.test_diabled {
+  height: 80px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 6px;
+  border: 1px solid rgba(234, 235, 237, 0.30);
+  background-color: #F4F4F4;
+  color: var(--color-neutral-3);
+  font-size: 12px;
+  font-weight: 400;
+}
+.test_disabled_bold {
+  font-weight: 700;
+  color: var(--color-neutral-3);
+  font-size: 12px;
+}
+.test_flex {
+  display: flex;
+  margin-top: 8px;
+}
+.test_button {
+  border: 2px solid transparent;
+  border-radius: 12px;
+  background-clip: padding-box, border-box;
+  background-origin: padding-box, border-box;
+  background-image: linear-gradient(to bottom, #FFF, #FFF), linear-gradient(to bottom, #FF7500, #8112FF);
+  padding: 8px;
+  margin-right: 8px;
+  cursor: pointer;
+}
+.test_button_color {
+  background: linear-gradient(90deg, var(--1-gradient-ai-robot-0, #FF7500) 1.87%, var(--1-gradient-ai-robot-100, #8112FF) 56.62%);
+  background-clip: text;
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+}
+.test_result {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 80px;
+  background-color: #2B2F36;
+  color: rgba(240, 241, 243, 0.30);
+  border-radius: 6px;
+}
+.test_result_text {
+  color: rgba(240, 241, 243, 0.30);
+}
+.opacity {
+  opacity: 0.3;
+  cursor: no-drop;
+}
+.preview_content_title {
+  font-weight: 400;
+  font-size: 14px;
+  color: var(--color-neutral-1);
+}
+.preview_content_item {
+  font-weight: 400;
+  font-size: 14px;
+  color: var(--color-neutral-2);
+  margin: 8px 0;
+}
+.preview_content_item:last-child{
+  margin-bottom: 0;
+}
+.el-button--default {
+  width: 158px;
+  height: 40px;
+  background-color: transparent;
+}
+.diaolog-content {
+  background-color: #F8F9FD;
+  padding: 8px;
+  max-height: 720px;
+  line-height: 21px; 
+  overflow-y: scroll;
+  white-space: break-spaces;
+}
+:deep(.el-dialog) {
+  min-height: 800px;
+}
+.dialog-header {
+  height: 48px;
+  width: 95%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+:deep(.el-dialog__header ) {
+  padding: 0;
+  height: 48px;
+}
+</style>

+ 137 - 0
src/views/PromptConfiguration/src/components/RespnseConfiguration.vue

@@ -0,0 +1,137 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import addimg from '../images/default_add@2x.png'
+
+interface TypeItem {
+  description: string
+}
+interface Props {
+  stepDataprops: TypeItem []
+}
+const props = withDefaults(defineProps<Props>(), {})
+
+const stepData = ref(props.stepDataprops)
+watch(
+  () => props.stepDataprops,
+  (current) => {
+    stepData.value = current
+    if(stepData.value == null) {
+      stepData.value = []
+    }
+  }
+)
+
+// 上移
+const updatastep = (index: number) => {
+  if (index > 0) {
+    const temp = stepData.value[index - 1]
+    stepData.value[index - 1] = stepData.value[index]
+    stepData.value[index] = temp
+  }
+}
+
+// 下移
+const downdatastep = (index: number) => {
+  if (index < stepData.value.length - 1) {
+    const temp = stepData.value[index + 1]
+    stepData.value[index + 1] = stepData.value[index]
+    stepData.value[index] = temp
+  }
+}
+
+// 删除
+const deletedatastep = (index: number) => {
+  stepData.value.splice(index, 1)
+}
+
+// 添加步骤
+const addstepdata = () => {
+  stepData.value.push({
+    description: ''
+  })
+}
+
+defineExpose({
+  addstepdata
+})
+</script>
+
+<template>
+  <div class="empty" v-if="stepData.length === 0">
+    <div>
+      <div><img :src="addimg" width="100" /></div>
+      <el-button @click="addstepdata" class="el-button--main">+ 添加步骤</el-button>
+    </div>
+  </div>
+  <div
+    class="detail-step-item"
+    v-else
+    v-for="(stepItem, index) in stepData"
+    :key="index"
+  >
+    <div class="regulation">
+      <div class="left-step-icon">{{ index+1 }}</div>
+      <div class="right-info">
+        <el-input v-model="stepItem.description" type="textarea" style="width: 92%;"></el-input>
+        <el-button class="el-button--noborder--configuration" style="margin-left: 4px;" @click="updatastep(index)">
+          <span class="font_family icon-icon_moveup_b icon_dark"></span>
+        </el-button>
+        <el-button @click="downdatastep(index)" class="el-button--noborder--configuration">
+          <span class="font_family icon-icon_movedown_b icon_dark"></span>
+        </el-button>
+        <el-button @click="deletedatastep(index)" class="el-button--noborder--configuration">
+          <span class="font_family icon-icon_delete_b icon_dark"></span>
+        </el-button>
+      </div>
+    </div>
+    <div v-if="index+1 !== stepData.length " class="line"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.empty {
+  display: flex;
+  margin-top: 30px;
+  justify-content: center;
+  height: 215px;
+}
+:deep(.el-button, .el-button.is-round) {
+  padding: 8px 15px;
+}
+.el-button--main {
+  margin-top: 10px;
+}
+.detail-step-item {
+  position: relative;
+}
+.regulation {
+  display: flex;
+}
+.left-step-icon {
+  width: 22px;
+  height: 22px;
+  margin-right: 8px;
+  border-radius: 50%;
+  background-color: var(--color-neutral-1);
+  font-size: 12px;
+  font-weight: 700;
+  text-align: center;
+  line-height: 22px;
+  color: var(--color-mode);
+}
+.line {
+  position: absolute;
+  top: 22px;
+  height: 74px;
+  margin-left: 10px;
+  border-left: 1px solid var(--color-neutral-1);
+}
+.right-info {
+  width: 100%;
+  margin-bottom: 16px;
+  display: flex;
+}
+:deep(.el-button.el-button--noborder--configuration) {
+  padding: 20px 12px;
+}
+</style>

+ 201 - 0
src/views/PromptConfiguration/src/components/TableConfiguration.vue

@@ -0,0 +1,201 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import addimg from '../images/default_add@2x.png'
+
+const headerCoulumn = ref([
+  {
+    name: '字段名称',
+    width: '13%'
+  },
+  {
+    name: '数据类型',
+    width: '13%'
+  },
+  {
+    name: '描述',
+    width: '56%'
+  },
+  {
+    name: '示例数据',
+    width: '13%'
+  },
+  {
+    name: '操作',
+    width: '5%'
+  }
+])
+
+interface TypeItem {
+  name: string
+  type: string
+  description: string
+  exampledata: string
+}
+interface Props {
+  tableDataList: TypeItem []
+}
+const props = withDefaults(defineProps<Props>(), {})
+
+const tableData = ref(props.tableDataList)
+watch(
+  () => props.tableDataList,
+  (current) => {
+    tableData.value = current
+    if(tableData.value == null) {
+      tableData.value = []
+    }
+  }
+)
+
+const addNewTableColumn = () => {
+  const item = {
+    name: '',
+    type: '',
+    description: '',
+    exampledata: '',
+  }
+  tableData.value.unshift(item)
+}
+
+// 展示全部字段
+const isShowAll = ref(false)
+const clickShowAll = () => {
+  if(tableData.value.length > 3) {
+    isShowAll.value = !isShowAll.value
+  }
+}
+
+// 删除字段
+const deleteitem = (item: any) => {
+  tableData.value.splice(tableData.value.indexOf(item), 1)
+}
+
+defineExpose({
+  addNewTableColumn
+})
+</script>
+<template>
+  <div class="tableconfiguration">
+    <div class="table_header">
+      <div class="header_item" v-for="(item,index) in headerCoulumn" :key="index" :style="{width: item.width}">{{ item.name }}</div>
+    </div>
+    <div class="table_content" :class="{ 'table_content_scroll': isShowAll }">
+      <div class="empty" v-if="tableData.length === 0">
+        <div>
+          <div><img :src="addimg" width="100" /></div>
+          <el-button @click="addNewTableColumn" class="el-button--main">+ 添加字段</el-button>
+        </div>
+      </div>
+      <div v-else>
+        <div v-for="(item,index) in tableData" :key="index" class="table-content-item">
+          <div style="width: 13%;" class="table-content-item-input">
+            <el-input v-model="item.name" type="textarea" placeholder="输入字段名称"></el-input>
+          </div>
+          <div style="width: 13%;" class="table-content-item-input">
+            <el-input v-model="item.type" type="textarea" placeholder="选择数据类型"></el-input>
+          </div>
+          <div style="width: 56%;" class="table-content-item-input">
+            <el-input v-model="item.description" type="textarea" placeholder="简要描述字段用途"></el-input>
+          </div>
+          <div style="width: 13%;" class="table-content-item-input">
+            <el-input v-model="item.exampledata" type="textarea" placeholder="输入示例数据"></el-input>
+          </div>
+          <div style="width: 5%;padding-top: 8px;" class="table-content-item-input">
+            <el-button @click="deleteitem(item)" class="el-button--blue" style="height: 24px;width: 24px;">
+              <span class="font_family icon-icon_delete_b icon_dark"></span>
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="showall" @click="clickShowAll" v-if="isShowAll"><span class="font_family icon-icon_up_b icon_dark icon_down"></span></div>
+  <div class="showall" @click="clickShowAll" v-else>展示全部字段<span class="font_family icon-icon_dropdown_b icon_dark icon_down"></span></div>
+</template>
+
+<style lang="css" scoped>
+.tableconfiguration {
+  border: 1px solid var(--color-border);
+  border-radius: 6px;
+  margin-bottom: 18px;
+}
+.table_header {
+  display: flex;
+  height: 40px;
+  align-items: center;
+  justify-content: space-between;
+  border-radius: 6px 6px 0 0;
+  background-color: var(--color-table-header-bg);
+  border-bottom: 1px solid var(--color-border);
+}
+.header_item {
+  border-right: 1px solid var(--color-border);
+  display: flex;
+  height: 40px;
+  align-items: center;
+  justify-content: start;
+  padding-left: 8px;
+  font-size: 14px;
+  font-weight: 700;
+}
+.header_item:last-child {
+  border-right: none;
+}
+.table_content {
+  max-height: 215px;
+  overflow: hidden;
+}
+.table_content_scroll {
+  min-height: 224px;
+  max-height: 725px;
+  overflow: scroll;
+}
+.empty {
+  display: flex;
+  margin-top: 30px;
+  justify-content: center;
+  height: 215px;
+}
+:deep(.el-button, .el-button.is-round) {
+  padding: 8px 15px;
+}
+.el-button--main {
+  margin-top: 10px;
+}
+.table-content-item {
+  display: flex;
+  height: 72px;
+}
+.table-content-item-input {
+  padding: 4px;
+  border-right: 1px solid var(--color-border);
+  border-bottom: 1px solid var(--color-border);
+}
+.table-content-item-input:last-child {
+  border-right: none;
+}
+:deep(.el-textarea__inner) {
+  height: 64px !important;
+  box-shadow: none;
+  border: 1px solid var(--color-border);
+}
+:deep(.el-select__wrapper) {
+  height: 64px !important;
+  box-shadow: none;
+  border: 1px solid var(--color-border);
+  align-items: baseline;
+}
+.showall {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.showall:hover {
+  color: var(--color-theme);
+  span {
+    fill: var(--color-theme);
+    color: var(--color-theme);
+  }
+  cursor: pointer;
+}
+</style>

BIN
src/views/PromptConfiguration/src/images/default_add@2x.png


BIN
src/views/PromptConfiguration/src/images/icon_ai_test@2x.png


BIN
src/views/PromptConfiguration/src/images/icon_success_big@2x.png


BIN
src/views/PromptConfiguration/src/images/submit_successful.png


+ 7 - 2
src/views/SystemMessage/src/SystemMessage.vue

@@ -192,7 +192,7 @@ onMounted(() => {
                 <span>{{ handleCount(unreadNotificationList.length) }}</span>
               </div>
             </template>
-            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'Unread'">
+            <div style="padding-bottom: 20px" v-if="activeTabName === 'Unread'">
               <NotificationMessageCard
                 v-if="activeTabName === 'Unread'"
                 :data="unreadNotificationList"
@@ -203,7 +203,7 @@ onMounted(() => {
           </el-tab-pane>
           <el-tab-pane label="Read" name="Read">
             <template #label><span style="margin-right: 4px">Read</span> </template>
-            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'Read'">
+            <div style="padding-bottom: 20px" v-if="activeTabName === 'Read'">
               <NotificationMessageCard
                 v-if="activeTabName === 'Read'"
                 :updateReadCardsOnChange="false"
@@ -322,5 +322,10 @@ onMounted(() => {
       height: 100%;
     }
   }
+  :deep {
+    .scroller {
+      padding-top: 10px;
+    }
+  }
 }
 </style>

+ 0 - 1
src/views/Tracking/src/components/PublicTracking/src/components/MilestonesTable.vue

@@ -18,7 +18,6 @@ const tableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 5 - 5
src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue

@@ -146,11 +146,11 @@ const SubscribeShipments = () => {
 <template>
   <div class="tracking-detail" v-vloading="loading">
     <!-- 分享链接 -->
-    <div class="share-link" @click="openShareDialog">
-      <el-tooltip content="Share" :offset="4">
+    <el-tooltip content="Share" :offset="4" placement="top">
+      <div class="share-link" @click="openShareDialog">
         <span class="font_family icon-icon_share_b share-icon"></span>
-      </el-tooltip>
-    </div>
+      </div>
+    </el-tooltip>
     <div class="header" :class="{ 'is-dark': themeStore.theme === 'dark' }">
       <div class="detail-status">
         <span
@@ -497,7 +497,7 @@ const SubscribeShipments = () => {
   }
   .share-link {
     position: fixed;
-    bottom: 152px;
+    bottom: 248px;
     right: 20px;
     display: flex;
     align-items: center;

+ 0 - 2
src/views/Tracking/src/components/TrackingDetail/src/components/AMS&ISF.vue

@@ -73,7 +73,6 @@ const AMSTableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,
@@ -105,7 +104,6 @@ const ISFTableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Tracking/src/components/TrackingDetail/src/components/AddReferenceDialog.vue

@@ -45,7 +45,6 @@ const tableData = ref<VxeGridProps<any>>({
   ],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue

@@ -15,7 +15,6 @@ const tableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Tracking/src/components/TrackingDetail/src/components/ContainersView.vue

@@ -15,7 +15,6 @@ const tableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Tracking/src/components/TrackingDetail/src/components/MilestonesTable.vue

@@ -16,7 +16,6 @@ const tableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue

@@ -262,7 +262,6 @@ const trackingTable = ref<any>({
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 20, scrollToTopOnChange: true },
   scrollX: { enabled: true, gt: 2, scrollToLeftOnChange: true },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

+ 0 - 1
src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

@@ -66,7 +66,6 @@ const tableData = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
-  stripe: true,
   emptyText: ' ',
   showHeaderOverflow: true,
   showOverflow: true,

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.