微信小程序——实现对话模式(调用大模型图片生成)
大家好,我是yma16,本文分享微信小程序——实现对话模式(调用大模型图片生成)。aigc图片生成AIGC (Artificial Intelligence Generated Content) 可以生成各种类型的图片,包括风景、动物、人物、抽象等等。生成图片的过程通常是使用预训练的神经网络模型,该模型可以根据输入的文本或图像生成新的图片。
·
⭐前言
大家好,我是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.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() {
}
})
⭐ 效果
生成的图片
提示词:在摸鱼的猫
⭐结束
本文分享到这结束,如有错误或者不足之处欢迎指出!
👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 最后,感谢你的阅读!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献4条内容
所有评论(0)