1.申请token:官网地址

https://www.aliyun.com/?spm=a2c4g.11186623.J_4VYgf18xNlTAyFFbOuOQe.d_logo.4e5a394eD2GYIuhttps://www.aliyun.com/?spm=a2c4g.11186623.J_4VYgf18xNlTAyFFbOuOQe.d_logo.4e5a394eD2GYIu登录之后选择大模型

选择token页面

创建token

选择账户等,我这里就一个就选了个主账户你有其他的也可以选其他的

2.创建nodejs,要求电脑上有安装nodejs,可以通过Window+R,输入cmd,之后输入输入node -v

2-1,在文件夹中创建文件config.js(用来配置 API Key)

// server/config.js
module.exports = {
  // 阿里通义千问 API Key(必填,先测这个)
  qwenApiKey: '',

}

自己创建的这个token放进去

2-2,config.js同级创建server.js

const express = require('express')
const axios = require('axios')
const config = require('./config')
const app = express()


app.use((req, res, next) => {
  // 精准允许前端源
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173')
  // 允许的方法
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
  // 允许的请求头
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
  // 预检请求直接返回 200
  if (req.method === 'OPTIONS') {
    return res.status(200).end()
  }
  next()
})

// 解析 JSON 请求体
app.use(express.json())

// 1. 通义千问 - 普通调用
app.post('/api/chat/qwen', async (req, res) => {
  try {
    const { messages } = req.body
    const response = await axios({
      url: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${config.qwenApiKey}`,
        'Content-Type': 'application/json'
      },
      data: {
        model: 'qwen-turbo',
        input: { messages },
        parameters: {
          result_format: 'message',
          stream: false,
          temperature: 0.7
        }
      }
    })
    res.json(response.data)
  } catch (err) {
    console.error('通义千问调用失败:', err.message)
    res.status(500).json({ 
      error: '调用失败', 
      detail: err.message 
    })
  }
})

// 2. 通义千问 - 流式调用(打字机效果)
app.post('/api/chat/qwen/stream', async (req, res) => {
  try {
    const { messages } = req.body;

    // 1. 向通义千问发起流式请求
    const response = await axios({
      url: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${config.qwenApiKey}`,
        'Content-Type': 'application/json'
      },
      data: {
        model: 'qwen-turbo',
        input: { messages },
        parameters: {
          result_format: 'message',
          stream: true,
          temperature: 0.7
        }
      },
      responseType: 'stream'
    });
    console.log('通义千问调用成功',response);
    // 2. 配置 SSE 响应头
    res.writeHead(200, {
      'Content-Type': 'text/event-stream; charset=utf-8',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
      'Access-Control-Allow-Origin': 'http://localhost:5173',
      'Access-Control-Allow-Headers': 'Content-Type'
    });

    // 3. 监听通义千问的流,逐块转发给前端
    response.data.on('data', (chunk) => {
      const lines = chunk.toString('utf8').split('\n').filter(Boolean);
      for (const line of lines) {
        try {
          // 尝试解析每一行 JSON
          const data = JSON.parse(line);
          // 构建 SSE 格式响应
          const sseData = `data: ${JSON.stringify(data)}\n\n`;
          res.write(sseData);
          console.log('转发数据:', data);
        } catch (e) {
          // 如果不是 JSON,直接转发
          res.write(`data: ${line}\n\n`);
          console.log('转发原始数据:', line);
        }
      }
    });

    // 4. 流结束时关闭连接
    response.data.on('end', () => {
      res.write('data: [DONE]\n\n');
      res.end();
    });

    // 5. 错误处理
    response.data.on('error', (err) => {
      console.error('通义千问流错误:', err);
      res.write(`data: ${JSON.stringify({ error: '流传输失败' })}\n\n`);
      res.end();
    });

  } catch (err) {
    console.error('流式调用失败:', err.message);
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: err.message }));
  }
});

// 启动服务
const PORT = 3001
app.listen(PORT, () => {
  console.log(`✅ 代理服务启动成功:http://localhost:${PORT}`)
  console.log(`📢 请确保已替换 config.js 里的 API Key`)
})

之后在终端

# 初始化 package.json
npm init -y
# 安装依赖
npm install express axios
# 启动服务
node server.js

这就是nodejs后端了,下面开始前端的

3-1.初始化vue3

# 初始化 Vue 项目(选 Vue3 + JavaScript)
npm create vite@latest . -- --template vue
# 安装依赖
npm install
# 安装辅助依赖(axios、element-plus 简化UI)
npm install axios element-plus

3-2,main.js替换

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

createApp(App).use(ElementPlus).mount('#app')

3-3,App.vue内

