前端转做AI程序员第一步——开发一个AI流式对话Demo

前端转做AI程序员第一步------开发一个AI流式对话Demo

1、首先新建一个叫Demo的文件夹,文件夹下设有两个文件,server.js(服务端)和index.html(前端)

2、server.js用express框架做服务器,大模型用千问的,模型的key去网站上申请https://bailian.console.aliyun.com/cn-beijing/model/settings/api-key

3、server.js代码参考,定制一个和大模型交互的接口------"流式对话接口"

const 复制代码
const cors = require('cors');

const app = express();
app.use(cors());
app.use(express.json());

// ========= 这里替换成你自己的 DashScope api key =========
const API_KEY = "sk-XXXXXXXXXXXXXXXXXX";
// ======================================================

const DASHSCOPE_URL =
  'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';

// 流式对话接口
app.post('/api/chat-stream', async (req, res) => {
  const { messages } = req.body;

  // SSE响应头
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  try {
    // 官方 dashscope npm 包是纯 ESM,且没有 chatStream;这里直接调官方流式 HTTP 接口
    const response = await fetch(DASHSCOPE_URL, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
        'X-DashScope-SSE': 'enable',
      },
      body: JSON.stringify({
        model: 'qwen-turbo',
        input: { messages },
        parameters: {
          result_format: 'message',
          incremental_output: true,
        },
      }),
    });

    if (!response.ok) {
      const errText = await response.text();
      res.write(
        `data: ${JSON.stringify({ error: errText || response.statusText, done: true })}\n\n`
      );
      return res.end();
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let buffer = '';

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split('\n');
      buffer = lines.pop() || '';

      for (const line of lines) {
        if (!line.startsWith('data:')) continue;
        const raw = line.slice(5).trim();
        if (!raw || raw === '[DONE]') continue;

        try {
          const json = JSON.parse(raw);
          const content = json.output?.choices?.[0]?.message?.content;
          if (content) {
            res.write(`data: ${JSON.stringify({ content })}\n\n`);
          }
          if (json.code) {
            res.write(
              `data: ${JSON.stringify({ error: json.message || json.code, done: true })}\n\n`
            );
            return res.end();
          }
        } catch {
          // 忽略无法解析的片段
        }
      }
    }

    // 流结束标记
    res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
    res.end();
  } catch (err) {
    res.write(`data: ${JSON.stringify({ error: err.message, done: true })}\n\n`);
    res.end();
  }
});

const port = 3001;
app.listen(port, () => {
  console.log(`✅ 后端服务启动成功`);
  console.log(`👉 访问后端地址:http://localhost:${port}`);
});

4、index.html参考代码,构建一个对话框

js 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>AI流式对话Demo</title>
  <style>
    #chat-box {
      width: 650px;
      height: 420px;
      border: 1px solid #ccc;
      padding: 12px;
      overflow-y: auto;
      margin-bottom: 10px;
      border-radius: 6px;
    }
    .msg {
      margin: 8px 0;
      line-height: 1.6;
    }
    .user {
      color: #2563eb;
    }
    .ai {
      color: #16a34a;
      white-space: pre-wrap;
    }
    textarea {
      width: 650px;
      height: 90px;
      padding: 8px;
      box-sizing: border-box;
      border-radius: 4px;
      border: 1px solid #ccc;
    }
    button {
      margin-top:8px;
      padding:6px 16px;
      cursor:pointer;
    }
  </style>
</head>
<body>
  <div id="chat-box"></div>
  <textarea id="input" placeholder="请输入你的问题..."></textarea><br>
  <button onclick="sendChat()">发送</button>
  <button onclick="stopGenerate()">停止生成</button>
  <button onclick="clearChat()">清空对话</button>

<script>
const chatBox = document.getElementById('chat-box');
const inputEl = document.getElementById('input');

// 对话上下文
let messages = [];
let reader = null; // 流控制器,用于停止

// 发送消息
async function sendChat() {
  const userText = inputEl.value.trim();
  if (!userText) return;

  // 追加用户消息
  messages.push({ role: 'user', content: userText });
  addMsg("user", userText);
  inputEl.value = '';

  // AI消息占位
  let aiDom = addMsg("ai", "");
  let aiText = "";

  const resp = await fetch("http://localhost:3001/api/chat-stream", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ messages })
  });

  reader = resp.body.getReader();
  const decoder = new TextDecoder("utf-8");

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const raw = decoder.decode(value);
    const events = raw.split("\n\n").filter(Boolean);
    for (const evt of events) {
      if (evt.startsWith("data: ")) {
        const data = JSON.parse(evt.replace("data: ", ""));
        if (data.done) {
          messages.push({ role: 'assistant', content: aiText });
          reader = null;
          break;
        }
        if(data.error){
          aiDom.innerText += "\n【错误】" + data.error;
          reader = null;
          return;
        }
        aiText += data.content;
        aiDom.innerText = aiText;
        chatBox.scrollTop = chatBox.scrollHeight;
      }
    }
  }
}

// 停止生成
function stopGenerate(){
  if(reader){
    reader.cancel();
    reader = null;
  }
}

// 清空全部对话
function clearChat(){
  stopGenerate();
  messages = [];
  chatBox.innerHTML = '';
}

// 渲染单条消息
function addMsg(type, text) {
  const div = document.createElement('div');
  div.className = `msg ${type}`;
  div.innerText = text;
  chatBox.appendChild(div);
  return div;
}
</script>
</body>
</html>

5.安装依赖包

diff 复制代码
npm init -y
npm install express dashscope cors

-   express:web 服务
-   dashscope:通义官方 SDK,自带流式返回
-   cors:解决跨域

6、启动web服务 node server.js 可以看到服务端口3001 服务启动 http://localhost:3001

7、双击index.html打开页面可以看到对话框,输入文字尝试调用

8、入门完成

相关推荐
猪猪拆迁队6 小时前
跨电脑复制共享-WebRTC 打洞踩坑
前端·后端·go
Amos_Web6 小时前
Rspack 源码解析(十八):Tree Shaking 如何用 SideEffects 重写模块连接
前端·rust·前端框架
百度一下吧6 小时前
前端包管理工具和使用手册
前端
anxiao_m7 小时前
水利数字孪生怎么选?主流可视化渲染平台深度横向测评
大数据·前端·人工智能·图形渲染·云渲染
kill5227 小时前
useRef
前端·javascript·react.js
蓝悦无人机7 小时前
从“摸形状“到“点到线面的距离“——聊聊 SLAM 前端(激光雷达篇)
前端·loam·数据关联·激光slam·点云特征提取·点到线面距离·icp/ndt
flash俊杰7 小时前
工程约束怎么不拖后腿
前端
霍营_托尼8 小时前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
溪语流沙8 小时前
【Web全栈进阶】FastAPI工程化:APIRouter拆分 + 配置 + 依赖注入
java·前端·fastapi
Shirley~~8 小时前
Three.js的基础概念
前端·3d