wujingjing
2025-03-04 92d2ea48d343fc00d81905167d033c40200ea716
src/components/chat/smallChat/index.vue
@@ -134,7 +134,14 @@
         <!-- 底部输入框 -->
         <div class="p-2 border-t">
            <ChatInput v-model="inputText" @sendClick="sendClick" @toggleHistory="toggleHistory" :showHistory="showHistory" />
            <ChatInput
               :isTalking="lastIsLoading"
               v-model="inputText"
               @sendClick="sendClick"
               @toggleHistory="toggleHistory"
               @stopGenClick="stopGenClick"
               :showHistory="showHistory"
            />
         </div>
      </div>
      <Teleport to="body">
@@ -144,18 +151,23 @@
</template>
<script setup lang="ts" name="smallChat">
import type { CancelTokenSource } from 'axios';
import axios from 'axios';
import { cloneDeep, defaults } from 'lodash-es';
import { fromLonLat } from 'ol/proj';
import { computed, nextTick, onMounted, ref } from 'vue';
import ChatInput from './ChatInput.vue';
import type { ChatMessage } from './types';
import { AssistantContent } from './types';
import WorkOrderDlg from './WorkOrderDlg.vue';
import { agentStreamByPost } from '/@/api/ai/chat';
import { getSearchMapElement } from '/@/api/map';
import { useDrag } from '/@/hooks/useDrag';
import { Logger } from '/@/model/logger/Logger';
import { GaoDeSourceType, gaoDeSourceTypeMap, type OLMap } from '/@/model/map/OLMap';
import assistantPic from '/static/images/role/assistant-200x192.png';
import { systemGlobalConfig } from '/@/stores/global';
import { formatDate } from '/@/utils/formatTime';
import userPic from '/static/images/role/user-200x206.png';
import { useDrag } from '/@/hooks/useDrag';
import { cloneDeep, defaults } from 'lodash-es';
import WorkOrderDlg from './WorkOrderDlg.vue';
const props = defineProps<{
   olMap?: OLMap;
}>();
@@ -180,9 +192,9 @@
      x: 200,
   },
});
const cancelSubmit = (reason) =>{
const cancelSubmit = (reason) => {
   refreshAssistantMessage({ reason: reason });
}
};
const submitDlg = () => {
   refreshAssistantMessage({ value: `成功`, isError: false });
};
@@ -196,12 +208,17 @@
   { title: '创建工单', question: '松福大道DN800松岗联通监测设备没有数据,创建一个设备维修工单,请及时派人维修。' },
]);
const chatContentRef = ref<HTMLDivElement>(null);
const getLastAssistantMessage = () => {
   const last = historyMessages.value[historyMessages.value.length - 1];
   const result = last.role === 'assistant' ? last : null;
   const result = last?.role === 'assistant' ? last : null;
   return result as ChatMessage<AssistantContent>;
};
const lastIsLoading = computed(() => {
   const last = getLastAssistantMessage();
   const loading = last?.content?.isLoading ?? false;
   return loading;
});
//#region ====================== 添加工单 ======================
const optDlgIsShow = ref(false);
@@ -237,16 +254,91 @@
   }
};
const handleCreateWorkOrder = (formData: any) => {
   openOptDlg(formData ?? {});
};
const handleSwitchLayer = (formData: { layerId: string; visible: boolean }) => {
   props.olMap.setLayerVisible(formData.layerId, formData.visible);
   refreshAssistantMessage({ value: `成功`, isError: false });
};
const changeTheme = (formData: { themeId: string }) => {
   props.olMap.setThemeById(formData.themeId);
   refreshAssistantMessage({ value: `成功`, isError: false });
};
const handleQueryObject = async (formData: { objectName: string }) => {
   const res = await getSearchMapElement({
      search_text: formData.objectName,
      max_count: 10,
      time: formatDate(new Date()),
   });
   const result = res?.values ?? [];
   props.olMap.clearObjectSearch();
   const features = [];
   for (const item of result) {
      if (!item.WKT) continue;
      const feature = props.olMap.readWKT(item.WKT);
      features.push(feature);
   }
   props.olMap.zoomToFeatures(features);
   props.olMap.highlightSearch(features);
   refreshAssistantMessage({ value: `成功`, isError: false });
};
const handleSearchAddress = async (formData: { address: string }) => {
   const result = await props.olMap.gaodeAddressSearch(formData.address, systemGlobalConfig.value['ui.project_city']);
   const pois = result?.pois ?? [];
   const searchResultList =
      pois
         .map((item) => {
            return {
               name: item.name,
               // address: item.address,
               cityname: item.cityname,
               adname: item.adname,
               model: item,
               isSearchObj: false,
               location: item.location,
            };
         })
         ?.slice(0, 5) ?? [];
   const features = [];
   for (const item of searchResultList) {
      if (!item.location) continue;
      const [lon, lat] = item.location.split(',').map(Number);
      const point = fromLonLat([lon, lat]);
      const WKT = `SRID=3857;POINT(${point[0]} ${point[1]})`;
      const feature = props.olMap.readWKT(WKT);
      features.push(feature);
   }
   props.olMap.highlightSearch(features);
   props.olMap.zoomToFeatures(features);
   refreshAssistantMessage({ value: `成功`, isError: false });
};
const handleSetBackgroundLayer = (formData: { LayerId: string }) => {
   if (!formData.LayerId) return;
   props.olMap.setSourceType(formData.LayerId as GaoDeSourceType);
   refreshAssistantMessage({ value: `成功`, isError: false });
};
let lastAxiosSource: CancelTokenSource = null;
const startStream = (question: string) => {
   if (lastIsInit) {
      showHistory.value = false;
   }
   if (question === '松福大道DN800松岗联通监测设备没有数据,创建一个设备维修工单,请及时派人维修。') {
      setTimeout(() => {
         openOptDlg();
      }, 400);
      return;
   }
   const currentSource = axios.CancelToken.source();
   lastAxiosSource = currentSource;
   // if (question === '松福大道DN800松岗联通监测设备没有数据,创建一个设备维修工单,请及时派人维修。') {
   //    setTimeout(() => {
   //       openOptDlg();
   //    }, 400);
   //    return;
   // }
   // mockCommand(question);
   // return;
@@ -259,18 +351,57 @@
      (chunkRes) => {
         Logger.info('agent stream response:\n\n' + JSON.stringify(chunkRes));
         if (chunkRes.mode === 'map') {
            haveMapOperate = true;
            if (chunkRes.type === 'string') {
               const jsonData = JSON.parse(chunkRes.value);
               handleMapCommand(jsonData);
         if (
            chunkRes.type === 'string' &&
            ['create_work_order', 'switch_layers', 'switch_topic', 'query_address', 'query_object', 'map'].includes(chunkRes.mode)
         ) {
            const jsonData = JSON.parse(chunkRes.value);
            switch (chunkRes.mode) {
               case 'create_work_order':
                  haveMapOperate = true;
                  handleCreateWorkOrder(jsonData);
                  break;
               case 'switch_layers':
                  haveMapOperate = true;
                  handleSwitchLayer(jsonData);
                  break;
               case 'switch_topic':
                  haveMapOperate = true;
                  changeTheme(jsonData);
                  break;
               case 'switch_background_layers':
                  haveMapOperate = true;
                  handleSetBackgroundLayer(jsonData);
                  break;
               case 'query_address':
                  haveMapOperate = true;
                  handleSearchAddress(jsonData);
                  break;
               case 'query_object':
                  haveMapOperate = true;
                  handleQueryObject(jsonData);
                  break;
               case 'map':
                  haveMapOperate = true;
                  handleMapCommand(jsonData);
                  break;
            }
         }
         if (chunkRes.mode === 'finish') {
            if (!haveMapOperate) {
               refreshAssistantMessage({ reason: `未识别到操作:"${question}"` });
            }
         }
      },
      {
         cancelToken: currentSource.token,
      }
   ).catch((error) => {
      Logger.error('agent stream error:\n\n' + error);
@@ -407,6 +538,17 @@
   });
};
//#endregion
//#region ====================== 流停止 ======================
const stopGenClick = () => {
   lastAxiosSource?.cancel();
   const last = getLastAssistantMessage();
   if (!last) return;
   last.content.isLoading = false;
   last.content.isError = true;
   last.content.reason = '用户停止操作';
};
//#endregion
onMounted(() => {});
</script>