Skip to content

Commit fa0a6a8

Browse files
committed
[add] demo
1 parent 8f19f36 commit fa0a6a8

File tree

11 files changed

+2380
-1
lines changed

11 files changed

+2380
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules
2+
/.env
3+
/dist

README.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,82 @@
1-
# diagram-org-chart-builder-ai-demo
1+
# AI Org Chart Builder with DHTMLX Diagram
2+
3+
A full-stack web application that integrates DHTMLX Diagram with AI for generating organizational charts and diagrams from natural language text descriptions.
4+
5+
### **[✨ Try the Live Demo >>>](https://dhtmlx.com/docs/demo/diagram-org-chart-builder-ai-demo/)**
6+
7+
## Features
8+
9+
- **Natural Language to Diagram:** Describe your diagram in plain English (e.g., "An organizational chart with CEO and department heads") and watch it come to life.
10+
- **Live Preview:** See the generated DHTMLX Diagram instantly.
11+
- **Editable JSON:** View, edit, and fine-tune the generated JSON configuration in a built-in code editor.
12+
- **Instant Updates:** Modify the JSON and click "Update Preview" to see your changes immediately without calling the AI again.
13+
14+
## AI Service
15+
16+
- Configured to work with any OpenAI API-compatible service.
17+
- Tested with `gpt-5-nano` model.
18+
19+
## Setup and Installation
20+
21+
Follow these steps to get the project running on your local machine.
22+
23+
```bash
24+
cd diagram-org-chart-builder-ai-demo
25+
npm install
26+
```
27+
28+
### Set up environment variables:
29+
Create a new file named `.env` inside the `diagram-org-chart-builder-ai-demo` directory by copying from `env.sample`. This file holds your secret keys and configuration.
30+
31+
📄 `diagram-org-chart-builder-ai-demo/.env`
32+
```ini
33+
# --- OpenAI API Configuration ---
34+
OPENAI_API_KEY=sk-YourSecretApiKeyGoesHere
35+
OPENAI_BASE_URL=https://api.openai.com/v1
36+
37+
# --- Security Configuration ---
38+
CORS_ALLOWED_ORIGINS=http://localhost:3001,http://127.0.0.1:3001,http://localhost:5500,http://127.0.0.1:5500
39+
```
40+
41+
- **`OPENAI_API_KEY`**: (Required) Your secret API key for the AI service.
42+
- **`OPENAI_BASE_URL`**: The API endpoint for the AI service. Can be changed to use a proxy or a different provider compatible with the OpenAI API.
43+
- **`CORS_ALLOWED_ORIGINS`**: A crucial security setting. This is a comma-separated list of web addresses allowed to connect to your backend server. For production, you **must** change this to your public frontend's URL (e.g., `https://myapp.com`).
44+
45+
### Run the Application
46+
47+
In the same `diagram-org-chart-builder-ai-demo` directory, run the start command:
48+
```bash
49+
npm start
50+
```
51+
52+
You should see the following output in your terminal:
53+
```
54+
Server started on port 3001
55+
```
56+
57+
### 4. Open in Browser
58+
59+
Open your favorite web browser and navigate to:
60+
[http://localhost:3001](http://localhost:3001)
61+
62+
You should see the application, ready to generate diagrams!
63+
64+
## How It Works: From Text to Diagram
65+
66+
1. **Describe your idea:** The user enters a text description of the diagram, for example, "director and three departments: sales, marketing and development".
67+
2. **AI-Powered JSON Generation:** The text is sent to the server, where our AI generates a structured JSON configuration based on the request.
68+
3. **Instant visualization:** The frontend receives the ready data and immediately renders an interactive DHTMLX diagram that can be seen on the screen right away.
69+
4. **Full control:** Next to the diagram, the JSON code is displayed. You can edit it manually and update the visualization in real time to bring the result to perfection.
70+
71+
## Deployment
72+
73+
This application is ready to be deployed on any service that supports Node.js, such as Render, Heroku, or Vercel.
74+
75+
**Key deployment steps:**
76+
- **Do not** upload your `.env` file. Use the hosting provider's "Environment Variables" section to set `OPENAI_API_KEY`, `OPENAI_BASE_URL`, and `CORS_ALLOWED_ORIGINS`.
77+
- The `Root Directory` should be left blank (or set to /).
78+
- The `Start Command` should be `npm start`.
79+
80+
## License
81+
82+
DHTMLX Diagram is a commercial library - use it under a [valid license](https://dhtmlx.com/docs/products/licenses.shtml) or [evaluation agreement](https://dhtmlx.com/docs/products/dhtmlxDiagram/download.shtml).

backend/ai-prompts.js

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/schema.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export const schemaList = [
2+
{
3+
type: "function",
4+
function: {
5+
name: "create_org_chart_diagram",
6+
description: "Creates an organizational chart diagram from an array of nodes.",
7+
parameters: {
8+
type: "object",
9+
properties: {
10+
nodes: {
11+
type: "array",
12+
description: "An array of objects, where each object represents an employee.",
13+
items: {
14+
type: "object",
15+
properties: {
16+
id: {
17+
type: "string",
18+
description: "Unique employee identifier (name in lowercase)."
19+
},
20+
text: {
21+
type: "string",
22+
description: "Employee's position (e.g., 'CEO', 'Manager', 'Developer')."
23+
},
24+
parent: {
25+
type: "string",
26+
description: "ID of the direct manager."
27+
},
28+
title: {
29+
type: "string",
30+
description: "Employee name to display."
31+
},
32+
partner: {
33+
type: "boolean",
34+
description: "Indicates if the employee is a partner."
35+
},
36+
assistant: {
37+
type: "boolean",
38+
description: "Indicates if the employee is an assistant."
39+
}
40+
},
41+
required: ["id", "text"]
42+
}
43+
}
44+
},
45+
required: ["nodes"]
46+
}
47+
}
48+
}
49+
];

backend/server.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import 'dotenv/config';
2+
import express from 'express';
3+
import { createServer } from 'http';
4+
import { Server } from 'socket.io';
5+
import OpenAI from 'openai';
6+
import path from 'path';
7+
import { fileURLToPath } from 'url';
8+
import { rephrasePrompt, diagramPrompt } from './ai-prompts.js';
9+
import { schemaList } from './schema.js';
10+
11+
const __filename = fileURLToPath(import.meta.url);
12+
const __dirname = path.dirname(__filename);
13+
14+
const app = express();
15+
const http = createServer(app);
16+
17+
const io = new Server(http, {
18+
cors: {
19+
origin: "*",
20+
methods: ["GET", "POST"]
21+
}
22+
});
23+
24+
app.use(express.static(path.join(__dirname, '../frontend')));
25+
26+
const openai = new OpenAI({
27+
apiKey: process.env.OPENAI_API_KEY,
28+
baseURL: process.env.OPENAI_BASE_URL
29+
});
30+
31+
io.on('connection', socket => {
32+
socket.on('rephrase_text', async (data, callback) => {
33+
try {
34+
const rephrasedText = await callOpenAIForRephrase(data.text);
35+
callback({ status: 'success', payload: rephrasedText });
36+
} catch(e) {
37+
callback({ status: 'error', message: e.message });
38+
}
39+
});
40+
41+
socket.on('generate_diagram', async (data, callback) => {
42+
try {
43+
const assistantMessage = await callOpenAIForDiagram(data.text);
44+
if (!assistantMessage?.tool_calls?.length) {
45+
throw new Error("AI did not return a valid function call.");
46+
}
47+
const toolCall = assistantMessage.tool_calls[0];
48+
const parsedResult = JSON.parse(toolCall.function.arguments);
49+
50+
parsedResult.nodes.forEach(node => {
51+
if (!node.text) {
52+
node.text = "Unknown Position";
53+
}
54+
});
55+
56+
const fixedNodes = fixHierarchy(parsedResult.nodes);
57+
callback({ status: 'success', payload: fixedNodes });
58+
} catch (e) {
59+
console.error('Error generating diagram:', e.message);
60+
callback({ status: 'error', message: e.message });
61+
}
62+
});
63+
});
64+
65+
function fixHierarchy(nodes) {
66+
if (!nodes || nodes.length === 0) return [];
67+
const existingIds = new Set(nodes.map(node => node.id));
68+
nodes.forEach(node => {
69+
if (node.parent && !existingIds.has(node.parent)) {
70+
console.warn(`"Orphan" detected! Node "${node.text}" refers to a non-existent parent "${node.parent}". Connection removed.`);
71+
delete node.parent;
72+
}
73+
});
74+
return nodes;
75+
}
76+
77+
async function callOpenAIForRephrase(userText) {
78+
const messages = [{ role: 'system', content: rephrasePrompt }, { role: 'user', content: userText }];
79+
80+
try {
81+
const res = await openai.chat.completions.create({
82+
model: 'gpt-5-nano',
83+
messages: messages,
84+
});
85+
return res.choices[0].message.content;
86+
} catch (e) {
87+
throw new Error(`Error from AI service: ${e.message}`);
88+
}
89+
}
90+
91+
async function callOpenAIForDiagram(userText) {
92+
const messages = [{ role: 'system', content: diagramPrompt }, { role: 'user', content: userText }];
93+
94+
try {
95+
const res = await openai.chat.completions.create({
96+
model: 'gpt-5-nano',
97+
messages: messages,
98+
tools: schemaList,
99+
tool_choice: { type: "function", function: { name: "create_org_chart_diagram" } },
100+
});
101+
return res.choices[0].message;
102+
} catch (e) {
103+
throw new Error(`Error from AI service: ${e.message}`);
104+
}
105+
}
106+
107+
const PORT = process.env.PORT || 3001;
108+
http.listen(PORT);

env.sample

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Environment variables
2+
#
3+
# INSTRUCTIONS:
4+
# 1. Copy this file to a new file named .env
5+
# 2. Fill in the required values below.
6+
#
7+
# NOTE: The .env file contains secrets and must NOT be committed to Git.
8+
#
9+
# --- OpenAI API Configuration ---
10+
11+
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
12+
OPENAI_BASE_URL=https://api.openai.com/v1
13+
14+
15+
# --- Security Configuration ---
16+
# A comma-separated list of domains allowed to connect to this server.
17+
# For local development, the default is usually sufficient.
18+
# For production, set this to your public frontend URL.
19+
# Example: https://my-app.com,https://staging.my-app.com
20+
21+
CORS_ALLOWED_ORIGINS=http://localhost:3001,http://localhost:3002,http://127.0.0.1:3001,http://localhost:5500,http://127.0.0.1:5500

0 commit comments

Comments
 (0)