Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ OpenScribe supports three workflows. **Mixed web mode is the default path.**
- Notes: Anthropic Claude (or other hosted LLM)
- Requires API keys in `apps/web/.env.local`

### OpenClaw + OpenEMR Demo Handoff (desktop)
- The note editor now includes `Send to OpenClaw` (desktop app path).
- Trigger flow: record encounter -> note appears -> click `Send to OpenClaw`.
- OpenScribe sends patient/note context to OpenClaw and requests an OpenEMR note action.

Optional environment variables for demos:

```bash
# OpenClaw CLI (default: openclaw on PATH)
OPENCLAW_BIN=openclaw

# Target OpenClaw agent/session (default: main)
OPENCLAW_AGENT=main

# If set to 1, OpenClaw can deliver responses to a configured channel
OPENCLAW_DELIVER=0

# Optional webhook transport instead of CLI
# OPENCLAW_DEMO_WEBHOOK_URL=http://127.0.0.1:8787/openscribe/handoff
# OPENCLAW_DEMO_WEBHOOK_TOKEN=your-token
```

### FYI Getting API Keys

**OpenAI** (transcription): [platform.openai.com/api-keys](https://platform.openai.com/api-keys) - Sign up → API Keys → Create new secret key
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
const [view, setView] = useState<ViewState>({ type: "idle" })
const [transcriptionStatus, setTranscriptionStatus] = useState<StepStatus>("pending")
const [noteGenerationStatus, setNoteGenerationStatus] = useState<StepStatus>("pending")
const [processingMetrics, setProcessingMetrics] = useState<ProcessingMetrics>({})

Check failure on line 94 in apps/web/src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'processingMetrics' is assigned a value but never used
const [sessionId, setSessionId] = useState<string | null>(null)

const currentEncounterIdRef = useRef<string | null>(null)
Expand Down Expand Up @@ -447,6 +447,16 @@

const handleStreamError = useCallback((event: MessageEvent | Event) => {
const readyState = eventSourceRef.current?.readyState
const hasFinalTranscript = Boolean(finalTranscriptRef.current?.trim())
const hasActiveSession = Boolean(sessionIdRef.current)

// EventSource commonly emits a terminal "error" event on normal close.
// Do not mark processing as failed if we already have final transcript or session is closed.
if (hasFinalTranscript || !hasActiveSession) {
debugWarn("Transcription stream closed", { readyState, hasFinalTranscript, hasActiveSession })
return
}

debugError("Transcription stream error", { event, readyState, apiBaseUrl: apiBaseUrlRef.current })
setTranscriptionStatus("failed")
setProcessingMetrics((prev) => ({
Expand Down Expand Up @@ -737,7 +747,7 @@
processingEndedAt: Date.now(),
}))
}
} catch (error) {

Check failure on line 750 in apps/web/src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'error' is defined but never used
setTranscriptionStatus("failed")
setNoteGenerationStatus("failed")
setProcessingMetrics((prev) => ({
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ export function middleware(request: NextRequest) {
}

export const config = {
matcher: ["/:path*"],
// Avoid running middleware on API routes so large multipart uploads
// (final audio blobs) are not subject to middleware body limits.
matcher: ["/((?!api).*)"],
}
5 changes: 5 additions & 0 deletions apps/web/src/types/desktop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ declare global {
readEntries: (filter?: unknown) => Promise<unknown[]>
exportLog: (options: { data: string; filename: string }) => Promise<{ success: boolean; canceled?: boolean; filePath?: string; error?: string }>
}
openscribeBackend?: {
invoke: (channel: string, ...args: unknown[]) => Promise<unknown>
on: (channel: string, listener: (event: unknown, payload: unknown) => void) => void
removeAllListeners: (channel: string) => void
}
}

interface Window {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"setup": "node scripts/setup-env.js",
"build": "next build apps/web --webpack",
"dev": "next dev apps/web --webpack -p 3001",
"dev:desktop": "concurrently -k \"pnpm dev\" \"pnpm electron:dev\"",
"dev:desktop": "concurrently -k \"pnpm dev:local\" \"pnpm electron:dev\"",
"electron:dev": "wait-on tcp:3001 && cross-env NODE_ENV=development ELECTRON_START_URL=http://localhost:3001 electron packages/shell/main.js",
"medasr:server": ". .venv-med/bin/activate && python scripts/medasr_server.py --port 8001",
"whisper:server": "local-only/openscribe-backend/.venv-backend/bin/python scripts/whisper_server.py --port 8002 --model tiny.en --backend cpp --gpu",
Expand Down
Loading
Loading