1import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
2import { create } from "zustand";
3import { DraftId, useComposerDraftStore } from "./composerDraftStore";
4import { releaseDraftAttachments } from "./lib/attachmentUploadQueue";
5
6export function questionAttachmentDraftPrefix(
7 environmentId: EnvironmentId,
8 threadId: ThreadId,
9): string {
10 return `${encodeURIComponent(JSON.stringify(environmentId))}:question-${encodeURIComponent(JSON.stringify(threadId))}-`;
11}
12
13export function questionAttachmentDraftId(
14 environmentId: EnvironmentId,
15 threadId: ThreadId,
16 requestId: string,
17 questionId: string,
18): DraftId {
19 return DraftId.make(
20 `${questionAttachmentDraftPrefix(environmentId, threadId)}${encodeURIComponent(JSON.stringify([requestId, questionId]))}`,
21 );
22}
23
24export const useQuestionAttachmentPreparation = create<{ counts: Record<string, number> }>(() => ({
25 counts: {},
26}));
27
28/** Count both staged files and in-flight preparation against the shared question limit. */
29export function countQuestionAttachments(keys: ReadonlyArray<DraftId>): number {
30 const store = useComposerDraftStore.getState();
31 const { counts } = useQuestionAttachmentPreparation.getState();
32 return keys.reduce((total, key) => {
33 const draft = store.getComposerDraft(key);
34 return total + (draft?.images.length ?? 0) + (draft?.files.length ?? 0) + (counts[key] ?? 0);
35 }, 0);
36}
37
38export function changeQuestionAttachmentPreparation(key: DraftId, delta: number): void {
39 useQuestionAttachmentPreparation.setState((state) =>
40 delta < 0 && !(key in state.counts)
41 ? state
42 : {
43 counts: { ...state.counts, [key]: Math.max(0, (state.counts[key] ?? 0) + delta) },
44 },
45 );
46}
47
48export function clearQuestionAttachmentDraft(key: DraftId): void {
49 const store = useComposerDraftStore.getState();
50 const draft = store.getComposerDraft(key);
51 if (draft) {
52 releaseDraftAttachments([...draft.images, ...draft.files]);
53 for (const image of draft.images) {
54 if (image.previewUrl.startsWith("blob:")) URL.revokeObjectURL(image.previewUrl);
55 }
56 }
57 store.clearComposerContent(key);
58 useQuestionAttachmentPreparation.setState(({ counts }) => {
59 const next = { ...counts };
60 delete next[key];
61 return { counts: next };
62 });
63}