Skip to content

Commit b7f7ec0

Browse files
ninelcc336lkn
andauthored
feat(generator): replace LLM-generated HTML with local template wrapping (#959)
* feat(generator): strictly regulate the syntax generation of GPT-Vis * fix(generator): fix template string escaping issue * refactor(vis): replace LLM-generated HTML with local template wrapping - Let LLM only generate pure GPT-Vis syntax instead of full HTML - Add wrapSyntaxInHTML() to embed syntax into a fixed HTML template locally - Remove fragile regex-based syntax extraction from LLM output - Simplify prompt by removing HTML structure requirements - Ensure HTML output is always well-formed regardless of LLM variance * fix: update review --------- Co-authored-by: lkn <lkn02425726@antgroup.com>
1 parent c5ede4d commit b7f7ec0

1 file changed

Lines changed: 72 additions & 74 deletions

File tree

src/visualization/generator.ts

Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/**
2-
* GPT-Vis HTML code generator with comprehensive chart examples
2+
* GPT-Vis visualization generator
3+
*
4+
* Strategy: LLM only generates pure GPT-Vis syntax, then we wrap it
5+
* into an HTML template locally. This avoids unreliable regex extraction
6+
* from LLM-generated HTML and ensures the HTML is always well-formed.
37
*/
48

59
import { generateText } from 'ai';
@@ -8,9 +12,55 @@ import { createOpenAI } from '@ai-sdk/openai';
812
import type { LLMConfig, ChartType } from '../types';
913

1014
/**
11-
* Generate complete HTML code for visualization with GPT-Vis syntax embedded
12-
* This function combines both syntax generation and HTML generation in a single LLM call
13-
* Returns both the syntax and HTML for frontend display
15+
* Wrap GPT-Vis syntax into a standalone HTML file that can be opened in any browser
16+
*/
17+
function wrapSyntaxInHTML(syntax: string): string {
18+
// Escape backticks, dollar signs, and backslashes for JS template literal
19+
const escapedSyntax = syntax
20+
.replace(/\\/g, '\\\\')
21+
.replace(/`/g, '\\`')
22+
.replace(/\$/g, '\\$')
23+
.replace(/<\/script>/g, '<\\/script>');
24+
25+
return `<!DOCTYPE html>
26+
<html>
27+
<head>
28+
<meta charset="UTF-8">
29+
<title>Data Visualization</title>
30+
<script src="https://unpkg.com/@antv/gpt-vis/dist/umd/index.min.js"></script>
31+
<style>
32+
html, body, #container {
33+
margin: 0;
34+
padding: 0;
35+
font-family: Arial, sans-serif;
36+
width: 100%;
37+
height: 100%;
38+
}
39+
</style>
40+
</head>
41+
<body>
42+
<div id="container"></div>
43+
<script>
44+
const gptVis = new GPTVis.GPTVis({
45+
container: '#container',
46+
});
47+
48+
const visSyntax = \`${escapedSyntax}\`;
49+
50+
gptVis.render(visSyntax);
51+
</script>
52+
</body>
53+
</html>`;
54+
}
55+
56+
/**
57+
* Generate GPT-Vis syntax via LLM and wrap it into a standalone HTML file
58+
*
59+
* The LLM is instructed to return only pure GPT-Vis syntax (no HTML wrapper).
60+
* The HTML is then generated locally from a fixed template, ensuring:
61+
* 1. Syntax extraction is 100% reliable (the entire LLM output IS the syntax)
62+
* 2. The HTML is always well-formed regardless of LLM output variance
63+
* 3. Users without @antv/gpt-vis installed can still use the HTML directly
1464
*/
1565
export async function generateVisualizationHTML(
1666
chartType: ChartType,
@@ -23,7 +73,7 @@ export async function generateVisualizationHTML(
2373
baseURL: llmConfig.baseURL,
2474
});
2575

26-
const prompt = `你是一个 GPT-Vis 可视化专家。根据图表类型、数据和用户查询,生成一个完整的可独立运行的 HTML 文件,其中包含正确的 GPT-Vis 语法。
76+
const prompt = `你是一个 GPT-Vis 可视化专家。根据图表类型、数据和用户查询,生成对应的 GPT-Vis 语法。
2777
2878
## 任务信息
2979
@@ -382,81 +432,29 @@ title "2024 Q1 Sales Report"
382432
383433
## 要求
384434
385-
1. 根据上述提供的图表类型、数据和用户查询,生成对应的 GPT-Vis 语法
386-
2. 将生成的 GPT-Vis 语法嵌入到一个完整的 HTML 文件中
387-
3. HTML 文件必须包含:
388-
- 完整的 HTML 结构 (<!DOCTYPE html>, <html>, <head>, <body>)
389-
- 引入 GPT-Vis 的 UMD 版本:https://unpkg.com/@antv/gpt-vis/dist/umd/index.min.js
390-
- 使用 GPTVis.GPTVis 类初始化并渲染图表
391-
- container 参数必须使用 CSS 选择器格式(如 '#container'),不能省略 # 前缀
392-
- 添加简洁美观的样式
393-
4. GPT-Vis 语法要求:
394-
- 数据字段映射必须正确,字段名和值之间用空格分隔,**不要用冒号**
395-
- data 必须在 title 等属性之前
396-
- 根据数据特征生成合适的标题
397-
- 确保语法格式完全符合 GPT-Vis 规范
398-
- 语法不要生成 width height,图表会按照容器自适应大小
399-
5. 只返回 HTML 代码,不要有任何其他说明文字
400-
6. 在 JavaScript 中使用模板字符串时,如果语法中包含反引号(\`)、美元符号($)或反斜杠(\\),需要用反斜杠转义
401-
402-
## HTML 模板参考
403-
404-
<!DOCTYPE html>
405-
<html>
406-
<head>
407-
<meta charset="UTF-8">
408-
<title>Data Visualization</title>
409-
<script src="https://unpkg.com/@antv/gpt-vis/dist/umd/index.min.js"></script>
410-
<style>
411-
html, body, #container {
412-
margin: 0;
413-
padding: 0;
414-
font-family: Arial, sans-serif;
415-
width: 100%;
416-
height: 100%;
417-
display: block;
418-
}
419-
</style>
420-
</head>
421-
<body>
422-
<div id="container"></div>
423-
<script>
424-
const gptVis = new GPTVis.GPTVis({
425-
container: '#container',
426-
});
427-
428-
const visSyntax = \`[这里放入根据数据生成的正确 GPT-Vis 语法]\`;
429-
430-
gptVis.render(visSyntax);
431-
</script>
432-
</body>
433-
</html>
434-
435-
请直接返回完整的 HTML 代码。`;
435+
1. 只生成 GPT-Vis 语法,不要生成 HTML 或任何其他代码
436+
2. 数据字段映射必须正确,字段名和值之间用空格分隔,**不要用冒号**
437+
3. data 必须在 title 等属性之前
438+
4. 根据数据特征生成合适的标题
439+
5. 确保语法格式完全符合上述 GPT-Vis 规范
440+
6. 不要生成 width height,图表会按照容器自适应大小
441+
7. 直接输出语法内容,不要用代码块包裹,不要有任何说明文字`;
436442

437443
const { text } = await generateText({
438444
model: openai(llmConfig.model) as any,
439445
prompt,
440446
});
441447

442-
// Remove markdown code block markers if present
443-
let html = text.replace(/[\s\S]*?```(?:html)?\s*/i, '').replace(/\s*```[\s\S]*$/, '').trim();
444-
445-
// Extract GPT-Vis syntax from the HTML
446-
// Look for the visSyntax variable assignment in template literal
447-
const syntaxMatch = html.match(/const visSyntax = `([^`]*)`/);
448-
let syntax = '';
449-
450-
if (syntaxMatch && syntaxMatch[1]) {
451-
syntax = syntaxMatch[1].trim();
452-
} else {
453-
// Fallback: try to find content between vis keyword and gptVis.render
454-
// Using [\s\S] instead of . with s flag for ES5 compatibility
455-
const fallbackMatch = html.match(/visSyntax\s*=\s*`([\s\S]*?)`[\s\S]*?gptVis\.render/);
456-
if (fallbackMatch && fallbackMatch[1]) {
457-
syntax = fallbackMatch[1].trim();
458-
}
459-
}
448+
// The LLM returns pure GPT-Vis syntax directly
449+
// Remove possible markdown code block wrappers (LLM may still wrap in ```...```)
450+
const syntax = text
451+
.trim()
452+
.replace(/^```(?:vis|yaml|text)?\s*\n?/i, '')
453+
.replace(/\n?\s*```$/, '')
454+
.trim();
455+
456+
// Wrap syntax into a standalone HTML file using a fixed template
457+
const html = wrapSyntaxInHTML(syntax);
460458

461459
return { syntax, html };
462460
}

0 commit comments

Comments
 (0)