Compare commits
3 Commits
develop
...
token-opti
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a0820d714a | ||
![]() |
28bd137269 | ||
![]() |
61e95c6842 |
95
package-lock.json
generated
95
package-lock.json
generated
@ -43,6 +43,8 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.13.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "3.5.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.18.1",
|
||||
@ -3458,6 +3460,99 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz",
|
||||
"integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-tailwindcss": {
|
||||
"version": "0.6.11",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
|
||||
"integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "*",
|
||||
"@prettier/plugin-pug": "*",
|
||||
"@shopify/prettier-plugin-liquid": "*",
|
||||
"@trivago/prettier-plugin-sort-imports": "*",
|
||||
"@zackad/prettier-plugin-twig": "*",
|
||||
"prettier": "^3.0",
|
||||
"prettier-plugin-astro": "*",
|
||||
"prettier-plugin-css-order": "*",
|
||||
"prettier-plugin-import-sort": "*",
|
||||
"prettier-plugin-jsdoc": "*",
|
||||
"prettier-plugin-marko": "*",
|
||||
"prettier-plugin-multiline-arrays": "*",
|
||||
"prettier-plugin-organize-attributes": "*",
|
||||
"prettier-plugin-organize-imports": "*",
|
||||
"prettier-plugin-sort-imports": "*",
|
||||
"prettier-plugin-style-order": "*",
|
||||
"prettier-plugin-svelte": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@ianvs/prettier-plugin-sort-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"@prettier/plugin-pug": {
|
||||
"optional": true
|
||||
},
|
||||
"@shopify/prettier-plugin-liquid": {
|
||||
"optional": true
|
||||
},
|
||||
"@trivago/prettier-plugin-sort-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"@zackad/prettier-plugin-twig": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-astro": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-css-order": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-import-sort": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-jsdoc": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-marko": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-multiline-arrays": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-organize-attributes": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-organize-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-sort-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-style-order": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
@ -4,8 +4,7 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite preview",
|
||||
"watch": "vite preview & vite build --watch",
|
||||
"dev": "vite preview & vite build --watch",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
@ -34,6 +33,8 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.13.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "3.5.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.18.1",
|
||||
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import {
|
||||
ScribeField,
|
||||
ScribeFieldSuggestion,
|
||||
ScribeFileType,
|
||||
ScribeStatus,
|
||||
VALUESET_SYSTEM_NAMES,
|
||||
} from "../types";
|
||||
@ -31,9 +32,11 @@ import {
|
||||
ChevronUpIcon,
|
||||
Cross1Icon,
|
||||
CrossCircledIcon,
|
||||
ImageIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { useScribePosition } from "@/utils/controller-position";
|
||||
import { printNode, zodToTs } from "zod-to-ts";
|
||||
import FileUpload from "./FileUpload";
|
||||
|
||||
export function Controller(props: {
|
||||
formState: unknown;
|
||||
@ -48,6 +51,7 @@ export function Controller(props: {
|
||||
const [toReview, setToReview] = useState<ScribeFieldSuggestion[]>();
|
||||
const [openEditTranscript, setOpenEditTranscript] = useState(false);
|
||||
const [controllerPosition] = useScribePosition();
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
|
||||
//Use this to test scribe
|
||||
const SCRIBE_TEST_INPUT = `The patient's encounter status is currently on hold, classified as an emergency with a priority of “as needed,”
|
||||
@ -80,7 +84,7 @@ export function Controller(props: {
|
||||
const res = await API.scribe.get(scribeInstanceId);
|
||||
const { status, transcript, ai_response } = res;
|
||||
|
||||
if (status === "FAILED") {
|
||||
if (status === "FAILED" || status === "REFUSED") {
|
||||
toast({ title: "Transcription failed", variant: "destructive" });
|
||||
clearInterval(interval);
|
||||
return reject(new Error("Transcription failed"));
|
||||
@ -125,6 +129,10 @@ export function Controller(props: {
|
||||
"ai_response",
|
||||
);
|
||||
const parsedFormData = JSON.parse(updatedFieldsResponse ?? "{}");
|
||||
const scribeTranscription = parsedFormData.__scribe__transcription;
|
||||
if (scribeTranscription && files.length !== 0) {
|
||||
setTranscript(scribeTranscription);
|
||||
}
|
||||
// run type validations
|
||||
const changedData = Object.entries(parsedFormData)
|
||||
.map(([k, v]) => {
|
||||
@ -229,24 +237,29 @@ export function Controller(props: {
|
||||
};
|
||||
|
||||
// Uploads a scribe audio blob. Returns the response of the upload.
|
||||
const uploadAudio = async (audioBlob: Blob, scribeInstanceId: string) => {
|
||||
const category = "AUDIO";
|
||||
const name = "audio.mp3";
|
||||
const uploadScribeFile = async (
|
||||
blob: Blob,
|
||||
scribeInstanceId: string,
|
||||
type: ScribeFileType,
|
||||
) => {
|
||||
const category = type === ScribeFileType.AUDIO ? "AUDIO" : "UNSPECIFIED";
|
||||
const extension = blob?.type?.split("/")?.[1].split(";")?.[0];
|
||||
const name = "file" + (extension ? `.${extension}` : "");
|
||||
const filename = Date.now().toString();
|
||||
|
||||
const data = await API.scribe.createFileUpload({
|
||||
original_name: name,
|
||||
file_type: 1,
|
||||
file_type: type,
|
||||
name: filename,
|
||||
associating_id: scribeInstanceId,
|
||||
file_category: category,
|
||||
mime_type: audioBlob?.type?.split(";")?.[0],
|
||||
mime_type: blob?.type?.split(";")?.[0],
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const url = data?.signed_url;
|
||||
const internal_name = data?.internal_name;
|
||||
const f = audioBlob;
|
||||
const f = blob;
|
||||
if (f === undefined) {
|
||||
reject(Error("No file to upload"));
|
||||
return;
|
||||
@ -270,7 +283,7 @@ export function Controller(props: {
|
||||
|
||||
return await API.scribe.editFileUpload(
|
||||
data.id,
|
||||
"SCRIBE",
|
||||
type === ScribeFileType.AUDIO ? "SCRIBE_AUDIO" : "SCRIBE_DOCUMENT",
|
||||
scribeInstanceId,
|
||||
{ upload_completed: true },
|
||||
);
|
||||
@ -282,13 +295,21 @@ export function Controller(props: {
|
||||
const data = await API.scribe.create({
|
||||
status: "CREATED",
|
||||
form_data: hfields as any,
|
||||
// system_prompt: "...",
|
||||
// json_prompt: "...",
|
||||
// prompt: "..."
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
audioBlobs.map((blob) => uploadAudio(blob, data?.external_id ?? "")),
|
||||
);
|
||||
await Promise.all([
|
||||
...audioBlobs.map((blob) =>
|
||||
uploadScribeFile(blob, data?.external_id ?? "", ScribeFileType.AUDIO),
|
||||
),
|
||||
...files.map((file) =>
|
||||
uploadScribeFile(
|
||||
file,
|
||||
data?.external_id ?? "",
|
||||
ScribeFileType.DOCUMENT,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
return data.external_id;
|
||||
};
|
||||
@ -394,7 +415,25 @@ export function Controller(props: {
|
||||
|
||||
const handleCancel = () => {
|
||||
setStatus("IDLE");
|
||||
resetRecording();
|
||||
setToReview(undefined);
|
||||
setFiles([]);
|
||||
setTranscript(undefined);
|
||||
setLastTranscript(undefined);
|
||||
};
|
||||
|
||||
const handleProcessFile = async () => {
|
||||
setStatus("UPLOADING");
|
||||
const fields = getQuestionInputs(props.formState);
|
||||
const instanceId = await createScribeInstance(fields);
|
||||
setInstanceId(instanceId);
|
||||
setStatus("TRANSCRIBING");
|
||||
await getTranscript(instanceId);
|
||||
setStatus("THINKING");
|
||||
const aiResponse = await getAIResponse(instanceId, fields);
|
||||
if (!aiResponse) return;
|
||||
setStatus("REVIEWING");
|
||||
setToReview(getFieldsToReview(aiResponse, fields));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -407,6 +446,9 @@ export function Controller(props: {
|
||||
<div
|
||||
className={`${status === "IDLE" ? "max-h-0 opacity-0" : "max-h-[400px]"} w-full overflow-hidden rounded-2xl ${status === "REVIEWING" && !(openEditTranscript || (toReview && !toReview.length)) ? "" : "border-secondary-400 border"} bg-white transition-all delay-100`}
|
||||
>
|
||||
{status === "ATTACHING" && (
|
||||
<FileUpload files={files} setFiles={setFiles} error={null} />
|
||||
)}
|
||||
{status === "RECORDING" && (
|
||||
<div className="flex items-center justify-center p-4 py-10">
|
||||
<div className="text-center">
|
||||
@ -501,7 +543,7 @@ export function Controller(props: {
|
||||
)}
|
||||
{status === "FAILED" && (
|
||||
<div className="flex flex-col items-center justify-center gap-4 px-4 py-10 text-red-500">
|
||||
<CrossCircledIcon className="text-4xl" />
|
||||
<CrossCircledIcon className="h-8 w-8" />
|
||||
{t("scribe_error")}
|
||||
</div>
|
||||
)}
|
||||
@ -518,7 +560,7 @@ export function Controller(props: {
|
||||
</button>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{status === "REVIEWING" && (
|
||||
{(status === "REVIEWING" || status === "ATTACHING") && (
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="border-secondary-400 bg-secondary-300 hover:bg-secondary-400 flex aspect-square h-full items-center justify-center rounded-full border p-4 text-xl transition-all"
|
||||
@ -527,13 +569,27 @@ export function Controller(props: {
|
||||
<Cross1Icon />
|
||||
</button>
|
||||
)}
|
||||
{status === "IDLE" && (
|
||||
<button
|
||||
onClick={() => setStatus("ATTACHING")}
|
||||
className="border-secondary-400 bg-secondary-300 hover:bg-secondary-400 flex aspect-square h-full items-center justify-center rounded-full border p-4 text-xl transition-all"
|
||||
>
|
||||
<ImageIcon />
|
||||
</button>
|
||||
)}
|
||||
<ScribeButton
|
||||
files={files}
|
||||
status={status}
|
||||
onClick={
|
||||
status !== "RECORDING"
|
||||
? handleStartRecording
|
||||
: handleStopRecording
|
||||
status === "ATTACHING"
|
||||
? handleProcessFile
|
||||
: files.length > 0
|
||||
? () => setStatus("ATTACHING")
|
||||
: status !== "RECORDING"
|
||||
? handleStartRecording
|
||||
: handleStopRecording
|
||||
}
|
||||
disabled={status === "ATTACHING" && files.length === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -549,6 +605,7 @@ export function Controller(props: {
|
||||
});
|
||||
setToReview(undefined);
|
||||
setStatus("IDLE");
|
||||
setFiles([]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
100
src/components/FileUpload.tsx
Normal file
100
src/components/FileUpload.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import { Cross2Icon, UploadIcon } from "@radix-ui/react-icons";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function FileUpload(props: {
|
||||
files: File[];
|
||||
setFiles: (files: File[]) => void;
|
||||
error: string | null;
|
||||
}) {
|
||||
const supported = ["image/jpeg", "image/png", "image/jpg"];
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { setFiles, error, files } = props;
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const handleDragEnter = (e: React.DragEvent<HTMLLabelElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent<HTMLLabelElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
const droppedFiles = Array.from(e.dataTransfer.files).filter((file) =>
|
||||
supported.includes(file.type),
|
||||
);
|
||||
if (droppedFiles.length > 0) {
|
||||
setFiles([...files, ...droppedFiles]);
|
||||
}
|
||||
e.dataTransfer.clearData();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 p-4">
|
||||
<div className="font-bold">{t("upload_images")}</div>
|
||||
{!!files.length && (
|
||||
<div className="flex max-w-[300px] flex-wrap items-center gap-2">
|
||||
{files.map((file, index) => (
|
||||
<div className="relative rounded-md shadow-md" key={index}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setFiles(files.filter((_, i) => i !== index));
|
||||
}}
|
||||
className="absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center rounded-full bg-white shadow-md"
|
||||
>
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
</button>
|
||||
<img
|
||||
src={URL.createObjectURL(file)}
|
||||
alt="uploaded"
|
||||
className="h-10 w-10 rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<label
|
||||
className={`cursor-pointer border border-dashed ${
|
||||
isDragging ? "border-blue-500 bg-blue-100" : "border-gray-300"
|
||||
} flex w-full flex-col items-center justify-center gap-4 rounded-md p-4 transition-all hover:bg-gray-100`}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<UploadIcon className="h-10 w-10" />
|
||||
<div className="text-center text-xs text-gray-500">
|
||||
{t("upload_images_description")}
|
||||
</div>
|
||||
<input
|
||||
accept={supported.join(",")}
|
||||
className="hidden"
|
||||
type="file"
|
||||
multiple
|
||||
onChange={(e) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
setFiles([...files, ...e.target.files]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
{error && <p>{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { ImageIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { ScribeStatus } from "../types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useKeyboardShortcut from "use-keyboard-shortcut";
|
||||
@ -10,10 +10,12 @@ import {
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export default function ScribeButton(props: {
|
||||
files: File[];
|
||||
status: ScribeStatus;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const { status, onClick } = props;
|
||||
const { status, onClick, disabled, files } = props;
|
||||
const { t } = useTranslation();
|
||||
const [, setControllerPosition] = useScribePosition();
|
||||
const [initMousePosition, setInitMousePosition] = useState<{
|
||||
@ -105,7 +107,7 @@ export default function ScribeButton(props: {
|
||||
onMouseLeave={handleDragEnd}
|
||||
onClick={() => (!estimatedMovingPosition ? onClick() : undefined)}
|
||||
className={`group z-10 flex items-center rounded-full ${status === "IDLE" ? "bg-primary-500 hover:bg-primary-600 text-white" : "border-secondary-400 bg-secondary-200 hover:bg-secondary-300 border"} ${!!estimatedMovingPosition ? "opacity-50" : ""} disabled:bg-secondary-300 transition-[background,top,right,left,bottom,opacity]`}
|
||||
disabled={["TRANSCRIBING", "THINKING"].includes(status)}
|
||||
disabled={["TRANSCRIBING", "THINKING"].includes(status) || disabled}
|
||||
style={{ touchAction: "none" }}
|
||||
>
|
||||
<div
|
||||
@ -115,6 +117,8 @@ export default function ScribeButton(props: {
|
||||
<MicrophoneIcon className="w-4 invert" />
|
||||
) : status === "RECORDING" ? (
|
||||
<MicrophoneSlashIcon className="w-5" />
|
||||
) : status === "ATTACHING" ? (
|
||||
<ImageIcon />
|
||||
) : (
|
||||
<ReloadIcon />
|
||||
)}
|
||||
@ -122,9 +126,13 @@ export default function ScribeButton(props: {
|
||||
<div className="pl-2 pr-6 font-semibold">
|
||||
{status === "IDLE"
|
||||
? t("voice_autofill")
|
||||
: status === "RECORDING"
|
||||
? t("stop_recording")
|
||||
: t("retake_recording")}
|
||||
: status === "ATTACHING"
|
||||
? t("process_images")
|
||||
: status === "RECORDING"
|
||||
? t("stop_recording")
|
||||
: files.length > 0
|
||||
? t("reupload_files")
|
||||
: t("retake_recording")}
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
|
@ -1,20 +1,25 @@
|
||||
{
|
||||
"voice_autofill": "Voice Autofill",
|
||||
"hearing": "We are hearing you...",
|
||||
"copilot_thinking": "Copilot is thinking...",
|
||||
"could_not_autofill": "We could not autofill any fields from what you said",
|
||||
"transcribe_again": "Transcribe Again",
|
||||
"transcript_edit_info": "You can update this if we made an error",
|
||||
"transcript_information": "This is what we heard",
|
||||
"process_transcript": "Process Again",
|
||||
"retake_recording": "Retake Recording",
|
||||
"scribe__reviewing_field": "Reviewing field {{currentField}} / {{totalFields}}",
|
||||
"scribe_error": "Could not autofill fields",
|
||||
"accept": "Accept",
|
||||
"accept_all": "Accept All",
|
||||
"reject": "Reject",
|
||||
"stop_recording": "Stop Recording",
|
||||
"autofilled_fields": "Autofilled Fields",
|
||||
"start_review": "Start Review",
|
||||
"scribe_no_match": "Copilot could not find a {{valueType}} that matches with \"{{query}}\". Please enter manually."
|
||||
}
|
||||
"voice_autofill": "Voice Autofill",
|
||||
"hearing": "We are hearing you...",
|
||||
"copilot_thinking": "Copilot is thinking...",
|
||||
"could_not_autofill": "We could not autofill any fields",
|
||||
"transcribe_again": "Transcribe Again",
|
||||
"transcript_edit_info": "You can update this if we made an error",
|
||||
"transcript_information": "This is what we understood",
|
||||
"process_transcript": "Process Again",
|
||||
"retake_recording": "Retake Recording",
|
||||
"scribe__reviewing_field": "Reviewing field {{currentField}} / {{totalFields}}",
|
||||
"scribe_error": "Could not autofill fields",
|
||||
"accept": "Accept",
|
||||
"accept_all": "Accept All",
|
||||
"reject": "Reject",
|
||||
"stop_recording": "Stop Recording",
|
||||
"autofilled_fields": "Autofilled Fields",
|
||||
"start_review": "Start Review",
|
||||
"scribe_no_match": "Copilot could not find a {{valueType}} that matches with \"{{query}}\". Please enter manually.",
|
||||
"process_images": "Process Images",
|
||||
"upload_images": "Upload Images",
|
||||
"upload_images_description": "Drag and drop or click to upload images",
|
||||
"cancel": "Cancel",
|
||||
"reupload_files": "Reupload Files"
|
||||
}
|
||||
|
340
src/types.ts
340
src/types.ts
@ -1,223 +1,233 @@
|
||||
export type FeatureFlag = "SCRIBE_ENABLED"; // "HCX_ENABLED" | "ABDM_ENABLED" |
|
||||
|
||||
export type UserBareMinimum = {
|
||||
id: number;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
user_type: unknown;
|
||||
last_login: string | undefined;
|
||||
read_profile_picture_url?: string;
|
||||
external_id: string;
|
||||
id: number;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
user_type: unknown;
|
||||
last_login: string | undefined;
|
||||
read_profile_picture_url?: string;
|
||||
external_id: string;
|
||||
};
|
||||
|
||||
export type GenderType = "Male" | "Female" | "Transgender";
|
||||
|
||||
export type UserModel = UserBareMinimum & {
|
||||
external_id: string;
|
||||
local_body?: number;
|
||||
district?: number;
|
||||
state?: number;
|
||||
video_connect_link: string;
|
||||
phone_number?: string;
|
||||
alt_phone_number?: string;
|
||||
gender?: GenderType;
|
||||
read_profile_picture_url?: string;
|
||||
date_of_birth: Date | null | string;
|
||||
is_superuser?: boolean;
|
||||
verified?: boolean;
|
||||
home_facility?: string;
|
||||
qualification?: string;
|
||||
doctor_experience_commenced_on?: string;
|
||||
doctor_medical_council_registration?: string;
|
||||
weekly_working_hours?: string | null;
|
||||
user_flags?: FeatureFlag[];
|
||||
external_id: string;
|
||||
local_body?: number;
|
||||
district?: number;
|
||||
state?: number;
|
||||
video_connect_link: string;
|
||||
phone_number?: string;
|
||||
alt_phone_number?: string;
|
||||
gender?: GenderType;
|
||||
read_profile_picture_url?: string;
|
||||
date_of_birth: Date | null | string;
|
||||
is_superuser?: boolean;
|
||||
verified?: boolean;
|
||||
home_facility?: string;
|
||||
qualification?: string;
|
||||
doctor_experience_commenced_on?: string;
|
||||
doctor_medical_council_registration?: string;
|
||||
weekly_working_hours?: string | null;
|
||||
user_flags?: FeatureFlag[];
|
||||
};
|
||||
|
||||
export type ScribeModel = {
|
||||
external_id: string;
|
||||
requested_by: UserModel;
|
||||
form_data: {
|
||||
friendlyName: string;
|
||||
default: string;
|
||||
description: string;
|
||||
example: string;
|
||||
id: string;
|
||||
options?: any[];
|
||||
type: string;
|
||||
}[];
|
||||
transcript: string;
|
||||
ai_response: string;
|
||||
status:
|
||||
external_id: string;
|
||||
requested_by: UserModel;
|
||||
form_data: {
|
||||
friendlyName: string;
|
||||
default: string;
|
||||
description: string;
|
||||
example: string;
|
||||
id: string;
|
||||
options?: any[];
|
||||
type: string;
|
||||
}[];
|
||||
transcript: string;
|
||||
ai_response: string;
|
||||
status:
|
||||
| "CREATED"
|
||||
| "READY"
|
||||
| "GENERATING_TRANSCRIPT"
|
||||
| "GENERATING_AI_RESPONSE"
|
||||
| "COMPLETED"
|
||||
| "REFUSED"
|
||||
| "FAILED";
|
||||
system_prompt?: string;
|
||||
json_prompt?: string;
|
||||
prompt?: string;
|
||||
};
|
||||
|
||||
export type ScribeStatus =
|
||||
| "FAILED"
|
||||
| "IDLE"
|
||||
| "RECORDING"
|
||||
| "UPLOADING"
|
||||
| "TRANSCRIBING"
|
||||
| "THINKING"
|
||||
| "REVIEWING"
|
||||
| "SCRIBING";
|
||||
| "FAILED"
|
||||
| "IDLE"
|
||||
| "ATTACHING"
|
||||
| "RECORDING"
|
||||
| "UPLOADING"
|
||||
| "TRANSCRIBING"
|
||||
| "THINKING"
|
||||
| "REVIEWING"
|
||||
| "SCRIBING";
|
||||
|
||||
export enum ScribeFileType {
|
||||
OTHER = 0,
|
||||
AUDIO = 1,
|
||||
DOCUMENT = 2,
|
||||
}
|
||||
|
||||
export type ScribeFieldOption = {
|
||||
value: string,
|
||||
text: string
|
||||
}
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type ScribeField = {
|
||||
question: FormQuestion,
|
||||
fieldElement: Element,
|
||||
value: string | null;
|
||||
}
|
||||
question: FormQuestion;
|
||||
fieldElement: Element;
|
||||
value: string | null;
|
||||
};
|
||||
|
||||
export type ScribeAIResponse = {
|
||||
[field_number: number]: unknown
|
||||
}
|
||||
[field_number: number]: unknown;
|
||||
};
|
||||
|
||||
export type ScribePromptMap = {
|
||||
[key in QuestionType | "default"]?: { prompt: string; example: unknown };
|
||||
}
|
||||
[key in QuestionType | "default"]?: { prompt: string; example: unknown };
|
||||
};
|
||||
|
||||
export type ScribeFieldSuggestion = ScribeField & { newValue: unknown }
|
||||
export type ScribeFieldSuggestion = ScribeField & { newValue: unknown };
|
||||
|
||||
export type ScribeFieldReviewedSuggestion = ScribeFieldSuggestion & { suggestionIndex: number, approved?: boolean }
|
||||
export type ScribeFieldReviewedSuggestion = ScribeFieldSuggestion & {
|
||||
suggestionIndex: number;
|
||||
approved?: boolean;
|
||||
};
|
||||
|
||||
export type FileCategory = "UNSPECIFIED" | "XRAY" | "AUDIO" | "IDENTITY_PROOF";
|
||||
|
||||
export interface CreateFileRequest {
|
||||
file_type: string | number;
|
||||
file_category: FileCategory;
|
||||
name: string;
|
||||
associating_id: string;
|
||||
original_name: string;
|
||||
mime_type: string;
|
||||
file_type: ScribeFileType;
|
||||
file_category: FileCategory;
|
||||
name: string;
|
||||
associating_id: string;
|
||||
original_name: string;
|
||||
mime_type: string;
|
||||
}
|
||||
|
||||
export interface CreateFileResponse {
|
||||
id: string;
|
||||
file_type: string;
|
||||
file_category: FileCategory;
|
||||
signed_url: string;
|
||||
internal_name: string;
|
||||
id: string;
|
||||
file_type: ScribeFileType;
|
||||
file_category: FileCategory;
|
||||
signed_url: string;
|
||||
internal_name: string;
|
||||
}
|
||||
|
||||
export interface FileUploadModel {
|
||||
id?: string;
|
||||
name?: string;
|
||||
associating_id?: string;
|
||||
created_date?: string;
|
||||
upload_completed?: boolean;
|
||||
uploaded_by?: UserBareMinimum;
|
||||
file_category?: FileCategory;
|
||||
read_signed_url?: string;
|
||||
is_archived?: boolean;
|
||||
archive_reason?: string;
|
||||
extension?: string;
|
||||
archived_by?: UserBareMinimum;
|
||||
archived_datetime?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
associating_id?: string;
|
||||
created_date?: string;
|
||||
upload_completed?: boolean;
|
||||
uploaded_by?: UserBareMinimum;
|
||||
file_category?: FileCategory;
|
||||
read_signed_url?: string;
|
||||
is_archived?: boolean;
|
||||
archive_reason?: string;
|
||||
extension?: string;
|
||||
archived_by?: UserBareMinimum;
|
||||
archived_datetime?: string;
|
||||
}
|
||||
|
||||
export interface FacilityModel {
|
||||
id?: string;
|
||||
name?: string;
|
||||
read_cover_image_url?: string;
|
||||
facility_type?: string;
|
||||
address?: string;
|
||||
features?: number[];
|
||||
location?: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
phone_number?: string;
|
||||
middleware_address?: string;
|
||||
modified_date?: string;
|
||||
created_date?: string;
|
||||
state?: number;
|
||||
district?: number;
|
||||
local_body?: number;
|
||||
ward?: number;
|
||||
pincode?: string;
|
||||
facility_flags?: FeatureFlag[];
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
kasp_empanelled?: boolean;
|
||||
patient_count?: number;
|
||||
bed_count?: number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
read_cover_image_url?: string;
|
||||
facility_type?: string;
|
||||
address?: string;
|
||||
features?: number[];
|
||||
location?: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
phone_number?: string;
|
||||
middleware_address?: string;
|
||||
modified_date?: string;
|
||||
created_date?: string;
|
||||
state?: number;
|
||||
district?: number;
|
||||
local_body?: number;
|
||||
ward?: number;
|
||||
pincode?: string;
|
||||
facility_flags?: FeatureFlag[];
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
kasp_empanelled?: boolean;
|
||||
patient_count?: number;
|
||||
bed_count?: number;
|
||||
}
|
||||
|
||||
export type QuestionType =
|
||||
| "group"
|
||||
| "display"
|
||||
| "boolean"
|
||||
| "decimal"
|
||||
| "integer"
|
||||
| "date"
|
||||
| "dateTime"
|
||||
| "time"
|
||||
| "string"
|
||||
| "text"
|
||||
| "url"
|
||||
| "choice"
|
||||
| "quantity"
|
||||
| "structured";
|
||||
| "group"
|
||||
| "display"
|
||||
| "boolean"
|
||||
| "decimal"
|
||||
| "integer"
|
||||
| "date"
|
||||
| "dateTime"
|
||||
| "time"
|
||||
| "string"
|
||||
| "text"
|
||||
| "url"
|
||||
| "choice"
|
||||
| "quantity"
|
||||
| "structured";
|
||||
|
||||
export interface FormQuestion {
|
||||
id: string;
|
||||
structured_type?: string;
|
||||
answer_option?: { value: string }[];
|
||||
text: string;
|
||||
required?: boolean;
|
||||
type: QuestionType
|
||||
repeats?: boolean;
|
||||
id: string;
|
||||
structured_type?: string;
|
||||
answer_option?: { value: string }[];
|
||||
text: string;
|
||||
required?: boolean;
|
||||
type: QuestionType;
|
||||
repeats?: boolean;
|
||||
}
|
||||
|
||||
export type ValueSetSystem =
|
||||
| "system-allergy-code"
|
||||
| "system-condition-code"
|
||||
| "system-medication"
|
||||
| "system-additional-instruction"
|
||||
| "system-administration-method"
|
||||
| "system-as-needed-reason"
|
||||
| "system-body-site"
|
||||
| "system-route"
|
||||
| "system-observation"
|
||||
| "system-body-site-observation"
|
||||
| "system-collection-method"
|
||||
| "system-ucum-units";
|
||||
| "system-allergy-code"
|
||||
| "system-condition-code"
|
||||
| "system-medication"
|
||||
| "system-additional-instruction"
|
||||
| "system-administration-method"
|
||||
| "system-as-needed-reason"
|
||||
| "system-body-site"
|
||||
| "system-route"
|
||||
| "system-observation"
|
||||
| "system-body-site-observation"
|
||||
| "system-collection-method"
|
||||
| "system-ucum-units";
|
||||
|
||||
export const VALUESET_SYSTEM_NAMES: { [key in ValueSetSystem]: string } = {
|
||||
"system-allergy-code": "Allergy",
|
||||
"system-condition-code": "Condition",
|
||||
"system-medication": "Medication",
|
||||
"system-additional-instruction": "Additional Instruction",
|
||||
"system-administration-method": "Administration Method",
|
||||
"system-as-needed-reason": "As Needed Reason",
|
||||
"system-body-site": "Body Site",
|
||||
"system-route": "Route",
|
||||
"system-observation": "Observation",
|
||||
"system-body-site-observation": "Body Site Observation",
|
||||
"system-collection-method": "Collection Method",
|
||||
"system-ucum-units": "UCUM Units",
|
||||
"system-allergy-code": "Allergy",
|
||||
"system-condition-code": "Condition",
|
||||
"system-medication": "Medication",
|
||||
"system-additional-instruction": "Additional Instruction",
|
||||
"system-administration-method": "Administration Method",
|
||||
"system-as-needed-reason": "As Needed Reason",
|
||||
"system-body-site": "Body Site",
|
||||
"system-route": "Route",
|
||||
"system-observation": "Observation",
|
||||
"system-body-site-observation": "Body Site Observation",
|
||||
"system-collection-method": "Collection Method",
|
||||
"system-ucum-units": "UCUM Units",
|
||||
};
|
||||
|
||||
export interface CodeSearchQuery {
|
||||
code_search_type: ValueSetSystem,
|
||||
code_search_query: string,
|
||||
primary?: boolean;
|
||||
code_search_type: ValueSetSystem;
|
||||
code_search_query: string;
|
||||
primary?: boolean;
|
||||
}
|
||||
export interface Code {
|
||||
system: string;
|
||||
code: string;
|
||||
display?: string;
|
||||
}
|
||||
system: string;
|
||||
code: string;
|
||||
display?: string;
|
||||
}
|
||||
|
@ -91,12 +91,12 @@ const withFallback = <T>(schema: z.ZodType<T>, fallback: T) =>
|
||||
export const STRUCTURED_INPUT_PROMPTS = {
|
||||
"encounter": {
|
||||
prompt: () => z.object({
|
||||
status: z.enum(["planned", "in_progress", "on_hold", "discharged", "completed", "cancelled", "discontinued", "entered_in_error", "unknown"]).describe("Status of the encounter"),
|
||||
status: z.enum(["planned", "in_progress", "on_hold", "discharged", "completed", "cancelled", "discontinued", "entered_in_error", "unknown"]),
|
||||
encounter_class: z.enum(["imp", "amb", "obsenc", "emer", "vr", "hh"]).describe(`Class of the encounter : "imp" (Inpatient (IP)) | "amb" (Ambulatory (OP)) | "obsenc" (Observation Room) | "emer" (Emergency) | "vr" (Virtual) | "hh" (Home Health)`),
|
||||
priority: z.enum(ENCOUNTER_PRIORITY).describe("Priority of the encounter"),
|
||||
priority: z.enum(ENCOUNTER_PRIORITY),
|
||||
external_identifier: z.string().optional().describe("ip/op/obs/emr number"),
|
||||
hospitalization: z.object({
|
||||
re_admission: z.boolean().describe("Encounter is a readmission"),
|
||||
re_admission: z.boolean(),
|
||||
admit_source: z.enum(["hosp_trans"
|
||||
, "emd"
|
||||
, "outp"
|
||||
@ -114,7 +114,7 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
, "vegan"
|
||||
, "halal"
|
||||
, "kosher"
|
||||
, "none"]).optional().describe("Dietary preference of the patient"),
|
||||
, "none"]).optional(),
|
||||
discharge_disposition: z.enum(["home"
|
||||
, "alt_home"
|
||||
, "other_hcf"
|
||||
@ -144,13 +144,13 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
},
|
||||
"medication_request": {
|
||||
prompt: (isRes?: boolean) => z.array(z.object({
|
||||
status: z.enum(MEDICATION_REQUEST_STATUS).describe("Status of the medication"),
|
||||
intent: z.enum(MEDICATION_REQUEST_INTENT).optional().describe("Intent of the medication request"),
|
||||
category: z.enum(["inpatient", "outpatient", "community", "discharge"]).describe("Category of the medication request"),
|
||||
priority: z.enum(["stat", "urgent", "asap", "routine"]).describe("Priority of the medication request"),
|
||||
status: z.enum(MEDICATION_REQUEST_STATUS),
|
||||
intent: z.enum(MEDICATION_REQUEST_INTENT).optional(),
|
||||
category: z.enum(["inpatient", "outpatient", "community", "discharge"]),
|
||||
priority: z.enum(["stat", "urgent", "asap", "routine"]),
|
||||
do_not_perform: z.literal(false).describe("Do not update this value"),
|
||||
medication: codeStructure(isRes, "system-medication", true),
|
||||
authored_on: withFallback(isoDateTime.default(new Date().toISOString()).describe("When was this medication request authored? In ISO datetime"), new Date().toISOString()),
|
||||
authored_on: withFallback(isoDateTime.default(new Date().toISOString()).describe("In ISO datetime"), new Date().toISOString()),
|
||||
dosage_instruction: z.array(z.object({
|
||||
sequence: z.number().optional(),
|
||||
text: z.string().optional(),
|
||||
@ -186,8 +186,8 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
as_needed_boolean: withFallback(z.boolean().describe("True if the prescription is PRN, else false. Do not ommit this.").default(false), false),
|
||||
as_needed_for: codeStructure(isRes, "system-as-needed-reason").optional().describe("If it is a PRN medication (as_needed_boolean is true), the indicator"),
|
||||
site: codeStructure(isRes, "system-body-site").optional().describe("The site the medication should be administered at"),
|
||||
route: codeStructure(isRes, "system-route").optional().describe("The route of the medicine"),
|
||||
method: codeStructure(isRes, "system-administration-method").optional().describe("The method in which the medicine should be administered"),
|
||||
route: codeStructure(isRes, "system-route").optional(),
|
||||
method: codeStructure(isRes, "system-administration-method").optional(),
|
||||
dose_and_rate: z.union([z.object({
|
||||
type: z.literal("ordered"),
|
||||
dose_quantity: doseQuantity,
|
||||
@ -203,7 +203,7 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
`),
|
||||
max_dose_per_period: doseRange.optional()
|
||||
})),
|
||||
note: z.string().optional().describe("Additional Notes")
|
||||
note: z.string().optional()
|
||||
})),
|
||||
example: [
|
||||
{
|
||||
@ -324,16 +324,16 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
},
|
||||
"medication_statement": {
|
||||
prompt: (isRes?: boolean) => z.array(z.object({
|
||||
status: z.enum(MEDICATION_STATEMENT_STATUS).describe("Status of the medication"),
|
||||
dosage_text: z.string().optional().describe("Text to support the dosage"),
|
||||
information_source: z.string().optional().describe("The information source of the medication"),
|
||||
status: z.enum(MEDICATION_STATEMENT_STATUS),
|
||||
dosage_text: z.string().optional(),
|
||||
information_source: z.string().optional(),
|
||||
medication: codeStructure(isRes, "system-medication", true),
|
||||
note: z.string().optional().describe("Additional notes on the medication"),
|
||||
reason: z.string().optional().describe("Reason for medication"),
|
||||
note: z.string().optional(),
|
||||
reason: z.string().optional(),
|
||||
effective_period: z.object({
|
||||
start: isoDateTime.describe("ISO date"),
|
||||
end: isoDateTime.describe("ISO date")
|
||||
}).optional().describe("Medication effective period")
|
||||
}).optional()
|
||||
})),
|
||||
example: [
|
||||
{
|
||||
@ -357,12 +357,12 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
"symptom": {
|
||||
prompt: (isRes?: boolean) => z.array(z.object({
|
||||
code: codeStructure(isRes, "system-condition-code", true),
|
||||
clinical_status: z.enum(["active", "recurrence", "relapse", "inactive", "remission", "resolved"]).describe("Clinical Status of the symptom"),
|
||||
verification_status: z.enum(["unconfirmed", "provisional", "differential", "confirmed", "refuted", "entered-in-error"]).describe("Verification status of the symptom"),
|
||||
severity: z.enum(["severe", "moderate", "mild"]).optional().describe("Severity of the symptom"),
|
||||
onset: withFallback(z.object({ onset_datetime: isoDateTime }).default({ onset_datetime: new Date().toISOString() }).describe("Onset date of the symptom in ISO format"), { onset_datetime: new Date().toISOString() }),
|
||||
recorded_date: isoDateTime.optional().describe("Date the symptom was recorded in ISO format"),
|
||||
note: z.string().optional().describe("Additional notes")
|
||||
clinical_status: z.enum(["active", "recurrence", "relapse", "inactive", "remission", "resolved"]),
|
||||
verification_status: z.enum(["unconfirmed", "provisional", "differential", "confirmed", "refuted", "entered-in-error"]),
|
||||
severity: z.enum(["severe", "moderate", "mild"]),
|
||||
onset: withFallback(z.object({ onset_datetime: isoDateTime }).default({ onset_datetime: new Date().toISOString() }).describe("ISO format"), { onset_datetime: new Date().toISOString() }),
|
||||
recorded_date: isoDateTime.optional().describe("ISO format"),
|
||||
note: z.string().optional()
|
||||
})),
|
||||
example: [
|
||||
{
|
||||
@ -384,11 +384,11 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
"diagnosis": {
|
||||
prompt: (isRes?: boolean) => z.array(z.object({
|
||||
code: codeStructure(isRes, "system-condition-code", true),
|
||||
clinical_status: z.enum(["active", "recurrence", "relapse", "inactive", "remission", "resolved"]).describe("Clincal Status of the diagnosis"),
|
||||
verification_status: z.enum(["unconfirmed", "provisional", "differential", "confirmed", "refuted", "entered-in-error"]).describe("Verification Status of the diagnosis"),
|
||||
onset: withFallback(z.object({ onset_datetime: isoDateTime }).default({ onset_datetime: new Date().toISOString() }).describe("Onset date of the symptom in ISO format"), { onset_datetime: new Date().toISOString() }),
|
||||
recorded_date: isoDateTime.optional().describe("Date the diagnosis was recorded. In ISO format"),
|
||||
note: z.string().optional().describe("Additional notes")
|
||||
clinical_status: z.enum(["active", "recurrence", "relapse", "inactive", "remission", "resolved"]),
|
||||
verification_status: z.enum(["unconfirmed", "provisional", "differential", "confirmed", "refuted", "entered-in-error"]),
|
||||
onset: withFallback(z.object({ onset_datetime: isoDateTime }).default({ onset_datetime: new Date().toISOString() }).describe("ISO format"), { onset_datetime: new Date().toISOString() }),
|
||||
recorded_date: withFallback(isoDateTime.describe("In ISO format").default(new Date().toISOString()), new Date().toISOString()),
|
||||
note: z.string().optional()
|
||||
})),
|
||||
example: [
|
||||
{
|
||||
@ -409,12 +409,12 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
"allergy_intolerance": {
|
||||
prompt: (isRes?: boolean) => z.array(z.object({
|
||||
code: codeStructure(isRes, "system-allergy-code", true),
|
||||
clinical_status: z.enum(["active", "inactive", "resolved"]).optional().describe("Clincal status of the allergy"),
|
||||
category: z.enum(["food", "medication", "environment", "biologic"]).optional().describe("Category of the allergy"),
|
||||
criticality: z.enum(["low", "high", "unable-to-assess"]).optional().describe("How critical is the allergy"),
|
||||
verification_status: z.enum(["unconfirmed", "presumed", "confirmed", "refuted", "entered-in-error"]).optional().describe("Verification Status of the allergy"),
|
||||
last_occurence: isoDateTime.optional().describe("The last occurance of the allergy In ISO format"),
|
||||
note: z.string().optional().describe("Additional Notes")
|
||||
clinical_status: z.enum(["active", "inactive", "resolved"]).optional(),
|
||||
category: z.enum(["food", "medication", "environment", "biologic"]).optional(),
|
||||
criticality: z.enum(["low", "high", "unable-to-assess"]).optional(),
|
||||
verification_status: z.enum(["unconfirmed", "presumed", "confirmed", "refuted", "entered-in-error"]).optional(),
|
||||
last_occurence: isoDateTime.optional().describe("ISO format"),
|
||||
note: z.string().optional()
|
||||
})),
|
||||
example: [
|
||||
{
|
||||
@ -433,7 +433,7 @@ export const STRUCTURED_INPUT_PROMPTS = {
|
||||
},
|
||||
"follow_up_appointment": {
|
||||
prompt: () => z.object({
|
||||
reason_for_visit: z.string().describe("The reason for the appointment")
|
||||
reason_for_visit: z.string()
|
||||
}),
|
||||
example: {
|
||||
reason_for_visit: "No change in condition"
|
||||
|
Loading…
x
Reference in New Issue
Block a user