{"id":14743,"date":"2025-12-11T15:51:41","date_gmt":"2025-12-11T12:51:41","guid":{"rendered":"https:\/\/dhtmlx.com\/blog\/?p=14743"},"modified":"2026-05-22T13:04:09","modified_gmt":"2026-05-22T10:04:09","slug":"implementing-ai-driven-theme-builder-dhtmlx-gantt","status":"publish","type":"post","link":"https:\/\/dhtmlx.com\/blog\/implementing-ai-driven-theme-builder-dhtmlx-gantt\/","title":{"rendered":"Implementing AI-Driven Theme Builder for DHTMLX Gantt"},"content":{"rendered":"<p>As AI becomes part of everyday development workflows, more teams are looking for practical patterns rather than generic chatbots. One recurring challenge is turning free-text requests into safe configuration changes for complex UI components. DHTMLX Gantt is a good example: it offers many configuration options and theme variables, but tuning them manually can be slow when you just want to try different layouts or visual styles.<\/p>\n<p>As a solution, it is possible to add a thin AI layer that accepts specific instructions (prompts) in plain English and converts them into structured configuration updates. The main idea here is to keep the configuration surface small and controlled, while the AI model handles the mapping from natural language to JSON. In this tutorial, you will learn how to add such a smart design assistant to an existing <a href=\"https:\/\/dhtmlx.com\/docs\/products\/dhtmlxGantt\/\" rel=\"noopener\" target=\"_blank\">DHTMLX-based JS Gantt chart<\/a> without rewriting the entire UI.<\/p>\n<h2>Overview of DHTMLX Demo: Structure and Workflow<\/h2>\n<p>Our tutorial is based on the ready-made AI Gantt Theme Builder demo delivered by the DHTMLX team. <\/p>\n<p>The UI structure of this demo app includes two interactive panels:<\/p>\n<ul>\n<li>The Gantt chart with uploaded project data on the left.<\/li>\n<li>The Chat panel with the Chat and Code tabs on the right.<\/li>\n<\/ul>\n<p>This intuitive user interface allows end-users to showcase their UI design skills by sending prompts in plain English to the AI Assistant (Chatbot) rather than manual coding. <\/p>\n<p>For instance, you can request a new theme with a warm color palette (orange, beige, soft red), make links thicker, and increase the row height. <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dhtmlx.com\/blog\/wp-content\/uploads\/2025\/12\/Gantt-Theme-Builder.gif\" alt=\"Gantt Theme Builder in action\" width=\"1908\" height=\"848\" class=\"aligncenter size-full wp-image-14747\" \/><a href=\"https:\/\/dhtmlx.com\/docs\/demo\/ai-gantt-theme-builder\/\" rel=\"noopener\" target=\"_blank\">Open live demo ><\/a><\/p>\n<p>The full demo project and technology stack details are provided in the <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\" rel=\"noopener\" target=\"_blank\">GitHub repository<\/a>. <\/p>\n<p>Let us explain the workflow in the example above step by step:<\/p>\n<ol>\n<li>The user sends a request (prompt) with the desired Gantt theme\/configuration via the Chat tab.<\/li>\n<li>The frontend passes this message to the backend, where it is analyzed and prepared for the AI service (LLM).<\/li>\n<li>The backend forwards the user request to the LLM for creating the new configuration based on the allowed functions.<\/li>\n<li>The LLM returns a structured JSON configuration, which includes a set of custom CSS variables and configuration settings.<\/li>\n<li>The frontend applies new parameters and updates the Gantt chart.<\/li>\n<\/ol>\n<p>Technically, the most curious part of this workflow, namely the transformation of user prompts into actionable code, is performed via OpenAI API or a compatible service, supporting the function-calling (<a href=\"https:\/\/platform.openai.com\/docs\/guides\/function-calling\" rel=\"noopener\" target=\"_blank\">tool-calling<\/a>) feature. <\/p>\n<p>Are you ready to learn the implementation details of this core feature and other peculiarities required for your own AI Gantt Theme Builder?<\/p>\n<p>Let us explore everything in its due order.<\/p>\n<h2>Setting Up AI-Ready User Interface<\/h2>\n<p>In this section, we\u2019ll walk through initializing the Gantt component, configuring the AI Assistant panel, and transmitting user prompts to the backend.<\/p>\n<h3>Gantt Chart Initialization and Its Styling Peculiarities<\/h3>\n<p>First, you need to add a Gantt chart to a web page. To do that, you initialize the DHTMLX <a href=\"https:\/\/dhtmlx.com\/docs\/products\/dhtmlxGantt\/\" rel=\"noopener\" target=\"_blank\">JavaScript Gantt component<\/a> and configure columns, plugins, and helper functions (<a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\/blob\/main\/frontend\/src\/main.js\" rel=\"noopener\" target=\"_blank\">main.js<\/a>):<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">const<\/span> gantt <span class=\"sy0\">=<\/span> Gantt.<span class=\"me1\">getGanttInstance<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">config<\/span>.<span class=\"me1\">columns<\/span> <span class=\"sy0\">=<\/span> <span class=\"br0\">&#91;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> name<span class=\"sy0\">:<\/span> <span class=\"st0\">'wbs'<\/span><span class=\"sy0\">,<\/span> label<span class=\"sy0\">:<\/span> <span class=\"st0\">'WBS'<\/span><span class=\"sy0\">,<\/span> width<span class=\"sy0\">:<\/span> <span class=\"nu0\">60<\/span><span class=\"sy0\">,<\/span> resize<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span> template<span class=\"sy0\">:<\/span> gantt.<span class=\"me1\">getWBSCode<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> name<span class=\"sy0\">:<\/span> <span class=\"st0\">'text'<\/span><span class=\"sy0\">,<\/span> label<span class=\"sy0\">:<\/span> <span class=\"st0\">'Task name'<\/span><span class=\"sy0\">,<\/span> tree<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span> width<span class=\"sy0\">:<\/span> <span class=\"nu0\">250<\/span><span class=\"sy0\">,<\/span> resize<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> name<span class=\"sy0\">:<\/span> <span class=\"st0\">'start_date'<\/span><span class=\"sy0\">,<\/span> align<span class=\"sy0\">:<\/span> <span class=\"st0\">'center'<\/span><span class=\"sy0\">,<\/span> width<span class=\"sy0\">:<\/span> <span class=\"nu0\">100<\/span><span class=\"sy0\">,<\/span> resize<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> name<span class=\"sy0\">:<\/span> <span class=\"st0\">'duration'<\/span><span class=\"sy0\">,<\/span> align<span class=\"sy0\">:<\/span> <span class=\"st0\">'center'<\/span><span class=\"sy0\">,<\/span> width<span class=\"sy0\">:<\/span> <span class=\"nu0\">80<\/span><span class=\"sy0\">,<\/span> resize<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> name<span class=\"sy0\">:<\/span> <span class=\"st0\">'add'<\/span><span class=\"sy0\">,<\/span> width<span class=\"sy0\">:<\/span> <span class=\"nu0\">40<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n<span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">plugins<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; auto_scheduling<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; undo<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; export_api<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; marker<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; tooltip<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\ngantt.<span class=\"me1\">config<\/span>.<span class=\"me1\">auto_scheduling<\/span> <span class=\"sy0\">=<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">config<\/span>.<span class=\"me1\">open_tree_initially<\/span> <span class=\"sy0\">=<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">config<\/span>.<span class=\"me1\">auto_types<\/span> <span class=\"sy0\">=<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">config<\/span>.<span class=\"me1\">scale_height<\/span> <span class=\"sy0\">=<\/span> <span class=\"nu0\">60<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">config<\/span>.<span class=\"me1\">link_radius<\/span> <span class=\"sy0\">=<\/span> <span class=\"nu0\">4<\/span><span class=\"sy0\">;<\/span><br \/>\ninitZoom<span class=\"br0\">&#40;<\/span>gantt<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\nfitTaskText<span class=\"br0\">&#40;<\/span>gantt<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><\/div><\/div>\n<p>As for the Gantt appearance, we should mention that this part of the Gantt configuration is dynamically controlled by CSS variables (colors, fonts, etc.) and the gantt.config object (row\/task height, link width, etc.). Therefore, AI-generated updates can be applied instantly.<\/p>\n<p>The AI Gantt Theme Builder maintains the current state of both the theme and configuration. When a new theme is applied via the AI assistant, it is merged with the previous one, preserving all previous changes.<\/p>\n<p><em><strong>Note:<\/strong> Currently, we are working on a temporary storage system (messages), which will enable the conversation history feature. It will store user messages, LLM responses, and the results of tool calls.<\/em><\/p>\n<h3>Assistant Panel: Chat and Code Tabs<\/h3>\n<p>As stated above, the AI assistant of our demo includes Chat and Code tabs. The Chat tab serves for natural language communication between users and the AI service. It is based on a simple chat widget that can be added to your project in the following way:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">...<br \/>\n&nbsp; <span class=\"co1\">\/\/ Initialize the chat widget with dependencies<\/span><br \/>\n<span class=\"kw5\">export<\/span> <span class=\"kw1\">const<\/span> initChat <span class=\"sy0\">=<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> socket<span class=\"sy0\">,<\/span> runCommand<span class=\"sy0\">,<\/span> getTheme<span class=\"sy0\">,<\/span> getConfigs <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; <br \/>\n&nbsp; <span class=\"co1\">\/\/ Create and set up the chat UI<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">const<\/span> chatWidgetContainer <span class=\"sy0\">=<\/span> document.<span class=\"me1\">querySelector<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">'#chat-tab'<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; chatWidgetContainer.<span class=\"me1\">dataset<\/span>.<span class=\"me1\">id<\/span> <span class=\"sy0\">=<\/span> <span class=\"st0\">'0'<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; chatWidgetContainer.<span class=\"me1\">innerHTML<\/span> <span class=\"sy0\">=<\/span> `<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;chat-popup&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;relative left-0 bottom-0 h-full w-full bg-white rounded-md shadow-md flex flex-col transition-all&quot;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;chat-header&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;flex justify-between items-center p-4 bg-gray-800 text-white rounded-t-md&quot;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>h3 <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;m-0 text-lg&quot;<\/span><span class=\"sy0\">&gt;<\/span>DHX Assistant<span class=\"sy0\">&lt;\/<\/span>h3<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class=\"sy0\">&lt;<\/span>button data<span class=\"sy0\">-<\/span>micromodal<span class=\"sy0\">-<\/span>trigger<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;modal-1&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;help-btn&quot;<\/span><span class=\"sy0\">&gt;?&lt;\/<\/span>button<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;chat-messages&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;flex-1 p-4 overflow-y-auto text-base&quot;<\/span><span class=\"sy0\">&gt;&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;loader&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;hidden justify-start mb-3&quot;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;spinner m-auto&quot;<\/span><span class=\"sy0\">&gt;&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;chat-input-container&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;p-4 border-t border-gray-200&quot;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;flex space-x-4 items-center&quot;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>input type<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;text&quot;<\/span> id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;chat-input&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;flex-1 border border-gray-300 rounded-md px-4 py-2 outline-none w-3\/4&quot;<\/span> placeholder<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;Type your message...&quot;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>button id<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;chat-submit&quot;<\/span> <span class=\"kw5\">class<\/span><span class=\"sy0\">=<\/span><span class=\"st0\">&quot;bg-gray-800 text-white rounded-md px-4 py-2 cursor-pointer&quot;<\/span><span class=\"sy0\">&gt;<\/span>Send<span class=\"sy0\">&lt;\/<\/span>button<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp;`<span class=\"sy0\">;<\/span><br \/>\n...<\/div><\/div>\n<p>The complete chat widget configuration is provided in the corresponding <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\/blob\/main\/frontend\/src\/chat-widget.js\" rel=\"noopener\" target=\"_blank\">file<\/a> of our demo repository.<\/p>\n<p>Apart from sending user prompts and displaying AI messages, the AI Assistant also provides suggestions and guidance for further actions. <\/p>\n<p>The Code tab is used after desired styling changes are applied to the Gantt chart. It allows reviewing and manually modifying generated CSS variables and config values using the corresponding editors. These editors are added and controlled via the <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\/blob\/main\/frontend\/src\/theme-manager.js\" rel=\"noopener\" target=\"_blank\">ThemeManager<\/a> object:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">initEditors<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">initCssEditor<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">initConfigEditor<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n<br \/>\n&nbsp; initCssEditor<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; require.<span class=\"me1\">config<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> paths<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> vs<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;https:\/\/cdn.jsdelivr.net\/npm\/monaco-editor@0.34.1\/min\/vs&quot;<\/span> <span class=\"br0\">&#125;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; require<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#91;<\/span><span class=\"st0\">&quot;vs\/editor\/editor.main&quot;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">cssEditor<\/span> <span class=\"sy0\">=<\/span> monaco.<span class=\"me1\">editor<\/span>.<span class=\"me1\">create<\/span><span class=\"br0\">&#40;<\/span>document.<span class=\"me1\">getElementById<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;css-editor-container&quot;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; value<span class=\"sy0\">:<\/span> <span class=\"kw1\">this<\/span>.<span class=\"me1\">originalThemeCSS<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; language<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;css&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; theme<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;vs-dark&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; automaticLayout<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; readOnly<span class=\"sy0\">:<\/span> <span class=\"kw2\">false<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">setupEditorEvents<\/span><span class=\"br0\">&#40;<\/span><span class=\"kw1\">this<\/span>.<span class=\"me1\">cssEditor<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">&quot;css&quot;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n<br \/>\n&nbsp; initConfigEditor<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; require.<span class=\"me1\">config<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span> paths<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> vs<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;https:\/\/cdn.jsdelivr.net\/npm\/monaco-editor@0.34.1\/min\/vs&quot;<\/span> <span class=\"br0\">&#125;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; require<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#91;<\/span><span class=\"st0\">&quot;vs\/editor\/editor.main&quot;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">configEditor<\/span> <span class=\"sy0\">=<\/span> monaco.<span class=\"me1\">editor<\/span>.<span class=\"me1\">create<\/span><span class=\"br0\">&#40;<\/span>document.<span class=\"me1\">getElementById<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;config-editor-container&quot;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; value<span class=\"sy0\">:<\/span> <span class=\"kw1\">this<\/span>.<span class=\"me1\">originalThemeConfigs<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; language<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;javascript&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; theme<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;vs-dark&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; automaticLayout<span class=\"sy0\">:<\/span> <span class=\"kw2\">true<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; readOnly<span class=\"sy0\">:<\/span> <span class=\"kw2\">false<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">setupEditorEvents<\/span><span class=\"br0\">&#40;<\/span><span class=\"kw1\">this<\/span>.<span class=\"me1\">configEditor<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">&quot;config&quot;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>The <strong>ThemeManager<\/strong> stores the initial and modified code (CSS + configuration), and updates styles in real time:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">setupEditorEvents<span class=\"br0\">&#40;<\/span>editor<span class=\"sy0\">,<\/span> type<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; editor.<span class=\"me1\">onDidChangeModelContent<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">const<\/span> value <span class=\"sy0\">=<\/span> editor.<span class=\"me1\">getValue<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span><span class=\"br0\">&#91;<\/span>`editedTheme$<span class=\"br0\">&#123;<\/span>type <span class=\"sy0\">===<\/span> <span class=\"st0\">&quot;css&quot;<\/span> <span class=\"sy0\">?<\/span> <span class=\"st0\">&quot;CSS&quot;<\/span> <span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;Configs&quot;<\/span><span class=\"br0\">&#125;<\/span>`<span class=\"br0\">&#93;<\/span> <span class=\"sy0\">=<\/span> value<span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">updateButtonsState<\/span><span class=\"br0\">&#40;<\/span>type<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">applyThemeOnFly<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n&nbsp; document.<span class=\"me1\">querySelector<\/span><span class=\"br0\">&#40;<\/span>`<span class=\"br0\">&#91;<\/span>data<span class=\"sy0\">-<\/span>editor<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;${type}&quot;<\/span><span class=\"br0\">&#93;<\/span>.<span class=\"me1\">copy<\/span><span class=\"sy0\">-<\/span>editor<span class=\"sy0\">-<\/span>btn`<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">addEventListener<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;click&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">copyContent<\/span><span class=\"br0\">&#40;<\/span>type<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n&nbsp; document.<span class=\"me1\">querySelector<\/span><span class=\"br0\">&#40;<\/span>`<span class=\"br0\">&#91;<\/span>data<span class=\"sy0\">-<\/span>editor<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;${type}&quot;<\/span><span class=\"br0\">&#93;<\/span>.<span class=\"me1\">save<\/span><span class=\"sy0\">-<\/span>editor<span class=\"sy0\">-<\/span>btn`<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">addEventListener<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;click&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">saveChanges<\/span><span class=\"br0\">&#40;<\/span>type<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<br \/>\n&nbsp; document.<span class=\"me1\">querySelector<\/span><span class=\"br0\">&#40;<\/span>`<span class=\"br0\">&#91;<\/span>data<span class=\"sy0\">-<\/span>editor<span class=\"sy0\">=<\/span><span class=\"st0\">&quot;${type}&quot;<\/span><span class=\"br0\">&#93;<\/span>.<span class=\"me1\">cancel<\/span><span class=\"sy0\">-<\/span>editor<span class=\"sy0\">-<\/span>btn`<span class=\"br0\">&#41;<\/span>.<span class=\"me1\">addEventListener<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;click&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">this<\/span>.<span class=\"me1\">cancelChanges<\/span><span class=\"br0\">&#40;<\/span>type<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp;<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>It also processes the &#8220;Save&#8221;, &#8220;Cancel&#8221;, and &#8220;Copy&#8221; actions, sending changes back through the <strong>runCommand<\/strong> when saved.<\/p>\n<h3>Sending User Prompt to the Backend<\/h3>\n<p>Once the user enters the prompt with the desired styling changes and clicks on the \u201cSend\u201d button, it is necessary to send the message to the backend via Websocket. This connection is established using the Socket.IO library:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">const<\/span> SOCKET_URL <span class=\"sy0\">=<\/span> <span class=\"kw5\">import<\/span>.<span class=\"me1\">meta<\/span>.<span class=\"me1\">env<\/span>.<span class=\"me1\">VITE_SOCKET_URL<\/span> <span class=\"sy0\">||<\/span> `$<span class=\"br0\">&#123;<\/span>window.<span class=\"me1\">location<\/span>.<span class=\"me1\">origin<\/span><span class=\"br0\">&#125;<\/span>`<span class=\"sy0\">;<\/span><br \/>\n<span class=\"kw1\">const<\/span> socket <span class=\"sy0\">=<\/span> io<span class=\"br0\">&#40;<\/span>SOCKET_URL<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><\/div><\/div>\n<p>Here is how the user prompt and the current Gantt settings are transmitted to the backend:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">function<\/span> sendUserMessage<span class=\"br0\">&#40;<\/span>message<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; displayUser<span class=\"br0\">&#40;<\/span>message<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp;<br \/>\n&nbsp; <span class=\"kw1\">const<\/span> payload <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; message<span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; theme<span class=\"sy0\">:<\/span> getTheme<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> <span class=\"co1\">\/\/ sends current theme state<\/span><br \/>\n&nbsp; &nbsp; configs<span class=\"sy0\">:<\/span> getConfigs<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span> <span class=\"co1\">\/\/ sends current config state<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; socket.<span class=\"me1\">emit<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">'user_msg'<\/span><span class=\"sy0\">,<\/span> JSON.<span class=\"me1\">stringify<\/span><span class=\"br0\">&#40;<\/span>payload<span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> <span class=\"co1\">\/\/ sends the payload to the backend via WebSocket<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>Now, it is the backend\u2019s turn to process the prompt, instruct the AI on the required styling changes, and send back the response. <\/p>\n<h2>Preparing the Backend for AI Commands<\/h2>\n<p>The backend is a centerpiece of the DHTMLX Gantt Theme Builder, which combines the chatbot, AI model, and Gantt chart into a single and coherent system under the hood. If you want to implement a similar backend for your AI Gantt Theme Builder, it should be able to perform the operations described below.<\/p>\n<h3>Providing the AI Model with Valid Styling Instructions<\/h3>\n<p>The backend defines a set of operations (i.e., tool functions) that the AI model can apply for generating new styling settings for the Gantt chart. These functions are defined in a JSON schema and exposed to the AI model through OpenAI\u2019s function-calling. Each function has strict input parameters, ensuring that the AI produces only valid and safe updates for the Gantt chart.<\/p>\n<p>In our case, there are two available tool functions with corresponding schema fragments:<\/p>\n<ul>\n<li><strong>set_theme<\/strong> &#8211; a primary mechanism for updating the visual style and layout configuration of the Gantt chart.<\/li>\n<\/ul>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; name<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;set_theme&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; parameters<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; type<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;object&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; properties<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; variables<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; type<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;array&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; items<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> key<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;string&quot;<\/span><span class=\"sy0\">,<\/span> value<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;string&quot;<\/span> <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; configs<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; type<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;array&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; items<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span> name<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;string&quot;<\/span><span class=\"sy0\">,<\/span> value<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"st0\">&quot;number&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">&quot;boolean&quot;<\/span><span class=\"br0\">&#93;<\/span> <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<ul>\n<li><strong>reset_theme<\/strong> &#8211; clears all custom CSS variables and restores defaults for all configuration settings.<\/li>\n<\/ul>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">&nbsp; <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; type<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;function&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">function<\/span><span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; name<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;reset_theme&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; description<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;Reset the current Gantt theme by clearing all custom CSS variables and configs (back to defaults).&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; parameters<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; type<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;object&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; properties<span class=\"sy0\">:<\/span> <span class=\"br0\">&#123;<\/span><span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; required<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><\/div><\/div>\n<p>The full schema description of available functions can be found in the <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\/blob\/main\/backend\/schemaList.js\" rel=\"noopener\" target=\"_blank\">schemaList.js<\/a> file of our official demo.<\/p>\n<p>This approach closely aligns with modern AI-assisted product design workflows, where structured outputs are generated from natural language inputs to speed up iteration and reduce manual effort. Platforms like <a href=\"https:\/\/www.magicpatterns.com\/teams\/product\" rel=\"noopener\" target=\"_blank\">Magic Patterns<\/a> AI product design platform, Framer, and others demonstrate a similar philosophy by enabling product teams to turn ideas, prompts, and user stories into usable UI designs within seconds. This makes it easier for teams to prototype, test, and refine interfaces without constantly switching between design and development tools.<\/p>\n<h3>Contextualizing User Prompt<\/h3>\n<p>Before sending a request to the LLM, the backend generates a structured system context (prompt) including:<\/p>\n<ul>\n<li>descriptions of all available CSS theme variables;<\/li>\n<li>full list of <strong>gantt.config<\/strong> options;<\/li>\n<li>current theme (all CSS variables) and configs;<\/li>\n<li>rules instructing the model how to modify them safely.<\/li>\n<\/ul>\n<p>The system prompt is created using the generateSystemPrompt function. The final request assembled in the <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\/blob\/4a5fd4f77a5320f2071c9f5605f60829cc10db03\/backend\/server.js#L86\" rel=\"noopener\" target=\"_blank\">talkToLLM<\/a> function:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">```<br \/>\n<span class=\"kw1\">const<\/span> messages <span class=\"sy0\">=<\/span> <span class=\"br0\">&#91;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> role<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;system&quot;<\/span><span class=\"sy0\">,<\/span> content<span class=\"sy0\">:<\/span> generateSystemPrompt<span class=\"br0\">&#40;<\/span>theme<span class=\"sy0\">,<\/span> configs<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#123;<\/span> role<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;user&quot;<\/span><span class=\"sy0\">,<\/span> content<span class=\"sy0\">:<\/span> request <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n<span class=\"br0\">&#93;<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<span class=\"kw1\">const<\/span> res <span class=\"sy0\">=<\/span> await openai.<span class=\"me1\">chat<\/span>.<span class=\"me1\">completions<\/span>.<span class=\"me1\">create<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; model<span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;gpt-5-nano&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; messages<span class=\"sy0\">,<\/span><br \/>\n&nbsp; tools<span class=\"sy0\">:<\/span> schemaList<span class=\"sy0\">,<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n```<\/div><\/div>\n<h3>Returning Structured LLM Responses<\/h3>\n<p>The AI model (LLM) returns a structured response that may contain the following:<\/p>\n<ul>\n<li>a regular assistant message (does not request any action)<\/li>\n<li>a tool call (structured instructions), if the Gantt chart appearance must be changed:<\/li>\n<\/ul>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; <span class=\"st0\">&quot;tool&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;set_theme&quot;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"st0\">&quot;variables&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#123;<\/span> <span class=\"st0\">&quot;key&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;--dhx-gantt-task-background&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">&quot;value&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;#dce8ff&quot;<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#123;<\/span> <span class=\"st0\">&quot;key&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;--dhx-gantt-grid-background&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">&quot;value&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;#f9fbff&quot;<\/span> <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"st0\">&quot;configs&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#123;<\/span> <span class=\"st0\">&quot;name&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"st0\">&quot;row_height&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"st0\">&quot;value&quot;<\/span><span class=\"sy0\">:<\/span> <span class=\"nu0\">42<\/span> <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#93;<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>If the response contains a tool call, the backend serializes and sends it to the frontend inside a <strong>tool_call<\/strong> WebSocket event.<\/p>\n<h2>Applying AI-Generated Changes to the Gantt Chart<\/h2>\n<p>The frontend must receive the event (instructions) from the backend and apply the corresponding changes to the Gantt chart. This section explains how incoming AI commands are dispatched and executed in the browser.<\/p>\n<p>First, the event listener receives the <strong>tool_call<\/strong> event and extracts the AI-generated command:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">```<br \/>\nsocket.<span class=\"me1\">on<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;tool_call&quot;<\/span><span class=\"sy0\">,<\/span> <span class=\"br0\">&#40;<\/span>callJSON<span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; <span class=\"kw1\">const<\/span> <span class=\"br0\">&#123;<\/span> cmd<span class=\"sy0\">,<\/span> params <span class=\"br0\">&#125;<\/span> <span class=\"sy0\">=<\/span> JSON.<span class=\"me1\">parse<\/span><span class=\"br0\">&#40;<\/span>callJSON<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; runCommand<span class=\"br0\">&#40;<\/span>cmd<span class=\"sy0\">,<\/span> params<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n```<\/div><\/div>\n<p>Then, the AI\u2019s instructions are executed with the <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\/blob\/4a5fd4f77a5320f2071c9f5605f60829cc10db03\/frontend\/src\/command-runner.js\" rel=\"noopener\" target=\"_blank\">command runner<\/a> (<strong>runCommand<\/strong> function). Here is an example of the command dispatching:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw5\">export<\/span> <span class=\"kw1\">default<\/span> <span class=\"kw1\">function<\/span> <span class=\"br0\">&#40;<\/span>gantt<span class=\"sy0\">,<\/span> <span class=\"br0\">&#123;<\/span> onThemeSet<span class=\"sy0\">,<\/span> onConfigSet <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span> defaultConfigValues <span class=\"sy0\">=<\/span> <span class=\"br0\">&#123;<\/span><span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; <span class=\"co1\">\/\/ handlers for commands defined in \/backend\/schemaList.js<\/span><br \/>\n&nbsp; <span class=\"kw1\">return<\/span> <span class=\"kw1\">function<\/span> runCommand<span class=\"br0\">&#40;<\/span>cmd<span class=\"sy0\">,<\/span> args<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"kw1\">switch<\/span> <span class=\"br0\">&#40;<\/span>cmd<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">case<\/span> <span class=\"st0\">&quot;reset_theme&quot;<\/span><span class=\"sy0\">:<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">const<\/span> themeStyleId <span class=\"sy0\">=<\/span> <span class=\"st0\">&quot;dynamic-theme&quot;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">const<\/span> styleEl <span class=\"sy0\">=<\/span> document.<span class=\"me1\">getElementById<\/span><span class=\"br0\">&#40;<\/span>themeStyleId<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>styleEl<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; styleEl.<span class=\"me1\">remove<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>onThemeSet<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onThemeSet<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> cmd<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>onConfigSet<span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onConfigSet<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span> cmd<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span>defaultConfigValues.<span class=\"me1\">length<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defaultConfigValues.<span class=\"me1\">forEach<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#40;<\/span>config<span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gantt.<span class=\"me1\">config<\/span><span class=\"br0\">&#91;<\/span>config.<span class=\"me1\">name<\/span><span class=\"br0\">&#93;<\/span> <span class=\"sy0\">=<\/span> config.<span class=\"me1\">value<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gantt.<span class=\"me1\">render<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; <span class=\"kw1\">break<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"co1\">\/\/ ... other commands<\/span><br \/>\n<br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"kw1\">default<\/span><span class=\"sy0\">:<\/span><br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; console.<span class=\"me1\">warn<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;Unknown cmd:&quot;<\/span><span class=\"sy0\">,<\/span> cmd<span class=\"sy0\">,<\/span> args<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#125;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>As a result, the Gantt appearance is updated to reflect the requested theme or configuration changes.<\/p>\n<p>This concludes the main instructions for implementing your AI Gantt Theme Builder. For more information, check out the official <a href=\"https:\/\/github.com\/DHTMLX\/gantt-theme-builder-ai-demo\" rel=\"noopener\" target=\"_blank\">GitHub repository<\/a> of our demo project. <\/p>\n<h2>Possible Improvements<\/h2>\n<p>The DHTMLX AI Gantt Theme Builder is a demo project with simplified business logic. But it can be a good foundation for a production-ready solution. If you followed our instructions and created a similar system, you should be aware of several aspects that can be further improved and refined:<\/p>\n<ul>\n<li>Our demo has no strict schema\/whitelist validation on the backend or frontend. It is reasonable to add validation of CSS variable names, range checking for numerical values, and type checking for configuration parameters.<\/li>\n<li>There is no robust mechanism for handling vague or ambiguous user requests. You could implement a confirmation step for potentially ambiguous changes.<\/li>\n<li>Each system prompt received by the LLM includes only the latest Gantt state and user request, limiting interaction capabilities with AI. To address this issue, you need to add conversation history support.<\/li>\n<li>Other potential enhancements: preview mode, collaborative editing, export options for theme packages, more advanced chat widget with voice input and file upload.<\/li>\n<\/ul>\n<p>These enhancements can help transform your AI Gantt Theme Builder into a comprehensive environment for AI-assisted visualization and configuration management of complex Gantt interfaces.<\/p>\n<h2>Wrapping Up<\/h2>\n<p>In this tutorial, you\u2019ve learned how to create an AI-assisted Gantt Theme Builder that reacts to natural-language prompts and applies visual changes in DHTMLX-based Gantt charts. This solution can be extended with validation, smarter ambiguity handling, and other AI-powered capabilities as your needs grow. The same pattern (natural language \u2192 structured JSON \u2192 UI update) can be used for other scenarios, such as switching calendars, toggling auto-scheduling modes, or applying predefined configuration presets.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As AI becomes part of everyday development workflows, more teams are looking for practical patterns rather than generic chatbots. One recurring challenge is turning free-text requests into safe configuration &#8230;<\/p>\n","protected":false},"author":101014,"featured_media":14745,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[71],"tags":[77,33,20],"class_list":["post-14743","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-ai","tag-all-posts","tag-gantt"],"_links":{"self":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/14743","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/users\/101014"}],"replies":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/comments?post=14743"}],"version-history":[{"count":10,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/14743\/revisions"}],"predecessor-version":[{"id":14746,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/14743\/revisions\/14746"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/media\/14745"}],"wp:attachment":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/media?parent=14743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/categories?post=14743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/tags?post=14743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}