1import type { LinkedPrompt } from "@t3tools/shared/promptLinks";
2
3import { type ComposerThreadTarget, useComposerDraftStore } from "~/composerDraftStore";
4
5import { toastManager } from "../ui/toast";
6
7/** A warning that stays until dismissed when the link's prompt hid characters from the reader. */
8export function warnAboutHiddenCharacters(prompt: LinkedPrompt): void {
9 if (prompt.hiddenCharacters === 0) return;
10 toastManager.add({
11 type: "warning",
12 title: "This prompt had hidden characters, it may be malicious",
13 description: `The Knife removed ${prompt.hiddenCharacters} hidden character${prompt.hiddenCharacters === 1 ? "" : "s"}. Read the prompt before you send it.`,
14 timeout: 0,
15 });
16}
17
18/**
19 * Puts a linked prompt in a composer for the user to read and send. Text the user already typed
20 * there is never replaced without asking: a toast offers to swap it for the linked prompt.
21 * `notes` carry anything the link asked for and did not get, such as a model.
22 */
23export function placeLinkedPrompt(
24 target: ComposerThreadTarget,
25 prompt: LinkedPrompt,
26 notes: ReadonlyArray<string> = [],
27): void {
28 warnAboutHiddenCharacters(prompt);
29 const place = () => {
30 useComposerDraftStore.getState().setPrompt(target, prompt.text);
31 // It was not written here, so it must be read before it is sent.
32 toastManager.add({
33 type: "info",
34 title: "Prompt added from a link",
35 description: ["Read it before you send.", ...notes].join(" "),
36 });
37 };
38 const existing = useComposerDraftStore.getState().getComposerDraft(target)?.prompt ?? "";
39 if (existing.trim().length === 0 || existing === prompt.text) {
40 place();
41 return;
42 }
43 const id = toastManager.add({
44 type: "info",
45 title: "Linked prompt ready",
46 description: "This thread already has a draft. Replace it with the linked prompt?",
47 timeout: 0,
48 actionProps: {
49 children: "Replace draft",
50 onClick: () => {
51 toastManager.close(id);
52 place();
53 },
54 },
55 });
56}