Attachments and mentions
The composer reads files, checks them against the limits and turns @ picks and dragged paths into mentions. The helpers are exported for a drop target or a file list of your own.
Reading files
import { checkAttachmentLimits, readAttachments, uploadBytes } from '@adecore/agents-react/chat/attachments';
const { accepted, rejected } = checkAttachmentLimits(
held.length,
files.map((file) => ({ file, name: file.name, mime: file.type, bytes: file.size })),
held.reduce((bytes, upload) => bytes + uploadBytes(upload), 0)
);
const uploads = await readAttachments(accepted.map((entry) => entry.file));checkAttachmentLimits(count, incoming, bytes) sorts files into accepted and rejected, each rejection with a reason a person can read: empty, larger than 10 MiB, one too many, or past 10 MiB for the message. The limits are those of the contract, which the host checks again. readAttachments(files) reads them as uploads, names a pasted picture that has none, and takes the image type from the name when the browser gave none. readStoredAttachments(read, stored) turns attachments the host keeps back into uploads, which is how a queued message comes back into the draft.
isImageAttachment(mime), fileBadge(name), formatBytes(bytes), filesOf(dataTransfer), uploadBytes(upload) and uploadPreviewUrl(upload) are the small parts.
Stored files
A sent attachment is an id and a path on the host's machine. The host's attachments.useUrl(scopeId, chatId, attachmentId) answers a ResourceUrl for it, { url, failure }: a URL to draw, or why there is none. Revoke an object URL you made when the hook lets go of it.
setChatHost({
attachments: {
read: (scopeId, chatId, attachmentId) => backend.readAttachment(chatId, attachmentId),
useUrl: (scopeId, chatId, attachmentId) => useObjectUrl(() => backend.readAttachment(chatId, attachmentId), [chatId, attachmentId])
}
});backend and useObjectUrl stand for your app's own; the hook keeps a ResourceUrl in state while the bytes load.
Mentions
A path picked with @ sits in the text and in mentions; a skill picked with $ in the text and in skills. findMentionQuery, findSkillQuery, insertMention, insertSkill, presentMentions and tokenizeChips are the parsing and inserting the composer does.
A file list can hand paths to the composer by drag. The payload is the paths joined by spaces under MENTION_DRAG_TYPE (application/x-adecore-mention):
import { carriesMentions, droppedMentions, writeMentionDrag } from '@adecore/agents-react/chat/mentions';
row.addEventListener('dragstart', (event) => writeMentionDrag(event.dataTransfer!, [path]));
target.addEventListener('dragover', (event) => {
if (carriesMentions(event.dataTransfer!.types)) {
event.preventDefault();
}
});
target.addEventListener('drop', (event) => insert(droppedMentions(event.dataTransfer!)));writeMentionDrag also writes the older type LEGACY_MENTION_DRAG_TYPE unless its third argument is false, and carriesMentions and droppedMentions read both (MENTION_DRAG_TYPES). Stop writing the older type once every receiver reads the new one. The paths are split on whitespace, so a path with a space in it does not survive a drag.