| | |
| | | class="small-chat-header h-12 flex items-center justify-between px-4" |
| | | style="border-bottom: 1px solid #e0e0e0" |
| | | > |
| | | <div class="text-lg font-bold py-2">WI水务智能助手</div> |
| | | <div class="text-lg font-bold py-2">WI水务智能平台</div> |
| | | <div class="flex items-center gap-2"> |
| | | <!-- <el-icon class="cursor-pointer text-gray-400 hover:text-gray-600"> |
| | | <Refresh /> |
| | |
| | | <!-- 欢迎语 --> |
| | | <!-- <div class="flex flex-col items-center gap-1.5 mt-8"> |
| | | <div class="text-lg">你好, 我是</div> |
| | | <div class="text-lg text-blue-500">WI水务智能助手</div> |
| | | <div class="text-lg text-blue-500">WI水务智能平台</div> |
| | | <span class="text-lg">你可以输入以下问题,进行地图操作</span> |
| | | </div> --> |
| | | <!-- 快捷问题 --> |
| | |
| | | |
| | | <!-- 底部输入框 --> |
| | | <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"> |
| | |
| | | </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 userPic from '/static/images/role/user-200x206.png'; |
| | | import { getSearchMapElement } from '/@/api/map'; |
| | | import { formatDate } from '/@/utils/formatTime'; |
| | | import { systemGlobalConfig } from '/@/stores/global'; |
| | | import { fromLonLat } from 'ol/proj'; |
| | | import { formatDate } from '/@/utils/formatTime'; |
| | | import userPic from '/static/images/role/user-200x206.png'; |
| | | const props = defineProps<{ |
| | | olMap?: OLMap; |
| | | }>(); |
| | |
| | | { 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); |
| | |
| | | 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 }) => { |
| | |
| | | props.olMap.setSourceType(formData.LayerId as GaoDeSourceType); |
| | | refreshAssistantMessage({ value: `成功`, isError: false }); |
| | | }; |
| | | let lastAxiosSource: CancelTokenSource = null; |
| | | |
| | | const startStream = (question: string) => { |
| | | if (lastIsInit) { |
| | | showHistory.value = false; |
| | | } |
| | | |
| | | const currentSource = axios.CancelToken.source(); |
| | | lastAxiosSource = currentSource; |
| | | // if (question === '松福大道DN800松岗联通监测设备没有数据,创建一个设备维修工单,请及时派人维修。') { |
| | | // setTimeout(() => { |
| | | // openOptDlg(); |
| | |
| | | |
| | | if ( |
| | | chunkRes.type === 'string' && |
| | | ['create_work_order', 'switch_layers', 'switch_topic', 'query_address', 'query_object'].includes(chunkRes.mode) |
| | | ['create_work_order', 'switch_layers', 'switch_topic', 'query_address', 'query_object', 'map'].includes(chunkRes.mode) |
| | | ) { |
| | | const jsonData = JSON.parse(chunkRes.value); |
| | | |
| | |
| | | haveMapOperate = true; |
| | | handleQueryObject(jsonData); |
| | | break; |
| | | case 'map': |
| | | haveMapOperate = true; |
| | | handleMapCommand(jsonData); |
| | | break; |
| | | } |
| | | } |
| | | |
| | |
| | | refreshAssistantMessage({ reason: `未识别到操作:"${question}"` }); |
| | | } |
| | | } |
| | | }, |
| | | { |
| | | cancelToken: currentSource.token, |
| | | } |
| | | ).catch((error) => { |
| | | Logger.error('agent stream error:\n\n' + error); |
| | |
| | | }); |
| | | }; |
| | | //#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> |
| | | |