diff --git a/frontend/components/chat/chat-input.tsx b/frontend/components/chat/chat-input.tsx index f43bca8..e8abb01 100644 --- a/frontend/components/chat/chat-input.tsx +++ b/frontend/components/chat/chat-input.tsx @@ -1,78 +1,31 @@ "use client"; import type { ChatStatus } from "ai"; -import { - ArrowUp, - Building2, - CalendarRange, - ChevronDown, - Hand, - Mic, - Plus, - Square, - Stethoscope, - UserPlus, - X, -} from "lucide-react"; +import { ArrowUp, Mic, Plus, Square, UserPlus, X } from "lucide-react"; import { type ChangeEvent, type KeyboardEvent, - type ReactNode, useCallback, - useMemo, useRef, useState, } from "react"; import { useTranslation } from "react-i18next"; +import { ModelPicker } from "@/components/chat/model-picker"; import { PatientFormDialog } from "@/components/chat/patient-form-dialog"; -import { - Menu, - MenuPopup, - MenuRadioGroup, - MenuRadioItem, - MenuTrigger, -} from "@/components/ui/menu"; +import type { Effort } from "@/lib/ai-models"; import { cn } from "@/lib/utils"; type ChatInputProps = { onSubmit: (text: string) => void; status: ChatStatus; onStop?: () => void; + model: string; + effort: Effort; + onModelChange: (model: string) => void; + onEffortChange: (effort: Effort) => void; }; -type Option = { value: string; label: string }; -type OptionKey = { value: string; labelKey: string }; - -const ACCESS_OPTIONS: OptionKey[] = [ - { value: "standard", labelKey: "chat.input.access.standard" }, - { value: "break-glass", labelKey: "chat.input.access.breakGlass" }, - { value: "read-only", labelKey: "chat.input.access.readOnly" }, -]; -const RESPONSE_OPTIONS: OptionKey[] = [ - { value: "concise", labelKey: "chat.input.response.concise" }, - { value: "detailed", labelKey: "chat.input.response.detailed" }, - { value: "comprehensive", labelKey: "chat.input.response.comprehensive" }, -]; -const SPECIALTY_OPTIONS: OptionKey[] = [ - { value: "internal-medicine", labelKey: "chat.input.specialtyOptions.internalMedicine" }, - { value: "cardiology", labelKey: "chat.input.specialtyOptions.cardiology" }, - { value: "pediatrics", labelKey: "chat.input.specialtyOptions.pediatrics" }, - { value: "emergency", labelKey: "chat.input.specialtyOptions.emergency" }, - { value: "all", labelKey: "chat.input.specialtyOptions.all" }, -]; -const FACILITY_OPTIONS: OptionKey[] = [ - { value: "main-hospital", labelKey: "chat.input.facilityOptions.mainHospital" }, - { value: "north-clinic", labelKey: "chat.input.facilityOptions.northClinic" }, - { value: "telehealth", labelKey: "chat.input.facilityOptions.telehealth" }, -]; -const TIME_OPTIONS: OptionKey[] = [ - { value: "30d", labelKey: "chat.input.timeOptions.30d" }, - { value: "12m", labelKey: "chat.input.timeOptions.12m" }, - { value: "5y", labelKey: "chat.input.timeOptions.5y" }, - { value: "all", labelKey: "chat.input.timeOptions.all" }, -]; - const iconButton = "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"; const pillButton = @@ -80,79 +33,19 @@ const pillButton = const contextPill = "flex h-7 items-center gap-1.5 rounded-md px-2 text-[13px] text-muted-foreground transition-colors hover:bg-foreground/5 hover:text-foreground"; -function SelectPill({ - ariaLabel, - triggerClassName, - chevronClassName, - icon, - prefix, - value, - onValueChange, - options, - align = "start", -}: { - ariaLabel: string; - triggerClassName: string; - chevronClassName: string; - icon: ReactNode; - prefix?: string; - value: string; - onValueChange: (value: string) => void; - options: Option[]; - align?: "start" | "center" | "end"; -}) { - const selected = options.find((option) => option.value === value); - - return ( - - - } - > - {icon} - {prefix ? ( - {prefix} - ) : null} - {selected?.label} - - - - - {options.map((option) => ( - - {option.label} - - ))} - - - - ); -} - -export function ChatInput({ onSubmit, status, onStop }: ChatInputProps) { +export function ChatInput({ + onSubmit, + status, + onStop, + model, + effort, + onModelChange, + onEffortChange, +}: ChatInputProps) { const { t } = useTranslation(); - const toOptions = useCallback( - (opts: OptionKey[]): Option[] => - opts.map((o) => ({ value: o.value, label: t(o.labelKey) })), - [t], - ); - const accessOptions = useMemo(() => toOptions(ACCESS_OPTIONS), [toOptions]); - const responseOptions = useMemo(() => toOptions(RESPONSE_OPTIONS), [toOptions]); - const specialtyOptions = useMemo( - () => toOptions(SPECIALTY_OPTIONS), - [toOptions], - ); - const facilityOptions = useMemo(() => toOptions(FACILITY_OPTIONS), [toOptions]); - const timeOptions = useMemo(() => toOptions(TIME_OPTIONS), [toOptions]); const [value, setValue] = useState(""); const [files, setFiles] = useState([]); - const [access, setAccess] = useState("standard"); - const [responseMode, setResponseMode] = useState("detailed"); - const [specialty, setSpecialty] = useState("internal-medicine"); - const [facility, setFacility] = useState("main-hospital"); - const [timeRange, setTimeRange] = useState("12m"); const [addOpen, setAddOpen] = useState(false); // Bumped on each open so the dialog remounts with a fresh file number + form. const [addKey, setAddKey] = useState(0); @@ -216,10 +109,10 @@ export function ChatInput({ onSubmit, status, onStop }: ChatInputProps) { event.preventDefault(); submit(); }} - className="w-full overflow-hidden rounded-[28px] border border-border/60 bg-muted shadow-sm" + className="w-full overflow-hidden rounded-[28px] border border-border/60 bg-input shadow-sm" > - {/* Top (lighter) card: textarea + toolbar, with a slightly smaller bottom radius */} -
+ {/* Textarea + toolbar, filling the rounded card. */} +