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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 62 additions & 57 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,71 +25,71 @@
"cancel": "Cancel",
"microphone": "Microphone",
"enable_statistics": "Enable Statistics",
"history" : "History",
"history": "History",
"click_to_view_details": "Click to view details",
"audio" : "Audio",
"status" : "Status",
"documents" : "Documents",
"audio": "Audio",
"status": "Status",
"documents": "Documents",
"use_this_scribe": "Use this scribe",
"statistics": "Statistics",
"scribe_history": "Scribe History",
"no_scribe_history": "No Scribes found",
"developer_mode": "Developer Mode",
"all_status" : "All Status",
"newest_first" : "Newest First",
"oldest_first" : "Oldest First",
"date_and_time" : "Date & Time",
"encounter_id" : "Encounter ID",
"facility" : "Facility",
"ai_provider" : "AI Provider",
"search" : "Search",
"all_status": "All Status",
"newest_first": "Newest First",
"oldest_first": "Oldest First",
"date_and_time": "Date & Time",
"encounter_id": "Encounter ID",
"facility": "Facility",
"ai_provider": "AI Provider",
"search": "Search",
"scribe_details": "Scribe Details",
"requested_by" : "Requested By",
"requested_by": "Requested By",
"ai_summary": "AI Autofill",
"transcript": "Transcript",
"metadata": "Metadata",
"time_taken" : "Time Taken",
"prompt" : "Prompt",
"input_tokens" : "Input Tokens",
"output_tokens" : "Output Tokens",
"total_tokens" : "Total Tokens",
"transcription_time" : "Transcription Time",
"completion_time" : "Completion Time",
"total_time" : "Total Time",
"completion_id" : "Completion ID",
"audio_model" : "Audio Model",
"chat_model" : "Chat Model",
"provider" : "Provider",
"time_taken": "Time Taken",
"prompt": "Prompt",
"input_tokens": "Input Tokens",
"output_tokens": "Output Tokens",
"total_tokens": "Total Tokens",
"transcription_time": "Transcription Time",
"completion_time": "Completion Time",
"total_time": "Total Time",
"completion_id": "Completion ID",
"audio_model": "Audio Model",
"chat_model": "Chat Model",
"provider": "Provider",
"start_time": "Start Time",
"end_time": "End Time",
"ai_response": "AI Response",
"created_at" : "Created At",
"field_id" : "Field ID",
"field_name" : "Field Name",
"value" : "Value",
"no_fields_autofilled" : "No fields were autofilled",
"patient_name" : "Patient Name",
"facility_name" : "Facility Name",
"created_at": "Created At",
"field_id": "Field ID",
"field_name": "Field Name",
"value": "Value",
"no_fields_autofilled": "No fields were autofilled",
"patient_name": "Patient Name",
"facility_name": "Facility Name",
"search_by": "Search by",
"output_schema": "Output Schema",
"chunks" : "Chunks",
"chunk_number" : "Chunk {{number}}",
"chunks": "Chunks",
"chunk_number": "Chunk {{number}}",
"chunk_output": "Chunk Output",
"making_changes" : "Making Changes",
"no_autofill" : "No Autofill",
"making_changes": "Making Changes",
"no_autofill": "No Autofill",
"uploading_file": "Uploading File",
"upload_error": "Error uploading files. Please try again.",
"audio_upload_error": "Error uploading audio. Please try again.",
"retry" : "Retry",
"retry": "Retry",
"benchmark": "Benchmark",
"tokens" : "Tokens",
"used_tokens" : "Used Tokens",
"available_tokens" : "Available Tokens",
"tokens": "Tokens",
"used_tokens": "Used Tokens",
"available_tokens": "Available Tokens",
"ocr_allowed": "OCR Allowed",
"facility/user" : "Facility/User",
"most_tokens" : "Most Tokens",
"least_tokens" : "Least Tokens",
"username" : "Username",
"facility/user": "Facility/User",
"most_tokens": "Most Tokens",
"least_tokens": "Least Tokens",
"username": "Username",
"user": "User",
"scribe_quotas": "Scribe Quotas",
"new_quota": "New Quota",
Expand All @@ -99,29 +99,33 @@
"delete_quota": "Delete Quota",
"quota_deleted": "Quota deleted successfully",
"quota_deletion_failed": "Quota deletion failed",
"quota_updated_successfully" : "Quota updated successfully",
"quota_updated_successfully": "Quota updated successfully",
"quota_update_failed": "Quota update failed",
"quota_created_successfully": "Quota created successfully",
"quota_creation_failed": "Quota creation failed",
"yes" : "Yes",
"no" : "No",
"yes": "Yes",
"no": "No",
"terms_and_conditions": "Terms and Conditions",
"terms_and_conditions_description": "Please read and accept the terms and conditions to use Scribe.",
"tokens_per_user": "Tokens per User",
"select_facility": "Select Facility",
"search_by_facility_name": "Search by Facility Name",
"cached_tokens": "Cached Tokens",
"estimated_cost": "Estimated Cost",
"thinking" : "Thinking",
"audio_duration" : "Audio Duration",
"thinking_tokens" : "Thinking Tokens",
"text" : "Text",
"cached" : "Cached",
"image" : "Image",
"estimated_audio" : "Asmd. Audio",
"assumed_cost" : "Assumed Cost",
"processing" : "Processing",
"thinking": "Thinking",
"audio_duration": "Audio Duration",
"thinking_tokens": "Thinking Tokens",
"text": "Text",
"cached": "Cached",
"image": "Image",
"estimated_audio": "Asmd. Audio",
"assumed_cost": "Assumed Cost",
"processing": "Processing",
"processing_history": "Processing History",
"create_benchmark": "Create Benchmark",
"no_scribe_to_benchmark": "No scribe available to create benchmark",
"create_benchmark_information": "Create a benchmark from this completed scribe to evaluate future scribes against it. Make sure to edit the fields as necessary before creating the benchmark.",
"benchmark_name": "Benchmark Name",
"actions": "Actions",
"usage_details": "Usage Details",
"quota_details": "Quota Details",
Expand All @@ -130,5 +134,6 @@
"least_tokens_used": "Least Tokens Used",
"no_quotas_found": "No Quotas found",
"started_using_since": "Started Using Since",
"no_facility_found": "No facility found"
"no_facility_found": "No facility found",
"duration": "Duration"
}
14 changes: 14 additions & 0 deletions src/components/Controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from "@/utils/field-utils";
import { uploadScribeFile } from "@/utils/upload-utils";
import useAuthUser from "@/hooks/useAuthUser";
import CreateBenchmark from "./CreateBenchmark";

