@adminide-stack/yantra-mobile 12.0.141 → 12.0.142-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/features/devices/readings.js.map +1 -1
- package/lib/features/devices/session.js +2 -22
- package/lib/features/devices/session.js.map +1 -1
- package/lib/features/dictate/DictateExperience.js +59 -7
- package/lib/features/dictate/DictateExperience.js.map +1 -1
- package/lib/features/dictate/components/NoteView.js +227 -12
- package/lib/features/dictate/components/NoteView.js.map +1 -1
- package/lib/features/dictate/dictateClient.js +93 -23
- package/lib/features/dictate/dictateClient.js.map +1 -1
- package/lib/features/dictate/dictatePreference.js +19 -0
- package/lib/features/dictate/dictatePreference.js.map +1 -0
- package/lib/features/dictate/noteText.js +17 -2
- package/lib/features/dictate/noteText.js.map +1 -1
- package/lib/features/dictate/privacy.js +96 -0
- package/lib/features/dictate/privacy.js.map +1 -0
- package/lib/features/dictate/shownNote.js +24 -0
- package/lib/features/dictate/shownNote.js.map +1 -0
- package/lib/screens/Devices/index.js +3 -205
- package/lib/screens/Devices/index.js.map +1 -1
- package/package.json +4 -4
- package/lib/features/devices/bodyComposition.js +0 -116
- package/lib/features/devices/bodyComposition.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dictateClient.js","sources":["../../../src/features/dictate/dictateClient.ts"],"sourcesContent":["/**\n * Recording -> SOAP note. The recording is uploaded first and handed to the\n * connector as a URL (connector params travel as one CLI argument, capped at\n * 128 KB - audio never rides inline), then YANTRA_MEETINGS_CLINICAL_NOTE\n * transcribes it with a medical speech model and writes the note.\n */\nimport type { RemoteConnectorExec } from '../canvas/videoEditor/remoteMediaClient';\nimport type { ClinicalNote, MedicationAction } from './types';\n\nexport const DICTATE_CONNECTOR = 'yantra-meetings';\nexport const CLINICAL_NOTE_ACTION = 'YANTRA_MEETINGS_CLINICAL_NOTE';\n/** Transcription plus the note runs ~10 s for two minutes of speech; an hour's dictation stays well inside this. */\nexport const NOTE_TIMEOUT_MS = 10 * 60_000;\n\nexport type DictateStage = 'uploading' | 'writing';\n\nexport interface DictateDeps {\n exec: RemoteConnectorExec;\n upload: (localUri: string) => Promise<string>;\n}\n\nexport interface WrittenNote {\n note: ClinicalNote;\n transcript: string;\n model?: string;\n}\n\nexport class DictateError extends Error {\n constructor(\n readonly kind: 'offline' | 'unavailable' | 'empty' | 'failed',\n message: string,\n ) {\n super(message);\n this.name = 'DictateError';\n }\n}\n\ntype Json = Record<string, unknown>;\nconst asRecord = (v: unknown): Json | null => (v && typeof v === 'object' && !Array.isArray(v) ? (v as Json) : null);\nconst str = (v: unknown): string => (typeof v === 'string' ? v.trim() : '');\nconst strings = (v: unknown): string[] => {\n if (Array.isArray(v)) return v.map(str).filter(Boolean);\n return str(v) ? [str(v)] : [];\n};\nconst ACTIONS: MedicationAction[] = ['start', 'continue', 'change', 'stop', 'mentioned'];\n\n/** The note whatever wraps it ({ data: { note } }, { result: ... }), shaped defensively. */\nexport function unwrapNote(raw: unknown): WrittenNote {\n let layer = asRecord(raw);\n const seen: Json[] = [];\n for (let depth = 0; layer && depth < 6; depth += 1) {\n seen.push(layer);\n const note = asRecord(layer.note);\n if (note) {\n const transcript = asRecord(layer.transcript);\n const meds = Array.isArray(note.medications) ? note.medications : [];\n return {\n note: {\n title: str(note.title) || 'Clinical note',\n subjective: strings(note.subjective),\n objective: strings(note.objective),\n assessment: strings(note.assessment),\n plan: strings(note.plan),\n medications: meds\n .map(asRecord)\n .filter((m): m is Json => !!m && !!str(m.name))\n .map((m) => {\n const action = str(m.action).toLowerCase() as MedicationAction;\n return {\n name: str(m.name),\n dose: str(m.dose),\n route: str(m.route),\n frequency: str(m.frequency),\n action: ACTIONS.includes(action) ? action : 'mentioned',\n };\n }),\n follow_up: str(note.follow_up),\n to_verify: strings(note.to_verify),\n },\n transcript: str(transcript?.text),\n model: str(layer.model) || undefined,\n };\n }\n layer = asRecord(layer.data) ?? asRecord(layer.result);\n }\n const reason = seen\n .flatMap((l) => [l.error, l.message, l.stderr])\n .find((v): v is string => typeof v === 'string' && v.trim().length > 0);\n throw new DictateError('failed', reason ?? 'The note came back empty.');\n}\n\n/** A message a clinician can act on, never a stack trace. */\nexport function dictateErrorText(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n if (err instanceof DictateError && err.kind === 'empty')\n return 'No speech was heard in the recording. Try again closer to the phone.';\n if (/network request failed|offline|not signed in/i.test(message))\n return 'You are offline or signed out. The recording is saved; try again when you are back online.';\n if (/no speech|has no speech/i.test(message))\n return 'No speech was heard in the recording. Try again closer to the phone.';\n // Every exec failure starts with the action's name (\"YANTRA_MEETINGS_CLINICAL_NOTE failed: ...\"), so\n // only cdecli's own words for a missing connector or action mean Dictate is not set up here.\n if (\n /action '[^']*' not found in connector|not found in registry|is not installed|auto-install (failed|did not complete)/i.test(\n message,\n )\n )\n return 'Dictate is not available on this workspace yet. The recording is saved.';\n // Ours, not theirs: the service behind the note (the connector's neutral \"temporarily unavailable\",\n // or, from an older connector, its raw \"all LLM providers failed\"), or the agent itself failing.\n if (\n /temporarily unavailable|problem on our side|all LLM providers failed|transcription failed|HTTP 5\\d\\d/i.test(\n message,\n )\n )\n return 'Yantra could not write the note right now. This is a problem on our side; the recording is saved, so try again in a few minutes.';\n if (/declined by the content safety rules/i.test(message))\n return 'The note could not be written from this dictation. The recording is saved.';\n if (/timed? ?out|abort/i.test(message)) return 'Writing the note took too long. The recording is saved; try again.';\n return 'The note could not be written. The recording is saved; try again.';\n}\n\nexport async function writeNoteFromRecording(\n audioUri: string,\n deps: DictateDeps,\n opts: { keyterms?: string[]; context?: string } = {},\n onStage: (stage: DictateStage) => void = () => undefined,\n): Promise<WrittenNote> {\n onStage('uploading');\n const inputUrl = await deps.upload(audioUri);\n onStage('writing');\n const raw = await deps.exec(\n DICTATE_CONNECTOR,\n CLINICAL_NOTE_ACTION,\n {\n input_url: inputUrl,\n ...(opts.keyterms?.length ? { keyterms: opts.keyterms } : {}),\n ...(opts.context?.trim() ? { context: opts.context.trim() } : {}),\n },\n { timeoutMs: NOTE_TIMEOUT_MS },\n );\n const written = unwrapNote(raw);\n const n = written.note;\n const empty = !written.transcript && ![n.subjective, n.objective, n.assessment, n.plan].some((s) => s.length);\n if (empty) throw new DictateError('empty', 'The dictation has no speech.');\n return written;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dictateClient.js","sources":["../../../src/features/dictate/dictateClient.ts"],"sourcesContent":["/**\n * Recording -> SOAP note. The recording is uploaded first and handed to the\n * connector as a URL (connector params travel as one CLI argument, capped at\n * 128 KB - audio never rides inline), then YANTRA_MEETINGS_CLINICAL_NOTE\n * transcribes it with a medical speech model and writes the note from a\n * transcript whose identifiers are placeholders; it returns the note both ways.\n *\n * Suggestions: YANTRA_MEETINGS_CLINICAL_SUGGEST reads the hidden version only,\n * so nothing identifying goes back to the AI from this phone.\n */\nimport type { RemoteConnectorExec } from '../canvas/videoEditor/remoteMediaClient';\nimport type {\n ClinicalNote,\n MedicationAction,\n NoteIdentifier,\n NoteRedaction,\n NoteSuggestion,\n SuggestionKind,\n} from './types';\n\nexport const DICTATE_CONNECTOR = 'yantra-meetings';\nexport const CLINICAL_NOTE_ACTION = 'YANTRA_MEETINGS_CLINICAL_NOTE';\nexport const SUGGEST_ACTION = 'YANTRA_MEETINGS_CLINICAL_SUGGEST';\n/** A read of one note runs about ten seconds. */\nexport const SUGGEST_TIMEOUT_MS = 4 * 60_000;\n/** Transcription plus the note runs ~10 s for two minutes of speech; an hour's dictation stays well inside this. */\nexport const NOTE_TIMEOUT_MS = 10 * 60_000;\n\nexport type DictateStage = 'uploading' | 'writing';\n\nexport interface DictateDeps {\n exec: RemoteConnectorExec;\n upload: (localUri: string) => Promise<string>;\n}\n\nexport interface WrittenNote {\n note: ClinicalNote;\n transcript: string;\n model?: string;\n /** Absent from a connector that does not hide identifiers yet. */\n redaction?: NoteRedaction;\n}\n\nexport class DictateError extends Error {\n constructor(\n readonly kind: 'offline' | 'unavailable' | 'empty' | 'failed',\n message: string,\n ) {\n super(message);\n this.name = 'DictateError';\n }\n}\n\ntype Json = Record<string, unknown>;\nconst asRecord = (v: unknown): Json | null => (v && typeof v === 'object' && !Array.isArray(v) ? (v as Json) : null);\nconst str = (v: unknown): string => (typeof v === 'string' ? v.trim() : '');\nconst strings = (v: unknown): string[] => {\n if (Array.isArray(v)) return v.map(str).filter(Boolean);\n return str(v) ? [str(v)] : [];\n};\nconst ACTIONS: MedicationAction[] = ['start', 'continue', 'change', 'stop', 'mentioned'];\nconst KINDS: SuggestionKind[] = ['missing', 'safety', 'coding', 'follow_up'];\n\nfunction shapeNote(note: Json): ClinicalNote {\n const meds = Array.isArray(note.medications) ? note.medications : [];\n return {\n title: str(note.title) || 'Clinical note',\n subjective: strings(note.subjective),\n objective: strings(note.objective),\n assessment: strings(note.assessment),\n plan: strings(note.plan),\n medications: meds\n .map(asRecord)\n .filter((m): m is Json => !!m && !!str(m.name))\n .map((m) => {\n const action = str(m.action).toLowerCase() as MedicationAction;\n return {\n name: str(m.name),\n dose: str(m.dose),\n route: str(m.route),\n frequency: str(m.frequency),\n action: ACTIONS.includes(action) ? action : 'mentioned',\n };\n }),\n follow_up: str(note.follow_up),\n to_verify: strings(note.to_verify),\n };\n}\n\n/** Only well-formed placeholders: a malformed one could not be put back. */\nexport function identifiersOf(v: unknown): NoteIdentifier[] {\n return (Array.isArray(v) ? v : [])\n .map(asRecord)\n .filter((i): i is Json => !!i && /^\\[[A-Z]+_\\d+\\]$/.test(str(i.token)) && !!str(i.value))\n .map((i) => ({ token: str(i.token), value: str(i.value), kind: str(i.kind) }));\n}\n\nfunction redactionOf(v: unknown): NoteRedaction | undefined {\n const r = asRecord(v);\n const note = asRecord(r?.note);\n if (!r || !note) return undefined;\n return {\n method: r.method === 'patterns' ? 'patterns' : 'entities',\n note: shapeNote(note),\n transcript: str(r.transcript),\n identifiers: identifiersOf(r.identifiers),\n };\n}\n\n/** The note whatever wraps it ({ data: { note } }, { result: ... }), shaped defensively. */\nexport function unwrapNote(raw: unknown): WrittenNote {\n let layer = asRecord(raw);\n const seen: Json[] = [];\n for (let depth = 0; layer && depth < 6; depth += 1) {\n seen.push(layer);\n const note = asRecord(layer.note);\n if (note) {\n const transcript = asRecord(layer.transcript);\n const redaction = redactionOf(layer.redaction);\n return {\n note: shapeNote(note),\n transcript: str(transcript?.text),\n model: str(layer.model) || undefined,\n ...(redaction ? { redaction } : {}),\n };\n }\n layer = asRecord(layer.data) ?? asRecord(layer.result);\n }\n const reason = seen\n .flatMap((l) => [l.error, l.message, l.stderr])\n .find((v): v is string => typeof v === 'string' && v.trim().length > 0);\n throw new DictateError('failed', reason ?? 'The note came back empty.');\n}\n\n/** A message a clinician can act on, never a stack trace. */\nexport function dictateErrorText(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n if (err instanceof DictateError && err.kind === 'empty')\n return 'No speech was heard in the recording. Try again closer to the phone.';\n if (/network request failed|offline|not signed in/i.test(message))\n return 'You are offline or signed out. The recording is saved; try again when you are back online.';\n if (/no speech|has no speech/i.test(message))\n return 'No speech was heard in the recording. Try again closer to the phone.';\n // Every exec failure starts with the action's name (\"YANTRA_MEETINGS_CLINICAL_NOTE failed: ...\"), so\n // only cdecli's own words for a missing connector or action mean Dictate is not set up here.\n if (\n /action '[^']*' not found in connector|not found in registry|is not installed|auto-install (failed|did not complete)/i.test(\n message,\n )\n )\n return 'Dictate is not available on this workspace yet. The recording is saved.';\n // Ours, not theirs: the service behind the note (the connector's neutral \"temporarily unavailable\",\n // or, from an older connector, its raw \"all LLM providers failed\"), or the agent itself failing.\n if (\n /temporarily unavailable|problem on our side|all LLM providers failed|transcription failed|HTTP 5\\d\\d/i.test(\n message,\n )\n )\n return 'Yantra could not write the note right now. This is a problem on our side; the recording is saved, so try again in a few minutes.';\n if (/declined by the content safety rules/i.test(message))\n return 'The note could not be written from this dictation. The recording is saved.';\n if (/timed? ?out|abort/i.test(message)) return 'Writing the note took too long. The recording is saved; try again.';\n return 'The note could not be written. The recording is saved; try again.';\n}\n\nexport async function writeNoteFromRecording(\n audioUri: string,\n deps: DictateDeps,\n opts: { keyterms?: string[]; context?: string } = {},\n onStage: (stage: DictateStage) => void = () => undefined,\n): Promise<WrittenNote> {\n onStage('uploading');\n const inputUrl = await deps.upload(audioUri);\n onStage('writing');\n const raw = await deps.exec(\n DICTATE_CONNECTOR,\n CLINICAL_NOTE_ACTION,\n {\n input_url: inputUrl,\n ...(opts.keyterms?.length ? { keyterms: opts.keyterms } : {}),\n ...(opts.context?.trim() ? { context: opts.context.trim() } : {}),\n },\n { timeoutMs: NOTE_TIMEOUT_MS },\n );\n const written = unwrapNote(raw);\n const n = written.note;\n const empty = !written.transcript && ![n.subjective, n.objective, n.assessment, n.plan].some((s) => s.length);\n if (empty) throw new DictateError('empty', 'The dictation has no speech.');\n return written;\n}\n\n/** The AI's suggestions on a note, asked for with its hidden version only. */\nexport async function suggestFromNote(\n redaction: NoteRedaction,\n deps: Pick<DictateDeps, 'exec'>,\n): Promise<{ items: NoteSuggestion[]; identifiers: NoteIdentifier[] }> {\n const raw = await deps.exec(\n DICTATE_CONNECTOR,\n SUGGEST_ACTION,\n {\n note: redaction.note,\n transcript: redaction.transcript,\n // Which placeholders exist, so a new one numbers on; never what they stand for.\n identifiers: redaction.identifiers.map(({ token, kind }) => ({ token, kind })),\n },\n { timeoutMs: SUGGEST_TIMEOUT_MS },\n );\n let layer = asRecord(raw);\n for (let depth = 0; layer && depth < 6; depth += 1) {\n if (Array.isArray(layer.suggestions)) {\n const items = layer.suggestions\n .map(asRecord)\n .filter((s): s is Json => !!s && KINDS.includes(str(s.kind) as SuggestionKind) && !!str(s.text))\n .map((s) => ({ kind: str(s.kind) as SuggestionKind, text: str(s.text), why: str(s.why) }));\n return { items, identifiers: identifiersOf(layer.identifiers) };\n }\n const reason = [layer.error, layer.message].find((v): v is string => typeof v === 'string' && !!v.trim());\n if (layer.successful === false && reason) throw new DictateError('failed', reason);\n layer = asRecord(layer.data) ?? asRecord(layer.result);\n }\n throw new DictateError('failed', 'The suggestions came back empty.');\n}\n\n/** Why there are no suggestions, in words a clinician can act on. */\nexport function suggestErrorText(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n if (/network request failed|offline|not signed in/i.test(message))\n return 'You are offline or signed out. Try again when you are back online.';\n if (/action '[^']*' not found in connector|not found in registry|is not installed/i.test(message))\n return 'Suggestions are not available on this workspace yet.';\n if (/temporarily unavailable|problem on our side|all LLM providers failed|HTTP 5\\d\\d/i.test(message))\n return 'Yantra could not read the note right now. This is a problem on our side; try again in a few minutes.';\n if (/timed? ?out|abort/i.test(message)) return 'Reading the note took too long. Try again.';\n return 'The suggestions could not be written. Try again.';\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
let AsyncStorage = null;
|
|
3
|
+
try {
|
|
4
|
+
AsyncStorage = (_a = require("@react-native-async-storage/async-storage").default) != null ? _a : null;
|
|
5
|
+
} catch (e) {
|
|
6
|
+
AsyncStorage = null;
|
|
7
|
+
}
|
|
8
|
+
const KEY = "yantra.dictate.showIdentifiers.v1";
|
|
9
|
+
async function readShowIdentifiers() {
|
|
10
|
+
if (!AsyncStorage) return false;
|
|
11
|
+
try {
|
|
12
|
+
return await AsyncStorage.getItem(KEY) === "1";
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function writeShowIdentifiers(show) {
|
|
18
|
+
AsyncStorage == null ? void 0 : AsyncStorage.setItem(KEY, show ? "1" : "0").catch(() => void 0);
|
|
19
|
+
}export{readShowIdentifiers,writeShowIdentifiers};//# sourceMappingURL=dictatePreference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictatePreference.js","sources":["../../../src/features/dictate/dictatePreference.ts"],"sourcesContent":["/**\n * Whether Dictate shows a note with its names and numbers put back or hidden.\n * Hidden unless the clinician chose otherwise on this phone; either way the AI\n * only ever saw placeholders.\n */\ntype StorageLike = {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n};\n\nlet AsyncStorage: StorageLike | null = null;\ntry {\n // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require\n AsyncStorage = require('@react-native-async-storage/async-storage').default ?? null;\n} catch {\n AsyncStorage = null;\n}\n\nconst KEY = 'yantra.dictate.showIdentifiers.v1';\n\nexport async function readShowIdentifiers(): Promise<boolean> {\n if (!AsyncStorage) return false;\n try {\n return (await AsyncStorage.getItem(KEY)) === '1';\n } catch {\n return false;\n }\n}\n\nexport function writeShowIdentifiers(show: boolean): void {\n AsyncStorage?.setItem(KEY, show ? '1' : '0').catch(() => undefined);\n}\n"],"names":[],"mappings":"AAAA,IAAA,EAAA;AASA,IAAI,YAAmC,GAAA,IAAA;AACvC,IAAI;AAEF,EAAA,YAAA,GAAA,CAAe,EAAQ,GAAA,OAAA,CAAA,2CAA2C,CAAE,CAAA,OAAA,KAArD,IAAgE,GAAA,EAAA,GAAA,IAAA;AACjF,CAAQ,CAAA,OAAA,CAAA,EAAA;AACN,EAAe,YAAA,GAAA,IAAA;AACjB;AACA,MAAM,GAAM,GAAA,mCAAA;AACZ,eAAsB,mBAAwC,GAAA;AAC5D,EAAI,IAAA,CAAC,cAAqB,OAAA,KAAA;AAC1B,EAAI,IAAA;AACF,IAAA,OAAQ,MAAM,YAAA,CAAa,OAAQ,CAAA,GAAG,CAAO,KAAA,GAAA;AAAA,GACvC,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,KAAA;AAAA;AAEX;AACO,SAAS,qBAAqB,IAAqB,EAAA;AACxD,EAAA,YAAA,IAAA,IAAA,GAAA,MAAA,GAAA,YAAA,CAAc,QAAQ,GAAK,EAAA,IAAA,GAAO,GAAM,GAAA,GAAA,CAAA,CAAK,MAAM,MAAM,MAAA,CAAA;AAC3D"}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
const NOTE_DISCLAIMER = "AI draft from a dictation. Review before use.";
|
|
2
|
+
const SUGGESTION_DISCLAIMER = "AI suggestions for you to consider, not medical advice.";
|
|
3
|
+
const SUGGESTION_HEADINGS = {
|
|
4
|
+
safety: "Check for safety",
|
|
5
|
+
missing: "Missing from the note",
|
|
6
|
+
coding: "Coding",
|
|
7
|
+
follow_up: "Follow-up"
|
|
8
|
+
};
|
|
2
9
|
const ACTION_LABEL = {
|
|
3
10
|
start: "start",
|
|
4
11
|
continue: "continue",
|
|
@@ -10,7 +17,7 @@ function medicationLine(med) {
|
|
|
10
17
|
const parts = [med.name, med.dose, med.route, med.frequency].map((p) => p.trim()).filter(Boolean);
|
|
11
18
|
return `${parts.join(" ")} (${ACTION_LABEL[med.action]})`;
|
|
12
19
|
}
|
|
13
|
-
function noteToText(note) {
|
|
20
|
+
function noteToText(note, transcript, suggestions) {
|
|
14
21
|
const lines = [note.title, NOTE_DISCLAIMER];
|
|
15
22
|
const section = (heading, items) => {
|
|
16
23
|
if (!items.length) return;
|
|
@@ -23,6 +30,14 @@ function noteToText(note) {
|
|
|
23
30
|
section("PLAN", note.plan);
|
|
24
31
|
section("MEDICATIONS", note.medications.map(medicationLine));
|
|
25
32
|
if (note.follow_up.trim()) lines.push("", "FOLLOW-UP", note.follow_up.trim());
|
|
33
|
+
if (suggestions == null ? void 0 : suggestions.length) {
|
|
34
|
+
lines.push("", "SUGGESTIONS", SUGGESTION_DISCLAIMER);
|
|
35
|
+
for (const kind of Object.keys(SUGGESTION_HEADINGS)) {
|
|
36
|
+
const group = suggestions.filter((s) => s.kind === kind);
|
|
37
|
+
if (group.length) lines.push(`${SUGGESTION_HEADINGS[kind]}:`, ...group.map((s) => `- ${s.text}${s.why ? ` (${s.why})` : ""}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (transcript == null ? void 0 : transcript.trim()) lines.push("", "TRANSCRIPT", transcript.trim());
|
|
26
41
|
return lines.join("\n");
|
|
27
42
|
}
|
|
28
43
|
function formatDuration(ms) {
|
|
@@ -32,4 +47,4 @@ function formatDuration(ms) {
|
|
|
32
47
|
const s = total % 60;
|
|
33
48
|
const ss = String(s).padStart(2, "0");
|
|
34
49
|
return h ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
35
|
-
}export{NOTE_DISCLAIMER,formatDuration,medicationLine,noteToText};//# sourceMappingURL=noteText.js.map
|
|
50
|
+
}export{NOTE_DISCLAIMER,SUGGESTION_DISCLAIMER,SUGGESTION_HEADINGS,formatDuration,medicationLine,noteToText};//# sourceMappingURL=noteText.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noteText.js","sources":["../../../src/features/dictate/noteText.ts"],"sourcesContent":["/**\n * The note as plain text, for Copy and Share: section headings in capitals,\n * one item per line, the checks first so they cannot be missed once pasted.\n */\nimport type { ClinicalNote, NoteMedication } from './types';\n\nexport const NOTE_DISCLAIMER = 'AI draft from a dictation. Review before use.';\n\nconst ACTION_LABEL: Record<NoteMedication['action'], string> = {\n start: 'start',\n continue: 'continue',\n change: 'change',\n stop: 'stop',\n mentioned: 'mentioned',\n};\n\n/** \"lisinopril 20 mg PO once daily (change)\" - only the parts that were dictated. */\nexport function medicationLine(med: NoteMedication): string {\n const parts = [med.name, med.dose, med.route, med.frequency].map((p) => p.trim()).filter(Boolean);\n return `${parts.join(' ')} (${ACTION_LABEL[med.action]})`;\n}\n\nexport function noteToText(note: ClinicalNote): string {\n const lines: string[] = [note.title, NOTE_DISCLAIMER];\n const section = (heading: string, items: string[]) => {\n if (!items.length) return;\n lines.push('', heading, ...items.map((item) => `- ${item}`));\n };\n section('CHECK BEFORE USE', note.to_verify);\n section('SUBJECTIVE', note.subjective);\n section('OBJECTIVE', note.objective);\n section('ASSESSMENT', note.assessment);\n section('PLAN', note.plan);\n section('MEDICATIONS', note.medications.map(medicationLine));\n if (note.follow_up.trim()) lines.push('', 'FOLLOW-UP', note.follow_up.trim());\n return lines.join('\\n');\n}\n\n/** \"4:07\" or \"1:02:09\". */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000));\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n const ss = String(s).padStart(2, '0');\n return h ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"noteText.js","sources":["../../../src/features/dictate/noteText.ts"],"sourcesContent":["/**\n * The note as plain text, for Copy and Share: section headings in capitals,\n * one item per line, the checks first so they cannot be missed once pasted,\n * and the transcript last, as the screen shows it under the note.\n */\nimport type { ClinicalNote, NoteMedication, NoteSuggestion, SuggestionKind } from './types';\n\nexport const NOTE_DISCLAIMER = 'AI draft from a dictation. Review before use.';\nexport const SUGGESTION_DISCLAIMER = 'AI suggestions for you to consider, not medical advice.';\n/** In the order the screen and the shared text show them. */\nexport const SUGGESTION_HEADINGS: Record<SuggestionKind, string> = {\n safety: 'Check for safety',\n missing: 'Missing from the note',\n coding: 'Coding',\n follow_up: 'Follow-up',\n};\n\nconst ACTION_LABEL: Record<NoteMedication['action'], string> = {\n start: 'start',\n continue: 'continue',\n change: 'change',\n stop: 'stop',\n mentioned: 'mentioned',\n};\n\n/** \"lisinopril 20 mg PO once daily (change)\" - only the parts that were dictated. */\nexport function medicationLine(med: NoteMedication): string {\n const parts = [med.name, med.dose, med.route, med.frequency].map((p) => p.trim()).filter(Boolean);\n return `${parts.join(' ')} (${ACTION_LABEL[med.action]})`;\n}\n\nexport function noteToText(note: ClinicalNote, transcript?: string, suggestions?: NoteSuggestion[] | null): string {\n const lines: string[] = [note.title, NOTE_DISCLAIMER];\n const section = (heading: string, items: string[]) => {\n if (!items.length) return;\n lines.push('', heading, ...items.map((item) => `- ${item}`));\n };\n section('CHECK BEFORE USE', note.to_verify);\n section('SUBJECTIVE', note.subjective);\n section('OBJECTIVE', note.objective);\n section('ASSESSMENT', note.assessment);\n section('PLAN', note.plan);\n section('MEDICATIONS', note.medications.map(medicationLine));\n if (note.follow_up.trim()) lines.push('', 'FOLLOW-UP', note.follow_up.trim());\n if (suggestions?.length) {\n lines.push('', 'SUGGESTIONS', SUGGESTION_DISCLAIMER);\n for (const kind of Object.keys(SUGGESTION_HEADINGS) as SuggestionKind[]) {\n const group = suggestions.filter((s) => s.kind === kind);\n if (group.length)\n lines.push(\n `${SUGGESTION_HEADINGS[kind]}:`,\n ...group.map((s) => `- ${s.text}${s.why ? ` (${s.why})` : ''}`),\n );\n }\n }\n if (transcript?.trim()) lines.push('', 'TRANSCRIPT', transcript.trim());\n return lines.join('\\n');\n}\n\n/** \"4:07\" or \"1:02:09\". */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.floor(ms / 1000));\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n const ss = String(s).padStart(2, '0');\n return h ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
const PLACEHOLDER = /\[[A-Z]+_\d+\]/g;
|
|
21
|
+
const LABEL = {
|
|
22
|
+
NAME: "Name",
|
|
23
|
+
CLINICIAN: "Clinician",
|
|
24
|
+
FACILITY: "Facility",
|
|
25
|
+
ORG: "Organization",
|
|
26
|
+
DOB: "Birth date",
|
|
27
|
+
DATE: "Date",
|
|
28
|
+
AGE: "Age",
|
|
29
|
+
ID: "ID",
|
|
30
|
+
PHONE: "Phone",
|
|
31
|
+
EMAIL: "Email",
|
|
32
|
+
URL: "Link",
|
|
33
|
+
IP: "IP address",
|
|
34
|
+
ADDRESS: "Address"
|
|
35
|
+
};
|
|
36
|
+
function placeholderLabel(token) {
|
|
37
|
+
var _a;
|
|
38
|
+
const m = /^\[([A-Z]+)_(\d+)\]$/.exec(token);
|
|
39
|
+
if (!m) return token;
|
|
40
|
+
return `${(_a = LABEL[m[1]]) != null ? _a : m[1].charAt(0) + m[1].slice(1).toLowerCase()} ${m[2]}`;
|
|
41
|
+
}
|
|
42
|
+
function splitPlaceholders(text) {
|
|
43
|
+
var _a;
|
|
44
|
+
const runs = [];
|
|
45
|
+
let last = 0;
|
|
46
|
+
for (const m of text.matchAll(PLACEHOLDER)) {
|
|
47
|
+
const at = (_a = m.index) != null ? _a : 0;
|
|
48
|
+
if (at > last) runs.push({
|
|
49
|
+
text: text.slice(last, at),
|
|
50
|
+
placeholder: false
|
|
51
|
+
});
|
|
52
|
+
runs.push({
|
|
53
|
+
text: m[0],
|
|
54
|
+
placeholder: true
|
|
55
|
+
});
|
|
56
|
+
last = at + m[0].length;
|
|
57
|
+
}
|
|
58
|
+
if (last < text.length) runs.push({
|
|
59
|
+
text: text.slice(last),
|
|
60
|
+
placeholder: false
|
|
61
|
+
});
|
|
62
|
+
return runs;
|
|
63
|
+
}
|
|
64
|
+
function restoreText(text, identifiers) {
|
|
65
|
+
const byToken = new Map(identifiers.map((id) => [id.token, id.value]));
|
|
66
|
+
return text.replace(PLACEHOLDER, (token) => {
|
|
67
|
+
var _a;
|
|
68
|
+
return (_a = byToken.get(token)) != null ? _a : token;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function labelText(text) {
|
|
72
|
+
return text.replace(PLACEHOLDER, (token) => `[${placeholderLabel(token)}]`);
|
|
73
|
+
}
|
|
74
|
+
function mapNote(note, fn) {
|
|
75
|
+
return {
|
|
76
|
+
title: fn(note.title),
|
|
77
|
+
subjective: note.subjective.map(fn),
|
|
78
|
+
objective: note.objective.map(fn),
|
|
79
|
+
assessment: note.assessment.map(fn),
|
|
80
|
+
plan: note.plan.map(fn),
|
|
81
|
+
medications: note.medications.map((m) => __spreadProps(__spreadValues({}, m), {
|
|
82
|
+
name: fn(m.name),
|
|
83
|
+
dose: fn(m.dose),
|
|
84
|
+
route: fn(m.route),
|
|
85
|
+
frequency: fn(m.frequency)
|
|
86
|
+
})),
|
|
87
|
+
follow_up: fn(note.follow_up),
|
|
88
|
+
to_verify: note.to_verify.map(fn)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function mapSuggestions(items, fn) {
|
|
92
|
+
return items.map((s) => __spreadProps(__spreadValues({}, s), {
|
|
93
|
+
text: fn(s.text),
|
|
94
|
+
why: fn(s.why)
|
|
95
|
+
}));
|
|
96
|
+
}export{labelText,mapNote,mapSuggestions,placeholderLabel,restoreText,splitPlaceholders};//# sourceMappingURL=privacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privacy.js","sources":["../../../src/features/dictate/privacy.ts"],"sourcesContent":["/**\n * Placeholders in a dictation note. Before any AI reads a dictation, the\n * connector replaces each identifier (a name, a birth date, a phone number...)\n * with a placeholder such as \"[NAME_1]\" and hands back what each one stands\n * for. This phone keeps both: it shows the note with the identifiers hidden or\n * put back, and anything it sends to the AI later (suggestions) carries the\n * placeholders only.\n */\nimport type { ClinicalNote, NoteIdentifier, NoteSuggestion } from './types';\n\nconst PLACEHOLDER = /\\[[A-Z]+_\\d+\\]/g;\n\nconst LABEL: Record<string, string> = {\n NAME: 'Name',\n CLINICIAN: 'Clinician',\n FACILITY: 'Facility',\n ORG: 'Organization',\n DOB: 'Birth date',\n DATE: 'Date',\n AGE: 'Age',\n ID: 'ID',\n PHONE: 'Phone',\n EMAIL: 'Email',\n URL: 'Link',\n IP: 'IP address',\n ADDRESS: 'Address',\n};\n\n/** \"[NAME_1]\" -> \"Name 1\", how a hidden identifier reads on screen. */\nexport function placeholderLabel(token: string): string {\n const m = /^\\[([A-Z]+)_(\\d+)\\]$/.exec(token);\n if (!m) return token;\n return `${LABEL[m[1]] ?? m[1].charAt(0) + m[1].slice(1).toLowerCase()} ${m[2]}`;\n}\n\n/** The text in runs, each placeholder its own run, for drawing them apart. */\nexport function splitPlaceholders(text: string): Array<{ text: string; placeholder: boolean }> {\n const runs: Array<{ text: string; placeholder: boolean }> = [];\n let last = 0;\n for (const m of text.matchAll(PLACEHOLDER)) {\n const at = m.index ?? 0;\n if (at > last) runs.push({ text: text.slice(last, at), placeholder: false });\n runs.push({ text: m[0], placeholder: true });\n last = at + m[0].length;\n }\n if (last < text.length) runs.push({ text: text.slice(last), placeholder: false });\n return runs;\n}\n\n/** Each placeholder put back to what it stands for; one this phone does not know stays. */\nexport function restoreText(text: string, identifiers: NoteIdentifier[]): string {\n const byToken = new Map(identifiers.map((id) => [id.token, id.value]));\n return text.replace(PLACEHOLDER, (token) => byToken.get(token) ?? token);\n}\n\n/** Each placeholder as its label in brackets, for text that leaves the phone hidden (\"[Name 1]\"). */\nexport function labelText(text: string): string {\n return text.replace(PLACEHOLDER, (token) => `[${placeholderLabel(token)}]`);\n}\n\nexport function mapNote(note: ClinicalNote, fn: (text: string) => string): ClinicalNote {\n return {\n title: fn(note.title),\n subjective: note.subjective.map(fn),\n objective: note.objective.map(fn),\n assessment: note.assessment.map(fn),\n plan: note.plan.map(fn),\n medications: note.medications.map((m) => ({\n ...m,\n name: fn(m.name),\n dose: fn(m.dose),\n route: fn(m.route),\n frequency: fn(m.frequency),\n })),\n follow_up: fn(note.follow_up),\n to_verify: note.to_verify.map(fn),\n };\n}\n\nexport function mapSuggestions(items: NoteSuggestion[], fn: (text: string) => string): NoteSuggestion[] {\n return items.map((s) => ({ ...s, text: fn(s.text), why: fn(s.why) }));\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {mapSuggestions,restoreText,mapNote}from'./privacy.js';function shownNote(record, note, showIdentifiers) {
|
|
2
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3
|
+
const {
|
|
4
|
+
redaction
|
|
5
|
+
} = record;
|
|
6
|
+
if (!redaction) return {
|
|
7
|
+
note,
|
|
8
|
+
transcript: (_a = record.transcript) != null ? _a : "",
|
|
9
|
+
suggestions: null
|
|
10
|
+
};
|
|
11
|
+
const items = (_c = (_b = record.suggestions) == null ? void 0 : _b.items) != null ? _c : null;
|
|
12
|
+
if (!showIdentifiers) return {
|
|
13
|
+
note: redaction.note,
|
|
14
|
+
transcript: redaction.transcript,
|
|
15
|
+
suggestions: items
|
|
16
|
+
};
|
|
17
|
+
const identifiers = [...redaction.identifiers, ...(_e = (_d = record.suggestions) == null ? void 0 : _d.identifiers) != null ? _e : []];
|
|
18
|
+
const restore = (text) => restoreText(text, identifiers);
|
|
19
|
+
return {
|
|
20
|
+
note: mapNote(redaction.note, restore),
|
|
21
|
+
transcript: (_f = record.transcript) != null ? _f : restore(redaction.transcript),
|
|
22
|
+
suggestions: items ? mapSuggestions(items, restore) : null
|
|
23
|
+
};
|
|
24
|
+
}export{shownNote};//# sourceMappingURL=shownNote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shownNote.js","sources":["../../../src/features/dictate/shownNote.ts"],"sourcesContent":["/**\n * The note as the clinician chose to see it: identifiers hidden (what the AI\n * saw) or put back. A note written before identifiers were hidden has only\n * the one version.\n */\nimport { mapNote, mapSuggestions, restoreText } from './privacy';\nimport type { ClinicalNote, DictationRecord, NoteSuggestion } from './types';\n\nexport type SuggestState = { status: 'idle' } | { status: 'loading' } | { status: 'error'; message: string };\n\nexport interface ShownNote {\n note: ClinicalNote;\n transcript: string;\n /** null until suggestions were asked for (and always for a note written before hiding). */\n suggestions: NoteSuggestion[] | null;\n}\n\nexport function shownNote(record: DictationRecord, note: ClinicalNote, showIdentifiers: boolean): ShownNote {\n const { redaction } = record;\n if (!redaction) return { note, transcript: record.transcript ?? '', suggestions: null };\n const items = record.suggestions?.items ?? null;\n if (!showIdentifiers) return { note: redaction.note, transcript: redaction.transcript, suggestions: items };\n const identifiers = [...redaction.identifiers, ...(record.suggestions?.identifiers ?? [])];\n const restore = (text: string) => restoreText(text, identifiers);\n return {\n note: mapNote(redaction.note, restore),\n transcript: record.transcript ?? restore(redaction.transcript),\n suggestions: items ? mapSuggestions(items, restore) : null,\n };\n}\n"],"names":[],"mappings":"8DAqBgB,SAAA,SAAA,CAAU,MAAyB,EAAA,IAAA,EAAoB,eAAqC,EAAA;AArB5G,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAsBE,EAAM,MAAA;AAAA,IACJ;AAAA,GACE,GAAA,MAAA;AACJ,EAAI,IAAA,CAAC,WAAkB,OAAA;AAAA,IACrB,IAAA;AAAA,IACA,UAAA,EAAA,CAAY,EAAO,GAAA,MAAA,CAAA,UAAA,KAAP,IAAqB,GAAA,EAAA,GAAA,EAAA;AAAA,IACjC,WAAa,EAAA;AAAA,GACf;AACA,EAAA,MAAM,KAAQ,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,MAAA,CAAO,WAAP,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,CAAoB,UAApB,IAA6B,GAAA,EAAA,GAAA,IAAA;AAC3C,EAAI,IAAA,CAAC,iBAAwB,OAAA;AAAA,IAC3B,MAAM,SAAU,CAAA,IAAA;AAAA,IAChB,YAAY,SAAU,CAAA,UAAA;AAAA,IACtB,WAAa,EAAA;AAAA,GACf;AACA,EAAA,MAAM,WAAc,GAAA,CAAC,GAAG,SAAA,CAAU,WAAa,EAAA,GAAA,CAAI,EAAO,GAAA,CAAA,EAAA,GAAA,MAAA,CAAA,WAAA,KAAP,IAAoB,GAAA,MAAA,GAAA,EAAA,CAAA,WAAA,KAApB,IAAmC,GAAA,EAAA,GAAA,EAAG,CAAA;AACzF,EAAA,MAAM,OAAU,GAAA,CAAC,IAAiB,KAAA,WAAA,CAAY,MAAM,WAAW,CAAA;AAC/D,EAAO,OAAA;AAAA,IACL,IAAM,EAAA,OAAA,CAAQ,SAAU,CAAA,IAAA,EAAM,OAAO,CAAA;AAAA,IACrC,aAAY,EAAO,GAAA,MAAA,CAAA,UAAA,KAAP,IAAqB,GAAA,EAAA,GAAA,OAAA,CAAQ,UAAU,UAAU,CAAA;AAAA,IAC7D,WAAa,EAAA,KAAA,GAAQ,cAAe,CAAA,KAAA,EAAO,OAAO,CAAI,GAAA;AAAA,GACxD;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {jsxs,jsx,Fragment}from'react/jsx-runtime';import React__default,{useState,useMemo,useCallback,useEffect,useRef}from'react';import {BackHandler,StyleSheet,View,Pressable,ActivityIndicator,ScrollView,Linking,Alert,Animated,Easing,Platform}from'react-native';import {Ionicons}from'@expo/vector-icons';import {Text}from'@admin-layout/gluestack-ui-mobile';import {useNavigation,useRoute,CommonActions}from'@react-navigation/native';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {mobileColors,mobileTokens}from'../../theme/mobileTokens.js';import {useAppTheme}from'../../theme/AppThemeProvider.js';import {deviceKind,DEVICE_KINDS}from'../../features/devices/catalog.js';import {useDevices,useDeviceSession,platformWriter,deviceStore}from'../../features/devices/devices.js';import {describeReading}from'../../features/devices/readings.js';import {
|
|
1
|
+
import {jsxs,jsx,Fragment}from'react/jsx-runtime';import React__default,{useState,useMemo,useCallback,useEffect,useRef}from'react';import {BackHandler,StyleSheet,View,Pressable,ActivityIndicator,ScrollView,Linking,Alert,Animated,Easing,Platform}from'react-native';import {Ionicons}from'@expo/vector-icons';import {Text}from'@admin-layout/gluestack-ui-mobile';import {useNavigation,useRoute,CommonActions}from'@react-navigation/native';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {mobileColors,mobileTokens}from'../../theme/mobileTokens.js';import {useAppTheme}from'../../theme/AppThemeProvider.js';import {deviceKind,DEVICE_KINDS}from'../../features/devices/catalog.js';import {useDevices,useDeviceSession,platformWriter,deviceStore}from'../../features/devices/devices.js';import {describeReading}from'../../features/devices/readings.js';import {useHealthSync}from'../../features/health/healthSync.js';var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
3
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
4
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -305,53 +305,6 @@ const styles = StyleSheet.create({
|
|
|
305
305
|
fontSize: 14,
|
|
306
306
|
marginTop: 2
|
|
307
307
|
},
|
|
308
|
-
measureBox: {
|
|
309
|
-
alignItems: "center",
|
|
310
|
-
paddingVertical: 24,
|
|
311
|
-
gap: 6
|
|
312
|
-
},
|
|
313
|
-
measureRing: {
|
|
314
|
-
position: "absolute",
|
|
315
|
-
top: 24,
|
|
316
|
-
width: 176,
|
|
317
|
-
height: 176,
|
|
318
|
-
borderRadius: 88,
|
|
319
|
-
borderWidth: 2
|
|
320
|
-
},
|
|
321
|
-
measureCore: {
|
|
322
|
-
width: 176,
|
|
323
|
-
height: 176,
|
|
324
|
-
borderRadius: 88,
|
|
325
|
-
alignItems: "center",
|
|
326
|
-
justifyContent: "center",
|
|
327
|
-
marginBottom: 12
|
|
328
|
-
},
|
|
329
|
-
measureWeight: {
|
|
330
|
-
fontSize: 44,
|
|
331
|
-
fontWeight: "700"
|
|
332
|
-
},
|
|
333
|
-
measureUnit: {
|
|
334
|
-
fontSize: 18,
|
|
335
|
-
fontWeight: "600"
|
|
336
|
-
},
|
|
337
|
-
compositionGrid: {
|
|
338
|
-
flexDirection: "row",
|
|
339
|
-
flexWrap: "wrap",
|
|
340
|
-
marginTop: 10,
|
|
341
|
-
rowGap: 10
|
|
342
|
-
},
|
|
343
|
-
compositionItem: {
|
|
344
|
-
width: "33.33%",
|
|
345
|
-
paddingRight: 8
|
|
346
|
-
},
|
|
347
|
-
compositionLabel: {
|
|
348
|
-
fontSize: 12
|
|
349
|
-
},
|
|
350
|
-
compositionValue: {
|
|
351
|
-
fontSize: 15,
|
|
352
|
-
fontWeight: "600",
|
|
353
|
-
marginTop: 1
|
|
354
|
-
},
|
|
355
308
|
savedLine: {
|
|
356
309
|
flexDirection: "row",
|
|
357
310
|
alignItems: "center",
|
|
@@ -614,15 +567,7 @@ function ReadingsCard({
|
|
|
614
567
|
}], children: whenLabel(r.at, now) }),
|
|
615
568
|
r.kind === "blood-pressure" && ((_a = r.problems) == null ? void 0 : _a.length) ? /* @__PURE__ */ jsx(Text, { style: [styles.readingNote, {
|
|
616
569
|
color: colors.warningText
|
|
617
|
-
}], children: r.problems.join(" ") }) : null
|
|
618
|
-
r.kind === "body" && r.composition ? /* @__PURE__ */ jsx(View, { style: styles.compositionGrid, children: compositionRows(r.composition, units).map((m) => /* @__PURE__ */ jsxs(View, { style: styles.compositionItem, children: [
|
|
619
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.compositionLabel, {
|
|
620
|
-
color: colors.textMuted
|
|
621
|
-
}], children: m.label }),
|
|
622
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.compositionValue, {
|
|
623
|
-
color: colors.text
|
|
624
|
-
}], children: m.value })
|
|
625
|
-
] }, m.label)) }) : null
|
|
570
|
+
}], children: r.problems.join(" ") }) : null
|
|
626
571
|
] })
|
|
627
572
|
] }, r.id);
|
|
628
573
|
}) })
|
|
@@ -827,151 +772,10 @@ function PickKind({
|
|
|
827
772
|
] }, k.id)) })
|
|
828
773
|
] });
|
|
829
774
|
}
|
|
830
|
-
function formatWeight(kg, units) {
|
|
831
|
-
if (kg === void 0) return "--";
|
|
832
|
-
const v = units === "imperial" ? kg / 0.45359237 : kg;
|
|
833
|
-
return (Math.round(v * 10) / 10).toFixed(1);
|
|
834
|
-
}
|
|
835
|
-
function ScaleMeasuring({
|
|
836
|
-
colors,
|
|
837
|
-
live,
|
|
838
|
-
units
|
|
839
|
-
}) {
|
|
840
|
-
const pulse = useRef(new Animated.Value(0)).current;
|
|
841
|
-
useEffect(() => {
|
|
842
|
-
const loop = Animated.loop(Animated.timing(pulse, {
|
|
843
|
-
toValue: 1,
|
|
844
|
-
duration: 1400,
|
|
845
|
-
easing: Easing.out(Easing.ease),
|
|
846
|
-
useNativeDriver: true
|
|
847
|
-
}));
|
|
848
|
-
loop.start();
|
|
849
|
-
return () => loop.stop();
|
|
850
|
-
}, [pulse]);
|
|
851
|
-
const ringScale = pulse.interpolate({
|
|
852
|
-
inputRange: [0, 1],
|
|
853
|
-
outputRange: [0.9, 1.5]
|
|
854
|
-
});
|
|
855
|
-
const ringOpacity = pulse.interpolate({
|
|
856
|
-
inputRange: [0, 1],
|
|
857
|
-
outputRange: [0.5, 0]
|
|
858
|
-
});
|
|
859
|
-
const unitLabel = units === "imperial" ? "lb" : "kg";
|
|
860
|
-
return /* @__PURE__ */ jsxs(View, { style: styles.measureBox, children: [
|
|
861
|
-
/* @__PURE__ */ jsx(Animated.View, { style: [styles.measureRing, {
|
|
862
|
-
borderColor: colors.primary,
|
|
863
|
-
opacity: ringOpacity,
|
|
864
|
-
transform: [{
|
|
865
|
-
scale: ringScale
|
|
866
|
-
}]
|
|
867
|
-
}], accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants" }),
|
|
868
|
-
/* @__PURE__ */ jsx(View, { style: [styles.measureCore, {
|
|
869
|
-
backgroundColor: colors.primarySoft
|
|
870
|
-
}], children: /* @__PURE__ */ jsxs(Text, { style: [styles.measureWeight, {
|
|
871
|
-
color: colors.text
|
|
872
|
-
}], accessibilityLabel: `${formatWeight(live.weightKg, units)} ${unitLabel}`, children: [
|
|
873
|
-
formatWeight(live.weightKg, units),
|
|
874
|
-
/* @__PURE__ */ jsxs(Text, { style: [styles.measureUnit, {
|
|
875
|
-
color: colors.textMuted
|
|
876
|
-
}], children: [
|
|
877
|
-
" ",
|
|
878
|
-
unitLabel
|
|
879
|
-
] })
|
|
880
|
-
] }) }),
|
|
881
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.statusTitle, {
|
|
882
|
-
color: colors.text
|
|
883
|
-
}], children: "Measuring" }),
|
|
884
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.statusBody, {
|
|
885
|
-
color: colors.textMuted
|
|
886
|
-
}], children: "Stand still, barefoot, until it settles." })
|
|
887
|
-
] });
|
|
888
|
-
}
|
|
889
|
-
function BrandLive({
|
|
890
|
-
kind,
|
|
891
|
-
colors,
|
|
892
|
-
live,
|
|
893
|
-
units
|
|
894
|
-
}) {
|
|
895
|
-
var _a, _b, _c;
|
|
896
|
-
const items = [];
|
|
897
|
-
const round1 = (n) => Math.round(n * 10) / 10;
|
|
898
|
-
if (kind === "kitchen-scale") items.push({
|
|
899
|
-
label: "Weight",
|
|
900
|
-
value: `${Math.round((_a = live.grams) != null ? _a : 0)} g`
|
|
901
|
-
});
|
|
902
|
-
if (kind === "tape") {
|
|
903
|
-
const cm = (_b = live.lengthCm) != null ? _b : 0;
|
|
904
|
-
items.push({
|
|
905
|
-
label: live.bodyPart ? live.bodyPart.replace(/_/g, " ") : "Length",
|
|
906
|
-
value: units === "imperial" ? `${round1(cm / 2.54)} in` : `${round1(cm)} cm`
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (kind === "jump-rope") {
|
|
910
|
-
items.push({
|
|
911
|
-
label: "Jumps",
|
|
912
|
-
value: String((_c = live.jumps) != null ? _c : 0)
|
|
913
|
-
});
|
|
914
|
-
if (live.elapsedS !== void 0) items.push({
|
|
915
|
-
label: "Time",
|
|
916
|
-
value: clockOf(live.elapsedS)
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
if (kind === "treadmill") {
|
|
920
|
-
if (live.speedKmh !== void 0) items.push({
|
|
921
|
-
label: "Speed",
|
|
922
|
-
value: `${round1(live.speedKmh)} km/h`
|
|
923
|
-
});
|
|
924
|
-
if (live.distanceM !== void 0) items.push({
|
|
925
|
-
label: "Distance",
|
|
926
|
-
value: `${round1(live.distanceM / 1e3)} km`
|
|
927
|
-
});
|
|
928
|
-
if (live.kcal !== void 0) items.push({
|
|
929
|
-
label: "Calories",
|
|
930
|
-
value: `${Math.round(live.kcal)} kcal`
|
|
931
|
-
});
|
|
932
|
-
if (live.elapsedS !== void 0) items.push({
|
|
933
|
-
label: "Time",
|
|
934
|
-
value: clockOf(live.elapsedS)
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
if (kind === "ring") {
|
|
938
|
-
if (live.bpm !== void 0) items.push({
|
|
939
|
-
label: "Heart rate",
|
|
940
|
-
value: `${live.bpm} bpm`
|
|
941
|
-
});
|
|
942
|
-
if (live.spo2 !== void 0) items.push({
|
|
943
|
-
label: "Blood oxygen",
|
|
944
|
-
value: `${live.spo2}%`
|
|
945
|
-
});
|
|
946
|
-
if (live.skinTempC !== void 0) items.push({
|
|
947
|
-
label: "Skin temp",
|
|
948
|
-
value: `${round1(live.skinTempC)} \xB0C`
|
|
949
|
-
});
|
|
950
|
-
if (live.steps !== void 0) items.push({
|
|
951
|
-
label: "Steps",
|
|
952
|
-
value: String(live.steps)
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
if (live.battery !== void 0) items.push({
|
|
956
|
-
label: "Battery",
|
|
957
|
-
value: `${live.battery}%`
|
|
958
|
-
});
|
|
959
|
-
return /* @__PURE__ */ jsx(View, { style: [styles.card, styles.livePanel, {
|
|
960
|
-
backgroundColor: colors.surface
|
|
961
|
-
}], children: /* @__PURE__ */ jsx(View, { style: styles.compositionGrid, children: items.map((m) => /* @__PURE__ */ jsxs(View, { style: styles.compositionItem, children: [
|
|
962
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.compositionLabel, {
|
|
963
|
-
color: colors.textMuted
|
|
964
|
-
}], children: m.label }),
|
|
965
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.compositionValue, {
|
|
966
|
-
color: colors.text
|
|
967
|
-
}], children: m.value })
|
|
968
|
-
] }, m.label)) }) });
|
|
969
|
-
}
|
|
970
775
|
function LivePanel({
|
|
971
776
|
kind,
|
|
972
777
|
colors,
|
|
973
778
|
live,
|
|
974
|
-
units,
|
|
975
779
|
recording,
|
|
976
780
|
connected,
|
|
977
781
|
onStart,
|
|
@@ -980,12 +784,6 @@ function LivePanel({
|
|
|
980
784
|
}) {
|
|
981
785
|
var _a, _b, _c, _d, _e;
|
|
982
786
|
const [type, setType] = useState("run");
|
|
983
|
-
if (kind === "scale") {
|
|
984
|
-
return /* @__PURE__ */ jsx(ScaleMeasuring, { colors, live, units });
|
|
985
|
-
}
|
|
986
|
-
if (kind === "kitchen-scale" || kind === "tape" || kind === "jump-rope" || kind === "treadmill" || kind === "ring") {
|
|
987
|
-
return /* @__PURE__ */ jsx(BrandLive, { kind, colors, live, units });
|
|
988
|
-
}
|
|
989
787
|
const [, tick] = useState(0);
|
|
990
788
|
useEffect(() => {
|
|
991
789
|
if (!recording) return void 0;
|
|
@@ -1145,7 +943,7 @@ function Measure({
|
|
|
1145
943
|
"."
|
|
1146
944
|
] })
|
|
1147
945
|
] }) : null,
|
|
1148
|
-
(state.step === "connected" || state.step === "ended") && live ? /* @__PURE__ */ jsx(LivePanel, { kind, colors, live,
|
|
946
|
+
(state.step === "connected" || state.step === "ended") && live ? /* @__PURE__ */ jsx(LivePanel, { kind, colors, live, recording, connected: state.step === "connected", onStart: (type) => session == null ? void 0 : session.startRecording(type), onStop: () => {
|
|
1149
947
|
void (session == null ? void 0 : session.stopRecording());
|
|
1150
948
|
}, onSave: () => {
|
|
1151
949
|
void (session == null ? void 0 : session.saveLive());
|