前端使用(通义千问),因为免费,流式调用(打字机效果和正常效果),AI聊天案例加后端+nodejs,前端都可以自己写
·
1.申请token:官网地址

选择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运行

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



所有评论(0)