فهرست منبع

Merge branch 'master' of http://182.92.126.35:3000/hrx/mky-vent-base

hongrunxia 2 هفته پیش
والد
کامیت
1bd54eaf9c

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
src/assets/icons/ai-logo.svg


+ 492 - 0
src/components/AIChat/MiniChat.vue

@@ -0,0 +1,492 @@
+<template>
+  <div class="mini-chat">
+    <!-- 左侧折叠区域 -->
+    <div class="left-side">
+      <SvgIcon name="add" size="20" />
+      <Popover trigger="click" :overlay-inner-style="{ padding: '1px' }">
+        <template #content>
+          <AIChat style="width: 700px; height: 500px" :visible="true" />
+        </template>
+        <SvgIcon :name="dialogVisible ? 'zoom-out' : 'zoom-in'" size="20" @click="openDialog" />
+      </Popover>
+    </div>
+    <!-- 右侧对话框 -->
+    <div class="right-side">
+      <!-- 对话区域 -->
+      <div class="dialog-area">
+        <div
+          v-for="message in sortedMessages"
+          :key="message.id"
+          class="flex items-center"
+          :style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
+        >
+          <SvgIcon v-if="message.type !== 'user'" size="30" class="ml-2px mr-2px" name="ai-logo" />
+          <div v-if="message.type !== 'user'" class="answer-message" v-html="formatMessage(message.content)"></div>
+          <div v-if="message.type === 'user'" class="ask-message">{{ message.content }}</div>
+        </div>
+      </div>
+      <!-- 底部操作栏 -->
+      <div class="input-area">
+        <Textarea v-model:value="inputText" placeholder="请输入你的问题" />
+        <div class="action-bar">
+          <!-- 左侧深度思考按钮 -->
+          <div class="think-btn" :class="{ active: isThinking }" @click="toggleThinking"> <span>深度思考</span> </div>
+
+          <!-- 右侧操作按钮 -->
+          <Space>
+            <SvgIcon name="send-image" />
+            <SvgIcon name="send-file" />
+            <Button type="primary" shape="circle" size="small" :loading="spinning" @click="handleSend">
+              <template #icon>
+                <SvgIcon name="send" />
+              </template>
+            </Button>
+          </Space>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, onMounted, unref, computed } from 'vue';
+  import { useUserStore } from '/@/store/modules/user';
+  import { SvgIcon } from '../Icon';
+  import { Space, Button, Textarea, Popover } from 'ant-design-vue';
+  import AIChat from './index.vue';
+
+  // 响应式变量声明
+  const dialogVisible = ref(true);
+  const isFold = ref(true); // 是否折叠
+  const inputText = ref(''); // 输入框内容
+  const historySessions = ref([]); // 消会话历史
+  const spinning = ref(false); // 加载状态
+  const systemMessage = ref(''); // 系统返回信息
+  const session_id = ref(''); // 会话id
+  const hasCreated = ref(false); // 标志位,防止重复调用create接口
+  const hasAdd = ref(false); // 标志位,防止重复调用create接口
+  const userStore = useUserStore(); //获取用户信息
+  const editingId = ref<number | null>(null);
+  const editText = ref('');
+  const isThinking = ref(false);
+  interface ListItem {
+    id: number;
+    title?: string;
+  }
+  let userId = unref(userStore.getUserInfo).id;
+  // const userId = ref(0);
+  type MessageItem = {
+    id: string; // 唯一标识(可用时间戳生成)
+    type: 'user' | 'system';
+    content: string;
+    timestamp: number; // 排序依据
+  };
+  const messageList = ref<MessageItem[]>([]);
+  const sortedMessages = computed(() => {
+    const list = messageList.value;
+    return list.sort((a, b) => a.timestamp - b.timestamp);
+  });
+  const vFocus = {
+    mounted: (el: HTMLElement) => el.querySelector('input')?.focus(),
+  };
+  const scrollToBottom = () => {
+    const dialogArea = document.querySelector('.dialog-area');
+    if (dialogArea) {
+      dialogArea.scrollTop = dialogArea.scrollHeight;
+    }
+  };
+
+  const openDialog = () => {
+    dialogVisible.value = !dialogVisible.value;
+  };
+  const fold = () => {
+    // isFold.value = !isFold.value;
+    // if (!isFold.value) {
+    //   sessionsHistoryList();
+    // }
+  };
+  //启用深度思考
+  const toggleThinking = () => {
+    isThinking.value = !isThinking.value;
+  };
+  //创建新对话
+  async function addNew() {
+    hasAdd.value = !hasAdd.value;
+    const params = {
+      user_id: userId,
+    };
+    let response = await fetch('http://182.92.126.35:6005/sessions/create', {
+      method: 'post',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(params),
+    });
+    const data = await response.json();
+    session_id.value = data.id;
+    messageList.value = [];
+  }
+  //编辑标题
+  const startEditing = (item: ListItem) => {
+    editingId.value = item.id;
+    editText.value = item.title || '';
+  };
+
+  // 保存修改
+  const handleSave = async (item: ListItem) => {
+    const params = {
+      chat_session_id: item.id,
+      new_title: editText.value,
+    };
+    try {
+      let response = await fetch('http://182.92.126.35:6005/sessions/change_title', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(params),
+      });
+      if (!response.ok) {
+        throw new Error('Network response was not ok');
+      }
+      item.title = editText.value;
+    } catch (error) {
+      console.error('保存失败:', error);
+    }
+    editingId.value = null;
+  };
+  //获取消息列表
+  async function handleSend() {
+    if (session_id.value === '') {
+      await addNew();
+      createSessionTitle({ session_id: session_id.value, title: inputText.value });
+      sendMessage1();
+    } else {
+      createSessionTitle({ session_id: session_id.value, title: inputText.value });
+      sendMessage1();
+    }
+  }
+  //发送消息
+  async function sendMessage() {
+    spinning.value = true;
+    // 添加用户消息
+    messageList.value.push({
+      id: `user_${Date.now()}`,
+      type: 'user',
+      content: inputText.value,
+      timestamp: Date.now(),
+    });
+    const params = {
+      chat_session_id: session_id.value,
+      prompt: inputText.value,
+      ref_file_ids: [],
+      thinking_enabled: false,
+    };
+    inputText.value = ''; // 清空输入框
+    //将用户输入的内容发送到后端
+    try {
+      // 将用户输入的内容发送到后端
+      let response = await fetch('http://182.92.126.35:6005/chat', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(params),
+      });
+
+      if (!response.ok) {
+        throw new Error('Network response was not ok');
+      }
+
+      const data = await response.json();
+      const assistantReply = data.reply.content; // 获取助手回复
+      // formatMessage(assistantReply);
+      systemMessage.value = assistantReply;
+
+      // 添加系统回答
+      messageList.value.push({
+        id: `system_${Date.now()}`,
+        type: 'system',
+        content: systemMessage.value,
+        timestamp: Date.now(),
+      });
+    } catch (error) {
+      // 请求失败时设置系统消息为"服务器异常"
+      systemMessage.value = '服务器异常';
+      console.error('请求失败:', error);
+    } finally {
+      spinning.value = false; // 无论请求成功与否,都停止加载指示器
+    }
+  }
+  //发送消息  流式响应
+  const sendMessage1 = async () => {
+    spinning.value = true; // 开始加载
+    messageList.value.push({
+      id: `user_${Date.now()}`,
+      type: 'user',
+      content: inputText.value,
+      timestamp: Date.now(),
+    });
+
+    // 构造请求参数
+    const params = {
+      chat_session_id: session_id.value, // 替换为实际的会话 ID
+      prompt: inputText.value,
+      ref_file_ids: [],
+      thinking_enabled: isThinking.value,
+    };
+    inputText.value = ''; // 清空输入框
+    try {
+      // 发送 POST 请求
+      const response = await fetch('http://182.92.126.35:6005/chat_stream', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(params),
+      });
+
+      // 检查响应是否成功
+      if (!response.ok) {
+        throw new Error('Network response was not ok');
+      }
+
+      // 获取可读流
+      const reader = response.body.getReader();
+
+      // 创建一条新的消息对象
+      const newMessage = {
+        id: `response_${Date.now()}`,
+        type: 'response', // 消息类型
+        content: '',
+        timestamp: Date.now(), // 时间戳用来排序
+      };
+
+      // 将新消息添加到消息列表
+      messageList.value.push(newMessage);
+
+      // 读取流式数据
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) {
+          console.log('Stream complete');
+          break;
+        }
+
+        // 将流数据转换为字符串
+        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') {
+                // 找到当前消息对象并更新 content
+                const targetMessage = messageList.value.find((msg) => msg.id === newMessage.id);
+                if (targetMessage) {
+                  targetMessage.content += data.content; // 追加内容
+                  scrollToBottom();
+                }
+              }
+            } catch (error) {
+              console.error('Failed to parse JSON:', error);
+            }
+          });
+        }
+      }
+    } catch (error) {
+      // 请求失败时设置系统消息
+      if (!response || !response.ok) {
+        systemMessage.value = '服务器异常';
+        messageList.value.push({
+          id: `system_${Date.now()}`,
+          type: 'system',
+          content: systemMessage.value,
+          timestamp: Date.now(),
+        });
+        console.error('请求失败:', error);
+      }
+    } finally {
+      spinning.value = false; // 停止加载
+    }
+  };
+  //创建标题
+  async function createSessionTitle({ session_id, title }) {
+    const params = {
+      chat_session_id: session_id,
+      prompt: title,
+    };
+    let response = await fetch('http://182.92.126.35:6005/sessions/title', {
+      method: 'post',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(params),
+    });
+    const data = await response.json();
+  }
+  //获取会话历史
+  async function sessionsHistoryList() {
+    const params = {
+      user_id: userId,
+    };
+    let response = await fetch(`http://182.92.126.35:6005/sessions`, {
+      method: 'post',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify(params),
+    });
+    const data = await response.json();
+    historySessions.value = data.chat_sessions;
+  }
+  //获取具体会话记录
+  async function sessionsHistory(id: string) {
+    let response = await fetch(`http://182.92.126.35:6005/sessions/history_chat/?chat_session_id=${id}`, {
+      method: 'get',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+    });
+    const data = await response.json();
+    if (data.chat_messages.length > 0) {
+      messageList.value = [];
+      data.chat_messages.forEach((item: any) => {
+        // role== user 用户提问
+        if (item.role === 'user') {
+          messageList.value.push({
+            id: `user_${Date.now()}`,
+            type: 'user',
+            content: item.content,
+            timestamp: Date.now(),
+          });
+        } else {
+          // role== assistant 机器回答
+          messageList.value.push({
+            id: `system_${Date.now()}`,
+            type: 'system',
+            content: item.content,
+            timestamp: Date.now(),
+          });
+        }
+      });
+    }
+  }
+  //格式化消息
+  function formatMessage(text: string) {
+    let formatted = text
+      // 处理换行
+      .replace(/\n\n/g, '<br>')
+      .replace(/\n###/g, '<br> ')
+      .replace(/###/g, '')
+      .replace(/---/g, '')
+      // 处理粗体
+      .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
+      // 处理斜体
+      .replace(/\*(.*?)\*/g, '<em>$1</em>')
+      // 处理行内代码
+      .replace(/`([^`]+)`/g, '<code>$1</code>');
+    return formatted;
+  }
+  // 初始化按钮定位
+  onMounted(() => {
+    sessionsHistoryList();
+  });
+</script>
+
+<style lang="less" scoped>
+  .mini-chat {
+    display: flex;
+  }
+
+  .left-side {
+    width: 40px; /* 折叠时宽度 */
+    background: #0c2842;
+    transition: width 0.5s ease; /* 平滑过渡动画 */
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    align-items: center;
+  }
+
+  .right-side {
+    flex: 1; /* 占据剩余空间 */
+    background: #09172c;
+    display: flex;
+    flex-direction: column;
+
+    .dialog-area {
+      flex: 1; /* 占据剩余空间 */
+      gap: 10px; /* 消息块间隔统一控制 */
+      overflow-y: auto; /* 垂直滚动条 */
+      padding: 5px;
+      display: flex;
+      flex-direction: column;
+      color: #fff;
+
+      .ask-message {
+        padding: 10px;
+        border-radius: 5px;
+        background: #0c2842;
+      }
+      .answer-message {
+        padding: 10px;
+        border-radius: 5px;
+        background: #0c2842;
+      }
+    }
+
+    .input-area {
+      background-color: #043256;
+      padding: 5px;
+
+      textarea {
+        background-color: transparent;
+        width: 100%;
+        height: 40px;
+        border: none;
+        resize: none;
+        outline: none;
+        overflow: hidden;
+        padding: 10px; /* 统一内边距 */
+        color: #fff;
+      }
+
+      .action-bar {
+        height: 30px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        .think-btn {
+          border: 1px solid #ccc;
+          width: 100px;
+          height: 20px;
+          line-height: 20px;
+          text-align: center;
+          border-radius: 5px;
+          cursor: pointer;
+          background: transparent;
+          color: white;
+          transition: background 0.3s;
+        }
+
+        .think-btn.active {
+          background: #1890ff;
+          color: white;
+          border-color: #1890ff;
+        }
+      }
+    }
+  }
+</style>
+<style>
+  .zxm-popover-inner-content {
+    padding: 1px;
+  }
+</style>

+ 234 - 147
src/components/AIChat/index.vue

@@ -1,120 +1,131 @@
 <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"
+  <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/addB.svg' : ''})`,
+            backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/add.svg' : ''})`,
+            backgroundColor: isFold ? '' : '#2cb6ff',
+            width: isFold ? '20px' : 'auto',
           }"
-        ></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)" />
+          @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
+                :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>
-            </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>
+          <!-- 文本输入区域 -->
+          <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 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>
-  </div>
+  </transition>
 </template>
 
 <script lang="ts" setup>
