Ver código fonte

feat: 基本实现前端图生文、文生图的效果

1. 与后端联调,接口联通
2. 调整部分组件的文件名
3. 优化部分组件
zhupei 1 ano atrás
pai
commit
43e89ecd6d

+ 3 - 1
.gitignore

@@ -28,4 +28,6 @@ pnpm-debug.log*
 
 *.backup.*
 
-example.*
+example.*
+
+no-use.*

+ 84 - 28
src/components/DialogWrap.vue

@@ -11,15 +11,18 @@
           <p>{{ open }}</p>
         </footer>
       </div>
-      <div class="section-suggestions">
+
+      <!-- <div class="section-suggestions">
         <div :class="['section-suggestion', readonly ? 'disabled' : '']" v-for="(suggestion, key) in suggestions"
           :key="key" @click="selectSuggestion(suggestion)">
           {{ suggestion }}<icon-arrow-right />
         </div>
-      </div>
+      </div> -->
+
+      <Suggestion :suggestions="suggestions" :readonly="readonly"/>
 
       <div class="dialog-list" v-if="dialogList.length > 0" :key="randomKey">
-        <div :class="['dialog-item', isSetting ? '' : 'dialog-absolute']" v-for="(item, key) in dialogList"
+        <div :class="['dialog-item', isSetting ? '' : 'dialog-absolute', 'dialog-item-' + key]" v-for="(item, key) in dialogList"
           :key="item.key" :data-item-index="key">
           <div class="dialog-header" v-if="item?.question">
             <div class="user-name" style="background-color: rgb(255, 110, 110)">
@@ -179,7 +182,7 @@
               <span>{{ item.other.token }}</span>
             </p>
             <p v-else></p>
-            <div>
+            <div v-if="show_info_btns">
               <a-tooltip content="重新生成">
                 <p @click="reQuestion(item.question)" :class="[readonly ? 'disabled' : '']">
                   <img src="@/assets/icons/refresh.png" width="15" />