Comment on lines +54 to 56
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "Controller.tsx" -type f 2>/dev/null | head -5

Repository: ohcnetwork/care_scribe_fe

Length of output: 51


🏁 Script executed:

git ls-files | grep -i "controller.tsx"

Repository: ohcnetwork/care_scribe_fe

Length of output: 98


🏁 Script executed:

rg -n "useAuthUser" --type tsx

Repository: ohcnetwork/care_scribe_fe

Length of output: 96


🏁 Script executed:

cat -n src/components/Controller.tsx | sed -n '50,80p'

Repository: ohcnetwork/care_scribe_fe

Length of output: 1441


🏁 Script executed:

rg "useAuthUser" src/components/Controller.tsx

Repository: ohcnetwork/care_scribe_fe

Length of output: 115


🏁 Script executed:

wc -l src/components/Controller.tsx

Repository: ohcnetwork/care_scribe_fe

Length of output: 102


🏁 Script executed:

cat -n src/components/Controller.tsx | grep -E "(useAuthUser|lastScribe|CreateBenchmark)"

Repository: ohcnetwork/care_scribe_fe

Length of output: 372


🏁 Script executed:

cat tsconfig.json | head -50

Repository: ohcnetwork/care_scribe_fe

Length of output: 281


🏁 Script executed:

cat -n src/components/CreateBenchmark.tsx | head -60

Repository: ohcnetwork/care_scribe_fe

Length of output: 2307


🏁 Script executed:

cat tsconfig.app.json | grep -A 5 "noUnusedLocals"

Repository: ohcnetwork/care_scribe_fe

Length of output: 245


Remove unused useAuthUser import to fix TS6133 build error

useAuthUser is imported but never used in this file. Since noUnusedLocals is enabled in tsconfig.app.json, this triggers TS6133. The auth gating already happens inside CreateBenchmark (line 42), so this import can be safely removed.

