浏览代码

[Mod 0000] AIChat组件化

houzekong 1 月之前
父节点
当前提交
710f9655b7
共有 2 个文件被更改,包括 265 次插入162 次删除
  1. 245 142
      src/components/AIChat/index.vue
  2. 20 20
      src/layouts/default/sider/bottomSider2.vue

+ 245 - 142
src/components/AIChat/index.vue

@@ -1,119 +1,132 @@
 <template>
-  <div class="dialog-overlay">
-    <!-- 左侧折叠区域 -->
-    <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
-      <div
-        class="addBtn"
-        :style="{
-          backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/add.svg' : ''})`,
-          backgroundColor: isFold ? '' : '#2cb6ff',
-          width: isFold ? '20px' : 'auto',
-        }"
-        @click="addNew"
-      >
-        <span
-          class="btn-text-bg"
-          :style="{
-            backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/addB.svg' : ''})`,
-          }"
-        ></span>
-        <span v-if="!isFold" class="btn-text">添加新对话</span>
-      </div>
-      <div class="divider0"></div>
-      <div
-        v-if="isFold"
-        class="historyBtn"
-        :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
-        @click="addNew"
-      ></div>
-      <div v-else class="historyBtn1">
-        <span
-          class="btn-text-bg"
-          :style="{
-            backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
-          }"
-        ></span>
-        <span v-if="!isFold" class="btn-text">历史对话</span>
-        <a-list style="width: 110px" :split="false" :data-source="historySessions" :scroll="200" class="custom-list">
-          <template #renderItem="{ item }">
-            <a-list-item
+  <div>
+    <transition name="fade">
+      <div v-if="visible" class="dialog-overlay">
+        <!-- 左侧折叠区域 -->
+        <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
+          <div
+            class="addBtn"
+            :style="{
+              backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/add.svg' : ''})`,
+              backgroundColor: isFold ? '' : '#2cb6ff',
+              width: isFold ? '20px' : 'auto',
+            }"
+            @click="addNew"
+          >
+            <span
+              class="btn-text-bg"
               :style="{
-                padding: '8px 10px 0 8px',
-                color: '#5e7081',
-                fontSize: '10px',
-                position: 'relative', // 新增定位
+                backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/addB.svg' : ''})`,
               }"
-              @click="sessionsHistory(item.id)"
-            >
-              <!-- 新增flex布局容器 -->
-              <div style="display: flex; justify-content: space-between; width: 100%">
-                <div v-if="editingId !== item.id" class="text-container">
-                  <span class="edit-text">{{ item.title || '新会话' }}</span>
-                  <edit-outlined class="edit-icon" @click="startEditing(item)" />
+            ></span>
+            <span v-if="!isFold" class="btn-text">添加新对话</span>
+          </div>
+          <div class="divider0"></div>
+          <div
+            v-if="isFold"
+            class="historyBtn"
+            :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
+            @click="addNew"
+          ></div>
+          <div v-else class="historyBtn1">
+            <span
+              class="btn-text-bg"
+              :style="{
+                backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
+              }"
+            ></span>
+            <span v-if="!isFold" class="btn-text">历史对话</span>
+            <a-list style="width: 110px" :split="false" :data-source="historySessions" :scroll="200" class="custom-list">
+              <template #renderItem="{ item }">
+                <a-list-item
+                  :style="{
+                    padding: '8px 10px 0 8px',
+                    color: '#5e7081',
+                    fontSize: '10px',
+                    position: 'relative', // 新增定位
+                  }"
+                  @click="sessionsHistory(item.id)"
+                >
+                  <!-- 新增flex布局容器 -->
+                  <div style="display: flex; justify-content: space-between; width: 100%">
+                    <div v-if="editingId !== item.id" class="text-container">
+                      <span class="edit-text">{{ item.title || '新会话' }}</span>
+                      <edit-outlined class="edit-icon" @click="startEditing(item)" />
+                    </div>
+
+                    <!-- 输入框 -->
+                    <a-input
+                      size="small"
+                      v-else
+                      v-model:value="editText"
+                      v-focus
+                      @blur="handleSave(item)"
+                      @keyup.enter="handleSave(item)"
+                      class="edit-input"
+                    />
+                  </div>
+                </a-list-item>
+              </template>
+            </a-list>
+          </div>
+          <div
+            class="foldBtn"
+            :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
+            @click="fold"
+          ></div>
+        </div>
+        <!-- 右侧对话框 -->
+        <div class="right-side">
+          <div class="input-content">
+            <!-- 对话区域 -->
+            <div class="dialog-area">
+              <div v-for="message in sortedMessages" :key="message.id" :class="['message-item', message.type]">
+                <!-- 用户提问样式 -->
+                <div v-if="message.type === 'user'" class="ask-message">
+                  <span>{{ message.content }}</span>
                 </div>
 
