|
|
@@ -42,6 +42,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei
|
|
|
.source{margin-top:10px;padding-top:8px;border-top:1px solid #eee;font-size:11px;color:#aaa;line-height:1.6}
|
|
|
.source span{color:#888;font-weight:500}
|
|
|
.source-sep{display:block;margin:16px 0 8px;padding-top:12px;border-top:1px dashed #ddd;color:#999;font-size:12px}
|
|
|
+.source-tags{margin-top:12px;padding-top:10px;border-top:1px dashed #ddd}
|
|
|
+.source-tags .src-title{font-size:12px;color:#999;margin-bottom:6px}
|
|
|
+.source-tag{display:inline-block;padding:3px 8px;margin:2px 4px;background:#f0f4ff;border:1px solid #d0d8f0;border-radius:12px;font-size:12px;color:#002FA7;cursor:pointer;transition:all .15s}
|
|
|
+.source-tag:hover{background:#002FA7;color:#fff;border-color:#002FA7}
|
|
|
+.source-tag .src-excerpt{display:none;margin-top:4px;padding:8px 10px;background:#fff;border-radius:6px;font-size:13px;color:#333;line-height:1.8;max-width:500px;word-break:break-word;box-shadow:0 2px 8px rgba(0,0,0,.08)}
|
|
|
+.source-tag.open .src-excerpt{display:block}
|
|
|
+.source-tag.open{background:#002FA7;color:#fff}
|
|
|
+.source-tag .src-excerpt strong{color:#002FA7;font-size:12px}
|
|
|
.ext-link{color:#002FA7;text-decoration:underline;word-break:break-all}
|
|
|
.ext-link:hover{color:#C41E3A}
|
|
|
.ext-link:visited{color:#6a1b9a}
|
|
|
@@ -249,7 +257,8 @@ async function streamAsk(query,media,aiDiv,contentDiv){
|
|
|
var ctrl=streamAbort?streamAbort:new AbortController();
|
|
|
var r=await fetch(endpoint,{method:'POST',headers:authHeaders(),body:body,signal:ctrl.signal});
|
|
|
if(!r.ok){contentDiv.innerHTML=md2html('请求失败: HTTP '+r.status);aiDiv.classList.remove('streaming');STREAMING=false;resetSendBtn();return}
|
|
|
- var reader=r.body.getReader(),decoder=new TextDecoder(),buffer='',fullText='',eventType='',sourcesHtml='';
|
|
|
+ sourcesData=[];
|
|
|
+ var reader=r.body.getReader(),decoder=new TextDecoder(),buffer='',fullText='',eventType='';
|
|
|
while(true){
|
|
|
var{ done, value }=await reader.read();
|
|
|
if(done)break;
|
|
|
@@ -269,7 +278,7 @@ async function streamAsk(query,media,aiDiv,contentDiv){
|
|
|
}
|
|
|
if(eventType==='intent'){currentIntent=data;var tagEl=aiDiv.querySelector('.tag');if(tagEl){tagEl.textContent=intentLabel(data);tagEl.className='tag '+tagClass(data)}continue}
|
|
|
if(eventType==='meta'){
|
|
|
- try{var meta=JSON.parse(data);}catch(e){}
|
|
|
+ try{var meta=JSON.parse(data);if(meta.sources&&meta.sources.length>0)sourcesData=meta.sources}catch(e){}
|
|
|
continue;
|
|
|
}
|
|
|
fullText+=data;
|
|
|
@@ -279,9 +288,9 @@ async function streamAsk(query,media,aiDiv,contentDiv){
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- contentDiv.innerHTML=md2html(fullText)+(sourcesHtml||'');var c=contentDiv.querySelector('.cursor');if(c)c.remove();
|
|
|
+ contentDiv.innerHTML=md2html(fullText)+buildSourceTags();var c=contentDiv.querySelector('.cursor');if(c)c.remove();
|
|
|
}catch(e){
|
|
|
- if(e.name==='AbortError'){contentDiv.innerHTML=md2html(fullText+' <span style="color:var(--rd)">已停止</span>')+(sourcesHtml||'');}
|
|
|
+ if(e.name==='AbortError'){contentDiv.innerHTML=md2html(fullText+' <span style="color:var(--rd)">已停止</span>')+buildSourceTags();}
|
|
|
else{contentDiv.innerHTML=md2html('连接失败: '+esc(e.message));}
|
|
|
}
|
|
|
aiDiv.classList.remove('streaming');STREAMING=false;streamAbort=null;resetSendBtn();
|
|
|
@@ -315,6 +324,35 @@ function doSend(text,media){
|
|
|
streamAsk(query,media,aiDiv,contentDiv);
|
|
|
}
|
|
|
|
|
|
+function buildSourceTags(){
|
|
|
+ if(!sourcesData||!sourcesData.length)return'';
|
|
|
+ var seen=new Set(),tags='',drugName='';
|
|
|
+ for(var i=0;i<sourcesData.length;i++){
|
|
|
+ var s=sourcesData[i],key=(s.section||'')+'|'+(s.name||'');
|
|
|
+ if(seen.has(key)||!s.section||s.section==='正文')continue;
|
|
|
+ seen.add(key);
|
|
|
+ if(!drugName) drugName=s.name||'';
|
|
|
+ var excerpt=(s.excerpt||'').replace(/【.+?】/g,'').replace(/来源:.*$/gm,'').trim();
|
|
|
+ var showExcerpt=excerpt.length>400?excerpt.substring(0,400)+'...':excerpt;
|
|
|
+ tags+='<span class="source-tag" onclick="var p=this.parentNode;this.classList.toggle(\'open\')">'+esc(s.section)+'<div class="src-excerpt"><strong>'+esc(drugName)+' · '+esc(s.source||'')+'</strong><br><br>'+esc(showExcerpt)+'</div></span>';
|
|
|
+ }
|
|
|
+ if(!tags)return'';
|
|
|
+ var h='<div class="source-tags"><div class="src-title">📖 药典原文参考 · '+esc(drugName)+' ('+seen.size+'个栏目,点击展开)</div>'+tags;
|
|
|
+ h+='<div style="margin-top:6px"><a href="javascript:void(0)" onclick="switchTab(\'drugs\');loadDrugDetailBySrc(\''+escAttr(drugName)+'\')" style="font-size:12px;color:#002FA7">🔍 查看完整药品详情 →</a></div>';
|
|
|
+ h+='</div>';
|
|
|
+ return h;
|
|
|
+}
|
|
|
+
|
|
|
+// 根据药名查找 drug_id 并打开详情
|
|
|
+window.loadDrugDetailBySrc=async function(drugName){
|
|
|
+ if(!TOKEN) await getToken();
|
|
|
+ try{
|
|
|
+ var r=await fetch(API_BASE+'/api/v1/drug/search?keyword='+encodeURIComponent(drugName)+'&page_size=1',{headers:authHeaders()});
|
|
|
+ var d=await r.json();var items=d.items||d.data||[];
|
|
|
+ if(items.length>0){loadDrugDetail(items[0].drug_id)}else{alert('未找到药品: '+drugName)}
|
|
|
+ }catch(e){}
|
|
|
+};
|
|
|
+
|
|
|
// 药品库
|
|
|
var ALL_DRUGS=[],drugPage=1,drugTotalPages=1,drugPageSize=20,drugKeyword='';
|
|
|
async function loadDrugList(page){
|
|
|
@@ -361,12 +399,26 @@ window.loadDrugDetail=async function(drugId){
|
|
|
var r=await fetch(API_BASE+'/api/v1/drug/'+encodeURIComponent(drugId),{headers:authHeaders()});
|
|
|
var d=await r.json();var drug=d.data||d;
|
|
|
var dd=document.getElementById('tabDrugs');
|
|
|
- var h='<div class="drug-detail"><button class="btn btn-secondary" style="margin-bottom:12px" onclick="showDrugList()">← 返回药品列表</button>';
|
|
|
+ var topBar='<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e0e0e0">';
|
|
|
+ topBar+='<button onclick="showDrugList()" style="background:none;border:none;font-size:18px;cursor:pointer;padding:4px 8px;color:#002FA7">←</button>';
|
|
|
+ topBar+='<span style="font-size:13px;color:var(--sub)">药品详情</span></div>';
|
|
|
+ var h='<div class="drug-detail">'+topBar;
|
|
|
h+='<h2>'+esc(drug.name||'')+'</h2>';
|
|
|
- h+='<div class="info-row">分类: '+(drug.category||'')+' · '+(drug.subcategory||'')+'</div>';
|
|
|
- h+='<div class="info-row">来源: '+(drug.source_version||'')+' '+(drug.source_volume||'')+'</div>';
|
|
|
- if(drug.sections&&typeof drug.sections==='object'){var keys=Object.keys(drug.sections);for(var k=0;k<keys.length;k++){h+='<h3>【'+esc(keys[k])+'】</h3><div class="section">'+md2html(drug.sections[keys[k]])+'</div>'}}
|
|
|
- h+='<br><button class="btn btn-primary" onclick="switchTab(\'chat\');quickAsk(\''+escAttr(drug.name)+'\')">💬 询问此药</button></div>';
|
|
|
+ h+='<div class="info-row">'+(drug.pinyin||'')+' '+(drug.name_en||'')+'</div>';
|
|
|
+ h+='<div class="info-row">分类: '+(drug.category||'')+(drug.subcategory?' · '+drug.subcategory:'')+'</div>';
|
|
|
+ h+='<div class="info-row">来源: '+(drug.source_version||'')+' '+(drug.source_volume||'')+(drug.source_page?' P'+drug.source_page:'')+'</div>';
|
|
|
+ if(drug.sections&&typeof drug.sections==='object'){var keys=Object.keys(drug.sections);
|
|
|
+ // 快速导航
|
|
|
+ h+='<div style="margin:14px 0;padding:10px 12px;background:#f8f9fc;border-radius:8px;font-size:12px;line-height:2">';
|
|
|
+ h+='<span style="color:var(--sub);font-weight:600">快速跳转:</span>';
|
|
|
+ for(var k=0;k<keys.length;k++){h+='<a href="javascript:void(0)" onclick="document.getElementById(\'sec-'+k+'\').scrollIntoView({behavior:\'smooth\'})" style="margin:0 6px;color:#002FA7;text-decoration:none;white-space:nowrap">'+esc(keys[k])+'</a>'}
|
|
|
+ h+='</div>';
|
|
|
+ for(var k=0;k<keys.length;k++){h+='<h3 id="sec-'+k+'">【'+esc(keys[k])+'】</h3><div class="section">'+md2html(drug.sections[keys[k]])+'</div>'}}
|
|
|
+ var bottomBar='<div style="display:flex;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid #e0e0e0">';
|
|
|
+ bottomBar+='<button class="btn btn-secondary" onclick="showDrugList()" style="flex:1">← 返回列表</button>';
|
|
|
+ bottomBar+='<button class="btn btn-primary" onclick="switchTab(\'chat\');quickAsk(\''+escAttr(drug.name)+'\')" style="flex:1">💬 询问此药</button>';
|
|
|
+ bottomBar+='</div>';
|
|
|
+ h+=bottomBar+'</div>';
|
|
|
dd.innerHTML=h;
|
|
|
}catch(e){document.getElementById('tabDrugs').innerHTML='<div class="welcome"><p>加载失败</p></div>'}
|
|
|
};
|