摘要
经过前面的四篇文章,我们已经实现了。文本对话,文生图的两个功能。其实我觉得这个系列这四篇文章就已经够用了。后续,我会继续提供一些额外模型能力的教程。这里读者如果有感兴趣的功能,也可以在评论区留下建议我们一起实现。
网页生成思路
那本篇文章主要来实现一下网页生成的能力,简单描述一下:就是用户输入一段query,然后给用户输出一个网页。那这个功能模型肯定不能支持,不能像文生图一样,给模型一个query,然后他给你部署好一个网页,然后通过url输出给你,这肯定是不行的。
那模型不支持,我们就无法做到了嘛,当然不。作为一个前端开发,我们肯定知道,一个网页代表的就是一个html。那模型有输出html的功能啊,那能不能经过我们的处理,实现这个网页生成的功能呢?
首先我们要给模型一个system,告诉模型你就是个只会写html的助手 。然后,当用户发来一个query之后,我们在query之后拼上请以html的格式输出。 哎,这样模型输出的不就是一个html了吗。
那在后端我们拿到了html的输出,只需要将其通过fs模型写到对应的目录下,再把html对应的目录返回给前端,这不就实现了吗。多么简单。
实现接口
现在,我们来到后端项目中,我们的chat接口要修改一下,我们要区分一下是走之前的对话链路,还是走生成网页的链路:
js
//routes/chat.js
router.post('/chat', function(req, res) {
res.set('Content-Type', 'text/event-stream;charset=utf-8');
res.set('Access-Control-Allow-Origin', '*');
res.set('X-Accel-Buffering', 'no');
res.set('Cache-Control', 'no-cache, no-transform');
const { message, sessionId, type } = req.body;
if (type === 'html') {
getHtml(message, sessionId, res);
return;
}
getChat(message, sessionId, res);
});
直接通过前端传过来的参数来判断,如果type === html,那么就走新的链路。
现在我们来实现一下getHtml方法,首先我们要修改一下模型的system。
js
const stream = await client.chat.completions.create({
messages: [
{ role: 'system', content: '你是一个擅长编写html的代码助手' },
...historyList,
{ role: 'user', content: message + ',请以html的格式输出 ,请确保生成的HTML代码是合法的,并且包含基本的结构和语义' },
],
model: 'gpt-3.5-turbo',
stream: true,
max_tokens: 5000, // 控制生成的 token 数
});
这样我们就限制了模型的输出一定是以html输出。但是模型输出html的同时,肯定也会输出一些文本,所以我们要把文本中的html部分截断出来,然后再通过fs写入对应的目录下:
js
//routes/chat.js
const startIndex = answer.indexOf('<html');
const endIndex = answer.lastIndexOf('</html>');
const html = answer.slice(startIndex, endIndex + 7);
const folderPath = path.join(__dirname, '../public/html');
const fileName = Date.now() + '.html';
const filePath = path.join(folderPath, fileName);
fs.writeFileSync(filePath, html, 'utf8');
最后,我们把文件所在的目录返回给前端,即可。这里肯定要定义一个新的类型事件:
js
//routes/chat.js
eventName = 'source';
res.write(`event: ${eventName}\n`);
res.write(`data: ${JSON.stringify({html: `http://localhost:3002/html/${fileName}`})}\n\n`)
后端部分的提交记录如下:
实现前端部分
node端的部分实现完了,我们来实现前端的部分。

首先我们在SkillList里增加一个技能,对应的类型也添加好。当我们选中这个类型之后,我们的sendData方法就要携带type参数,发送给后端,现在我们修改一下sendData方法:
js
//src/page/components/DialogInput/index.tsx
const callbackMap: CallBackMap = {
message: messageCallback,
major: majorCallback,
close: closeCallback,
}
if (skillStore.selectedSkill === 'html') {
data.type = 'html';
callbackMap.source = (source) => {
changeLastHtmlUrl(source.html);
};
}
connectSSE(url, data, callbackMap);
这里记得我们要对source的事件类型也进行处理一下。
最后修改一下视图层前端部分就实现了,是不是非常简单。
更多的提交内容可以查看:
最后我们来看一下效果吧:
