- Notifications
You must be signed in to change notification settings - Fork83
Description
Hi team,
First off, I really appreciate the amazing work you’re doing with Chatkit — it’s been great to build on top of it.
I currently have a branch ready across chatkit, chatkit-python, and chatkit-react that enables support for custom components. The core idea is to introduce a new widget called CustomWidget, which accepts the necessary props and JSON configuration. On the frontend, these would then be hydrated with user-defined custom components.
In theory, everything looks good with my current changes. I’ve also implemented a hook to list available custom components and automatically hydrate them in chat. However, since the Chatkit CDN code is minified, I don’t have a straightforward way to fully test this end-to-end.
Would it still be useful for me to open these PRs so the team can review the approach and direction? Or is this not aligned with OpenAI’s current vision for widgets?
I genuinely believe that enabling custom components could significantly improve flexibility, enhance customization, and drive greater adoption of Chatkit.
Thanks for your time and the great work you’re doing!
==============================
Proposed Implementation
I have a working implementation across all three repos that I'm happy to contribute.
1. Core Widget Type (@openai/chatkit)
// packages/chatkit/types/widgets.d.tsexporttypeWidgetComponent=|TextComponent|Title|Caption// ... existing types|CustomComponent;// New typeexporttypeCustomComponent={type:'Custom';key?:string;id?:string;componentType:string;// Identifier for the React componentprops?:Record<string,unknown>;// Props to pass to the component}&BlockProps;
2. Widget Options Extension
// packages/chatkit/types/index.d.tsexporttypeWidgetsOption={onAction?:(...)=>Promise<void>;// New callback for custom component renderingonRenderCustom?:(component:Widgets.CustomComponent,container:HTMLElement,)=>void|Promise<void>;};
3. React Hook (@openai/chatkit-react)
// packages/chatkit-react/src/useCustomWidgets.tsxexporttypeCustomComponentRegistry=Record<string,React.ComponentType<any>>;exportfunctionuseCustomWidgets(components:CustomComponentRegistry){constrootsRef=React.useRef<Map<HTMLElement,ReactDOM.Root>>(newMap());constonRenderCustom=React.useCallback((component:Widgets.CustomComponent,container:HTMLElement)=>{constComponent=components[component.componentType];if(!Component){console.warn(`Custom component "${component.componentType}" not found in registry.`);return;}// Clean up existing root if it existsconstexistingRoot=rootsRef.current.get(container);if(existingRoot){existingRoot.unmount();}// Create new root and renderconstroot=ReactDOM.createRoot(container);rootsRef.current.set(container,root);root.render(<React.StrictMode><Component{...(component.props||{})}/></React.StrictMode>);},[components]);// Cleanup on unmountReact.useEffect(()=>{constroots=rootsRef.current;return()=>{for(constrootofroots.values()){root.unmount();}roots.clear();};},[]);return{widgets:{ onRenderCustom}};}
Usage Example
Frontend
import{useChatKit,useCustomWidgets,ChatKit}from'@openai/chatkit-react';// Define custom componentfunctionProductCard({ name, price, imageUrl}:ProductCardProps){return(<divclassName="product-card"><imgsrc={imageUrl}alt={name}/><h3>{name}</h3><p>${price.toFixed(2)}</p><button>Add to Cart</button></div>);}functionApp(){// Register custom componentsconstcustomWidgets=useCustomWidgets({'product-card':ProductCard,'user-profile':UserProfile,'chart':ChartWidget,});constchatKit=useChatKit({api:{url:'/api/chatkit',domainKey:'key'},widgets:customWidgets.widgets,});return<ChatKitcontrol={chatKit.control}/>;}
Backend (Python SDK)
fromchatkitimportCardfromchatkit.widgetsimportCustomComponentwidget=Card(children=[CustomComponent(component_type="product-card",props={"name":"Wireless Headphones","price":149.99,"imageUrl":"https://example.com/image.jpg" } ) ])
Backend (JSON)
{"type":"Card","children": [ {"type":"Custom","componentType":"product-card","props": {"name":"Wireless Headphones","price":149.99,"imageUrl":"https://example.com/image.jpg" } } ]}