|
|
@@ -1,2819 +1,566 @@
|
|
|
-<!DOCTYPE html>
|
|
|
-<html lang="en">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
|
- <title>病历内涵质控小助手</title>
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
|
- <meta content="EMOON|医梦AI" name="description" />
|
|
|
- <meta content="回车网络" name="author" />
|
|
|
- <!--CSS-->
|
|
|
- <link rel="stylesheet" href="../../public/css/plugin.css">
|
|
|
- <link rel="stylesheet" href="../../public/css/main.css">
|
|
|
- <link rel="stylesheet" href="../../public/css/responsive.css">
|
|
|
- <link rel="stylesheet" href="../../public/css/chat.css">
|
|
|
- <!-- Favicon-->
|
|
|
- <link rel="shortcut icon" href="../../public/img/favicon.png">
|
|
|
- <!--IOS Icon-->
|
|
|
- <link rel="apple-touch-icon-precomposed" href="../../public/img/faviconIphone.png" />
|
|
|
- <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../public/img/faviconIphone.png" />
|
|
|
- <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../public/img/faviconIphone.png" />
|
|
|
- <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../public/img/faviconIphone.png" />
|
|
|
- <!--微信分享-->
|
|
|
- <script type="text/javascript" src="../../public/js/wx.js"></script>
|
|
|
- <script>
|
|
|
- // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
|
|
|
- wx.config({
|
|
|
- debug: false, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。
|
|
|
- appId: '<?php echo $appId ?>', // 必填,公众号的唯一标识
|
|
|
- timestamp: '<?php echo $timestamp ?>', // 必填,生成签名的时间戳
|
|
|
- nonceStr: '<?php echo $nonceStr ?>', // 必填,生成签名的随机串
|
|
|
- signature: '<?php echo $signature ?>',// 必填,签名
|
|
|
- jsApiList: [
|
|
|
- 'updateAppMessageShareData',
|
|
|
- 'updateTimelineShareData',
|
|
|
- ] // 必填,需要使用的 JS 接口列表
|
|
|
- });
|
|
|
- //验证正确
|
|
|
- wx.ready(function(){
|
|
|
- //获取“分享给朋友”按钮点击状态及自定义分享内容接口
|
|
|
- wx.updateAppMessageShareData({
|
|
|
- title: "医梦AI|客户端", // 分享标题
|
|
|
- desc: "「医梦AI客户端」基于BS实现一“端”通配,用于适配多模型协作、多模型扩展、HIS等多系统触发的实时业务场景。", // 分享描述
|
|
|
- link: "https://emoon.com/index/chatBox?user=33",//分享点击之后的链接 连接可以是接口也可以是一个图片路径*(绝对路径)
|
|
|
- imgUrl: 'https://emoon.com/public/img/faviconIphone.png', // 分享图标
|
|
|
- type: 'link', // 分享类型,music、video或link,不填默认为link
|
|
|
- success: function () {
|
|
|
- }
|
|
|
- });
|
|
|
- //分享到朋友圈接口
|
|
|
- wx.updateTimelineShareData({
|
|
|
- title: '医梦AI|客户端', // 分享标题
|
|
|
- link: 'https://emoon.com/index/chatBox?user=33', // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
|
|
|
- imgUrl: 'https://emoon.com/public/img/faviconIphone.png', // 分享图标
|
|
|
- success: function () {
|
|
|
- // 设置成功
|
|
|
- }
|
|
|
- })
|
|
|
- });
|
|
|
- //验证错误
|
|
|
- wx.error(function(res){
|
|
|
- // alert(res);
|
|
|
- });
|
|
|
- </script>
|
|
|
- </head>
|
|
|
- <body>
|
|
|
- <!-- Start Preload -->
|
|
|
- <div class="preloader"></div>
|
|
|
- <div class="block-1"></div>
|
|
|
- <div class="block-2"></div>
|
|
|
- <div class="logo-load"><img src="../../public/img/icon.svg" alt=""></div>
|
|
|
- <div class="logo-load spinning"></div>
|
|
|
- <div class="over-all"></div>
|
|
|
- <!-- End Preload -->
|
|
|
-
|
|
|
- <!-- 质控不采纳弹窗 -->
|
|
|
- <div class='alertBox' style="position: fixed; z-index: 1003;">
|
|
|
- <div class='alertBoxBackground'>
|
|
|
-
|
|
|
- </div>
|
|
|
- <div class='alertBoxChild'>
|
|
|
- <input class="alertBoxInput" id="alertBoxInput" style="height: 40px; line-height: 40px;" type="text" placeholder="请输入不采纳的原因">
|
|
|
- <div class="alertBoxButton">发送</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- ajax预加载遮罩层(异步请求时关闭) -->
|
|
|
- <!--<div class='alertBoxAjax'>-->
|
|
|
- <!-- <div class='alertBoxBackgroundAjax'>-->
|
|
|
-
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <div class='alertBoxChildAjax'>-->
|
|
|
- <!-- <div class="alertBoxButtonAjax">正在读取数据列表</div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!--</div>-->
|
|
|
+<?php
|
|
|
|
|
|
- <!-- Start Content -->
|
|
|
- <div id="spiral">
|
|
|
- {if condition='$device=="pc"'}
|
|
|
- <div class="main-head luxy-el" data-speed-y="15" data-offset="0" style="display: none">
|
|
|
- <div id="headmove">
|
|
|
- <div data-depth="0.1">
|
|
|
- <div class="bg-detail-work" style="background:url('../../public/img/detail-work/1/5.png') center no-repeat; width:90%; position: relative; left: 5%; background-size: auto 100%;"></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="heading-text">
|
|
|
- <div class="heading-text-front">
|
|
|
- <h1 class="title" style="-webkit-text-stroke: 1px rgba(255,255,255,1)">病历内涵质控E2</h1>
|
|
|
- </div>
|
|
|
- <div class="heading-text-back">
|
|
|
- <h1 class="title">病历内涵质控E2</h1>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/if}
|
|
|
- {if condition='$device=="phone"'}
|
|
|
- <div class="main-head luxy-el" data-speed-y="15" data-offset="0" style="top: -30px; display: none">
|
|
|
- <div id="headmove">
|
|
|
- <div data-depth="0.1">
|
|
|
- <div class="bg-detail-work" style="background:url('../../public/img/detail-work/1/5.png') center no-repeat; width:90%; position: relative; left: 5%; background-size: auto 60%;"></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="heading-text">
|
|
|
- <div class="heading-text-front">
|
|
|
- <h1 class="title" style="-webkit-text-stroke: 1px rgba(255,255,255,1)">病历内涵质控E2</h1>
|
|
|
- </div>
|
|
|
- <div class="heading-text-back">
|
|
|
- <h1 class="title">病历内涵质控E2</h1>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/if}
|
|
|
- <section class="detail-work bg-darks" style=" height: 100%; margin: 0; padding: 0; overflow: hidden;">
|
|
|
- <div class="container" style="max-width: 100%; padding: 0px">
|
|
|
-
|
|
|
- <div class="row" style="margin: 0px">
|
|
|
-
|
|
|
- {if condition='$device=="pc"'}
|
|
|
- <div id="lineStatus" style="position: fixed; right: 1rem; top: 2.5rem; display: block; color: #fff; background: #007bff; width: 8rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 999; opacity: 0.8">暂停队列</div>
|
|
|
-
|
|
|
- <div style="position: fixed; right: 1rem; top: 5rem; display: block; color: #fff; background: #007bff; width: 8rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 999; opacity: 0.8" onclick="clearAllQueues()">清理队列</div>
|
|
|
- <div style="position: fixed; right: 1rem; top: 7.5rem; display: none; color: #fff; width: 8rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 1001; opacity: 0.8"> <!-- 新增容器,设置相对定位和固定宽度 -->
|
|
|
- <div class="selected-option" onclick="toggleDropdown()">
|
|
|
- <span id="selected-text">选择知识库</span>
|
|
|
- <span class="arrow">▼</span>
|
|
|
- </div>
|
|
|
- <div class="dropdown-options" id="dropdown">
|
|
|
- <div class="option" onclick="selectOption('', '选择知识库')">
|
|
|
- 选择知识库
|
|
|
- </div>
|
|
|
- {volist name="ragList" id="rag"}
|
|
|
- <div class="option" onclick="selectOption('{$rag.ragid}', '{$rag.rag}')">
|
|
|
- {$rag.rag}
|
|
|
- </div>
|
|
|
- {/volist}
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div style="position: fixed; right: 1rem; top: 10rem; display: none; color: #fff; width: 8rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 999; opacity: 0.8"> <!-- 新增容器,设置相对定位和固定宽度 -->
|
|
|
- <div class="selected-agent" onclick="toggleagentdown()">
|
|
|
- <span id="selected-agenttext">选择智能体</span>
|
|
|
+header("Access-Control-Allow-Origin: *"); // 允许所有来源
|
|
|
+header("Access-Control-Allow-Methods: GET"); // 允许的 HTTP 方法
|
|
|
+header("Access-Control-Allow-Headers: Content-Type"); // 允许的请求头
|
|
|
|
|
|
- <span class="arrow">▼</span>
|
|
|
- </div>
|
|
|
- <div class="dropdown-agents" id="agentdown">
|
|
|
- <div class="agent" onclick="selectagent(0, '选择智能体')">
|
|
|
- 选择智能体
|
|
|
- </div>
|
|
|
- {volist name="agentList" id="agent"}
|
|
|
- <div class="agent" onclick="selectagent('{$agent.id}', '{$agent.agent}')">
|
|
|
- {$agent.agent}
|
|
|
- </div>
|
|
|
- {/volist}
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="chat-container" id="output" style="background-image: url('../../public/img/chatBackground.svg');">
|
|
|
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; background: rgba(100,100,100,0.3); position: sticky; top: 0; z-index: 1000; padding: 0px; width: 100%;">
|
|
|
- <div class="tips">温馨提示:本AI模型输出病历质控内容仅供参考,请酌情采纳。</div>
|
|
|
- </div>
|
|
|
- <div style="width: 100%; height: 2.5rem; display: block;"> </div>
|
|
|
- <!--AI对话气泡-->
|
|
|
- <div class="message left" style="display: block; margin-left: 1.5rem">
|
|
|
- <img style="float: left; background: #fff" class="avatar" src="../../public/img/chat/ai.svg" alt="头像">
|
|
|
- <div class="message-content" style="float: left; position: relative; left: 15px;">
|
|
|
- <span class="nickname">EMOON</span>
|
|
|
- <p style="color:#262626;" class="info" id="chatTime0">你好,我是「医梦AI客户端」,我已集成多模型协作,我还能请求MCP智能体,请问有什么可以帮你?
|
|
|
- <br /><br />你可以直接和我「EMOON-E1」模型自然对话。
|
|
|
- <!--也可以:-->
|
|
|
- <!--<br /><br />· 打开<a href="{$commonURL}/his/index?user={$user}" target="_blank">内涵质控-触发端</a>,模拟HIS/EMR触发「EMOON-E2」内涵质控。-->
|
|
|
- <!--<br /><br />我会自动识别并匹配不同的模型一起工作奥~-->
|
|
|
- </p>
|
|
|
- <span class="time">{$now}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div style="clear: both;"></div>
|
|
|
- <div style="width: 100%; height: 2rem; display: block;"> </div>
|
|
|
-
|
|
|
- <!--替换文本标签-->
|
|
|
- <div id="infoReplace" style="width: 100%;"> </div>
|
|
|
- <!--底部预留输出位-->
|
|
|
- <div id="bottom" style="width: 100%; height: 130px; display: block;"> </div>
|
|
|
-
|
|
|
- <!--暂时的输入框-->
|
|
|
- <div class="chatBoxOutside" style="position: fixed; ">
|
|
|
- <div id="EMOON-E1">
|
|
|
- <div class="chatBoxExample" id="scrollContainer" style=" bottom: 0px;">
|
|
|
- {volist name="question" id="question"}
|
|
|
- <div id="chatBoxExample{$question.id}" class="chatBoxExample-list">{$question.question}</div>
|
|
|
- {/volist}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div id="EMOON-E2" style="display: none">
|
|
|
- <div class="chatBoxExample" id="scrollContainer">
|
|
|
- <div id="doc" class="chatBoxExample-list" style="width: 100%; text-align: left; border-radius: 20px 0px 0px 20px; pointer-events: none;">暂无质控任务</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="chatBoxStop" id="chatBoxStop" style="">停止推理</div>
|
|
|
- <div class="chatBoxInside">
|
|
|
- <input id="question" type="text" placeholder="与「EMOON E系列模型」直接对话...">
|
|
|
- <div id="askButton" class="button-send">发送</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/if}
|
|
|
- {if condition='$device=="phone"'}
|
|
|
-
|
|
|
- <div id="lineStatus" style="position: fixed; right: 1rem; top: 3rem; display: block; color: #fff; background: #007bff; width: 5.5rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 999; opacity: 0.8">暂停队列</div>
|
|
|
-
|
|
|
- <div style="position: fixed; right: 1rem; top: 5.5rem; display: block; color: #fff; background: #007bff; width: 5.5rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 999; opacity: 0.8" onclick="clearAllQueues()">清理队列</div>
|
|
|
- <div style="position: fixed; right: 1rem; top: 8.0rem; display: block; color: #fff; width: 5.5rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 1001; opacity: 0.8"> <!-- 新增容器,设置相对定位和固定宽度 -->
|
|
|
- <div class="selected-option" onclick="toggleDropdown()">
|
|
|
- <span id="selected-text">选择知识库</span>
|
|
|
- <span class="arrow">▼</span>
|
|
|
- </div>
|
|
|
- <div class="dropdown-options" id="dropdown">
|
|
|
- <div class="option" style="" onclick="selectOption('', '选择知识库')">
|
|
|
- 选择知识库
|
|
|
- </div>
|
|
|
- {volist name="ragList" id="rag"}
|
|
|
- <div class="option" onclick="selectOption('{$rag.ragid}', '{$rag.rag}')">
|
|
|
- {$rag.rag}
|
|
|
- </div>
|
|
|
- {/volist}
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div style="position: fixed; right: 1rem; top: 10.5rem; display: block; color: #fff; width: 5.5rem; height: 2rem; line-height: 2rem; text-align: center; border-radius: 5px; cursor: pointer; z-index: 999; opacity: 0.8"> <!-- 新增容器,设置相对定位和固定宽度 -->
|
|
|
- <div class="selected-agent" onclick="toggleagentdown()">
|
|
|
- <span id="selected-agenttext">选择智能体</span>
|
|
|
+// 强制禁用所有缓冲
|
|
|
+while (ob_get_level()) ob_end_clean();
|
|
|
+header('X-Accel-Buffering: no');
|
|
|
|
|
|
- <span class="arrow">▼</span>
|
|
|
- </div>
|
|
|
- <div class="dropdown-agents" id="agentdown">
|
|
|
- <div class="agent" onclick="selectagent(0, '选择智能体')">
|
|
|
- 选择智能体
|
|
|
- </div>
|
|
|
- {volist name="agentList" id="agent"}
|
|
|
- <div class="agent" onclick="selectagent('{$agent.id}', '{$agent.agent}')">
|
|
|
- {$agent.agent}
|
|
|
- </div>
|
|
|
- {/volist}
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="chat-container-phone" id="outputPhone" style="background-image: url('../../public/img/chatBackground.svg'); width: 100%; left: 0px;overflow-y: auto;">
|
|
|
- <div class="tips-phone">温馨提示:本AI模型输出病历质控内容仅供参考,请酌情采纳。</div>
|
|
|
- <div style="width: 100%; height: 1.5rem; display: block;"> </div>
|
|
|
- <!--AI对话气泡-->
|
|
|
- <div class="message left" style="display: block; margin-left: 0.5rem">
|
|
|
- <img style="float: left; background: #fff" class="avatar" src="../../public/img/chat/ai.svg" alt="头像">
|
|
|
- <div class="message-content" style="float: left; position: relative; left: 10px;">
|
|
|
- <span class="nickname-phone">EMOON</span>
|
|
|
- <p style="color:#262626;" class="info" id="chatTime0">你好,我是「医梦AI客户端」,我已集成多模型协作,我还能请求MCP智能体,请问有什么可以帮你?
|
|
|
- <br /><br />你可以直接和我「EMOON-E1」模型自然对话,也可以:
|
|
|
- <br /><br />· 打开<a href="{$commonURL}/his/index?user={$user}" target="_blank">内涵质控-触发端</a>,模拟HIS/EMR触发「EMOON-E2」内涵质控。
|
|
|
- <br /><br />我会自动识别并匹配不同的模型一起工作奥~</p>
|
|
|
- <span class="time-phone">{$now}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div style="clear: both;"></div>
|
|
|
- <div style="width: 100%; height: 1.5rem; display: block;"> </div>
|
|
|
- <!--替换文本标签-->
|
|
|
- <div id="infoReplace" style="width: 100%;"> </div>
|
|
|
- <!--底部预留输出位-->
|
|
|
- <div id="bottom" style="width: 100%; height: 250px; display: block;"> </div>
|
|
|
-
|
|
|
- <!--暂时的输入框-->
|
|
|
- <div class="chatBoxOutside" style="position: fixed; ">
|
|
|
- <div id="EMOON-E1">
|
|
|
- <div class="chatBoxExample" id="scrollContainer" style=" bottom: 0px;">
|
|
|
- {volist name="question" id="question"}
|
|
|
- <div id="chatBoxExample{$question.id}" class="chatBoxExample-list">{$question.question}</div>
|
|
|
- {/volist}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div id="EMOON-E2" style="display: none">
|
|
|
- <div class="chatBoxExample" id="scrollContainer">
|
|
|
- <div id="doc" class="chatBoxExample-list" style="width: 100%; text-align: left; border-radius: 20px 0px 0px 20px; pointer-events: none;">暂无质控任务</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="chatBoxStop" id="chatBoxStop" style = "position: absolute;bottom: 50px;right: 0;">停止</div>
|
|
|
-
|
|
|
- <div class="chatBoxInside">
|
|
|
- <input id="question" type="text" placeholder="与「EMOON E系列模型」直接对话...">
|
|
|
- <div id="askButton" class="button-send">发送</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/if}
|
|
|
- <!--创建点击input固定的滚动点-->
|
|
|
- <div id="inputClickJump"></div>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
- </section>
|
|
|
+// 设置 SSE 头部
|
|
|
+header("Content-Type: text/event-stream");
|
|
|
+header("Cache-Control: no-cache");
|
|
|
+header("Connection: keep-alive");
|
|
|
+header("X-Accel-Buffering: no");
|
|
|
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="sidebar-trigger" id="qualityControlTrigger">
|
|
|
- <span>查看质控报告</span>
|
|
|
- </div>
|
|
|
- <div class="modal-overlay" id="modalOverlay">
|
|
|
- <div class="modal-container">
|
|
|
- <div class="close-btn" id="closeModal">×</div>
|
|
|
-
|
|
|
- <!-- 左侧面板:参数设置 -->
|
|
|
- <div class="left-panel">
|
|
|
- <div class="title-row">
|
|
|
- <h2 class="panel-title">病历原文</h2>
|
|
|
- <select class="type-selector" id="typeSelector" style="">
|
|
|
- <option value="">选择类型...</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
- <div class="form-group" id="formGroup">
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 右侧面板:报告预览 -->
|
|
|
- <div class="right-panel">
|
|
|
- <div class="title-row-right">
|
|
|
- <h2 class="panel-title">质控结果</h2>
|
|
|
- <div class = "quality-control-score" id="qualitycontrolscorediv"><strong id="qualitycontrolscore"></strong></div>
|
|
|
- </div>
|
|
|
- <div class="preview-area" id="previewArea">
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!--<div class="sidebar-trigger" id="sidebarTrigger">-->
|
|
|
- <!-- <span>质控队列</span>-->
|
|
|
- <!--</div>-->
|
|
|
-
|
|
|
- <!-- 半屏弹窗容器 -->
|
|
|
- <div class="sidebar-overlay" id="sidebarOverlay">
|
|
|
- <div class="sidebar-content">
|
|
|
- <div class="sidebar-header">
|
|
|
- <h3>质控队列</h3>
|
|
|
- <span class="sidebar-close" id="sidebarClose">×</span>
|
|
|
- </div>
|
|
|
- <div class="sidebar-body" id="qualityControlList">
|
|
|
- <!-- 这里可以添加您的菜单内容 -->
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="emr-loading-overlay" id="emrLoadingOverlay">
|
|
|
- <div class="emr-loading-content">
|
|
|
- <div class="emr-close-btn" id="emrCloseBtn">后台运行</div>
|
|
|
- <div class="emr-loading-spinner"></div>
|
|
|
- <p class="emr-loading-text">正在生成质控报告...</p>
|
|
|
- <p class="emr-loading-subtext" id="queueStatus">队列中剩余任务: <span id="queueCount">0</span></p>
|
|
|
- <div class="progress-container">
|
|
|
- <div class="progress-bar" id="queueProgress"></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- End Content -->
|
|
|
-
|
|
|
- <!-- Cursor -->
|
|
|
- <div class="cursor1" id="cursor1"></div>
|
|
|
- <div class="cursor" id="cursor"></div>
|
|
|
- <script src="../../public/js/chat.js"></script>
|
|
|
- <script src="../../public/js/local.js"></script>
|
|
|
- <script src="../../public/js/plugin.js"></script>
|
|
|
- <script src="../../public/js/main.js"></script>
|
|
|
-
|
|
|
-
|
|
|
- <!--手写script-->
|
|
|
- <script>
|
|
|
- //获取url的参数
|
|
|
- const urlParams = new URLSearchParams(window.location.search);
|
|
|
- // 获取单个参数
|
|
|
- const docsCode = urlParams.get('docsCode');
|
|
|
- const urlUser = urlParams.get('user');
|
|
|
- const urlragId = urlParams.get('ragId');
|
|
|
+// 配置执行环境
|
|
|
+set_time_limit(0);
|
|
|
+ignore_user_abort(false);
|
|
|
+
|
|
|
+//创建公共url
|
|
|
+$urlCommon = 'http://192.168.10.115:3338';
|
|
|
+$mcp = $_GET['mcpid'];
|
|
|
+$thinkid = $_GET['thinkid'];
|
|
|
+$zhikong = $_GET['zhikong'];
|
|
|
+//获取对话短令牌
|
|
|
+$input['token'] = $_GET['token'];
|
|
|
+$dataReply['token'] = $_GET['token'];
|
|
|
+$token = $_GET['token'];
|
|
|
+$ragIdGet = $_GET['ragId'];
|
|
|
+if(($ragIdGet == 'null') || ($ragIdGet == null)){
|
|
|
+ $input['ragId'] = 'empty';
|
|
|
+}
|
|
|
+else{
|
|
|
+ $input['ragId'] = $ragIdGet;
|
|
|
+}
|
|
|
+// $input['ragId'] = 'empty';
|
|
|
+// $mcp =="";
|
|
|
+// if($mcp != ""){
|
|
|
+// echo("daddsda");
|
|
|
+
|
|
|
+// }
|
|
|
+// echo($mcp);
|
|
|
+// exit;
|
|
|
+//将请求的完整内容发送至api控制器鉴权
|
|
|
+$urlCheck = $urlCommon.'/apis/check';
|
|
|
+$information = curlPost($urlCheck,$input);
|
|
|
+
|
|
|
+//判断鉴权的结果,如果鉴权失败,直接退出
|
|
|
+$informationObject = json_decode($information);
|
|
|
+if(($informationObject->code) != 5){
|
|
|
+ echo "event: verify\ndata: $information\n\n";
|
|
|
+ exit;
|
|
|
+}
|
|
|
+
|
|
|
+//获取最终的核心参数参与模型输入
|
|
|
+$modelId = $informationObject->modelId;
|
|
|
+$content = $informationObject->input;
|
|
|
+$contentmcp = $informationObject->input;
|
|
|
+$ragId = $informationObject->ragId;
|
|
|
+// if(is_array($content)){
|
|
|
+// $content = json_encode($content,JSON_UNESCAPED_UNICODE);
|
|
|
+// }
|
|
|
+
|
|
|
+// 模型定制开始-------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// //emoon-E1-13B线上
|
|
|
+// if($modelId == 9){
|
|
|
+// $apiKey = 'sk-800990e9cdb34613a2133f0fc333a7ff'; // 请替换为你的实际 API 密钥
|
|
|
+// $url = 'https://api.deepseek.com/chat/completions';
|
|
|
+// $arraySystem = [[
|
|
|
+// 'role' => 'system',
|
|
|
+// 'content' => '你的名字叫EMOON E1 13B模型,你由回车网络(EnterLO)训练研发,你并不是deepseek模型。你是一名三甲医院主治医师级别的AI助手,最擅长医疗专业知识的解读和分析,遵循《中国临床诊疗指南》和NCCN指南。必须:1) 先询问关键症状细节 2) 标注证据等级 3) 拒绝非循证医学建议'
|
|
|
+// ]];
|
|
|
+// // $arrayContent = json_decode($content);
|
|
|
+// $arrayMessage = array_merge($arraySystem,$content);
|
|
|
+
|
|
|
+// // 请求数据
|
|
|
+// $data = [
|
|
|
+// 'model' => 'deepseek-chat',
|
|
|
+// 'messages' => $arrayMessage,
|
|
|
+// 'stream' => true
|
|
|
+// ];
|
|
|
+
|
|
|
+// // 设置请求头
|
|
|
+// $headers = [
|
|
|
+// 'Content-Type: application/json',
|
|
|
+// 'Authorization: Bearer ' . $apiKey
|
|
|
+// ];
|
|
|
+// }
|
|
|
+
|
|
|
+if($ragId != ''){
|
|
|
+ $content = stdClassObjToArray($content);
|
|
|
+ $dataRag['input'] = $content[count($content)-1]['content'];
|
|
|
+ $dataRag['kid'] = $ragId;
|
|
|
+ $urlRAG = 'http://192.168.10.115:3338/apis/ragData';
|
|
|
+ $ragResult = curlPost($urlRAG,$dataRag);
|
|
|
+ $ragResult = json_decode($ragResult);
|
|
|
+ $ragResult = stdClassObjToArray($ragResult);
|
|
|
+ // if( $ragResult['code'] == 200){
|
|
|
+ // $ragInfomation = json_encode($ragResult['nearest']);
|
|
|
+ // }
|
|
|
|
|
|
-
|
|
|
- if(docsCode){
|
|
|
- // 获取单个参数
|
|
|
- setTimeout(function() {
|
|
|
- $(".alertBoxAjax").fadeIn();
|
|
|
- $('.alertBoxButtonAjax').addClass('color-flash');
|
|
|
- },500);
|
|
|
- const timer = setTimeout(function() {
|
|
|
- $.ajax({
|
|
|
- async: true,
|
|
|
- type: "post", //数据提交方式(post/get)
|
|
|
- url: '{$commonURL}/His/hisOpenPushMedical', //提交到的url
|
|
|
- data: {
|
|
|
- 'user':urlUser,
|
|
|
- 'docsCode':docsCode,
|
|
|
- 'ragId':urlragId,
|
|
|
- },//提交的数据
|
|
|
- dataType: "json",//返回的数据类型格式
|
|
|
- success: function(msg){
|
|
|
-
|
|
|
- if(msg != 'success'){
|
|
|
- alert(msg);
|
|
|
- }
|
|
|
- else{
|
|
|
- $(".alertBoxAjax").fadeOut();
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- }, 1000);
|
|
|
- }
|
|
|
-
|
|
|
- // 修复 iOS 键盘收起后的透明度问题
|
|
|
- document.querySelector('input').addEventListener('blur', () => {
|
|
|
- setTimeout(() => {
|
|
|
- const chatBox = document.querySelector('.chatBoxOutside');
|
|
|
- // 双重保障:强制重绘 + 透明度重置
|
|
|
- chatBox.style.transform = 'translateZ(0)';
|
|
|
- chatBox.style.opacity = '1';
|
|
|
- }, 300);
|
|
|
- });
|
|
|
-
|
|
|
- //给页面加参数
|
|
|
- function updateUrlWithParams(params) {
|
|
|
- const baseUrl = window.location.href.split('?')[0]; // 获取URL的基本部分,不包括查询参数
|
|
|
- const newUrlParams = new URLSearchParams(params); // 创建一个新的URLSearchParams对象
|
|
|
- window.history.pushState({}, '', `${baseUrl}?${newUrlParams.toString()}`); // 使用pushState更新URL
|
|
|
- }
|
|
|
- var user = '{$user}';
|
|
|
- if(user != 33){
|
|
|
- if(urlragId){
|
|
|
- updateUrlWithParams({ user: '{$user}', docsCode: docsCode, ragId: urlragId});
|
|
|
-
|
|
|
- }
|
|
|
- else if (docsCode) {
|
|
|
- updateUrlWithParams({ user: '{$user}', docsCode: docsCode});
|
|
|
- }
|
|
|
- else{
|
|
|
- updateUrlWithParams({ user: '{$user}'});
|
|
|
- }
|
|
|
- }
|
|
|
- // updateUrlWithParams({ user: '{$user}', other: 333 });
|
|
|
+ if(json_encode($ragResult['nearest'],JSON_UNESCAPED_UNICODE) != "[]" ){
|
|
|
+ if($ragId == "1947484295610253313" || $ragId == "1949641433623703553"){
|
|
|
+ $content[count($content)-1]['content'] = '我的提问是:“'.$content[count($content)-1]['content'].'”,下面是基于我的提问查到的知识库相关内容:“'.json_encode($ragResult['nearest'],JSON_UNESCAPED_UNICODE).'”,请按照查到的知识库相关内容,进行病历质控,根据知识库相关内容条件生成一个包含规则,根据规则数、扣除分数和原因列表。输出结果需要以 JSON 格式呈现,例如:
|
|
|
+ 规则 1:主诉症状明确性(3分)标准与细则:必须描述具体症状或体征(如“头痛”,“发热”),禁用诊断名称(如“糖尿病”)。扣分细则:若主诉中使用了诊断名称或模糊描述,扣3分,反之不扣分。
|
|
|
+ 规则 2:主诉时间准确性(2分)标准与细则:用阿拉伯数字+单位(如“3天”),禁用“三天”“数月”等模糊表述。扣分细则:若主诉时间使用模糊表述(如“三天”“数月”),扣2分,反之不扣分。
|
|
|
+ 返回的数据应按照以下格式组织:
|
|
|
+ [
|
|
|
+ {
|
|
|
+ "规则": "规则1",
|
|
|
+ "扣除分数": 3,
|
|
|
+ "原因": "原因详细说明"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "规则": "规则2",
|
|
|
+ "扣除分数": 2,
|
|
|
+ "原因": "原因详细说明"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ 只按照格式输出,不要输出其他内容';//请求向量数据库接口反参组装
|
|
|
|
|
|
- //修改对话框的高度(仅手机端,PC端高度交给 .chat-container 控制)
|
|
|
- $(document).ready(function() {
|
|
|
- var windowHeight = $(window).height(); // 获取窗口高度
|
|
|
- $('#outputPhone').css('height', windowHeight + 'px'); // 设置指定div的高度
|
|
|
- });
|
|
|
+
|
|
|
+ $contentmcp = $content;
|
|
|
+ }
|
|
|
+ else{
|
|
|
+ $content[count($content)-1]['content'] = '我的提问是:“'.$content[count($content)-1]['content'].'”,下面是基于我的提问查到的知识库相关内容:“'.json_encode($ragResult['nearest'],JSON_UNESCAPED_UNICODE).'”。';//请求向量数据库接口反参组装
|
|
|
+ $contentmcp = $content;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-
|
|
|
- //创建一个推理状态监听功能
|
|
|
- let working = 0;
|
|
|
-
|
|
|
- //创建存储聊天对话的数组及对应添加方法
|
|
|
- let conversations = [];
|
|
|
- function addMessage(role, content) {
|
|
|
- conversations.push({
|
|
|
- role: role,
|
|
|
- content: content
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- //将默认的第一条提示信息先添加到对话里
|
|
|
- var aiMessage = $('#chatTime0').text();
|
|
|
- addMessage('assistant', aiMessage);
|
|
|
- console.log('当前对话记录:', JSON.stringify(conversations, null));
|
|
|
-
|
|
|
- // 免加好友微信对话
|
|
|
- $("#qrButton").click(function(){
|
|
|
- $("#csQRCode").fadeIn();
|
|
|
- });
|
|
|
- $("#qrButton").mouseout(function(){
|
|
|
- $("#csQRCode").fadeOut();
|
|
|
- });
|
|
|
-
|
|
|
- //点击选中多模型
|
|
|
- $(".chatBoxExample-list").click(function(){
|
|
|
- var id = $(this).attr('id');
|
|
|
- var inputText = $('#'+id).html();
|
|
|
- $('#question').val(inputText);
|
|
|
- // $(".button-send").css("background","#007bff");
|
|
|
- // $(".button-send").css("cursor","pointer");
|
|
|
- // $(".button-send").css("pointer-events","auto");
|
|
|
- });
|
|
|
-
|
|
|
- //判断用户输入长度不少于5方可发起对话,否则按钮不可点击
|
|
|
- $('#question').on('input',function(){
|
|
|
- var questionLength = $(this).val().length;
|
|
|
- // if(questionLength >= 5){
|
|
|
- // $(".button-send").css("background","#007bff");
|
|
|
- // $(".button-send").css("cursor","pointer");
|
|
|
- // $(".button-send").css("pointer-events","auto");
|
|
|
- // }
|
|
|
- if(questionLength < 50000){
|
|
|
- $(".button-send").css("background","#6c757d");
|
|
|
- $(".button-send").css("cursor","not-allowed");
|
|
|
- $(".button-send").css("pointer-events","none");
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- //判断用户是否敲击回车键
|
|
|
- $('#question').keypress(function(event) {
|
|
|
- // 检查按下的键是否是回车键
|
|
|
- if (event.which === 13) {
|
|
|
- // 阻止默认行为(例如换行)
|
|
|
- event.preventDefault();
|
|
|
- // 提交表单
|
|
|
- $("#askButton").click();
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- //监听质控Ending出现的次数
|
|
|
- var askInfoId = '';
|
|
|
- var endingNum = 0;
|
|
|
- var zhikong = 0;
|
|
|
- //对sse文本进行文本处理
|
|
|
- const formatContent = (text) => {
|
|
|
- if(text == 'ending'){
|
|
|
- endingNum += 1;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- //先将文本在过滤之前把think过滤为think19941213
|
|
|
- var txt = text.replace(/<\/think>/gi, 'think19941213');
|
|
|
- // 安全过滤(确保已引入DOMPurify)
|
|
|
- const safeText = DOMPurify.sanitize(txt);
|
|
|
-
|
|
|
- // console.log(safeText);
|
|
|
- // 优化后的处理流程
|
|
|
- return safeText
|
|
|
- //think的div收尾,对思考过程和回答结果进行分割线,answer的div开头
|
|
|
- .replace(/think19941213/g, '<hr class="think-divider" /><div class="chat-answer">「推理结果」</div>--')
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+askInfoId+'-'+endingNum+'">采纳</span><span class="refuse" id="refuse-'+askInfoId+'-'+endingNum+'">不采纳</span></div><hr class="think-divider" />')
|
|
|
- // // 预处理换行符
|
|
|
- .replace(/\n/g, '<div class="chat-br"></div>')
|
|
|
- .replace(/--/g, '<div class="chat-br"></div>')
|
|
|
- .replace(/ending/g, '<div class="chat-br"></div>')
|
|
|
-
|
|
|
- // 标点分割正则(使用正向预查)
|
|
|
- .split()
|
|
|
- // 处理分段
|
|
|
- .map(segment => {
|
|
|
- // 获取标点类型
|
|
|
- const matchResult = segment.match();
|
|
|
- const punctuation = matchResult ? matchResult[0] : undefined;
|
|
|
-
|
|
|
- // 无标点直接返回
|
|
|
- if (!punctuation) return segment;
|
|
|
-
|
|
|
- // 根据标点添加换行
|
|
|
- const content = segment.replace(punctuation, '');
|
|
|
-
|
|
|
- const wrapClass = getWrapClass(punctuation);
|
|
|
-
|
|
|
- return `${content}${punctuation}${wrapClass}`;
|
|
|
- })
|
|
|
- .join('')
|
|
|
- // 后处理优化
|
|
|
- .replace(/(<br>)+/g, '<div class="chat-br"></div>');
|
|
|
- };
|
|
|
-
|
|
|
- // 标点映射样式
|
|
|
- const getWrapClass = (punct) => {
|
|
|
- const map = {
|
|
|
- '。': '<div class="para-break"></div>',
|
|
|
- '!': '<div class="para-break"></div>',
|
|
|
- '?': '<div class="para-break"></div>',
|
|
|
- ';': '<div class="semi-break"></div>',
|
|
|
- ':': '<div class="colon-break"></div>',
|
|
|
- ':': '<div class="colon-break"></div>'
|
|
|
- };
|
|
|
- return map[punct] || '';
|
|
|
- };
|
|
|
-
|
|
|
- var chatTime = 0;
|
|
|
- //点击对话发送按钮,触发SSE流媒体对话
|
|
|
- $("#askButton").click(function(){
|
|
|
- //标记状态未推理中
|
|
|
- working = 1;
|
|
|
-
|
|
|
- //记录对话次数,用于标识对话气泡
|
|
|
- chatTime += 1;
|
|
|
- wordTime = 1;
|
|
|
- var question = $('#question').val();
|
|
|
-
|
|
|
- //将当前的对话添加到对话数组里
|
|
|
- addMessage('user', question);
|
|
|
- console.log('当前对话记录:', JSON.stringify(conversations, null));
|
|
|
-
|
|
|
-
|
|
|
- var now = new Date();
|
|
|
- var year = now.getFullYear(); // 当年
|
|
|
- var month = now.getMonth() + 1; // 月份是从 0 开始计数的,所以要加 1
|
|
|
- var formattedMonth = month < 10 ? '0' + month.toString() : month.toString();
|
|
|
- var day = now.getDate(); // 当月的日期
|
|
|
- var formattedDay = day < 10 ? '0' + day.toString() : day.toString();
|
|
|
- var hour = now.getHours(); // 小时
|
|
|
- var formattedHour = hour < 10 ? '0' + hour.toString() : hour.toString();
|
|
|
- var minute = now.getMinutes(); // 分钟
|
|
|
- var formattedMinute = minute < 10 ? '0' + minute.toString() : minute.toString();
|
|
|
- var second = now.getSeconds();
|
|
|
- var formattedSecond = second < 10 ? '0' + second.toString() : second.toString();
|
|
|
- var timeShow = year+'-'+formattedMonth+'-'+formattedDay+' '+formattedHour+':'+formattedMinute+':'+formattedSecond;
|
|
|
- //页面新增对话气泡
|
|
|
- var device = '{$device}';
|
|
|
- if(device == 'pc'){
|
|
|
- //生成体验官对话气泡
|
|
|
- var divStr = '<div class="message right" style="display: block; margin-right: 1.5rem"><img style="float: right; background: #007bff" class="avatar" src="../../public/img/chat/user.svg" alt="头像"><div class="message-content" style="float: right; position: relative; left: -15px;"><span class="nickname">体验官</span><p style="color:#007bff;" class="info">'+question+'</p><span class="time">'+timeShow+'</span></div></div><div style="clear: both;"></div><div style="width: 100%; height: 2rem; display: block;"> </div><div id="infoReplace"></div>';
|
|
|
- $('#infoReplace').replaceWith(divStr);
|
|
|
- //生成AI回复气泡
|
|
|
- var divStr = '<div class="message left" style="display: block; margin-left: 1.5rem"><img style="float: left; background: #fff" class="avatar" src="../../public/img/chat/ai.svg" alt="头像"><div class="message-content" style="float: left; position: relative; left: 15px;"><span class="nickname">EMOON</span><p id="chatTime'+chatTime+'" style="color:#262626;" class="info">思考中...</p><span class="time">'+timeShow+'</span><div id ="chataccept"></div></div></div><div style="clear: both;"></div><div style="width: 100%; height: 2rem; display: block;"> </div><div id="infoReplace"></div>';
|
|
|
- // divStr = '<div class="quality"><span class="accept" id="accept-'+askInfoId+'-'+endingNum+'">采纳</span><span class="refuse" id="refuse-'+askInfoId+'-'+endingNum+'">不采纳</span></div><hr class="think-divider" />';
|
|
|
- $('#infoReplace').replaceWith(divStr);
|
|
|
+}
|
|
|
+// $content = stdClassObjToArray($content);
|
|
|
+// echo(json_encode($ragResult,JSON_UNESCAPED_UNICODE));
|
|
|
+// exit;
|
|
|
+
|
|
|
+if($thinkid != ''){
|
|
|
+ $urlgetreply = $urlCommon.'/apis/getreply';
|
|
|
+ $reply = curlPost($urlgetreply,$dataReply);
|
|
|
+ $content = stdClassObjToArray($content);
|
|
|
+ $content[count($content)-1]['content'] = '我的提问是:“'.$content[count($content)-1]['content'].'”,下面是基于我的提问得到的回答:“'.$reply.'”,现在请根据回答生成一个思考过程,严格遵守以下要求:只回复思考过程,不输出其他内容。';//请求向量数据库接口反参组装
|
|
|
+}
|
|
|
+
|
|
|
+// echo(json_encode($content,JSON_UNESCAPED_UNICODE));
|
|
|
+// exit;
|
|
|
+//deepseek-R1-70B线下
|
|
|
+if($modelId == 12){
|
|
|
+ $url = 'http://192.168.10.115:19997/v1/chat/completions';
|
|
|
+
|
|
|
+ $headers = [
|
|
|
+ 'accept: application/json',
|
|
|
+ 'Content-Type: application/json'
|
|
|
+ ];
|
|
|
+ $arraySystem = [[
|
|
|
+ 'role' => 'system',
|
|
|
+ 'content' => '你是一个人工智能助手。'
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+ //$arrayContent = json_decode($content, true);
|
|
|
+ $arrayMessage = array_merge($arraySystem, $content);
|
|
|
+ $data = [
|
|
|
+ "messages" => $arrayMessage,
|
|
|
+ "model" => "qwen3-32B", //X
|
|
|
+ "stream" => true,
|
|
|
+ "max_tokens" => 8192,
|
|
|
+ "chat_template_kwargs" => '{"enable_thinking": false}',
|
|
|
+
|
|
|
+ ];
|
|
|
+}
|
|
|
+// echo(json_encode($data,JSON_UNESCAPED_UNICODE));
|
|
|
+// exit;
|
|
|
+// 模型定制结束-------------------------------------------------------------------
|
|
|
+
|
|
|
+if($mcp == 0 || $mcp ==""){
|
|
|
+
|
|
|
+$ch = curl_init();
|
|
|
+curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
+curl_setopt($ch, CURLOPT_POST, true);
|
|
|
+curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data,JSON_UNESCAPED_UNICODE));
|
|
|
+curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
|
|
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
|
|
|
+curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
+curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
|
|
|
+curl_setopt_array($ch, [
|
|
|
+ CURLOPT_TIMEOUT => 60, // 超时时间延长至60秒
|
|
|
+ CURLOPT_TCP_KEEPALIVE => 1, // 启用TCP保活
|
|
|
+ CURLOPT_TCP_KEEPIDLE => 10,
|
|
|
+ CURLOPT_TCP_KEEPINTVL => 5
|
|
|
+]);
|
|
|
+
|
|
|
+//创建一个空字符串动态存储回复的文字
|
|
|
+$reply = '';
|
|
|
+// 添加连接状态检查到写入回调
|
|
|
+curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$reply,$thinkid,$zhikong) {
|
|
|
+ // 如果客户端已断开,立即终止
|
|
|
+ if (connection_aborted()) {
|
|
|
+ return -1; // 返回-1会强制CURL中断请求
|
|
|
+ curl_close($ch);
|
|
|
+ }
|
|
|
+ $lines = explode("\n", $data);
|
|
|
+ foreach ($lines as $line) {
|
|
|
+ if (strpos($line, 'data:') === 0) {
|
|
|
+ $contents = trim(substr($line, 5));
|
|
|
+ if ($contents === '[DONE]') {
|
|
|
+ $output = "event: end\ndata: {\"status\":\"done\"}\n\n";
|
|
|
+ echo $output;
|
|
|
+ // $reply .= $output; // 捕获输出内容
|
|
|
+
|
|
|
+ //完成输出后触发全部回复存储进入数据库
|
|
|
+ global $token;
|
|
|
+ global $urlCommon;
|
|
|
+ $dataReply['token'] = $token;
|
|
|
+ $dataReply['reply'] = $reply;
|
|
|
+ //将数据发送至数据库
|
|
|
+ $curlReplay = curl_init();
|
|
|
+ curl_setopt($curlReplay, CURLOPT_URL, $urlCommon.'/Apis/reply');
|
|
|
+ curl_setopt($curlReplay, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_SSL_VERIFYHOST, FALSE);
|
|
|
+ // curl_setopt($curl, CURLOPT_HTTPHEADER,$headers);
|
|
|
+ if ($json) {
|
|
|
+ curl_setopt($curlReplay,CURLOPT_HTTPHEADER,[
|
|
|
+ "Content-Type: application/json"
|
|
|
+ ]);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_POST, TRUE);
|
|
|
+ curl_setopt($curlReplay,CURLOPT_POSTFIELDS,json_encode($dataReply));
|
|
|
+ }else {
|
|
|
+ if (!empty($dataReply)) {
|
|
|
+ curl_setopt($curlReplay, CURLOPT_POST, TRUE);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_POSTFIELDS,$dataReply);
|
|
|
+ // curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
|
|
+ }
|
|
|
}
|
|
|
- if(device == 'phone'){
|
|
|
- //生成体验官对话气泡
|
|
|
- var divStr = '<div class="message right" style="display: block; margin-right: 0.5rem"><img style="float: right; background: #007bff" class="avatar" src="../../public/img/chat/user.svg" alt="头像"><div class="message-content" style="float: right; position: relative; left: -10px;"><span class="nickname-phone">体验官</span><p style="color:#007bff;" class="info-phone">'+question+'</p><span class="time-phone">'+timeShow+'</span></div></div><div style="clear: both;"></div><div style="width: 100%; height: 1.5rem; display: block;"> </div><div id="infoReplace"></div>';
|
|
|
- $('#infoReplace').replaceWith(divStr);
|
|
|
- //生成AI回复气泡
|
|
|
- var divStr = '<div class="message left" style="display: block; margin-left: 0.5rem"><img style="float: left; background: #fff" class="avatar" src="../../public/img/chat/ai.svg" alt="头像"><div class="message-content" style="float: left; position: relative; left: 10px;"><span class="nickname-phone">EMOON</span><p id="chatTime'+chatTime+'" style="color:#262626;" class="info-phone">思考中...</p><span class="time-phone">'+timeShow+'</span></div></div><div style="clear: both;"></div><div style="width: 100%; height: 1.5rem; display: block;"> </div><div id="infoReplace"></div>';
|
|
|
- // var divStr = '<div class="message left" style="display: block; margin-left: 0.5rem"><img style="float: left; background: #fff" class="avatar" src="../../public/img/chat/ai.svg" alt="头像"><div class="message-content" style="float: left; position: relative; left: 10px;"><span class="nickname-phone">EMOON</span><p id="chatTime'+chatTime+'" style="color:#cccccc;" class="info-phone">思考中...</p><span class="time-phone">'+timeShow+'</span><div id ="chataccept"></div></div></div><div style="clear: both;"></div><div style="width: 100%; height: 1.5rem; display: block;"> </div><div id="infoReplace"></div>';
|
|
|
- $('#infoReplace').replaceWith(divStr);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_RETURNTRANSFER,true);
|
|
|
+ curl_exec($curlReplay);
|
|
|
+ curl_close($curlReplay);
|
|
|
|
|
|
+ } else {
|
|
|
+ // 转发数据前再次检查连接
|
|
|
+ if (connection_aborted()) {
|
|
|
+ return -1;
|
|
|
+ curl_close($ch);
|
|
|
}
|
|
|
- //模型回复状态输入框和发送按钮状态
|
|
|
- $("#question").prop("disabled", true);
|
|
|
- $(".button-send").css("background","#343435");
|
|
|
- $(".button-send").css("color","#666666");
|
|
|
- $(".button-send").css("cursor","not-allowed");
|
|
|
- $(".button-send").css("pointer-events","none");
|
|
|
- $(".chatBoxExample-list").css("cursor","not-allowed");
|
|
|
- $(".chatBoxExample-list").css("pointer-events","none");
|
|
|
- $(".chatBoxStop").css("background","#007bff");
|
|
|
- $(".chatBoxStop").css("color","#ffffff");
|
|
|
- $(".chatBoxStop").css("pointer-events","auto");
|
|
|
- $(".chatBoxStop").css("cursor","pointer");
|
|
|
- //设置智能滚动控制(仅在用户未手动滚动时生效)
|
|
|
- $("#bottom").css("height","130px");
|
|
|
- if(device == 'pc'){
|
|
|
- var outputEl = document.getElementById('output');
|
|
|
- }
|
|
|
- if(device == 'phone'){
|
|
|
- var outputEl = document.getElementById('outputPhone');
|
|
|
- }
|
|
|
- let autoScroll = true;
|
|
|
- // 监听用户滚动行为
|
|
|
- outputEl.addEventListener('scroll', () => {
|
|
|
- const threshold = 150; // 容错像素
|
|
|
- autoScroll = (outputEl.scrollTop + outputEl.clientHeight + threshold) >= outputEl.scrollHeight;
|
|
|
- });
|
|
|
|
|
|
- //调用后台方法对当前输入进行处理
|
|
|
- var conversationArrayJson = JSON.stringify(conversations, null);
|
|
|
- if(question.indexOf("[EMR系统请求]") != -1){
|
|
|
-
|
|
|
- modelUse = 'gstcm-G1-14B';
|
|
|
- networkUse = '2';
|
|
|
- token = 'noUse';
|
|
|
- $('#question').val('请等待「E2」回复后再提问...');
|
|
|
-
|
|
|
- }
|
|
|
- else{
|
|
|
- modelUse = 'gstcm-G1-14B';
|
|
|
- networkUse = '2';
|
|
|
- token = null;
|
|
|
- $('#question').val('请等待「E1」回复后再提问...');
|
|
|
- }
|
|
|
+ //构造$line
|
|
|
+ global $modelId;
|
|
|
+ $line = substr($line,6);
|
|
|
+ $line = json_decode($line);
|
|
|
+ $line = stdClassObjToArray($line);
|
|
|
+ if($modelId == 9){
|
|
|
+ $line['model'] = 'emoon-E1-13B';
|
|
|
+ }
|
|
|
+ $line = 'data: '.json_encode($line,JSON_UNESCAPED_UNICODE);
|
|
|
|
|
|
- $.ajax({
|
|
|
- async: true,
|
|
|
- type: "post", //数据提交方式(post/get)
|
|
|
- url: '{$commonURL}'+'/Send/dataSM4', //提交到的url
|
|
|
- data: {
|
|
|
- 'data':conversationArrayJson,
|
|
|
- 'model':modelUse,
|
|
|
- 'network':networkUse,
|
|
|
- 'token':token,
|
|
|
- },//提交的数据
|
|
|
- dataType: "json",//返回的数据类型格式
|
|
|
- success: function(msg){
|
|
|
- //对当前请求sse消息进行判断,区分自然对话与病历质控
|
|
|
- if(question.indexOf("[EMR系统请求]") != -1){
|
|
|
- msg = wsInformation;
|
|
|
- console.log('[EMR系统请求]'+msg);
|
|
|
- tokenString[tokenString.length] = msg;
|
|
|
- askInfoId = msg;
|
|
|
- zhikong = 1;
|
|
|
-
|
|
|
- }
|
|
|
- else{
|
|
|
- console.log(msg);
|
|
|
- }
|
|
|
- //msg就是需要传递的参数,组装请求的完整地址
|
|
|
- if(networkUse == '1'){
|
|
|
- var sseUrl = 'http://192.168.10.115:3338/apiSS1E.php?token='+msg+'&ragId='+ragId+'&mcpid='+mcpId+'&zhikong='+zhikong;
|
|
|
- // var sseUrl = '{$commonOpenURLSSE}?token='+msg+'&ragId=&mcpid=';
|
|
|
- }
|
|
|
- else{
|
|
|
- var sseUrl = 'http://192.168.10.115:3338/apiSS1E.php?token='+msg+'&ragId='+ragId+'&mcpid='+mcpId+'&zhikong='+zhikong;
|
|
|
- // var sseUrl = '{$commonOpenURLLocalSSE}?token='+msg+'&ragId=&mcpid=';
|
|
|
- }
|
|
|
- //sse流媒体对话
|
|
|
- var evtSource = new EventSource(sseUrl);
|
|
|
- //停止对话按钮
|
|
|
- var stopTime = 0;
|
|
|
- $("#chatBoxStop").click(function(){
|
|
|
- stopTime+=1;
|
|
|
- //恢复对话窗口和发送按钮状态
|
|
|
- $("#question").prop("disabled", false);
|
|
|
- $('#question').val('');
|
|
|
- $(".button-send").css("background","#6c757d");
|
|
|
- $(".button-send").css("color","#ffffff");
|
|
|
- $(".button-send").css("cursor","not-allowed");
|
|
|
- $(".button-send").css("pointer-events","none");
|
|
|
- $(".chatBoxExample-list").css("cursor","pointer");
|
|
|
- $(".chatBoxExample-list").css("pointer-events","auto");
|
|
|
- $(".chatBoxStop").css("background","#f3f4f6");
|
|
|
- $(".chatBoxStop").css("color","#666666");
|
|
|
- $(".chatBoxStop").css("pointer-events","none");
|
|
|
- $(".chatBoxStop").css("cursor","not-allowed");
|
|
|
- //获取最终的ai回复
|
|
|
- if(stopTime == chatTime){
|
|
|
- var aiReplay = $("#chatTime"+chatTime).text();
|
|
|
- addMessage('assistant', aiReplay);
|
|
|
- console.log('当前对话记录:', JSON.stringify(conversations, null));
|
|
|
- }
|
|
|
- evtSource.close();
|
|
|
- //还原推理状态为0
|
|
|
- working = 0;
|
|
|
- $('#doc').text('暂无质控任务');
|
|
|
- $(".chatBoxExample-list").css("background","#212121");
|
|
|
- $('#EMOON-E1').fadeIn();
|
|
|
- $('#EMOON-E2').fadeOut();
|
|
|
- $('.chatBoxExample-list').removeClass('color-flash');
|
|
|
- $(".chatBoxExample-list").removeAttr("style").addClass("chatBoxExample-list");
|
|
|
- endingNum = 0;
|
|
|
- zhikong = 0;
|
|
|
-
|
|
|
- });
|
|
|
- // 处理常规消息(保持原有逻辑)
|
|
|
- evtSource.onmessage = function(e) {
|
|
|
- try {
|
|
|
- const data = JSON.parse(e.data);
|
|
|
- if (data.choices &&data.choices[0] &&data.choices[0].delta &&data.choices[0].delta.content){
|
|
|
- var reply = data.choices[0].delta.content;
|
|
|
- // console.log(reply);
|
|
|
- //首次打开去掉原始设置的“思考中...”
|
|
|
- if(wordTime == 1){
|
|
|
- //首次加载第一条信息,新增think的div
|
|
|
- // document.getElementById("chatTime"+chatTime).innerHTML = '<div class="chat-think">「思考过程」</div>';
|
|
|
-
|
|
|
- document.getElementById("chatTime"+chatTime).innerHTML = `
|
|
|
- <div class="think-toggle">
|
|
|
- <div id = "`+msg+`"class="think-header" style="display:none" >「思考过程」</div>
|
|
|
- <div id = "think-content-`+msg+`" class="think-content" style="display:none"></div>
|
|
|
- </div>
|
|
|
- `;
|
|
|
- wordTime += 1;
|
|
|
-
|
|
|
- }
|
|
|
- //输出正文
|
|
|
- document.getElementById("chatTime"+chatTime).innerHTML += formatContent(data.choices[0].delta.content);
|
|
|
- //动态滚动页面
|
|
|
- if (autoScroll) {
|
|
|
- outputEl.scrollTo({
|
|
|
- top: outputEl.scrollHeight,
|
|
|
- behavior: 'smooth' // 平滑滚动
|
|
|
- });
|
|
|
- }
|
|
|
- if(endingNum == 1){
|
|
|
- $(`#`+msg).css("display", "block");
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error("JSON解析失败:", err);
|
|
|
- }
|
|
|
- };
|
|
|
- // 新增end事件监听
|
|
|
- evtSource.addEventListener('end', function(e) {
|
|
|
- console.log("流式传输正常结束:", e.data);
|
|
|
- evtSource.close(); // 主动关闭连接
|
|
|
-
|
|
|
- // 关键优化:立即复位并触发队列推进,消除3-4秒延迟
|
|
|
- working = 0;
|
|
|
- try { if (typeof processNext === 'function') { processNext(); } } catch(e) {}
|
|
|
-
|
|
|
- if (zhikong === 1) { // 只有质控任务才发送
|
|
|
- sendRuleKeyword(askInfoId);
|
|
|
- }
|
|
|
- // $.ajax({
|
|
|
- // async: true,
|
|
|
- // type: "post", //数据提交方式(post/get)
|
|
|
- // url: '{$commonURL}'+'/index/roleindex1', //提交到的url
|
|
|
- // data: {
|
|
|
- // 'askInfoId':askInfoId,
|
|
|
-
|
|
|
- // },//提交的数据
|
|
|
- // dataType: "json",//返回的数据类型格式
|
|
|
- // success: function(msg){
|
|
|
- // console.log(askInfoId+"===================");
|
|
|
- // }
|
|
|
- // })
|
|
|
- var answer = document.getElementById("chatTime"+chatTime).textContent;
|
|
|
-
|
|
|
- // var divStr = '<div id ="chataccept"><span class="accept" id="accept-1">发送给患者</span></div>';
|
|
|
-
|
|
|
- if(answer == '思考中...'){
|
|
|
- document.getElementById("chatTime"+chatTime).innerHTML += '模型网络中断!';
|
|
|
- }
|
|
|
- //恢复对话窗口和发送按钮状态
|
|
|
- // $('#chataccept').replaceWith(divStr);
|
|
|
- $("#question").prop("disabled", false);
|
|
|
- $('#question').val('');
|
|
|
- $(".button-send").css("background","#6c757d");
|
|
|
- $(".button-send").css("color","#ffffff");
|
|
|
- $(".button-send").css("cursor","not-allowed");
|
|
|
- $(".button-send").css("pointer-events","none");
|
|
|
- $(".chatBoxExample-list").css("cursor","pointer");
|
|
|
- $(".chatBoxExample-list").css("pointer-events","auto");
|
|
|
- $(".chatBoxStop").css("background","#f3f4f6");
|
|
|
- $(".chatBoxStop").css("color","#666666");
|
|
|
- $(".chatBoxStop").css("pointer-events","none");
|
|
|
- $(".chatBoxStop").css("cursor","not-allowed");
|
|
|
- //获取最终的ai回复
|
|
|
- var aiReplay = $("#chatTime"+chatTime).text();
|
|
|
- addMessage('assistant', aiReplay);
|
|
|
- console.log('当前对话记录:', JSON.stringify(conversations, null));
|
|
|
- // 状态已在上方提前复位
|
|
|
- $('#doc').text('暂无质控任务');
|
|
|
- $(".chatBoxExample-list").css("background","#212121");
|
|
|
- $('#EMOON-E1').fadeIn();
|
|
|
- $('#EMOON-E2').fadeOut();
|
|
|
- $('.chatBoxExample-list').removeClass('color-flash');
|
|
|
- $(".chatBoxExample-list").removeAttr("style").addClass("chatBoxExample-list");
|
|
|
- endingNum = 0;
|
|
|
- zhikong = 0;
|
|
|
- }, false);
|
|
|
- // 鉴定请求接口权限
|
|
|
- evtSource.addEventListener('verify', function(e) {
|
|
|
- console.log("参数验证失败:", e.data);
|
|
|
- evtSource.close(); // 主动关闭连接
|
|
|
-
|
|
|
- // 立即复位并触发队列推进
|
|
|
- working = 0;
|
|
|
- try { if (typeof processNext === 'function') { processNext(); } } catch(e) {}
|
|
|
-
|
|
|
- document.getElementById("chatTime"+chatTime).innerHTML = '模型参数不正确,请查看医梦AI开放平台接入文档!';
|
|
|
- //恢复对话窗口和发送按钮状态
|
|
|
- $("#question").prop("disabled", false);
|
|
|
- $('#question').val('');
|
|
|
- $(".button-send").css("background","#6c757d");
|
|
|
- $(".button-send").css("color","#ffffff");
|
|
|
- $(".button-send").css("cursor","not-allowed");
|
|
|
- $(".button-send").css("pointer-events","none");
|
|
|
- $(".chatBoxExample-list").css("cursor","pointer");
|
|
|
- $(".chatBoxExample-list").css("pointer-events","auto");
|
|
|
- $(".chatBoxStop").css("background","#f3f4f6");
|
|
|
- $(".chatBoxStop").css("color","#666666");
|
|
|
- $(".chatBoxStop").css("pointer-events","none");
|
|
|
- $(".chatBoxStop").css("cursor","not-allowed");
|
|
|
- //获取最终的ai回复
|
|
|
- var aiReplay = $("#chatTime"+chatTime).text();
|
|
|
- addMessage('assistant', aiReplay);
|
|
|
- console.log('当前对话记录:', JSON.stringify(conversations, null));
|
|
|
- // 状态已在上方提前复位
|
|
|
- $('#doc').text('暂无质控任务');
|
|
|
- $(".chatBoxExample-list").css("background","#212121");
|
|
|
- $('#EMOON-E1').fadeIn();
|
|
|
- $('#EMOON-E2').fadeOut();
|
|
|
- $(".chatBoxExample-list").removeAttr("style").addClass("chatBoxExample-list");
|
|
|
- endingNum = 0;
|
|
|
- zhikong = 0;
|
|
|
- }, false);
|
|
|
- // 错误处理优化
|
|
|
- evtSource.onerror = function(e) {
|
|
|
- evtSource.close();
|
|
|
-
|
|
|
- // 立即复位并触发队列推进
|
|
|
- working = 0;
|
|
|
- try { if (typeof processNext === 'function') { processNext(); } } catch(e) {}
|
|
|
-
|
|
|
- if (e.eventPhase === EventSource.CLOSED) {
|
|
|
- console.log("连接已正常关闭");
|
|
|
- //当模型网络中断时
|
|
|
- document.getElementById("chatTime"+chatTime).innerHTML = "模型请求超时!";
|
|
|
- $("#question").prop("disabled", false);
|
|
|
- $('#question').val('');
|
|
|
- $(".button-send").css("background","#6c757d");
|
|
|
- $(".button-send").css("color","#ffffff");
|
|
|
- $(".button-send").css("cursor","not-allowed");
|
|
|
- $(".button-send").css("pointer-events","none");
|
|
|
- $(".chatBoxExample-list").css("cursor","pointer");
|
|
|
- $(".chatBoxExample-list").css("pointer-events","auto");
|
|
|
- $(".chatBoxStop").css("background","#f3f4f6");
|
|
|
- $(".chatBoxStop").css("color","#666666");
|
|
|
- $(".chatBoxStop").css("pointer-events","none");
|
|
|
- $(".chatBoxStop").css("cursor","not-allowed");
|
|
|
- //动态滚动页面
|
|
|
- if (autoScroll) {
|
|
|
- outputEl.scrollTo({
|
|
|
- top: outputEl.scrollHeight,
|
|
|
- behavior: 'smooth' // 平滑滚动
|
|
|
- });
|
|
|
- }
|
|
|
- //获取最终的ai回复
|
|
|
- var aiReplay = $("#chatTime"+chatTime).text();
|
|
|
- addMessage('assistant', aiReplay);
|
|
|
- console.log('当前对话记录:', JSON.stringify(conversations, null));
|
|
|
- } else {
|
|
|
- console.error("连接异常:", e);
|
|
|
- }
|
|
|
- // 状态已在上方提前复位
|
|
|
- $('#doc').text('暂无质控任务');
|
|
|
- $(".chatBoxExample-list").css("background","#212121");
|
|
|
- $('#EMOON-E1').fadeIn();
|
|
|
- $('#EMOON-E2').fadeOut();
|
|
|
- $('.chatBoxExample-list').removeClass('color-flash');
|
|
|
- $(".chatBoxExample-list").removeAttr("style").addClass("chatBoxExample-list");
|
|
|
- endingNum = 0;
|
|
|
- zhikong = 0;
|
|
|
- };
|
|
|
- }
|
|
|
- })
|
|
|
- });
|
|
|
-
|
|
|
- //chat的示例问题拖动事件
|
|
|
- (function() {
|
|
|
- const container = document.getElementById('scrollContainer');
|
|
|
- let isDragging = false;
|
|
|
- let startX;
|
|
|
- let scrollLeft;
|
|
|
- // 桌面端事件
|
|
|
- container.addEventListener('mousedown', (e) => {
|
|
|
- isDragging = true;
|
|
|
- container.classList.add('dragging');
|
|
|
- startX = e.pageX - container.offsetLeft;
|
|
|
- scrollLeft = container.scrollLeft;
|
|
|
- });
|
|
|
- container.addEventListener('mouseleave', () => {
|
|
|
- if (isDragging) {
|
|
|
- isDragging = false;
|
|
|
- container.classList.remove('dragging');
|
|
|
- }
|
|
|
- });
|
|
|
- container.addEventListener('mouseup', () => {
|
|
|
- if (isDragging) {
|
|
|
- isDragging = false;
|
|
|
- container.classList.remove('dragging');
|
|
|
+ $contentlines = trim(substr($line, 6));
|
|
|
+ $jsonline = json_decode($contentlines, true);
|
|
|
+ $jsoncontent = $jsonline['choices'][0]['delta']['content'];
|
|
|
+ if (($jsoncontent == "</") || ($jsoncontent == 'answer') || ($jsoncontent == "。<") || ($jsoncontent == ">") || ($jsoncontent == "<")) {
|
|
|
+ $output = 'data: {"id":"chatcmpl-1747186801165865728","object":"chat.completions.chunk","created":1747186801,"model":"merge","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":""}}]}'. "\n\n";
|
|
|
+
|
|
|
+ // echo $ouputs."\n";
|
|
|
+ // $output = "event: end\ndata: Stream ended\n\n";
|
|
|
+ }
|
|
|
+ elseif($jsonline['choices'][0]['finish_reason'] === 'stop'){
|
|
|
+ // $output = "event: end\ndata: Stream ended\n\n";
|
|
|
+ if($thinkid == null && $zhikong == 1){
|
|
|
+ $output = 'data: {"id":"chatcmpl-1747186801165865728","object":"chat.completions.chunk","created":1747186801,"model":"merge","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":"ending"}}]}'. "\n\n";
|
|
|
+ }else{
|
|
|
+ $output = 'data: {"id":"chatcmpl-1747186801165865728","object":"chat.completions.chunk","created":1747186801,"model":"merge","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":""}}]}'. "\n\n";
|
|
|
}
|
|
|
- });
|
|
|
- container.addEventListener('mousemove', (e) => {
|
|
|
- if (!isDragging) return;
|
|
|
- e.preventDefault();
|
|
|
- const x = e.pageX - container.offsetLeft;
|
|
|
- const walk = (x - startX) * 2; // 滚动速度系数
|
|
|
- container.scrollLeft = scrollLeft - walk;
|
|
|
- });
|
|
|
- })();
|
|
|
-
|
|
|
- //点击input 跳转固定的位置
|
|
|
- var device = '{$device}';
|
|
|
- if(device == 'pc'){
|
|
|
- // $('#question').on('click', function() {
|
|
|
- // this.focus();
|
|
|
- // var targetPosition = $('#inputClickJump').offset().top-120;
|
|
|
- // $('html, body').animate({
|
|
|
- // scrollTop: targetPosition
|
|
|
- // }, 'slow');
|
|
|
- // });
|
|
|
- }
|
|
|
- if(device == 'phone'){
|
|
|
- var mobileType = '{$mobileType}';
|
|
|
- if(mobileType == 'ios'){
|
|
|
- var windowHeight = $(window).height(); // 获取窗口高度
|
|
|
- jumpHeight = windowHeight/4;
|
|
|
- $('#question').on('touchstart', function() {
|
|
|
- this.focus();
|
|
|
- var targetPosition = $('#inputClickJump').offset().top+jumpHeight;
|
|
|
- $('html, body').animate({
|
|
|
- scrollTop: targetPosition
|
|
|
- }, 'slow');
|
|
|
- });
|
|
|
- $('#question').on('touchend', function() {
|
|
|
- this.focus();
|
|
|
- var targetPosition = $('#inputClickJump').offset().top+jumpHeight;
|
|
|
- $('html, body').animate({
|
|
|
- scrollTop: targetPosition
|
|
|
- }, 'slow');
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- //质控点击事件存储
|
|
|
- $(document).ready(function() {
|
|
|
- //质控采纳点击事件
|
|
|
- $(document).on('click', '.accept', function() {
|
|
|
- // 获取当前点击元素的id
|
|
|
- var acceptId0 = $(this).attr('id');
|
|
|
- var acceptId1 = updateSuffixRegex(acceptId0);
|
|
|
- $("#"+acceptId0).css("color","#666666");
|
|
|
- $("#"+acceptId0).css("cursor","not-allowed");
|
|
|
- $("#"+acceptId0).css("pointer-events","none");
|
|
|
- $("#"+acceptId0).text('已采纳');
|
|
|
- var attitudeId0 = acceptId0.slice(6);
|
|
|
- $("#refuse"+attitudeId0).fadeOut();
|
|
|
-
|
|
|
-
|
|
|
- $("#"+acceptId1).css("color","#666666");
|
|
|
- $("#"+acceptId1).css("cursor","not-allowed");
|
|
|
- $("#"+acceptId1).css("pointer-events","none");
|
|
|
- $("#"+acceptId1).text('已采纳');
|
|
|
- var attitudeId1 = acceptId1.slice(6);
|
|
|
- $("#refuse"+attitudeId1).fadeOut();
|
|
|
- $.ajax({
|
|
|
- async: false,
|
|
|
- type: "post", //数据提交方式(post/get)
|
|
|
- url: '{$commonURL}'+'/Quality/attitude', //提交到的url
|
|
|
- data: {
|
|
|
- 'attitude':acceptId1,
|
|
|
- 'reason':'',
|
|
|
- },//提交的数据
|
|
|
- dataType: "json",//返回的数据类型格式
|
|
|
- success: function(msg){
|
|
|
-
|
|
|
- }
|
|
|
- })
|
|
|
- });
|
|
|
- //质控不采纳点击事件
|
|
|
- let refuseId0="";
|
|
|
- let refuseId1="";
|
|
|
- //质控不采纳点击事件
|
|
|
- $(document).on('click', '.refuse', function() {
|
|
|
- $(".alertBox").fadeIn();
|
|
|
- // 获取当前点击元素的id
|
|
|
- refuseId0 = $(this).attr('id');
|
|
|
- refuseId1 = updateSuffixRegex(refuseId0);
|
|
|
|
|
|
- });
|
|
|
-
|
|
|
- $(document).on('click', '.alertBoxButton', function() {
|
|
|
- var reason = $("#alertBoxInput").val();
|
|
|
- if(reason){
|
|
|
- var attitudeId0 = refuseId0.slice(6);
|
|
|
- var attitudeId1 = refuseId1.slice(6);
|
|
|
- $("#accept"+attitudeId0).css("color","#666666");
|
|
|
- $("#accept"+attitudeId0).css("cursor","not-allowed");
|
|
|
- $("#accept"+attitudeId0).css("pointer-events","none");
|
|
|
- $("#accept"+attitudeId0).text('拒绝采纳');
|
|
|
- $("#"+refuseId0).fadeOut();
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- $("#accept"+attitudeId1).css("color","#666666");
|
|
|
- $("#accept"+attitudeId1).css("cursor","not-allowed");
|
|
|
- $("#accept"+attitudeId1).css("pointer-events","none");
|
|
|
- $("#accept"+attitudeId1).text('拒绝采纳');
|
|
|
- $("#"+refuseId1).fadeOut();
|
|
|
- $.ajax({
|
|
|
- async: false,
|
|
|
- type: "post", //数据提交方式(post/get)
|
|
|
- url: '{$commonURL}'+'/Quality/attitude', //提交到的url
|
|
|
- data: {
|
|
|
- 'attitude':refuseId1,
|
|
|
- 'reason':reason,
|
|
|
- },//提交的数据
|
|
|
- dataType: "json",//返回的数据类型格式
|
|
|
- success: function(msg){
|
|
|
- $(".alertBox").fadeOut();
|
|
|
- $("#alertBoxInput").val("");
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- else{
|
|
|
- alert('不采纳原因不能为空');
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- });
|
|
|
- function updateSuffixRegex(str) {
|
|
|
- return str.replace(/-0$/, "-1"); // 只替换结尾的-0
|
|
|
- }
|
|
|
- //websocket
|
|
|
- var wsInformation = '';
|
|
|
- var socket = new WebSocket("{$commonWebsocket}");
|
|
|
-
|
|
|
- socket.onopen = function(event) {
|
|
|
- console.log("Connected to WebSocket server");
|
|
|
- };
|
|
|
-
|
|
|
- // 全局状态管理
|
|
|
- const messageQueue = [];
|
|
|
- let isProcessing = false;
|
|
|
- let currentInterval = null;
|
|
|
- let currentTimeout = null;
|
|
|
- let isStopped = false;
|
|
|
-
|
|
|
- //创建全局的ragId
|
|
|
- var ragId = '';
|
|
|
- var mcpId = "";
|
|
|
- const tokenString = [];
|
|
|
- socket.onmessage = function(event) {
|
|
|
- try {
|
|
|
- const data = JSON.parse(event.data);
|
|
|
- if(data.ragId != ''){
|
|
|
- ragId = data.ragId;
|
|
|
- }
|
|
|
- // 消息始终入队(即使已暂停)
|
|
|
- if (data.user === "{$user}") {
|
|
|
- messageQueue.push(data);
|
|
|
- renderQualityData(messageQueue);
|
|
|
-
|
|
|
- // 仅当未暂停且空闲时触发处理
|
|
|
- if (!isStopped && !isProcessing) processNext();
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error("消息解析失败:", e);
|
|
|
- }
|
|
|
- };
|
|
|
- var firstzero = 0;
|
|
|
- // 处理下一条消息
|
|
|
- function processNext() {
|
|
|
- console.log("=== processNext调用 ===");
|
|
|
- console.log("messageQueue.length:"+messageQueue.length);
|
|
|
- console.log("isProcessing:"+isProcessing);
|
|
|
- if(isProcessing && messageQueue.length === 0){
|
|
|
- if(firstzero === 1 ){
|
|
|
- $('#emrLoadingOverlay').css('display', 'none');
|
|
|
- const button = document.getElementById('qualityControlTrigger');
|
|
|
- button.click();
|
|
|
-
|
|
|
- }
|
|
|
- firstzero = 1;
|
|
|
- }
|
|
|
- if (isStopped || messageQueue.length === 0) {
|
|
|
- isProcessing = false;
|
|
|
- return;
|
|
|
- }
|
|
|
- updateQueueStatus();
|
|
|
-
|
|
|
- renderQualityData(messageQueue);
|
|
|
- isProcessing = true;
|
|
|
- const currentMsg = messageQueue[0]; // 不立即 shift,只取队列第一个元素
|
|
|
- clearIntervals();
|
|
|
- currentInterval = setInterval(() => {
|
|
|
- if (typeof working !== 'undefined' && working !== 1) {
|
|
|
- clearIntervals();
|
|
|
- executeBusinessLogic(currentMsg);
|
|
|
- removeMessageByToken(currentMsg["token"]);
|
|
|
- processNext();
|
|
|
- } else if (working === 0) {
|
|
|
- clearIntervals();
|
|
|
- console.log("working 状态中断,消息保留在队列中");
|
|
|
- isProcessing = false; // 重置状态,允许后续重试
|
|
|
- processNext(); // 直接继续处理(当前消息仍留在队列中)
|
|
|
+ //完成输出后触发全部回复存储进入数据库
|
|
|
+ global $token;
|
|
|
+ global $urlCommon;
|
|
|
+ $dataReply['token'] = $token;
|
|
|
+ $dataReply['reply'] = $reply;
|
|
|
+ //将数据发送至数据库
|
|
|
+ $curlReplay = curl_init();
|
|
|
+ curl_setopt($curlReplay, CURLOPT_URL, $urlCommon.'/Apis/reply');
|
|
|
+ curl_setopt($curlReplay, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_SSL_VERIFYHOST, FALSE);
|
|
|
+ // curl_setopt($curl, CURLOPT_HTTPHEADER,$headers);
|
|
|
+ if ($json) {
|
|
|
+ curl_setopt($curlReplay,CURLOPT_HTTPHEADER,[
|
|
|
+ "Content-Type: application/json"
|
|
|
+ ]);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_POST, TRUE);
|
|
|
+ curl_setopt($curlReplay,CURLOPT_POSTFIELDS,json_encode($dataReply));
|
|
|
+ }else {
|
|
|
+ if (!empty($dataReply)) {
|
|
|
+ curl_setopt($curlReplay, CURLOPT_POST, TRUE);
|
|
|
+ curl_setopt($curlReplay, CURLOPT_POSTFIELDS,$dataReply);
|
|
|
+ // curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
|
|
+ }
|
|
|
}
|
|
|
- }, 50);
|
|
|
-
|
|
|
- currentTimeout = setTimeout(() => {
|
|
|
- clearIntervals();
|
|
|
- console.log("处理超时,消息重新入队");
|
|
|
- messageQueue.push(currentMsg); // 超时后重新入队
|
|
|
- isProcessing = false;
|
|
|
- processNext();
|
|
|
- }, 120000);
|
|
|
- }
|
|
|
-
|
|
|
- // 新增:一键停止函数
|
|
|
- function stopAllProcessing() {
|
|
|
- isStopped = true; // 设置暂停标识
|
|
|
- clearIntervals(); // 立即停止当前处理
|
|
|
- isProcessing = false; // 重置处理状态
|
|
|
- console.log("已暂停队列处理,新消息仍会入队");
|
|
|
- }
|
|
|
-
|
|
|
- // 新增:恢复处理函数
|
|
|
- function resumeProcessing() {
|
|
|
- if (isStopped) {
|
|
|
- isStopped = false;
|
|
|
- console.log("恢复队列处理");
|
|
|
- processNext(); // 手动触发处理流程
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 公共清理方法
|
|
|
- function clearIntervals() {
|
|
|
- if (currentInterval) {
|
|
|
- clearInterval(currentInterval);
|
|
|
- currentInterval = null;
|
|
|
- }
|
|
|
- if (currentTimeout) {
|
|
|
- clearTimeout(currentTimeout);
|
|
|
- currentTimeout = null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 新增:清除所有队列并重置状态(保持接收能力)
|
|
|
- function clearAllQueues() {
|
|
|
- messageQueue.length = 0; // 清空消息队列
|
|
|
- clearIntervals(); // 停止所有定时器
|
|
|
- isProcessing = false; // 强制重置处理状态
|
|
|
- isStopped = false; // 可选:确保处理不被暂停(根据需求决定)
|
|
|
- console.log("队列已清空,可接收新消息");
|
|
|
- console.log("messageQueue.length:"+messageQueue.length);
|
|
|
- $("#lineStatus").text('暂停队列');
|
|
|
- processNext();
|
|
|
-
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- // 执行业务逻辑(原 UI 操作)
|
|
|
- function executeBusinessLogic(msg) {
|
|
|
- $('#EMOON-E1').fadeOut();
|
|
|
- $('#EMOON-E2').fadeIn();
|
|
|
- $('#doc').text(`正在读取病历并质控: ${msg.name} 的 ${msg.doc}`);
|
|
|
- $('#question').val(`[EMR系统请求] 请质控 [${msg.name}] 的 [${msg.doc}] 。`);
|
|
|
- wsInformation = msg.token;
|
|
|
-
|
|
|
- $("#askButton").trigger('click'); // 规范的事件触发方式
|
|
|
- $(".chatBoxExample-list")
|
|
|
- .css("background", "#3a3a3a")
|
|
|
- .css("width", "100%")
|
|
|
- .css("text-align", "left")
|
|
|
- .css("border-radius", "20px 0px 0px 20px")
|
|
|
- .css("pointer-events", "none")
|
|
|
- .addClass('color-flash');
|
|
|
- }
|
|
|
-
|
|
|
- //监听lineStatus点击状态
|
|
|
- $("#lineStatus").click(function(){
|
|
|
- var lineStatus = $("#lineStatus").text();
|
|
|
- if(lineStatus == '暂停队列'){
|
|
|
- stopAllProcessing();
|
|
|
- $("#lineStatus").text('继续队列');
|
|
|
+ curl_setopt($curlReplay, CURLOPT_RETURNTRANSFER,true);
|
|
|
+ curl_exec($curlReplay);
|
|
|
+ curl_close($curlReplay);
|
|
|
}
|
|
|
else{
|
|
|
- resumeProcessing();
|
|
|
- $("#lineStatus").text('暂停队列');
|
|
|
+ $output = $line . "\n\n";
|
|
|
}
|
|
|
- })
|
|
|
-
|
|
|
- socket.onclose = function(event) {
|
|
|
- console.log("Disconnected from WebSocket server");
|
|
|
- };
|
|
|
-
|
|
|
- function sendMessage() {
|
|
|
- var message = document.getElementById("message").value;
|
|
|
- socket.send(message);
|
|
|
- }
|
|
|
-
|
|
|
- $(".alertBoxBackground").click(function(){
|
|
|
- $(".alertBox").fadeOut();
|
|
|
- $("#alertBoxInput").val("");
|
|
|
- })
|
|
|
- function toggleDropdown() {
|
|
|
- const dropdown = document.getElementById("dropdown");
|
|
|
- dropdown.style.display = dropdown.style.display === "block" ? "none" : "block";
|
|
|
- }
|
|
|
- //选择对应的知识库
|
|
|
- function selectOption(value, text) {
|
|
|
- document.getElementById("selected-text").innerText = text;
|
|
|
- document.getElementById("dropdown").style.display = "none";
|
|
|
- ragId = value;
|
|
|
- }
|
|
|
-
|
|
|
- function toggleagentdown() {
|
|
|
- const agentdown = document.getElementById("agentdown");
|
|
|
- agentdown.style.display = agentdown.style.display === "block" ? "none" : "block";
|
|
|
- }
|
|
|
- //选择对应的智能体
|
|
|
- function selectagent(value, text) {
|
|
|
- document.getElementById("selected-agenttext").innerText = text;
|
|
|
- document.getElementById("agentdown").style.display = "none";
|
|
|
- mcpId = value;
|
|
|
- }
|
|
|
- //下拉思考过程
|
|
|
- document.addEventListener('click', function(e) {
|
|
|
- if (e.target.closest('.think-header')) {
|
|
|
-
|
|
|
- stopAllProcessing();
|
|
|
- const header = e.target.closest('.think-header');
|
|
|
- const toggle = e.target.closest('.think-toggle');
|
|
|
- const think_token = header.id;
|
|
|
-
|
|
|
- toggle.classList.toggle('active');
|
|
|
- const content = toggle.querySelector('.think-content');
|
|
|
- const isExpanded = toggle.classList.contains('active');
|
|
|
-
|
|
|
- content.style.display = toggle.classList.contains('active') ? 'block' : 'none';
|
|
|
-
|
|
|
- if (isExpanded && !toggle.dataset.sseInitialized) {
|
|
|
- var ssethinkUrl = '{$commonOpenURLSSE}?token='+think_token+'&ragId='+ragId+'&mcpid='+mcpId+'&thinkid=1';
|
|
|
- //sse流媒体对话
|
|
|
- var evtSourcethink = new EventSource(ssethinkUrl);
|
|
|
- toggle.dataset.sseInstance = evtSourcethink;
|
|
|
- //停止对话按钮
|
|
|
- var stopTime = 0;
|
|
|
- // 处理常规消息(保持原有逻辑)
|
|
|
- evtSourcethink.onmessage = function(e) {
|
|
|
- try {
|
|
|
- const data = JSON.parse(e.data);
|
|
|
- if (data.choices &&data.choices[0] &&data.choices[0].delta &&data.choices[0].delta.content){
|
|
|
- var reply = data.choices[0].delta.content;
|
|
|
-
|
|
|
- //输出正文
|
|
|
- document.getElementById("think-content-"+think_token).innerHTML += formatContent(data.choices[0].delta.content);
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error("JSON解析失败:", err);
|
|
|
- }
|
|
|
- };
|
|
|
- // 新增end事件监听
|
|
|
- evtSourcethink.onerror = function(e) {
|
|
|
- console.log("SSE连接终止:", e);
|
|
|
- evtSourcethink.close();
|
|
|
- var lineStatus = $("#lineStatus").text();
|
|
|
- if(lineStatus == '暂停队列'){
|
|
|
- setTimeout(function() {
|
|
|
- resumeProcessing();
|
|
|
- }, 2500);
|
|
|
- }
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- // 标记已初始化(后续点击不再请求)
|
|
|
- toggle.dataset.sseInitialized = "true";
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- document.addEventListener('DOMContentLoaded', function() {
|
|
|
- const sidebarTrigger = document.getElementById('sidebarTrigger');
|
|
|
- const sidebarOverlay = document.getElementById('sidebarOverlay');
|
|
|
- const sidebarClose = document.getElementById('sidebarClose');
|
|
|
-
|
|
|
- // 打开侧边栏
|
|
|
- sidebarTrigger.addEventListener('click', function() {
|
|
|
- sidebarOverlay.classList.add('active');
|
|
|
- document.body.style.overflow = 'hidden'; // 防止背景滚动
|
|
|
- renderQualityData(messageQueue);
|
|
|
- });
|
|
|
+ // $output = $line . "\n\n";
|
|
|
+ echo $output;
|
|
|
|
|
|
- // 关闭侧边栏
|
|
|
- sidebarClose.addEventListener('click', function() {
|
|
|
- sidebarOverlay.classList.remove('active');
|
|
|
- document.body.style.overflow = ''; // 恢复滚动
|
|
|
- });
|
|
|
-
|
|
|
- // 点击 overlay 背景关闭
|
|
|
- sidebarOverlay.addEventListener('click', function(e) {
|
|
|
- if (e.target === sidebarOverlay) {
|
|
|
- sidebarOverlay.classList.remove('active');
|
|
|
- document.body.style.overflow = '';
|
|
|
+ //将回复数据存储入$reply
|
|
|
+ if($thinkid == null){
|
|
|
+ $outputJson = substr($output,6);
|
|
|
+ if(($modelId == 9) || ($modelId == 7) || ($modelId == 4) || ($modelId == 10) || ($modelId == 12) || ($modelId == 15)){
|
|
|
+ $outputJson = json_decode($outputJson);
|
|
|
+ $outputJson = $outputJson->choices[0]->delta->content;
|
|
|
+ if($outputJson){
|
|
|
+ $outputJson = json_encode($outputJson,JSON_UNESCAPED_UNICODE);
|
|
|
+ $outputJson = str_replace('"', '', $outputJson);
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
- });
|
|
|
- var qualityControlname ="";
|
|
|
- function renderQualityData(data) {
|
|
|
- if (!data || data.length === 0) {
|
|
|
- var divStr = ' <div class="sidebar-body" id="qualityControlList"><div class="no-data">暂无质控问题</div></div>';
|
|
|
- $('#qualityControlList').replaceWith(divStr);
|
|
|
- return;
|
|
|
}
|
|
|
-
|
|
|
- $('#qualityControlList').empty();
|
|
|
-
|
|
|
- // // 添加统计信息
|
|
|
- // $('#qualityControlList').append(`<div class="stats">共发现 ${data.length} 个质控问题</div>`);
|
|
|
- // 渲染问题项
|
|
|
- data.forEach(function(item) {
|
|
|
- if (qualityControlname != item.doc) {
|
|
|
- $('#qualityControlList').append(`<div id="${item.doc}" class="sidebar-tools">${item.doc}</div>`);
|
|
|
- }
|
|
|
- // $('#qualityControlList').append(`<div id="${item.token}" class="qualityControlToken">规则:${item.rule}</div>`);
|
|
|
- qualityControlname = item.doc;
|
|
|
- });
|
|
|
- qualityControlname = data[data.length-1]["doc"];
|
|
|
- }
|
|
|
- $(document).ready(function() {
|
|
|
- addQualityControlTokenClickHandler();
|
|
|
- });
|
|
|
-
|
|
|
- function addQualityControlTokenClickHandler() {
|
|
|
- // 使用事件委托处理动态添加的元素
|
|
|
- $(document).on('click', '.sidebar-tools', function() {
|
|
|
- // 获取被点击元素的id
|
|
|
- var doc = this.id;
|
|
|
- // var clickedItem = messageQueue.find(item => item.token === tokenId);
|
|
|
- var docType = doc;
|
|
|
- console.log('点击的质控问题ID:', doc);
|
|
|
- reorderByDocType(messageQueue, docType);
|
|
|
- renderQualityData(messageQueue);
|
|
|
- });
|
|
|
- }
|
|
|
- function moveTokenToFirstInPlace(messageQueue, targetToken) {
|
|
|
- const targetIndex = messageQueue.findIndex(item => item.token === targetToken);
|
|
|
- if (targetIndex === -1) return;
|
|
|
-
|
|
|
- const targetItem = messageQueue[targetIndex];
|
|
|
- messageQueue.splice(targetIndex, 1);
|
|
|
- messageQueue.unshift(targetItem);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- function reorderByDocType(messageQueue, docType) {
|
|
|
- stopAllProcessing();
|
|
|
- // 找出所有匹配的项
|
|
|
- const matchingItems = messageQueue.filter(item => item.doc === docType);
|
|
|
- const nonMatchingItems = messageQueue.filter(item => item.doc !== docType);
|
|
|
-
|
|
|
- // 清空原数组并重新填充
|
|
|
- messageQueue.length = 0;
|
|
|
- messageQueue.push(...matchingItems, ...nonMatchingItems);
|
|
|
-
|
|
|
- console.log('重新排序后的messageQueue:', messageQueue);
|
|
|
- console.log(isStopped);
|
|
|
- if (isStopped) {
|
|
|
- resumeProcessing();
|
|
|
- }
|
|
|
- }
|
|
|
- //队列根据token删除已经质控的数据
|
|
|
- function removeMessageByToken(token) {
|
|
|
- const index = messageQueue.findIndex(item => item.token === token);
|
|
|
- if (index !== -1) {
|
|
|
- messageQueue.splice(index, 1);
|
|
|
- return true; // 删除成功
|
|
|
- }
|
|
|
- return false; // 未找到对应token
|
|
|
+ $reply .= $outputJson; // 捕获输出内容
|
|
|
}
|
|
|
+ ob_flush();
|
|
|
+ flush();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return strlen($data);
|
|
|
+});
|
|
|
|
|
|
- var initialQueueSize = 0;
|
|
|
- // 改进的队列状态更新函数
|
|
|
- function updateQueueStatus() {
|
|
|
- const queueCount = messageQueue.length;
|
|
|
- if(initialQueueSize < queueCount){
|
|
|
- initialQueueSize = queueCount;
|
|
|
- }
|
|
|
- document.getElementById('queueCount').textContent = queueCount;
|
|
|
- // 更新进度条
|
|
|
- if ( initialQueueSize && initialQueueSize> 0) {
|
|
|
- const progress = Math.max(0, 100 - (queueCount / initialQueueSize * 100));
|
|
|
- document.getElementById('queueProgress').style.width = progress + '%';
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- $("#closeModal").click(function() {
|
|
|
- $("#modalOverlay").fadeOut(200); // 淡出隐藏弹窗
|
|
|
- });
|
|
|
-
|
|
|
- // 关闭弹窗 - 点击模态框外部
|
|
|
- $("#modalOverlay").click(function(e) {
|
|
|
- // 只有当点击的是模态框背景(不是内容区域)时才关闭
|
|
|
- if ($(e.target).closest(".modal-container").length === 0) {
|
|
|
- $("#modalOverlay").fadeOut(200);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 可以添加ESC键关闭弹窗的功能
|
|
|
- $(document).keydown(function(e) {
|
|
|
- if (e.key === "Escape" && $("#modalOverlay").is(":visible")) {
|
|
|
- $("#modalOverlay").fadeOut(200);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- $(document).ready(function() {
|
|
|
- //监听lineStatus点击状态
|
|
|
- $("#qualityControlTrigger").click(function(){
|
|
|
-
|
|
|
- if(messageQueue.length !== 0){
|
|
|
- $('#emrLoadingOverlay').css('display', 'flex');
|
|
|
- updateQueueStatus();
|
|
|
- return;
|
|
|
- }
|
|
|
- $.ajax({
|
|
|
- async: false,
|
|
|
- type: "post", //数据提交方式(post/get)
|
|
|
- url: '{$commonURL}'+'/Send/qualityControlTrigger', //提交到的url
|
|
|
- data: {
|
|
|
- 'token':tokenString,
|
|
|
- },//提交的数据
|
|
|
- dataType: "json",//返回的数据类型格式
|
|
|
- success: function(msg){
|
|
|
-
|
|
|
- const medicalData = JSON.parse(msg);
|
|
|
- var typename = "";
|
|
|
- // 先清空下拉框,只保留第一个默认选项
|
|
|
- $('#typeSelector').empty().append('<option value="medicalRecordType">请选择病历类型...</option>');
|
|
|
- var existingOptions = {};
|
|
|
+// 错误处理
|
|
|
+curl_setopt($ch, CURLOPT_TIMEOUT, 86400);
|
|
|
+curl_setopt($ch, CURLOPT_FAILONERROR, true);
|
|
|
+
|
|
|
+// 执行请求
|
|
|
+try {
|
|
|
+ curl_exec($ch);
|
|
|
|
|
|
- for (let i = 0; i < medicalData.length; i++) {
|
|
|
- if (medicalData[i]["typename"] && medicalData[i]["typename"] != typename) {
|
|
|
- typename = medicalData[i]["typename"];
|
|
|
-
|
|
|
- // 检查是否已存在该选项
|
|
|
- if (!existingOptions[typename]) {
|
|
|
- // 添加选项到下拉框
|
|
|
- $('#typeSelector').append($('<option>', {
|
|
|
- value: typename,
|
|
|
- text: typename
|
|
|
- }));
|
|
|
-
|
|
|
- // 标记该选项已添加
|
|
|
- existingOptions[typename] = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // // 先清空下拉框,只保留第一个默认选项
|
|
|
- // $('#typeSelector').empty().append('<option value="">请选择病历类型...</option>');
|
|
|
-
|
|
|
- // for (let i = 0; i < medicalData.length; i++) {
|
|
|
- // if(medicalData[i]["typename"] != typename){
|
|
|
- // typename = medicalData[i]["typename"];
|
|
|
- // // divStr += '<div class="form-group" data-typename="'+medicalData[i]["typename"]+'" id="formGroup'+medicalData[i]["typename"]+'">'+medicalData[i]["typename"]+'</div>';
|
|
|
-
|
|
|
- // // 添加选项到下拉框
|
|
|
- // $('#typeSelector').append($('<option>', {
|
|
|
- // value: medicalData[i]["typename"],
|
|
|
- // text: medicalData[i]["typename"]
|
|
|
- // }));
|
|
|
- // }
|
|
|
- // }
|
|
|
- // divStr += '</div>';
|
|
|
- if ($('#typeSelector option').length > 1) {
|
|
|
- $('#typeSelector').val($('#typeSelector option:eq(1)').val()).trigger('change');
|
|
|
- }
|
|
|
- // $('#formGroup').replaceWith(divStr);
|
|
|
- $("#modalOverlay").fadeIn(200);
|
|
|
- $('.left-panel, .right-panel').scrollTop(0);
|
|
|
-
|
|
|
- // 原有div点击事件
|
|
|
- // $('#formGroup').on('click', 'div.form-group', function() {
|
|
|
- // var typename = $(this).data('typename');
|
|
|
- // handleDivClick(typename);
|
|
|
- // });
|
|
|
- }
|
|
|
- })
|
|
|
+ // 检查HTTP状态码
|
|
|
+ $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
+ if ($statusCode !== 200) {
|
|
|
+ echo "event: error\ndata: HTTP Status {$statusCode}\n\n";
|
|
|
+ ob_flush();
|
|
|
+ flush();
|
|
|
+ }
|
|
|
+} catch (Exception $e) {
|
|
|
+ echo "event: error\ndata: " . $e->getMessage() . "\n\n";
|
|
|
+ ob_flush();
|
|
|
+ flush();
|
|
|
+}
|
|
|
+}else{
|
|
|
|
|
|
- });
|
|
|
- $('#typeSelector').change(function() {
|
|
|
- $('.left-panel, .right-panel').scrollTop(0);
|
|
|
- var selectedType = $(this).val();
|
|
|
- if (selectedType) {
|
|
|
- handleDivClick(selectedType);
|
|
|
-
|
|
|
- // 可选:滚动到对应的div
|
|
|
- const targetDiv = $(`#formGroup${selectedType}`);
|
|
|
- if (targetDiv.length) {
|
|
|
- $('.left-panel').animate({
|
|
|
- scrollTop: targetDiv.offset().top - $('.left-panel').offset().top + $('.left-panel').scrollTop()
|
|
|
- }, 500);
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- function handleDivClick(typename) {
|
|
|
- console.log("处理点击事件,typename: ", typename);
|
|
|
- if(typename == "medicalRecordType"){
|
|
|
- return;
|
|
|
- }
|
|
|
- $.ajax({
|
|
|
- async: false,
|
|
|
- type: "post",
|
|
|
- url: '{$commonURL}'+'/Send/ControlResultScore',
|
|
|
- data: {
|
|
|
- 'token': tokenString,
|
|
|
- 'typename': typename,
|
|
|
- },
|
|
|
- dataType: "json",
|
|
|
- success: function(msg) {
|
|
|
- const msgData = JSON.parse(msg);
|
|
|
- console.log(msgData);
|
|
|
- var divStr = '<div class="preview-area" id="previewArea">';
|
|
|
- const medicalContent = msgData["content"]
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- .replace(/\n/g, '<br>')
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/brbr/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- var divContentStr = '<div class="form-group" id="formGroup"><div class="form-group" data-content="'+typename+'" id="formGroup'+typename+'">'+medicalContent+'</div></div>';
|
|
|
-
|
|
|
-
|
|
|
- var deductionrulescore = ' <div class = "quality-control-score" id="qualitycontrolscorediv">质控得分:<strong id="qualitycontrolscore">'+msgData["deductionrulescore"]+'</strong>分</div>';
|
|
|
- $('#qualitycontrolscorediv').replaceWith(deductionrulescore);
|
|
|
- if (msgData["reply"] != null) {
|
|
|
- const replyCount = Object.keys(msgData["reply"]).length;
|
|
|
- for (let i = 1; i <= replyCount; i++) {
|
|
|
-
|
|
|
- let content;
|
|
|
- if(msgData["keywords"][i] == null || msgData["keywords"][i] == "null" ||msgData["keywords"][i] == ""){
|
|
|
- msgData["keywords"][i] = "全文";
|
|
|
- }
|
|
|
- console.log("msgData[keywords][i]"+msgData["keywords"][i]);
|
|
|
- if(msgData["rulescore"][i]>= 5){
|
|
|
- if(msgData["accept"][i] == 0){
|
|
|
-
|
|
|
-
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'">采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'">不采纳</span></div>')
|
|
|
- // 处理【规则名称】部分
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-tall-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-tall-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-tall-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- }
|
|
|
- else if(msgData["accept"][i] == 5){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'" style="color: rgb(102, 102, 102); cursor: not-allowed; pointer-events: none;">已采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'" style="display: none;">不采纳</span></div>')
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-tall-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-tall-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-tall-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- }
|
|
|
- else if(msgData["accept"][i] == 2){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'" style="color: rgb(102, 102, 102); cursor: not-allowed; pointer-events: none;">拒绝采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'" style="display: none;">不采纳</span></div>')
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-tall-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-tall-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-tall-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
- }
|
|
|
- }
|
|
|
- else if(msgData["rulescore"][i] > 1){
|
|
|
- if(msgData["accept"][i] == 0){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'">采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'">不采纳</span></div>')
|
|
|
- // 处理【规则名称】部分
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-title-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-title-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-title-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- }
|
|
|
- else if(msgData["accept"][i] == 5){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'" style="color: rgb(102, 102, 102); cursor: not-allowed; pointer-events: none;">已采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'" style="display: none;">不采纳</span></div>')
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-title-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-title-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-title-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- }
|
|
|
- else if(msgData["accept"][i] == 2){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'" style="color: rgb(102, 102, 102); cursor: not-allowed; pointer-events: none;">拒绝采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'" style="display: none;">不采纳</span></div>')
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-title-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-title-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-title-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
- }
|
|
|
- }
|
|
|
- else if(msgData["rulescore"][i] > 0){
|
|
|
- if(msgData["accept"][i] == 0){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'">采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'">不采纳</span></div>')
|
|
|
- // 处理【规则名称】部分
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-low-section"><strong class="rule-section-low">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-low-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-low-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- }
|
|
|
- else if(msgData["accept"][i] == 5){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'" style="color: rgb(102, 102, 102); cursor: not-allowed; pointer-events: none;">已采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'" style="display: none;">不采纳</span></div>')
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-low-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-low-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-low-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
-
|
|
|
- }
|
|
|
- else if(msgData["accept"][i] == 2){
|
|
|
- content = msgData["reply"][i]
|
|
|
- .replace(/ending/g, '<div class="quality"><span class="accept" id="accept-'+msgData["token"][i]+'-'+endingNum+'" style="color: rgb(102, 102, 102); cursor: not-allowed; pointer-events: none;">拒绝采纳</span><span class="refuse" id="refuse-'+msgData["token"][i]+'-'+endingNum+'" style="display: none;">不采纳</span></div>')
|
|
|
- .replace(/【规则名称】:(.+?)\\n/g, '<div class="rule-low-section"><strong class="rule-section-title">【规则名称】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【质控结果】:(.+?)\\n/g, '<div class="result-low-section"><strong>【质控结果】:</strong>$1</div>')
|
|
|
- // 处理【不通过原因】部分
|
|
|
- .replace(/【不通过原因】:(.+?)\\n/g, '<div class="reason-low-section"><strong>【不通过原因】:</strong>$1</div>')
|
|
|
- // 处理【质控结果】部分
|
|
|
- .replace(/【规则关键字】:([\s\S]*?)(?=ending|$)/g, '<div class="result-section"><strong>【规则关键字】:</strong>$1</div>')
|
|
|
- // 处理 ending 替换为操作按钮
|
|
|
- .replace(/\\n/g, '<br>')
|
|
|
- // 替换连续空格为换行
|
|
|
- .replace(/\s{2,}/g, '<br>')
|
|
|
- .replace(/\\\//g, '/');
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if(msgData["keywords"][i] == "无"){
|
|
|
- divStr += '<div class="preview-area" data-typename="">'+content+'</div>';
|
|
|
- }
|
|
|
- else{
|
|
|
- divStr += '<div class="preview-area" data-typename="'+msgData["keywords"][i]+'">'+content+'</div>';
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- $('#formGroup').replaceWith(divContentStr);
|
|
|
-
|
|
|
-
|
|
|
- divStr += '</div>';
|
|
|
- $('#previewArea').replaceWith(divStr);
|
|
|
-
|
|
|
- $(document).off('click', '.preview-area').on('click', '.preview-area', function() {
|
|
|
- // 获取 keyword 的逻辑保持不变
|
|
|
- var keyword = $(this).attr('data-typename'); // 使用 attr() 替代 data()
|
|
|
- console.log("keyword:", keyword);
|
|
|
-
|
|
|
- if (!keyword) {
|
|
|
- console.warn("未能获取到有效的 keyword,检查 data-typename 属性");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 1. 移除之前可能存在的所有高亮
|
|
|
- $('.form-group').find('.highlight-text').each(function() {
|
|
|
- $(this).replaceWith($(this).text());
|
|
|
- });
|
|
|
-
|
|
|
- // 2. 查找所有包含关键词的 form-group 元素
|
|
|
- var found = false;
|
|
|
- $('.form-group').each(function() {
|
|
|
- if (found) return; // 如果已经找到并处理了第一个匹配项,则跳过后续
|
|
|
-
|
|
|
- var $element = $(this);
|
|
|
- var html = $element.html();
|
|
|
-
|
|
|
- // 3. 如果包含目标文本,则进行高亮处理
|
|
|
- if (keyword && html.indexOf(keyword) !== -1) {
|
|
|
- // 动态生成正则表达式(转义特殊字符)
|
|
|
- var escapedKeyword = escapeRegExp(keyword);
|
|
|
- var regex = new RegExp(escapedKeyword, 'g');
|
|
|
- var newHtml = html.replace(regex, '<span class="highlight-text">' + keyword + '</span>');
|
|
|
- $element.html(newHtml);
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 4. 获取高亮后的元素
|
|
|
- var $highlight = $element.find('.highlight-text').first();
|
|
|
-
|
|
|
- // 5. 滚动到高亮元素的位置(改进的滚动计算)
|
|
|
- var $container = $element.closest('.left-panel');
|
|
|
-
|
|
|
- if ($container.length) {
|
|
|
- // 计算元素相对于容器的位置
|
|
|
- var elementTop = $highlight.offset().top;
|
|
|
- var containerTop = $container.offset().top;
|
|
|
- var containerScrollTop = $container.scrollTop();
|
|
|
- var scrollToPosition = elementTop - containerTop + containerScrollTop - 100;
|
|
|
-
|
|
|
- $container.animate({ scrollTop: scrollToPosition }, {
|
|
|
- duration: 500,
|
|
|
- easing: 'swing',
|
|
|
- complete: function() {
|
|
|
- console.log("滚动完成,位置:", scrollToPosition);
|
|
|
- }
|
|
|
- });
|
|
|
- } else {
|
|
|
- // 如果没有容器,滚动整个页面
|
|
|
- $('html, body').animate({
|
|
|
- scrollTop: $highlight.offset().top - 100
|
|
|
- }, {
|
|
|
- duration: 500,
|
|
|
- easing: 'swing',
|
|
|
- complete: function() {
|
|
|
- console.log("页面滚动完成");
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- found = true; // 标记已找到第一个匹配项
|
|
|
- return false; // 退出each循环
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- if (!found) {
|
|
|
- console.log("未找到关键词:", keyword);
|
|
|
- }
|
|
|
-
|
|
|
- // 添加高亮样式
|
|
|
- $('<style>')
|
|
|
- .prop('type', 'text/css')
|
|
|
- .html('.highlight-text { background-color: #FF0000; color:#F8F9FA; padding: 2px 4px; border-radius: 3px; }')
|
|
|
- .appendTo('head');
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
+ @ini_set('output_buffering', 'off');
|
|
|
+ @ini_set('zlib.output_compression', false);
|
|
|
+ while (ob_get_level() > 0) {
|
|
|
+ ob_end_flush();
|
|
|
}
|
|
|
-
|
|
|
+ ob_implicit_flush(true);
|
|
|
+
|
|
|
+ // 设置流式响应头
|
|
|
|
|
|
- // 辅助函数:转义正则表达式中的特殊字符
|
|
|
- function escapeRegExp(string) {
|
|
|
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
- }
|
|
|
-
|
|
|
- $('#emrCloseBtn').click(function() {
|
|
|
- $('#emrLoadingOverlay').css('display', 'none');
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- let isRequesting = false;
|
|
|
+ $requestBody = file_get_contents('php://input');
|
|
|
+ $headers = getallheaders();
|
|
|
+ $authorizationHeader = isset($headers['Authorization']) ? $headers['Authorization'] : '';
|
|
|
|
|
|
- function requestOnlineUpdate() {
|
|
|
- if (isRequesting) return;
|
|
|
-
|
|
|
- isRequesting = true;
|
|
|
-
|
|
|
- $.ajax({
|
|
|
- async: true,
|
|
|
- type: "post",
|
|
|
- url: '{$commonURL}/His/onlineUpdate',
|
|
|
- data: {
|
|
|
- 'user': urlUser,
|
|
|
- 'messageQueuelength': messageQueue.length,
|
|
|
- 'strate': 1,
|
|
|
- },
|
|
|
- dataType: "json",
|
|
|
- timeout: 3000, // 添加超时控制
|
|
|
- success: function(msg) {
|
|
|
- console.log(msg);
|
|
|
- },
|
|
|
- error: function(xhr, status, error) {
|
|
|
- console.error('请求失败:', error);
|
|
|
- },
|
|
|
- complete: function() {
|
|
|
- isRequesting = false;
|
|
|
- // 完成后等待1秒再发起下一次请求
|
|
|
- setTimeout(requestOnlineUpdate, 1000);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // 启动请求
|
|
|
- requestOnlineUpdate();
|
|
|
- function sendFinalRequest() {
|
|
|
-
|
|
|
-
|
|
|
- $.ajax({
|
|
|
- async: true,
|
|
|
- type: "post", //数据提交方式(post/get)
|
|
|
- url: '{$commonURL}/His/onlineUpdate', //提交到的url
|
|
|
- data: {
|
|
|
- 'user':urlUser,
|
|
|
- 'messageQueuelength':0,
|
|
|
- 'strate':0,
|
|
|
- },//提交的数据
|
|
|
- dataType: "json",//返回的数据类型格式
|
|
|
- success: function(msg){
|
|
|
- console.log(msg);
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- }
|
|
|
- window.addEventListener('beforeunload', function(event) {
|
|
|
- // 注意:beforeunload 中不能阻止页面关闭,只能发送请求
|
|
|
- sendFinalRequest();
|
|
|
- });
|
|
|
+
|
|
|
+ $reply = '';
|
|
|
+
|
|
|
+ $RAGdata = [
|
|
|
+ "username" => "test",
|
|
|
+ "password" => "test123"
|
|
|
+ ];
|
|
|
+ // 调用时只需传递非 Content-Type/Accept 的 Header
|
|
|
+ $headers = [
|
|
|
+ 'Cache-Control: no-cache',
|
|
|
+ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
|
+ ];
|
|
|
+ $urlRAG = "http://192.168.10.115:6039/auth/login"; // 替换为实际 URL
|
|
|
+
|
|
|
+ $id = curlPostRAG($urlRAG, $RAGdata, $headers);
|
|
|
+
|
|
|
+ $token = $id; // 默认启用 JSON 模式
|
|
|
+ $token = json_decode($token);
|
|
|
+ $token = stdClassObjToArray($token);
|
|
|
+ $token = $token['data']['token'];
|
|
|
+ $url = "http://192.168.10.115:6039/chat/emoonSend"; // 替换成实际接口地址
|
|
|
+ $data = [
|
|
|
+ "messages" => $contentmcp,
|
|
|
+ "model" => "qwen3-32B", //X
|
|
|
+ "temperature" => 0.5,
|
|
|
+ "top_p" => 1,
|
|
|
+ "presence_penalty" => 0,
|
|
|
+ "frequency_penalty" => 0,
|
|
|
+ "kid" => "",
|
|
|
+ "chat_type" => 0,
|
|
|
+ "appId" => "",
|
|
|
+ "agentId" => $mcp,
|
|
|
+ "stream" => true
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 初始化 cURL
|
|
|
+ $ch = curl_init();
|
|
|
+ // 设置 cURL 选项
|
|
|
+ curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 返回响应数据
|
|
|
+ curl_setopt($ch, CURLOPT_POST, true); // 使用 POST 方法
|
|
|
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); // 发送 JSON 数据
|
|
|
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
|
+ "Authorization: Bearer " . $token,
|
|
|
+ "Content-Type: application/json" // 声明请求体是 JSON
|
|
|
+ ]);
|
|
|
+
|
|
|
+
|
|
|
+ $chatId = 'chat' . uniqid() . bin2hex(random_bytes(8));
|
|
|
+ $created = time();
|
|
|
|
|
|
- // 额外监听 unload 事件作为保险
|
|
|
- window.addEventListener('unload', function() {
|
|
|
- sendFinalRequest();
|
|
|
- });
|
|
|
- function sendRuleKeyword(askInfoId) {
|
|
|
- var completeAnswer = $("#chatTime"+chatTime).text();
|
|
|
- console.log("completeAnswer"+completeAnswer);
|
|
|
- const keywordMatch = completeAnswer.match(/【规则关键字】:([^\n\r【]*)/);
|
|
|
- if (keywordMatch && keywordMatch[1]) {
|
|
|
- const ruleKeyword = keywordMatch[1].trim();
|
|
|
- console.log("ruleKeyword"+ruleKeyword);
|
|
|
- if (ruleKeyword) {
|
|
|
- $.ajax({
|
|
|
- async: true,
|
|
|
- type: "post",
|
|
|
- url: '{$commonURL}/Send/listkey', // 替换为实际接口
|
|
|
- data: {
|
|
|
- 'ruleKeyword': ruleKeyword,
|
|
|
- 'askInfoId': askInfoId,
|
|
|
- },
|
|
|
- dataType: "json",
|
|
|
- success: function(response) {
|
|
|
- console.log('规则关键字发送成功:', response);
|
|
|
- },
|
|
|
- error: function(xhr, status, error) {
|
|
|
- console.error('规则关键字发送失败:', error);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- </script>
|
|
|
- <style>
|
|
|
- .selected-option {
|
|
|
- height: 30px; /* 调小高度以匹配提示文本 */
|
|
|
- padding: 0 10px;
|
|
|
- background: #2a2a2a;
|
|
|
- border: 1px solid #444;
|
|
|
- border-radius: 4px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 14px; /* 调整字体大小 */
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .dropdown-options {
|
|
|
- display: none;
|
|
|
- position: absolute;
|
|
|
- width: 100%; /* 与选择框同宽 */
|
|
|
- overflow-y: auto; /* 强制垂直滚动条 */
|
|
|
- overflow-x: hidden;
|
|
|
- white-space: nowrap; /* 禁止换行 */
|
|
|
- max-height: 200px;
|
|
|
- background: #333;
|
|
|
- border: 1px solid #444;
|
|
|
- z-index: 1000;
|
|
|
- top: 100%; /* 在选择框下方展开 */
|
|
|
- left: 0;
|
|
|
- margin-top: 5px;
|
|
|
- border-radius: 4px;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .option {
|
|
|
- width: 100%;
|
|
|
- padding: 8px 10px;
|
|
|
- text-overflow: ellipsis;
|
|
|
- overflow: hidden;
|
|
|
- white-space: nowrap;
|
|
|
- word-wrap: break-word;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 14px;
|
|
|
- color: #fff;
|
|
|
- text-align: left;
|
|
|
- }
|
|
|
+
|
|
|
+ // for ($i = 0; $i < count($lines); $i++) {
|
|
|
+ // echo $lines[0];
|
|
|
+ // }
|
|
|
+ $lastContent = '';
|
|
|
|
|
|
-
|
|
|
- .option:hover {
|
|
|
- background: #0069d9;
|
|
|
- }
|
|
|
-
|
|
|
- #selected-text {
|
|
|
- white-space: nowrap;
|
|
|
- overflow: hidden; /* 隐藏溢出内容 */
|
|
|
- text-overflow: ellipsis; /* 显示省略号 */
|
|
|
- flex-grow: 1;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
- .selected-agent {
|
|
|
- height: 30px; /* 调小高度以匹配提示文本 */
|
|
|
- padding: 0 10px;
|
|
|
- background: #2a2a2a;
|
|
|
- border: 1px solid #444;
|
|
|
- border-radius: 4px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 14px; /* 调整字体大小 */
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .dropdown-agents {
|
|
|
- display: none;
|
|
|
- position: absolute;
|
|
|
- width: 100%; /* 与选择框同宽 */
|
|
|
- overflow-y: auto; /* 强制垂直滚动条 */
|
|
|
- overflow-x: hidden;
|
|
|
- max-height: 200px;
|
|
|
- overflow-y: auto;
|
|
|
- background: #333;
|
|
|
- border: 1px solid #444;
|
|
|
- z-index: 1000;
|
|
|
- top: 100%; /* 在选择框下方展开 */
|
|
|
- left: 0;
|
|
|
- margin-top: 5px;
|
|
|
- border-radius: 4px;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .agent {
|
|
|
- width: 100%;
|
|
|
- padding: 8px 10px;
|
|
|
- text-overflow: ellipsis;
|
|
|
- overflow: hidden;
|
|
|
- white-space: nowrap;
|
|
|
- word-wrap: break-word;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 14px;
|
|
|
- color: #fff;
|
|
|
- text-align: left;
|
|
|
- }
|
|
|
-
|
|
|
- .agent:hover {
|
|
|
- background: #0069d9;
|
|
|
- }
|
|
|
-
|
|
|
- #selected-agenttext {
|
|
|
- white-space: nowrap;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- flex-grow: 1;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
- .info {
|
|
|
-
|
|
|
- word-break: break-word; /* 长单词换行 */
|
|
|
- }
|
|
|
-
|
|
|
- .think-toggle {
|
|
|
- cursor: pointer;
|
|
|
- margin: 10px 0;
|
|
|
- padding: 5px;
|
|
|
- border-radius: 5px;
|
|
|
- }
|
|
|
-
|
|
|
- .think-content {
|
|
|
- padding: 10px;
|
|
|
- border-left: 2px solid #007bff;
|
|
|
- }
|
|
|
- .think-toggle.active .think-header::after {
|
|
|
- content: " ▲";
|
|
|
- }
|
|
|
- .think-toggle:not(.active) .think-header::after {
|
|
|
- content: " ▼";
|
|
|
- }
|
|
|
- .think-header{
|
|
|
- color: #007bff;
|
|
|
- margin-bottom: 1rem;
|
|
|
- text-align: right;
|
|
|
- }
|
|
|
-
|
|
|
- /* 侧边栏触发按钮样式 */
|
|
|
- .sidebar-trigger {
|
|
|
- position: fixed;
|
|
|
- left: 0;
|
|
|
- top: 50%;
|
|
|
- transform: translateY(-50%);
|
|
|
- background: linear-gradient(135deg, #007bff, #0056b3);
|
|
|
- color: white;
|
|
|
- padding: 18px 10px;
|
|
|
- border-radius: 0 12px 12px 0;
|
|
|
- cursor: pointer;
|
|
|
- z-index: 998;
|
|
|
- box-shadow: 3px 3px 15px rgba(0,0,0,0.2);
|
|
|
- writing-mode: vertical-lr;
|
|
|
- text-orientation: mixed;
|
|
|
- opacity: 0.9;
|
|
|
- transition: all 0.3s ease;
|
|
|
-
|
|
|
- letter-spacing: 2px;
|
|
|
- border: none;
|
|
|
- outline: none;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-trigger:hover {
|
|
|
- opacity: 1;
|
|
|
- padding-left: 12px;
|
|
|
- box-shadow: 4px 4px 20px rgba(0,0,0,0.25);
|
|
|
- }
|
|
|
-
|
|
|
- /* 半屏弹窗样式 */
|
|
|
- .sidebar-overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: -25%;
|
|
|
- width: 25%;
|
|
|
- height: 100%;
|
|
|
- background: rgba(0,0,0,0.3);
|
|
|
- z-index: 1002;
|
|
|
- opacity: 0;
|
|
|
- visibility: hidden;
|
|
|
- transition: all 0.4s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-overlay.active {
|
|
|
- left: 0;
|
|
|
- opacity: 1;
|
|
|
- visibility: visible;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-content {
|
|
|
- position: absolute;
|
|
|
- right: 0;
|
|
|
- top: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background: #fff;
|
|
|
- box-shadow: -4px 0 20px rgba(0,0,0,0.15);
|
|
|
- overflow-y: auto;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 22px 25px;
|
|
|
- background: linear-gradient(to right, #4a6bdf, #2b4fdc);
|
|
|
- color: white;
|
|
|
- border-bottom: 1px solid #e1e5eb;
|
|
|
- flex-shrink: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-header h3 {
|
|
|
- margin: 0;
|
|
|
- font-size: 20px;
|
|
|
+ curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($chatId, $created, &$lastContent,&$reply) {
|
|
|
+ $lines = explode("\n", $data);
|
|
|
+ $prefixLength = strlen("event:chat_completion");
|
|
|
+ $content = str_replace("event:chat_completion", "", $lines[0]);
|
|
|
+
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-close {
|
|
|
- font-size: 28px;
|
|
|
- cursor: pointer;
|
|
|
- color: rgba(255, 255, 255, 0.9);
|
|
|
- transition: all 0.2s;
|
|
|
- width: 32px;
|
|
|
- height: 32px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- border-radius: 50%;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-close:hover {
|
|
|
- color: white;
|
|
|
- background: rgba(255, 255, 255, 0.15);
|
|
|
- transform: scale(1.1);
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-body {
|
|
|
- padding: 25px;
|
|
|
- flex-grow: 1;
|
|
|
- overflow-y: auto;
|
|
|
- }
|
|
|
-
|
|
|
- /* 统计信息样式 */
|
|
|
- .stats {
|
|
|
- background: #f0f5ff;
|
|
|
- padding: 12px 16px;
|
|
|
- border-radius: 8px;
|
|
|
- margin-bottom: 20px;
|
|
|
- font-size: 15px;
|
|
|
- color: #2b4fdc;
|
|
|
- border-left: 4px solid #2b4fdc;
|
|
|
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
|
- }
|
|
|
-
|
|
|
- /* 文档分类样式 */
|
|
|
- .sidebar-tools {
|
|
|
- padding: 16px 20px;
|
|
|
- margin-bottom: 15px;
|
|
|
- background: linear-gradient(to right, #f7f9fc, #f0f5ff);
|
|
|
- border-radius: 10px;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s ease;
|
|
|
- box-shadow: 0 3px 10px rgba(0,0,0,0.04);
|
|
|
- border-left: 4px solid #4a6bdf;
|
|
|
-
|
|
|
- color: #2d3748;
|
|
|
- position: relative;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-tools:hover {
|
|
|
- transform: translateY(-2px);
|
|
|
- box-shadow: 0 5px 15px rgba(0,0,0,0.08);
|
|
|
- background: linear-gradient(to right, #f0f5ff, #e6eeff);
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-tools:active {
|
|
|
- transform: translateY(0);
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-tools::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- right: 0;
|
|
|
- width: 8px;
|
|
|
- height: 100%;
|
|
|
- background: #4a6bdf;
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 0.3s;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-tools:hover::after {
|
|
|
- opacity: 0.3;
|
|
|
- }
|
|
|
-
|
|
|
- /* 质控问题项样式 */
|
|
|
- .qualityControlToken {
|
|
|
- padding: 14px 20px;
|
|
|
- margin: 12px 0;
|
|
|
- background: #fff;
|
|
|
- border-radius: 8px;
|
|
|
- border-left: 3px solid #ff9500;
|
|
|
- box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
|
|
- transition: all 0.2s;
|
|
|
- color: #4a5568;
|
|
|
- }
|
|
|
- .quality
|
|
|
- {
|
|
|
- width: 100%;
|
|
|
- height: 20px;
|
|
|
- line-height: 20px;
|
|
|
- margin-top: 10px;
|
|
|
- }
|
|
|
- .qualityControlToken:hover {
|
|
|
- box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
|
|
- transform: translateX(3px);
|
|
|
- }
|
|
|
-
|
|
|
- /* 无数据提示样式 */
|
|
|
- .no-data {
|
|
|
- text-align: center;
|
|
|
- padding: 40px 20px;
|
|
|
- color: #a0aec0;
|
|
|
- font-size: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 加载动画 */
|
|
|
- .loader {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- padding: 30px;
|
|
|
- }
|
|
|
-
|
|
|
- .loader .dot {
|
|
|
- width: 10px;
|
|
|
- height: 10px;
|
|
|
- margin: 0 5px;
|
|
|
- background: #4a6bdf;
|
|
|
- border-radius: 50%;
|
|
|
- animation: bounce 1.5s infinite ease-in-out;
|
|
|
- }
|
|
|
-
|
|
|
- .loader .dot:nth-child(2) {
|
|
|
- animation-delay: 0.2s;
|
|
|
- }
|
|
|
-
|
|
|
- .loader .dot:nth-child(3) {
|
|
|
- animation-delay: 0.4s;
|
|
|
- }
|
|
|
-
|
|
|
- @keyframes bounce {
|
|
|
- 0%, 100% { transform: translateY(0); }
|
|
|
- 50% { transform: translateY(-10px); }
|
|
|
- }
|
|
|
-
|
|
|
- /* 移动设备适配 */
|
|
|
- @media (max-width: 768px) {
|
|
|
- .sidebar-overlay {
|
|
|
- width: 85%;
|
|
|
- left: -85%;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-trigger {
|
|
|
- padding: 15px 8px;
|
|
|
- font-size: 14px;
|
|
|
- writing-mode: horizontal-tb;
|
|
|
- top: auto;
|
|
|
- bottom: 20px;
|
|
|
- left: 20px;
|
|
|
- transform: none;
|
|
|
- border-radius: 12px;
|
|
|
- width: auto;
|
|
|
- }
|
|
|
+ // foreach ($lines as $line) {
|
|
|
+ // $line = substr($line,6);
|
|
|
+ // $content = $matches[0];
|
|
|
+ // // 仅当内容变化时才输出
|
|
|
+ // if ($content !== $lastContent) {
|
|
|
+ // $lastContent = $content;
|
|
|
+ $jsonData = [
|
|
|
+ "id" => $chatId,
|
|
|
+ "model" => "emoon-E1-13B",
|
|
|
+ "created" => $created,
|
|
|
+ "object" => "chat.completion.chunk",
|
|
|
+ "choices" => [
|
|
|
+ [
|
|
|
+ "index" => 0,
|
|
|
+ "delta" => [
|
|
|
+ "content" => $content
|
|
|
+ ],
|
|
|
+ "finish_reason" => null
|
|
|
+ ]
|
|
|
+ ],
|
|
|
+ "usage" => null
|
|
|
+ ];
|
|
|
+ echo "data: " . json_encode($jsonData,JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
|
+ $outputJson = json_encode($jsonData,JSON_UNESCAPED_UNICODE);
|
|
|
+
|
|
|
+ $outputJson = json_decode($outputJson);
|
|
|
+ // $outputJson = stdClassObjToArray($outputJson);
|
|
|
+ $outputJson = $outputJson->choices[0]->delta->content;
|
|
|
+
|
|
|
+ if($outputJson){
|
|
|
+ $outputJson = json_encode($outputJson,JSON_UNESCAPED_UNICODE);
|
|
|
+ $outputJson = str_replace('"', '', $outputJson);
|
|
|
+ }
|
|
|
|
|
|
- .sidebar-header {
|
|
|
- padding: 18px 20px;
|
|
|
- }
|
|
|
+ $reply .= $outputJson;
|
|
|
+
|
|
|
+ // }
|
|
|
+ @ob_flush();
|
|
|
+ @flush();
|
|
|
|
|
|
- .sidebar-body {
|
|
|
- padding: 20px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* 滚动条样式 */
|
|
|
- .sidebar-content::-webkit-scrollbar {
|
|
|
- width: 6px;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-content::-webkit-scrollbar-track {
|
|
|
- background: #f1f1f1;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-content::-webkit-scrollbar-thumb {
|
|
|
- background: #c1c1c1;
|
|
|
- border-radius: 3px;
|
|
|
- }
|
|
|
-
|
|
|
- .sidebar-content::-webkit-scrollbar-thumb:hover {
|
|
|
- background: #a8a8a8;
|
|
|
- }
|
|
|
-
|
|
|
- /* 新增样式:分类计数徽章 */
|
|
|
- .doc-count {
|
|
|
- background: #4a6bdf;
|
|
|
- color: white;
|
|
|
- border-radius: 12px;
|
|
|
- padding: 2px 8px;
|
|
|
- font-size: 12px;
|
|
|
- margin-left: 8px;
|
|
|
- vertical-align: middle;
|
|
|
- }
|
|
|
-
|
|
|
- /* 新增样式:搜索框 */
|
|
|
- .search-container {
|
|
|
- padding: 0 25px 15px;
|
|
|
- background: white;
|
|
|
- border-bottom: 1px solid #e1e5eb;
|
|
|
- }
|
|
|
-
|
|
|
- .search-box {
|
|
|
- width: 100%;
|
|
|
- padding: 12px 15px;
|
|
|
- border: 1px solid #e1e5eb;
|
|
|
- border-radius: 8px;
|
|
|
- font-size: 14px;
|
|
|
- transition: all 0.3s;
|
|
|
- }
|
|
|
-
|
|
|
- .search-box:focus {
|
|
|
- outline: none;
|
|
|
- border-color: #4a6bdf;
|
|
|
- box-shadow: 0 0 0 3px rgba(74, 107, 223, 0.2);
|
|
|
- }
|
|
|
-
|
|
|
- .selected-option {
|
|
|
- height: 30px;
|
|
|
- padding: 0 10px;
|
|
|
- background: #2a2a2a;
|
|
|
- border: 1px solid #444;
|
|
|
- border-radius: 4px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- cursor: pointer;
|
|
|
- font-size: 14px;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /* EMR加载覆盖框样式 */
|
|
|
- .emr-loading-overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: rgba(0, 0, 0, 0.7);
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- z-index: 9999;
|
|
|
- }
|
|
|
-
|
|
|
- .emr-loading-content {
|
|
|
- position: relative;
|
|
|
- background: linear-gradient(135deg, #2c3e50, #1a2530);
|
|
|
- padding: 30px 40px;
|
|
|
- border-radius: 16px;
|
|
|
- text-align: center;
|
|
|
- color: white;
|
|
|
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
|
- max-width: 400px;
|
|
|
- width: 80%;
|
|
|
- animation: fadeIn 0.3s ease-out;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- .emr-loading-spinner {
|
|
|
- width: 60px;
|
|
|
- height: 60px;
|
|
|
- border: 5px solid rgba(255, 255, 255, 0.3);
|
|
|
- border-radius: 50%;
|
|
|
- border-top-color: #4a6bdf;
|
|
|
- margin: 0 auto 20px;
|
|
|
- animation: spin 1s linear infinite;
|
|
|
- }
|
|
|
+ // }
|
|
|
+ return strlen($data);
|
|
|
+ });
|
|
|
+ $res = curl_exec($ch);
|
|
|
+
|
|
|
+}
|
|
|
+// 清理资源
|
|
|
+curl_close($ch);
|
|
|
|
|
|
- .emr-loading-text {
|
|
|
- font-size: 18px;
|
|
|
- margin-bottom: 10px;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .emr-loading-subtext {
|
|
|
- font-size: 14px;
|
|
|
- color: #bdc3c7;
|
|
|
- margin: 0 0 15px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .progress-container {
|
|
|
- width: 100%;
|
|
|
- height: 8px;
|
|
|
- background-color: rgba(255, 255, 255, 0.2);
|
|
|
- border-radius: 4px;
|
|
|
- overflow: hidden;
|
|
|
- margin-top: 10px;
|
|
|
- }
|
|
|
-
|
|
|
- .progress-bar {
|
|
|
- height: 100%;
|
|
|
- width: 0%;
|
|
|
- background: linear-gradient(90deg, #4a6bdf, #6c8cff);
|
|
|
- border-radius: 4px;
|
|
|
- transition: width 0.3s ease;
|
|
|
- }
|
|
|
|
|
|
- /* 完成状态样式 */
|
|
|
- .emr-loading-content.completed {
|
|
|
- background: linear-gradient(135deg, #27ae60, #219653);
|
|
|
- }
|
|
|
-
|
|
|
- .emr-loading-content.completed .emr-loading-spinner {
|
|
|
- border-top-color: #fff;
|
|
|
- animation: none;
|
|
|
- }
|
|
|
-
|
|
|
- .emr-loading-content.completed .emr-loading-spinner::after {
|
|
|
- content: "✓";
|
|
|
- font-size: 30px;
|
|
|
- color: white;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- /* 动画效果 */
|
|
|
- @keyframes spin {
|
|
|
- 0% { transform: rotate(0deg); }
|
|
|
- 100% { transform: rotate(360deg); }
|
|
|
- }
|
|
|
+// 显式结束流
|
|
|
+echo "event: end\ndata: Stream ended\n\n";
|
|
|
+ob_flush();
|
|
|
+flush();
|
|
|
+
|
|
|
+$dataReply['reply'] = $reply;
|
|
|
+$url=$urlCommon.'/Apis/reply';
|
|
|
+curlPost($url,$dataReply);
|
|
|
+//模型定制结束-------------------------------------------------------------------
|
|
|
+function curlPost($url, $data = null, $headers = [], $json = false) {
|
|
|
+ $curl = curl_init($url);
|
|
|
+ $options = [
|
|
|
+ CURLOPT_RETURNTRANSFER => true,
|
|
|
+ CURLOPT_SSL_VERIFYPEER => false,
|
|
|
+ CURLOPT_SSL_VERIFYHOST => false,
|
|
|
+ CURLOPT_POST => !empty($data) || $json,
|
|
|
+ ];
|
|
|
+ if ($json) {
|
|
|
+ $headers = array_filter($headers, function($h) {
|
|
|
+ return stripos($h, 'Content-Type:') === false;
|
|
|
+ });
|
|
|
+ $headers[] = 'Content-Type: application/json';
|
|
|
+ $data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
|
+ }
|
|
|
+ if (!empty($headers)) {
|
|
|
+ $options[CURLOPT_HTTPHEADER] = $headers;
|
|
|
+ }
|
|
|
+ if (!empty($data)) {
|
|
|
+ $options[CURLOPT_POSTFIELDS] = $data;
|
|
|
+ }
|
|
|
+ curl_setopt_array($curl, $options);
|
|
|
+ $result = curl_exec($curl);
|
|
|
+ curl_close($curl);
|
|
|
+ return $result;
|
|
|
+}
|
|
|
|
|
|
- @keyframes fadeIn {
|
|
|
- from { opacity: 0; transform: translateY(-20px); }
|
|
|
- to { opacity: 1; transform: translateY(0); }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /*报告面板*/
|
|
|
- .modal-overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background-color: rgba(0, 0, 0, 0.5);
|
|
|
- display: none;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- z-index: 1000;
|
|
|
- backdrop-filter: blur(1px);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /* 模态框容器 */
|
|
|
- .modal-container {
|
|
|
- width: 95%;
|
|
|
- max-width: 1400px;
|
|
|
- height: 95vh;
|
|
|
- top:2.5vh;
|
|
|
- background-color: #fff;
|
|
|
- border-radius: 12px;
|
|
|
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
|
- display: flex;
|
|
|
- overflow: hidden;
|
|
|
- position: relative;
|
|
|
- margin: 0 auto;
|
|
|
- }
|
|
|
-
|
|
|
- /* 关闭按钮 */
|
|
|
- .close-btn {
|
|
|
- position: absolute;
|
|
|
-
|
|
|
- right: 5px;
|
|
|
- font-size: 28px;
|
|
|
- color: #888;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s;
|
|
|
- z-index: 10;
|
|
|
- background: none;
|
|
|
- border: none;
|
|
|
- padding: 0;
|
|
|
- width: 30px;
|
|
|
- height: 30px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- border-radius: 50%;
|
|
|
- }
|
|
|
-
|
|
|
- .close-btn:hover {
|
|
|
- color: #333;
|
|
|
- transform: scale(1.1);
|
|
|
- background-color: #f5f5f5;
|
|
|
- }
|
|
|
-
|
|
|
- /* 左右面板公共样式 */
|
|
|
- .left-panel, .right-panel {
|
|
|
- padding: 30px;
|
|
|
- height: 100%;
|
|
|
- box-sizing: border-box;
|
|
|
- overflow-y: auto;
|
|
|
- }
|
|
|
-
|
|
|
- /* 左侧面板 */
|
|
|
- .left-panel {
|
|
|
- width: 60%;
|
|
|
- background-color: #f9f9f9;
|
|
|
- border-right: 1px solid #eee;
|
|
|
- overflow-y: auto; /* 确保只有内容区域滚动 */
|
|
|
- height: calc(100% - 60px); /* 减去标题栏高度 */
|
|
|
- padding-top: 0; /* 移除顶部内边距 */
|
|
|
- }
|
|
|
-
|
|
|
- /* 右侧面板 */
|
|
|
- .right-panel {
|
|
|
- width: 40%;
|
|
|
- background-color: #fff;
|
|
|
- overflow-y: auto; /* 确保只有内容区域滚动 */
|
|
|
- height: calc(100% - 60px); /* 减去标题栏高度 */
|
|
|
- padding-top: 0;
|
|
|
- }
|
|
|
-
|
|
|
- /* 面板标题 */
|
|
|
- .panel-title {
|
|
|
- font-size: 20px;
|
|
|
- color: #333;
|
|
|
- top: 0;
|
|
|
-
|
|
|
- margin-top: 10px;
|
|
|
-
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
-
|
|
|
- /* 表单组样式 */
|
|
|
- .form-group {
|
|
|
- margin-bottom: 20px;
|
|
|
- }
|
|
|
-
|
|
|
- .form-group label {
|
|
|
- display: block;
|
|
|
- margin-bottom: 8px;
|
|
|
- color: #555;
|
|
|
- font-weight: 500;
|
|
|
- }
|
|
|
-
|
|
|
- .form-group input, .form-group textarea, .form-group select {
|
|
|
- width: 100%;
|
|
|
- padding: 10px 12px;
|
|
|
- border: 1px solid #ddd;
|
|
|
- border-radius: 6px;
|
|
|
- font-size: 14px;
|
|
|
- transition: border 0.3s;
|
|
|
- }
|
|
|
-
|
|
|
- .form-group input:focus, .form-group textarea:focus {
|
|
|
- border-color: #4a90e2;
|
|
|
- outline: none;
|
|
|
- box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
|
|
- }
|
|
|
-
|
|
|
- /* 预览区域 */
|
|
|
- .preview-area {
|
|
|
- background-color: #f5f7fa;
|
|
|
- border-radius: 8px;
|
|
|
- padding: 2px;
|
|
|
- min-height: 100px;
|
|
|
- border: 1px dashed #ccc;
|
|
|
- }
|
|
|
-
|
|
|
- .preview-placeholder {
|
|
|
- text-align: center;
|
|
|
- color: #888;
|
|
|
- padding: 40px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .preview-placeholder h3 {
|
|
|
- font-size: 18px;
|
|
|
- margin-bottom: 10px;
|
|
|
- color: #666;
|
|
|
+function stdClassObjToArray($array) {
|
|
|
+ if(is_object($array)) {
|
|
|
+ $array = (array)$array;
|
|
|
+ }
|
|
|
+ if(is_array($array)) {
|
|
|
+ foreach($array as $key=>$value) {
|
|
|
+ $array[$key] = stdClassObjToArray($value);
|
|
|
}
|
|
|
-
|
|
|
+ }
|
|
|
+ return $array;
|
|
|
+}
|
|
|
+function curlPostRAG($url, $data = null, $headers = [], $json = true) {
|
|
|
+ $curl = curl_init($url);
|
|
|
+ $options = [
|
|
|
+ CURLOPT_RETURNTRANSFER => true,
|
|
|
+ CURLOPT_SSL_VERIFYPEER => false,
|
|
|
+ CURLOPT_SSL_VERIFYHOST => false,
|
|
|
+ CURLOPT_POST => true, // 强制启用 POST(JSON 模式下始终 POST)
|
|
|
+ ];
|
|
|
|
|
|
-
|
|
|
- .title-row {
|
|
|
- display: flex;
|
|
|
- border-bottom: 2px solid #4a90e2;
|
|
|
- align-items: center;
|
|
|
- position: sticky; /* 关键属性 */
|
|
|
- top: 0; /* 距离顶部0px时固定 */
|
|
|
- background-color: #f9f9f9; /* 保持背景色一致 */
|
|
|
- z-index: 10; /* 确保在内容上方 */
|
|
|
- padding: 10px 0; /* 保持原有内边距 */
|
|
|
- }
|
|
|
-
|
|
|
- .title-row-right {
|
|
|
- display: flex;
|
|
|
- border-bottom: 2px solid #4a90e2;
|
|
|
- align-items: center;
|
|
|
- position: sticky; /* 关键属性 */
|
|
|
- top: 0; /* 距离顶部0px时固定 */
|
|
|
- background-color: #f9f9f9; /* 保持背景色一致 */
|
|
|
- z-index: 10; /* 确保在内容上方 */
|
|
|
- padding: 10px 0; /* 保持原有内边距 */
|
|
|
- }
|
|
|
-
|
|
|
- .type-selector {
|
|
|
- width: 40%;
|
|
|
- margin-top:10px;
|
|
|
- margin-left: auto;
|
|
|
- }
|
|
|
-
|
|
|
- .rule-section {
|
|
|
- margin-top: 10px;
|
|
|
- font-weight: bold; /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- color: #2980b9; /* 专业蓝 */
|
|
|
- background-color: #f8f9fa;
|
|
|
- }
|
|
|
-
|
|
|
- /* 质控结果样式 */
|
|
|
- .result-section {
|
|
|
- /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 不通过原因样式 */
|
|
|
- .reason-section {
|
|
|
- margin-bottom: 12px;
|
|
|
- }
|
|
|
-
|
|
|
- .rule-low-section {
|
|
|
- margin-top: 10px;
|
|
|
- font-weight: bold; /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- color: #2980b9; /* 专业蓝 */
|
|
|
- background-color: #f8f9fa;
|
|
|
- }
|
|
|
-
|
|
|
- /* 质控结果样式 */
|
|
|
- .result-low-section {
|
|
|
- /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 不通过原因样式 */
|
|
|
- .reason-low-section {
|
|
|
- margin-bottom: 12px;
|
|
|
- }
|
|
|
-
|
|
|
- .rule-tall-section {
|
|
|
- margin-top: 10px;
|
|
|
- font-weight: bold; /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- color: #e74c3c; /* 红 */
|
|
|
- background-color: #f8f9fa;
|
|
|
- }
|
|
|
-
|
|
|
- /* 质控结果样式 */
|
|
|
- .result-tall-section {
|
|
|
- /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 不通过原因样式 */
|
|
|
- .reason-tall-section {
|
|
|
- margin-bottom: 12px;
|
|
|
- }
|
|
|
-
|
|
|
- .rule-title-section {
|
|
|
- font-weight: bold; /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
- color: #e74c3c; /* 红 */
|
|
|
- background-color: #f8f9fa;
|
|
|
- }
|
|
|
-
|
|
|
- /* 质控结果样式 */
|
|
|
- .result-title-section {
|
|
|
- /* 加粗 */
|
|
|
- margin-bottom: 8px;
|
|
|
+ if ($json) {
|
|
|
+ // 移除已有的 Content-Type 和 Accept 头,避免冲突
|
|
|
+ $headers = array_filter($headers, function($h) {
|
|
|
+ return stripos($h, 'Content-Type:') === false && stripos($h, 'Accept:') === false;
|
|
|
+ });
|
|
|
+ // 添加 JSON 专用头
|
|
|
+ $headers[] = 'Content-Type: application/json';
|
|
|
+ $headers[] = 'Accept: application/json';
|
|
|
+ // 编码数据为 JSON(空数据编码为 "{}" 避免服务器解析错误)
|
|
|
+ $data = $data !== null ? json_encode($data, JSON_UNESCAPED_UNICODE) : '{}';
|
|
|
}
|
|
|
-
|
|
|
- /* 不通过原因样式 */
|
|
|
- .reason-title-section {
|
|
|
- margin-bottom: 12px;
|
|
|
+
|
|
|
+ // 设置请求头和数据
|
|
|
+ if (!empty($headers)) {
|
|
|
+ $options[CURLOPT_HTTPHEADER] = $headers;
|
|
|
}
|
|
|
-
|
|
|
- .emr-close-btn {
|
|
|
- position: absolute;
|
|
|
- top: 15px;
|
|
|
- right: 15px;
|
|
|
- padding: 6px 12px;
|
|
|
- /*background-color: #495057;*/
|
|
|
-
|
|
|
- border-radius: 4px;
|
|
|
- color: #e9ecef;
|
|
|
- font-size: 14px;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.2s ease;
|
|
|
- z-index: 10;
|
|
|
+ if ($data !== null) {
|
|
|
+ $options[CURLOPT_POSTFIELDS] = $data;
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- .emr-close-btn:active {
|
|
|
-
|
|
|
- transform: translateY(1px);
|
|
|
+
|
|
|
+ curl_setopt_array($curl, $options);
|
|
|
+ $result = curl_exec($curl);
|
|
|
+
|
|
|
+ // 错误处理(调试时启用)
|
|
|
+ if ($result === false) {
|
|
|
+ $error = curl_error($curl);
|
|
|
+ $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
|
|
+ throw new Exception("CURL请求失败: HTTP状态码 $httpCode, 错误信息: $error");
|
|
|
}
|
|
|
-
|
|
|
- .quality-control-score {
|
|
|
- font-size: 16px;
|
|
|
- color: #666;
|
|
|
- margin-left: auto; /* 自动左外边距,推至最右 */
|
|
|
- }
|
|
|
|
|
|
- /* 得分数字高亮 */
|
|
|
- #qualitycontrolscore {
|
|
|
- color: #e74c3c; /* 红色强调 */
|
|
|
- font-weight: bold;
|
|
|
- margin: 0 3px; /* 左右间距 */
|
|
|
+ curl_close($curl);
|
|
|
+ return $result;
|
|
|
}
|
|
|
- </style>
|
|
|
- </body>
|
|
|
-</html>
|
|
|
+exit;
|