关于AI前端项目该使用的几种前端技术

94 阅读6分钟

关于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项目前端是没有太多工作的。除了业务代码外,就对接大模型接口有点点技术含量,其他的就是业务代码,样式交互变化。哎,来这家公司感觉也学不到什么东西,还没有我上家公司学习的地方多。大概是废了。只能在掘金看看掘金老哥们的美好生活了。