前篇已经解说如何运用Laf将ChatGPT免费接入微信公众号,现进行延伸扩展,结合uniapp轻松完成智能对话。

一、什么是uniapp?

uni-app是一个运用 Vue.js 开发所有前端运用的结构,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快运用等多个渠道。

想了解详情可戳 uniapp.dcloud.net.cn/

二、创立云函数

进入Laf云开发渠道 laf.dev/ ,创立云函数“uniapp-gpt”。

laf+uniapp三分钟轻松玩转ChatGPT

然后将下方代码复制粘贴到云函数中,注意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("\n\n", "");    
        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编辑器并新建项目,如下图:

laf+uniapp三分钟轻松玩转ChatGPT

laf+uniapp三分钟轻松玩转ChatGPT

创立完成后主界面和项目结果目录如下:

laf+uniapp三分钟轻松玩转ChatGPT

复制粘贴以下代码到“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="/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="/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(/\n\n/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="/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="/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,能够呈现渐渐翻滚到底部作用

四、最终作用

laf+uniapp三分钟轻松玩转ChatGPT

​uniapp项目已开源,地址:三分钟轻松玩转ChatGPT