uniapp微信小程序实现deepseek r1接入 搭建简易客服问答
进入deepseek官网:https://www.deepseek.com。进入官网右上角【API开放平台】。左侧菜单栏【API keys】,创建API key。核心:配置sse,接收流式数据,实现打字机效果。话不多说,上代码。页面vue代码:common.js代码三.附效果视频
·
一. 申请deepseek访问key值
进入deepseek官网:https://www.deepseek.com。进入官网右上角【API开放平台】。
左侧菜单栏【API keys】,创建API key。
二.uniapp调用访问
核心:配置sse,接收流式数据,实现打字机效果。话不多说,上代码。
页面vue代码:
<template>
<view class="setting">
<view class="kefu-box">
<!-- 聊天内容 -->
<scroll-view :scroll-y="true" id="scroll-view-content" class="scroll-h"
:style="{height: scrollStyle.contentViewHeight + 'px'}"
:show-scrollbar="true"
:scroll-with-animation="true"
:scroll-anchoring="true"
refresher-background="#f5f5f5"
:refresher-triggered="refreshTriggered"
:scroll-into-view="'chat-item-'+scrollMsgIdx"
:scroll-top="scrollTop"
>
<view v-for="(item, index) in chatList" :key="index" class="content"
:style="{ flexDirection: item.role == 'user' ? 'row-reverse' : '' }" :id="'chat-item-'+index">
<!-- 头像 -->
<image v-if="item.role == 'assistant'" src='wxservice.png' mode="aspectFill" class="avatar-image"></image>
<image v-if="item.role == 'user'" src='wxme.png' mode="aspectFill" class="avatar-image"></image>
<!-- 用户名称、消息时间 -->
<view class="user-chat-info">
<view class="user-info" :class="item.role == 'user' ? 'order-2' : ''" :style="{ flexDirection: item.role == 'user' ? 'row-reverse' : '' }">
<view class="user-chat-name" v-if="item.role == 'user'">
用户
</view>
<view class="user-chat-name" v-if="item.role == 'assistant'">
喝碗茶冷静一下
</view>
</view>
<!-- 聊天消息 -->
<view class="content-text" :style="{ textAlign: item.role == 'user' ? 'right' : 'left' }">
<view class="text-span" v-if="item.role == 'user'">
<u-parse :content="item.content"></u-parse>
</view>
<view class="role-span" v-if="item.role == 'assistant'">
<u-parse :content="item.content"></u-parse>
</view>
</view>
<u-loading-icon color="#fcb08c" :show="item.role == 'assistant' && item.ds_loading" text="思考中......"></u-loading-icon>
</view>
</view>
</scroll-view>
</view>
<!-- 底部发送信息 -->
<view class="footerCon" :style="'transform: translate3d(0,' + percent + '%,0);'" ref="footerCon">
<view class="footer row-bottom" ref="footer">
<u--textarea style="width:80%;" type="text" placeholder="请输入内容" class="input" ref="input" @focus="focus" cursor-spacing="20" v-model="message" confirm-type="send" @confirm="sendTest('')" ></u--textarea>
<view class="send" @click="sendTest('')">
发送
</view>
</view>
</view>
</view>
</template>
<script>
import {StreamRequest,decodeUTF8} from '@/common/common.js'
export default {
data() {
return {
message: '', //输入信息
userInfo: '', //用户信息
chatList: [{ role: "assistant", content: "您好,我是喝碗茶冷静一下.您有什么问题可以问我,我会尽力解答(*^_^*)"}], //信息列表
knowledgeInfo: '', //知识库
agentUser: '', //缓存信息
constData: this.$constData,
page: 1,
pageSize: 10,
totalPage: -1,//总聊天记录页数
freshing: false,
// 超出限制数组
model: 'product',
pid: 1,
percent: 0,
refreshTriggered: true,
isSocketOpen: false,//webscoket是否已经打开
scrollMsgIdx: 0, // 滚动条定位为到哪条消息
isMorePage: false,//是否多屏数据
re_message:'',
scrollTop:0,
scrollStyle:{
pageHeight: 0,
contentViewHeight: 0,
footViewHeight: 90,
mitemHeight: 0
}, //滚动插件style
};
},
created () {
let res = uni.getSystemInfoSync(); //获取手机可使用窗口高度 api为获取系统信息同步接口
this.scrollStyle.pageHeight = res.windowHeight;
this.scrollStyle.contentViewHeight = res.windowHeight - uni.getSystemInfoSync().screenWidth / 750 * (100) - 70; //像素 因为给出的是像素高度 然后我们用的是upx 所以换算一下
//this.scrollToBottom(); //创建后调用回到底部方法
this.chatList = [{ role: "assistant", content: "您好,我是喝碗茶冷静一下.您有什么问题可以问我,我会尽力解答(*^_^*)"}] //信息列表
},
watch: {
chatList(newVal, oldVal) {
this.chatList = newVal;
}
},
methods: {
//获取聊天消息的高度
getChatHeight(){
if(!this.isMorePage){
let query = uni.createSelectorQuery().in(this);
query.select('#scroll-view-content').boundingClientRect(data => {
if (data) {
let systemInfo = uni.getStorageSync('systemInfo');
let editHeight = 65;//有表情框
let toMoreHeight = 0;
let caHeight = Number(systemInfo.windowHeight) - Number(editHeight)-Number(data.height)- toMoreHeight;
this.isMorePage = Number(caHeight) <= 0;//是否只有一屏//内容的高度大于可用窗口的高度-输入文字的高度
this.scrollTop = data.height * 100;
}
}).exec();
}
},
scrollToBottom(){
let size = this.chatList.length;
if (size > 0) {
this.scrollToMsgIdx(size - 1);
}
},
scrollToMsgIdx(idx) {
// 如果scrollMsgIdx值没变化,滚动条不会移动
if (idx == this.scrollMsgIdx && idx > 0) {
let that = this;
let query = uni.createSelectorQuery();
query.selectAll('.content').boundingClientRect();
query.select('#scroll-view-content').boundingClientRect();
query.exec((res) => {
that.scrollStyle.mitemHeight = 0;
res[0].forEach((rect) => that.scrollStyle.mitemHeight = that.scrollStyle.mitemHeight + rect.height + 40) //获取所有内部子元素的高度
if (that.scrollStyle.mitemHeight > (that.scrollStyle.contentViewHeight - 100)) { //判断子元素高度是否大于显示高度
that.scrollTop = that.scrollStyle.mitemHeight - that.scrollStyle.contentViewHeight //用子元素的高度减去显示的高度就获益获得序言滚动的高度
}
})
}
this.$nextTick(() => {
this.scrollMsgIdx = idx;
});
},
focus() {
this.getChatHeight();
},
//发送消息
async sendTest(message,flag) {
if (!this.message && !message) {
return uni.showToast({
title: '消息为空',
icon: 'none'
})
}
var content = {
model: "deepseek-reasoner", // 指定 R1 模型:cite[4]:cite[8]
messages: [{ role: "user", content: this.message }],
stream: true,
max_tokens: 4096,
// temperature:0.2,
}
this.chatList.push({ role: "user", content: this.message })
this.chatList.push({ role: "assistant", content: "信息接收中......"})
//this.getChatHeight();
this.scrollToBottom();
this.message = "";
try {
const requestTask= await StreamRequest(content)
// 返回请求头信息
requestTask.onHeadersReceived((e)=>{
this.chatList[this.chatList.length - 1].content = ""
this.chatList[this.chatList.length - 1].ds_loading = true
})
// 成功回调 返回流传输信息 返回arrayBuffer
requestTask.onChunkReceived((res)=>{
var decodedData = this.decode(res.data);
if (decodedData) {
this.chatList[this.chatList.length - 1].content += decodedData
// 数据和DOM都更新完成后,获取容器高度
//this.getChatHeight();
this.scrollToBottom();
}
})
} catch (err) {
this.chatList.push({ role: "assistant", content: "服务暂不可用,请重试。" });
}
},
//数据解析器(处理SSE格式)
decode(data) {
const text = decodeUTF8(data);
const lines = text.split('\n');
let result = '';
for (let line of lines) {
if (line.startsWith('data: ')) {
const jsonData = line.slice(6).trim();
// 结束标识处理
if (jsonData === '[DONE]') {
this.chatList[this.chatList.length - 1].ds_loading = false;
return result;
}
// 清理控制字符(防止JSON解析失败)
const cleanedData = jsonData.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
try {
const parsedData = JSON.parse(cleanedData);
// 提取AI生成内容
result += parsedData.choices[0].delta.reasoning_content || '';
} catch (e) {
console.error('解析失败:', e);
}
}
}
return result;
},
},
}
</script>
<style lang="scss">
.scroll-h {
max-height: calc(100vh - 100px);
padding-bottom: 20upx;
overflow-anchor: auto;
transform: rotate(360deg);
}
.autoHeight{
height: calc(100% - 65px);
overflow-y: auto;
}
.scroll-h-icon.autoHeight {
height: calc(100% - 380px);
overflow-y: auto;
}
.user-chat-info {
flex: 1;
}
.user-info{
margin-bottom: 10upx;
}
.order-2.user-info {
text-align: right;
}
.setting {
min-height: auto;
background: #f5f5f5;
}
.kefu-box {
padding: 0 20upx;
height: calc(100vh - 100px);
.content {
transform: rotate(360deg);
display: flex;
// align-items: center;
padding-bottom: 35upx;
}
.avatar-image {
width: 60upx;
height: 60upx;
border-radius: 50%;
margin: 10upx 10upx;
}
.content-text-knowledge {
padding: 10upx 15upx;
background-color: #fff !important;
border-radius: 5px;
margin-top: 10upx;
position: relative;
.knowledge-title {
font-size: 20upx;
// font-weight: 550;
margin-bottom: 20upx;
}
.border-none {
:last-child {
border: none !important;
}
.iconfont {
font-size: 24upx !important;
}
}
.knowledge-flxe {
min-width: 60upx;
max-width: 600upx;
padding: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
border-bottom: 1px solid #f5f5f5;
font-size: 20upx;
}
}
.content-text {
// background-color: #c7dcfa;
margin-top: 10upx;
position: relative;
.text-span {
max-width: 600upx;
border-radius: 5px;
padding: 10upx 15upx;
display: inline-block;
background-color: #c7dcfa;
text-align: left;
}
.avatar-image {
max-width: 300upx;
max-height: 300upx;
}
.role-span {
max-width: 600upx;
border-radius: 5px;
padding: 10upx 15upx;
display: inline-block;
background-color: #fcb08c;
text-align: left;
}
}
.online-time {
color: #666;
font-size: 20upx;
}
}
.user-chat-name {
color: #000;
}
.setting-item {
padding: 25upx 20upx;
border-bottom: 2upx #dfdfdf solid;
font-size: 20upx;
}
.avatar {
width: 60upx;
height: 60upx;
border-radius: 50%;
overflow: hidden;
}
.item-value {
color: #999999;
}
.send {
width: 100upx;
text-align: center;
height: 60upx;
line-height: 60upx;
background-color: #f56c6c;
font-size: 12px;
color: #ffffff;
margin-left: 10px;
}
.input {
max-height: 150upx;
overflow-y: auto;
overflow-x: hidden;
flex: 1;
margin: 0 10upx;
border-radius: 10upx;
background-color: #e5e5e5;
/* padding: 17upx 30upx; */
height: 60upx;
padding: 0 10upx;
}
.footer {
width: 90%;
background-color: #fff;
padding: 17upx 26upx;
display: flex;
align-items: center;
}
.footer image {
width: 61upx;
height: 60upx;
display: block;
}
.footerCon {
position: fixed;
bottom: 0upx;
min-height: 100upx;
width: 100%;
transition: all 0.005s cubic-bezier(0.25, 0.5, 0.5, 0.9);
background-color: #fff;
}
.footerCon .on {
bottom: 300upx;
transform: translate3d(0, 0, 0) !important;
}
.bottom-icon {
padding: 15upx;
}
.chat-image{
max-width: 375upx !important;
}
.load-more{
text-align: center;
color: #1423fc;
padding: 5px 0;
transform: rotate(180deg);
}
textarea{
background-color: #ebeced;
border-radius: 5px;
padding:5px;
}
.u-loading-icon{
// width: 25px!important;
}
</style>
common.js代码
export function StreamRequest(data) {
return new Promise((resolve, reject) => {
const response = uni.request({
url: 'https://api.deepseek.com/chat/completions', // 请求地址
method: "POST", // 你的请求方法
data: data,
header: {
'Content-Type': 'application/json', // 声明接受事件流
'Authorization': "Bearer 你申请的key"
},
responseType: "text",
enableChunked: true, // 开启流传输
success: (res) => {
resolve(res)
},
fail: (err) => {
reject(err)
},
})
// 返回请求的响应
resolve(response)
})
}
export function decodeUTF8(data) {
// 将二进制数据转为Uint8数组
const uint8Array = new Uint8Array(data);
// 传统方式转换字符串(兼容旧环境)
let string = '';
for (let i = 0; i < uint8Array.length; i++) {
string += String.fromCharCode(uint8Array[i]);
}
// 双重解码处理特殊字符(如中文)
return decodeURIComponent(escape(string));
}
三.附效果视频
更多推荐
所有评论(0)