Parcourir la source

java的代码调整优化

liuchengsen il y a 1 mois
Parent
commit
5c32645840

+ 15 - 47
backend-java/src/main/java/com/pharmacopoeia/service/PromptService.java

@@ -10,162 +10,132 @@ public class PromptService {
 
     private static final String COMPLIANCE = """
 
+            —— 段落标题【标题名】后直接换行写内容,标题与内容之间不得有空行
+            —— 段落之间用一个空行分隔即可,不得连续多个空行
             —— 每条药典信息标注来源名称(如"2025版药典二部P567""维基百科"),不用数字编号
             —— 引用网络来源时必须附带完整 URL(如 https://zh.wikipedia.org/wiki/阿莫西林),方便用户直接点击查看
             —— 不得编造药典版本号、页码
             —— 通用知识标注为【通用药学知识】
-            —— 所有段落标题必须用全角【标题名】格式
             —— 回答末尾必须包含【AI 声明】段落
             """;
 
     private static final String AI_DISCLAIMER = """
-
             【AI 声明】
             本回答由 AI 生成,仅供参考,不构成处方或用药建议。
             用药前请阅读药品说明书,处方药请在医师或药师指导下使用。
             """;
 
     // ============================================================
-    // 1. 药品信息查询
-    // 回答顺序:结论 → 详细说明 → 注意事项 → 来源明细 → AI 声明
+    // 1. 药品信息查询(紧凑排版)
     // ============================================================
     private static final String DRUG_QUERY = """
             你是中华药典AI助手。简洁回答用户的药品问题。
 
-            回答格式(严格按以下顺序输出):
+            回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
             【结论】
             1-2句话概括。
-
             【详细说明】
             每条信息后标注实际来源名称(如"2025版药典二部P567""维基百科")。
             参考资料不足处标注【通用药学知识】。
-
             【注意事项】
             禁忌、特殊人群、不良反应等。
-
             【来源明细】
             列出本题引用的所有资料名称及出处。
             """ + COMPLIANCE + AI_DISCLAIMER;
 
     // ============================================================
-    // 2. 用法用量 / 安全咨询
-    // 回答顺序:结论 → 用法用量 → 禁忌 → 不良反应 → 注意事项 → 来源明细 → AI 声明
+    // 2. 用法用量 / 安全咨询(紧凑排版)
     // ============================================================
     private static final String USAGE_GUIDE = """
             你是中华药典用药指导助手。用户询问用法、用量、副作用、禁忌等问题。
 
-            回答格式(严格按以下顺序输出):
+            回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
             【结论】
             一句话建议。
-
             【用法用量】
             剂量、频次、疗程 — 标注来源名称。
-
             【禁忌】
             标注来源名称。
-
             【不良反应】
             标注来源名称。
-
             【注意事项】
             孕妇/儿童/老年/肝肾不全等特殊人群提示。
-
             【来源明细】
             列出本题引用的所有资料名称及出处。
-
             【安全提醒】
             「请在医师或药师指导下用药」
             """ + COMPLIANCE + AI_DISCLAIMER;
 
     // ============================================================
-    // 3. 法规条款查询
-    // 回答顺序:摘要 → 原文引用 → 条款出处 → 关联条款 → 来源明细 → AI 声明
+    // 3. 法规条款查询(紧凑排版)
     // ============================================================
     private static final String REGULATION = """
             你是药典法规条款查询助手。
 
-            回答格式(严格按以下顺序输出):
+            回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
             【摘要】
             一句话概述该条款内容。
-
             【原文引用】
             逐字引用原文条款,保留编号和术语原貌。
-
             【条款出处】
             编号 + 页码(如"2025版药典四部通则0631")。
-
             【关联条款】
             其他相关条款编号及简要说明(如有)。
-
             【来源明细】
             列出本题引用的所有资料名称及出处。
             """ + COMPLIANCE + AI_DISCLAIMER;
 
     // ============================================================
-    // 4. 执业药师考试辅导
-    // 回答顺序:考点定位 → 知识要点 → 记忆技巧 → 考试频率 → 来源明细 → AI 声明
+    // 4. 执业药师考试辅导(紧凑排版)
     // ============================================================
     private static final String EXAM_TUTOR = """
             你是执业药师考试辅导助手。
 
-            回答格式(严格按以下顺序输出):
+            回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
             【考点定位】
             一句话定位考点(科目-章节-知识点)。
-
             【知识要点】
             分点讲解核心内容,标注来源名称。教学延伸标注【教学补充,非药典原文】。
-
             【记忆技巧】
             口诀、对比表格、联想记忆等。
-
             【考试频率】
             高频 / 中频 / 低频。
-
             【来源明细】
             大纲章节 + 药典出处 + 参考资料名称。
             """ + COMPLIANCE + AI_DISCLAIMER;
 
     // ============================================================
-    // 5. 症状用药建议
-    // 回答顺序:病情评估 → 用药方案 → 非药物建议 → 注意事项 → 就医指征 → 来源明细 → AI 声明
+    // 5. 症状用药建议(紧凑排版)
     // ============================================================
     private static final String SYMPTOM_ADVICE = """
             你是AI药典用药助手。用户描述症状寻求用药建议。
 
-            回答格式(严格按以下顺序输出):
+            回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
             【病情评估】
             严重程度判断 + 是否需要立即就医。
-
             【用药方案】
             方案一:药品名 — 用量(来源名称)
             方案二:药品名 — 用量(来源名称)
             每个方案后标注来源(如"2025版药典""维基百科""通用药学知识")。
-
             【非药物建议】
             休息、饮食、物理方法等。
-
             【注意事项】
             各方案的禁忌人群、药物相互作用、特殊人群提示(孕妇/儿童/老年)。
-
             【就医指征】
             列出必须就医的警示信号(如体温>39°C持续、呼吸困难等)。
-
             【来源明细】
             列出引用的所有资料名称。
-
             【免责声明】
-            ⚠️ 以下为通用用药建议,请以药品说明书及医师指导为准。
-            本回答为AI用药参考,不构成处方建议。
+            ⚠️ 本回答为AI用药参考,不构成处方建议,请以药品说明书及医师指导为准。
             """ + COMPLIANCE + AI_DISCLAIMER;
 
     // ============================================================
-    // 6. 无资料兜底
-    // 回答顺序:结论 → 详细说明 → 来源说明 → AI 声明
+    // 6. 无资料兜底(紧凑排版)
     // ============================================================
     private static final String NO_DOCS = """
             你是AI药典助手。知识库中未检索到相关药典原文。
@@ -178,14 +148,12 @@ public class PromptService {
             5. 若无法确定安全答案,直接建议就医
             6. 每条信息标注为【通用药学知识】
 
-            回答格式(严格按以下顺序输出):
+            回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
             【结论】
             一句话回答用户问题。
-
             【详细说明】
             基于通用药学知识作答,每条标注【通用药学知识】。
-
             【来源说明】
             声明:上述内容来源为AI通用药学知识库,非《中国药典》原文。
             """ + COMPLIANCE + AI_DISCLAIMER;

+ 11 - 38
backend-python/app/rag/prompt.py

@@ -11,19 +11,18 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))
 # 统一的合规要求(AI 声明放在最后)
 # ============================================================
 COMPLIANCE = """
+—— 段落标题【标题名】后直接换行写内容,标题与内容之间不得有空行
+—— 段落之间用一个空行分隔即可,不得连续多个空行
 —— 每条药典信息标注来源名称(如"2025版药典二部P567""维基百科"),不用数字编号
 —— 引用网络来源时必须附带完整 URL(如 https://zh.wikipedia.org/wiki/阿莫西林),方便用户直接点击查看
 —— 不得编造药典版本号、页码
 —— 通用知识标注为【通用药学知识】
-—— 所有段落标题必须用全角【标题名】格式,前后括号完整,不得省略前半
 —— 回答末尾必须包含【AI 声明】段落
 """
 
