123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696 |
- <script setup lang="jsx">
- import { ref, watch } from 'vue';
- import { useRouter } from 'vue-router';
- import { NTabs, NTab, NSelect } from 'naive-ui';
- import { useChatStore } from '@/stores/modules/chatStore';
- import { BaseTable, ChatWelcome, RecodeSquareCardItem, TheSubMenu, TheChatView } from "@/components";
- import { useInfinite, useRecommend, useFetchStream, useScroll } from '@/composables';
- import { ChatBaseCard, ChatAnswer } from '@/components/Chat';
- import { CustomModal } from "./components";
- import { inColumns, outColumns } from './config';
- import { formatToData } from "@/utils/format";
- import { waterApi } from '@/api/water';
- const { recommendList } = useRecommend({ type: 1 });
- const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
- const { refetch, cancelFetch } = useFetchStream("/grpc/decisionStream", { methdos: 'POST' }, false);
- const { recordList, isFetching, onScrolltolower, onRestore } = useInfinite('/front/bigModel/warning/pageList', { type: 0, warningStatus: 0 });
- const router = useRouter();
- const chatStore = useChatStore();
- // 回答列表
- const answerResult = ref([]);
- // 获取最终回答流数据参数
- const flowParams = {
- feedback: '',
- category: '',
- warningId: '',
- simulate: '{}'
- };
- const answerLoading = ref(false);
- const textDataSources = ref(null);
- const warningActive = ref(0);
- const subMenuRef = ref(null);
- // 进出水数据
- const jsTableData = ref([]);
- const csTableData = ref([]);
- const visible = ref(false);
- const modalData = ref({});
- // 菜单水数据类型
- const waterTypeValue = ref('');
- const options = [
- { label: '全部', value: '' },
- { label: '进水', value: 0 },
- { label: '出水', value: 1 }
- ]
- const mockData = [
- {
- "createBy": "task-job",
- "createTime": "2025-02-22 18:08",
- "updateBy": "task-job",
- "updateTime": "2025-02-23 09:08",
- "remark": "0",
- "id": 4287,
- "type": 0,
- "category": "进水SS",
- "time": "2025-02-23 08:08",
- "reason": "进水SS超标报警",
- "warningVal": 900,
- "designVal": 315,
- "controlVal": null,
- "forecastVal": null,
- "functionWay": 0,
- "level": "一级",
- "status": 2,
- "isEmergency": 0,
- "offTime": "2025-02-23 09:08:00",
- "operator": null,
- "review": null,
- "useRecommend": null,
- "waterType": 0,
- "symbol": 0,
- "cwrwxz": 2.8571,
- "cwrwfhz": 2.1813,
- "delFlag": 0,
- "revision": 15,
- "counts": "2",
- "warningValStr": null,
- "warningStatus": null,
- "symbolDesc": "超标准值"
- },
- {
- "createBy": "task-job",
- "createTime": "2025-02-22 19:08",
- "updateBy": "task-job",
- "updateTime": "2025-02-22 22:08",
- "remark": "0",
- "id": 4289,
- "type": 0,
- "category": "出水总氮",
- "time": "2025-02-22 21:08",
- "reason": "出水总氮超管控报警",
- "warningVal": 12.58,
- "designVal": 15,
- "controlVal": 12,
- "forecastVal": null,
- "functionWay": 0,
- "level": "三级",
- "status": 2,
- "isEmergency": 0,
- "offTime": "2025-02-22 22:08:00",
- "operator": null,
- "review": null,
- "useRecommend": null,
- "waterType": 1,
- "symbol": 1,
- "cwrwxz": 1.0699,
- "cwrwfhz": 0.282,
- "delFlag": 0,
- "revision": 3,
- "counts": "4",
- "warningValStr": null,
- "warningStatus": null,
- "symbolDesc": "超管控值"
- },
- {
- "createBy": "task-job",
- "createTime": "2025-02-20 03:08",
- "updateBy": "task-job",
- "updateTime": "2025-02-20 08:08",
- "remark": "0",
- "id": 4256,
- "type": 0,
- "category": "进水SS",
- "time": "2025-02-20 07:08",
- "reason": "进水SS超标报警",
- "warningVal": 900,
- "designVal": 315,
- "controlVal": null,
- "forecastVal": null,
- "functionWay": 0,
- "level": "一级",
- "status": 2,
- "isEmergency": 0,
- "offTime": "2025-02-20 08:08:20",
- "operator": null,
- "review": null,
- "useRecommend": null,
- "waterType": 0,
- "symbol": 0,
- "cwrwxz": 1.0698,
- "cwrwfhz": 0.6046,
- "delFlag": 0,
- "revision": 5,
- "counts": "2",
- "warningValStr": null,
- "warningStatus": null,
- "symbolDesc": "超标准值"
- },
- {
- "createBy": "task-job",
- "createTime": "2025-02-19 17:08",
- "updateBy": "task-job",
- "updateTime": "2025-02-19 21:08",
- "remark": "0",
- "id": 4247,
- "type": 0,
- "category": "进水总磷",
- "time": "2025-02-19 20:08",
- "reason": "进水总磷超标报警",
- "warningVal": 8.32,
- "designVal": 7.1,
- "controlVal": null,
- "forecastVal": null,
- "functionWay": 0,
- "level": "一级",
- "status": 2,
- "isEmergency": 0,
- "offTime": "2025-02-19 21:08:20",
- "operator": null,
- "review": null,
- "useRecommend": null,
- "waterType": 0,
- "symbol": 0,
- "cwrwxz": 1.1554,
- "cwrwfhz": 0.7731,
- "delFlag": 0,
- "revision": 4,
- "counts": "2",
- "warningValStr": null,
- "warningStatus": null,
- "symbolDesc": "超标准值"
- },
- ]
- watch(() => waterTypeValue.value, curValue => {
- onRestore({ warningStatus: warningActive.value, waterType: curValue });
- })
- const handleModelVisible = () => {
- visible.value = true;
- }
- const resetConfiguration = () => {
- /**
- * 临时这样,后续统一处理
- * */
- textDataSources.value = '';
- answerLoading.value = false;
- answerResult.value = [];
- flowParams.feedback = '';
- flowParams.category = '';
- flowParams.warningId = '';
- flowParams.simulate = '{}';
- cancelFetch();
- }
- /**
- * 报警详情
- */
- const handleOpenContent = async (item) => {
- const { id, category, reason: title, counts } = item;
- if (id == flowParams.warningId) return;
- flowParams.category = category;
- flowParams.warningId = id;
- flowParams.feedback = '';
- flowParams.simulate = '{}';
- answerLoading.value = false;
- answerResult.value = [];
- const { data } = await waterApi.getWaringDetails(id);
- const showVal = JSON.parse(data.showVal);
- const { basic, jsData, csData } = showVal;
- try {
- const answer = JSON.parse(data.answer);
- const reportList = [];
- const alertList = [];
- let simulateObj = null;
- answer.map(item => {
- const answerObjItem = JSON.parse(item);
- switch (answerObjItem.biz) {
- case "DECISION_REPORT":
- reportList.push(answerObjItem.message);
- break
- case "DECISION_ALERT":
- alertList.push(answerObjItem);
- break
- case "DECISION_SIMULATE":
- if (warningActive.value === 1) return;
- const { off, on, pred } = JSON.parse(answerObjItem.message);
- simulateObj = {
- biz: 'DECISION_SIMULATE',
- off,
- on,
- pred,
- isDisable: false
- }
- modalData.value = simulateObj;
- }
- })
- if (reportList.length) {
- answerResult.value.push({
- biz: 'DECISION_REPORT',
- answer: reportList.join(""),
- loading: false,
- delayLoading: false
- })
- }
- if (alertList.length) {
- const [parseAnswer] = alertList.map(item => {
- item.message = Object.keys(item.message).map(key => ({ ...item.message[key], isActive: null }));
- return item;
- })
- answerResult.value.push({
- biz: 'DECISION_ALERT',
- loading: false,
- delayLoading: false,
- isAllSelect: false,
- list: parseAnswer?.message
- })
- }
- if (simulateObj) {
- answerResult.value.push(simulateObj);
- }
- } catch (error) {
- answerResult.value.push({
- biz: 'DECISION_REPORT',
- answer: data.answer,
- loading: false,
- delayLoading: false
- })
- }
- cancelFetch();
- // answerResult.value = [];
- // const reportList = [];
- // const alertList = [];
- // let simulateObj = null;
- // answer.map(item => {
- // const answerObjItem = JSON.parse(item);
- // switch(answerObjItem.biz) {
- // case "DECISION_REPORT":
- // reportList.push(answerObjItem.message);
- // break
- // case "DECISION_ALERT":
- // alertList.push(answerObjItem);
- // break
- // case "DECISION_SIMULATE":
- // console.log( "DECISION_SIMULATE", answerObjItem.message );
- // if (warningActive.value === 1) return;
- // const { off, on, pred } = JSON.parse(answerObjItem.message);
- // simulateObj = {
- // biz: 'DECISION_SIMULATE',
- // off,
- // on,
- // pred,
- // isDisable: false
- // }
- // modalData.value = simulateObj;
- // }
- // })
- // if ( reportList.length ) {
- // answerResult.value.push({
- // biz: 'DECISION_REPORT',
- // answer: reportList.join(""),
- // loading: false,
- // delayLoading: false
- // })
- // }
- // if ( alertList.length ) {
- // const [ parseAnswer ] = alertList.map(item => {
- // item.message = Object.keys(item.message).map(key => ({ ...item.message[key], isActive: null }));
- // return item;
- // })
- // answerResult.value.push({
- // biz: 'DECISION_ALERT',
- // loading: false,
- // delayLoading: false,
- // isAllSelect: false,
- // list: parseAnswer?.message
- // })
- // }
- // if (simulateObj) {
- // answerResult.value.push(simulateObj);
- // }
- basic.title = title;
- textDataSources.value = formatToData({
- dataSource: basic,
- warnKey: '报警值',
- statusVal: !!warningActive.value ? '系统关闭' : basic['状态']
- });
- /**
- * 临时修改 - 后续需要删除
- * */
- textDataSources.value = {
- ...textDataSources.value,
- list: textDataSources.value.list.map(item => {
- if (item.label === '持续时间') item.value = counts;
- return item
- })
- }
- jsTableData.value = [jsData];
- csTableData.value = [csData];
- }
- const onChangeTabs = warningStatus => {
- resetConfiguration();
- warningActive.value = warningStatus;
- onRestore({ warningStatus });
- subMenuRef.value.scrollToTop();
- }
- // 生成流数据
- const onRegenerate = async () => {
- answerLoading.value = true;
- const len = answerResult.value.length ? answerResult.value.length : 0;
- const tempReport = {
- biz: 'DECISION_REPORT',
- answer: '',
- loading: true,
- delayLoading: true,
- };
- let tempSimulate = null;
- answerLoading.value = answerResult.value[len - 1].biz !== 'DECISION_TABLE';
- const feedback = flowParams.feedback
- const params = {
- body: JSON.stringify({ ...flowParams, feedback: JSON.stringify(feedback) }),
- errorHandler: () => { },
- successHandler: data => {
- const item = JSON.parse(data);
- answerLoading.value = false;
- if (item.biz === 'DECISION_REPORT') {
- tempReport.answer += item.message;
- tempReport.delayLoading = false;
- answerResult.value[len] = { ...tempReport };
- }
- if (item.biz === 'DECISION_ALERT') {
- const list = Object.keys(item.message).map(key => ({ ...item.message[key], isActive: null }));
- answerResult.value.push({
- biz: 'DECISION_ALERT',
- loading: true,
- delayLoading: false,
- isAllSelect: false,
- list
- })
- }
- if (item.biz === 'DECISION_SIMULATE') {
- const lastAnswerItem = answerResult.value[len - 1];
- if (lastAnswerItem.biz === 'DECISION_TABLE') {
- answerResult.value[len - 1] = {
- ...lastAnswerItem,
- content: JSON.parse(item.message).pred.join(", ")
- }
- } else {
- const { off, on, pred } = JSON.parse(item.message);
- tempSimulate = {
- biz: 'DECISION_SIMULATE',
- off,
- on,
- pred,
- isDisable: false
- }
- modalData.value = tempSimulate;
- }
- }
- scrollToBottomIfAtBottom();
- }
- }
- try {
- await refetch(params);
- const answerItem = answerResult.value[answerResult.value.length - 1];
- if (answerItem?.biz) {
- answerItem.loading = false;
- answerItem.delayLoading = false;
- if (answerItem.biz === 'DECISION_TABLE') {
- scrollToBottom()
- }
- }
- if (tempSimulate) {
- answerResult.value.push(tempSimulate);
- }
- setTimeout(() => {
- scrollToBottomIfAtBottom();
- }, 500)
- }
- catch (error) {
- console.log("exist error .....", error);
- }
- }
- // 回答选项点击
- const handlerAlertOptions = (item, val, index) => {
- const { list, isAllSelect } = item;
- if (isAllSelect) return;
- val.isActive = index;
- const isExists = list.find(({ isActive }) => isActive === null);
- if (!isExists) {
- item.isAllSelect = true;
- const result = item.list
- .map(({ id, options, isActive }) => ({ [id]: options[isActive] }))
- .reduce((accumulator, currentValue) => {
- Object.keys(currentValue).forEach(key => accumulator[key] = currentValue[key]);
- return accumulator;
- }, {});
- const newResult = { ...flowParams.feedback, ...result };
- flowParams.feedback = newResult;
- onRegenerate();
- }
- }
- // 开始预测
- const handleSendSimulate = ({ simulate, table }) => {
- const len = answerResult.value.length;
- flowParams.simulate = simulate;
- answerResult.value[len - 1].isDisable = true;
- answerResult.value.push({
- biz: 'DECISION_TABLE',
- loading: true,
- delayLoading: false,
- table,
- isDisable: false
- })
- scrollToBottom();
- onRegenerate();
- }
- // 欢迎页提交
- const handleWelcomeRecommend = question => {
- chatStore.setChatQuestion(question);
- router.push('/answer');
- }
- </script>
- <template>
- <section class="flex items-start h-full" id="warning">
- <TheSubMenu title="水质报警" @scrollToLower="onScrolltolower" :loading="isFetching" ref="subMenuRef">
- <template #top>
- <div class="border-[#DAE5ED]">
- <n-tabs type="line" justify-content="space-evenly">
- <n-tab name="oasis" tab="正在报警" @click="onChangeTabs(0)"></n-tab>
- <n-tab name="thebeatles" tab="历史报警" @click="onChangeTabs(1)"></n-tab>
- </n-tabs>
- <div class="select-card">
- <n-select v-model:value="waterTypeValue" :options="options" size="tiny" />
- </div>
- </div>
- </template>
- <div class="px-[12px] py-[14px] text-[#5e5e5e]">
- <p v-show="!recordList.length" class="pt-[30px] text-[12px] text-[#999] text-center">暂无报警数据</p>
- <div class="grid grid-cols-1 gap-[12px]">
- <div v-show="warningActive == 1">
- <RecodeSquareCardItem v-for="item, index in mockData" :key="item.id" :item="item"
- @on-click="handleOpenContent" :style="{ marginBottom: index < 3 ? '10px' : '' }" />
- </div>
- <RecodeSquareCardItem v-for="item in recordList" :key="item.id" :item="item" @on-click="handleOpenContent" />
- </div>
- </div>
- </TheSubMenu>
- <TheChatView ref="scrollRef" :is-footer="false">
- <ChatWelcome title="您好,我是LibraAI工艺管控助手" card-title="常见处理方案:" :sub-title="[
- '水质报警功能针对五大核心指标实时监测,发现异常后将推送给相关人员决策方案',
- '报警时间为每小时警报,请大家及时处理'
- ]" :card-content="recommendList" @on-click="handleWelcomeRecommend" v-if="!textDataSources" />
- <ChatBaseCard v-if="textDataSources">
- <div class="waring-answer-wrapper">
- <dl class="message-inner warning-info_medium ">
- <dt class="mb-[2px] font-bold text-[#1A2029]">{{ textDataSources?.title }}</dt>
- <dd v-for="item, index in textDataSources?.list" :key="index"><span
- :class="{ 'text-[#F44C49]': item.isWarning }">{{ item.label }}: {{ item.value }}</span></dd>
- </dl>
- <div class="table-inner">
- <div class="warning-table mb-[8px]">
- <div class="title">
- <span>当前进水数据:</span>
- </div>
- <div class="main">
- <BaseTable :columns="inColumns" :data="jsTableData"></BaseTable>
- </div>
- </div>
- <div class="warning-table">
- <div class="title">
- <span>当前出水数据:</span>
- </div>
- <div class="main">
- <BaseTable :columns="outColumns" :data="csTableData"></BaseTable>
- </div>
- </div>
- </div>
- </div>
- </ChatBaseCard>
- <section v-for="item, index in answerResult" :key="index">
- <template v-if="item.biz === 'DECISION_REPORT'">
- <ChatAnswer :loading="item.loading" :delay-loading="item.delayLoading" :toggleVisibleIcons="false"
- :content="item.answer"></ChatAnswer>
- </template>
- <template v-if="item.biz === 'DECISION_ALERT'">
- <ChatBaseCard :loading="item.loading" :delay-loading="item.delayLoading" :toggleVisibleIcons="false">
- <p class="mb-[15px] font-bold text-[#1A2029]">需要确定以下问题,完成决策方案:</p>
- <ul class="radio-wrapper space-y-[14px]">
- <li class="flex items-center" v-for="val, i in item.list" :key="i">
- <p class="mr-[14px]">{{ val.mainContent }}</p>
- <p class="radio-btn-group space-x-[14px]">
- <span v-for="option, index in val.options" :class="['radio-btn', { active: val.isActive === index }]"
- @click="handlerAlertOptions(item, val, index)">{{ option }}</span>
- </p>
- </li>
- </ul>
- </ChatBaseCard>
- </template>
- <template v-if="item.biz === 'DECISION_SIMULATE'">
- <button class="
- px-[30px] py-[10px] mb-[20px]
- rounded-[8px]
- bg-white text-[13px]
- text-[#5E5E5E] hover:text-[#2454FF]" :disabled="item.isDisable" @click="handleModelVisible">
- 水质预测推演
- </button>
- </template>
- <template v-if="item.biz === 'DECISION_TABLE'">
- <ChatAnswer :loading="item.loading" :delay-loading="item.delayLoading" :toggleVisibleIcons="false"
- class="reset-chart">
- <div class="markdown-body text-[15px] break-all">
- <strong class="block mb-[16px]">推荐指标调整:</strong>
- <div class="custom-table-wrapper">
- <table>
- <thead>
- <tr>
- <th v-for="text in item.table.header" :key="text">{{ text }}</th>
- </tr>
- </thead>
- <tbody class="text-center">
- <tr>
- <td v-for="text in item.table.body" :key="text">{{ text }}</td>
- </tr>
- </tbody>
- </table>
- </div>
- <strong class="block mb-[16px]">预测推演结果:</strong>
- <span>以上指标达成后,预计{{ flowParams.category }}可达到:{{ item.content }}</span>
- </div>
- </ChatAnswer>
- <button class="
- px-[30px] py-[10px] mb-[20px]
- rounded-[8px]
- bg-white text-[13px]
- text-[#5E5E5E] hover:text-[#2454FF]" :disabled="item.isDisable" @click="handleModelVisible">
- 水质预测推演
- </button>
- </template>
- </section>
- <ChatAnswer :loading="answerLoading" :delay-loading="answerLoading" :toggleVisibleIcons="false"
- v-show="answerLoading" loadingText="内容生成中,大概需要1分钟..."></ChatAnswer>
- </TheChatView>
- </section>
- <CustomModal v-model:visible="visible" :current-data="modalData" @on-submit="handleSendSimulate"></CustomModal>
- </template>
- <style lang="scss">
- .reset-chart {
- .markdown-body {
- .custom-table-wrapper {
- width: 100%;
- overflow: hidden;
- padding: 10px;
- table td,
- table th {
- white-space: normal !important;
- }
- }
- }
- }
- .select-card {
- padding: 15px 10px 0 10px;
- }
- </style>
|