import { useState } from "react"; import type { WidgetProps } from "./types"; import type { UiResponse, WidgetDescriptor } from "../api/types"; import { Dialog, DialogContent, DialogTitle } from "../components/ui/dialog"; import { GateArtifactBody } from "./GateArtifactBody"; import { ReservedControls } from "./ReservedControls"; import { LinkageHost } from "./LinkageHost"; /** * Artifact review gate rendered as a centered (70%) modal: the artifact fills * the top (scrollable) area via ArtifactView; the action bar (options + reserved * controls) sits at the bottom. The gate contract forbids silent dismissal, so the * dialog has no close button and is not closeable by Esc/backdrop — the only way * out is an action or a reserved control, both of which call onRespond. */ export function ArtifactGateWidget({ descriptor, onRespond, sessionId }: WidgetProps) { const [pendingLinkage, setPendingLinkage] = useState<{ parentResponse: UiResponse; childDescriptor: WidgetDescriptor; } | null>(null); function handleOption(optionId: string) { const option = descriptor.options?.find((o) => o.id === optionId); const parentResponse: UiResponse = { id: descriptor.id, kind: "artifact-gate", choices: [optionId] }; if (option?.opens) setPendingLinkage({ parentResponse, childDescriptor: option.opens }); else onRespond(parentResponse); } return ( {descriptor.title ?? "Artifact review"}
{descriptor.artifact ? ( ) : (

No artifact.

)}
{pendingLinkage ? ( ) : ( <>
{descriptor.options?.map((o, i) => ( ))}
onRespond({ id: descriptor.id, control: c, ...(t !== undefined ? { text: t } : {}) }) } /> )}
); }