@@ -123,7 +134,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 +145,26 @@
   const userStore = useUserStore(); //获取用户信息
   const editingId = ref<number | null>(null);
   const editText = ref('');
+  const isThinking = ref(false);
   interface ListItem {
     id: number;
     title?: string;
   }
+  const props = 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[]>([]);
@@ -161,9 +182,8 @@
   };
 
   const openMenu = () => {
-    dialogVisible.value = !dialogVisible.value;
-    if (dialogVisible.value) {
-      // addNew();
+    if (props.visible) {
+      addNew();
       hasCreated.value = true;
     }
   };
@@ -173,6 +193,10 @@
       sessionsHistoryList();
     }
   };
+  //启用深度思考
+  const toggleThinking = () => {
+    isThinking.value = !isThinking.value;
+  };
   //创建新对话
   async function addNew() {
     hasAdd.value = !hasAdd.value;
@@ -291,13 +315,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 +346,8 @@
         id: `response_${Date.now()}`,
         type: 'response', // 消息类型
         content: '',
+        content1: '',
+        Origintype: '',
         timestamp: Date.now(), // 时间戳用来排序
       };
 
@@ -333,24 +358,34 @@
       while (true) {
         const { done, value } = await reader.read();
         if (done) {
-          console.log('Stream complete');
           break;
         }
 
         // 将流数据转换为字符串
         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') {
+              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 +397,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 +462,31 @@
             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(),
+            });
+          }
         }
       });
     }
