Skip to main content

Create a custom visualizer

In this tutorial, you will build a custom visualizer that renders content inside DIAL Chat using the ChatVisualizerConnector library. By the end, you will have a React application that receives data from DIAL Chat and displays it in an iframe.

Prerequisites​

  • Node.js 24+ and npm
  • A running DIAL Chat instance
  • Familiarity with React and TypeScript

Goal​

You will create a visualizer that:

  1. Connects to DIAL Chat via the ChatVisualizerConnector library
  2. Receives custom content from a model or application response
  3. Renders that content inside the Chat UI

Step 1: Configure DIAL Chat​

Set the following environment variables in your DIAL Chat deployment. See DIAL Chat configuration for the full reference.

ALLOWED_IFRAME_SOURCES — a space-separated list of allowed iframe origins. For development, you can use *. For production, specify the exact origin where your visualizer is hosted.

ALLOWED_IFRAME_SOURCES=http://localhost:8000

CUSTOM_VISUALIZERS — a JSON array defining each visualizer:

CUSTOM_VISUALIZERS=[
{
"title": "CUSTOM_VISUALIZER",
"description": "Custom visualizer to render images",
"icon": "data:image/svg+xml;base64,<BASE64_ENCODED_SVG>",
"contentType": "image/png,image/jpg",
"url": "http://localhost:8000"
}
]
FieldDescription
titleUnique identifier for the visualizer. Must match appName in your code.
descriptionShort description shown in the Chat UI.
iconBase64-encoded SVG icon.
contentTypeComma-separated MIME types this visualizer handles.
urlURL where the visualizer is hosted.

ALLOW_VISUALIZER_SEND_MESSAGES — optional flag. Set to true to allow the visualizer to send messages on behalf of the user (enables custom buttons and input fields in the iframe).

Step 2: Scaffold the project​

Create a new React project and install the connector library:

mkdir my-visualizer && cd my-visualizer
npm init -y
npm i react react-dom @epam/ai-dial-chat-visualizer-connector
npm i -D typescript @types/react @types/react-dom

Project structure​

After completing all steps, your project will look like this:

my-visualizer/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.tsx
│ └── Module.tsx
└── public/
└── index.html

Step 3: Understand the data contract​

The model or application must send data as JSON with a layout object containing width and height properties. These interfaces are exported by the connector library:

interface CustomVisualizerDataLayout {
width: number;
height: number;
}

interface CustomVisualizerData {
layout: CustomVisualizerDataLayout;
}

The AttachmentData interface wraps the payload received from DIAL Chat:

interface AttachmentData {
mimeType: string;
visualizerData: CustomVisualizerData;
}

Step 4: Create my-visualizer/src/Module.tsx​

This is the main visualizer component. It connects to DIAL Chat, receives data, and renders it.

import { FC, useEffect, useMemo, useRef, useState } from 'react';
import {
AttachmentData,
ChatVisualizerConnector,
CustomVisualizerDataLayout,
} from '@epam/ai-dial-chat-visualizer-connector';

interface YourVisualizerLayout extends CustomVisualizerDataLayout {
// Add any extra layout properties your visualizer needs
}

export const Module: FC = () => {
const [data, setData] = useState<AttachmentData>();
const chatVisualizerConnector = useRef<ChatVisualizerConnector | null>(null);

const dialHost = '<YOUR_DIAL_CHAT_HOST>';
const appName = 'CUSTOM_VISUALIZER';

useEffect(() => {
if (!chatVisualizerConnector.current && dialHost && appName) {
chatVisualizerConnector.current = new ChatVisualizerConnector(
dialHost,
appName,
setData,
);

return () => {
chatVisualizerConnector.current?.destroy();
chatVisualizerConnector.current = null;
};
}
}, [appName, dialHost]);

useEffect(() => {
if (appName && dialHost) {
chatVisualizerConnector.current?.sendReady();
chatVisualizerConnector.current?.sendReadyToInteract();
}
}, [dialHost, appName]);

const typedVisualizerData = useMemo(() => {
return (
data?.visualizerData &&
(data.visualizerData as unknown as {
dataToRender: string;
layout: YourVisualizerLayout;
})
);
}, [data?.visualizerData]);

return (
<div>
{!!typedVisualizerData?.dataToRender && (
<div>{typedVisualizerData.dataToRender}</div>
)}
</div>
);
};

Key points:

  • dialHost — the URL of your DIAL Chat instance. Replace <YOUR_DIAL_CHAT_HOST> with the actual host.
  • appName — must match the title value in the CUSTOM_VISUALIZERS environment variable.
  • sendReady() — hides the loader in the iframe.
  • sendReadyToInteract() — tells DIAL Chat the visualizer is ready to receive data.

Step 5: Create my-visualizer/src/index.tsx​

This is the application entry point that mounts the visualizer component.

import { createRoot } from 'react-dom/client';
import { Module } from './Module';

const root = createRoot(document.getElementById('root')!);
root.render(<Module />);

Verification​

  1. Start your visualizer application (for example, on http://localhost:8000).
  2. Restart DIAL Chat with the environment variables from Step 1.
  3. Send a message to a model or application configured to return custom_content with a MIME type matching your visualizer's contentType.
  4. The response renders inside an iframe in the Chat conversation.

What you learned​

  • How to configure DIAL Chat to load custom visualizers via ALLOWED_IFRAME_SOURCES and CUSTOM_VISUALIZERS
  • How to use the ChatVisualizerConnector library to receive and render data from DIAL Chat
  • The data contract between DIAL Chat and a visualizer (AttachmentData, CustomVisualizerDataLayout)

Further reading​