@@ -228,15 +231,30 @@ import { defineComponent, ref, onBeforeUnmount, onMounted, reactive, nextTick, i
 
 import formatAnwserString from './refference-effect/format-anwser-string';
 import applyRefferencePopover from './refference-effect/apply-refference-popover';
+import applyImageDisplay from './refference-effect/apply-image-display';
 import initTempHistory, {getChatIdFromUrl} from './refference-effect/temp-history';
 
 import { getDocumentUrl } from '../ajax/document';
 
+import Suggestion from './dialog-wrap/Suggestion.vue';
+
 export default defineComponent({
   components: {
-    DialogFeedback
+    DialogFeedback,
+
+    Suggestion
   },
-  props: ["open", "suggestions", "isSetting", "readonly", "type", "list", "agentId", "dialogId", 'reference'],
+  props: [
+    "open", 
+    "suggestions", 
+    "isSetting", 
+    "readonly", 
+    "type", 
+    "list", 
+    "agentId", 
+    "dialogId", 
+    'reference'
+  ],
   setup(props, { emit }) {
     const knowledgeList = ref([[{}]]);
     const DOCUMENT_RESULT = {};
@@ -252,6 +270,8 @@ export default defineComponent({
     const { helper, ajax } = inject("$global");
     const userName = ref(helper.read("userName") || "");
 
+    const show_info_btns = ref(false);
+
     /**
      * 初始化临时历史记录
      * 在对话过程中,消息记录是一条一条的呈现
@@ -343,11 +363,11 @@ export default defineComponent({
     const handleCancel = () => {
       showFeedback.value = false;
     };
-    const selectSuggestion = (text) => {
-      if (!props.readonly) {
+    // const selectSuggestion = (text) => {
+    //   if (!props.readonly) {
 
-      }
-    };
+    //   }
+    // };
 
     const copyAnswer = (text) => {
       if (!props.readonly) {
@@ -435,7 +455,7 @@ export default defineComponent({
       helper.$bus.off("send-message");
       helper.$bus.off("clear-message");
       helper.$bus.off("stop-writing");
-      helper.webSocket.close();
+      helper.webSocket?.close();
     });
 
     onMounted(() => {
@@ -445,9 +465,14 @@ export default defineComponent({
        * 答案中 引用图标(感叹号)点击时,浮层显示引用内容
        */
       applyRefferencePopover(props.list);
+      applyImageDisplay(props.list);
 
-      // socket 消息队列
-      // TODO: 只允许保存一条消息
+      /**
+       * TODO: 只允许保存一条消息,避免出现多条消息同时发送的情况
+       * 当网络状况不佳,用户发送第一条数据时,socket还未连接成功,紧接着又发送第二条数据,第三条数据....
+       * 此时,消息队列里将会有多条数据等待发送
+       * 当socket连接成功后,遍历消息队列里的数据,会将这多条数据一同发送,此时就会出现异常情况。
+       */
       let msg_list = [];
 
       function createSocket() {
@@ -501,20 +526,14 @@ export default defineComponent({
       // 创建socket链接
       createSocket();
 
-      helper.webSocket.onmessage = (event) => {
-        /**
-         * 例如:ws://localhost/api/chat/ws/42e4fcdc9bea11efac300242ac160006/765f672c700f49b79a473fc906fe388c?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwidXNlcl9pZCI6MjEsImV4cCI6MTczMjQzNzkzNH0.7SPOZpnaTUu2KPzWIJrhhkRehjePeuL8TABo7rNgnCY
-         */
-        const socket_url = event.target.url; // socket连接地址
-        const cur_chat_id = getChatIdFromUrl(socket_url);
-
-        // setTempHistory(event);
-        tempHistory.setHistory(event);
-
+      /**
+       * 处理socket数据
+       */
+      function handleSocketMessage(evt_dt, cur_chat_id){
         let res_data = null;
 
         try{
-          res_data = JSON.parse(event.data);
+          res_data = JSON.parse(evt_dt);
         }catch(err){
           console.log(err);
         }
@@ -576,7 +595,7 @@ export default defineComponent({
           if (props.type == 'smartData') {
             answerData.value = '正在处理中...'
             // 以下是智能数据对话的变量
-            const { image_url, excel_url, excel_name, code, sql } = JSON.parse(event.data);
+            const { image_url, excel_url, excel_name, code, sql } = JSON.parse(evt_dt);
             const port = window.location.port ? `:${window.location.port}` : '';
 
             // 如果有图片或excel,则更新图片或excel地址
@@ -600,7 +619,7 @@ export default defineComponent({
           }
           // 出题
           if (props.type == 'question') {
-            const { file_url, file_name } = JSON.parse(event.data);
+            const { file_url, file_name } = JSON.parse(evt_dt);
             const port = window.location.port ? `:${window.location.port}` : '';
 
             if (file_url) {
@@ -634,6 +653,24 @@ export default defineComponent({
         // 获取所有的临时历史记录
         const full_history = tempHistory.getHistory();
         applyRefferencePopover(full_history);
+        const current_index = tempHistory.getChatIndex(cur_chat_id);
+        applyImageDisplay(full_history, current_index);
+      }
+
+      helper.webSocket.onmessage = (event) => {
+        const evt_dt = event.data;
+
+        /**
+         * 例如:ws://localhost/api/chat/ws/42e4fcdc9bea11efac300242ac160006/765f672c700f49b79a473fc906fe388c?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwidXNlcl9pZCI6MjEsImV4cCI6MTczMjQzNzkzNH0.7SPOZpnaTUu2KPzWIJrhhkRehjePeuL8TABo7rNgnCY
+         */
+        const socket_url = event.target.url; // socket连接地址
+        const cur_chat_id = getChatIdFromUrl(socket_url);
+
+        // setTempHistory(event);
+        tempHistory.setHistory(event);
+
+        // 处理socket数据
+        handleSocketMessage(evt_dt, cur_chat_id);
       };
 
       /**
@@ -656,6 +693,21 @@ export default defineComponent({
           sendData.doc_ids = [helper.read('docId')];
         }
 
+        // 小数绘图
+        if(props.type === 'huitu'){
+          const img_file = helper.read('sendData');
+          const upload_file_id = img_file.upload_file_id;
+
+          sendData = {
+            message: value,
+            upload_file_id: upload_file_id
+          }
+
+          setTimeout(() => {
+            helper.write('sendData', {}); 
+          });
+        }
+
         const dt = JSON.stringify(sendData);
 
         // 发送socket数据
@@ -681,7 +733,7 @@ export default defineComponent({
       copyAnswer,
       dialogList,
       getHTML,
-      selectSuggestion,
+      // selectSuggestion,
       dialogScroll,
       reQuestion,
       menuList,
@@ -697,7 +749,9 @@ export default defineComponent({
       downloadFile,
 
       openDocument,
-      openDocuementPage
+      openDocuementPage,
+
+      show_info_btns
     };
   }
 });
@@ -1255,6 +1309,7 @@ export default defineComponent({
   }
 }
 
+/**
 .section-suggestions {
   margin: 12px 0 20px;
 
@@ -1280,6 +1335,7 @@ export default defineComponent({
     }
   }
 }
+ */
 
 .dialog-other {
   margin-bottom: 16px;

+ 10 - 3
src/components/MessagePicUpload.vue

@@ -28,6 +28,7 @@
 </template>
 <script>
 import { defineComponent, ref, inject, onMounted, onBeforeUnmount } from "vue";
+
 export default defineComponent({
     components: {},
     props: ["readonly", "eventName"],
@@ -36,9 +37,15 @@ export default defineComponent({
         const value = ref("");
         const maxLength = ref(6000);
         const sendMessage = () => {
-            if (value.value.length > 0 && !props.readonly) {
-                const eventName = props.eventName || "send-message";
-                helper.$bus.emit(eventName, value.value);
+            const msg_str = value.value;
+
+            if (msg_str.length > 0 && !props.readonly) {
+                // console.log("1234444");
+                // const eventName = props.eventName || "send-message";
+                // console.log("1234444", eventName, msg_str);
+                // helper.$bus.emit(eventName, msg_str);
+
+                helper.$bus.emit('open-dialog', msg_str);
                 value.value = "";
             }
         };

+ 72 - 48
src/components/PictureCards.vue

@@ -1,6 +1,6 @@
 <template>
   <main>
-    <section>
+    <section class="xiaoshuhuitu-sections">
       <div v-for="(item, key) in data" :key="key" :style="{ background: item.background }">
         <img :src="getAssetURL(item.img)" />
         <div :style="{ color: item.color }">
@@ -10,16 +10,11 @@
       </div>
     </section>
 
-    <AnwserPictureResult :image_list="image_list"/>
-    <!-- <AnwserPictureLoading /> -->
-
-    <!-- <ImgEditBtn /> -->
-    
-    <footer v-if="hasUpload" class="picture-upload-footer">
+    <footer class="picture-upload-footer">
       <a-popover :popup-visible="showUpload" position="tl" @popup-visible-change="() => {
         showDeleteTooltip = false;
       }">
-        <div :class="{ 'text-hide': showUpload }">
+        <div class="text-hide">
           <div @click="() => {
             showUpload = true;
           }
@@ -44,12 +39,21 @@
               </div>
             </header>
 
-            <div>
+            <div class="xiaoshuhuitu-image-upload">
               <section>
-                <a-upload draggable :multiple="!uploadFile" :limit="50" :show-file-list="false"
-                  @before-upload="onBeforeUpload" @exceed-limit="onExceedLimit" @before-remove="onBeforeRemove"
-                  :custom-request="customRequest" :show-upload-button="{ showOnExceedLimit: true }"
-                  accept=".png,.jpg">
+                <a-upload 
+                  draggable 
+                  list-type="picture"
+                  :multiple="false" 
+                  :limit="1" 
+                  :show-file-list="false"
+                  @before-upload="onBeforeUpload" 
+                  @exceed-limit="onExceedLimit" 
+                  @before-remove="onBeforeRemove"
+                  :custom-request="customRequest" 
+                  :show-upload-button="{ showOnExceedLimit: false }"
+                  accept=".png,.jpg"
+                >
                   <template #upload-button>
                     <div class="upload-main">
                       <p>拖拽图片到这里,或<span>点此上传</span></p>
@@ -61,21 +65,6 @@
                 </a-upload>
               </section>
 
-              <!-- <section v-else>
-                <a-upload draggable :multiple="!uploadFile" :limit="50" :show-file-list="false"
-                  @before-upload="onBeforeUpload" @exceed-limit="onExceedLimit" @before-remove="onBeforeRemove"
-                  :custom-request="customRequest" :show-upload-button="{ showOnExceedLimit: true }" accept="*">
-                  <template #upload-button>
-                    <div class="upload-main">
-                      <p>拖拽图片到这里,或<span>点此上传</span></p>
-                      <span>
-                        支持上传{{ uploadFile ? ".png,.jpg" : ".xlsx‌文档" }},大小不超过10M
-                      </span>
-                    </div>
-                  </template>
-                </a-upload>
-              </section> -->
-
               <div v-if="tableData.length > 0">
                 <p>
                   <span>已选择图片列表</span>
@@ -133,12 +122,7 @@
       </a-popover>
 
       <span class="placeholder-width-5px"></span>
-      <ComfyuiBtn />
-
-      <!-- <section v-if="hasSlider">
-        <p>出题随机性</p>
-        <a-slider :default-value="50" :style="{ width: '100px' }" />
-      </section> -->
+      <BtnIframeComfyui />
     </footer>
   </main>
 </template>
@@ -146,11 +130,11 @@
 import dayjs from "dayjs";
 import { defineComponent, ref, inject, reactive } from "vue";
 
+import { Message } from '@arco-design/web-vue';
+
 import AnwserPictureResult from './anwser-pictures/AnwserPictureResult.vue';
-// import AnwserPictureLoading from './anwser-pictures/AnwserPictureLoading.vue';
-import ComfyuiBtn from './comfyui/ComfyuiBtn.vue';
+import BtnIframeComfyui from './btn-iframe-comfyui/BtnIframeComfyui.vue';
 
-// import TuiImageEditor from './image-editor/TuiImageEditorVue3.vue';
 import ImgEditBtn from './image-editor/ImgEditBtn.vue';
 
 export default defineComponent({
@@ -158,7 +142,7 @@ export default defineComponent({
   components: {
     AnwserPictureResult,
     // AnwserPictureLoading,
-    ComfyuiBtn,
+    BtnIframeComfyui,
 
     // TuiImageEditor,
     ImgEditBtn
@@ -222,11 +206,49 @@ export default defineComponent({
           }
         } else {
           fileList.value.forEach(file => {
-            formData.append('files', file)
+            formData.append('file', file)
           });
 
-          await ajax.file.uploadExcels(formData);
-          helper.$bus.emit("open-dialog", '合并Excel');
+          const data = await ajax.message.createDialog(props.agentId);
+          const result = await ajax.file.uploadFile(props.agentId, data.chat_id, formData);
+
+          // 保存小数绘图数据
+          helper.write('sendData', {
+            name: '小数绘图',
+
+            agent_id: props.agentId,
+            chat_id: props.chat_id,
+
+            upload_file_id: result.id,
+            mime_type: result.mime_type,
+            name: result.name,
+            created_by: result.created_by,
+          });
+
+          // 隐藏 popover
+          showUpload.value = false;
+
+          Message.info('图片已保存,请输入文字进行提问');
+
+          // 文件清空
+          fileList.value = [];
+
+          // created_at: 1732531229
+          // created_by: "069e660f-ffc1-4b02-bfed-0cfef46c928d"
+          // extension: "png"
+          // id: "0e15a523-d029-4ffd-b0b4-3ca4a99f65a4"
+          // mime_type: "image/png"
+          // name: "下载.png"
+          // size: 67593
+
+          /**
+          {
+              "message":"图片描述了什么",
+              "upload_file_id":"a1c91dda-2752-474f-8d90-ce0f072ee0a2"
+          }
+           */
+
+          // helper.$bus.emit("open-dialog", '合并Excel');
         }
       }
 
@@ -246,7 +268,7 @@ export default defineComponent({
     };
 
     const onExceedLimit = () => {
-      helper.message("error", `最多上传50个文件`);
+      helper.message("error", `最多上传1个文件`);
     };
 
     const customRequest = async (option) => {
@@ -317,11 +339,11 @@ export default defineComponent({
     };
 
 
-    const image_list = [
-      'https://s3.harix.iamidata.com/sd-txt2img-ningbo/20241121_4173677d-b021-4b3a-9535-b7e4de17cded?AWSAccessKeyId=mjf0vn4dlj76z7ph&Expires=2147483647&Signature=kBHLFhQKRUd%2F%2B1hnw0k5DZ8VuA4%3D',
-      'https://s3.harix.iamidata.com/sd-txt2img-ningbo/20241121_f0c4a724-ed43-4950-ade0-60190aa8eda4?AWSAccessKeyId=mjf0vn4dlj76z7ph&Expires=2147483647&Signature=Vd8Ja5X3W8qJFZl30NE8d%2B5GW40%3D',
-      'https://s3.harix.iamidata.com/sd-txt2img-ningbo/20241121_a969df4c-5b6f-4577-827d-f7459e1a1c69?AWSAccessKeyId=mjf0vn4dlj76z7ph&Expires=2147483647&Signature=CHVinI9kJWnToLXWZ5BHdj54nLM%3D'
-    ]
+    // const image_list = [
+    //   'https://s3.harix.iamidata.com/sd-txt2img-ningbo/20241121_4173677d-b021-4b3a-9535-b7e4de17cded?AWSAccessKeyId=mjf0vn4dlj76z7ph&Expires=2147483647&Signature=kBHLFhQKRUd%2F%2B1hnw0k5DZ8VuA4%3D',
+    //   'https://s3.harix.iamidata.com/sd-txt2img-ningbo/20241121_f0c4a724-ed43-4950-ade0-60190aa8eda4?AWSAccessKeyId=mjf0vn4dlj76z7ph&Expires=2147483647&Signature=Vd8Ja5X3W8qJFZl30NE8d%2B5GW40%3D',
+    //   'https://s3.harix.iamidata.com/sd-txt2img-ningbo/20241121_a969df4c-5b6f-4577-827d-f7459e1a1c69?AWSAccessKeyId=mjf0vn4dlj76z7ph&Expires=2147483647&Signature=CHVinI9kJWnToLXWZ5BHdj54nLM%3D'
+    // ]
 
 
     return {
@@ -345,7 +367,7 @@ export default defineComponent({
       handleDeleteButton,
       selectFiles,
 
-      image_list
+      // image_list
     };
   }
 });
@@ -484,10 +506,12 @@ main {
       transition-duration: 0.3s;
       width: 250px;
 
+      /**
       &.text-hide {
         width: 110px;
         overflow: hidden;
       }
+       */
 
       &.text-narrow {
         width: 100px;

+ 9 - 1
src/components/anwser-pictures/AnwserPictureResult.vue

@@ -35,7 +35,7 @@
                 @next="handleNext" 
             />
 
-            <ImgEditBtn :image_url="image_list[cur_index]"/>
+            <ImgEditBtn :image_url="image_list[cur_index - 1]"/>
         </div>
     </div>
 </template>
@@ -111,4 +111,12 @@ const handleNext = () => {
     display: flex;
     align-items: center;
 }
+
+
+</style>
+
+<style>
+.anwser-pictures-images img{
+    width: 100%;
+}
 </style>

+ 58 - 0
src/components/btn-iframe-chujuan/BtnIframeChujuan.vue

@@ -0,0 +1,58 @@
+<template>
+  <a-button @click="handleClick" class="comfyui-btn">
+    <img class="comfyui-logo-img" :src="logo" /> ComfyUI
+  </a-button>
+
+  <a-modal v-model:visible="visible" @ok="handleOk" @cancel="handleCancel" modal-class="comfyui-container-modal" body-class="comfyui-container-body" width="90%">
+    <template #title>
+      <img class="comfyui-logo-img" :src="logo" /> ComfyUI
+    </template>
+    
+    <iframe :src="iframe_src" class="comfyui-iframe"></iframe>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import logo from '../../assets/images/comfyui-logo.png'
+
+const iframe_src = 'http://192.168.20.119:8501';
+
+const visible = ref(false);
+
+const handleClick = () => {
+    visible.value = true;
+};
+const handleOk = () => {
+    visible.value = false;
+};
+const handleCancel = () => {
+    visible.value = false;
+}
+</script>
+
+<style scoped>
+.comfyui-btn{
+    border-radius: 30px;
+    color: #353535;
+}
+.comfyui-logo-img{
+    width: 22px;
+    display: inline-block;
+    margin-right: 5px;
+}
+</style>
+
+<style>
+.comfyui-container-modal{
+
+}
+.arco-modal-body.comfyui-container-body{
+    width: 100%;
+    padding: 0;
+}
+.arco-modal-body.comfyui-container-body .comfyui-iframe{
+    width: 100%;
+    height: 80vh;
+}
+</style>

+ 0 - 0
src/components/comfyui/ComfyuiBtn.vue → src/components/btn-iframe-comfyui/BtnIframeComfyui.vue


+ 47 - 0
src/components/dialog-wrap/Suggestion.vue

@@ -0,0 +1,47 @@
+<template>
+<div class="section-suggestions">
+    <div :class="['section-suggestion', readonly ? 'disabled' : '']" v-for="(suggestion, key) in suggestions"
+    :key="key" @click="selectSuggestion(suggestion)">
+        {{ suggestion }}<icon-arrow-right />
+    </div>
+</div>
+</template>
+<script setup>
+const props = defineProps({
+    suggestions: Array,
+    readonly: Boolean,
+});
+
+const selectSuggestion = (text) => {
+    if (!props.readonly) {
+
+    }
+};
+</script>
+<style>
+.section-suggestions {
+  margin: 12px 0 20px;
+
+  .section-suggestion {
+    width: 380px;
+    height: 40px;
+    line-height: 40px;
+    border: 1px solid #e5e5e5;
+    border-radius: 12px;
+    padding: 0 8px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    cursor: pointer;
+    margin-top: 8px;
+
+    &.disabled {
+      cursor: not-allowed;
+    }
+
+    &:hover {
+      background: rgb(245, 247, 254);
+    }
+  }
+}
+</style>

+ 4 - 0
src/components/image-editor/TuiImageEditorVue3.vue

@@ -438,6 +438,10 @@ onMounted(() => {
     }
 
     editorInstance = new ImageEditor(editor.value, opts);
+    editorInstance.addImageObject(props.image_url).then(objectProps => {
+      // https://nhn.github.io/tui.image-editor/latest/ImageEditor#addImageObject
+      console.log(ojectProps.id, 'console.log(ojectProps.id);');
+    });
     addEventListener();
 });
 

+ 19 - 0
src/components/refference-effect/RootImage.vue

@@ -0,0 +1,19 @@
+<template>
+    <AnwserPictureResult :image_list="image_list"/>
+</template>
+
+<script setup>
+// import RefferenceFlag from './RefferenceFlag.vue'
+
+import AnwserPictureResult from '../anwser-pictures/AnwserPictureResult.vue';
+// import AnwserPictureLoading from './anwser-pictures/AnwserPictureLoading.vue';
+
+const props = defineProps({
+    src: String,
+    alt: String
+});
+
+const image_list = [
+    props.src,
+]
+</script>

+ 0 - 0
src/components/refference-effect/RefferenceRoot.vue → src/components/refference-effect/RootRefference.vue


+ 66 - 0
src/components/refference-effect/apply-image-display.js

@@ -0,0 +1,66 @@
+import dynamicImageDisplay from './effect-image-display';
+
+export default function applyImageDisplay(dialogList, index) {
+    let unmount = () => { };
+
+    /**
+     * 合法数据示例
+     * 
+    [
+        {
+            anwser: '',
+            question: ''
+        }
+    ]
+    */
+
+    // 处理引用数据
+    setTimeout(() => {
+        if (index !== undefined && typeof index === 'number' && index / 1 >= 0) {
+            // 有序号
+            // 查询会话数量
+            const ele_item = document.querySelectorAll('.dialog-item.dialog-item-' + index + ' .xiaoshuhuitu-image-container');
+
+            const len = ele_item.length;
+
+            for (let i = 0; i < len; i++) {
+
+                const ele = ele_item[i]
+
+                if (ele.length === 0) {
+                    return;
+                }
+
+                renderFlag([ele]);
+            }
+        } else {
+            // 无序号,渲染所有的内容
+            const ele = document.querySelectorAll('.xiaoshuhuitu-image-container');
+
+            if (ele.length === 0) {
+                return;
+            }
+
+            renderFlag(ele);
+        }
+    }, 100);
+
+    function renderFlag(ele) {
+        // 遍历找到的所有标记元素
+        ele.forEach((item, idx) => {
+            const data_src = item.getAttribute('data-src');
+            const data_alt = item.getAttribute('data-alt');
+
+            unmount = dynamicImageDisplay(item, {
+                src: data_src,
+                alt: data_alt
+            });
+        });
+    }
+
+    return () => {
+        if (typeof unmount === 'function') {
+            unmount();
+        }
+    }
+}

+ 1 - 1
src/components/refference-effect/apply-refference-popover.js

@@ -1,4 +1,4 @@
-import dynamicRegRefFlag from './refference-flag'
+import dynamicRegRefFlag from './effect-refference-flag'
 
 /**
  * 应用PDF文档引用

+ 36 - 0
src/components/refference-effect/effect-image-display.js

@@ -0,0 +1,36 @@
+import { createApp } from "vue";
+
+import ArcoVue from "@arco-design/web-vue";
+import ArcoVueIcon from "@arco-design/web-vue/es/icon";
+
+import RootImage from './RootImage.vue';
+
+/**
+ * 动态展示生成的图片
+ * @param {Element} mount_element - 挂载点元素
+ * @param {Object} propsObject - props值 
+ * @param {string} propsObject.src - 图片链接
+ * @param {string} propsObject.alt - alt值
+ */
+export default function dynamicImageDisplay(mount_element, propsObject = {}) {
+    /**
+     * @see https://cn.vuejs.org/api/application.html#createapp
+     */
+    const app = createApp(RootImage, {
+        src: propsObject.src,
+        alt: propsObject.alt
+    });
+
+    // 引用字节跳动相关的组件
+    app.use(ArcoVueIcon);
+    app.use(ArcoVue);
+
+    app.mount(mount_element);
+
+    // 返回一个卸载APP的方法
+    return () => {
+        app.unmount();
+    }
+}
+
+

+ 2 - 2
src/components/refference-effect/refference-flag.js → src/components/refference-effect/effect-refference-flag.js

@@ -3,7 +3,7 @@ import { createApp } from "vue";
 import ArcoVue from "@arco-design/web-vue";
 import ArcoVueIcon from "@arco-design/web-vue/es/icon";
 
-import RefferenceRoot from './RefferenceRoot.vue';
+import RootRefference from './RootRefference.vue';
 
 /**
  * 创建一个vue APP,并将此APP挂载到对应的DOM节点中
@@ -26,7 +26,7 @@ export default function dynamicRegRefFlag(mount_element, propsObject = {}) {
     /**
      * @see https://cn.vuejs.org/api/application.html#createapp
      */
-    const app = createApp(RefferenceRoot, {
+    const app = createApp(RootRefference, {
         img: propsObject.refference.img_id || '',
         image_id: propsObject.refference.img_id,
         text: propsObject.refference.content_with_weight || '',

+ 37 - 2
src/components/refference-effect/format-anwser-string.js

@@ -15,7 +15,42 @@ export default function formatAnwserString(string) {
         return '';
     }
 
-    const result = marked.parse(string);
+    /**
+     * 临时替换 []( 为 ![](
+     * 因为后端目前返回的图片格式非图片格式 而是 连接格式
+     * 待数据返回正确后,此处代码再做调整
+     */
+    let temp_str = string;
+    if (string.indexOf('![]') < 0) {
+        // 未发现 ![] 字符串,尝试将 [] 替换为 ![]
+        temp_str = string.replace(/\[\]\(/g, '![](');
+    } else {
+        // 已发现 ![] 字符串
+    }
+
+
+
+    const result = marked.parse(temp_str);
+
+    /**
+     * 替换图片字符串
+     * 将字符串中的图片数据替换掉
+     */
+    var rep_img = result.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, str) {
+        // match值类似于
+        //         <img src="https://res.stepfun.com/image_gen/20241125/01936254e2ad7aea9d3d371d9a376469.png?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYTJhYzZiM2JhZTU2N
+        // GMxMGFkNTkwNTk3OTVkJDQ%2F20241125%2Fcn-shanghai%2Ftos%2Frequest&X-Tos-Date=20241125T075910Z&X-Tos-Expires=3600&x-Tos-Signature=3fa70bdbcc1759ab45aaa1b8bd60eef886e55114c32a39f478ee85d4f262804e&x-Tos-SignededH
+        // eaders=host" alt="">
+
+        const box = document.createElement('div');
+        box.innerHTML = match;
+        const image_ele = box.querySelector('img');
+        const img_src = image_ele.src; // 图片链接
+        // const img_src = 'http://localhost/src/assets/slider/logo.jpg';
+        const img_alt = image_ele.alt; // 图片alt属性
+
+        return `<span class="xiaoshuhuitu-image-container" data-src="${img_src}" data-alt="${img_alt}"></span>`;
+    });
 
     const reg = /<span class="circle" data-list-index="\d"><\/span>/g;
 
@@ -25,7 +60,7 @@ export default function formatAnwserString(string) {
      * 在界面中可以通过 refference-flag 获取到对应的元素
      */
     let idx = 0; // 记录当前是第几次匹配
-    const formated = result.replaceAll(reg, function (match, index, full_str, c) {
+    const formated = rep_img.replaceAll(reg, function (match, index, full_str, c) {
         let match_final = match.replace('></span>', `data-flag-index="${idx}"></span>`);
 
         match_final = match_final.replace('class="circle"', `class="refflag-refflag-refflag ${REFFERENCE_FLAG}"`);

+ 56 - 20
src/views/Home.vue

@@ -41,6 +41,7 @@
               <!-- 智能数据 -->
               <DocumentsmartData v-if="navId === 5 && !showDialog" :agentId="selectId" />
               <!-- 小数绘图 -->
+              <!-- <PicArticle v-if="navId === 6 && !showDialog" :agentId="selectId" /> -->
               <PicArticle v-if="navId === 6 && !showDialog" :agentId="selectId" />
               <!-- 文档出卷 -->
               <DocumentQuestion v-if="navId === 7 && !showDialog" :agentId="selectId" />
@@ -212,7 +213,18 @@ export default defineComponent({
   },
   setup() {
     // NOTE: 先把4文库问答的类型改成report类型,后续再改回来
-    const dialogType = ["report", "excel", "knowledgeQuiz", "report", "normal", "smartData", "normal", "question", "question"];
+    const dialogType = [
+      "report", 
+      "excel", 
+      "knowledgeQuiz", 
+      "report", 
+      "normal", 
+      "smartData", 
+      "huitu",  // 小数绘图
+      "question", 
+      "question"
+    ];
+
     const { config, helper, ajax } = inject("$global");
 
     const SYSTEM_NAME = config.defaultTitle
@@ -220,13 +232,17 @@ export default defineComponent({
     const closeOthers = ref(false);
     const swiperNumber = ref(5);
     const navId = ref(-1);
+
     const readonly = ref(false);
-    const showDialog = ref(false); // 是否显示对话内容
+
+    const showDialog = ref(false); // 是否显示对话内容 v-if
+    const initDialog = ref(false); // 对话是否初始化
+    const hideDialog = ref(false); // 是否隐藏对话 v-show
+
     const historyMode = ref(false);
     const dialogList = ref([]);
     const selectId = ref("");
-    const initDialog = ref(false);
-    const hideDialog = ref(false);
+
     const agentIds = ref([]);
     const dialogId = ref("");
     const historyList = ref([]);
@@ -240,6 +256,29 @@ export default defineComponent({
     const openAll = () => {
       closeOthers.value = !closeOthers.value;
     };
+
+    const setReadonly = (status) => {
+      readonly.value = status;
+    };
+    const changeParse = (status) => {
+      parseStatus.value = status;
+    }
+    const goDocument = () => {
+      navId.value = -2;
+      hideDialog.value = true;
+    };
+    const goBack = () => {
+      navId.value = 0;
+      hideDialog.value = false;
+    };
+
+    /**
+     * 当点击某个导航时触发的方法
+     * 例如:点击 知识问答 文档智能 智能问答 智能数据 等
+     * @params {object} node
+     * @params {number} node.id - 例如 3 4 5 
+     * @params {string} node.title - 例如:文档智能 小数绘图
+     */
     const selectNav = async (node) => {
       navId.value = node.id;
       textValue.value = [];
@@ -252,11 +291,16 @@ export default defineComponent({
       } else {
         swiperNumber.value = 4;
       }
+
       helper.$bus.emit("stop-writing");
       showDialog.value = false;
 
+      // 获取当前选定的agent对应的ID
       selectId.value = (agents.find(agent => agent.name === node.title) || {}).id || null;
+
+      // 获取历史记录
       const data = await ajax.message.getDialogs(selectId.value);
+
       if (navId.value === 0 || navId.value === 3) {
         const result = await ajax.agent.getVariables(selectId.value);
         variables.value = result;
@@ -273,20 +317,11 @@ export default defineComponent({
         });
       }
     };
-    const setReadonly = (status) => {
-      readonly.value = status;
-    };
-    const changeParse = (status) => {
-      parseStatus.value = status;
-    }
-    const goDocument = () => {
-      navId.value = -2;
-      hideDialog.value = true;
-    };
-    const goBack = () => {
-      navId.value = 0;
-      hideDialog.value = false;
-    };
+
+    /**
+     * 选择某个历史记录
+     * @params {string} id - 历史记录对应的ID
+     */
     const selectHistory = async (id) => {
       showDialog.value = true;
       // 历史记录对话模式,不调用对话ID接口
@@ -297,6 +332,7 @@ export default defineComponent({
       helper.$bus.emit("set-loading", true);
       helper.$bus.emit("stop-writing");
       initDialog.value = false;
+
       // 历史记录 通过当前name取id
       const name = config.slider.find(item => item.id === navId.value).title
       const agentsId = agents.find(agent => agent.name === name).id;
@@ -432,6 +468,7 @@ export default defineComponent({
     onMounted(async () => {
       agents = await ajax.agent.getAgents();
       agentIds.value = agents && Array.isArray(agents) && agents.map((data) => data.id) || [];
+
       helper.$bus.on("open-dialog", async (value) => {
         // 如果不是历史记录对话模式,则创建对话
         if (!historyMode.value) {
@@ -442,8 +479,8 @@ export default defineComponent({
         dialogList.value = [];
         showDialog.value = true;
         initDialog.value = true;
+
         // TODO
-        console.log('value', value)
         setTimeout(() => {
           helper.$bus.emit("send-message", value);
         }, 200);
@@ -451,7 +488,6 @@ export default defineComponent({
     });
 
     onBeforeUnmount(() => {
-      helper.webSocket?.close();
       helper.$bus.off("open-dialog");
     });
 

+ 6 - 0
vite.config.js

@@ -26,6 +26,12 @@ export default defineConfig({
                 secure: false,
                 ws: true,
                 rewrite: (path) => path.replace(/^\/proxy_url/, "")
+            },
+            '/filesx': {
+                target: "https://res.stepfun.com",
+                changeOrigin: true,
+                secure: false,
+                ws: true,
             }
         }
     }