"use client" /** * Live previews for LeoAssistBar — catalog detail + Design OS docs. * * The fake Leo does deterministic string work so the previews are honest about * what undo restores, without needing a model behind them. */ import * as React from "react" import { createPortal } from "react-dom" import { AskLeoButton } from "@/components/ask-leo-button" import { LeoAssistField } from "@/components/leo-assist-field" import { LeoAssistBar, type LeoAssistAction, type LeoAssistRequest, type LeoAssistSelection, } from "@/components/leo-assist-bar" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Field, FieldGroup, FieldLabel, FieldLegend, FieldSet, } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Separator } from "@/components/ui/separator" import { Textarea } from "@/components/ui/textarea" import { Tip } from "@/components/ui/tip" import { getTextareaSelectionRect } from "@/lib/textarea-selection-rect" import { cn } from "@/lib/utils" const SEED_TEXT = "Students on this rotation will be supervised by a licensed physical therapist and are expected to complete a weekly reflection, attend the Monday huddle, and log all patient encounters in the system before the end of each shift." /** * All entries live in the overflow More menu. * * One glyph per action, and none of them repeated across the two lists. In an * overflow menu the icon is read before the label, so two actions wearing the * same wand are two actions the user has to read twice to tell apart. */ const REWRITE_ACTIONS: readonly LeoAssistAction[] = [ { id: "polish", label: "Polish", icon: "fa-wand-magic-sparkles" }, { id: "shorten", label: "Shorten", icon: "fa-scissors" }, { id: "expand", label: "Add detail", icon: "fa-paragraph" }, { id: "formal", label: "More formal", icon: "fa-user-tie" }, { id: "bullets", label: "Make a list", icon: "fa-list" }, ] const GENERATE_ACTIONS: readonly LeoAssistAction[] = [ { id: "draft", label: "Draft", icon: "fa-file-pen", instruction: "Draft a site description for a physical therapy rotation", }, { id: "from-template", label: "Start from the standard description", icon: "fa-clipboard-list", }, ] const REWRITE_EXAMPLES = [ "Write it for a first year", "Cut it to two sentences", "Lead with attendance", ] as const const GENERATE_EXAMPLES = [ "An outpatient PT rotation", "A twelve week placement", ] as const function polish(text: string, instruction: string): string { switch (instruction) { case "Polish": return "Students on this rotation are supervised by a licensed physical therapist. Each week they submit a reflection, attend the Monday huddle, and log every patient encounter before the shift ends." case "Shorten": return "Students are supervised by a licensed physical therapist. Submit a weekly reflection, attend the Monday huddle, and log encounters before each shift ends." case "Add detail": return `${text} Reflections are reviewed by the clinical coordinator within two business days.` case "More formal": return "Students assigned to this rotation shall be supervised by a licensed physical therapist. Each student is required to submit a weekly reflection, attend the Monday huddle, and record all patient encounters prior to the conclusion of every shift." case "Make a list": return "Supervised by a licensed physical therapist.\nSubmit a weekly reflection.\nAttend the Monday huddle.\nLog all patient encounters before each shift ends." default: return `${text.replace(/\s+$/, "")} (${instruction})` } } /** * A scoped rewrite gets mechanical transforms rather than the canned paragraphs * above: the point of the preview is to show that only the selected span moves, * which is only believable if the replacement is visibly derived from it. */ function rewriteSpan(span: string, instruction: string): string { const trimmed = span.trim() switch (instruction) { case "Polish": return trimmed.replace(/\s+/g, " ").replace(/\s*,\s*and\b/g, " and") case "Shorten": return trimmed.split(/,\s*/)[0] case "Add detail": return `${trimmed}, documented the same day` case "More formal": return trimmed .replace(/\bare expected to\b/g, "shall") .replace(/\bwill be\b/g, "are") .replace(/\ball\b/g, "every") case "Make a list": return trimmed.split(/,\s*/).join("\n") default: return `${trimmed} (${instruction})` } } function fakeLeo(request: LeoAssistRequest): string { if (request.selection) { return request.mode === "generate" ? "a short passage written by Leo" : rewriteSpan(request.text, request.instruction) } if (request.mode === "generate") { if (request.instruction === "Start from the standard description") { return SEED_TEXT } return "Students on this rotation work alongside a licensed physical therapist in an outpatient clinic. They carry a partial caseload by week four and document every encounter the same day." } return polish(request.text, request.instruction) } function delay(value: T, ms: number) { return new Promise(resolve => { window.setTimeout(() => resolve(value), ms) }) } /** Locks every control inside while Leo is in flight. */ function FormLock({ busy, children, className, }: { busy: boolean children: React.ReactNode className?: string }) { return (
{children}
) } function LeoAssistBarDemo({ seeded = true, delayMs = 1400, }: { seeded?: boolean delayMs?: number }) { const fieldId = React.useId() const [text, setText] = React.useState(seeded ? SEED_TEXT : "") const [busy, setBusy] = React.useState(false) const run = React.useCallback( (request: LeoAssistRequest) => delay(fakeLeo(request), delayMs), [delayMs], ) return (
Site description