微信小程序——实现对话模式(调用大模型图片生成)

文章来源:CSDN 发布日期:2024-04-18 分类:小程序博客 阅读( )

文章目录

    • ⭐前言
    • ⭐ 后端接口封装
      • 💖 使用axiOS调用api
      • 💖 暴露koa接口
    • ⭐ 前端的交互设计
      • 💖 布局设计
      • 💖 页面逻辑
    • ⭐ 效果
    • ⭐结束

yma16-logo

⭐前言

大家好,我是yma16,本文分享微信小程序——实现对话模式(调用大模型图片生成)。

aigc图片生成
AIGC (Artificial Intelligence Generated Content) 可以生成各种类型的图片,包括风景、动物、人物、抽象等等。生成图片的过程通常是使用预训练的神经网络模型,该模型可以根据输入的文本或图像生成新的图片。

⭐ 后端接口封装

💖 使用axiOS调用api

koa封装axiOS请求

const axiOS = require('axiOS')const axiOSInstance = (baseURL, headers) => {    const instance = axiOS.create({        baseURL: baseURL,        timeout: 20000,        headers: {...headers }    });    return instance}const postAction = (baseURL, path, headers, data) => {    const http = axiOSInstance(baseURL, headers)    return http.post(path, data)}module.exports = {    postAction}

💖 暴露koa接口

这里我调用的时掘金的bot-api

const Router = require('koa-router');const router = new Router();const { postAction } = require('../../utils/request/index');const API_KEY = '你的apikey'const bot_id = '你的bot_id'// 和bot聊天router.post('/chat/bot', async(ctx) => {    try {        const bodyParams = ctx.request.body        const { user, query } = bodyParams        console.log('bodyParams', bodyParams)        const headers = {            "Authorization": `Bearer ${API_KEY}`,            "Content-Type": "application/json",            "Host": 'api.coze.cn',            "Connection": "keep-alive"        }        const data = {            "bot_id": bot_id,            "user": user,            "query": query,        }        const baseUrl = "https://api.coze.cn"        const path = '/open_api/v2/chat'        const res = await postAction(baseUrl, path, headers, data)        ctx.body = {            code: res.status,            data: res.data,            msg: res.statusText        };    } catch (r) {        ctx.body = {            code: 0,            msg: r        }    }});module.exports = router;

⭐ 前端的交互设计

💖 布局设计

界面布局设计(一对一的对话模式)

<view class="container-box">  <view class="chat-container" id="chat-container-id" style="width: 100%;">    <scroll-view scroll-y="true" class="scroll-answer" scroll-with-animation bindscrolltoupper="upper" bindscrolltolower="lower" bindscroll="scroll" scroll-into-view="{{toView}}" scroll-top="{{scrollTop}}" wx:if="{{ chatObjConfig.option&&chatObjConfig.option.length>0 }}">      <view wx:for="{{ chatObjConfig.option }}" wx:for-index="index" wx:for-item="item" wx:key="index" id="chat-mode{{index}}">        <view class="create-time">          {{item.createTime}}        </view>        <view class="form-request">          <view wx:if="{{!item.isEdit}}" class='questioned'>            <view style="display: flex;text-align: right;flex-direction:row-reverse;">              <view class="questioned-box-container">                <view class='questioned-box' style="text-align: left;">                  {{item.question}}                </view>                <view class='questioned-box-poly'>                </view>                <view style="text-align: right;line-height: 50px;display: flex;max-height: 50px;">                  <view class='form-request-user'>                    <!-- {{currentUserInfo.nickName}} -->                    <image class="user-image" src="{{currentUserInfo.avatarUrl}}"></image>                  </view>                </view>              </view>            </view>          </view>        </view>        <view class="form-response" wx:if="{{!item.isEdit}}">          <view style="display: flex;">            <view style="line-height: 50px;">              <view class='form-response-user'>                <image class="ai-image" src="{{aiConfig.avatarUrl}}"></image>                <!-- {{aiConfig.nickName}} -->              </view>            </view>            <view class="form-response-box-poly">            </view>            <view class='form-response-box' style="overflow: auto;">              <towxml wx:key="index" nodes="{{item.answerMarkdown}}" style="position: relative;background: transparent;user-select: text;" />            </view>          </view>          <view style="display: flex;width: 100%;box-sizing: border-box;" wx:if="{{layoutConfig.isShowCopyBtn}}">            <view style="width: 70%;">            </view>            <view style="width: 30%;text-align: center;">              <button class="copy-btn" size="mini" bindtap="copyBtn" data-response=" {{item.answer}}">{{layoutConfig.copyText}}</button>            </view>          </view>        </view>      </view>      <view class="form-submit" wx:if="{{mode==='openAiUse'}}" style="width: 100%;">      </view>    </scroll-view>    <view wx:else class="scroll-answer">      <view class="create-time">        {{currenTime}}      </view>      <view style="display: flex;">        <view style="line-height: 50px;">          <view class='form-response-user'>            <image class="ai-image" src="{{aiConfig.avatarUrl}}"></image>            <!-- {{aiConfig.nickName}} -->          </view>        </view>        <view class="form-response-box-poly">        </view>        <view class="form-response-box" style="padding: 0 10px;">          {{layoutConfig.emptyText}}        </view>      </view>    </view>    <view class="bottom-box">      <view class='submit-input'>        <textarea class='send-input' bindinput="bindKeyInput" placeholder="{{layoutConfig.searchText}}" bindconfirm="search" value="{{searchOpenAiText}}" disabled="{{isLoading||isTruth}}" />      </view>      <view class='send-btn' type="primary" bindtap="search" loading="{{isLoading}}" disabled="{{isLoading}}">{{layoutConfig.sendText}}</view>    </view>  </view></view>

