探索ChatGPT背后的前端黑科技
由于图片和格式解析问题,可前往 阅读原文
在人工智能与互联网技术飞速发展的今天,像ChatGPT这样的智能对话系统已经成为科技领域的焦点。它不仅能够进行自然流畅的对话,还能以多种格式展示内容,为用户带来高效且丰富的交互体验。然而,这些令人惊叹的功能背后,离不开前端技术的支持与实现
本文将深入探索ChatGPT背后的前端黑科技,希望能为开发者提供有价值的参考,帮助他们在实际项目中更高效地实现类似的功能
以下内容仅是本人的一次思考,如有更好方案的可在评论区留言
:::warning 小贴士
文章中涉及到的示例代码你都可以从 这里查看 ,若对你有用还望点赞支持
:::
单页面应用(SPA)
单页面应用是指在整个使用过程中,只有一个HTML页面被加载。所有的导航和交互操作都是在前端通过JavaScript完成,无需重新加载整个页面。这使得用户可以在同一个页面内无缝浏览不同的内容
虽然在开发过程中可能会遇到一些挑战(比如SEO优化),但SPA凭借其优势已成为现代Web开发的主流趋势。相信大家都对React/Vue/Angular/Svelte
等都不陌生,就不多做介绍了
实时通讯
实时通讯的总要性以及使用场景就不多说了,来看下常用的几种通讯技术
Server-Sent Events(SSE)
如果你仔细查看了ChatGPT的对话请求过程就会看到,服务器在不断的推送数据
这里用到的就是SSE技术,基于HTTP协议单向推送技术。它使得服务器能够在数据生成时实时推送给客户端,而无需客户端频繁轮询服务器。这非常适合需要实时更新的应用场景,如新闻推送、股票价格监控和日志跟踪
使用SSE注意事项
- 在响应头中添加
Content-Type: text/event-stream
,以通知客户端这是一个SSE连接 - 返回的数据要包含
data: xxx
,并以data: xxx\n\n
双换行符格式,这样客户端才能正确解析,可以指定id、type
等数据;默认情况下事件都是message
,也可以自定义事件 - 建立好后的sse链接就会不断发数据,直到
EventSource
调用close事件
- 客户端使用EventSource来接收数据
编写客户端页面代码:
const eventSource = new EventSource('http://localhost:3000/sse');eventSource.addEventListener("message", (e) => {console.log(e);if (e.data >> 0 > 4) eventSource.close(); // 客户端根据数据判断然后主动断开连接
});// 服务器如果判断数据发完了可以发送type为done的数据包,客户端就可以监听到此类事件
eventSource.addEventListener("done", (e) => {});eventSource.addEventListener('error', (e) => {console.log('Connection failed:', e);
});
使用express搭建服务器:
app.get("/sse", (req, res) => {// 设置响应头为SSE格式(必填的)res.setHeader("Content-Type", "text/event-stream");res.setHeader("Cache-Control", "no-cache");res.setHeader("Connection", "keep-alive");let count = 0; let timer;const sendEvent = () => {res.write(`id: ${count}\n\n`);res.write(`data: ${count}\n\n`);count++;timer = setTimeout(sendEvent, 1000);};sendEvent();req.on("close", () => {console.log("Client disconnected");clearTimeout(timer);});
});
推荐使用Nest框架搭建sse服务器,更简单强大:
@Controller("/api/sse")
export class SseController {private subject = new Subject<void>();@Sse("msgs")sse(@Query("msg") msg: string): Observable<MessageEvent> {const eventStream = Array.from({ length: 5 }, (v, i) => i).map((message, index) =>of({data: `from sse events: ${index}, 你发送了 【${msg}】`,id: randomUUID(),retry: 0,type: index === 4 ? "done" : "message",} as MessageEvent).pipe(delay(1000)),takeUntil(this.subject),);return concat(...eventStream);}// 主动停止@Post("/stop")stop() {return this.subject.next();}
}
Websocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许客户端和服务端之间建立持久连接,实现双向实时数据传输
相比sse支持双向通讯、实现更加复杂,适合在线客服、实时消息通知、在线文档编辑、白板共享等等,不过也有在ChatGPT类似应用中用到的
客户端页面代码示例:
// 连接到 WebSocket 服务器
const ws = new WebSocket('ws://localhost:8080');ws.onopen = () => { console.log('Connected to server'); };
ws.onmessage = (event) => { console.log(`Received message: ${event.data}`); };
ws.onclose = () => { console.log('Connection closed'); };
node创建Websocket服务器:
const WebSocket = require('ws');// 创建一个 HTTP 服务器(可选)
const http = require('http');
const server = http.createServer((req, res) => {res.writeHead(404, {'Content-Type': 'text/plain'});res.end('Not Found');
});// 使用 ws 库创建 WebSocket 服务器
const wss = new WebSocket.Server({ server });wss.on('connection', (ws) => {console.log('New client connected');// 发送消息到客户端ws.send('Hello from server!');// 接收客户端消息ws.on('message', (message) => {console.log(`Received message: ${message}`);// 回复客户端ws.send(`Echo back: ${message}`);});// 处理客户端断开连接ws.on('close', () => {console.log('Client disconnected');});
});
Chunked Transfer
HTTP 分块传输(HTTP Chunked Transfer)是一种将大数据量分解为多个较小的数据块进行传输的技术。每个数据块被称为“chunk”,这些chunk独立地通过网络传输,并在接收端重新组装成原始数据
这种方法特别适用于需要逐步处理数据的场景,例如视频流媒体和大文件下载,使用看起来和sse很像,也是可以用在这种gpt这种场景的
使用express编写示例:
app.use("/chunked", (req, res) => {// 必须要设置Transfer-Encoding头信息res.setHeader("Transfer-Encoding", "chunked");let timer, i = 1;// 1s返回一次 总共返回9次timer = setInterval(() => {res.write(`${i}`);if (i >= 10) {clearInterval(timer);res.end();}i++;}, 1000);
});
现在请求这个接口看下效果:
模拟打印
是不是看到ChatGPT的不断打印文字的效果很有感觉,做到这一点也并不难,下面是一个简单实现的🌰
// 模拟 ChatGPT 打印文字效果
function simulateChatGPTTyping(outputElement, text) {let index = 0;outputElement.innerHTML = '';function typeText() {if (index < text.length * 2) {// 随机延迟,模拟思考时间setTimeout(() => {// 随机选择一个字符来打字const randomIndex = Math.floor(Math.random() * text.length);const char = text[randomIndex];// 更新输出内容outputElement.innerHTML += char;// 滚动到最新位置outputElement.scrollTop = outputElement.scrollHeight;index++;typeText();}, 10 + Math.random() * 200); // 延迟范围:100-300ms} else {// 打印完成,添加换行符setTimeout(() => {outputElement.innerHTML += '<br>';}, 500);}}typeText();
}// 示例文本
const text = `我是人工智能助手ChatGPT。我可以帮助你回答问题、提供信息和进行对话。你可以问我任何你感兴趣的问题,我会尽力为你提供详细的解答。例如,你可以问我关于科技、历史、文化、科学、数学、编程等方面的知识。我还可以帮助你完成一些任务,比如编写代码片段、解释技术概念或者提供建议。请告诉我你需要什么帮助!
`;
// 初始化输出容器
const output = document.getElementById('output');
// 开始模拟打字效果
simulateChatGPTTyping(output, text);
模块化组件
ChatGPT能生成很多种类的结果,包括:文字、列表、表格、代码等等,那么前端如何对应展示呢❓这里只讲下实现思路
其中最简单的一种方案就是把后端返回的markdown格式的数据直接喂给页面上的markdown组件,只需要丰富markdown组件功能就行,对格式的解析直接交给其内部
这种方式比较简单,容易大众化无法做好定制功能,要实现界面的定制功能,就要设计到对数据的解析了;需要自研如何解析匹配数据,然后根据不同的类型数据调用不同的组件,这样就可以满足定制功能了,相对来说比较复杂点
实现定制功能需要做很多种的组件,那么就涉及到了前端组件库的设计了,比如:按需加载等等
语音技术
随着互联网技术的发展,页面上也出现了各种各样的富媒体内容,如:音频、视频等等,工作压力下可能有很多读者无法持续关注这方便内容,下面就来看下ChatGPT用到的功能
文字朗读
文字朗读看上去很高大上,很多都支持这个功能,如:攻粽号朗读,ChatGPT也支持
作者博客网站也是加上了文本朗读的功能
实现它非常简单,浏览器提供了speechSynthesis标准来实现文字朗读的功能,基本各大主流浏览器都支持
来简单朗读一段文本:
const synth = window.speechSynthesis;
const text = "我是一段文本,请朗读";
const utterance = new SpeechSynthesisUtterance(text);const voices = synth.getVoices();
const chineseVoice = voices.find((voice) => voice.lang === "zh-CN");
if (chineseVoice) {utterance.voice = chineseVoice; // 设置语言
}
// 朗读
synth.speak(utterance);
除此之外还支持调整音色、声音以及监听各种朗读事件等等
对话
对话相对文本朗读来说更复杂一点,涉及到录音、播放声音等逻辑,整体流程就是录音、识别、播放声音。先来看如何录音
H5提供了MediaRecorder标准API来进行媒体的轻松录音,需要通过调用 MediaRecorder()
构造方法进行实例化。使用之前需要调用MediaDevices.getUserMedia()给予使用媒体输入的许可权限,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道,包括音频、视频
let mediaRecorder: MediaRecorder;
const recordDataChunks: Blob[] = [];function startRecording() {navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {try {mediaRecorder = new MediaRecorder(stream);mediaRecorder.ondataavailable = e => {recordDataChunks.push(e.data);};mediaRecorder.start(1000);state.isRecording = true;mediaRecorder.addEventListener("stop", () => {console.log("录制完成");const blob = new Blob(recordDataChunks, {type: "audio/ogg; codecs=opus",});const url = URL.createObjectURL(blob);console.log(url);});} catch (error) {state.isRecording = false;}});
}
拿到用户的声音后就需要开始识别,然后思考了,最后将内容播放出来就可以了
语音识别
语音识别即音频转文字的功能,ChatGPT在说话时也会将语音实时转化为文字
这个功能使用js还是比较简单的
function transferAudioToText() {// 检查浏览器是否支持 Web Speech APIif (!("webkitSpeechRecognition" in window)) {message.error("你的浏览器不支持语音识别");return;}const recognition = new webkitSpeechRecognition();recognition.continuous = false;recognition.lang = "zh-CN";recognition.interimResults = false;recognition.start();recognition.onresult = (e: any) => {console.log(e.results[0][0].transcript);};
}
当开始识别时就会调用麦克风讲话,然后实时识别语音
音频可视化
ChatGPT在说话时由用图案动画反馈说话状态
H5提供了 AudioContext 接口提供了音频节点的创建和音频处理或解码的执行操作,使用起来也不是很麻烦,但相对前面2者稍微复杂点
其主要逻辑就是拿到音频后通过audiocontext获取音频节点,最后通过canvas画出想要的图案即可
来看下怎么做
// 创建音频上下文
const audioCtx = new AudioContext();
const audioSource = audioCtx.createMediaStreamSource(mediaStream); // 这里把音频媒体流传入
const analyser = audioCtx.createAnalyser(); // 创建音频分析器
audioSource.connect(analyser); // 将音频连接到分析器
analyser.fftSize = 2048;
audioDataBuffer = new Uint8Array(analyser.frequencyBinCount);// 最后将其渲染到canvas上就可以了
const ctx = canvasRef.value!.getContext("2d")!;
const canvasW = (canvasRef.value!.width = canvasRef.value!.parentElement!.offsetWidth - 48);
const canvasH = (canvasRef.value!.height = 120);
ctx.fillStyle = "#4646fc";function drawAudioTrackBarGraphic() {ctx.clearRect(0, 0, canvasW, canvasH);// 然后通过analyser拿到节点信息analyser!.getByteFrequencyData(audioDataBuffer!);const barLen = audioDataBuffer!.length / 10;const barWidth = canvasW / barLen;for (let i = 0; i < barLen; i++) {const data = audioDataBuffer![i];const barHeight = (data / 255) * canvasH;const x1 = i * barWidth;const y = canvasH - barHeight;ctx.fillRect(x1, y, barWidth, barHeight);}rFId = requestAnimationFrame(drawAudioTrackBarGraphic);
}requestAnimationFrame(drawAudioTrackBarGraphic);
好了,到这里基本上实现了录音、语音转文字、讲话动态反馈效果:
富文本与光标
可以看到ChatGPT的对话框不是简单的textarea
标签,而是使用了富文本技术
富文本技术随着技术的进步也发展了多个阶段的产物
- 以
document.execCommand
命令的最初的简单富文本 - 以
contenteditable
标签的可编辑dom,开发根据内容自行实现格式展示方式 - 以
canvas
为主要的自研光标系统,代表为google docs
而ChatGPT这里简单的对话框则使用了contenteditable=true
标签,然后通过内容根据浏览器光标API getSelection 来实现内容的富文本化,其实现还是有一点点复杂的,关键还是内容解析和标签处理
安全防范
好的产品和应用一定少不了安全方面的防范,对于web 应用基本上和我之前的文章 HTTP协议及安全防范 中讲的安全知识大差不差
来看看ChatGPT怎么做的❓
首先就是CSP
防范XSS攻击
还有禁止客户端的 MIME 类型嗅探行为,通知浏览器应该只通过 HTTPS 访问该站点等等
总之,万变不离其宗。读者可以翻阅往期文章
性能优化
性能方面的手段也值得读者学习
缓存
常见的HTTP缓存,以及indexDB数据库使用等等,这里不再讲了
Server Push
利用HTTP2的服务器主动推送功能,加快资源的加载,这在HTTP文中也讲过了
TailwindCSS/UnoCSS
Tailwind CSS和UnoCSS都是用于快速构建用户界面的CSS工具,还有读者不了解的需要抓紧看看了
总结
可以看出一个ChatGPT聊天应用虽然看起来非常简单,但背后的逻辑思维非常复杂,涉及到很多复杂的技术,没有一个团队是很难做好的
由于图片和格式解析问题,可前往 阅读原文
相关文章:

探索ChatGPT背后的前端黑科技
由于图片和格式解析问题,可前往 阅读原文 在人工智能与互联网技术飞速发展的今天,像ChatGPT这样的智能对话系统已经成为科技领域的焦点。它不仅能够进行自然流畅的对话,还能以多种格式展示内容,为用户带来高效且丰富的交互体验。然…...

Agents Go Deep 智能体深入探索
Agents Go Deep 智能体深入探索 核心事件 OpenAI发布了一款先进的智能体“深度研究”,它能借助网络搜索和推理生成研究报告。 最新进展 功能特性:该智能体依据数百个在线资源生成详细报告,目前仅支持文本输出,不过很快会增加对图…...
DeepSeek全生态接入指南:官方通道+三大云平台
DeepSeek全生态接入指南:官方通道三大云平台 一、官方资源入口 1.1 核心交互平台 🖥️ DeepSeek官网: https://chat.deepseek.com/ (体验最新对话模型能力) 二、客户端工具 OllamaChatboxCherry StudioAnythingLLM …...

c++TinML转html
cTinML转html 前言解析解释转译html类定义开头html 结果这是最终效果(部分):  前言 在python.tkinter设计标记语言(转译2-html)中提到了将Ti…...
STM32硬件SPI函数解析与示例
1. SPI 简介 SPI(Serial Peripheral Interface)即串行外设接口,是一种高速、全双工、同步的通信总线,常用于微控制器与各种外设(如传感器、存储器等)之间的通信。STM32 系列微控制器提供了多个 SPI 接口&a…...

滤波器:卡尔曼滤波
卡尔曼滤波(Kalman Filter)是一种高效的递归算法,主要用于动态系统的状态估计。它通过结合系统模型和噪声干扰的观测数据,实现对系统状态的最优估计(在最小均方误差意义下)。以下从原理、使用场景和特点三个…...

深度学习框架探秘|TensorFlow vs PyTorch:AI 框架的巅峰对决
在深度学习框架中,TensorFlow 和 PyTorch 无疑是两大明星框架。前面两篇文章我们分别介绍了 TensorFlow(点击查看) 和 PyTorch(点击查看)。它们引领着 AI 开发的潮流,吸引着无数开发者投身其中。但这两大框…...

Windows环境管理多个node版本
前言 在实际工作中,如果我们基于Windows系统开发,同时需要维护老项目,又要开发新项目,且不同项目依赖的node版本又不同时,那么就需要根据项目切换不同的版本。本文使用Node Version Manager(nvm࿰…...
opencascade 源码学习BRepBuilderAPI-BRepBuilderAPI
BRepBuilderAPI BRepBuilderAPI 是一个用于构建和操作 BRep(边界表示法,Boundary Representation)拓扑数据结构的工具类。它提供了高级接口,用于创建几何形状(如顶点、边、面、实体等)以及进行扫掠&#x…...
Vue 2 + Webpack 项目中集成 ESLint 和 Prettier
在 Vue 2 Webpack 项目中集成 ESLint 和 Prettier 可以帮助你规范代码风格并自动格式化代码。以下是详细的步骤: 1. 安装 ESLint 和 Prettier 相关依赖 在项目根目录下运行以下命令,安装 ESLint、Prettier 和相关插件: npm install --save…...

Renesas RH850 EEL库的优点
文章目录 1. 磨损均衡(Wear Leveling)2. 数据抽象与易用性3. 后台维护与自动刷新4. 多优先级操作5. ECC 错误处理与数据完整性EEL 与 FDL 的协作机制1. 分层架构2. 存储池划分3. 协作流程4. 同步与互斥5. 性能优化实际应用场景示例场景:车辆里程存储总结1. 磨损均衡(Wear L…...
torch导出ONNX模型报错:OnnxExporterError: Module onnx is not installed
问题: 使用torch 导出模型为onnx文件时报错:torch.onnx.OnnxExporterError: Module onnx is not installed! 环境: 操作系统 Win10 python运行环境 Anacoda3 torch 2.6.0 torchvision …...

LabVIEW 用户界面设计基础原则
在设计LabVIEW VI的用户界面时,前面板的外观和布局至关重要。良好的设计不仅提升用户体验,还能提升界面的易用性和可操作性。以下是设计用户界面时的一些关键要点: 1. 前面板设计原则 交互性:组合相关的输入控件和显示控件&#x…...

使用Python爬虫实时监控行业新闻案例
目录 背景环境准备请求网页数据解析网页数据定时任务综合代码使用代理IP提升稳定性运行截图与完整代码总结 在互联网时代,新闻的实时性和时效性变得尤为重要。很多行业、技术、商业等领域的新闻都可以为公司或者个人发展提供有价值的信息。如果你有一项需求是要实时…...
qt QTextEdit用法总结
1. 基本介绍 QTextEdit 是 Qt 中用于显示和编辑富文本(支持 HTML 子集)和纯文本的控件。 支持文本格式(字体、颜色、对齐)、列表、表格、图片插入等富文本功能。 底层通过 QTextDocument 管理内容,提供强大的文本处理…...

《open3d qt 网格采样成点云》
open3d qt 网格采样成点云 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionMeshUniformSample_triggered();//均匀采样 void MainWindow::...

企业数据安全:切实有效的数据安全保障措施分享:
确保企业数据安全是一项持续不懈的任务,鉴于技术的飞速发展,网络攻击者持续探索新型手段以窃取敏感信息并谋取利益。若企业欲避免成为数据泄露的下一个牺牲品,就必须始终保持警觉,预先规划,以不变应万变。为了帮助企业…...

rocketmq-netty通信设计-request和response
1、NettyRemotingServer启动分析 org.apache.rocketmq.remoting.netty.NettyRemotingServer#start public void start() {this.defaultEventExecutorGroup new DefaultEventExecutorGroup(nettyServerConfig.getServerWorkerThreads(),new ThreadFactory() {private AtomicI…...

DeepSeek 助力 Vue 开发:打造丝滑的卡片(Card)
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
计算机组成原理—— 总线系统(十一)
在追求梦想的旅途中,我们常常会遇到崎岖的道路和难以预料的风暴。然而,正是这些挑战塑造了我们的坚韧和毅力,使我们能够超越自我,触及那些看似遥不可及的目标。不要因为一时的困境而气馁,也不要因为他人的质疑而动摇自…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...