@@ -452,9 +507,16 @@
       .replace(/`([^`]+)`/g, '<code>$1</code>');
     return formatted;
   }
+  const emit = defineEmits(['update:modelValue']);
+
+  const close = () => {
+    emit('update:modelValue', false);
+  };
+
   // 初始化按钮定位
   onMounted(() => {
     sessionsHistoryList();
+    openMenu();
   });
 </script>
 
@@ -538,14 +600,7 @@
     }
   }
   .dialog-overlay {
-    width: 32%;
-    height: 55%;
-    z-index: 999;
     display: flex;
-    position: fixed;
-    right: 90px;
-    bottom: 20px;
-    box-shadow: 0 0 3px 3px #1074c1;
     background-color: #09172c;
   }
 
@@ -693,10 +748,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 +773,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 +812,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 +877,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');

+ 2 - 2
src/layouts/default/layout.data.ts

@@ -1,7 +1,7 @@
 export const noHeadeLink = ['/micro-vent-3dModal/dashboard/analysis', '/model3D/home', '/fileManager/cad-viewer'];
 export const noSiderLink = ['/fileManager/cad-viewer', '/micro-vent-3dModal/dashboard/bd/fire', '/micro-vent-3dModal/dashboard/bd/dust'];
-// export const noChatLink = ['/micro-vent-3dModal/modelchannel/model3D/home'];
-export const noChatLink = [];
+export const noChatLink = ['/micro-vent-3dModal/modelchannel/model3D/home'];
+// export const noChatLink = [];
 export const noContentLink = ['/micro-vent-3dModal/modelchannel/model3D/home'];
 export const ThemeModel = {
   theme5_5: {

+ 24 - 835
src/layouts/default/sider/bottomSider2.vue

@@ -1,849 +1,38 @@
 <template>
   <div>
     <div class="trigger-button">
-      <div class="icon" @click="openMenu"></div>
+      <div class="icon" @click="openMenu" :style="{ zIndex: 1000 }"></div>
+      <Aichat
+        style="width: 32%; height: 55%; z-index: 999; position: fixed; right: 90px; bottom: 20px; box-shadow: 0 0 3px 3px #1074c1"
+        :visible="dialogVisible"
+      />
     </div>
-    <transition name="fade">
-      <div v-if="dialogVisible" 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
-                  :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>
-
-                <!-- 系统回答样式 -->
-                <div v-else class="system-message">
-                  <div class="answerIcon"></div>
-                  <div class="answer-message">
-                    <div v-html="formatMessage(message.content)"></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">
-                <!-- 左侧深度思考按钮 -->
-                <div class="think-btn" :class="{ active: isThinking }" @click="toggleThinking"> <span>深度思考</span> </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>
-    </transition>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, unref, nextTick, computed } from 'vue';
-import { useUserStore } from '/@/store/modules/user';
-import { EditOutlined } from '@ant-design/icons-vue';
-// 响应式变量声明
-const dialogVisible = ref(false);
-const isFold = ref(false); // 是否折叠
-const inputText = ref(''); // 输入框内容
-const historySessions = ref([]); // 消会话历史
-const spinning = ref(false); // 加载状态
-const systemMessage = ref(''); // 系统返回信息
-const session_id = ref(''); // 会话id
-const hasCreated = ref(false); // 标志位,防止重复调用create接口
-const hasAdd = ref(false); // 标志位,防止重复调用create接口
-const userStore = useUserStore(); //获取用户信息
-const editingId = ref<number | null>(null);
-const editText = ref('');
-const isThinking = ref(false);
-interface ListItem {
-  id: number;
-  title?: string;
-}
-let userId = unref(userStore.getUserInfo).id;
-// const userId = ref(0);
-type MessageItem = {
-  id: string; // 唯一标识(可用时间戳生成)
-  type: 'user' | 'system';
-  content: string;
-  timestamp: number; // 排序依据
-};
-const messageList = ref<MessageItem[]>([]);
-const sortedMessages = computed(() => {
-  return messageList.value.sort((a, b) => a.timestamp - b.timestamp);
-});
-const vFocus = {
-  mounted: (el: HTMLElement) => el.querySelector('input')?.focus(),
-};
-const scrollToBottom = () => {
-  const dialogArea = document.querySelector('.dialog-area');
-  if (dialogArea) {
-    dialogArea.scrollTop = dialogArea.scrollHeight;
-  }
-};
-
-const openMenu = () => {
-  dialogVisible.value = !dialogVisible.value;
-  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;
-  const params = {
-    user_id: userId,
+  import { ref } from 'vue';
+  import Aichat from '/@/components/AIChat/index.vue';
+  const dialogVisible = ref(false);
+  const openMenu = () => {
+    dialogVisible.value = !dialogVisible.value;
   };
-  let response = await fetch('http://182.92.126.35:6005/sessions/create', {
-    method: 'post',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    body: JSON.stringify(params),
-  });
-  const data = await response.json();
-  session_id.value = data.id;
-  messageList.value = [];
-}
-//编辑标题
-const startEditing = (item: ListItem) => {
-  editingId.value = item.id;
-  editText.value = item.title || '';
-};
-
-// 保存修改
-const handleSave = async (item: ListItem) => {
-  const params = {
-    chat_session_id: item.id,
-    new_title: editText.value,
-  };
-  try {
-    let response = await fetch('http://182.92.126.35:6005/sessions/change_title', {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(params),
-    });
-    if (!response.ok) {
-      throw new Error('Network response was not ok');
-    }
-    item.title = editText.value;
-  } catch (error) {
-    console.error('保存失败:', error);
-  }
-  editingId.value = null;
-};
-//获取消息列表
-async function handleSend() {
-  if (session_id.value === '') {
-    await addNew();
-    createSessionTitle({ session_id: session_id.value, title: inputText.value });
-    sendMessage1();
-  } else {
-    createSessionTitle({ session_id: session_id.value, title: inputText.value });
-    sendMessage1();
-  }
-}
-//发送消息
-async function sendMessage() {
-  spinning.value = true;
-  // 添加用户消息
-  messageList.value.push({
-    id: `user_${Date.now()}`,
-    type: 'user',
-    content: inputText.value,
-    timestamp: Date.now(),
-  });
-  const params = {
-    chat_session_id: session_id.value,
-    prompt: inputText.value,
-    ref_file_ids: [],
-    thinking_enabled: false,
-  };
-  inputText.value = ''; // 清空输入框
-  //将用户输入的内容发送到后端
-  try {
-    // 将用户输入的内容发送到后端
-    let response = await fetch('http://182.92.126.35:6005/chat', {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(params),
-    });
-
-    if (!response.ok) {
-      throw new Error('Network response was not ok');
-    }
-
-    const data = await response.json();
-    const assistantReply = data.reply.content; // 获取助手回复
-    // formatMessage(assistantReply);
-    systemMessage.value = assistantReply;
-
-    // 添加系统回答
-    messageList.value.push({
-      id: `system_${Date.now()}`,
-      type: 'system',
-      content: systemMessage.value,
-      timestamp: Date.now(),
-    });
-  } catch (error) {
-    // 请求失败时设置系统消息为"服务器异常"
-    systemMessage.value = '服务器异常';
-    console.error('请求失败:', error);
-  } finally {
-    spinning.value = false; // 无论请求成功与否,都停止加载指示器
-  }
-}
-//发送消息  流式响应
-const sendMessage1 = async () => {
-  spinning.value = true; // 开始加载
-  messageList.value.push({
-    id: `user_${Date.now()}`,
-    type: 'user',
-    content: inputText.value,
-    timestamp: Date.now(),
-  });
-
-  // 构造请求参数
-  const params = {
-    chat_session_id: session_id.value, // 替换为实际的会话 ID
-    prompt: inputText.value,
-    ref_file_ids: [],
-    thinking_enabled: isThinking.value,
-  };
-  inputText.value = ''; // 清空输入框
-  try {
-    // 发送 POST 请求
-    const response = await fetch('http://182.92.126.35:6005/chat_stream', {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json',
-      },
-      body: JSON.stringify(params),
-    });
-
-    // 检查响应是否成功
-    if (!response.ok) {
-      throw new Error('Network response was not ok');
-    }
-
-    // 获取可读流
-    const reader = response.body.getReader();
-
-    // 创建一条新的消息对象
-    const newMessage = {
-      id: `response_${Date.now()}`,
-      type: 'response', // 消息类型
-      content: '',
-      timestamp: Date.now(), // 时间戳用来排序
-    };
-
-    // 将新消息添加到消息列表
-    messageList.value.push(newMessage);
-
-    // 读取流式数据
-    while (true) {
-      const { done, value } = await reader.read();
-      if (done) {
-        console.log('Stream complete');
-        break;
-      }
-
-      // 将流数据转换为字符串
-      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') {
-              // 找到当前消息对象并更新 content
-              const targetMessage = messageList.value.find((msg) => msg.id === newMessage.id);
-              if (targetMessage) {
-                targetMessage.content += data.content; // 追加内容
-                scrollToBottom();
-              }
-            }
-          } catch (error) {
-            console.error('Failed to parse JSON:', error);
-          }
-        });
-      }
-    }
-  } catch (error) {
-    // 请求失败时设置系统消息
-    if (!response || !response.ok) {
-      systemMessage.value = '服务器异常';
-      messageList.value.push({
-        id: `system_${Date.now()}`,
-        type: 'system',
-        content: systemMessage.value,
-        timestamp: Date.now(),
-      });
-      console.error('请求失败:', error);
-    }
-  } finally {
-    spinning.value = false; // 停止加载
-  }
-};
-//创建标题
-async function createSessionTitle({ session_id, title }) {
-  const params = {
-    chat_session_id: session_id,
-    prompt: title,
-  };
-  let response = await fetch('http://182.92.126.35:6005/sessions/title', {
-    method: 'post',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    body: JSON.stringify(params),
-  });
-  const data = await response.json();
-}
-//获取会话历史
-async function sessionsHistoryList() {
-  const params = {
-    user_id: userId,
-  };
-  let response = await fetch(`http://182.92.126.35:6005/sessions`, {
-    method: 'post',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    body: JSON.stringify(params),
-  });
-  const data = await response.json();
-  historySessions.value = data.chat_sessions;
-}
-//获取具体会话记录
-async function sessionsHistory(id: string) {
-  let response = await fetch(`http://182.92.126.35:6005/sessions/history_chat/?chat_session_id=${id}`, {
-    method: 'get',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-  });
-  const data = await response.json();
-  if (data.chat_messages.length > 0) {
-    messageList.value = [];
-    data.chat_messages.forEach((item: any) => {
-      // role== user 用户提问
-      if (item.role === 'user') {
-        messageList.value.push({
-          id: `user_${Date.now()}`,
-          type: 'user',
-          content: item.content,
-          timestamp: Date.now(),
-        });
-      } else {
-        // role== assistant 机器回答
-        messageList.value.push({
-          id: `system_${Date.now()}`,
-          type: 'system',
-          content: item.content,
-          timestamp: Date.now(),
-        });
-      }
-    });
-  }
-}
-//格式化消息
-function formatMessage(text: string) {
-  let formatted = text
-    // 处理换行
-    .replace(/\n\n/g, '<br>')
-    .replace(/\n###/g, '<br> ')
-    .replace(/###/g, '')
-    .replace(/---/g, '')
-    // 处理粗体
-    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
-    // 处理斜体
-    .replace(/\*(.*?)\*/g, '<em>$1</em>')
-    // 处理行内代码
-    .replace(/`([^`]+)`/g, '<code>$1</code>');
-  return formatted;
-}
-// 初始化按钮定位
-onMounted(() => {
-  sessionsHistoryList();
-});
 </script>
 
 <style lang="less" scoped>
-@keyframes menuShow {
-  0% {
-    width: 0;
-    height: 0;
-  }
-  100% {
-    width: 480px;
-    height: 100vh;
-  }
-}
-.custom-list {
-  height: 360px;
-  overflow-y: auto;
-}
-/* 穿透组件作用域 */
-::v-deep .custom-list {
-  scrollbar-width: thin;
-  scrollbar-color: #1890ff #f0f0f0;
-  &::-webkit-scrollbar {
-    width: 4px;
-    height: 6px;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    background: #1890ff;
-    border-radius: 4px;
-  }
-
-  &::-webkit-scrollbar-track {
-    background: #f0f0f0;
-    border-radius: 4px;
-  }
-}
-::v-deep .zxm-list-items {
-  color: #1890ff;
-}
-::v-deep .zxm-list-item:hover {
-  text-decoration: underline;
-  color: #1890ff !important;
-}
-.text-container {
-  display: flex;
-  align-items: center;
-  width: 100%;
-  overflow: hidden;
-}
-
-.text-ellipsis {
-  flex: 1;
-}
-.edit-text {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  min-width: 0;
-}
-.edit-icon {
-  flex-shrink: 0;
-  cursor: pointer;
-  margin-left: auto;
-}
-.edit-input {
-  font-size: 10px;
-}
-.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%;
+  .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;
+    }
   }
-}
-.dialog-overlay {
-  width: 32%;
-  height: 55%;
-  z-index: 999;
-  display: flex;
-  position: fixed;
-  right: 90px;
-  bottom: 20px;
-  box-shadow: 0 0 3px 3px #1074c1;
-  background-color: #09172c;
-}
-
-/* 遮罩层淡入淡出 */
-.fade-enter-active,
-.fade-leave-active {
-  transition: opacity 0.3s;
-}
-.fade-enter-from,
-.fade-leave-to {
-  opacity: 0;
-}
-
-/* 弹窗缩放动画 */
-.scale-enter-active,
-.scale-leave-active {
-  transition: all 0.3s ease;
-}
-.scale-enter-from {
-  transform: scale(0.5) translate(-50%, -50%);
-  opacity: 0;
-}
-.scale-leave-to {
-  transform: scale(1.2) translate(-50%, -50%);
-  opacity: 0;
-}
-
-.left-side {
-  background: #0c2842;
-  transition: width 0.5s ease; /* 平滑过渡动画 */
-  width: 120px; /* 展开时宽度 */
-  position: relative; /* 用于按钮定位 */
-}
-.left-side.collapsed {
-  width: 40px; /* 折叠时宽度 */
-}
-
-.addBtn {
-  height: 30px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  padding: 2px;
-  right: 10px;
-  top: 10px;
-  left: 10px;
-  align-items: center;
-  border-radius: 3px;
-  cursor: pointer;
-}
-.btn-text-bg {
-  width: 14px;
-  height: 14px;
-  position: absolute;
-  background-size: 100% 100%;
-  right: 10px;
-  top: 9px;
-  left: 10px;
-  bottom: 10px;
-}
-.btn-text {
-  margin-left: 3px;
-  font-size: 12px;
-  color: #fff;
-  white-space: nowrap;
-  margin-left: 30px;
-  line-height: 26px;
-}
-.historyBtn {
-  width: 20px;
-  height: 20px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  padding: 2px;
-  right: 10px;
-  top: 100px;
-}
-.historyBtn1 {
-  width: 20px;
-  height: 20px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  left: 3px;
-  top: 80px;
-}
-.divider0 {
-  border-bottom: 1px solid #1074c1;
-  width: auto;
-  margin: 0 10px;
-  height: 13%;
-  display: block;
-  background: transparent;
-}
-.foldBtn {
-  width: 20px;
-  height: 20px;
-  position: absolute;
-  background-size: 100% 100%;
-  background-position: center;
-  padding: 2px;
-  right: 10px;
-  bottom: 10px;
-  cursor: pointer;
-}
-
-.right-side {
-  flex: 1; /* 占据剩余空间 */
-  background: #09172c;
-}
-
-.input-content {
-  display: flex;
-  flex-direction: column;
-  justify-content: flex-end; /* 内容底部对齐 */
-  height: 100%;
-  padding: 20px; /* 统一内边距 */
-}
-.ask-message {
-  align-self: flex-end;
-  float: right;
-  max-width: 70%;
-  padding: 10px;
-  margin: 10px;
-  border-radius: 5px;
-  color: #fff;
-  background: #0c2842;
-  align-self: flex-end; /* 右侧对齐‌:ml-citation{ref="2" data="citationList"} */
-}
-.answer {
-  display: flex;
-  flex-direction: row;
-}
-.answerIcon {
-  flex-shrink: 0;
-  margin-top: 10px;
-  width: 35px;
-  height: 35px;
-  background-image: url('/@/assets/images/vent/home/answerIcon.svg');
-  background-size: 100% 100%;
-}
-.answer-message {
-  float: left;
-  padding: 10px;
-  margin: 10px;
-  border-radius: 5px;
-  background: #0c2842;
-}
-
-/** 系统返回信息**/
-.system-message {
-  display: flex;
-  flex-direction: row;
-  align-self: flex-start;
-  width: 100%;
-  padding: 12px;
-  display: flex;
-}
-.answerIcon {
-  margin-top: 10px;
-  width: 35px;
-  height: 35px;
-  background-image: url('/@/assets/images/vent/home/answerIcon.svg');
-  background-size: 100% 100%;
-}
-.think-area {
-  color: #7979799f;
-}
-.answer-area {
-  color: #fff;
-}
-.dialog-area {
-  flex: 1; /* 占据剩余空间 */
-  gap: 50px; /* 消息块间隔统一控制 */
-  overflow-y: auto; /* 垂直滚动条 */
-  margin-bottom: 10px;
-}
-.loading-wrapper,
-.content-wrapper {
-  min-height: 40px;
-}
-.message-item.user {
-  margin-bottom: 50px;
-}
-.input-area {
-  background-color: #043256 !important;
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-textarea {
-  background-color: #043256 !important;
-  width: 100%;
-  height: 40px;
-  border: none;
-  resize: none;
-  outline: none;
-  overflow: hidden;
-  padding: 10px; /* 统一内边距 */
-  color: #fff;
-}
-
-.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: 8px;
-}
-
-.upload-btn {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-.upload-btn {
-  float: right;
-  display: flex;
-  cursor: pointer;
-  padding: 6px 12px;
-}
-
-.divider {
-  color: #ccc;
-  font-weight: 300;
-  margin: 0 10px;
-}
-
-.send-file {
-  width: 20px;
-  height: 20px;
-  background-image: url('/@/assets/images/vent/home/sendFile.svg');
-  background-size: 100% 100%;
-  border-radius: 4px;
-  cursor: pointer;
-}
-.send-img {
-  width: 20px;
-  height: 20px;
-  background-image: url('/@/assets/images/vent/home/sendImg.svg');
-  background-size: 100% 100%;
-  border-radius: 4px;
-  cursor: pointer;
-}
-.send-btn {
-  width: 20px;
-  height: 20px;
-  margin-left: 10px;
-  margin-right: 10px;
-  background-color: #1074c1;
-  background-image: url('/@/assets/images/vent/home/send.svg');
-  background-position: center;
-  background-size: 100% 100%;
-  border-radius: 2px;
-  cursor: pointer;
-}
 </style>

+ 1 - 1
src/views/vent/home/configurable/components/content.vue

@@ -116,7 +116,7 @@
   import BlastDelta from '../../../monitorManager/deviceMonitor/components/device/modal/blastDelta.vue';
   import QHCurve from './preset/QHCurve.vue';
   import MeasureDetail from './preset/MeasureDetail.vue';
-  import AIChat from '/@/components/AIChat/index.vue';
+  import AIChat from '/@/components/AIChat/MiniChat.vue';
   import DeviceAlarm from './preset/DeviceAlarm.vue';
   // import FIreWarn from './preset/FIreWarn.vue';
   // import FIreControl from './preset/FIreControl.vue';

+ 6 - 6
src/views/vent/home/configurable/configurable.data.ts

@@ -427,10 +427,10 @@ export const testConfigVent: Config[] = [
     },
   },
   {
-    deviceType: 'warn',
-    moduleName: '预警监测',
-    // deviceType: '',
-    // moduleName: '智能通风Deepseek',
+    // deviceType: 'warn',
+    // moduleName: '预警监测',
+    deviceType: '',
+    moduleName: '智能通风Deepseek',
     pageType: 'vent',
     moduleData: {
       header: {
@@ -454,8 +454,8 @@ export const testConfigVent: Config[] = [
         direction: 'row',
         items: [
           {
-            name: 'list',
-            // name: 'ai_chat',
+            // name: 'list',
+            name: 'ai_chat',
             basis: '100%',
           },
         ],

+ 186 - 0
src/views/vent/home/configurable/tester.vue

@@ -0,0 +1,186 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <div class="company-home">
+    <!-- <div style="width: 100%; height: 100%; position: absolute; left: 0; top: 0; z-index: 0">
+      <VentModal />
+    </div> -->
+    <div v-if="!route.query.embed" class="top-bg">
+      <!-- <img style="width: 300px; height: 40px; position: fixed; left: 5px; top: 5px" src="./meeee.png" /> -->
+      <div class="main-title">{{ mainTitle }}</div>
+    </div>
+    <!-- <a-dropdown class="module-dropdown" :class="{ 'module-dropdown-original': isOriginal }" :trigger="['click']" placement="bottomRight">
+      <a class="ant-dropdown-link" @click.prevent>
+        全矿井通风检测
+        <CaretDownOutlined />
+      </a>
+      <template #overlay>
+        <MonitorCenter />
+      </template>
+    </a-dropdown> -->
+    <!-- 采用定位方式以避免出现各个模块隐藏时其他模块下移的问题 -->
+
+    <template v-if="isOriginal">
+      <ModuleOriginal
+        v-for="cfg in configs"
+        :key="cfg.deviceType"
+        :show-style="cfg.showStyle"
+        :module-data="cfg.moduleData"
+        :module-name="cfg.moduleName"
+        :device-type="cfg.deviceType"
+        :data="data"
+        :visible="true"
+      />
+    </template>
+    <template v-else-if="isCommon">
+      <ModuleCommon
+        v-for="cfg in configs"
+        :key="cfg.deviceType"
+        :show-style="cfg.showStyle"
+        :module-data="cfg.moduleData"
+        :module-name="cfg.moduleName"
+        :device-type="cfg.deviceType"
+        :data="data"
+        :visible="true"
+      />
+    </template>
+    <template v-else>
+      <!-- 下面是正常展示的各新版模块 -->
+      <ModuleEnhanced
+        v-for="cfg in enhancedConfigs"
+        :key="cfg.deviceType"
+        :visible="cfg.visible"
+        :show-style="cfg.showStyle"
+        :module-data="cfg.moduleData"
+        :module-name="cfg.moduleName"
+        :device-type="cfg.deviceType"
+        :data="data"
+        @close="cfg.visible = false"
+      />
+      <!-- 下面是用于呼出已隐藏的模块的按钮 -->
+      <div class="pos-absolute top-70px left-460px z-3">
+        <div v-for="(item, i) in hiddenList" :key="`vvhchg${i}`">
+          <AButton class="module-trigger-button" @click="item.visible = true">{{ item.moduleName }}</AButton>
+        </div>
+      </div>
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, onUnmounted } from 'vue';
+  // import { CaretDownOutlined } from '@ant-design/icons-vue';
+  // import MonitorCenter from './components/MonitorCenter.vue';
+  import { useInitConfigs, useInitPage } from './hooks/useInit';
+  import ModuleEnhanced from './components/ModuleEnhanced.vue';
+  import ModuleOriginal from './components/ModuleOriginal.vue';
+  import ModuleCommon from './components/ModuleCommon.vue';
+  // import { useRoute } from 'vue-router';
+  // import VentModal from '/@/components/vent/micro/ventModal.vue';
+  import { getHomeData } from './configurable.api';
+  import { useRoute } from 'vue-router';
+  import { testConfigVent } from './configurable.data';
+
+  const { configs, isOriginal, isCommon } = useInitConfigs();
+  const { mainTitle, enhancedConfigs, hiddenList, data, updateData, updateEnhancedConfigs } = useInitPage('智能通风管控系统');
+  const route = useRoute();
+  let interval: number | undefined;
+
+  onMounted(() => {
+    configs.value = testConfigVent;
+    updateEnhancedConfigs(configs.value);
+
+    getHomeData({}).then(updateData);
+    setInterval(() => {
+      getHomeData({}).then(updateData);
+    }, 60000);
+  });
+
+  onUnmounted(() => {
+    clearInterval(interval);
+  });
+</script>
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+
+  @font-face {
+    font-family: 'douyuFont';
+    src: url('../../../../assets/font/douyuFont.otf');
+  }
+
+  @{theme-deepblue} {
+    .company-home {
+      --image-modal-top: url('@/assets/images/themify/deepblue/vent/home/modal-top.png');
+    }
+  }
+
+  .company-home {
+    --image-modal-top: url('@/assets/images/vent/home/modal-top.png');
+    width: 100%;
+    height: 100%;
+    color: @white;
+    position: relative;
+    background: #000000;
+    // background: url('@/assets/images/home-container/configurable/firehome/bg.png') no-repeat center;
+
+    .top-bg {
+      width: 100%;
+      height: 56px;
+      background: var(--image-modal-top) no-repeat center;
+      position: absolute;
+      z-index: 1;
+      .main-title {
+        height: 56px;
+        font-family: 'douyuFont';
+        font-size: 20px;
+        letter-spacing: 2px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    // .module-left {
+    //   position: absolute;
+    //   width: 450px;
+    //   height: 280px;
+    //   left: 0;
+    // }
+    // .module-right {
+    //   position: absolute;
+    //   width: 450px;
+    //   height: 280px;
+    //   right: 0;
+    // }
+    // .module-bottom {
+    //   position: absolute;
+    //   width: 1000px;
+    //   height: 280px;
+    // }
+    .module-dropdown {
+      padding: 10px;
+      background-image: @vent-configurable-dropdown;
+      border-bottom: 2px solid @vent-configurable-home-light-border;
+      color: @vent-font-color;
+      position: absolute;
+      top: 70px;
+      right: 460px;
+    }
+    .module-dropdown-original {
+      padding: 10px;
+      background-image: @vent-configurable-dropdown;
+      border-bottom: 2px solid @vent-configurable-home-light-border;
+      color: @vent-font-color;
+      position: absolute;
+      top: 70px;
+      right: 460px;
+    }
+    .module-trigger-button {
+      color: @vent-font-color;
+      background-image: @vent-configurable-dropdown;
+      border: none;
+      border-bottom: 2px solid @vent-configurable-home-light-border;
+    }
+  }
+  :deep(.loading-box) {
+    position: unset;
+  }
+</style>

+ 52 - 28
src/views/vent/safetyList/common/detail.vue

@@ -179,9 +179,10 @@
         <!-- 监测详情 -->
         <div class="detail-content" v-if="activeKey == 'manageAuto'">
           <!-- <a-button preIcon="ant-design:sync-outlined" @click="visibleModalEdit1 = true">重置</a-button> -->
-          <a-table size="small" :scroll="{ y: 710 }" :columns="columnsDetail" :data-source="tableData1">
+          <a-table size="small" :scroll="{ y: 710 }" :columns="columnsDetail" :data-source="tableData1"
+            @expand="tableExpand">
             <template #action="{ record }">
-              <a-button v-if="hasPermission('operateRecord:return')" type="primary" size="small"
+              <a-button v-if="hasPermission('operateRecord:return') && record.deviceList" type="primary" size="small"
                 @click="handleEdit(record)">编辑</a-button>
             </template>
             <template #bodyCell="{ column, text }"></template>
@@ -363,6 +364,7 @@ import {
   update158DevName,
   updateDebugStatus,
   get158StationData,
+  set158StationDevicesRead,
   set158StationData,
   get158StationDevices,
   set158StationRead,
@@ -506,25 +508,40 @@ function getMonitor1(flag = false) {
     flag ? 0 : 5000
   );
 }
+let timer2: null | NodeJS.Timeout = null;
+function getMonitor2(flag = false) {
+  timer2 = setTimeout(
+    async () => {
+      await getShowReadList();
+      if (timer2) {
+        timer2 = null;
+      }
+      getMonitor2();
+    },
+    flag ? 0 : 3000
+  );
+}
 async function getDeviceList() {
   let res = await set158StationRead({ stationId: devStationId.value, deviceId: formEdit.cgq });
   if (res) {
-    let data = await get158StationData();
-    let list = data.filter((v) => v.stationId == devStationId.value)[0];
-    // formView = {
-    //   dylfsfx: list.dylfsfx,
-    //   dylbjzt: list.dylbjzt,
-    //   dyl1f2sADz: list.dyl1f2sADz,
-    //   dyl2f1sADz: list.dyl2f1sADz,
-    //   tfl: list.tfl,
-    //   yjbb: list.yjbb,
-    //   rjbb: list.rjbb,
-    //   zxlxbz: list.zxlxbz,
-    //   rqsj: list.rqsj,
-    // };
-    formView = Object.assign({}, list)
+    getMonitor2(true)
   }
 }
+async function getShowReadList(){
+ let data = await get158StationData();
+ if(data && data.length!=0){
+  let list = data.filter((v) => v.stationId == devStationId.value)[0];
+  formView = Object.assign({}, list)
+ }
+}
+async function tableExpand(expaned, record) {
+  let res = await set158StationDevicesRead({ stationId: record.stationId })
+  if (res) {
+    clearTimeout(timer1)
+    getMonitor1(true)
+  }
+
+}
 //tab选项切换
 async function onChangeTab(tab) {
   activeKey.value = tab;
@@ -541,7 +558,7 @@ async function onChangeTab(tab) {
     getMonitor()
   } else if (activeKey.value == 'manageAuto') {
     await getStationList();
-    getMonitor1();
+    getMonitor1(true);
   } else if (activeKey.value == 'operationRecord') {
     await getSearchRecord({ stationId: '', deviceId: '' })
   }
@@ -549,6 +566,7 @@ async function onChangeTab(tab) {
 
 //弹窗关闭
 function cancenModal() {
+  clearTimeout(timer2)
   formEdit.id = ''
   formEdit.cgq = ''
   formEdit.rs485modbusdz = ''
@@ -576,20 +594,26 @@ function cancenModal() {
   formEdit.sgzjbcmm = ''
 
   formView.dylfsfx = ''
-    formView.dylbjzt = ''
-    formView.dyl1f2sADz = ''
-    formView.dyl2f1sADz = ''
-    formView.tfl = ''
-    formView.yjbb = ''
-    formView.rjbb = ''
-    formView.zxlxbz = ''
-    formView.rqsj = ''
+  formView.dylbjzt = ''
+  formView.dyl1f2sADz = ''
+  formView.dyl2f1sADz = ''
+  formView.tfl = ''
+  formView.yjbb = ''
+  formView.rjbb = ''
+  formView.zxlxbz = ''
+  formView.rqsj = ''
 }
 //获取详细信息列表
 async function getStationList() {
   let res = await get158StationData();
   res.forEach((el) => {
+    el.key = el.deviceId
     el.linkstatusC = el.linkstatus ? '连接' : '断开';
+    el.children = el.deviceList
+    el.children.forEach(ec => {
+      ec.key = ec.deviceId
+      ec.linkstatusC = ec.linkstatus ? '连接' : '断开';
+    })
   });
   tableData1.value = res;
 }
@@ -1457,9 +1481,9 @@ onUnmounted(() => {
   border: 1px solid #3ad8ff77 !important;
 }
 
-::v-deep(.zxm-select-selection-item) {
-  color: #fff !important;
-}
+// ::v-deep(.zxm-select-selection-item) {
+//   color: #fff ;
+// }
 
 // ::v-deep(.zxm-form-item-label > label) {
 //   color: #fff !important;

+ 4 - 1
src/views/vent/safetyList/safetyList.api.ts

@@ -13,7 +13,8 @@ enum Api {
   set158StationRead = '/safety/ventanalyDeviceInfo/set158StationRead',
   remove158Substation='/safety/ventanalyDeviceInfo/remove158Substation',//删除158分站及其关联传感器
   get158SetLog='/safety/ventanalySubStation/get158SetLog',//操作记录
-  remove158Device='/safety/ventanalyDeviceInfo/remove158Device'//删除158分站传感器
+  remove158Device='/safety/ventanalyDeviceInfo/remove158Device',//删除158分站传感器
+  set158StationDevicesRead='/safety/ventanalyDeviceInfo/set158StationDevicesRead'
 }
 
 // 分站查询接口
@@ -44,3 +45,5 @@ export const remove158Substation = (params) => defHttp.post({ url: Api.remove158
 export const get158SetLog = (params) => defHttp.post({ url: Api.get158SetLog,params }, { joinParamsToUrl: true });
 //删除158分站传感器
 export const remove158Device = (params) => defHttp.post({ url: Api.remove158Device, params });
+//158监测详情读取列表
+export const set158StationDevicesRead = (params) => defHttp.post({ url: Api.set158StationDevicesRead,params }, { joinParamsToUrl: true });

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است