diff --git a/package-lock.json b/package-lock.json index 82cdf13..cbe1662 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "dependencies": { "@google/genai": "^1.29.0", "@tailwindcss/vite": "^4.1.14", + "@types/crypto-js": "^4.2.2", "@vitejs/plugin-react": "^5.0.4", + "crypto-js": "^4.2.0", "diff": "^9.0.0", "dotenv": "^17.2.3", "express": "^4.21.2", @@ -1492,6 +1494,12 @@ "@types/node": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1904,6 +1912,12 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", diff --git a/package.json b/package.json index 57eaf04..d6c2847 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "dependencies": { "@google/genai": "^1.29.0", "@tailwindcss/vite": "^4.1.14", + "@types/crypto-js": "^4.2.2", "@vitejs/plugin-react": "^5.0.4", + "crypto-js": "^4.2.0", "diff": "^9.0.0", "dotenv": "^17.2.3", "express": "^4.21.2", diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index fd1dbfe..19dd6bd 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -13,6 +13,7 @@ import { defaultReportContent } from '../utils/defaultContent'; import { printDocument } from '../utils/print'; import { storage, getDefaultApiKey } from '../utils/storage'; import { diffChars } from 'diff'; +import CryptoJS from 'crypto-js'; export default function ReportEditor() { const navigate = useNavigate(); @@ -897,11 +898,8 @@ export default function ReportEditor() { const host = 'iat-api.xfyun.cn'; const date = new Date().toUTCString(); const signatureOrigin = `host: "${host}"\ndate: "${date}"\nGET /v2/iat HTTP/1.1`; - const encoder = new TextEncoder(); - const key = await crypto.subtle.importKey('raw', encoder.encode(apiSecret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']); - const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(signatureOrigin)); - const signatureBase64 = btoa(String.fromCharCode(...new Uint8Array(signature))); - const authorizationOrigin = `api_key="${apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signatureBase64}"`; + const signature = CryptoJS.HmacSHA256(signatureOrigin, apiSecret).toString(CryptoJS.enc.Base64); + const authorizationOrigin = `api_key="${apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`; const authorization = btoa(authorizationOrigin); return `wss://iat-api.xfyun.cn/v2/iat?authorization=${encodeURIComponent(authorization)}&date=${encodeURIComponent(date)}&host=${encodeURIComponent(host)}`; } @@ -1054,7 +1052,7 @@ export default function ReportEditor() { } const systemPrompt = aiModifyEnabled ? '你是一名专业的外科医生助理。当前处于【修改模式】。\n我为你提供了当前手术报告的【全局参考内容】作为背景知识,以及你需要修改的【目标区域 HTML 源码】。\n请根据用户的【医生指令】,直接重写并输出目标区域的 HTML。\n\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 必须包含 "reply"(简短回复)和 "updatedHtml"(修改后的完整 HTML 代码)两个字段\n3. 【内容边界】:全局参考内容仅供你理解上下文。你的 updatedHtml 只能包含目标区域本身的内容(例如:如果目标区域是"手术步骤",你就只写步骤)。严禁输出签名、落款、术后总结等属于报告其他部分的结构!\n4. 段落必须使用
标签包裹,段落间绝对不要使用
标签,也不要使用换行符 (\\n)\n5. 输出的 HTML 必须紧凑,标签之间不要有空格或换行\n6. 绝对不要包含任何 Markdown 标记(如 ```json)'
- : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记';
+ : '你是一名专业的外科医生助理。当前处于【对话模式】。\n请仔细阅读我提供的【全局手术报告参考内容】,并根据【医生指令】进行专业解答。\n重要指令:\n1. 必须返回合法的 JSON 对象\n2. 仅包含 "reply"(你的专业回答)一个字段\n3. 不要返回任何 HTML 代码\n4. 绝对不要包含任何 Markdown 标记\n5. 无论图片内容是什么,请严格以纯 JSON 格式输出,禁止任何多余的开头解释';
const payload: any = {
model: modelName,
messages: [
@@ -1106,8 +1104,17 @@ export default function ReportEditor() {
responseJson = JSON.parse(cleanedText);
} catch {
const jsonMatch = cleanedText.match(/\{[\s\S]*\}/);
- if (jsonMatch) responseJson = JSON.parse(jsonMatch[0]);
- else throw new Error('AI 返回格式异常,无法解析 JSON');
+ if (jsonMatch) {
+ try {
+ responseJson = JSON.parse(jsonMatch[0]);
+ } catch {
+ if (!aiModifyEnabled) responseJson = { reply: cleanedText };
+ else throw new Error('AI 返回格式异常,无法解析 JSON');
+ }
+ } else {
+ if (!aiModifyEnabled) responseJson = { reply: cleanedText };
+ else throw new Error('AI 返回格式异常,无法解析 JSON');
+ }
}
if (responseJson.reply) {
setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', content: responseJson.reply }]);
@@ -1121,7 +1128,7 @@ export default function ReportEditor() {
cleanHtml = cleanHtml.replace(/<\/p>\s*
/gi, '
'); cleanHtml = cleanHtml.trim(); cleanHtml = cleanHtml.replace(/>(\s+)<'); - cleanHtml = cleanHtml.replace(/
/gi, '
'); + cleanHtml = cleanHtml.replace(/
/gi, '
'); if (targetRegionEl) { setDiffModal({ isOpen: true, diff --git a/src/utils/defaultContent.ts b/src/utils/defaultContent.ts index 44a1bad..4f3605f 100644 --- a/src/utils/defaultContent.ts +++ b/src/utils/defaultContent.ts @@ -57,7 +57,7 @@ export const defaultReportContent = `
1.患者仰卧位,麻醉成功后,常规消毒术野、铺无菌巾,于脐下穿刺建立CO2气腹,气腹压力为12mmHg,进镜探查无穿刺损伤,分别于剑突下2.0cm、右锁中线肋缘下2.0cm各点穿刺置穿刺器,插入相应手术器械。
2.腹腔镜探查:腹腔内无腹水形成,无明显粘连,肝脏色红质软,无明显结节硬化改变,胆囊大小约 cm× cm× cm,壁轻度水肿,张力可,胆囊三角解剖关系清楚,胆囊管及胆总管无明显扩张。胃、十二指肠、小肠、结肠、脾脏及盆腔未见明显异常。术中诊断:胆囊结石伴慢性胆囊炎。遂行腹腔镜胆囊切除术。
3.切除胆囊:钳夹胆囊颈部并解剖胆囊三角,游离出胆囊动脉及胆囊管,明确胆囊与胆总管的关系,距胆总管0.3cm处近端以一枚可吸收夹,远端夹一枚钛夹夹闭胆囊管,两夹间以剪刀剪断胆囊管,另用一枚可吸收夹夹闭胆囊动脉后离断。顺行游离胆囊浆膜,完整切除胆囊后装入标本袋取出。胆囊床严密止血并覆盖止血材料。
4.检查腹腔内无活动性出血及漏胆后,清点器械纱布无误,拔除腔镜器械,排出腹腔残余气体,缝合各刺孔,术毕。
5.手术顺利,麻醉满意。切除的标本经家属过目后送病理。术中出血约 ml,术中输血成分,输血量,是否有输血不良反应。
1.患者仰卧位,麻醉成功后,常规消毒术野、铺无菌巾,于脐下穿刺建立CO2气腹,气腹压力为12mmHg,进镜探查无穿刺损伤,分别于剑突下2.0cm、右锁中线肋缘下2.0cm各点穿刺置穿刺器,插入相应手术器械。
2.腹腔镜探查:腹腔内无腹水形成,无明显粘连,肝脏色红质软,无明显结节硬化改变,胆囊大小约 cm× cm× cm,壁轻度水肿,张力可,胆囊三角解剖关系清楚,胆囊管及胆总管无明显扩张。胃、十二指肠、小肠、结肠、脾脏及盆腔未见明显异常。术中诊断:胆囊结石伴慢性胆囊炎。遂行腹腔镜胆囊切除术。
3.切除胆囊:钳夹胆囊颈部并解剖胆囊三角,游离出胆囊动脉及胆囊管,明确胆囊与胆总管的关系,距胆总管0.3cm处近端以一枚可吸收夹,远端夹一枚钛夹夹闭胆囊管,两夹间以剪刀剪断胆囊管,另用一枚可吸收夹夹闭胆囊动脉后离断。顺行游离胆囊浆膜,完整切除胆囊后装入标本袋取出。胆囊床严密止血并覆盖止血材料。
4.检查腹腔内无活动性出血及漏胆后,清点器械纱布无误,拔除腔镜器械,排出腹腔残余气体,缝合各刺孔,术毕。
5.手术顺利,麻醉满意。切除的标本经家属过目后送病理。术中出血约 ml,术中输血成分,输血量,是否有输血不良反应。