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 (
{job && !failed && (
Writing your text. You can close this window; it will appear in Texts when ready.
)}
{(error || failed) && (
{error || task?.data.error || "The text could not be generated."}
)}
);
}