<template>
  <div class="chat-container">
    <el-page-header content="AI 聊天助手"></el-page-header>

    <!-- 聊天区域 -->
    <div class="chat-content">
      <div class="message" v-for="(item, index) in chatList" :key="index">
        <div class="user" v-if="item.role === 'user'">
          <span class="label">我:</span>
          <span class="content">{{ item.content }}</span>
        </div>
        <div class="ai" v-else>
          <span class="label">AI:</span>
          <span class="content">{{ item.content }}</span>
        </div>
      </div>
    </div>

    <!-- 输入区域 -->
    <div class="chat-input">
      <el-input
        v-model="inputMsg"
        placeholder="输入问题,比如「前端怎么学 AI 开发?」"
        @keyup.enter="sendNormalMsg"
      ></el-input>
      <el-button type="primary" @click="sendNormalMsg">普通调用</el-button>
      <el-button type="success" @click="sendStreamMsg">流式发送</el-button>
      <el-button type="warning" @click="clearChat">清空</el-button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'

// 聊天列表
const chatList = ref([])
// 输入框内容
const inputMsg = ref('')

// 1. 普通调用(非流式)
// 1. 普通调用(非流式)
const sendNormalMsg = async () => {
  if (!inputMsg.value.trim()) {
    ElMessage.warning('请输入问题!')
    return
  }

  // 添加用户消息
  chatList.value.push({
    role: 'user',
    content: inputMsg.value.trim()
  })
  const tempMsg = inputMsg.value.trim()
  inputMsg.value = ''

  try {
    // 调用代理接口(发送完整历史记录)
    const res = await axios.post('http://localhost:3001/api/chat/qwen', {
      messages: chatList.value  // 发送完整聊天历史
    })

    // 添加 AI 回复
    chatList.value.push({
      role: 'assistant',
      content: res.data.output?.choices?.[0]?.message?.content || '暂无回复'
    })
  } catch (err) {
    ElMessage.error('调用失败:' + err.response?.data?.detail || err.message)
    chatList.value.push({
      role: 'assistant',
      content: '调用失败:' + (err.response?.data?.detail || err.message)
    })
  }
}

// 2. 流式调用(打字机效果)
const sendStreamMsg = async () => {
  if (!inputMsg.value.trim()) {
    ElMessage.warning('请输入问题!')
    return
  }

  // 添加用户消息
  chatList.value.push({
    role: 'user',
    content: inputMsg.value.trim()
  })
  const tempMsg = inputMsg.value.trim()
  inputMsg.value = ''

  // 添加空的 AI 回复占位
  const aiMsgIndex = chatList.value.push({
    role: 'assistant',
    content: ''
  }) - 1

  try {
    // 流式请求(发送完整历史记录)
    const response = await fetch('http://localhost:3001/api/chat/qwen/stream', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        messages: chatList.value  // 发送完整聊天历史
      })
    })
    console.log(response,'-------response------')
    // 解析流式数据
    const reader = response.body.getReader()
      console.log(reader,'-------reader------')
    const decoder = new TextDecoder()
    
    // 打字速度控制(毫秒/字符)
    const typingSpeed = 50 // 可以根据需要调整,值越大速度越慢

    // 逐字符添加的函数
    const addCharWithDelay = (char) => {
      return new Promise(resolve => {
        setTimeout(() => {
          chatList.value[aiMsgIndex].content += char
          resolve()
        }, typingSpeed)
      })
    }

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

      const chunk = decoder.decode(value)
      console.log(chunk,'-------chunk------')
      const lines = chunk.split('\n').filter(line => line.trim() !== '')
      console.log(lines,'-------lines------')
      for (const line of lines) {
        console.log(line,'-------line------')
        if (line.startsWith('data: ')) {
          console.log('-------有数据------')
          try {
            const data = JSON.parse(line.slice(6))
            console.log(data,'-------data------success')
            // 逐字拼接 AI 回复(带延迟)
            if (data.output?.choices?.[0]?.message?.content) {
              const content = data.output.choices[0].message.content
              // 逐个字符添加,带延迟
              for (const char of content) {
                await addCharWithDelay(char)
              }
            }
          } catch (e) {
            // 忽略解析失败的行(流式数据可能有收尾字符)
          }
        }
      }
    }
  } catch (err) {
    ElMessage.error('流式调用失败:' + err.message)
    chatList.value[aiMsgIndex].content = '流式调用失败:' + err.message
  }
}

// 清空聊天
const clearChat = () => {
  chatList.value = []
  inputMsg.value = ''
}
</script>

<style scoped>
.chat-container {
  width: 800px;
  margin: 20px auto;
  padding: 20px;
}

.chat-content {
  height: 500px;
  border: 1px solid #e6e6e6;
  border-radius: 8px;
  padding: 10px;
  overflow-y: auto;
  margin-bottom: 20px;
}

.message {
  margin: 10px 0;
  line-height: 1.6;
}

.user .label, .ai .label {
  font-weight: bold;
  margin-right: 8px;
}

.user .label {
  color: #409eff;
}

.ai .label {
  color: #67c23a;
}

.content {
  font-size: 14px;
}

.chat-input {
  display: flex;
  gap: 10px;
}

.chat-input el-input {
  flex: 1;
}
</style>

之后npm i一下,在之后npm run dev运行

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