({
children,
storage,
llm,
artifactRenderers,
artifactCategories,
})
| 17 | const EMPTY_CATEGORIES: never[] = []; |
| 18 | |
| 19 | export const ChatProvider: FC<ChatProviderProps> = ({ |
| 20 | children, |
| 21 | storage, |
| 22 | llm, |
| 23 | artifactRenderers, |
| 24 | artifactCategories, |
| 25 | }) => { |
| 26 | const [resolvedStorage] = useState(() => storage ?? createDefaultInMemoryStorage()); |
| 27 | const [chatStore] = useState(() => createChatStore({ storage: resolvedStorage, llm })); |
| 28 | const [detailedViewStore] = useState(() => createDetailedViewStore()); |
| 29 | const [threadContextStore] = useState(() => createThreadContextStore()); |
| 30 | const [artifactRendererRegistry] = useState(() => |
| 31 | buildArtifactRendererRegistry(artifactRenderers ?? []), |
| 32 | ); |
| 33 | |
| 34 | // Dev-mode warning if artifactRenderers reference changes after mount — |
| 35 | // captured registry is mount-only, so changes are silently ignored otherwise. |
| 36 | const initialRenderersRef = useRef(artifactRenderers); |
| 37 | const hasWarnedRef = useRef(false); |
| 38 | useEffect(() => { |
| 39 | if ( |
| 40 | typeof process !== "undefined" && |
| 41 | process.env?.["NODE_ENV"] !== "production" && |
| 42 | !hasWarnedRef.current && |
| 43 | initialRenderersRef.current !== artifactRenderers |
| 44 | ) { |
| 45 | console.warn( |
| 46 | "[OpenUI] `artifactRenderers` prop changed after ChatProvider mount. " + |
| 47 | "The original array is kept; new renderers will not be registered. " + |
| 48 | "Memoize the array (useMemo) to avoid this warning.", |
| 49 | ); |
| 50 | hasWarnedRef.current = true; |
| 51 | } |
| 52 | }, [artifactRenderers]); |
| 53 | |
| 54 | // Cross-store subscription: reset detailed-view + thread-context state when the active thread changes. |
| 55 | // useEffect (not inline) so the cleanup function unsubscribes on unmount. |
| 56 | useEffect(() => { |
| 57 | const unsubscribe = chatStore.subscribe( |
| 58 | (state) => state.selectedThreadId, |
| 59 | () => { |
| 60 | detailedViewStore.getState().reset(); |
| 61 | threadContextStore.getState().reset(); |
| 62 | }, |
| 63 | ); |
| 64 | return unsubscribe; |
| 65 | }, [chatStore, detailedViewStore, threadContextStore]); |
| 66 | |
| 67 | return ( |
| 68 | <ChatContext.Provider value={chatStore}> |
| 69 | <DetailedViewContext.Provider value={detailedViewStore}> |
| 70 | <ThreadContextContext.Provider value={threadContextStore}> |
| 71 | <ArtifactRenderersContext.Provider value={artifactRendererRegistry}> |
| 72 | <ArtifactStorageContext.Provider value={resolvedStorage.artifact ?? null}> |
| 73 | <ArtifactCategoriesContext.Provider value={artifactCategories ?? EMPTY_CATEGORIES}> |
| 74 | {children} |
| 75 | </ArtifactCategoriesContext.Provider> |
| 76 | </ArtifactStorageContext.Provider> |
nothing calls this directly
no test coverage detected