前篇已经讲解如何使用Laf将ChatGPT免费接入微信公众号,现进行延伸扩展,结合uniapp轻松实现智能对话。
一、什么是uniapp?
uni-app是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。
想了解详情可戳 uniapp.dcloud.net.cn/
二、创建云函数
进入Laf云开发平台 laf.dev/ ,创建云函数“uniapp-gpt”。
然后将下方代码复制粘贴到云函数中,注意accessToken的获取方式
import cloud from '@lafjs/cloud'export
default async function (ctx: FunctionContext) {
const _body = ctx.body
//参数校验
if (!_body.keyword) {
return resultData(-1, '参数keyword不能为空!');
}
//引入 ChatGPTUnofficialProxyAPI 模块
const { ChatGPTUnofficialProxyAPI } = await import('chatgpt');
//创建 ChatGPTUnofficialProxyAPI 实例
const api = new ChatGPTUnofficialProxyAPI({
//https://chat.openai.com/api/auth/session 在浏览器中登录ChatGPT网页版,再访问这个网址获取accessToken
accessToken: "accessToken",
apiReverseProxyUrl: "https://ai.fakeopen.com/api/conversation"
});
try {
// 如果有上下文 id,就带上
let res = await api.sendMessage(_body.keyword)
console.log("OpenAI回复的内容", JSON.stringify(res));
let text = res.text.replace("nn", "");
return resultData(0, '成功!', text);
} catch (e) {
console.log('出现异常', e.message);
return resultData(-1, '出现异常:' + e.message);
}
}
//返回结果数据
async function resultData(code = -1, msg = '', data = null) {
return { code, msg, data }
}
完成之后发布云函数,复制url路径并保存。
三、创建uniapp项目
打开HbuilderX编辑器并新建项目,如下图:
创建完成后主界面和项目结果目录如下:
复制粘贴以下代码到“index.vue”文件中
<template>
<view class="content">
<scroll-view class="scroll-view" :style="{height:scrollViewHeight +'px'}" :scroll-y="true"
:scroll-top="scrollTop" :scroll-with-animation="true">
<view id="scroll-view-content" class="msg-list">
<view v-for="(item,index) in msgList" :key="index" class="msg-item">
<img v-if="item.type == 1" class="img_1" src="https://b2.7b2.com/static/images/ai.png" />
<view :style="{ 'margin-left': (item.type == 2 ? 'auto' : '0') }" class="msg" v-html="item.msg">
</view>
<img v-if="item.type == 2" class="img_2" src="https://b2.7b2.com/static/images/me.png" />
</view>
</view>
</scroll-view>
<view class="footer" ref="footer">
<input type="text" v-model="keyword" class="txt" :disabled="isSend" placeholder="请输入关键词......" />
<button type="primary" class="btn" @click="sendMessage()">{{btnTxt}}</button>
</view>
</view>
</template>
<script>
var _this;
export default {
data() {
return {
scrollTop: 0, //滚动条位置
scrollViewHeight: 0, //容器高度初始化为0
msgList: [],
keyword: '',
btnTxt: '发送',
isSend: false
}
},
onLoad() {
_this = this;
},
mounted: function() {
_this.calculateHeight();
//当浏览器窗口大小发生变化时,重新计算容器高度
window.addEventListener('resize', () => {
_this.calculateHeight();
});
},
methods: {
// 计算容器高度
calculateHeight() {
// 获取底部元素
let _footer = _this.$refs.footer;
if (_footer) {
_this.scrollViewHeight = document.documentElement.clientHeight - _footer.$el.offsetHeight - 44;
}
},
//发送信息
sendMessage() {
if (_this.isSend) {
// _this.showToast('请等待上一次对话结束!');
return;
}
const _keyword = _this.keyword;
if (!_keyword) {
_this.showToast('请输入关键词!');
return;
}
_this.keyword = '';
_this.btnTxt = '加载中';
_this.isSend = true;
_this.msgList.push({
type: 2,
msg: _keyword
});
//延迟0.5秒执行
setTimeout(function() {
_this.msgList.push({
type: 1,
msg: '🤖机器人正在思考🤔中...'
});
}, 500);
//延迟1秒执行
setTimeout(function() {
_this.sendRequest(_keyword);
}, 1000);
},
//发起请求
sendRequest(keyword) {
uni.showLoading({
title: '请稍后...'
});
uni.request({
url: 'https://xxx.laf.dev/uniapp-gpt',
method: 'POST',
data: {
keyword: keyword
},
success: (res) => {
// console.log('success', res.data);
let d = res.data;
if (d.code == 0 && d.data) {
let text = d.data.replace(/nn/g, '<br>').replace(/n/g, '<br>');
_this.msgList.push({
type: 1,
msg: ''
});
//实现打字机效果
let index = 0;
let timer = setInterval(function() {
if (text.length < index) {
clearInterval(timer);
}
_this.msgList[_this.msgList.length - 1].msg += text.substr(index, 1);
_this.scrollToBottom();
index++;
}, 100);
} else {
_this.showToast(d.msg);
}
},
complete: (res) => {
// console.log('complete', res);
uni.hideLoading();
_this.btnTxt = '发送';
_this.isSend = false;
}
});
},
//通用提示封装
showToast(title) {
uni.showToast({
title: title,
icon: 'none',
duration: 2000
});
},
//实现自动滚动到最底部
scrollToBottom() {
_this.$nextTick(() => {
uni.createSelectorQuery().in(_this).select('#scroll-view-content').
boundingClientRect(res => {
let top = res.height - _this.scrollViewHeight;
if (top > 0) {
_this.scrollTop = top;
}
}).exec();
})
}
}
}
</script>
<style lang="scss">
.content {
.msg-list {
padding: 0px 12px;
.msg-item:last-child {
padding-bottom: 10px;
}
.msg-item {
padding-top: 10px;
text-align: left;
display: flex;
.img_1 {
width: 36px;
height: 36px;
margin-right: 8px;
}
.img_2 {
width: 36px;
height: 36px;
margin-left: 8px;
}
.msg {
padding: 8px 15px;
font-size: 14px;
color: #303133;
border-radius: 10rpx;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
}
}
.footer {
width: 100%;
border-top: 1px solid #ddd;
background-color: #F5F5F5;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
box-sizing: border-box;
.txt {
flex: 1;
height: 40px;
padding: 5px;
border-radius: 5px;
box-sizing: border-box;
border: none;
outline: none;
}
.btn {
width: 82px;
height: 40px;
line-height: 40px;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
}
}
</style>
其中,uni.request请求时的url使用自己云函数中的url路径
说明:
1. 内容输出时实现打字机效果,这里采用了定时器的方式,每隔100毫秒将后端返回的内容拆分后逐个进行追加,核心代码如下:
//实现打字机效果
let index = 0;
let timer = setInterval(function() {
if (text.length < index) {
clearInterval(timer);
}
_this.msgList[_this.msgList.length - 1].msg += text.substr(index, 1);
_this.scrollToBottom();
index++;
}, 100);
2. scroll-view实现自动滚动到最底部,官网uniapp文档上说可以控制滚动条,但是并没有自动滚动到底部的设置选项。首先是在scroll-view滚动视图组件内再加一层view视图,然后再在内容变更时实现滚动底部的处理方法scrollToBottom(),核心代码如下:
<scroll-view class="scroll-view" :style="{height:scrollViewHeight +'px'}" :scroll-y="true"
:scroll-top="scrollTop" :scroll-with-animation="true">
<view id="scroll-view-content" class="msg-list">
<view v-for="(item,index) in msgList" :key="index" class="msg-item">
<img v-if="item.type == 1" class="img_1" src="https://b2.7b2.com/static/images/ai.png" />
<view :style="{ 'margin-left': (item.type == 2 ? 'auto' : '0') }" class="msg" v-html="item.msg">
</view>
<img v-if="item.type == 2" class="img_2" src="https://b2.7b2.com/static/images/me.png" />
</view>
</view>
</scroll-view>
//实现自动滚动到最底部
scrollToBottom() {
_this.$nextTick(() => {
uni.createSelectorQuery().in(_this).select('#scroll-view-content').
boundingClientRect(res => {
let top = res.height - _this.scrollViewHeight;
if (top > 0) {
_this.scrollTop = top;
}
}).exec();
})
}
注意事项:
需要注意组件scroll-view的属性设置
-
需要设置固定高度,这样视图里面内容当只有超过该高度才会有滚动效果
-
需要设置scroll-with-animation=true,可以出现慢慢滚动到底部效果
四、最终效果
uniapp项目已开源,地址:三分钟轻松玩转ChatGPT