/** Accessible multimodal UI. It emits intents but never fetches, uploads, or opens devices. */
import { Camera, FileText, Image as ImageIcon, Mic2, MonitorUp, Pencil, Upload } from "lucide-react";
import { useMemo, useRef, useState } from "react";
import {
buildAnnotation, buildCaptureIntent, buildImageEdit, buildTranscriptCorrection,
captureAuthorization, multimodalEnabled, normalizeMultimodalPage,
} from "./model.ts";
import { validateUpload } from "./security.ts";
import type { FileCandidate, MediaView, MultimodalProps } from "./types.ts";
function MediaPreview({item}: {item: MediaView}) {
if (item.kind === "image" && item.previewUrl) return
{item.fileName} · version {item.artifactVersion}
;
if (item.kind === "document" && item.previewText) return
{item.fileName}
{item.previewText}
Safe text preview; active document content is not executed.
;
const Icon = item.kind === "image" ? ImageIcon : item.kind === "audio" ? Mic2 : FileText;
return
{item.fileName}
{item.mime} · {Math.ceil(item.bytes / 1024)} KB
Content is not executed. Open an authorized artifact preview to inspect it.
;
}
function UploadPanel({props, authorization, setNotice}: {
props: MultimodalProps;
authorization: MultimodalProps["page"]["upload_authorization"];
setNotice: (message: string) => void;
}) {
const input = useRef(null);
const authorized = authorization === "authorized";
function handle(files: FileList | readonly File[]) {
const accepted: FileCandidate[] = [];
const rejected: string[] = [];
for (const file of Array.from(files).slice(0, 20)) {
const result = validateUpload(file, String(authorization));
if (result.ok) accepted.push(result.file);
else rejected.push(`${file.name || "File"}: ${result.reason}`);
}
if (accepted.length) props.onUpload?.(accepted);
setNotice(rejected.length ? rejected.join(" ") : `${accepted.length} file${accepted.length === 1 ? "" : "s"} ready to upload.`);
}
return ;
}
function MediaEditor({item, props}: {item: MediaView; props: MultimodalProps}) {
const [draft, setDraft] = useState("");
const [page, setPage] = useState("");
if (item.kind === "audio") return null;
const image = item.kind === "image";
return ;
}
function TranscriptEditor({props, page}: {props: MultimodalProps; page: ReturnType}) {
const transcript = page?.transcript;
const [text, setText] = useState(transcript?.text || "");
if (!transcript) return No voice transcript is attached to this turn.
;
return ;
}
function CaptureButton({kind, props}: {kind: "camera" | "screen"; props: MultimodalProps}) {
const decision = captureAuthorization(props.autonomyPolicy, null, props.identity);
const Icon = kind === "camera" ? Camera : MonitorUp;
return ;
}
/** Render nothing unless foundation, projects, artifacts, autonomy, and multimodal are active. */
export function MultimodalChat(props: MultimodalProps) {
const enabled = multimodalEnabled(props.flags);
const page = useMemo(() => enabled ? normalizeMultimodalPage(props.page, props.identity, props.scope) : null,
[enabled, props.identity, props.page, props.scope]);
const [selectedId, setSelectedId] = useState(null);
const [notice, setNotice] = useState("");
if (!enabled || !page) return null;
const selected = page.media.find((item) => item.id === selectedId) || page.media[0] || null;
return
{props.error && {props.error}
}
{page.rejected > 0 &&
Some media was withheld because its owner, binding, type, or lineage was invalid.
}
{selected ? <>> :
Select media to preview or annotate it.
}
{notice ||
`${page.media.length} media items and ${page.threads.length} generation threads available`}
;
}