Files
agent/apps/web/src/assistant/attachment-adapter.ts
T

66 lines
2.1 KiB
TypeScript

import type { Attachment, AttachmentAdapter, CompleteAttachment, PendingAttachment } from "@assistant-ui/react";
type AttachmentApi = {
uploadAttachment(file: File): Promise<{ attachment: { attachmentId: string } }>;
deleteAttachment(attachmentId: string): Promise<unknown>;
};
export class HarnessAttachmentAdapter implements AttachmentAdapter {
public readonly accept = "image/jpeg,image/png,image/webp";
readonly #previews = new Map<string, string>();
public constructor(private readonly api: AttachmentApi) {}
public async add({ file }: { file: File }): Promise<PendingAttachment> {
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<void> {
if (attachment.id.startsWith("att_")) await this.api.deleteAttachment(attachment.id);
this.#revoke(attachment.id);
}
public send(attachment: PendingAttachment): Promise<CompleteAttachment> {
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);
}
}