Apply this diff:

-import { cleanAIResponse, poller } from "@/utils/response-utils";
-import {
-  getHydratedFields, getQuestionInputs
-} from "@/utils/field-utils";
-import { uploadScribeFile } from "@/utils/upload-utils";
-import useAuthUser from "@/hooks/useAuthUser";
-import CreateBenchmark from "./CreateBenchmark";
+import { cleanAIResponse, poller } from "@/utils/response-utils";
+import { getHydratedFields, getQuestionInputs } from "@/utils/field-utils";
+import { uploadScribeFile } from "@/utils/upload-utils";
+import CreateBenchmark from "./CreateBenchmark";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import useAuthUser from "@/hooks/useAuthUser";
import CreateBenchmark from "./CreateBenchmark";
import CreateBenchmark from "./CreateBenchmark";
🧰 Tools
🪛 GitHub Actions: Build and Deploy

[error] 54-54: TS6133: 'useAuthUser' is declared but its value is never read.

🪛 GitHub Check: build

[failure] 54-54:
'useAuthUser' is declared but its value is never read.

🤖 Prompt for AI Agents
In src/components/Controller.tsx around lines 54 to 56, the import of
useAuthUser is unused and causes a TS6133 build error; remove the unused import
line (import useAuthUser from "@/hooks/useAuthUser";) from the top of the file
so only the necessary imports remain (keeping CreateBenchmark import intact),
then run TypeScript/tsc or the project build to verify the error is resolved.