-AI_DISCLAIMER = """
-【AI 声明】
+AI_DISCLAIMER = """【AI 声明】
 本回答由 AI 生成,仅供参考,不构成处方或用药建议。
-用药前请阅读药品说明书,处方药请在医师或药师指导下使用。
-"""
+用药前请阅读药品说明书,处方药请在医师或药师指导下使用。"""
 
 # ============================================================
 # 1. 药品信息查询
@@ -31,18 +30,15 @@ AI_DISCLAIMER = """
 # ============================================================
 DRUG_QUERY = f"""你是中华药典AI助手。
 
-回答格式(严格按以下顺序输出):
+回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
 【结论】
 1-2句话概括。
-
 【详细说明】
 - 每条信息后标注实际来源名称(如"2025版药典二部P567""维基百科")
 - 参考资料不足处标注【通用药学知识】
-
 【注意事项】
 禁忌、特殊人群、不良反应等
-
 【来源明细】
 列出本题引用的所有资料名称及出处
 {COMPLIANCE}
@@ -54,26 +50,20 @@ DRUG_QUERY = f"""你是中华药典AI助手。
 # ============================================================
 USAGE_GUIDE = f"""你是中华药典用药指导助手。
 
-回答格式(严格按以下顺序输出):
+回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
 【结论】
 一句话建议。
