Add Amazon Transcribe streaming (no S3) with Medical specialty support
- New src/utils/transcribeAWS.js: streams audio directly to AWS Transcribe without requiring an S3 bucket - Supports AWS_TRANSCRIBE_MEDICAL=true for Transcribe Medical (better clinical accuracy: drug names, diagnoses, procedures) - AWS_TRANSCRIBE_SPECIALTY configures specialty (default PRIMARYCARE) - transcribe.js auto-selects AWS when AWS_BEDROCK_REGION is set, or can be forced with TRANSCRIBE_PROVIDER=aws|openai - Falls back to OpenAI Whisper when AWS is not configured - Add @aws-sdk/client-transcribe-streaming as optional dependency - Update .env.example with transcription configuration docs
This commit is contained in:
parent
e0757310c8
commit
14497b3270
5 changed files with 322 additions and 87 deletions
17
.env.example
17
.env.example
|
|
@ -21,10 +21,25 @@ OPENROUTER_API_KEY=sk-or-v1-your-key
|
|||
# AZURE_OPENAI_API_VERSION=2024-02-01
|
||||
|
||||
# ============================================================
|
||||
# Whisper (always OpenAI for now)
|
||||
# TRANSCRIPTION (speech-to-text)
|
||||
# ============================================================
|
||||
|
||||
# Option A: OpenAI Whisper (default if no AWS configured)
|
||||
OPENAI_API_KEY=sk-your-openai-key
|
||||
|
||||
# Option B: Amazon Transcribe (HIPAA eligible, no S3 needed)
|
||||
# Uses same AWS credentials as Bedrock above.
|
||||
# Set TRANSCRIBE_PROVIDER=aws to force AWS even if OPENAI_API_KEY is set.
|
||||
# Leave unset to auto-detect (uses AWS when AWS_BEDROCK_REGION is configured).
|
||||
# TRANSCRIBE_PROVIDER=aws
|
||||
|
||||
# Amazon Transcribe Medical — better accuracy for clinical dictation
|
||||
# Knows drug names, diagnoses, procedures, SOAP terminology
|
||||
# HIPAA eligible (ensure your AWS account has a BAA)
|
||||
# AWS_TRANSCRIBE_MEDICAL=true
|
||||
# AWS_TRANSCRIBE_SPECIALTY=PRIMARYCARE
|
||||
# Other options: CARDIOLOGY, NEUROLOGY, ONCOLOGY, RADIOLOGY, UROLOGY
|
||||
|
||||
# Optional
|
||||
ELEVENLABS_API_KEY=
|
||||
|
||||
|
|
|
|||
246
package-lock.json
generated
246
package-lock.json
generated
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"name": "pediatric-ai-scribe",
|
||||
"version": "3.1.0",
|
||||
"version": "7.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pediatric-ai-scribe",
|
||||
"version": "3.1.0",
|
||||
"version": "7.0.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-transcribe-streaming": "^3.700.0",
|
||||
"@marp-team/marp-cli": "^4.3.1",
|
||||
"@marp-team/marp-core": "^4.3.0",
|
||||
"@tiptap/core": "^3.20.4",
|
||||
|
|
@ -36,7 +37,8 @@
|
|||
"speakeasy": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.700.0"
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.700.0",
|
||||
"@aws-sdk/client-transcribe-streaming": "^3.1017.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/crc32": {
|
||||
|
|
@ -248,11 +250,67 @@
|
|||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-transcribe-streaming": {
|
||||
"version": "3.1017.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-transcribe-streaming/-/client-transcribe-streaming-3.1017.0.tgz",
|
||||
"integrity": "sha512-7lnA73jWQPP5bkyBwFowLOsNkBpJfC9CcA4EXiCE4ivV0Vah225yURMTuNxhNXBAnszA3pmjB9y/eS5R14+vSg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.25",
|
||||
"@aws-sdk/eventstream-handler-node": "^3.972.11",
|
||||
"@aws-sdk/middleware-eventstream": "^3.972.8",
|
||||
"@aws-sdk/middleware-host-header": "^3.972.8",
|
||||
"@aws-sdk/middleware-logger": "^3.972.8",
|
||||
"@aws-sdk/middleware-recursion-detection": "^3.972.8",
|
||||
"@aws-sdk/middleware-sdk-transcribe-streaming": "^3.972.8",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.25",
|
||||
"@aws-sdk/middleware-websocket": "^3.972.13",
|
||||
"@aws-sdk/region-config-resolver": "^3.972.9",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@aws-sdk/util-endpoints": "^3.996.5",
|
||||
"@aws-sdk/util-user-agent-browser": "^3.972.8",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.11",
|
||||
"@smithy/config-resolver": "^4.4.13",
|
||||
"@smithy/core": "^3.23.12",
|
||||
"@smithy/eventstream-serde-browser": "^4.2.12",
|
||||
"@smithy/eventstream-serde-config-resolver": "^4.3.12",
|
||||
"@smithy/eventstream-serde-node": "^4.2.12",
|
||||
"@smithy/fetch-http-handler": "^5.3.15",
|
||||
"@smithy/hash-node": "^4.2.12",
|
||||
"@smithy/invalid-dependency": "^4.2.12",
|
||||
"@smithy/middleware-content-length": "^4.2.12",
|
||||
"@smithy/middleware-endpoint": "^4.4.27",
|
||||
"@smithy/middleware-retry": "^4.4.44",
|
||||
"@smithy/middleware-serde": "^4.2.15",
|
||||
"@smithy/middleware-stack": "^4.2.12",
|
||||
"@smithy/node-config-provider": "^4.3.12",
|
||||
"@smithy/node-http-handler": "^4.5.0",
|
||||
"@smithy/protocol-http": "^5.3.12",
|
||||
"@smithy/smithy-client": "^4.12.7",
|
||||
"@smithy/types": "^4.13.1",
|
||||
"@smithy/url-parser": "^4.2.12",
|
||||
"@smithy/util-base64": "^4.3.2",
|
||||
"@smithy/util-body-length-browser": "^4.2.2",
|
||||
"@smithy/util-body-length-node": "^4.2.3",
|
||||
"@smithy/util-defaults-mode-browser": "^4.3.43",
|
||||
"@smithy/util-defaults-mode-node": "^4.2.47",
|
||||
"@smithy/util-endpoints": "^3.3.3",
|
||||
"@smithy/util-middleware": "^4.2.12",
|
||||
"@smithy/util-retry": "^4.2.12",
|
||||
"@smithy/util-utf8": "^4.2.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/core": {
|
||||
"version": "3.973.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.23.tgz",
|
||||
"integrity": "sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.973.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.24.tgz",
|
||||
"integrity": "sha512-vvf82RYQu2GidWAuQq+uIzaPz9V0gSCXVqdVzRosgl5rXcspXOpSD3wFreGGW6AYymPr97Z69kjVnLePBxloDw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
|
|
@ -274,13 +332,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-env": {
|
||||
"version": "3.972.21",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.21.tgz",
|
||||
"integrity": "sha512-BkAfKq8Bd4shCtec1usNz//urPJF/SZy14qJyxkSaRJQ/Vv1gVh0VZSTmS7aE6aLMELkFV5wHHrS9ZcdG8Kxsg==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.22",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.22.tgz",
|
||||
"integrity": "sha512-cXp0VTDWT76p3hyK5D51yIKEfpf6/zsUvMfaB8CkyqadJxMQ8SbEeVroregmDlZbtG31wkj9ei0WnftmieggLg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
"@smithy/types": "^4.13.1",
|
||||
|
|
@ -291,13 +348,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-http": {
|
||||
"version": "3.972.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.23.tgz",
|
||||
"integrity": "sha512-4XZ3+Gu5DY8/n8zQFHBgcKTF7hWQl42G6CY9xfXVo2d25FM/lYkpmuzhYopYoPL1ITWkJ2OSBQfYEu5JRfHOhA==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.24.tgz",
|
||||
"integrity": "sha512-h694K7+tRuepSRJr09wTvQfaEnjzsKZ5s7fbESrVds02GT/QzViJ94/HCNwM7bUfFxqpPXHxulZfL6Cou0dwPg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/fetch-http-handler": "^5.3.15",
|
||||
"@smithy/node-http-handler": "^4.5.0",
|
||||
|
|
@ -313,20 +369,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.972.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.23.tgz",
|
||||
"integrity": "sha512-PZLSmU0JFpNCDFReidBezsgL5ji9jOBry8CnZdw4Jj6d0K2z3Ftnp44NXgADqYx5BLMu/ZHujfeJReaDoV+IwQ==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.24.tgz",
|
||||
"integrity": "sha512-O46fFmv0RDFWiWEA9/e6oW92BnsyAXuEgTTasxHligjn2RCr9L/DK773m/NoFaL3ZdNAUz8WxgxunleMnHAkeQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.21",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.21",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.23",
|
||||
"@aws-sdk/nested-clients": "^3.996.13",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.22",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.24",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.24",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.22",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.24",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.24",
|
||||
"@aws-sdk/nested-clients": "^3.996.14",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/credential-provider-imds": "^4.2.12",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
|
|
@ -339,14 +394,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-login": {
|
||||
"version": "3.972.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.23.tgz",
|
||||
"integrity": "sha512-OmE/pSkbMM3dCj1HdOnZ5kXnKK+R/Yz+kbBugraBecp0pGAs21eEURfQRz+1N2gzIHLVyGIP1MEjk/uSrFsngg==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.24.tgz",
|
||||
"integrity": "sha512-sIk8oa6AzDoUhxsR11svZESqvzGuXesw62Rl2oW6wguZx8i9cdGCvkFg+h5K7iucUZP8wyWibUbJMc+J66cu5g==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/nested-clients": "^3.996.13",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/nested-clients": "^3.996.14",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
"@smithy/protocol-http": "^5.3.12",
|
||||
|
|
@ -359,18 +413,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.24.tgz",
|
||||
"integrity": "sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.25",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.25.tgz",
|
||||
"integrity": "sha512-m7dR0Dsva2P+VUpL+VkC0WwiDby5pgmWXkRVDB5rlwv0jXJrQJf7YMtCoM8Wjk0H9jPeCYOxOXXcIgp/qp5Alg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "^3.972.21",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-ini": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.21",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.23",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.22",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.24",
|
||||
"@aws-sdk/credential-provider-ini": "^3.972.24",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.22",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.24",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.24",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/credential-provider-imds": "^4.2.12",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
|
|
@ -383,13 +436,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-process": {
|
||||
"version": "3.972.21",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.21.tgz",
|
||||
"integrity": "sha512-nRxbeOJ1E1gVA0lNQezuMVndx+ZcuyaW/RB05pUsznN5BxykSlH6KkZ/7Ca/ubJf3i5N3p0gwNO5zgPSCzj+ww==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.22",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.22.tgz",
|
||||
"integrity": "sha512-Os32s8/4gTZjBk5BtoS/cuTILaj+K72d0dVG7TCJX/fC4598cxwLDmf1AEHEpER5oL3K//yETjvFaz0V8oO5Xw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.7",
|
||||
|
|
@ -401,15 +453,32 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.972.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.23.tgz",
|
||||
"integrity": "sha512-APUccADuYPLL0f2htpM8Z4czabSmHOdo4r41W6lKEZdy++cNJ42Radqy6x4TopENzr3hR6WYMyhiuiqtbf/nAA==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.24.tgz",
|
||||
"integrity": "sha512-PaFv7snEfypU2yXkpvfyWgddEbDLtgVe51wdZlinhc2doubBjUzJZZpgwuF2Jenl1FBydMhNpMjD6SBUM3qdSA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/nested-clients": "^3.996.13",
|
||||
"@aws-sdk/token-providers": "3.1014.0",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/nested-clients": "^3.996.14",
|
||||
"@aws-sdk/token-providers": "3.1015.0",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.7",
|
||||
"@smithy/types": "^4.13.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.1015.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1015.0.tgz",
|
||||
"integrity": "sha512-3OSD4y110nisRhHzFOjoEeHU4GQL4KpzkX9PxzWaiZe0Yg2+thZKM0Pn9DjYwezH5JYfh/K++xK/SE0IHGrmCQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/nested-clients": "^3.996.14",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.7",
|
||||
|
|
@ -421,14 +490,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.972.23",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.23.tgz",
|
||||
"integrity": "sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.24.tgz",
|
||||
"integrity": "sha512-J6H4R1nvr3uBTqD/EeIPAskrBtET4WFfNhpFySr2xW7bVZOXpQfPjrLSIx65jcNjBmLXzWq8QFLdVoGxiGG/SA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/nested-clients": "^3.996.13",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/nested-clients": "^3.996.14",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/property-provider": "^4.2.12",
|
||||
"@smithy/shared-ini-file-loader": "^4.4.7",
|
||||
|
|
@ -519,14 +587,32 @@
|
|||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.972.24",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.24.tgz",
|
||||
"integrity": "sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ==",
|
||||
"license": "Apache-2.0",
|
||||
"node_modules/@aws-sdk/middleware-sdk-transcribe-streaming": {
|
||||
"version": "3.972.8",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-transcribe-streaming/-/middleware-sdk-transcribe-streaming-3.972.8.tgz",
|
||||
"integrity": "sha512-fBIX+TEwzKnJnQiMjOLUl0480HbKd0enZ77Q5aEVzegT2jHyESEHFWJaB3q64r6xXZYioPxLnuTkDe6xVDjqCA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@aws-sdk/util-format-url": "^3.972.8",
|
||||
"@smithy/eventstream-serde-browser": "^4.2.12",
|
||||
"@smithy/protocol-http": "^5.3.12",
|
||||
"@smithy/signature-v4": "^5.3.12",
|
||||
"@smithy/types": "^4.13.1",
|
||||
"@smithy/uuid": "^1.1.2",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.972.25",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.25.tgz",
|
||||
"integrity": "sha512-QxiMPofvOt8SwSynTOmuZfvvPM1S9QfkESBxB22NMHTRXCJhR5BygLl8IXfC4jELiisQgwsgUby21GtXfX3f/g==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@aws-sdk/util-endpoints": "^3.996.5",
|
||||
"@smithy/core": "^3.23.12",
|
||||
|
|
@ -564,24 +650,23 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/nested-clients": {
|
||||
"version": "3.996.13",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.13.tgz",
|
||||
"integrity": "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.996.14",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.14.tgz",
|
||||
"integrity": "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "^3.973.23",
|
||||
"@aws-sdk/core": "^3.973.24",
|
||||
"@aws-sdk/middleware-host-header": "^3.972.8",
|
||||
"@aws-sdk/middleware-logger": "^3.972.8",
|
||||
"@aws-sdk/middleware-recursion-detection": "^3.972.8",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.24",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.25",
|
||||
"@aws-sdk/region-config-resolver": "^3.972.9",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@aws-sdk/util-endpoints": "^3.996.5",
|
||||
"@aws-sdk/util-user-agent-browser": "^3.972.8",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.10",
|
||||
"@aws-sdk/util-user-agent-node": "^3.973.11",
|
||||
"@smithy/config-resolver": "^4.4.13",
|
||||
"@smithy/core": "^3.23.12",
|
||||
"@smithy/fetch-http-handler": "^5.3.15",
|
||||
|
|
@ -723,13 +808,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/util-user-agent-node": {
|
||||
"version": "3.973.10",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.10.tgz",
|
||||
"integrity": "sha512-E99zeTscCc+pTMfsvnfi6foPpKmdD1cZfOC7/P8UUrjsoQdg9VEWPRD+xdFduKnfPXwcvby58AlO9jwwF6U96g==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.973.11",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.11.tgz",
|
||||
"integrity": "sha512-1qdXbXo2s5MMLpUvw00284LsbhtlQ4ul7Zzdn5n+7p4WVgCMLqhxImpHIrjSoc72E/fyc4Wq8dLtUld2Gsh+lA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.24",
|
||||
"@aws-sdk/middleware-user-agent": "^3.972.25",
|
||||
"@aws-sdk/types": "^3.973.6",
|
||||
"@smithy/node-config-provider": "^4.3.12",
|
||||
"@smithy/types": "^4.13.1",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"speakeasy": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.700.0"
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.700.0",
|
||||
"@aws-sdk/client-transcribe-streaming": "^3.1017.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,49 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const { whisperClient } = require('../utils/ai');
|
||||
const { transcribeWithAWS, isAWSTranscribeConfigured } = require('../utils/transcribeAWS');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } });
|
||||
|
||||
// Determine which provider to use:
|
||||
// TRANSCRIBE_PROVIDER=aws → always use Amazon Transcribe
|
||||
// TRANSCRIBE_PROVIDER=openai → always use OpenAI Whisper
|
||||
// (default) if AWS Bedrock region is set → use AWS Transcribe
|
||||
// otherwise → use OpenAI Whisper
|
||||
function getTranscribeProvider() {
|
||||
var env = process.env.TRANSCRIBE_PROVIDER;
|
||||
if (env === 'aws') return 'aws';
|
||||
if (env === 'openai') return 'openai';
|
||||
// Auto-detect: prefer AWS when Bedrock is already configured
|
||||
if (isAWSTranscribeConfigured()) return 'aws';
|
||||
return 'openai';
|
||||
}
|
||||
|
||||
var provider = getTranscribeProvider();
|
||||
var medical = process.env.AWS_TRANSCRIBE_MEDICAL === 'true';
|
||||
console.log('🎙️ Transcribe provider:', provider + (provider === 'aws' && medical ? ' (Medical)' : ''));
|
||||
|
||||
router.post('/transcribe', authMiddleware, upload.single('audio'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) return res.status(400).json({ error: 'No audio' });
|
||||
if (!whisperClient) return res.status(400).json({ error: 'Whisper not configured' });
|
||||
|
||||
const file = new File([req.file.buffer], 'audio.webm', { type: req.file.mimetype || 'audio/webm' });
|
||||
const result = await whisperClient.audio.transcriptions.create({
|
||||
if (provider === 'aws') {
|
||||
if (!isAWSTranscribeConfigured()) {
|
||||
return res.status(400).json({ error: 'AWS Transcribe not configured. Set AWS_BEDROCK_REGION.' });
|
||||
}
|
||||
var text = await transcribeWithAWS(req.file.buffer, req.file.mimetype || 'audio/webm');
|
||||
return res.json({ success: true, text: text, provider: 'aws-transcribe' });
|
||||
}
|
||||
|
||||
// OpenAI Whisper
|
||||
if (!whisperClient) return res.status(400).json({ error: 'Whisper not configured. Set OPENAI_API_KEY.' });
|
||||
var file = new File([req.file.buffer], 'audio.webm', { type: req.file.mimetype || 'audio/webm' });
|
||||
var result = await whisperClient.audio.transcriptions.create({
|
||||
file, model: 'whisper-1', language: 'en',
|
||||
prompt: 'Medical patient encounter. Pediatric. Clinical terms, diagnoses, medications.'
|
||||
});
|
||||
res.json({ success: true, text: result.text });
|
||||
res.json({ success: true, text: result.text, provider: 'openai-whisper' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
|
|
|||
107
src/utils/transcribeAWS.js
Normal file
107
src/utils/transcribeAWS.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// ============================================================
|
||||
// TRANSCRIBE-AWS.JS — Amazon Transcribe Streaming
|
||||
// No S3 required. Audio streams directly to AWS.
|
||||
//
|
||||
// Env vars:
|
||||
// TRANSCRIBE_PROVIDER=aws — use this instead of Whisper
|
||||
// AWS_TRANSCRIBE_MEDICAL=true — use Transcribe Medical (better
|
||||
// accuracy for clinical dictation)
|
||||
// AWS_TRANSCRIBE_SPECIALTY=PRIMARYCARE — medical specialty
|
||||
// Options: PRIMARYCARE, CARDIOLOGY, NEUROLOGY, ONCOLOGY,
|
||||
// RADIOLOGY, UROLOGY
|
||||
// AWS_BEDROCK_REGION — reused for Transcribe region
|
||||
// AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY — reused credentials
|
||||
// ============================================================
|
||||
|
||||
const CHUNK_SIZE = 32768; // 32 KB per audio chunk
|
||||
|
||||
async function* makeAudioStream(buffer) {
|
||||
for (var i = 0; i < buffer.length; i += CHUNK_SIZE) {
|
||||
yield { AudioEvent: { AudioChunk: buffer.slice(i, i + CHUNK_SIZE) } };
|
||||
// Yield to event loop between chunks to avoid blocking
|
||||
await new Promise(function(r) { setTimeout(r, 0); });
|
||||
}
|
||||
}
|
||||
|
||||
async function transcribeWithAWS(audioBuffer, mimeType) {
|
||||
var TranscribeModule = require('@aws-sdk/client-transcribe-streaming');
|
||||
var TranscribeStreamingClient = TranscribeModule.TranscribeStreamingClient;
|
||||
|
||||
var region = process.env.AWS_BEDROCK_REGION || process.env.AWS_REGION || 'us-east-1';
|
||||
var credentials = process.env.AWS_ACCESS_KEY_ID ? {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
||||
} : undefined;
|
||||
|
||||
var client = new TranscribeStreamingClient({ region: region, credentials: credentials });
|
||||
|
||||
// Map browser MIME type to AWS MediaEncoding
|
||||
// audio/webm;codecs=opus → ogg-opus (same Opus codec, different container)
|
||||
// audio/ogg;codecs=opus → ogg-opus (exact match)
|
||||
// audio/wav → pcm
|
||||
var mediaEncoding = 'ogg-opus';
|
||||
if (mimeType && mimeType.indexOf('wav') !== -1) mediaEncoding = 'pcm';
|
||||
|
||||
var useMedical = process.env.AWS_TRANSCRIBE_MEDICAL === 'true';
|
||||
var specialty = process.env.AWS_TRANSCRIBE_SPECIALTY || 'PRIMARYCARE';
|
||||
|
||||
var transcript = '';
|
||||
|
||||
if (useMedical) {
|
||||
var StartMedicalStreamTranscriptionCommand = TranscribeModule.StartMedicalStreamTranscriptionCommand;
|
||||
var medCmd = new StartMedicalStreamTranscriptionCommand({
|
||||
LanguageCode: 'en-US',
|
||||
MediaSampleRateHertz: 48000,
|
||||
MediaEncoding: mediaEncoding,
|
||||
Specialty: specialty,
|
||||
Type: 'DICTATION',
|
||||
AudioStream: makeAudioStream(audioBuffer)
|
||||
});
|
||||
var medResp = await client.send(medCmd);
|
||||
for await (var event of medResp.TranscriptResultStream) {
|
||||
if (event.TranscriptEvent && event.TranscriptEvent.Transcript) {
|
||||
var results = event.TranscriptEvent.Transcript.Results || [];
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var r = results[i];
|
||||
if (!r.IsPartial && r.Alternatives && r.Alternatives[0]) {
|
||||
transcript += r.Alternatives[0].Transcript + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var StartStreamTranscriptionCommand = TranscribeModule.StartStreamTranscriptionCommand;
|
||||
var cmd = new StartStreamTranscriptionCommand({
|
||||
LanguageCode: 'en-US',
|
||||
MediaSampleRateHertz: 48000,
|
||||
MediaEncoding: mediaEncoding,
|
||||
AudioStream: makeAudioStream(audioBuffer)
|
||||
});
|
||||
var resp = await client.send(cmd);
|
||||
for await (var ev of resp.TranscriptResultStream) {
|
||||
if (ev.TranscriptEvent && ev.TranscriptEvent.Transcript) {
|
||||
var res = ev.TranscriptEvent.Transcript.Results || [];
|
||||
for (var j = 0; j < res.length; j++) {
|
||||
var item = res[j];
|
||||
if (!item.IsPartial && item.Alternatives && item.Alternatives[0]) {
|
||||
transcript += item.Alternatives[0].Transcript + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transcript.trim();
|
||||
}
|
||||
|
||||
// Check if AWS Transcribe is available and configured
|
||||
function isAWSTranscribeConfigured() {
|
||||
try {
|
||||
require('@aws-sdk/client-transcribe-streaming');
|
||||
return !!(process.env.AWS_BEDROCK_REGION || process.env.AWS_REGION);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { transcribeWithAWS, isAWSTranscribeConfigured };
|
||||
Loading…
Reference in a new issue