From c845fcf7fd9148664afb6ecbca2a2c2cc517e3d4 Mon Sep 17 00:00:00 2001 From: Simon Farshid Date: Sun, 8 Sep 2024 18:16:45 -0700 Subject: [PATCH] feat: allow sending attachment-only messages (#784) --- .changeset/pink-crews-glow.md | 5 +++++ .../react/src/context/providers/ThreadProvider.tsx | 2 ++ packages/react/src/context/stores/Composer.ts | 2 ++ packages/react/src/context/stores/EditComposer.ts | 13 ++++++++++--- .../primitive-hooks/composer/useComposerSend.tsx | 2 +- packages/react/src/runtimes/core/ThreadRuntime.tsx | 2 ++ .../src/runtimes/utils/ThreadRuntimeComposer.tsx | 4 ++++ 7 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .changeset/pink-crews-glow.md diff --git a/.changeset/pink-crews-glow.md b/.changeset/pink-crews-glow.md new file mode 100644 index 000000000..6652cb0d8 --- /dev/null +++ b/.changeset/pink-crews-glow.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react": patch +--- + +feat: allow sending attachment-only messages diff --git a/packages/react/src/context/providers/ThreadProvider.tsx b/packages/react/src/context/providers/ThreadProvider.tsx index b2fcb0dd2..2a5d54eed 100644 --- a/packages/react/src/context/providers/ThreadProvider.tsx +++ b/packages/react/src/context/providers/ThreadProvider.tsx @@ -65,11 +65,13 @@ export const ThreadProvider: FC> = ({ const composerState = context.useComposer.getState(); if ( + thread.composer.isEmpty !== composerState.isEmpty || thread.composer.text !== composerState.text || thread.composer.attachments !== composerState.attachments || state.capabilities.cancel !== composerState.canCancel ) { writableStore(context.useComposer).setState({ + isEmpty: thread.composer.isEmpty, text: thread.composer.text, attachments: thread.composer.attachments, canCancel: state.capabilities.cancel, diff --git a/packages/react/src/context/stores/Composer.ts b/packages/react/src/context/stores/Composer.ts index d411d891f..def3a88d6 100644 --- a/packages/react/src/context/stores/Composer.ts +++ b/packages/react/src/context/stores/Composer.ts @@ -21,6 +21,7 @@ export type ComposerState = Readonly<{ canCancel: boolean; isEditing: true; + isEmpty: boolean; send: () => void; cancel: () => void; @@ -60,6 +61,7 @@ export const makeComposerStore = ( canCancel: runtime.capabilities.cancel, isEditing: true, + isEmpty: runtime.composer.isEmpty, send: () => { const runtime = useThreadRuntime.getState(); diff --git a/packages/react/src/context/stores/EditComposer.ts b/packages/react/src/context/stores/EditComposer.ts index 1444365b3..ae3a88338 100644 --- a/packages/react/src/context/stores/EditComposer.ts +++ b/packages/react/src/context/stores/EditComposer.ts @@ -2,7 +2,7 @@ import { create } from "zustand"; import { ReadonlyStore } from "../ReadonlyStore"; export type EditComposerState = Readonly<{ - // TODO + // TODO /** @deprecated Use `text` instead. */ value: string; /** @deprecated Use `setText` instead. */ @@ -13,6 +13,7 @@ export type EditComposerState = Readonly<{ canCancel: boolean; isEditing: boolean; + isEmpty: boolean; edit: () => void; send: () => void; @@ -36,15 +37,21 @@ export const makeEditComposerStore = ({ text: "", setText: (text) => { - set({ text }); + set({ text, isEmpty: text.trim().length === 0 }); }, canCancel: false, isEditing: false, + isEmpty: true, edit: () => { const text = onEdit(); - set({ isEditing: true, canCancel: true, text }); + set({ + isEditing: true, + canCancel: true, + isEmpty: text.trim().length === 0, + text, + }); }, send: () => { const text = get().text; diff --git a/packages/react/src/primitive-hooks/composer/useComposerSend.tsx b/packages/react/src/primitive-hooks/composer/useComposerSend.tsx index d3822ceb0..8d7730685 100644 --- a/packages/react/src/primitive-hooks/composer/useComposerSend.tsx +++ b/packages/react/src/primitive-hooks/composer/useComposerSend.tsx @@ -12,7 +12,7 @@ export const useComposerSend = () => { const disabled = useCombinedStore( [useThread, useComposer], - (t, c) => t.isRunning || !c.isEditing || c.text.length === 0, + (t, c) => t.isRunning || !c.isEditing || c.isEmpty, ); const callback = useCallback(() => { diff --git a/packages/react/src/runtimes/core/ThreadRuntime.tsx b/packages/react/src/runtimes/core/ThreadRuntime.tsx index c214d1266..0c4322c46 100644 --- a/packages/react/src/runtimes/core/ThreadRuntime.tsx +++ b/packages/react/src/runtimes/core/ThreadRuntime.tsx @@ -21,6 +21,8 @@ export declare namespace ThreadRuntime { addAttachment: (file: File) => Promise; removeAttachment: (attachmentId: string) => Promise; + isEmpty: boolean; + text: string; setText: (value: string) => void; diff --git a/packages/react/src/runtimes/utils/ThreadRuntimeComposer.tsx b/packages/react/src/runtimes/utils/ThreadRuntimeComposer.tsx index 0ee2ec80d..ef65f676c 100644 --- a/packages/react/src/runtimes/utils/ThreadRuntimeComposer.tsx +++ b/packages/react/src/runtimes/utils/ThreadRuntimeComposer.tsx @@ -8,6 +8,10 @@ export class ThreadRuntimeComposer implements ThreadRuntime.Composer { public attachmentAccept: string = "*"; + public get isEmpty() { + return !this.text.trim() && !this.attachments.length; + } + constructor( private runtime: { messages: ThreadRuntime["messages"];