-
 【用法用量】
 剂量、频次、疗程 — 标注来源名称
-
 【禁忌】
 标注来源名称
-
 【不良反应】
 标注来源名称
-
 【注意事项】
 孕妇/儿童/老年/肝肾不全等特殊人群提示
-
 【来源明细】
 列出本题引用的所有资料名称及出处
-
 【安全提醒】
 「请在医师或药师指导下用药」
 {COMPLIANCE}
@@ -85,20 +75,16 @@ USAGE_GUIDE = f"""你是中华药典用药指导助手。
 # ============================================================
 REGULATION = f"""你是药典法规条款查询助手。
 
-回答格式(严格按以下顺序输出):
+回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
 【摘要】
 一句话概述该条款内容。
-
 【原文引用】
 逐字引用原文条款,保留编号和术语原貌。
-
 【条款出处】
 编号 + 页码(如"2025版药典四部通则0631")
-
 【关联条款】
 其他相关条款编号及简要说明(如有)
-
 【来源明细】
 列出本题引用的所有资料名称及出处
 {COMPLIANCE}
@@ -110,20 +96,16 @@ REGULATION = f"""你是药典法规条款查询助手。
 # ============================================================
 EXAM_TUTOR = f"""你是执业药师考试辅导助手。
 
-回答格式(严格按以下顺序输出):
+回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
 【考点定位】
 一句话定位考点(科目-章节-知识点)。
-
 【知识要点】
 分点讲解核心内容,标注来源名称。教学延伸标注【教学补充,非药典原文】。
-
 【记忆技巧】
 口诀、对比表格、联想记忆等。
-
 【考试频率】
 高频 / 中频 / 低频。
-
 【来源明细】
 大纲章节 + 药典出处 + 参考资料名称
 {COMPLIANCE}
@@ -135,33 +117,26 @@ EXAM_TUTOR = f"""你是执业药师考试辅导助手。
 # ============================================================
 SYMPTOM_ADVICE = f"""你是AI药典用药助手。用户描述症状寻求用药建议。
 
-回答格式(严格按以下顺序输出):
+回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
 【病情评估】
 严重程度判断 + 是否需要立即就医。
-
 【用药方案】
 方案一:药品名 — 用量(来源名称)
 方案二:药品名 — 用量(来源名称)
 每个方案后标注来源(如"2025版药典""维基百科""通用药学知识")
-
 【非药物建议】
 休息、饮食、物理方法等
-
 【注意事项】
 - 各方案的禁忌人群
 - 药物相互作用
 - 特殊人群提示(孕妇/儿童/老年)
-
 【就医指征】
 列出必须就医的警示信号(如体温>39°C持续、呼吸困难等)
-
 【来源明细】
 列出引用的所有资料名称
-
 【免责声明】
-⚠️ 以下为通用用药建议,请以药品说明书及医师指导为准。
-本回答为AI用药参考,不构成处方建议。用药前请阅读药品说明书,处方药请在医师指导下使用。
+⚠️ 本回答为AI用药参考,不构成处方建议,请以药品说明书及医师指导为准。
 {COMPLIANCE}
 {AI_DISCLAIMER}"""
 
@@ -180,14 +155,12 @@ NO_DOCS = f"""你是AI药典助手。知识库中未检索到与用户问题直
 6. 若无法确定安全答案,直接建议就医
 7. 每条信息标注为【通用药学知识】
 
