#挑战100天100个GPTs

写在前面

  • 前段时间做了一个 mermaid 助手可以生成内容
  • 但是无法直接生成图片算是一个缺憾,于是想着给它升级一下

效果呈现

image.png
image.png

期望目标

  • 希望能帮我完成同时出图同时出图

GPTs 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
角色:Mermaid 专家

"Rain's Mermaid助手" 旨在理解用户需求,提供基于文本描述和截图转换为Mermaid语法的图表生成服务。此服务确保提供所有Mermaid格式的视觉参考,并为每种格式提供语法解释。

能力:
- 仅生成符合Mermaid语法的图表。
- 接受中文输入,严格遵守Mermaid语法规则。
- 为所有用户提供详细解释。
- 将用户截图转换为Mermaid语法,强调准确、全面和忠实原始内容的重要性。
- 自动生成Mermaid图表的图片并提供链接。

工作流程:
1. 根据用户描述生成最合适的Mermaid图表。
2. 以Markdown格式展示Mermaid图表。
3. 创建所有Mermaid样式的视觉参考图。
4. 将截图转换为Mermaid语法,确保完整、全面和忠实地代表原始内容。
5. 根据用户专业知识调整解释,确保初学者易于理解,高级用户获得深入信息。
6. 在回复中包含生成的Mermaid图表图片链接。 在交流过程中,要求全程使用中文。
7. 注意:每次生成Mermaid代码后,都要立即调用 "mermaidChart" action 来获取图片链接,并在回复中包含该链接。

在交流过程中,要求全程使用中文。

思考过程

image.png

为什么选择用插入 HTML 的方式

这里的思考出发点是为了方便后续调整样式。实际上直接用 nodejs 的 mermaid 包也是可以的,但是在托管服务器 上有文件大小的限制,这里尝试了几种方式都失败了。后来想到用这个相对比较简单的办法,通过在一个 html 中嵌入 markdown 的代码,渲染网页,再截屏。

为什么考虑用图片而不是 base64 编码

还是从最初的角度出发来思考。如何返回给 openai 一个结果,可能有 2 个方式,图片 url,base 64 编码的图片内容。如果是 url,图片还能存放一下,编码内容相对麻烦一些。思考再三,最后决定采用 url 的方式。还有个原因对 base64 编码没信心,放弃了。

为什么要传到网盘去?

这里考虑是 openai 访问我自己的服务器负担比较重,本来就是一个免费的托管服务器,加图片麻烦,后来选择了一个免费的网盘先用着了。

给我们看看代码?

有点乱,如果想完整代码可以加微信。下面是部分关键代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

async function generateMermaidImage(mermaidCode) {
const cacheKey = `mermaid_${Buffer.from(mermaidCode).toString('base64')}`;
const cachedImage = cache.get(cacheKey);
if (cachedImage) {
return cachedImage;
}

let browser;
try {
browser = await launchBrowser();
const page = await browser.newPage();

await page.setViewport({
width: FIXED_WIDTH,
height: 1000, // Set an initial height, it will be adjusted later
deviceScaleFactor: 2,
});

// Read the HTML template file
const htmlTemplate = await fs.readFile(path.join(process.cwd(), 'public', 'mermaid-template.html'), 'utf-8');

// Replace placeholder with actual Mermaid code
const html = htmlTemplate.replace('{{MERMAID_CODE}}', mermaidCode);

// Set the page content
await page.setContent(html, { waitUntil: 'networkidle0' });

// Wait for Mermaid to render
try {
// Wait for Mermaid to render with extended timeout111
await page.waitForSelector('#mermaid-container svg', { timeout: SELECTOR_TIMEOUT1 });
} catch (error) {
throw new Error('Mermaid rendering timed out');
}

// Adjust the page height to fit the content
const height = await page.evaluate(() => {
const svg = document.querySelector('#mermaid-container svg');
return svg ? svg.getBoundingClientRect().height : 0;
});

if (height === 0) {
console.error('Failed to get SVG height');
throw new Error('Failed to render Mermaid diagram');
}

await page.setViewport({
width: FIXED_WIDTH,
height: Math.ceil(height),
deviceScaleFactor: 2,
});

const element = await page.$('#mermaid-container');
if (!element) {
throw new Error('Mermaid container not found');
}
const screenshot = await element.screenshot({
type: 'webp',
omitBackground: true
});

const optimizedImage = await sharp(screenshot)
.png({ quality: 80, compressionLevel: 9 })
.toBuffer();

const optimizedBase64 = optimizedImage.toString('base64');
cache.set(cacheKey, optimizedBase64);

return optimizedBase64;
} catch (error) {
console.error('Error in generateMermaidImage:', error);
throw error;
} finally {
if (browser) {
await browser.close();
console.log('Browser closed');
}
}
}