-                <!-- 输入框 -->
-                <a-input
-                  size="small"
-                  v-else
-                  v-model:value="editText"
-                  v-focus
-                  @blur="handleSave(item)"
-                  @keyup.enter="handleSave(item)"
-                  class="edit-input"
-                />
+                <!-- 系统回答样式 -->
+                <div v-else class="system-message">
+                  <div class="answerIcon"></div>
+                  <div class="answer-message">
+                    <div v-if="message.Origintype === 'thinking'">
+                      <span :id="'thinking-' + message.id" class="thinking-text" v-html="formatMessage(message.content)"></span>
+                    </div>
+                    <div v-if="message.Origintype1 === 'text'">
+                      <span :id="'text-' + message.id" class="answer-text" v-html="formatMessage(message.content1)"></span>
+                    </div>
+                  </div>
+                </div>
               </div>
-            </a-list-item>
-          </template>
-        </a-list>
-      </div>
-      <div
-        class="foldBtn"
-        :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
-        @click="fold"
-      ></div>
-    </div>
-    <!-- 右侧对话框 -->
-    <div class="right-side">
-      <div class="input-content">
-        <!-- 对话区域 -->
-        <div class="dialog-area">
-          <div v-for="message in sortedMessages" :key="message.id" :class="['message-item', message.type]">
-            <!-- 用户提问样式 -->
-            <div v-if="message.type === 'user'" class="ask-message">
-              <span>{{ message.content }}</span>
             </div>
+            <!-- 文本输入区域 -->
+            <div v-if="spinning" class="thinking-area">
+              <span style="color: #fff">思考中···</span>
+              <a-spin :spinning="spinning"></a-spin>
+            </div>
+            <div class="input-area">
+              <textarea v-model="inputText" placeholder="请输入你的问题"> </textarea>
+              <!-- 底部操作栏 -->
+              <div class="action-bar">
+                <!-- 左侧深度思考按钮 -->
+                <div class="think-btn" :class="{ active: isThinking }" @click="toggleThinking"> <span>深度思考</span> </div>
 
-            <!-- 系统回答样式 -->
-            <div v-else class="system-message">
-              <div class="answerIcon"></div>
-              <div class="answer-message">
-                <div v-html="formatMessage(message.content)"></div>
+                <!-- 右侧操作按钮 -->
+                <div class="right-actions">
+                  <label class="upload-btn">
+                    <div class="send-file"></div>
+                    <span class="divider"> | </span>
+                    <div class="send-img"></div>
+                    <div class="send-btn" @click="handleSend"></div>
+                  </label>
+                </div>
               </div>
             </div>
           </div>
         </div>
-        <!-- 文本输入区域 -->
-        <div v-if="spinning" class="thinking-area">
-          <span style="color: #fff">思考中···</span>
-          <a-spin :spinning="spinning"></a-spin>
-        </div>
-        <div class="input-area">
-          <textarea v-model="inputText" placeholder="请输入你的问题"> </textarea>
-          <!-- 底部操作栏 -->
-          <div class="action-bar">
-            <!-- 文件上传按钮 -->
-            <label class="upload-btn">
-              <div class="send-file" />
-              <span class="divider"> | </span>
-              <div class="send-img" />
-            </label>
-            <!-- 发送按钮 -->
-            <div class="send-btn" @click="handleSend"></div>
-          </div>
-        </div>
       </div>
-    </div>
+    </transition>
   </div>
 </template>
 
@@ -123,7 +136,7 @@
   import { EditOutlined } from '@ant-design/icons-vue';
   // 响应式变量声明
   const dialogVisible = ref(false);
-  const isFold = ref(true); // 是否折叠
+  const isFold = ref(false); // 是否折叠
   const inputText = ref(''); // 输入框内容
   const historySessions = ref([]); // 消会话历史
   const spinning = ref(false); // 加载状态
@@ -134,16 +147,26 @@
   const userStore = useUserStore(); //获取用户信息
   const editingId = ref<number | null>(null);
   const editText = ref('');
