|
@@ -51,8 +51,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei
|
|
|
.btn-primary{background:var(--bk);color:#fff}
|
|
.btn-primary{background:var(--bk);color:#fff}
|
|
|
.btn-danger{background:var(--rd);color:#fff}
|
|
.btn-danger{background:var(--rd);color:#fff}
|
|
|
.btn-secondary{background:#999;color:#fff}
|
|
.btn-secondary{background:#999;color:#fff}
|
|
|
-.btn-upload{background:#f0f0f0;color:#333;font-size:18px;padding:6px 10px!important;min-height:44px;line-height:1}
|
|
|
|
|
-.btn-upload:hover{background:#e0e0e0}
|
|
|
|
|
|
|
+.btn-upload{background:#f0f0f0;color:#555;font-size:20px;padding:8px 10px!important;min-height:44px;min-width:44px;line-height:1;border-radius:50%!important;border:1px dashed #ccc;cursor:pointer}
|
|
|
|
|
+.btn-upload:hover{background:#E8EDF8;border-color:#002FA7;color:#002FA7}
|
|
|
|
|
+.media-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;background:linear-gradient(135deg,#E8EDF8,#dce4f5);border:2px solid #002FA7;border-radius:12px;padding:6px 10px;font-size:24px;cursor:pointer;min-height:44px;min-width:52px;gap:0;flex-shrink:0;-webkit-user-select:none;user-select:none;transition:all .15s}
|
|
|
|
|
+.media-btn:hover{background:linear-gradient(135deg,#002FA7,#1a3fbf);color:#fff;border-color:#002FA7;transform:scale(1.05)}
|
|
|
|
|
+.media-btn:active{transform:scale(.95)}
|
|
|
|
|
+.media-label{font-size:10px;font-weight:600;color:#002FA7;white-space:nowrap;margin-top:-2px}
|
|
|
|
|
+.media-btn:hover .media-label{color:#fff}
|
|
|
.btn:disabled{opacity:.4;cursor:default}
|
|
.btn:disabled{opacity:.4;cursor:default}
|
|
|
.tags{display:flex;gap:6px;padding:6px 14px 10px;flex-wrap:wrap;flex-shrink:0}
|
|
.tags{display:flex;gap:6px;padding:6px 14px 10px;flex-wrap:wrap;flex-shrink:0}
|
|
|
.tag-btn{font-size:12px;padding:5px 12px;border-radius:16px;border:1px solid rgba(0,47,167,.2);background:var(--bk-l);color:var(--bk);cursor:pointer;white-space:nowrap;min-height:36px;display:flex;align-items:center;-webkit-user-select:none;user-select:none}
|
|
.tag-btn{font-size:12px;padding:5px 12px;border-radius:16px;border:1px solid rgba(0,47,167,.2);background:var(--bk-l);color:var(--bk);cursor:pointer;white-space:nowrap;min-height:36px;display:flex;align-items:center;-webkit-user-select:none;user-select:none}
|
|
@@ -87,14 +92,24 @@ body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei
|
|
|
</div>
|
|
</div>
|
|
|
<div class="status-bar" style="color:#2e7d32">演示模式 — 单击标签或输入问题体验</div>
|
|
<div class="status-bar" style="color:#2e7d32">演示模式 — 单击标签或输入问题体验</div>
|
|
|
<div id="tabChat">
|
|
<div id="tabChat">
|
|
|
- <div class="chat" id="chat"><div class="welcome"><div class="icon"></div><p>输入药品名称、成分或用药问题<br><small style="color:#aaa">示例:阿莫西林禁忌 / 布洛芬用法用量</small></p></div></div>
|
|
|
|
|
|
|
+ <div class="chat" id="chat"><div class="welcome"><div class="icon">📷💊🎬</div><p>输入文字、上传图片或视频<br><small style="color:#aaa">上传药品包装/说明书照片自动识别 · 也可直接输入药品名提问</small></p></div></div>
|
|
|
<div class="tags"><span class="tag-btn" onclick="quickAsk('阿莫西林禁忌')">阿莫西林禁忌</span><span class="tag-btn" onclick="quickAsk('二甲双胍不良反应')">二甲双胍不良反应</span><span class="tag-btn" onclick="quickAsk('布洛芬用法用量')">布洛芬用法用量</span><span class="tag-btn" onclick="quickAsk('头孢克肟适应症')">头孢克肟适应症</span><span class="tag-btn" onclick="quickAsk('奥美拉唑相互作用')">奥美拉唑相互作用</span><span class="tag-btn" onclick="quickAsk('青霉素过敏处理')">青霉素过敏处理</span></div>
|
|
<div class="tags"><span class="tag-btn" onclick="quickAsk('阿莫西林禁忌')">阿莫西林禁忌</span><span class="tag-btn" onclick="quickAsk('二甲双胍不良反应')">二甲双胍不良反应</span><span class="tag-btn" onclick="quickAsk('布洛芬用法用量')">布洛芬用法用量</span><span class="tag-btn" onclick="quickAsk('头孢克肟适应症')">头孢克肟适应症</span><span class="tag-btn" onclick="quickAsk('奥美拉唑相互作用')">奥美拉唑相互作用</span><span class="tag-btn" onclick="quickAsk('青霉素过敏处理')">青霉素过敏处理</span></div>
|
|
|
- <div class="input-area"><input id="userInput" placeholder="输入药品问题..."><input type="file" id="imageInput" accept="image/*" onchange="onFilePicked('image')" style="display:none"><input type="file" id="videoInput" accept="video/*" onchange="onFilePicked('video')" style="display:none"><button class="btn btn-upload" id="imageBtn" onclick="document.getElementById('imageInput').click()" title="上传图片">📷</button><button class="btn btn-upload" id="videoBtn" onclick="document.getElementById('videoInput').click()" title="上传视频">🎬</button><button class="btn btn-danger" id="sendBtn" onclick="send()">发送</button><button class="btn btn-secondary" id="stopBtn" onclick="stopCurrent()" style="display:none">停止</button></div>
|
|
|
|
|
- <div id="previewArea" style="display:none;padding:6px 14px;background:#fff;border-top:1px solid #eee;display:none;align-items:center;gap:8px">
|
|
|
|
|
- <span id="previewLabel" style="font-size:12px;color:#888"></span>
|
|
|
|
|
- <img id="previewThumb" style="max-width:80px;max-height:60px;border-radius:6px;display:none">
|
|
|
|
|
- <video id="previewVid" style="max-width:120px;max-height:68px;border-radius:6px;display:none" muted></video>
|
|
|
|
|
- <button class="btn btn-secondary" style="font-size:11px;padding:2px 10px;min-height:auto" onclick="clearMedia()">✕ 移除</button>
|
|
|
|
|
|
|
+ <!-- 媒体预览 -->
|
|
|
|
|
+ <div id="previewArea" style="display:none;padding:8px 14px;background:#fffef5;border-top:1px solid #f0e68c;align-items:center;gap:10px;flex-wrap:wrap">
|
|
|
|
|
+ <span id="previewLabel" style="font-size:13px;color:#666;font-weight:500"></span>
|
|
|
|
|
+ <img id="previewThumb" style="max-width:80px;max-height:60px;border-radius:6px;display:none;border:1px solid #ddd">
|
|
|
|
|
+ <video id="previewVid" style="max-width:150px;max-height:84px;border-radius:6px;display:none;border:1px solid #ddd" muted></video>
|
|
|
|
|
+ <button class="btn btn-secondary" style="font-size:12px;padding:4px 12px;min-height:auto" onclick="clearMedia()">✕ 移除附件</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 输入区 -->
|
|
|
|
|
+ <div class="input-area">
|
|
|
|
|
+ <button class="media-btn" onclick="document.getElementById('imageInput').click()" title="上传图片">📷<span class="media-label">图片</span></button>
|
|
|
|
|
+ <button class="media-btn" onclick="document.getElementById('videoInput').click()" title="上传视频">🎬<span class="media-label">视频</span></button>
|
|
|
|
|
+ <input type="file" id="imageInput" accept="image/*" onchange="onFilePicked('image')" style="display:none">
|
|
|
|
|
+ <input type="file" id="videoInput" accept="video/*" onchange="onFilePicked('video')" style="display:none">
|
|
|
|
|
+ <input id="userInput" placeholder="输入药品问题,或上传图片/视频...">
|
|
|
|
|
+ <button class="btn btn-danger" id="sendBtn" onclick="send()">发送</button>
|
|
|
|
|
+ <button class="btn btn-secondary" id="stopBtn" onclick="stopCurrent()" style="display:none">停止</button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div id="tabDrugs" class="hidden">
|
|
<div id="tabDrugs" class="hidden">
|
|
@@ -125,7 +140,13 @@ function intentLabel(i){var m={drug_query:'药品查询',usage_guide:'用法用
|
|
|
function tagClass(i){var m={drug_query:'tag-drug',usage_guide:'tag-usage',symptom_advice:'tag-symptom',regulation:'tag-regulation',exam_tutor:'tag-exam'};return m[i]||'tag-fallback'}
|
|
function tagClass(i){var m={drug_query:'tag-drug',usage_guide:'tag-usage',symptom_advice:'tag-symptom',regulation:'tag-regulation',exam_tutor:'tag-exam'};return m[i]||'tag-fallback'}
|
|
|
function esc(s){if(!s)return'';return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
function esc(s){if(!s)return'';return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
|
function escAttr(s){if(!s)return'';return s.replace(/"/g,'"').replace(/'/g,''')}
|
|
function escAttr(s){if(!s)return'';return s.replace(/"/g,'"').replace(/'/g,''')}
|
|
|
-function md2html(h){if(!h)return'';h=esc(h);h=h.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');h=h.replace(/⚠️?\s*([^\n<]+)/g,'<span class="warn">$1</span>');var s='(?:结论|详细说明|注意事项|来源明细|适应症|用法与用量|用法用量|禁忌|不良反应|副作用|药理|药物相互作用|贮藏|特殊人群|安全提醒|就医指征|非药物建议|用药建议|用药方案|通用药学知识|来源汇总|病情评估|处理方案|就医指征|免责声明|AI 声明|AI声明|来源说明|安全提醒)';h=h.replace(new RegExp('(?<![【])】\\s*('+s+')','g'),'【$1】');h=h.replace(new RegExp('(?<![【])('+s+')】','g'),'【$1】');h=h.replace(/(?<![<br>\n])【(.+?)】/g,'<br>【$1】');h=h.replace(/\n\n+/g,'</p><p>');h=h.replace(/\n/g,'<br>');h=h.replace(/【(.+?)】/g,'<h3>【$1】</h3>');h=h.replace(/(?:<br>|^)[-*•]\s+(.+?)(?=<br>|$)/g,'<li>$1</li>');h=h.replace(/((?:<li>.*?<\/li>)+)/g,'<ul>$1</ul>');h=h.replace(/(?:<br>|^)(\d+)\.\s+(.+?)(?=<br>|$)/g,'<li>$2</li>');h=h.replace(/((?:<li>.*?<\/li>)+)/g,function(m){return m.indexOf('<ul>')>=0?m:'<ol>'+m+'</ol>'});h='<p>'+h+'</p>';h=h.replace(/<p>(<br>)*<\/p>/g,'');h=h.replace(/<p><\/p>/g,'');h=h.replace(/(https?:\/\/[^\s<>"')、。,;《》]+)/g,'<a href="$1" target="_blank" rel="noopener" class="ext-link">$1</a>');return h}
|
|
|
|
|
|
|
+function md2html(h){if(!h)return'';h=esc(h);h=h.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');h=h.replace(/⚠️?\s*([^\n<]+)/g,'<span class="warn">$1</span>');
|
|
|
|
|
+// markdown 链接 [文字](url) → <a>
|
|
|
|
|
+h=h.replace(/\[([^\]]+)\]\((https?:\/\/[^\s<>)]+)\)/g,'<a href="$2" target="_blank" rel="noopener" class="ext-link">$1</a>');
|
|
|
|
|
+var s='(?:结论|详细说明|注意事项|来源明细|适应症|用法与用量|用法用量|禁忌|不良反应|副作用|药理|药物相互作用|贮藏|特殊人群|安全提醒|就医指征|非药物建议|用药建议|用药方案|通用药学知识|来源汇总|病情评估|处理方案|就医指征|免责声明|AI 声明|AI声明|来源说明|安全提醒)';h=h.replace(new RegExp('(?<![【])】\\s*('+s+')','g'),'【$1】');h=h.replace(new RegExp('(?<![【])('+s+')】','g'),'【$1】');h=h.replace(/(?<![<br>\n])【(.+?)】/g,'<br>【$1】');h=h.replace(/\n\n+/g,'</p><p>');h=h.replace(/\n/g,'<br>');h=h.replace(/【(.+?)】/g,'<h3>【$1】</h3>');h=h.replace(/(?:<br>|^)[-*•]\s+(.+?)(?=<br>|$)/g,'<li>$1</li>');h=h.replace(/((?:<li>.*?<\/li>)+)/g,'<ul>$1</ul>');h=h.replace(/(?:<br>|^)(\d+)\.\s+(.+?)(?=<br>|$)/g,'<li>$2</li>');h=h.replace(/((?:<li>.*?<\/li>)+)/g,function(m){return m.indexOf('<ul>')>=0?m:'<ol>'+m+'</ol>'});h='<p>'+h+'</p>';h=h.replace(/<p>(<br>)*<\/p>/g,'');h=h.replace(/<p><\/p>/g,'');
|
|
|
|
|
+// 纯 URL 自动转链接(宽松匹配,在 HTML 标签外)
|
|
|
|
|
+h=h.replace(/(?<!href=")(?<!href=')(?<!">)(?<!'>)(https?:\/\/[^\s<>]+)/g,'<a href="$1" target="_blank" rel="noopener" class="ext-link">$1</a>');
|
|
|
|
|
+return h}
|
|
|
var STREAMING=false,streamTimer=null,messageQueue=[],currentIntent='';
|
|
var STREAMING=false,streamTimer=null,messageQueue=[],currentIntent='';
|
|
|
var pendingMedia=null; // {type:'image'|'video', base64:'...', mime:'...', name:'...'}
|
|
var pendingMedia=null; // {type:'image'|'video', base64:'...', mime:'...', name:'...'}
|
|
|
function onFilePicked(type){
|
|
function onFilePicked(type){
|