import { usePrompt } from "./drafts.js"; import { useEffect, useState } from "react"; import { useCrdtText, useInterface, referenceOf, type ExactReference } from "@quixos/camino-react"; import { Icon, LocalChoice, TextArea, SegmentedControl } from "@quixos/camino-react/ui"; import { interfaces } from "./gen/client.js"; import { accepted, asInterface, errorMessage, type Root } from "./model.js"; type TextNode = Root["documents"]["entries"][number]["node"]; function TextRow({ node, open, selected, changed, }: { node: TextNode; open: (ref: ExactReference) => void; selected?: string | undefined; changed: (id: string, archived: boolean) => void; }) { const api = useInterface( { archived: { set: interfaces.ReadingDocument.archived.set } }, asInterface(referenceOf(node), "document"), ); const [busy, setBusy] = useState(false), [error, setError] = useState(""); const archive = async () => { if (api.status !== "ready") return; setBusy(true); setError(""); try { await accepted(api.value.archived.set(!node.archived)); changed(referenceOf(node).object, !node.archived); } catch (e) { setError(errorMessage(e)); } finally { setBusy(false); } }; return (
{error && (

{error}

)}
); } export function Texts({ root, selected, open, add, speech, }: { root: Root; selected?: string | undefined; open: (ref: ExactReference) => void; add: () => void; speech: () => void; }) { const [view, setView] = useState("active"), [overrides, setOverrides] = useState>({}); useEffect(() => { setOverrides((old) => { const next = { ...old }; for (const { node } of root.documents.entries) { const id = referenceOf(node).object; if (next[id] === node.archived) delete next[id]; } return Object.keys(next).length === Object.keys(old).length ? old : next; }); }, [root.documents]); const nodes = root.documents.entries .map((e) => e.node) .filter((n) => (overrides[referenceOf(n).object] ?? n.archived) === (view === "archived")); return (

Texts

{nodes.length} {view === "archived" ? "archived " : ""} {nodes.length === 1 ? "text" : "texts"}

{selected && root.documents.entries.some((e) => referenceOf(e.node).object === selected) && ( )}
{nodes.length === 0 && (

{view === "archived" ? "No archived texts" : "No texts yet"}

{view === "active" && }
)}
); } export function GenerateText({ library, root, initialReader, navigation, added, }: { library: ExactReference; root: Root; initialReader?: string | undefined; navigation: ExactReference; added: (ref: ExactReference) => void; }) { const draft = usePrompt(navigation, "generate"); const prompt = draft.text; const api = useInterface(interfaces.LanguageReader, library), [reader, setReader] = useState(initialReader ?? root.defaultReader?._camino.ref.object ?? ""), [busy, setBusy] = useState(false), [job, setJob] = useState(""), [error, setError] = useState(""); const task = root.tasks.entries.map((e) => e.node).find((n) => referenceOf(n).object === job); useEffect(() => { if (task?.data.status === "ready" && task.document) added(referenceOf(task.document)); }, [task?.data.status, task?.document?._camino.ref.object]); const failed = !!task && ["failed", "cancelled", "uncertain"].includes(task.data.status); const generate = async () => { if (api.status !== "ready" || !reader) return; setBusy(true); setError(""); try { await draft.flush(); const ref = await accepted( api.value.generateDocument.call({ prompt, reader: { ...root.readers.entries.find((e) => referenceOf(e.node).object === reader)!.node._camino.ref, expectation: { kind: "atom", id: "atom:reading:reader" }, }, createdAt: new Date().toISOString(), }), ); setJob(ref.object); draft.clearSubmitted(prompt); } catch (e) { setError(errorMessage(e)); } finally { setBusy(false); } }; return (