@assistant-ui/react
Version:
Open-source TypeScript/React library for building production-grade AI chat experiences
83 lines (68 loc) • 2.32 kB
text/typescript
"use client";
import {
type ActionButtonElement,
type ActionButtonProps,
createActionButton,
} from "../../utils/createActionButton";
import { useCallback, useInsertionEffect, useRef } from "react";
import { useAui } from "@assistant-ui/store";
import { useComposerAddAttachment as useComposerAddAttachmentBehavior } from "@assistant-ui/core/react";
const useComposerAddAttachment = ({
multiple = true,
}: {
/** allow selecting multiple files */
multiple?: boolean | undefined;
} = {}) => {
const { disabled, addAttachment } = useComposerAddAttachmentBehavior();
const aui = useAui();
const scopeGenerationRef = useRef(0);
useInsertionEffect(
() => () => {
scopeGenerationRef.current += 1;
},
[aui.composer, aui.optional.threadListItem],
);
const callback = useCallback(() => {
const scopeGeneration = scopeGenerationRef.current;
const input = document.createElement("input");
input.type = "file";
input.multiple = multiple;
input.hidden = true;
const attachmentAccept = aui.composer.getState().attachmentAccept;
if (attachmentAccept !== "*") {
input.accept = attachmentAccept;
}
document.body.appendChild(input);
input.onchange = async (e) => {
const fileList = (e.target as HTMLInputElement).files;
if (!fileList) return;
const attachmentPromises = Array.from(fileList, async (file) => {
try {
if (scopeGenerationRef.current !== scopeGeneration) return;
await addAttachment(file);
} catch {
// The composer runtime emits composer.attachmentAddError before rejecting.
}
});
document.body.removeChild(input);
await Promise.all(attachmentPromises);
};
input.oncancel = () => {
if (!input.files || input.files.length === 0) {
document.body.removeChild(input);
}
};
input.click();
}, [aui, multiple, addAttachment]);
if (disabled) return null;
return callback;
};
export namespace ComposerPrimitiveAddAttachment {
export type Element = ActionButtonElement;
export type Props = ActionButtonProps<typeof useComposerAddAttachment>;
}
export const ComposerPrimitiveAddAttachment = createActionButton(
"ComposerPrimitive.AddAttachment",
useComposerAddAttachment,
["multiple"],
);