在医生PC端的HIS系统中,通过增加语音按钮实现:
约束条件:
┌─────────────────────────────────────────────────┐
│ 医生浏览器环境 │
├─────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────────┐ │
│ │ HIS系统 │ ↔ │ Chrome扩展 │ │
│ │ (网页) │ │ (内容脚本) │ │
│ └──────────┘ └──────────────┘ │
│ ↑ ↕ │
│ │ ┌──────────────┐ │
│ │ │ Background │ │
│ │ │ Script │ │
│ │ └──────────────┘ │
│ │ ↕ │
│ │ ┌──────────────┐ │
│ └────────────→│ 本地语音服务 │ │
│ │ (内网部署) │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────┘
| 层级 | 技术选型 | 说明 |
|---|---|---|
| 界面注入 | Chrome Extension | 在HIS页面添加"语音输入"按钮 |
| 语音识别 | Web Speech API | 浏览器内置,无需外网 |
| 结构化处理 | 本地NLP服务 | 内网部署的医疗NLP模型 |
| 表单填写 | DOM Manipulation | 通过JS自动填表 |
// manifest.json - 扩展配置文件
{
"manifest_version": 3,
"name": "医生语音助手",
"permissions": ["activeTab", "scripting"],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}],
"background": {
"service_worker": "background.js"
}
}
工作流程:
// content.js
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.lang = 'zh-CN';
recognition.continuous = false;
recognition.interimResults = false;
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
console.log('识别结果:', transcript);
// 发送到本地NLP服务进行结构化
sendToNLPServer(transcript);
};
recognition.start();
优点:
缺点:
# 部署开源语音识别引擎
docker run -d -p 9000:9000 \
-e ASR_MODEL=wav2vec2 \
ghcr.io/k2-fsa/sherpa-onnx:latest
// 调用本地语音服务
async function transcribeAudio(audioBlob) {
const response = await fetch('http://localhost:9000/asr', {
method: 'POST',
body: audioBlob
});
return await response.json();
}
使用本地部署的医疗NLP模型:
# 本地NLP服务 (Flask示例)
from flask import Flask, request, jsonify
from transformers import AutoTokenizer, AutoModelForTokenClassification
app = Flask(__name__)
# 加载中文医疗NER模型
tokenizer = AutoTokenizer.from_pretrained("./medical-ner-model")
model = AutoModelForTokenClassification.from_pretrained("./medical-ner-model")
@app.route('/extract', methods=['POST'])
def extract_entities():
text = request.json['text']
# NER识别
entities = recognize_medical_entities(text, model, tokenizer)
# 结构化输出
return jsonify({
"主诉": entities.get("主诉"),
"现病史": entities.get("现病史"),
"诊断": entities.get("诊断"),
"用药": entities.get("用药")
})
def recognize_medical_entities(text, model, tokenizer):
# 实现医疗实体识别逻辑
# 返回结构化数据
pass
推荐模型:
// content.js
function fillForm(structuredData) {
// 方式1: 通过字段ID填写
if (document.getElementById('chief_complaint')) {
document.getElementById('chief_complaint').value = structuredData.主诉;
}
// 方式2: 通过字段名称填写
const inputs = document.querySelectorAll('input[type="text"]');
inputs.forEach(input => {
if (input.placeholder.includes('主诉')) {
input.value = structuredData.主诉;
}
});
// 方式3: 通过XPath定位(复杂表单)
const xpath = "//label[contains(text(), '现病史')]/../input";
const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (result.singleNodeValue) {
result.singleNodeValue.value = structuredData.现病史;
}
// 触发change事件,让HIS系统识别数据变化
document.getElementById('chief_complaint').dispatchEvent(new Event('change'));
}
medical-voice-extension/
├── manifest.json # 扩展配置文件
├── src/
│ ├── content.js # 内容脚本(注入HIS页面)
│ ├── background.js # 后台脚本(处理语音)
│ ├── ui.js # UI组件(语音按钮)
│ └── utils/
│ ├── form-filler.js # 表单填写工具
│ └── nlp-client.js # NLP服务客户端
├── server/ # 本地语音+NLP服务
│ ├── app.py # Flask服务
│ ├── models/ # 医疗NER模型
│ └── asr/ # 语音识别引擎
└── docs/ # 文档
└── deployment.md # 部署指南
打包扩展
cd medical-voice-extension
zip -r extension.zip . -x "*.git*" "node_modules/*"
安装到Chrome
chrome://extensions/安装到Edge
# Edge同样支持Chrome扩展
打开 edge://extensions/
重复Chrome步骤
Firefox降级方案
// 检测浏览器,Firefox使用本地语音服务
if (!window.SpeechRecognition && !window.webkitSpeechRecognition) {
useLocalASRServer();
}
方案A: 使用Sherpa-ONNX(推荐)
# 1. 安装Sherpa-ONNX
git clone https://github.com/k2-fsa/sherpa-onnx
cd sherpa-onnx
mkdir build && cd build
cmake -DSHERPA_ONNX_ENABLE_PYTHON=ON ..
make -j4
# 2. 下载中文模型
cd ../
python ./scripts/sherpa-onnx-downloader.py \
--model-type=paraformer \
--language=zh
# 3. 启动服务
./build/bin/sherpa-onnx-online-websocket-server \
--port=6006 \
--model-dir=./paraformer-zh
方案B: 使用Whisper (更高准确率)
# 1. 安装依赖
pip install faster-whisper flask flask-cors
# 2. 启动服务
python asr_server.py
# asr_server.py
from flask import Flask, request, jsonify
from faster_whisper import WhisperModel
import tempfile
app = Flask(__name__)
model = WhisperModel("base", device="cpu", compute_type="int8")
@app.route('/transcribe', methods=['POST'])
def transcribe():
audio_file = request.files['audio']
with tempfile.NamedTemporaryFile() as tmp:
audio_file.save(tmp.name)
segments, info = model.transcribe(tmp.name, language="zh")
text = "".join([segment.text for segment in segments])
return jsonify({"text": text})
if __name__ == '__main__':
app.run(port=5000, host='0.0.0.0')
# 1. 安装依赖
pip install flask transformers torch
# 2. 下载医疗NER模型
# 使用Harvest或自己训练的模型
git clone https://github.com/chineseGLUE/Harvest.git
cd Harvest/models
wget https://huggingface.co/HuatGPT/HuatGPT-medical-ner/resolve/main/pytorch_model.bin
# 3. 启动服务
cd ../
python nlp_server.py
# nlp_server.py
from flask import Flask, request, jsonify
from transformers import AutoTokenizer, AutoModelForTokenClassification
import torch
app = Flask(__name__)
# 加载模型
tokenizer = AutoTokenizer.from_pretrained("./models/medical-ner")
model = AutoModelForTokenClassification.from_pretrained("./models/medical-ner")
@app.route('/extract', methods=['POST'])
def extract_entities():
data = request.json
text = data['text']
# 分词
inputs = tokenizer(text, return_tensors="pt")
# 预测
with torch.no_grad():
outputs = model(**inputs)
# 解析实体
predictions = torch.argmax(outputs.logits, dim=2)
entities = parse_entities(tokenizer, predictions[0])
return jsonify({
"主诉": entities.get("CHIEF_COMPLAINT", ""),
"现病史": entities.get("HISTORY", ""),
"诊断": entities.get("DIAGNOSIS", ""),
"用药": entities.get("MEDICATION", "")
})
def parse_entities(tokenizer, predictions):
# 实现实体解析逻辑
# 返回结构化数据
pass
if __name__ == '__main__':
app.run(port=5001, host='0.0.0.0')
// src/utils/nlp-client.js
const CONFIG = {
ASR_URL: 'http://localhost:5000/transcribe', // 语音识别服务
NLP_URL: 'http://localhost:5001/extract', // NLP服务
};
async function processVoice(audioBlob) {
// 1. 语音转文字
const asrResult = await fetch(CONFIG.ASR_URL, {
method: 'POST',
body: audioBlob
});
const { text } = await asrResult.json();
// 2. 文字结构化
const nlpResult = await fetch(CONFIG.NLP_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const structuredData = await nlpResult.json();
return structuredData;
}
Chrome官方文档(中文)
MDN Web Docs (中文)
实战教程
Web Speech API教程
Sherpa-ONNX中文文档
Whisper中文教程
Harvest中文医疗文本理解
Transformers中文文档
医疗NER论文
DOM操作教程
XPath教程
| 浏览器 | Web Speech API | 扩展支持 | 推荐方案 |
|---|---|---|---|
| Chrome | ✅ | ✅ | 使用Web Speech API |
| Edge | ✅ | ✅ | 使用Web Speech API |
| Firefox | ❌ | ✅ | 使用本地ASR服务 |
| Safari | ✅ | ⚠️ | 需测试扩展支持 |
常见问题:
解决方案:
// 检测iframe
function fillFormInIframe(data) {
const iframe = document.querySelector('iframe');
if (iframe) {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const input = iframeDoc.getElementById('field_id');
input.value = data.主诉;
}
}
// Shadow DOM穿透
function queryShadowSelector(selector) {
const elements = [];
const walk = (node) => {
if (node.shadowRoot) {
const el = node.shadowRoot.querySelector(selector);
if (el) elements.push(el);
walk(node.shadowRoot);
}
node.childNodes?.forEach(walk);
};
walk(document.body);
return elements;
}
方案优势:
技术栈:
推荐学习路径:
总开发周期: 约5-7个工作日