import type { Attachment, AttachmentAdapter, CompleteAttachment, PendingAttachment } from "@assistant-ui/react"; type AttachmentApi = { uploadAttachment(file: File): Promise<{ attachment: { attachmentId: string } }>; deleteAttachment(attachmentId: string): Promise; }; export class HarnessAttachmentAdapter implements AttachmentAdapter { public readonly accept = "image/jpeg,image/png,image/webp"; readonly #previews = new Map(); public constructor(private readonly api: AttachmentApi) {} public async add({ file }: { file: File }): Promise { if (!this.accept.split(",").includes(file.type)) throw new Error("仅支持 JPEG、PNG 和 WebP 图片"); const preview = URL.createObjectURL(file); try { const response = await this.api.uploadAttachment(file); this.#previews.set(response.attachment.attachmentId, preview); return { id: response.attachment.attachmentId, type: "image", name: file.name, contentType: file.type, file, status: { type: "requires-action", reason: "composer-send" }, }; } catch (error) { URL.revokeObjectURL(preview); throw error; } } public async remove(attachment: Attachment): Promise { if (attachment.id.startsWith("att_")) await this.api.deleteAttachment(attachment.id); this.#revoke(attachment.id); } public send(attachment: PendingAttachment): Promise { this.#revoke(attachment.id); return Promise.resolve({ id: attachment.id, type: "image", name: attachment.name, contentType: attachment.contentType, status: { type: "complete" }, content: [], }); } public previewUrl(attachmentId: string): string | undefined { return this.#previews.get(attachmentId); } public dispose(): void { for (const url of this.#previews.values()) URL.revokeObjectURL(url); this.#previews.clear(); } #revoke(attachmentId: string): void { const preview = this.#previews.get(attachmentId); if (preview !== undefined) URL.revokeObjectURL(preview); this.#previews.delete(attachmentId); } }