|
@@ -55,6 +55,15 @@
|
|
|
.ext-link{color:#002FA7;text-decoration:underline;word-break:break-all}
|
|
.ext-link{color:#002FA7;text-decoration:underline;word-break:break-all}
|
|
|
.ext-link:hover{color:#C41E3A}
|
|
.ext-link:hover{color:#C41E3A}
|
|
|
.ext-link:visited{color:#6a1b9a}
|
|
.ext-link:visited{color:#6a1b9a}
|
|
|
|
|
+ .brand-recs{margin-top:12px;padding-top:10px;border-top:1px dashed #e0c080}
|
|
|
|
|
+ .brand-recs-title{font-size:12px;color:#b8860b;margin-bottom:8px;font-weight:600}
|
|
|
|
|
+ .brand-card{display:block;background:linear-gradient(135deg,#fffdf5,#fff8e1);border:1px solid #f0d060;border-radius:10px;padding:12px 14px;margin-bottom:8px;text-decoration:none;transition:all .2s;cursor:pointer}
|
|
|
|
|
+ .brand-card:hover{background:linear-gradient(135deg,#fff8e1,#fff3c4);border-color:#d4a017;box-shadow:0 2px 8px rgba(180,130,20,.12);transform:translateY(-1px)}
|
|
|
|
|
+ .brand-card-name{font-size:14px;font-weight:700;color:#8b6914;margin-bottom:4px}
|
|
|
|
|
+ .brand-card-desc{font-size:12px;color:#999074;line-height:1.5}
|
|
|
|
|
+ .brand-card-arrow{float:right;font-size:13px;color:#c4a035;margin-left:8px}
|
|
|
|
|
+ .brand-more-btn{display:block;width:100%;padding:8px;margin-top:6px;background:#fffdf5;border:1px dashed #e0c080;border-radius:8px;font-size:12px;color:#b8860b;cursor:pointer;text-align:center;transition:all .15s}
|
|
|
|
|
+ .brand-more-btn:hover{background:#fff8e1;border-color:#d4a017}
|
|
|
.input-area{display:flex;padding:10px 14px;background:#fff;border-top:1px solid #e0e0e0;gap:8px;flex-shrink:0;z-index:10}
|
|
.input-area{display:flex;padding:10px 14px;background:#fff;border-top:1px solid #e0e0e0;gap:8px;flex-shrink:0;z-index:10}
|
|
|
#previewArea{flex-shrink:0}
|
|
#previewArea{flex-shrink:0}
|
|
|
.input-area input{flex:1;padding:10px 14px;border:1px solid #ddd;border-radius:24px;font-size:14px;outline:none;min-height:44px}
|
|
.input-area input{flex:1;padding:10px 14px;border:1px solid #ddd;border-radius:24px;font-size:14px;outline:none;min-height:44px}
|
|
@@ -129,7 +138,7 @@
|
|
|
<div class="input-area">
|
|
<div class="input-area">
|
|
|
<input type="file" id="imageInput" accept="image/*" onchange="onFilePicked('image')" style="display:none">
|
|
<input type="file" id="imageInput" accept="image/*" onchange="onFilePicked('image')" style="display:none">
|
|
|
<input id="userInput" placeholder="输入药品问题,或上传图片...">
|
|
<input id="userInput" placeholder="输入药品问题,或上传图片...">
|
|
|
- <button class="btn btn-danger" id="sendBtn" onclick="send()">发送</button>
|
|
|
|
|
|
|
+ <button class="btntn-danger" id="sendBtn" onclick="send()">发送</button>
|
|
|
<span style="display:flex;flex-direction:column;align-items:center;gap:2px;flex-shrink:0;cursor:pointer" onclick="document.getElementById('imageInput').click()"><button class="media-btn" type="button">+</button></span>
|
|
<span style="display:flex;flex-direction:column;align-items:center;gap:2px;flex-shrink:0;cursor:pointer" onclick="document.getElementById('imageInput').click()"><button class="media-btn" type="button">+</button></span>
|
|
|
<button class="btn btn-secondary" id="stopBtn" onclick="stopCurrent()" style="display:none">停止</button>
|
|
<button class="btn btn-secondary" id="stopBtn" onclick="stopCurrent()" style="display:none">停止</button>
|
|
|
</div>
|
|
</div>
|
|
@@ -295,6 +304,7 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var STREAMING=false,streamAbort=null,currentIntent='';
|
|
var STREAMING=false,streamAbort=null,currentIntent='';
|
|
|
|
|
+ var sourcesData=[],brandRecsData=[];
|
|
|
var pendingMedia=null;
|
|
var pendingMedia=null;
|
|
|
|
|
|
|
|
// ============================================================
|
|
// ============================================================
|
|
@@ -460,6 +470,8 @@
|
|
|
}
|
|
}
|
|
|
if(!r.ok){contentDiv.innerHTML=md2html('请求失败: HTTP '+r.status);aiDiv.classList.remove('streaming');STREAMING=false;resetSendBtn();return}
|
|
if(!r.ok){contentDiv.innerHTML=md2html('请求失败: HTTP '+r.status);aiDiv.classList.remove('streaming');STREAMING=false;resetSendBtn();return}
|
|
|
sourcesData=[];
|
|
sourcesData=[];
|
|
|
|
|
+ brandRecsData=[];
|
|
|
|
|
+ brandRecsData=[];
|
|
|
var reader=r.body.getReader(),decoder=new TextDecoder(),buffer='',fullText='',eventType='';
|
|
var reader=r.body.getReader(),decoder=new TextDecoder(),buffer='',fullText='',eventType='';
|
|
|
while(true){
|
|
while(true){
|
|
|
var{ done, value }=await reader.read();
|
|
var{ done, value }=await reader.read();
|
|
@@ -483,6 +495,10 @@
|
|
|
try{var meta=JSON.parse(data);if(meta.sources&&meta.sources.length>0)sourcesData=meta.sources}catch(e){}
|
|
try{var meta=JSON.parse(data);if(meta.sources&&meta.sources.length>0)sourcesData=meta.sources}catch(e){}
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
+ if(eventType==='brand_recommend'){
|
|
|
|
|
+ try{var br=JSON.parse(data);brandRecsData=br.recommendations||[];}catch(e){brandRecsData=[]}
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
fullText+=data;
|
|
fullText+=data;
|
|
|
contentDiv.innerHTML=md2html(fullText)+'<span class="cursor">|</span>';
|
|
contentDiv.innerHTML=md2html(fullText)+'<span class="cursor">|</span>';
|
|
|
document.getElementById('chat').scrollTop=document.getElementById('chat').scrollHeight;
|
|
document.getElementById('chat').scrollTop=document.getElementById('chat').scrollHeight;
|
|
@@ -490,9 +506,9 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- contentDiv.innerHTML=md2html(fullText)+buildSourceTags();var c=contentDiv.querySelector('.cursor');if(c)c.remove();
|
|
|
|
|
|
|
+ contentDiv.innerHTML=md2html(fullText)+buildBrandRecs()+buildSourceTags();var c=contentDiv.querySelector('.cursor');if(c)c.remove();
|
|
|
}catch(e){
|
|
}catch(e){
|
|
|
- if(e.name==='AbortError'){contentDiv.innerHTML=md2html(fullText+' <span style="color:var(--rd)">已停止</span>')+buildSourceTags();}
|
|
|
|
|
|
|
+ if(e.name==='AbortError'){contentDiv.innerHTML=md2html(fullText+' <span style="color:var(--rd)">已停止</span>')+buildBrandRecs()+buildSourceTags();}
|
|
|
else{contentDiv.innerHTML=md2html('连接失败: '+esc(e.message));}
|
|
else{contentDiv.innerHTML=md2html('连接失败: '+esc(e.message));}
|
|
|
}
|
|
}
|
|
|
aiDiv.classList.remove('streaming');STREAMING=false;streamAbort=null;resetSendBtn();
|
|
aiDiv.classList.remove('streaming');STREAMING=false;streamAbort=null;resetSendBtn();
|
|
@@ -573,6 +589,61 @@
|
|
|
streamAsk(query,media,aiDiv,contentDiv);
|
|
streamAsk(query,media,aiDiv,contentDiv);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function buildBrandRecs(){
|
|
|
|
|
+ if(!brandRecsData||!brandRecsData.length)return'';
|
|
|
|
|
+ var SHOW_MAX=3;
|
|
|
|
|
+ var showRecs=brandRecsData.slice(0,SHOW_MAX);
|
|
|
|
|
+ var extraRecs=brandRecsData.slice(SHOW_MAX);
|
|
|
|
|
+ var h='<div class="brand-recs">';
|
|
|
|
|
+ h+='<div class="brand-recs-title">💊 品牌药品推荐</div>';
|
|
|
|
|
+ for(var i=0;i<showRecs.length;i++){
|
|
|
|
|
+ var r=showRecs[i];
|
|
|
|
|
+ var name=r.brand_name||'';
|
|
|
|
|
+ var desc=r.description||(r.function||'');
|
|
|
|
|
+ if(desc.length>120)desc=desc.substring(0,120)+'...';
|
|
|
|
|
+ var url=r.jump_url||'';
|
|
|
|
|
+ if(url){
|
|
|
|
|
+ h+='<a class="brand-card" href="'+escAttr(url)+'" target="_blank" rel="noopener">';
|
|
|
|
|
+ h+='<span class="brand-card-arrow">→</span>';
|
|
|
|
|
+ h+='<div class="brand-card-name">'+esc(name)+'</div>';
|
|
|
|
|
+ h+='<div class="brand-card-desc">'+esc(desc)+'</div>';
|
|
|
|
|
+ h+='</a>';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ h+='<div class="brand-card" style="cursor:default">';
|
|
|
|
|
+ h+='<div class="brand-card-name">'+esc(name)+'</div>';
|
|
|
|
|
+ h+='<div class="brand-card-desc">'+esc(desc)+'</div>';
|
|
|
|
|
+ h+='</div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if(extraRecs.length>0){
|
|
|
|
|
+ var moreId='brandMore'+Date.now();
|
|
|
|
|
+ h+='<div id="'+moreId+'" style="display:none">';
|
|
|
|
|
+ for(var j=0;j<extraRecs.length;j++){
|
|
|
|
|
+ var er=extraRecs[j];
|
|
|
|
|
+ var ename=er.brand_name||'';
|
|
|
|
|
+ var edesc=er.description||(er.function||'');
|
|
|
|
|
+ if(edesc.length>120)edesc=edesc.substring(0,120)+'...';
|
|
|
|
|
+ var eurl=er.jump_url||'';
|
|
|
|
|
+ if(eurl){
|
|
|
|
|
+ h+='<a class="brand-card" href="'+escAttr(eurl)+'" target="_blank" rel="noopener">';
|
|
|
|
|
+ h+='<span class="brand-card-arrow">→</span>';
|
|
|
|
|
+ h+='<div class="brand-card-name">'+esc(ename)+'</div>';
|
|
|
|
|
+ h+='<div class="brand-card-desc">'+esc(edesc)+'</div>';
|
|
|
|
|
+ h+='</a>';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ h+='<div class="brand-card" style="cursor:default">';
|
|
|
|
|
+ h+='<div class="brand-card-name">'+esc(ename)+'</div>';
|
|
|
|
|
+ h+='<div class="brand-card-desc">'+esc(edesc)+'</div>';
|
|
|
|
|
+ h+='</div>';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ h+='</div>';
|
|
|
|
|
+ h+='<button class="brand-more-btn" onclick="var d=document.getElementById(\''+moreId+'\');var t=this;if(d.style.display===\'none\'){d.style.display=\'block\';t.textContent=\'▲ 收起 '+extraRecs.length+' 条推荐\'}else{d.style.display=\'none\';t.textContent=\'▼ 展开 '+extraRecs.length+' 条推荐\'}">▼ 展开 '+extraRecs.length+' 条推荐</button>';
|
|
|
|
|
+ }
|
|
|
|
|
+ h+='</div>';
|
|
|
|
|
+ return h;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function buildSourceTags(){
|
|
function buildSourceTags(){
|
|
|
if(!sourcesData||!sourcesData.length)return'';
|
|
if(!sourcesData||!sourcesData.length)return'';
|
|
|
// 按药名分组
|
|
// 按药名分组
|