关于AI前端项目该使用的几种前端技术
最近入职了一家做AI的公司,看了一下他们的项目代码后我总结一下关于web前端工程师应该在AI项目中扮演或者应该掌握哪些知识技能
1、获取文本流式数据方法
不同的公司使用的获取流式数据的技术也不一样,我今天就讲一下我呆的两家公司使用的什么方法来对接流式数据
Fetch
第一种方法很多掘友们大概都不知道Fetch这个接口也能用来获取流式数据,包括我这家公司的面试官,当我说到这个的时候他还不相信,哈哈哈。
async function getStreamByFetch(){
let res = await fetch('/接口地址');
// 关键在这里 getReader()方法
const reader = res.body.getReader();
// 在这个方法里面有两参数 done: 表示流是否结束 另一个就是数据了
const { done, value } = await reader.read();
// 可以使用一个while循环持续获取数据块,知道done为true
let result = ‘’;
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 将 Uint8Array 转换为字符串
result += decoder.decode(value, { stream: true });
console.log('Received chunk:', decoder.decode(value)); // 实时处理每个数据块
// 可以在这里更新 UI 或执行其他操作
}
}
SSE
第二种方法就是大家熟知的SSE,这个没什么好说的,直接上代码(AI生成的,哈哈哈)
const eventSource = new EventSource('接口地址'); // 替换为你的 SSE 服务器地址
// 监听 'message' 事件(默认事件)
// 当服务器发送没有指定事件类型的消息时,会触发此事件
eventSource.onmessage = function(event) {
console.log('收到消息:', event.data);
// event.data 包含服务器发送的数据
// event.lastEventId 包含最后一个事件的 ID (如果服务器有发送)
// 可以在这里更新 UI 或执行其他操作
};
// 监听自定义事件
// 如果服务器通过 'event: myCustomEvent' 指定了事件类型,则触发此事件
eventSource.addEventListener('myCustomEvent', function(event) {
console.log('收到自定义事件 (myCustomEvent):', event.data);
// 处理特定事件类型的数据
});
// 监听错误事件
eventSource.onerror = function(error) {
console.error('EventSource 发生错误:', error);
// 可以根据错误类型进行重试或通知用户
if (eventSource.readyState === EventSource.CLOSED) {
console.log('连接已关闭,可能需要手动重连');
}
};
// 监听连接打开事件
eventSource.onopen = function() {
console.log('SSE 连接已打开!');
};
// 手动关闭连接
function closeSSEConnection() {
eventSource.close();
console.log('SSE 连接已关闭。');
}
// 示例:5秒后关闭连接(在实际应用中,你可能基于用户操作或特定条件关闭)
// setTimeout(closeSSEConnection, 5000);
WS
第三种使用websocket实现,我现在的公司就是用的这个,这个也没什么很难的地方,只是相比于前两个大一些,资源损耗高一些
// 创建一个新的 WebSocket 实例
// 使用 'ws://' 或 'wss://' (加密连接) 协议,指向你的 WebSocket 服务器地址
const ws = new WebSocket('ws://localhost:8080'); // 替换为你的 WebSocket 服务器地址
// 监听连接成功事件
ws.onopen = (event) => {
console.log('WebSocket 连接已建立!', event);
// 连接成功后,可以立即发送数据到服务器
ws.send('Hello Server! I am connected.');
};
// 监听接收到消息事件
// 每当服务器推送数据时,此事件就会触发
ws.onmessage = (event) => {
console.log('收到服务器消息:', event.data);
// event.data 包含服务器发送的数据
// 根据数据类型进行处理:
try {
const receivedData = JSON.parse(event.data);
console.log('解析后的数据:', receivedData);
// 如果数据是 JSON 格式,可以进一步处理
// 例如:更新 UI、添加到列表中等
} catch (e) {
// 如果不是 JSON,则作为普通文本处理
console.log('非 JSON 格式数据。');
}
};
// 监听连接关闭事件
ws.onclose = (event) => {
if (event.wasClean) {
console.log(`WebSocket 连接已正常关闭,代码: ${event.code}, 原因: ${event.reason}`);
} else {
// 例如,服务器进程被杀死或网络中断
console.error('WebSocket 连接异常中断!');
}
// 可以尝试重新连接逻辑
};
// 监听错误事件
ws.onerror = (error) => {
console.error('WebSocket 发生错误:', error);
};
// 发送数据到服务器的函数
function sendMessageToServer(message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
console.log('发送消息到服务器:', message);
} else {
console.warn('WebSocket 连接未打开,无法发送消息。');
}
}
// 示例:5秒后发送一条消息
// setTimeout(() => {
// sendMessageToServer('这是来自客户端的定时消息!');
// }, 5000);
// 手动关闭 WebSocket 连接
function closeWebSocketConnection() {
ws.close();
console.log('WebSocket 连接正在关闭...');
}
// 示例:10秒后关闭连接
// setTimeout(closeWebSocketConnection, 10000);
2、对接文本流式数据展示方法
第二个大方向就是后端返回数据格式,我做过两个ai的项目(对接过两个),一个是返回的普通格式(普通json格式没啥好说的,重点在于和后端商量好不同的信令类型啥的),一个是返回的markdown格式(当前公司)
markdown格式
markdown格式,我想经常发博客的掘友们都很熟悉了,对于我们这种不造轮子的人来说,直接上插件
marked
轻量级
import { marked } from 'marked';
const markdownText = '# Hello World\n**Bold** *Italic*';
const htmlContent = marked(markdownText);
// 渲染到页面
document.getElementById('preview').innerHTML = htmlContent;
markdown-it
功能更强大,支持插件扩展,比如 emoji、语法高亮等。
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
const htmlContent = md.render('# Hello World\n`code`');
document.getElementById('preview').innerHTML = htmlContent;
md-editor-v3
vue3使用 (我用的)
<template>
<MdEditor v-model="text" />
</template>
<script setup>
import { ref } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css'; // 样式必须引入
const text = ref('# Hello md-editor-v3');
</script>
等等。。。。。
3、文本展示框的样式抖动问题
有人可能不知道我说的啥意思,我开始也没懂这个bug给我提的什么意思,后面经过测试的一番指点就明白了。是因为我们使用的流式数据在展示过程中,是不断往下添加内容,添加内容后让滚动条不断滚动到底部。这样就导致回答内容框总是先下后上,不断抖动。而市面上的问答都没有这个问题,怎么解决的呢???又是一番冥思苦想,想不出来还是问ai好了。。。。
具体业务代码就不展示了,层级太深也不好看(主要是接手的其他人的代码,那叫一个混乱,我刚入行的时候都写的比他(她)好)
下面是一个撑开容器上层,底部固定不变的小demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>底部固定向上扩展</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
height: 80vh;
border: 2px solid #333;
overflow-y: auto;
display: flex;
flex-direction: column;
padding: 10px;
}
.spacer {
flex: 1; /* 自动填充剩余空间,把 .box 推到底部 */
}
.box {
width: 50%;
margin: 0 auto;
border: 1px solid black;
background: #f9f9f9;
padding: 10px;
}
.nested1, .nested2, .nested3 {
padding: 4px;
border-left: 3px solid #ccc;
margin-bottom: 4px;
}
h1 {
margin: 4px 0;
font-size: 16px;
}
</style>
</head>
<body>
<div class="container" id="container">
<div class="spacer"></div> <!-- 占位,让下面内容靠底 -->
<div class="box" id="box">
<!-- 内容将插入这里,可能是深层嵌套 -->
</div>
</div>
<script>
const box = document.getElementById('box');
const container = document.getElementById('container');
let i = 0;
setInterval(() => {
// 模拟嵌套结构
const nested1 = document.createElement('div');
nested1.className = 'nested1';
const nested2 = document.createElement('div');
nested2.className = 'nested2';
const nested3 = document.createElement('div');
nested3.className = 'nested3';
const el = document.createElement('h1');
el.innerText = 'hello world ' + i++;
nested3.appendChild(el);
nested2.appendChild(nested3);
nested1.appendChild(nested2);
box.appendChild(nested1);
// 滚动到底部
container.scrollTop = container.scrollHeight;
}, 500);
</script>
</body>
</html>
4、总结
暂时感觉能讲的就这些了,其实一个ai项目前端是没有太多工作的。除了业务代码外,就对接大模型接口有点点技术含量,其他的就是业务代码,样式交互变化。哎,来这家公司感觉也学不到什么东西,还没有我上家公司学习的地方多。大概是废了。只能在掘金看看掘金老哥们的美好生活了。