function MetaInformation(props: { meta: ScribeModel["meta"] }) {
const latestProcessing =
Expand Down Expand Up @@ -98,6 +99,7 @@ export function Controller(props: {
const [openEditTranscript, setOpenEditTranscript] = useState(false);
const [devMode] = useStorage("scribe-enable-dev-mode");
const [controllerPosition] = useStorage("scribe-controller-position");
const [lastScribe, setLastScribe] = useState<ScribeModel | null>(null);
const [scribe, setScribe] = useState<ScribeModel | null>(null);
const [files, setFiles] = useState<File[]>([]);
const path = usePath();
Expand Down Expand Up @@ -261,6 +263,9 @@ export function Controller(props: {
form_data: hfields,
requested_in_facility_id: facilityId || "",
requested_in_encounter_id: encounterId || "",
requested_in_questionnaire_ids: (props.formState as any).map(
(q: any) => q.questionnaire.id,
) as string[],
});

try {
Expand Down Expand Up @@ -420,9 +425,18 @@ export function Controller(props: {
setToReview(getFieldsToReview(aiResponse, fields));
};

useEffect(() => {
if (scribe) {
setLastScribe(scribe);
}
}, [scribe]);

return (
<>
{/* placeholder */}
{lastScribe && (
<CreateBenchmark scribe={lastScribe} formState={props.formState} />
)}
<div className="h-10" />
<div
className={`fixed z-40 flex ${controllerPosition.includes("top") ? "top-5 flex-col-reverse" : "bottom-5 flex-col"} ${controllerPosition.includes("right") ? "right-5 items-end" : "left-5 items-start"} gap-4 transition-all`}
Expand Down
90 changes: 90 additions & 0 deletions src/components/CreateBenchmark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import useAuthUser from "@/hooks/useAuthUser";
import { ScribeModel } from "@/types";
import { Button } from "./ui/button";
import { useTranslation } from "react-i18next";
import { I18NNAMESPACE } from "@/utils/constants";
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog";
import { useContainerRef } from "@/hooks/useContainerRef";
import { useStorage } from "@/hooks/useStorage";
import { CreatedBenchmark } from "@/pages/Benchmark";
import { useState } from "react";
import { Input } from "./ui/input";
import { openDB, putAudio } from "@/utils/idb";

export default function CreateBenchmark(props: {
scribe: ScribeModel;
formState: unknown;
}) {
const { t } = useTranslation(I18NNAMESPACE);
const containerRef = useContainerRef();
const [createdBenchmarks, setCreatedBenchmarks] = useStorage(
"scribe-created-benchmarks",
);
const [name, setName] = useState("");
const [open, setOpen] = useState(false);

const { scribe, formState } = props;

const user = useAuthUser();

const handleCreateBenchmark = async () => {
if (!scribe) {
alert(t("no_scribe_to_benchmark"));
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using browser alert() provides poor user experience and is inconsistent with the modern UI components used elsewhere in the codebase. Consider using a toast notification or the Dialog component to display this error message.

Copilot uses AI. Check for mistakes.
return;
}

const storedIdentifier = crypto.randomUUID();
const db = await openDB();

for (const audio of scribe.audio) {
const response = await fetch(audio.read_signed_url);
const arrayBuffer = await response.arrayBuffer();

await putAudio(db, storedIdentifier, {
identifier: storedIdentifier,
mimeType: audio.mime_type,
data: arrayBuffer,
});
}

db.close();

const form: any = formState ? formState : [];
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any type for form bypasses type safety. Define a proper interface for formState in the component props and use that type here, or at minimum use unknown with type guards to safely access the data.

Copilot uses AI. Check for mistakes.
const newBenchmark: CreatedBenchmark = {
name: name || `Benchmark ${createdBenchmarks.length + 1}`,
id: crypto.randomUUID(),
createdAt: new Date(),
files: scribe.audio.map((a) => ({
identifier: storedIdentifier,
type: "audio",
mimeType: a.mime_type,
})),
formState: form,
};
setCreatedBenchmarks((prev) => [...prev, newBenchmark]);
setOpen(false);
};

if (!user?.is_superuser) return null;

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>{t("create_benchmark")}</Button>
</DialogTrigger>
<DialogContent portalProps={{ container: containerRef?.current }}>
<DialogTitle className="text-lg font-semibold">
{t("create_benchmark")}?
</DialogTitle>
<p>{t("create_benchmark_information")}</p>
<Input
className="mt-4"
placeholder={t("benchmark_name") || ""}
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Button onClick={handleCreateBenchmark}>{t("create_benchmark")}</Button>
</DialogContent>
</Dialog>
);
}
2 changes: 1 addition & 1 deletion src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function SheetTitle({
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn(
"font-semibold text-neutral-950 dark:text-neutral-50",
"text-2xl font-bold text-neutral-950 dark:text-neutral-50",
className,
)}
{...props}
Expand Down
10 changes: 2 additions & 8 deletions src/hooks/useStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Benchmark } from "@/pages/Benchmark";
import { Benchmark, CreatedBenchmark } from "@/pages/Benchmark";
import { ScribeControllerPosition } from "@/types";
import { useCallback, useEffect, useState } from "react";

Expand All @@ -7,6 +7,7 @@ export const storageDefaults = {
"scribe-controller-position": "bottom-right" as ScribeControllerPosition,
"scribe-enable-dev-mode": false as boolean,
"scribe-benchmarks": [] as Benchmark[],
"scribe-created-benchmarks": [] as CreatedBenchmark[],
} as const;

export type StorageKeys = keyof typeof storageDefaults;
Expand Down Expand Up @@ -46,13 +47,6 @@ export function useStorage<K extends StorageKeys>(
: newValueOrUpdater;

localStorage.setItem(key, JSON.stringify(newValue));
// trigger same-tab listeners manually
window.dispatchEvent(
new StorageEvent("storage", {
key,
newValue: JSON.stringify(newValue),
}),
);

return newValue;
});
Expand Down
15 changes: 15 additions & 0 deletions src/manifest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ const manifest: Manifest = {
<HistoryDetailsLazy scribeId={id} />
</Page>
),
"/admin/scribe/history/:id": ({ id }) => (
<Page>
<HistoryDetailsLazy scribeId={id} />
</Page>
),
"/admin/scribe/benchmark": () => (
<Page>
<BenchmarkPage />
Expand All @@ -60,6 +65,11 @@ const manifest: Manifest = {
<ScribeQuotaUsageLazy quotaId={quotaId} />
</Page>
),
"/admin/scribe/history": () => (
<Page>
<HistoryListLazy admin={true} />
</Page>
),
},
extends: [],
components: {
Expand Down Expand Up @@ -88,6 +98,11 @@ const manifest: Manifest = {
name: "Benchmark",
icon: <SidebarIcon />,
},
{
url: `/admin/scribe/history`,
name: "History",
icon: <SidebarIcon />,
},
],
},
],
Expand Down
Loading