- TTS: switch to axios, drop voice param (configured in LiteLLM per model)
- STT: log full LiteLLM error body so 500s are diagnosable in logs
- TTS: same error detail logging
- Fix 'ElevenLabs unavailable' toast to generic 'TTS unavailable'
- Add red Stop button to encounter recording UI
- TTS: switch from OpenAI SDK to axios (same fix as STT), drop voice
param since it's configured inside LiteLLM per model
- Fix 'ElevenLabs unavailable' toast shown even when provider is LiteLLM
- Add dedicated red Stop button to encounter recording UI
Vertex AI Chirp via LiteLLM rejects/hangs when 'prompt' and
'response_format' are included — these are OpenAI Whisper-only params.
Send only file + model for LiteLLM/Chirp.
Having LITELLM_API_BASE for AI text was auto-routing audio transcription
through LiteLLM even when the proxy has no Whisper model configured,
causing silent hangs. Now LiteLLM STT only activates when LITELLM_STT_MODEL
is explicitly set. Falls back correctly to AWS Transcribe when configured.
OpenAI SDK's audio.transcriptions.create() hangs with LiteLLM
(no timeout, SDK-level incompatibility with multipart handling).
Use axios + form-data directly with 120s timeout — same approach
as ElevenLabs TTS. Handles both {text:"..."} and plain string responses.
Old pedscribe-v11 cache was serving stale admin.js to browsers
even after server updates. New cache name forces old SW to
deactivate and all clients to get fresh JS on next load.
Also switch JS/CSS from stale-while-revalidate to network-first
so code fixes are picked up immediately.
- LITELLM_MODELS = [] — no hardcoded models, global selector now only
shows what admin has actually added via Search API
- getAvailableModelsWithOverrides: for LiteLLM returns only custom list
- Remove toggle safety check — admin can disable any/all models freely
- Admin panel always reloads on tab open (was cached, showing stale data)
- Add 'Clear all models' button for LiteLLM to wipe and start fresh
- Add POST /config/models/clear-all endpoint
- TRANSCRIBE_PROVIDER=litellm routes audio to LiteLLM /audio/transcriptions
- TTS_PROVIDER=litellm routes to LiteLLM /audio/speech
- Both auto-detect when LITELLM_API_BASE is set (no extra config needed)
- LITELLM_STT_MODEL (default: whisper-1), LITELLM_TTS_MODEL (default: tts-1)
- LITELLM_TTS_VOICE (default: alloy) — alloy/echo/fable/onyx/nova/shimmer
- ElevenLabs still works if ELEVENLABS_API_KEY is set and TTS_PROVIDER=elevenlabs
- Health endpoint now reports tts provider
- Root cause: PUT /config/:key(*) wildcard was registered before
/config/models/toggle and /config/models/default, intercepting them
and returning "value is required" (body had modelId not value)
- Fix: move all model-specific PUT routes before the wildcard
- LiteLLM: return empty built-in list with discovery hint (hardcoded
models don't match user's proxy — must use Search API)
- After adding a discovered model: auto-select it in the default dropdown
- GET /config/models now returns defaultModel so dropdown pre-selects it
- Add Vertex AI provider (Gemini models via @google-cloud/vertexai SDK)
- Add LiteLLM proxy support (OpenAI-compatible, routes to any provider)
- Admin panel: model search/discover from provider API, enable/disable, custom models, set default
- New endpoints: /config/models/discover, /config/models/add-discovered, /config/models/default
- Updated models.js with VERTEX_MODELS and LITELLM_MODELS lists
- Updated health endpoint with vertex + litellm status
Root cause: TWA LauncherActivity.onCreate calls Bitmap.createBitmap on the
splash drawable — the XML layer-list with only a color fill had 0x0 intrinsic
dimensions, causing IllegalArgumentException: "width and height must be > 0".
Fixes:
- Replace splash.xml with splash.png (384x384 blue circle with P logo)
- Add proper PNG launcher icons at all 5 density buckets (mdpi through xxxhdpi)
- Change android:icon from @drawable to @mipmap for proper icon resolution
Audio Backups:
- New audio_backups table in PostgreSQL (bytea, gzip compressed)
- POST /api/audio-backups — upload with gzip compression (level 6)
- GET /api/audio-backups — list user's backups
- GET /api/audio-backups/:id/audio — download decompressed audio
- DELETE /api/audio-backups/:id — delete backup
- Auto-cleanup every hour (24h expiry)
- Frontend saves to server first, falls back to IndexedDB
- Settings shows source badge (server/local) per backup
AI Corrections:
- Corrections list is now expandable — click to view original vs corrected
- Shows red "Original" and green "Corrected to" sections
- Click arrow to expand/collapse each correction
- Date shown on each correction
The TWA LauncherActivity crashed with Resources$NotFoundException (0xffffffff)
because android:value with hex color strings is not supported by
androidbrowserhelper — it expects android:resource pointing to color resources.
- Created res/values/colors.xml with all app colors
- Changed AndroidManifest.xml to use android:resource="@color/..."
- Updated styles.xml to reference color resources
The admin model management (enable/disable, custom models, default override)
had persistent rendering issues. Removed the UI panel — models are managed
via the global model selector in the header, which works reliably. Backend
API endpoints for model config are retained for future use.
- Add GET /api/transcribe/status endpoint — returns whether any server
transcription provider (Whisper/AWS/Local) is configured
- Frontend checks status on login via checkTranscribeStatus()
- When no provider configured: recording stops instantly, keeps live
Web Speech API text, shows friendly toast — no error, no upload wait
- Works in encounter, dictation, and SOAP tabs
- App now works fully out-of-the-box with just an AI provider key
Keep 8KB CHUNK_SIZE (proven stable) but replace 10ms setTimeout delay
with a microtask break every 16 chunks. This avoids the AWS SDK
"Deserialization error: inspect {error}.\$response" while still
eliminating the ~1.25s/MB artificial delay from the old 10ms sleep.
- AWS Transcribe: remove 10ms delay between chunks (was adding ~1.25s/MB),
increase chunk size from 8KB to 32KB (AWS max per frame)
- Add detailed timing logs (ffmpeg, streaming, total) for diagnostics
- OpenAI Whisper: use response_format='text' for faster response parsing
- Frontend: show transcription time in toast, request 16kHz sample rate,
increase bitrate to 32kbps Opus (better quality, still small files)
- Return duration in API response for all providers
The r0adkll/sign-android-release@v1 hardcodes build-tools 29.0.3 which
isn't available. Now uses apksigner from the latest installed build-tools
directly with zipalign + sign + verify steps.
The gradlew stub and missing gradle-wrapper.jar caused CI build to fail.
Now uses gradle/actions/setup-gradle@v4 to install Gradle, then generates
wrapper before building. Also renames signed APK and uploads both signed
and unsigned to GitHub Releases.
- APK: Add WAKE_LOCK, BOOT_COMPLETED, ACCESS_NETWORK_STATE permissions
- APK: Disable allowBackup for medical data security
- APK: AudioRecordingService now acquires wake lock, has stop action in notification
- Serve /.well-known/assetlinks.json for TWA domain verification
- Service worker: cache app shell, stale-while-revalidate for assets, network-first for API
- Admin model management: validate model ID format, prevent built-in conflicts, audit toggle actions, prevent disabling all models
- Bump version to v9.0.0, Docker tag to v9
- Full model management UI in admin panel: toggle models on/off, add custom
model IDs (any OpenRouter/Bedrock ID), set admin-configured default model
- /api/models now returns admin-set default model, frontend respects it
- Toggle switch CSS for clean enable/disable UX
- Backend already had the API endpoints, this adds the missing UI
- Add local Whisper (whisper.cpp / faster-whisper) as transcription provider
Set TRANSCRIBE_PROVIDER=local with configurable model size and binary path
- Upgrade all refine/instruction inputs to resizable textareas across
encounter, dictation, hospital course, chart review, well visit, sick visit
- Make AI memory injection flexible: physician preferences and corrections
are now actively applied (not just "formatting reference"), while still
overridable by current prompt instructions
- OIDC callback now passes only ?sso=ok flag, token stays in
httpOnly cookie (prevents token leaking to logs/referrer/history)
- Frontend auth.js uses cookie-based auth for SSO flow
- Add [PHYSICIAN TEMPLATES] boundary markers around physicianMemories
in all 5 generation routes to mitigate prompt injection
- Consistent boundary format across wellVisit, sickVisit, hpi, soap,
hospitalCourse
OIDC/SSO:
- New /api/auth/oidc route with PKCE for secure authorization
- Supports Azure AD, Okta, Keycloak, PocketID, Google, any OIDC provider
- Admin configurable: issuer, client ID/secret, button label
- Option to disable local auth (force SSO only)
- Auto-creates users on first SSO login, links existing by email
- SSO button on login page, hidden until admin enables OIDC
Firefox:
- Show info toast on first recording that live preview requires
Chrome/Edge; server-side transcription still works in all browsers
AWS Transcribe rejects audio event frames over ~16KB with a
cryptic "Deserialization error" / "Your stream is too big" message
hidden inside the SDK error object. Reducing to 8KB per chunk
fixes both Standard and Medical Transcribe streaming.
When Medical Transcribe fails (wrong IAM permissions, region not
supported), automatically falls back to Standard Transcribe instead
of returning an error. Logs the specific failure reason.
- Add try-catch to /nextcloud/disconnect route (was crashing on DB errors)
- Update docker-compose.yml image tag from v7 to v8
- Remove unused SESSION_SECRET from .env.example
Previously only well-visit and sick-visit used saved physician
templates. Now HPI encounter, HPI dictation, SOAP, and hospital
course all fetch getUserMemoryContext() and pass physicianMemories
to the backend so the AI learns from saved templates/preferences.
- Add deduplication logic to prevent Chrome Speech API from repeating
sentences during long recording sessions (all 4 recording modules)
- Enable AWS Transcribe Medical with PRIMARYCARE specialty in .env
- Bump version to 8.0.0
- transcribeAWS.js: convert browser WebM/Opus → PCM 16kHz mono via
ffmpeg before sending to AWS Transcribe — PCM is unambiguous and
most reliable; gracefully falls back to ogg-opus if ffmpeg absent
- Dockerfile: install ffmpeg (apk add ffmpeg) so Docker image works
out of the box with AWS Transcribe
- README: document Amazon Transcribe setup, ffmpeg requirement,
Transcribe Medical specialty options, and env vars reference
- 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
- Fix word repetition: use sessionFinals pattern so each browser SR
session starts fresh; no overlap when recognition auto-restarts
- Fix HTML injection / '>' parse error: escape < > & in live transcript
innerHTML before inserting speech recognition text
- Add 24 MB blob guard: fall back to live SR transcript if audio file
is too large for Whisper API (long sessions)
- Bump version to 7.0.0, update docker-compose image tag to v7
- Added request flow walkthrough, AI provider selection logic
- Documented database layer (raw pg, no ORM) with helper methods
- Expanded directory structure with detailed per-file descriptions
- Expanded frontend JS descriptions (what each file does)
- Rewrote PDF section with current 50k char limit, supported types
- Updated version history through v6.0
- Re-added Opus 4.6 (JSON sanitizer now handles its output)
- Added Logs & Debugging section with docker logs commands and prefixes
- Added Bedrock Model Notes (inference profiles, maxTokens, JSON sanitization)
- Updated version history through v5.8
- Updated rate limiting docs, build/push process, current image tag
Sonnet 4.6 outputs literal newline characters inside JSON string
values (e.g. in HTML body field) instead of \n escape sequences.
Added sanitizeJsonString that walks the string character by character
and escapes unescaped control chars inside quoted values.