Skip to content

Commit

Permalink
feat: add AttachmentAdapter for AI SDK (#775)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yonom authored Sep 8, 2024
1 parent 21716fd commit 7dcab47
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-cows-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@assistant-ui/react-ai-sdk": patch
---

feat: add AttachmentAdapter for AI SDK
7 changes: 7 additions & 0 deletions .changeset/mean-poets-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@assistant-ui/react-playground": patch
"@assistant-ui/react-ai-sdk": patch
"@assistant-ui/react": patch
---

fix: message copy handling for runtimes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useCachedChunkedMessages } from "../utils/useCachedChunkedMessages";
import { convertMessage } from "../utils/convertMessage";
import { useInputSync } from "../utils/useInputSync";
import { toCreateMessage } from "../utils/toCreateMessage";
import { vercelAttachmentAdapter } from "../utils/vercelAttachmentAdapter";

export const useVercelUseAssistantRuntime = (
assistantHelpers: ReturnType<typeof useAssistant>,
Expand All @@ -23,6 +24,9 @@ export const useVercelUseAssistantRuntime = (
assistantHelpers.setInput("");
},
convertMessage,
adapters: {
attachments: vercelAttachmentAdapter,
},
});

useInputSync(assistantHelpers, runtime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useExternalStoreRuntime } from "@assistant-ui/react";
import { useInputSync } from "../utils/useInputSync";
import { sliceMessagesUntil } from "../utils/sliceMessagesUntil";
import { toCreateMessage } from "../utils/toCreateMessage";
import { vercelAttachmentAdapter } from "../utils/vercelAttachmentAdapter";

export const useVercelUseChatRuntime = (
chatHelpers: ReturnType<typeof useChat>,
Expand Down Expand Up @@ -44,6 +45,9 @@ export const useVercelUseChatRuntime = (
chatHelpers.setInput("");
},
convertMessage,
adapters: {
attachments: vercelAttachmentAdapter,
},
});

useInputSync(chatHelpers, runtime);
Expand Down
20 changes: 20 additions & 0 deletions packages/react-ai-sdk/src/ui/utils/vercelAttachmentAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AttachmentAdapter } from "@assistant-ui/react";
import { generateId } from "ai";

export const vercelAttachmentAdapter: AttachmentAdapter = {
async add({ file }) {
return {
id: generateId(),
type: "file",
name: file.name,
file,
};
},
async send() {
// noop
return { content: [] };
},
async remove() {
// noop
},
};
2 changes: 1 addition & 1 deletion packages/react-playground/src/lib/playground-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const CAPABILITIES = Object.freeze({
edit: false,
reload: false,
cancel: true,
unstable_copy: false,
unstable_copy: true,
speak: false,
attachments: false,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AddToolResultOptions } from "../../context";
import { AppendMessage, ThreadMessage } from "../../types";
import { AttachmentAdapter } from "../attachment";
import { SpeechSynthesisAdapter } from "../speech/SpeechAdapterTypes";
import { ThreadMessageLike } from "./ThreadMessageLike";

Expand Down Expand Up @@ -33,6 +34,9 @@ type ExternalStoreAdapterBase<T> = {
| ((message: ThreadMessage) => SpeechSynthesisAdapter.Utterance)
| undefined;
convertMessage?: ExternalStoreMessageConverter<T> | undefined;
adapters?: {
attachments?: AttachmentAdapter | undefined;
};
unstable_capabilities?:
| {
copy?: boolean | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ export class ExternalStoreThreadRuntime implements ReactThreadRuntime {
edit: this._store.onEdit !== undefined,
reload: this._store.onReload !== undefined,
cancel: this._store.onCancel !== undefined,
unstable_copy: this._store.unstable_capabilities?.copy !== null,
speak: this._store.onSpeak !== undefined,
attachments: false,
unstable_copy: this._store.unstable_capabilities?.copy !== false, // default true
attachments: !!this.store.adapters?.attachments,
};

this.composer.attachmentAdapter = this._store.adapters?.attachments;

if (oldStore) {
// flush the converter cache when the convertMessage prop changes
if (oldStore.convertMessage !== store.convertMessage) {
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/runtimes/local/LocalThreadRuntime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ export class LocalThreadRuntime implements ThreadRuntime {
hasUpdates = true;
}

this.composer.adapter = options.adapters?.attachments;
const canAttach = this.composer.adapter !== undefined;
this.composer.attachmentAdapter = options.adapters?.attachments;
const canAttach = this.composer.attachmentAdapter !== undefined;
if (this.capabilities.attachments !== canAttach) {
this.capabilities.attachments = canAttach;
hasUpdates = true;
Expand Down
16 changes: 9 additions & 7 deletions packages/react/src/runtimes/utils/ThreadRuntimeComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AttachmentAdapter } from "../attachment/AttachmentAdapter";
import { ThreadRuntime } from "../core";

export class ThreadRuntimeComposer implements ThreadRuntime.Composer {
public adapter?: AttachmentAdapter | undefined;
public attachmentAdapter?: AttachmentAdapter | undefined;

constructor(
private runtime: {
Expand All @@ -21,22 +21,24 @@ export class ThreadRuntimeComposer implements ThreadRuntime.Composer {
}

async addAttachment(file: File) {
if (!this.adapter) throw new Error("Attachments are not supported");
if (!this.attachmentAdapter)
throw new Error("Attachments are not supported");

const attachment = await this.adapter.add({ file });
const attachment = await this.attachmentAdapter.add({ file });

this._attachments = [...this._attachments, attachment];
this.notifySubscribers();
}

async removeAttachment(attachmentId: string) {
if (!this.adapter) throw new Error("Attachments are not supported");
if (!this.attachmentAdapter)
throw new Error("Attachments are not supported");

const index = this._attachments.findIndex((a) => a.id === attachmentId);
if (index === -1) throw new Error("Attachment not found");
const attachment = this._attachments[index]!;

await this.adapter.remove(attachment);
await this.attachmentAdapter.remove(attachment);

this._attachments = this._attachments.toSpliced(index, 1);
this.notifySubscribers();
Expand All @@ -60,10 +62,10 @@ export class ThreadRuntimeComposer implements ThreadRuntime.Composer {
}

public async send() {
const attachmentContentParts = this.adapter
const attachmentContentParts = this.attachmentAdapter
? await Promise.all(
this.attachments.map(async (a) => {
const { content } = await this.adapter!.send(a);
const { content } = await this.attachmentAdapter!.send(a);
return content;
}),
)
Expand Down

0 comments on commit 7dcab47

Please sign in to comment.