1import type { ChatAttachment } from "@t3tools/contracts";
2import {
3 composerDraftHasUserContent,
4 useComposerDraftStore,
5 type ComposerFileAttachment,
6 type ComposerImageAttachment,
7 type ComposerThreadTarget,
8} from "../../composerDraftStore";
9
10/** Keep an unsaved edit when its queued run starts or is removed remotely. */
11export function recoverQueuedMessageEdit(input: {
12 readonly editTarget: ComposerThreadTarget;
13 readonly threadTarget: ComposerThreadTarget;
14 readonly originalText: string;
15}): "kept" | "discarded" | "clean" {
16 const store = useComposerDraftStore.getState();
17 const draft = store.getComposerDraft(input.editTarget);
18 const dirty =
19 draft !== null &&
20 (draft.prompt !== input.originalText || draft.images.length > 0 || draft.files.length > 0);
21 if (dirty && !composerDraftHasUserContent(store.getComposerDraft(input.threadTarget))) {
22 store.moveComposerPromptAndImages(input.editTarget, input.threadTarget);
23 return "kept";
24 }
25 store.clearComposerContent(input.editTarget);
26 return dirty ? "discarded" : "clean";
27}
28
29/** Generic files need upload references; images also support the inline transport. */
30export async function prepareQueuedEditAttachments(input: {
31 readonly existingAttachments: ReadonlyArray<ChatAttachment>;
32 readonly images: ReadonlyArray<ComposerImageAttachment>;
33 readonly files: ReadonlyArray<ComposerFileAttachment>;
34 readonly uploadFiles: (
35 files: ReadonlyArray<ComposerFileAttachment>,
36 ) => Promise<ReadonlyArray<ChatAttachment>>;
37 readonly readImage: (file: File) => Promise<string>;
38}) {
39 const files = input.files.length === 0 ? [] : await input.uploadFiles(input.files);
40 if (files.length !== input.files.length)
41 throw new Error("Retry or remove failed uploads before saving.");
42 const images = await Promise.all(
43 input.images.map(async (image) => ({
44 type: "image" as const,
45 id: image.id,
46 name: image.name,
47 mimeType: image.mimeType,
48 sizeBytes: image.sizeBytes,
49 dataUrl: await input.readImage(image.file),
50 ...(image.source ? { source: image.source } : {}),
51 })),
52 );
53 return [...input.existingAttachments, ...images, ...files];
54}