样式设置

/* pages/aiBot/aiBot.wxss */.container-box{  position: relative;  width: 100vw;  height: 100vh;  background: rgb(245, 245, 245);  overflow: hidden;  box-sizing: border-box;}.container-box-article {  position: relative;  padding-top:0px;  width: 100%;  height: calc(100vh - 88px);  box-shadow: inset 5px 5px #262626;  overflow: auto;  user-select: text;}.scroll-answer {  height: calc(100vh - 100px);}.chat-container {  width: 100%;  height: 100vh;  overflow-y: auto;  overflow-x: hidden;  position: relative;}.paste-btn {  background: rgba(16, 116, 187);  color: #ffffff;  /* transform: scale(.7); */  border-radius: 5px;}.clear-btn {  background: rgba(16, 116, 187);  color: #ffffff;  /* transform: scale(.7); */  border-radius: 5px;}.paste-btn:hover {  border: none;  background: rgb(221, 0, 66);}.user-image-box {  width: 300px;  text-align: center;  align-items: center;}.user-image {  position: relative;  width: 15px;  height: 15px;  border-radius: 50%;  background-color: transparent;  background: transparent;}.ai-image {  position: relative;  width: 20px;  height: 20px;  border-radius: 50%;}.questioned-box-container {  display: flex;}.questioned-box {  position: relative;  max-width: calc(100vw - 90px);  height: auto;  overflow-x: auto;  background-color: rgb(255, 255, 255);  border-radius: 10px;  right: -5px;  padding: 0 10px;  z-index: 999;  color: #333;  font-family: PingFang SC, Lantinghei SC, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;  font-weight: 300;  font-size: 32rpx;  user-select: text;  box-shadow: -5rpx 3rpx 1rpx -4rpx #c8c3c3;}.questioned-box-poly {  position: relative;  top: 15px;  width: 0;  height: 0;  border-radius: 5px;  border-top: 10px solid transparent;  border-bottom: 10px solid transparent;  border-left: 12px solid rgb(255, 255, 255);  box-shadow: 0rpx 0rpx 0rpx 0rpx #c8c3c3;}.clear-paste-btn{  width:70%;  display: flex;}.submit-input {  box-shadow: 0 2rpx 5rpx 5rpx #c8c3c3;  width: 70%;}.send-input {  height: 60px;  background: rgba(255, 255, 255, .8);  width: 100%;  height: 100px;  position: relative;  text-indent: 8px;  /* padding-left: 5px; */  color: rgb(0, 114, 221);}.send-btn::after{  position: absolute;  left:0;  top:0;  width: 100px;  height: 100%;  background-color:  rgba(255, 255, 255, .8);}.up-down-btn{  width:30%;}.send-btn {  box-shadow: 0 2rpx 5rpx 5rpx #c8c3c3;  width: 30%;  background-color:rgba(16, 116, 187);  color: #ffffff;  height: 100px;  line-height: 100px;  /* border-radius: 10px 0 0 10px; */  text-align: center;}.empty-reponse-msg {  position: relative;  max-width: calc(100vw - 90px);  height: auto;  overflow-x: auto;  background-color: rgb(255, 255, 255);  border-radius: 10px;  left: -5px;  padding: 0 10px;  z-index: 999;  color: #333;  font-family: PingFang SC, Lantinghei SC, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;  font-weight: 300;  font-size: 32rpx;  user-select: text;}.create-time {  width: 100%;  text-align: center;  color: rgb(255, 255, 255);  background: rgb(218, 218, 218);  margin: 5px auto;  box-shadow: inset 0 1rpx 2rpx 1rpx rgba(0, 0, 0, 0.2);}.form-response-user {  background-color: rgba(0, 72, 94, 0);  color: #fff;}.form-response-box-poly {  position: relative;  top: 15px;  width: 0;  height: 0;  border-radius: 5px;  border-top: 10px solid transparent;  border-bottom: 10px solid transparent;  border-right: 12px solid rgb(255, 255, 255);}.form-response-box {  position: relative;  max-width: calc(100vw - 50px);  /* word-break:keep-all; */  /* white-space: pre-wrap; */  white-space: pre-line;  height: auto;  overflow-x: auto;  background-color: rgb(255, 255, 255);  border-radius: 10px;  color: #333;  font-family: PingFang SC, Lantinghei SC, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;  font-weight: 300;  font-size: 32rpx;  left: -5px;  box-sizing: content-box;  z-index: 999;  user-select: text;  box-shadow: 5rpx 3rpx 1rpx -4rpx #c8c3c3;}.form-request {  display: block;  width: 100%;  color: #fff;  background-color: rgba(37, 0, 97, 0);  line-height: 50px;}.form-response {  position: relative;  width: 100%;  margin-top: 10px;  display: block;  margin-bottom: 10px;  color: #fff;  background-color: rgba(0, 72, 94, 0);  box-sizing: border-box;  min-height: 60px;}.form-response-user {  background-color: rgba(0, 72, 94, 0);  color: #fff;}.form-request-user {  background-color: rgba(37, 0, 97, 0);  color: #fff;}.bottom-box {  display: flex;  width: 100%;  display: absolute;  bottom: 100px;}

引入markdown

{  "usingComponents": {    "towxml":"/towxml/towxml"  }}

💖 页面逻辑

page逻辑

// pages/aiBot/aiBot.jsconst app = getApp();Page({  /**   * 页面的初始数据   */  data: {    currentUserInfo: {      nickName: '',      avatarUrl: 'https://profile-avatar.csdnimg.cn/8bea3d4b0c56486691de8f54fb649fa4_qq_38870145.jpg!1',    },    saveKey: 'aiBot',    baseCloudUrl: app.remoteConfig.baseCloudUrl,    password: "***",    username: "***",    token: '',    currenTime: '',    isLoading: false,    searchOpenAiText: '画一只猫',    chatObjConfig: {      option: [        //   {        //   question: '',        //   answer: '',        //   isEdit: true,        //   createTime: ''        // }      ],      currentIndex: 0,      errorMsg: 'openai的服务器异常!'    },    layoutConfig: {      showPasteBtn: false,      showTopBtn: false,      introduceText: 'api介绍',      useText: '使用',      returnText: '返回介绍',      sendText: '发送',      searchText: '请输入关键词进行对话',      reportText: '复制数据',      copyText: '复制',      pasteText: '粘贴',      upText: "↑",      downText: "↓",      errorMsg: 'bot ai服务器异常!',      emptyText: '欢迎使用aibot',      storageKey: 'openAiOptionsConfig',      permissionTitle: '很抱歉您没有权限!',      permissionContent: '请联系微信号:cse-yma16/r/n 需要1元开通权限/r/n1元可支持100条消息!',      wxInfoImg: 'https://yongma16.xyz/staticFile/common/img/userInfo.png',      limitMsgCount: 10,      confirmText: '添加微信',      cancelText: '返回'    },    aiConfig: {      avatarUrl: 'https://yongma16.xyz/staticFile/common/img/aiTop.jpg',      bgUrl: 'https://yongma16.xyz/staticFile/common/img/aiBg.jpg',      nickName: 'openai',    },  },  getUserToken() {    const that = this    wx.showLoading({      title: 'gen token loading',    });    wx.request({      url: this.data.baseCloudUrl + 'token/gen',      method: 'POST',      data: {        username: this.data.username,        password: this.data.password      },      success: (res => {        that.setData({          token: res.data.token        })        wx.hideLoading()      }),      fail: r => {        console.log('cloud r', r)        wx.hideLoading()      }    })  },  getCurrentTime() {    const now = new Date()    const year = now.getFullYear()    const month = now.getMonth()    const date = now.getDate()    const hour = now.getHours()    const minutes = now.getMinutes()    const second = now.getSeconds()    const formatNum = (n) => {      return n > 9 ? n.toString() : '0' + n    }    return `${year}-${formatNum(month + 1)}-${formatNum(date)} ${formatNum(hour)}:${formatNum(minutes)}:${formatNum(second)}`  },  bindKeyInput(e) {    console.log('e.detail.value', e.detail.value)    this.setData({      searchOpenAiText: e.detail.value    })  },  scrollToBottom() {    const index = this.data.chatObjConfig.option.length - 1    this.setData({      toView: `chat-mode${index}`    })  },  saveStore() {  },  search(e) {    this.scrollToBottom()    if (this.data.isLoading) {      wx.showModal({        cancelColor: 'cancelColor',        title: '正在响应中,请稍等...'      })      return    }    if (!this.data.searchOpenAiText) {      wx.showModal({        cancelColor: 'cancelColor',        title: '请输入!'      })      return    }    wx.showLoading({      title: '加载中',    })    this.setData({      isLoading: true    })    const that = this    return new Promise((resolve, reject) => {      wx.request({        url: that.data.baseCloudUrl + '/chat/bot',        method: 'POST',        header: {          Authorization: `bearer ${that.data.token}`        },        data: {          user: 'qwerqwre',          query: that.data.searchOpenAiText        },        success: (res) => {          console.log(res, 'res')          const data = res.data.data          const option = that.data.chatObjConfig.option          console.log('data', data)          const choices = data.messages?.[2]          const answer = choices?.content || that.data.layoutConfig.errorMsg          option.push({            question: that.data.searchOpenAiText,            answer: answer,            answerMarkdown: app.changeMrkdownText(answer),            createTime: that.getCurrentTime(),            isEdit: false,          })          const chatObjConfig = {            option: option          }          that.setData({            isLoading: false,            searchOpenAiText: '',            chatObjConfig: chatObjConfig          })          wx.hideLoading()          that.scrollToBottom()          resolve(res)          console.log('that.data.chatObjConfig.option', that.data.chatObjConfig.option)          that.saveStore()        },        fail: error => {                    that.setData({            isLoading: false          })          wx.hideLoading()          wx.showModal({            cancelColor: 'cancelColor',            title: '网络波动失败...'          })        }      });    })  },  /**   * 生命周期函数--监听页面加载   */  onLoad(options) {    const aiBotConfig = app.wxProgramConfig.aiBotConfig    console.log('aiBotConfig', aiBotConfig)    this.setData({      saveKey: aiBotConfig.saveKey,      searchOpenAiText: aiBotConfig.searchOpenAiText,      password: aiBotConfig.cloudPwd || "U2FsdGVkX1+jfEkF2OXTQ5iIG4mrYc5/TLOiIntyENU=",      username: aiBotConfig.cloudEmail || "1575057249@qq.com",    })    this.getUserToken()    this.setData({      currenTime: this.getCurrentTime()    })    const currentUserInfo = wx.getStorageSync('currentUserInfo')    if (currentUserInfo && currentUserInfo.nickName) {      console.log('currentUserInfo', currentUserInfo)      this.setData({        currentUserInfo: currentUserInfo      })    }    // 缓存    const chatObjConfig = wx.getStorageSync(this.data.saveKey)    if (chatObjConfig) {      this.setData({        chatObjConfig: chatObjConfig,      })    }  },  /**   * 生命周期函数--监听页面初次渲染完成   */  onReady() {  },  /**   * 生命周期函数--监听页面显示   */  onShow() {  },  /**   * 生命周期函数--监听页面隐藏   */  onHide() {  },  /**   * 生命周期函数--监听页面卸载   */  onUnload() {    // 缓存    if (this.data.chatObjConfig) {      wx.setStorageSync(app.wxProgramConfig.aiBotConfig.saveKey, this.data.chatObjConfig)    }  },  /**   * 页面相关事件处理函数--监听用户下拉动作   */  onPullDownRefresh() {  },  /**   * 页面上拉触底事件的处理函数   */  onReachBottom() {  },  /**   * 用户点击右上角分享   */  onShareAppMessage() {  }})

⭐ 效果

生成的图片
aigc-img
aigc-gril
aigc-cat
提示词:在摸鱼的猫
cat-catch-fishi

⭐结束

本文分享到这结束,如有错误或者不足之处欢迎指出!

gril

👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 最后,感谢你的阅读!

最新文章:

二维码