A Chatbot You Can Embed Anywhere
We built a widget that embeds XGen AI workflows as a chatbot on external websites. Drop in a single script tag and any site gets an AI chatbot.
Embed Widget Architecture
The widget is built as a self-contained bundle and loaded via a <script> tag. Shadow DOM isolates it from the host site's CSS so there are no style conflicts.
<!-- 외부 사이트에 삽입하는 코드 -->
<script
src="https://widget.xgen.plateer.com/embed.js"
data-workflow-id="wf_abc123"
data-theme="light"
data-position="bottom-right"
async
></script>
Shadow DOM Isolation
class XGenChatWidget extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'closed' });
// 스타일 주입
const style = document.createElement('style');
style.textContent = widgetStyles;
shadow.appendChild(style);
// React 앱 마운트
const container = document.createElement('div');
shadow.appendChild(container);
const root = createRoot(container);
root.render(
<ChatWidget
workflowId={this.getAttribute('data-workflow-id')!}
theme={this.getAttribute('data-theme') as 'light' | 'dark'}
/>
);
}
}
customElements.define('xgen-chat', XGenChatWidget);
Widget UI Layout
The widget starts as a floating button that can be expanded or collapsed. Clicking it opens the chat window with an animation.
const ChatWidget: React.FC<WidgetProps> = ({ workflowId, theme }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="fixed bottom-4 right-4 z-50">
{isOpen && (
<div className="mb-4 w-96 h-[500px] rounded-2xl shadow-2xl overflow-hidden">
<ChatHeader onClose={() => setIsOpen(false)} />
<ChatMessages workflowId={workflowId} />
<ChatInput />
</div>
)}
<FloatingButton
onClick={() => setIsOpen(!isOpen)}
isOpen={isOpen}
/>
</div>
);
};
Customization Options
The widget supports a range of customization: color theme, position, initial message, avatar image, and more.
Bundle Optimization
The embed script needs to be as small as possible. We swapped React for Preact and applied tree shaking to bring the final bundle down to roughly 45 KB (gzip). To avoid impacting host-site performance, loading is deferred with async + requestIdleCallback.
CORS and Security
Because the widget calls the XGen API from external sites, CORS configuration matters. Workflow owners specify an allowlist of domains, and API calls are restricted to those domains only.