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

文章目录

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

⭐前言

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

aigc图片生成

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

⭐ 后端接口封装

💖 使用axios调用api

koa封装axios请求

javascript 复制代码
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

javascript 复制代码
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;

⭐ 前端的交互设计

💖 布局设计

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

html 复制代码
<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>

样式设置

css 复制代码
/* 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

json 复制代码
{
  "usingComponents": {
    "towxml":"/towxml/towxml"
  }
}

💖 页面逻辑

page逻辑

javascript 复制代码
// pages/aiBot/aiBot.js

const 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() {

  }
})

⭐ 效果

生成的图片


提示词:在摸鱼的猫

⭐结束

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

👍 点赞,是我创作的动力!

⭐️ 收藏,是我努力的方向!

✏️ 评论,是我进步的财富!

💖 最后,感谢你的阅读!

相关推荐
EdisonZhou14 分钟前
大模型应用开发初探 : 通用函数调用Planner
aigc·.net·.net core
人工智能培训咨询叶梓1 小时前
生成式人工智能在无人机群中的应用、挑战和机遇
人工智能·语言模型·自然语言处理·aigc·无人机·多模态·生成式人工智能
2401_8459375311 小时前
PHP一键约课高效健身智能健身管理系统小程序源码
微信·微信小程序·小程序·微信公众平台·微信开放平台
程序员入门进阶13 小时前
基于微信小程序的科创微应用平台设计与实现+ssm(lw+演示+源码+运行)
微信小程序·小程序
AI极客菌17 小时前
Stable Diffusion绘画 | 生成高清多细节图片的各个要素
人工智能·ai·ai作画·stable diffusion·aigc·midjourney·人工智能作画
计算机源码社20 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
SHI-HANG21 小时前
13 Midjourney从零到商用·实战篇:漫画设计一条龙
ai作画·aigc·midjourney
双普拉斯1 天前
微信小程序点赞动画特效实现
nginx·微信小程序·notepad++
程序员阿龙1 天前
【2025】基于微信小程序的网上点餐系统设计与实现、基于微信小程序的智能网上点餐系统、微信小程序点餐系统设计、智能点餐系统开发、微信小程序网上点餐平台设计
微信小程序·小程序·毕业设计·订单管理·在线点餐·订单跟踪·在线支付
NLP工程化1 天前
Dify 中的讯飞星火平台工具源码分析
aigc·dify