+  const isThinking = ref(false);
   interface ListItem {
     id: number;
     title?: string;
   }
+  defineProps({
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+  });
   let userId = unref(userStore.getUserInfo).id;
   // const userId = ref(0);
   type MessageItem = {
-    id: string; // 唯一标识(可用时间戳生成)
+    id: string; // 唯一标识时间戳
     type: 'user' | 'system';
-    content: string;
+    content?: string;
+    content1?: string;
+    Origintype?: string;
+    Origintype1?: string;
     timestamp: number; // 排序依据
   };
   const messageList = ref<MessageItem[]>([]);
@@ -160,19 +183,24 @@
     }
   };
 
-  const openMenu = () => {
-    dialogVisible.value = !dialogVisible.value;
-    if (dialogVisible.value) {
-      // addNew();
-      hasCreated.value = true;
-    }
-  };
+  // const openMenu = () => {
+  //   dialogVisible.value = props.modelValue;
+  //   // console.log(props.dialogVisible, 'ssssssssss');
+  //   if (dialogVisible.value) {
+  //     // addNew();
+  //     hasCreated.value = true;
+  //   }
+  // };
   const fold = () => {
     isFold.value = !isFold.value;
     if (!isFold.value) {
       sessionsHistoryList();
     }
   };
+  //启用深度思考
+  const toggleThinking = () => {
+    isThinking.value = !isThinking.value;
+  };
   //创建新对话
   async function addNew() {
     hasAdd.value = !hasAdd.value;
@@ -291,13 +319,12 @@
       content: inputText.value,
       timestamp: Date.now(),
     });
-
     // 构造请求参数
     const params = {
       chat_session_id: session_id.value, // 替换为实际的会话 ID
       prompt: inputText.value,
       ref_file_ids: [],
-      thinking_enabled: false,
+      thinking_enabled: isThinking.value,
     };
     inputText.value = ''; // 清空输入框
     try {
@@ -323,6 +350,8 @@
         id: `response_${Date.now()}`,
         type: 'response', // 消息类型
         content: '',
+        content1: '',
+        Origintype: '',
         timestamp: Date.now(), // 时间戳用来排序
       };
 
@@ -340,17 +369,30 @@
         // 将流数据转换为字符串
         const chunk = new TextDecoder().decode(value);
         console.log('Received chunk:', chunk);
+
+        // 使用正则表达式匹配完整的 JSON 对象
         const jsonRegex = /{.*?}/g;
         const matches = chunk.match(jsonRegex);
         if (matches) {
           matches.forEach((match) => {
             try {
               const data = JSON.parse(match);
-              if (data.type === 'text') {
+              console.log(data.type, '数据类型11111111111111111');
+              if (data.type === 'thinking') {
                 // 找到当前消息对象并更新 content
                 const targetMessage = messageList.value.find((msg) => msg.id === newMessage.id);
                 if (targetMessage) {
                   targetMessage.content += data.content; // 追加内容
+                  targetMessage.Origintype = data.type;
+                  scrollToBottom();
+                }
+              }
+              if (data.type === 'text') {
+                // 找到当前消息对象并更新 content
+                const targetMessage = messageList.value.find((msg) => msg.id === newMessage.id);
+                if (targetMessage) {
+                  targetMessage.content1 += data.content; // 追加内容
+                  targetMessage.Origintype1 = data.type;
                   scrollToBottom();
                 }
               }
@@ -362,14 +404,17 @@
       }
     } catch (error) {
       // 请求失败时设置系统消息
-      systemMessage.value = '服务器异常';
-      messageList.value.push({
-        id: `system_${Date.now()}`,
-        type: 'system',
-        content: systemMessage.value,
-        timestamp: Date.now(),
-      });
-      console.error('请求失败:', error);
+      if (!response || !response.ok) {
+        systemMessage.value = '服务器异常';
+        messageList.value.push({
+          id: `system_${Date.now()}`,
+          type: 'system',
+          content: systemMessage.value,
+          Origintype: 'text',
+          timestamp: Date.now(),
+        });
+        console.error('请求失败:', error);
+      }
     } finally {
       spinning.value = false; // 停止加载
     }
@@ -424,14 +469,33 @@
             content: item.content,
             timestamp: Date.now(),
           });
-        } else {
+        } else if (item.role === 'assistant') {
           // role== assistant 机器回答
-          messageList.value.push({
-            id: `system_${Date.now()}`,
-            type: 'system',
-            content: item.content,
-            timestamp: Date.now(),
-          });
+          if (item.thinking_enabled) {
+            messageList.value.push({
+              id: `system_${Date.now()}_thinking`,
+              type: 'system',
+              content: item.thinking_content,
+              Origintype: 'thinking',
+              timestamp: Date.now(),
+            });
+            messageList.value.push({
+              id: `system_${Date.now()}_text`,
+              type: 'system',
+              content1: item.content,
+              Origintype1: 'text',
+              timestamp: Date.now(),
+            });
+          } else {
+            messageList.value.push({
+              id: `system_${Date.now()}`,
+              type: 'system',
+              content: item.content,
+              timestamp: Date.now(),
+            });
+          }
+
+          console.log(item.content);
         }
       });
     }