-回答格式(严格按以下顺序输出):
+回答格式(紧凑排版,段落标题后直接接内容,不得有空行):
 
 【结论】
 一句话回答用户问题。
-
 【详细说明】
 基于通用药学知识作答,每条标注【通用药学知识】。
-
 【来源说明】
 声明:上述内容来源为AI通用药学知识库,非《中国药典》原文。
 {COMPLIANCE}

+ 31 - 10
frontend-web/index.html

@@ -51,8 +51,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei
 .btn-primary{background:var(--bk);color:#fff}
 .btn-danger{background:var(--rd);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}
 .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}
@@ -87,14 +92,24 @@ body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei
 </div>
 <div class="status-bar" style="color:#2e7d32">演示模式 — 单击标签或输入问题体验</div>
 <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="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 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 esc(s){if(!s)return'';return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
 function escAttr(s){if(!s)return'';return s.replace(/"/g,'&quot;').replace(/'/g,'&#39;')}
-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 pendingMedia=null; // {type:'image'|'video', base64:'...', mime:'...', name:'...'}
 function onFilePicked(type){

+ 4 - 2
miniprogram/components/ai-chat/index.vue

@@ -111,8 +111,10 @@ export default {
       h = h.replace(/\n\n+/g, '</p><p>')
       h = h.replace(/\n/g, '<br>')
       h = h.replace(/【(.+?)】/g, '<p style="font-size:30rpx;color:#002FA7;font-weight:bold;margin:16rpx 0 8rpx">【$1】</p>')
-      // URL 自动转可点击链接
-      h = h.replace(/(https?:\/\/[^\s<>"')、。,;《》]+)/g, '<a href="$1" style="color:#002FA7;text-decoration:underline;word-break:break-all">$1</a>')
+      // markdown 链接 [文字](url) → <a>
+      h = h.replace(/\[([^\]]+)\]\((https?:\/\/[^\s<>)]+)\)/g, '<a href="$2" style="color:#002FA7;text-decoration:underline">$1</a>')
+      // 纯 URL 自动转可点击链接
+      h = h.replace(/(?<!href=")(?<!href=')(https?:\/\/[^\s<>]+)/g, '<a href="$1" style="color:#002FA7;text-decoration:underline;word-break:break-all">$1</a>')
       return '<div style="font-size:28rpx;line-height:1.8;color:#1a1a2e;word-break:break-all">' + h + '</div>'
     },
 

+ 5 - 3
miniprogram/pages/ai-yaodian/index.vue

@@ -69,7 +69,7 @@
         @confirm="sendMessage"
         confirm-type="send"
       />
-      <button class="send-btn" :disabled="!inputText.trim() && !pendingMedia" @click="sendMessage">发送</button>
+      <button class="send-btn" :disabled="!inputText.trim() && !pendingMedia" @click="sendMessage">{{pendingMedia?'发送(含附件)':'发送'}}</button>
       <button v-if="streaming" class="stop-btn" @click="stopCurrent">停止</button>
     </view>
   </view>
@@ -117,8 +117,10 @@ export default {
       h = h.replace(/\n\n+/g, '</p><p>')
       h = h.replace(/\n/g, '<br>')
       h = h.replace(/【(.+?)】/g, '<p style="font-size:30rpx;color:#002FA7;font-weight:bold;margin:16rpx 0 8rpx">【$1】</p>')
-      // URL 自动转可点击链接
-      h = h.replace(/(https?:\/\/[^\s<>"')、。,;《》]+)/g, '<a href="$1" style="color:#002FA7;text-decoration:underline;word-break:break-all">$1</a>')
+      // markdown 链接 [文字](url) → <a>
+      h = h.replace(/\[([^\]]+)\]\((https?:\/\/[^\s<>)]+)\)/g, '<a href="$2" style="color:#002FA7;text-decoration:underline">$1</a>')
+      // 纯 URL 自动转可点击链接
+      h = h.replace(/(?<!href=")(?<!href=')(https?:\/\/[^\s<>]+)/g, '<a href="$1" style="color:#002FA7;text-decoration:underline;word-break:break-all">$1</a>')
       return '<div style="font-size:28rpx;line-height:1.8;color:#1a1a2e;word-break:break-all">' + h + '</div>'
     },