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
59import { generateText } from 'ai' ;
@@ -8,9 +12,55 @@ import { createOpenAI } from '@ai-sdk/openai';
812import 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 ( / < \/ s c r i p t > / 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 */
1565export 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 ] * ?` ` ` (?: h t m l ) ? \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 ( / c o n s t v i s S y n t a x = ` ( [ ^ ` ] * ) ` / ) ;
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 ( / v i s S y n t a x \s * = \s * ` ( [ \s \S ] * ?) ` [ \s \S ] * ?g p t V i s \. r e n d e r / ) ;
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 ( / ^ ` ` ` (?: v i s | y a m l | t e x t ) ? \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