@@ -452,9 +516,16 @@
       .replace(/`([^`]+)`/g, '<code>$1</code>');
     return formatted;
   }
+  const emit = defineEmits(['update:modelValue']);
+
+  const close = () => {
+    emit('update:modelValue', false);
+  };
+
   // 初始化按钮定位
   onMounted(() => {
     sessionsHistoryList();
+    console.log('ssssssssss');
   });
 </script>
 
@@ -693,10 +764,15 @@
     padding: 10px;
     margin: 10px;
     border-radius: 5px;
-    color: #fff;
     background: #0c2842;
   }
-
+  .thinking-text {
+    color: gray;
+    font-size: 12px;
+  }
+  .answer-text {
+    color: #fff;
+  }
   /** 系统返回信息**/
   .system-message {
     display: flex;
@@ -713,14 +789,11 @@
     background-image: url('/@/assets/images/vent/home/answerIcon.svg');
     background-size: 100% 100%;
   }
-  .answer-message {
-    float: left;
-    width: 100%;
-    padding: 10px;
-    margin: 10px;
-    border-radius: 5px;
+  .think-area {
+    color: #7979799f;
+  }
+  .answer-area {
     color: #fff;
-    background: #0c2842;
   }
   .dialog-area {
     flex: 1; /* 占据剩余空间 */
@@ -755,12 +828,42 @@
 
   .action-bar {
     display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 8px 16px;
+  }
+
+  .think-btn {
+    border: 1px solid #ccc;
+    width: 120px;
+    height: 20px;
+    line-height: 20px;
+    text-align: center;
+    border-radius: 50px;
+    cursor: pointer;
+    background: white;
+    transition: background 0.3s;
+  }
+
+  .think-btn.active {
+    background: #1890ff;
+    color: white;
+    border-color: #1890ff;
+  }
+  .right-actions {
+    display: flex;
     align-items: center;
-    gap: 12px;
+    gap: 8px;
   }
 
   .upload-btn {
     display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .upload-btn {
+    float: right;
+    display: flex;
     cursor: pointer;
     padding: 6px 12px;
   }
@@ -790,7 +893,7 @@
   .send-btn {
     width: 20px;
     height: 20px;
-    margin-left: auto;
+    margin-left: 10px;
     margin-right: 10px;
     background-color: #1074c1;
     background-image: url('/@/assets/images/vent/home/send.svg');

+ 20 - 20
src/layouts/default/sider/bottomSider2.vue

@@ -8,28 +8,28 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
-import Aichat from './Aichat.vue';
-const dialogVisible = ref(false);
-const openMenu = () => {
-  dialogVisible.value = !dialogVisible.value;
-};
+  import { ref } from 'vue';
+  import Aichat from '/@/components/AIChat/index.vue';
+  const dialogVisible = ref(false);
+  const openMenu = () => {
+    dialogVisible.value = !dialogVisible.value;
+  };
 </script>
 
 <style lang="less" scoped>
-.trigger-button {
-  position: fixed;
-  bottom: 10px;
-  right: 10px;
-  z-index: 1000000;
-  .icon {
-    width: 60px;
-    height: 60px;
-    position: relative;
-    background-image: url('/@/assets/images/vent/home/wakeBtn.png');
-    background-position: center;
-    background-size: 100% 100%;
-    cursor: pointer;
+  .trigger-button {
+    position: fixed;
+    bottom: 10px;
+    right: 10px;
+    z-index: 1000000;
+    .icon {
+      width: 60px;
+      height: 60px;
+      position: relative;
+      background-image: url('/@/assets/images/vent/home/wakeBtn.png');
+      background-position: center;
+      background-size: 100% 100%;
+      cursor: pointer;
+    }
   }
-}
 </style>