From cb16e269f096f33de237da8dca8636e7e1deab51 Mon Sep 17 00:00:00 2001 From: masudahiroto <96814344+masudahiroto@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:56:11 +0900 Subject: [PATCH 01/12] feat(client): make the chat panel's regions replaceable The built-in chat UI was one 1620-line component, so a host app that needed a different layout had to replace the whole panel through CustomChat and rebuild every region from scratch. Each region is now a slot - Header, EmptyState, Message, PendingIndicator, ToolApproval, Disclaimer and Composer - passed through `components` on the provider. Every slot's built-in lives in `chatSlots/` as an exported `Default*` component typed by its own slot props, so an override can wrap or re-invoke one instead of rebuilding the region. The panel drops to 515 lines and only wires props. Typing each default against its own props is what forces those props to be complete. Two gaps had to be closed to make it typecheck: `saveAsCommand` on the message slot, and `submitMode`, `attachmentProcessing`, `disclaimerVisible` and `slashCommands` on the composer. Both were read from panel scope, so replacing `Message` had silently dropped "save as slash command". A turn's raw messages and the run's ordered parts are exposed as well (`sourceMessages`, `streamingParts`, `executingTool`): merging a turn into one bubble drops its tool calls, tool results and per-step reasoning, and `streamingText` / `streamingReasoning` flatten a multi-step answer into two strings. Both strings stay, so the built-in bubble is unchanged. Also stops a turn's pendingSources from leaking into the next turn when its assistant messages carried only tool calls, and guards `streamingParts` with `displayedChatId` like its sibling streaming fields. The example app gains a slots showcase that lays a turn out as a timeline with tool cards, per-step reasoning and citation cards. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 104 ++ apps/example/src/App.tsx | 3 + .../example/src/pages/CustomSlotsDemoPage.tsx | 625 ++++++++ apps/example/src/pages/CustomUIPage.tsx | 34 + apps/example/src/pages/orbitTimeline.tsx | 353 +++++ .../src/providers/NavigationAIProvider.tsx | 1 + .../client/src/components/UseAIChat.test.tsx | 15 + packages/client/src/components/UseAIChat.tsx | 22 +- .../client/src/components/UseAIChatPanel.tsx | 1333 +++-------------- .../src/components/UseAIChatSlots.test.tsx | 225 +++ .../components/chatSlots/DefaultComposer.tsx | 269 ++++ .../chatSlots/DefaultDisclaimer.tsx | 22 + .../chatSlots/DefaultEmptyState.tsx | 83 + .../components/chatSlots/DefaultHeader.tsx | 425 ++++++ .../components/chatSlots/DefaultMessage.tsx | 241 +++ .../chatSlots/DefaultPendingIndicator.tsx | 67 + .../chatSlots/DefaultToolApproval.tsx | 31 + .../components/chatSlots/FeedbackButton.tsx | 88 ++ .../client/src/components/chatSlots/Slot.tsx | 30 + .../client/src/components/chatSlots/index.ts | 9 + .../src/components/chatSlots/messageFiles.ts | 24 + .../client/src/components/chatSlots/types.ts | 186 +++ .../useServerEvents.streamingParts.test.tsx | 98 ++ packages/client/src/hooks/useServerEvents.ts | 57 +- packages/client/src/index.ts | 28 + .../client/src/providers/useAIProvider.tsx | 15 +- .../src/utils/mergeAssistantMessages.test.ts | 45 + .../src/utils/mergeAssistantMessages.ts | 44 +- 28 files changed, 3307 insertions(+), 1170 deletions(-) create mode 100644 apps/example/src/pages/CustomSlotsDemoPage.tsx create mode 100644 apps/example/src/pages/orbitTimeline.tsx create mode 100644 packages/client/src/components/UseAIChatSlots.test.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultComposer.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultDisclaimer.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultEmptyState.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultHeader.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultMessage.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultPendingIndicator.tsx create mode 100644 packages/client/src/components/chatSlots/DefaultToolApproval.tsx create mode 100644 packages/client/src/components/chatSlots/FeedbackButton.tsx create mode 100644 packages/client/src/components/chatSlots/Slot.tsx create mode 100644 packages/client/src/components/chatSlots/index.ts create mode 100644 packages/client/src/components/chatSlots/messageFiles.ts create mode 100644 packages/client/src/components/chatSlots/types.ts create mode 100644 packages/client/src/hooks/useServerEvents.streamingParts.test.tsx diff --git a/README.md b/README.md index 15ded121..b0c4b838 100644 --- a/README.md +++ b/README.md @@ -723,6 +723,110 @@ root.render( ); ``` +For partial customization, override only the built-in chat regions you own. +Every slot receives the built-in UI as `children`: render it to decorate the +default behavior, or omit it to replace that region completely. + +```tsx +import type { + ChatMessageSlotProps, + ChatComposerSlotProps, +} from '@meetsmore-oss/use-ai-client'; + +function Message({ message, children }: ChatMessageSlotProps) { + return
{children}
; +} + +function Composer({ input, onInputChange, onSend, canSend }: ChatComposerSlotProps) { + return ( +
{ event.preventDefault(); onSend(); }}> +