Integration Examples
Applies to: ChengOS v0.1.0+ | Last verified: 2026-08-12 | Source:
chengflow-sdk/example/App.tsx,chengflow-sdk/src/components/index.ts,chengflow-sdk/src/components/ChatProvider.tsx,chengflow-sdk/src/hooks
Four integration shapes, from "one component" to "no framework at all". Pick the one that matches how much of the UI you want to own.
1. The whole console, one component
The shipped example is nine lines of real code:
import { MultiChannelLayout } from "@chengflow/chat";
const config = {
apiBaseUrl: import.meta.env.VITE_API_BASE_URL || "/api/v1",
wsBaseUrl: import.meta.env.VITE_WS_BASE_URL || "/ws/executions",
channelId: import.meta.env.VITE_CHANNEL_ID || "weather-app",
boundWorkflowId: import.meta.env.VITE_BOUND_WORKFLOW_ID || "",
};
export default function App() {
return <MultiChannelLayout config={config} loginTitle="Chengflow"
chatWindowProps={{ height: "100vh" }} />;
}
MultiChannelLayout is the complete browser gateway: login, workspace selection, channel
management, and chat — the flow described in
Gateway Model. Use it when you want the product, not a component
library.
The example also reads a runtime config object off window, falling back to build-time env
vars. That pattern is worth copying: it lets one built bundle be deployed against different
backends by writing a small config script at serve time, instead of rebuilding per environment.
2. Just the chat window
import { ChatProvider, ChatWindow } from "@chengflow/chat";
<ChatProvider config={{
apiBaseUrl: "https://api.example.com/api/v1",
wsBaseUrl: "wss://api.example.com/ws/executions",
workspaceId: "…",
channelId: "weather-app",
boundWorkflowId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
}}>
<ChatWindow />
</ChatProvider>
ChatProvider runs useChannel(config) and injects the result into context; ChatWindow
consumes it. Any component under the provider can call useChatContext().
3. Your own UI over the hook
When the chat needs to look like your product:
import { useChannel } from "@chengflow/chat";
function MyChat({ config }) {
const {
messages, sendMessage, isLoading, connectionStatus,
streamingContent, resetConversation, error, supportsAttachments,
} = useChannel(config);
return (
<div>
<StatusDot status={connectionStatus} />
{messages.map(m => <MyBubble key={m.id} message={m} />)}
{streamingContent && <MyBubble message={{ content: streamingContent }} streaming />}
<MyInput onSend={sendMessage} disabled={isLoading}
allowFiles={supportsAttachments} />
</div>
);
}
useChannel also returns submitApproval, continueAgentReview, and
submitContextMemoryReview. If your workflow contains an
approval gate, you must render something for it — otherwise the
execution sits in waiting_for_review with no way for the user to answer.
supportsAttachments is derived by asking whether the bound workflow contains an
io/file_upload node, so the file button appears only where uploads actually go somewhere.
4. No framework
The core classes are plain TypeScript:
import { ChannelClient, SessionManager, WsClient } from "@chengflow/chat/sdk";
const sessions = new SessionManager("weather-app");
const client = new ChannelClient(config);
const ws = new WsClient({ url: config.wsBaseUrl, tokenProvider: getToken });
ws.connect();
const sessionId = sessions.getOrCreateSessionId();
const { conversation_id, execution_id } =
await client.execute("weather-app", workflowId, "What is the weather?", sessionId);
sessions.setConversationId(conversation_id);
ws.send({ type: "SUBSCRIBE", scope: { type: "conversation", conversationId: conversation_id } });
if (execution_id) {
ws.send({ type: "SUBSCRIBE", scope: { type: "execution", executionId: execution_id } });
}
Note the if (execution_id): it can be null when the message did not trigger a workflow.
Pairing external platforms
To let users connect their own Telegram bot or Slack app from your UI, the SDK ships a pairing
form and an edit panel per platform — Telegram, WhatsApp, Slack, WeCom, DingTalk — plus
AppLinkPlatformModals to present them together.
The generic path is better than picking a form by hand:
const pattern = await management.getChannelAuthPattern(workspaceId, channelId);
// render fields from `pattern`
const result = await management.connectChannel(workspaceId, channelId, values);
if (result.state === "configuring") {
// OAuth redirect or QR in setup_data → then completeConnect(...)
}
getChannelAuthPattern returns the fields the platform requires, so one generic form covers
every platform and a new adapter needs no frontend change. See
Messaging Channels for what each pattern means.
Building a management console
import { ManagementClient, ChannelList, CreateChannelModal, AppShell } from "@chengflow/chat";
listChannels, createChannel, listWorkspaces, and listPublishedWorkflows cover a channel
admin screen. AppShell and ChannelList give you the layout if you do not want to build it.
Auth
LoginPage and ResetPasswordPage are drop-in. The example routes /reset-password by reading
the token query parameter:
if (window.location.pathname === "/reset-password") {
const token = new URLSearchParams(window.location.search).get("token");
return <ResetPasswordPage apiBaseUrl={config.apiBaseUrl} token={token} />;
}
If your application already handles auth, implement AuthTokenProvider and pass it to
ChannelClient instead of using BrowserAuthSession.
i18n
I18nProvider, useI18n, and setLocale are exported from the package root — not from
components — so import them from @chengflow/chat directly.
Choosing
| You want | Use |
|---|---|
| A working product now | MultiChannelLayout |
| Chat inside your app's shell | ChatProvider + ChatWindow |
| Your own chat UI | useChannel |
| Non-React, or a server | ChannelClient + WsClient + SessionManager |
Next steps
- SDK API Reference — every class and method.
- SDK Quickstart — from zero to a first message.
- Channel Routing & Session Mapping — what the session id means.