diff --git a/packages/react-ai-sdk/package.json b/packages/react-ai-sdk/package.json index 4615a3936..3c3fa1f52 100644 --- a/packages/react-ai-sdk/package.json +++ b/packages/react-ai-sdk/package.json @@ -34,7 +34,7 @@ "zustand": "^5.0.2" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@types/react": "*", "react": "^18 || ^19 || ^19.0.0-rc" }, diff --git a/packages/react-hook-form/package.json b/packages/react-hook-form/package.json index 71637c367..63f64196c 100644 --- a/packages/react-hook-form/package.json +++ b/packages/react-hook-form/package.json @@ -30,7 +30,7 @@ "zod": "^3.24.1" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@types/react": "*", "react": "^18 || ^19 || ^19.0.0-rc", "react-hook-form": "^7" diff --git a/packages/react-langgraph/package.json b/packages/react-langgraph/package.json index e68620327..b9ebb0207 100644 --- a/packages/react-langgraph/package.json +++ b/packages/react-langgraph/package.json @@ -30,7 +30,7 @@ "zod": "^3.24.1" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@types/react": "*", "react": "^18 || ^19 || ^19.0.0-rc" }, diff --git a/packages/react-markdown/package.json b/packages/react-markdown/package.json index e3b0757cc..7c73064f8 100644 --- a/packages/react-markdown/package.json +++ b/packages/react-markdown/package.json @@ -41,7 +41,7 @@ "react-markdown": "^9.0.1" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@types/react": "*", "react": "^18 || ^19 || ^19.0.0-rc", "tailwindcss": "^3.4.4" diff --git a/packages/react-playground/package.json b/packages/react-playground/package.json index aab36418b..81fb0db97 100644 --- a/packages/react-playground/package.json +++ b/packages/react-playground/package.json @@ -47,7 +47,7 @@ "zustand": "^5.0.2" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@types/react": "*", "react": "^18 || ^19 || ^19.0.0-rc", "tailwindcss": "^3.4.4" diff --git a/packages/react-syntax-highlighter/package.json b/packages/react-syntax-highlighter/package.json index 9af0685b3..50d61d076 100644 --- a/packages/react-syntax-highlighter/package.json +++ b/packages/react-syntax-highlighter/package.json @@ -27,7 +27,7 @@ "build": "tsup src/index.ts --format cjs,esm --dts --sourcemap --clean" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@assistant-ui/react-markdown": "^0.7.7", "@types/react": "*", "@types/react-syntax-highlighter": "*", diff --git a/packages/react-trieve/package.json b/packages/react-trieve/package.json index 988cdd04c..9d94251ad 100644 --- a/packages/react-trieve/package.json +++ b/packages/react-trieve/package.json @@ -45,7 +45,7 @@ "unist-util-visit": "^5.0.0" }, "peerDependencies": { - "@assistant-ui/react": "^0.7.20", + "@assistant-ui/react": "^0.7.21", "@assistant-ui/react-markdown": "^0.7.7", "@types/react": "*", "react": "^18 || ^19 || ^19.0.0-rc", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 7ef39ed60..a5abad05d 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,11 @@ # @assistant-ui/react +## 0.7.21 + +### Patch Changes + +- feat: Composer.unstable_on("send", callback) + ## 0.7.20 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 5645401e8..bf114e8cf 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -29,7 +29,7 @@ "conversational-ui", "conversational-ai" ], - "version": "0.7.20", + "version": "0.7.21", "license": "MIT", "exports": { ".": { diff --git a/packages/react/src/api/ComposerRuntime.ts b/packages/react/src/api/ComposerRuntime.ts index 732c90627..4b2b7cf1a 100644 --- a/packages/react/src/api/ComposerRuntime.ts +++ b/packages/react/src/api/ComposerRuntime.ts @@ -1,6 +1,7 @@ import { Attachment, PendingAttachment } from "../types/AttachmentTypes"; import { ComposerRuntimeCore, + ComposerRuntimeEventType, ThreadComposerRuntimeCore, } from "../runtimes/core/ComposerRuntimeCore"; import { Unsubscribe } from "../types"; @@ -162,6 +163,13 @@ export abstract class ComposerRuntimeImpl implements ComposerRuntime { return this._core.subscribe(callback); } + public unstable_on(event: ComposerRuntimeEventType, callback: () => void) { + const core = this._core.getState(); + if (!core) throw new Error("Composer is not available"); + + return core.unstable_on(event, callback); + } + public getAttachmentAccept(): string { const core = this._core.getState(); if (!core) throw new Error("Composer is not available"); diff --git a/packages/react/src/runtimes/composer/BaseComposerRuntimeCore.tsx b/packages/react/src/runtimes/composer/BaseComposerRuntimeCore.tsx index 50b5ea8ff..c834a305d 100644 --- a/packages/react/src/runtimes/composer/BaseComposerRuntimeCore.tsx +++ b/packages/react/src/runtimes/composer/BaseComposerRuntimeCore.tsx @@ -5,7 +5,10 @@ import { } from "../../types/AttachmentTypes"; import { AppendMessage, Unsubscribe } from "../../types"; import { AttachmentAdapter } from "../attachment"; -import { ComposerRuntimeCore } from "../core/ComposerRuntimeCore"; +import { + ComposerRuntimeCore, + ComposerRuntimeEventType, +} from "../core/ComposerRuntimeCore"; import { MessageRole } from "../../types/AssistantTypes"; const isAttachmentComplete = (a: Attachment): a is CompleteAttachment => @@ -48,23 +51,27 @@ export abstract class BaseComposerRuntimeCore implements ComposerRuntimeCore { return this._role; } - setRole(role: MessageRole) { + public setRole(role: MessageRole) { this._role = role; this.notifySubscribers(); } - setText(value: string) { + public setText(value: string) { this._text = value; this.notifySubscribers(); } - reset() { + private _resetInternal() { this._text = ""; this._role = "user"; this._attachments = []; this.notifySubscribers(); } + public reset() { + this._resetInternal(); + } + public async send() { const adapter = this.getAttachmentAdapter(); const attachments = @@ -83,12 +90,18 @@ export abstract class BaseComposerRuntimeCore implements ComposerRuntimeCore { content: this.text ? [{ type: "text", text: this.text }] : [], attachments, }; - this.reset(); + this._resetInternal(); this.handleSend(message); + this._notifyEventSubscribers("send"); } - public abstract handleSend(message: Omit): void; - public abstract cancel(): void; + + public cancel() { + this.handleCancel(); + } + + protected abstract handleSend(message: Omit): void; + protected abstract handleCancel(): void; async addAttachment(file: File) { const adapter = this.getAttachmentAdapter(); @@ -115,6 +128,7 @@ export abstract class BaseComposerRuntimeCore implements ComposerRuntimeCore { } private _subscriptions = new Set<() => void>(); + protected notifySubscribers() { for (const callback of this._subscriptions) callback(); } @@ -123,4 +137,31 @@ export abstract class BaseComposerRuntimeCore implements ComposerRuntimeCore { this._subscriptions.add(callback); return () => this._subscriptions.delete(callback); } + + private _eventSubscribers = new Map< + ComposerRuntimeEventType, + Set<() => void> + >(); + + protected _notifyEventSubscribers(event: ComposerRuntimeEventType) { + const subscribers = this._eventSubscribers.get(event); + if (!subscribers) return; + + for (const callback of subscribers) callback(); + } + + public unstable_on(event: ComposerRuntimeEventType, callback: () => void) { + const subscribers = this._eventSubscribers.get(event); + if (!subscribers) { + this._eventSubscribers.set(event, new Set([callback])); + } else { + subscribers.add(callback); + } + + return () => { + const subscribers = this._eventSubscribers.get(event); + if (!subscribers) return; + subscribers.delete(callback); + }; + } } diff --git a/packages/react/src/runtimes/composer/DefaultEditComposerRuntimeCore.tsx b/packages/react/src/runtimes/composer/DefaultEditComposerRuntimeCore.tsx index 2ecbe06f6..b824dfb2b 100644 --- a/packages/react/src/runtimes/composer/DefaultEditComposerRuntimeCore.tsx +++ b/packages/react/src/runtimes/composer/DefaultEditComposerRuntimeCore.tsx @@ -50,7 +50,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore { this.notifySubscribers(); } - public async cancel() { + public handleCancel() { this.endEditCallback(); this.notifySubscribers(); } diff --git a/packages/react/src/runtimes/composer/DefaultThreadComposerRuntimeCore.tsx b/packages/react/src/runtimes/composer/DefaultThreadComposerRuntimeCore.tsx index ef67664f1..9370e84a0 100644 --- a/packages/react/src/runtimes/composer/DefaultThreadComposerRuntimeCore.tsx +++ b/packages/react/src/runtimes/composer/DefaultThreadComposerRuntimeCore.tsx @@ -46,7 +46,7 @@ export class DefaultThreadComposerRuntimeCore }); } - public async cancel() { + public async handleCancel() { this.runtime.cancelRun(); } } diff --git a/packages/react/src/runtimes/core/BaseThreadRuntimeCore.tsx b/packages/react/src/runtimes/core/BaseThreadRuntimeCore.tsx index 995ba7a63..a6a741a30 100644 --- a/packages/react/src/runtimes/core/BaseThreadRuntimeCore.tsx +++ b/packages/react/src/runtimes/core/BaseThreadRuntimeCore.tsx @@ -180,7 +180,10 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore { this._notifySubscribers(); } - private _eventSubscribers = new Map void>>(); + private _eventSubscribers = new Map< + ThreadRuntimeEventType, + Set<() => void> + >(); public unstable_on(event: ThreadRuntimeEventType, callback: () => void) { if (event === "model-config-update") { diff --git a/packages/react/src/runtimes/core/ComposerRuntimeCore.tsx b/packages/react/src/runtimes/core/ComposerRuntimeCore.tsx index b7bb81653..d660ed4e1 100644 --- a/packages/react/src/runtimes/core/ComposerRuntimeCore.tsx +++ b/packages/react/src/runtimes/core/ComposerRuntimeCore.tsx @@ -1,6 +1,8 @@ import type { Attachment, PendingAttachment, Unsubscribe } from "../../types"; import { MessageRole } from "../../types/AssistantTypes"; +export type ComposerRuntimeEventType = "send"; + export type ComposerRuntimeCore = Readonly<{ attachments: readonly Attachment[]; @@ -25,6 +27,11 @@ export type ComposerRuntimeCore = Readonly<{ cancel: () => void; subscribe: (callback: () => void) => Unsubscribe; + + unstable_on: ( + event: ComposerRuntimeEventType, + callback: () => void, + ) => Unsubscribe; }>; export type ThreadComposerRuntimeCore = ComposerRuntimeCore &