提交 2a5d4ce4 编写于 作者: DCloud_JSON's avatar DCloud_JSON

替换 SSEChannel 为 sseChannel

上级 fe468eca
......@@ -88,7 +88,7 @@
"vueVersion" : "3",
"h5" : {
"unipush" : {
"enable" : false
"enable" : true
}
},
"fallbackLocale" : "zh-Hans"
......
<template>
<template>
<view class="page">
<view class="container">
<view v-if="isWidescreen" class="header">uni-ai-chat</view>
<text class="noData" v-if="msgList.length === 0">没有对话记录</text>
<scroll-view :scroll-into-view="scrollIntoView" scroll-y="true" class="msg-list" :enable-flex="true">
<text class="noData" v-if="msgList.length === 0">没有对话记录</text>
<scroll-view :scroll-into-view="scrollIntoView" scroll-y="true" class="msg-list" :enable-flex="true">
<view v-for="(msg,index) in msgList" class="msg-item" :key="index">
<view class="create_time-box">
<uni-dateformat class="create_time" :date="msg.create_time" format="MM/dd hh:mm:ss"></uni-dateformat>
</view>
<view :class="{reverse:!msg.isAi}">
<view class="userInfo">
<image class="avatar" :src="msg.isAi?'../../static/uni-ai.png':'../../static/avatar.png'" mode="widthFix"></image>
</view>
</view>
<view :class="{reverse:!msg.isAi}">
<view class="userInfo">
<image class="avatar" :src="msg.isAi?'../../static/uni-ai.png':'../../static/avatar.png'" mode="widthFix"></image>
</view>
<view class="content">
<!-- <text class="copy" @click="copy">复制</text> -->
<!-- <text class="copy" @click="copy">复制</text> -->
<uni-ai-msg :md="msg.content" :show-cursor="index == msgList.length-1 && msg.isAi && sseIndex"></uni-ai-msg>
<view v-if="index == msgList.length-1 && adpid && msg.insufficientPoints">
<uni-ad-rewarded-video :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video>
</view>
</view>
<uni-icons v-if="index == msgList.length-1 && !msg.isAi && msg.state != 100 && msgStateIcon(msg)"
@click="msg.state == -100 ? retriesSendMsg() : ''" :color="msg.state===0?'#999':'#d22'"
:type="msgStateIcon(msg)" class="msgStateIcon">
</uni-icons>
</view>
</view>
</view>
<uni-icons v-if="index == msgList.length-1 && !msg.isAi && msg.state != 100 && msgStateIcon(msg)"
@click="msg.state == -100 ? retriesSendMsg() : ''" :color="msg.state===0?'#999':'#d22'"
:type="msgStateIcon(msg)" class="msgStateIcon">
</uni-icons>
</view>
</view>
<view class="tip-ai-ing" v-if="msgList.length && msgList.length%2 !== 0">
<text>uni-ai正在思考中...</text>
<view v-if="NODE_ENV == 'development' && !stream">
如需提速,请开通<uni-link class="uni-link" href="https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html" text="[流式响应]"></uni-link>
</view>
</view>
<view id="last-msg-item"></view>
</scroll-view>
</view>
<view id="last-msg-item"></view>
</scroll-view>
<view class="foot-box">
<view class="menu" v-if="isWidescreen">
<view class="trash menu-item">
......@@ -53,56 +53,56 @@
<text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send" type="primary">发送</button>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
</view>
</template>
<script>
import config from '@/config.js';
const {adpid} = config
export default {
data() {
return {
scrollIntoView: "",
msgList: [],
content: "",
const {adpid} = config
export default {
data() {
return {
scrollIntoView: "",
msgList: [],
content: "",
sseIndex: 0,
stream:true,
isWidescreen:false,
adpid
}
},
adpid
}
},
computed: {
inputBoxDisabled() {
if (this.sseIndex !== 0) {
return true
}
return !!(this.msgList.length && this.msgList.length%2 !== 0)
},
placeholderText() {
if (this.inputBoxDisabled) {
return 'uni-ai正在回复中'
} else {
// #ifdef H5
return window.innerWidth > 960 ? '请输入内容,ctrl + enter 发送' : '请输入要发给uni-ai的内容'
// #endif
return '请输入要发给uni-ai的内容'
}
inputBoxDisabled() {
if (this.sseIndex !== 0) {
return true
}
return !!(this.msgList.length && this.msgList.length%2 !== 0)
},
placeholderText() {
if (this.inputBoxDisabled) {
return 'uni-ai正在回复中'
} else {
// #ifdef H5
return window.innerWidth > 960 ? '请输入内容,ctrl + enter 发送' : '请输入要发给uni-ai的内容'
// #endif
return '请输入要发给uni-ai的内容'
}
},
NODE_ENV(){
return process.env.NODE_ENV
}
},
}
},
watch: {
msgList:{
handler(msgList) {
uni.setStorageSync('uni-ai-msg', msgList)
uni.setStorageSync('uni-ai-msg', msgList)
},
deep:true
}
},
}
},
async mounted() {
if(this.adpid && uniCloud.getCurrentUserInfo().tokenExpired > Date.now()){
let db = uniCloud.databaseForJQL();
......@@ -120,17 +120,17 @@
// console.log('score',score);
// for (let i = 0; i < 15; i++) {
// this.msgList.push({
// isAi: i % 2 == true,
// content: "1-" + i
// })
// }
this.msgList = uni.getStorageSync('uni-ai-msg') || []
// this.msgList.pop()
// console.log('this.msgList', this.msgList);
// for (let i = 0; i < 15; i++) {
// this.msgList.push({
// isAi: i % 2 == true,
// content: "1-" + i
// })
// }
this.msgList = uni.getStorageSync('uni-ai-msg') || []
// this.msgList.pop()
// console.log('this.msgList', this.msgList);
this.showLastMsg()
// #ifdef H5
......@@ -167,7 +167,7 @@
this.isWidescreen = matches;
})
// #endif
},
},
methods: {
//检查是否开通uni-push;决定是否启用stream
async checkIsOpenPush(){
......@@ -233,8 +233,8 @@
async retriesSendMsg() {
// 检查是否开通uni-push;决定是否启用stream
await this.checkIsOpenPush()
this.send()
},
this.send()
},
async beforeSendMsg() {
// 如果开启了广告位需要登录
if(this.adpid){
......@@ -277,57 +277,57 @@
icon: 'none'
});
}
this.msgList.push({
isAi: false,
content: this.content,
state: 0,
create_time: Date.now()
})
this.showLastMsg()
// 清空文本内容
this.$nextTick(() => {
this.content = ''
})
this.send()
},
async send() {
let messages = []
// 复制一份,消息列表数据
let msgs = JSON.parse(JSON.stringify(this.msgList))
// 带总结的消息 index
let findIndex = [...msgs].reverse().findIndex(item => item.summarize)
// console.log('findIndex', findIndex)
if (findIndex != -1) {
let aiSummaryIndex = msgs.length - findIndex - 1
// console.log('aiSummaryIndex', aiSummaryIndex)
msgs[aiSummaryIndex].content = msgs[aiSummaryIndex].summarize
// 拿最后一条带直接的消息作为与ai对话的msg body
msgs = msgs.splice(aiSummaryIndex, msgs.length - 1)
} else {
// 如果未总结过就直接从末尾拿10条
msgs = msgs.splice(-10)
}
messages = msgs.map(item => {
let role = "user"
if (item.isAi) {
role = item.summarize ? 'system' : 'assistant'
}
return {
content: item.content,
role
}
})
this.msgList.push({
isAi: false,
content: this.content,
state: 0,
create_time: Date.now()
})
this.showLastMsg()
// 清空文本内容
this.$nextTick(() => {
this.content = ''
})
this.send()
},
async send() {
let messages = []
// 复制一份,消息列表数据
let msgs = JSON.parse(JSON.stringify(this.msgList))
// 带总结的消息 index
let findIndex = [...msgs].reverse().findIndex(item => item.summarize)
// console.log('findIndex', findIndex)
if (findIndex != -1) {
let aiSummaryIndex = msgs.length - findIndex - 1
// console.log('aiSummaryIndex', aiSummaryIndex)
msgs[aiSummaryIndex].content = msgs[aiSummaryIndex].summarize
// 拿最后一条带直接的消息作为与ai对话的msg body
msgs = msgs.splice(aiSummaryIndex, msgs.length - 1)
} else {
// 如果未总结过就直接从末尾拿10条
msgs = msgs.splice(-10)
}
messages = msgs.map(item => {
let role = "user"
if (item.isAi) {
role = item.summarize ? 'system' : 'assistant'
}
return {
content: item.content,
role
}
})
console.log('send to ai messages:', messages);
let SSEChannel = false;
let sseChannel = false;
if(this.stream){
SSEChannel = new uniCloud.SSEChannel() // 创建消息通道
// console.log('SSEChannel',SSEChannel);
SSEChannel.on('message', (message) => { // 监听message事件
sseChannel = new uniCloud.SSEChannel() // 创建消息通道
// console.log('sseChannel',sseChannel);
sseChannel.on('message', (message) => { // 监听message事件
// console.log('on message', message);
if (this.sseIndex === 0) {
this.msgList.push({
......@@ -344,7 +344,7 @@
}
this.sseIndex++
})
SSEChannel.on('end', (e) => { // 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息
sseChannel.on('end', (e) => { // 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息
// console.log('on end', e);
if(e && (e.summarize || e.insufficientPoints)){
this.updateLastMsg(lastMsg=>{
......@@ -358,102 +358,102 @@
this.sseIndex = 0
this.showLastMsg()
})
await SSEChannel.open() // 等待通道开启
}
await sseChannel.open() // 等待通道开启
}
const uniAiChat = uniCloud.importObject("uni-ai-chat",{
customUI:true
})
uniAiChat.send({
messages,
SSEChannel
})
.then(res => {
this.updateLastMsg({state:100})
if (!SSEChannel) {
// console.log(res, res.reply);
this.msgList.push({
isAi: true,
content: res.data.reply,
summarize: res.data.summarize,
})
uniAiChat.send({
messages,
sseChannel
})
.then(res => {
this.updateLastMsg({state:100})
if (!sseChannel) {
// console.log(res, res.reply);
this.msgList.push({
isAi: true,
content: res.data.reply,
summarize: res.data.summarize,
create_time: Date.now(),
insufficientPoints:res.data.insufficientPoints
})
this.showLastMsg()
}
})
insufficientPoints:res.data.insufficientPoints
})
this.showLastMsg()
}
})
.catch(e => {
console.log(e);
this.updateLastMsg({state:-100})
uni.showModal({
content: JSON.stringify(e.message),
showCancel: false
});
})
},
showLastMsg() {
this.$nextTick(() => {
this.scrollIntoView = "last-msg-item"
this.$nextTick(() => {
this.scrollIntoView = ""
})
})
},
msgStateIcon(msg) {
switch (msg.state) {
case 0:
// 发送中
return 'spinner-cycle'
break;
case -100:
// 发送失败
return 'refresh-filled'
break;
case -200:
// 禁止发送(内容不合法)
return 'info-filled'
break;
default:
return false
break;
}
},
clear() {
uni.showModal({
title: "确认要清空聊天记录?",
content: '本操作不可撤销',
complete: (e) => {
if (e.confirm) {
this.msgList = []
}
}
});
console.log(e);
this.updateLastMsg({state:-100})
uni.showModal({
content: JSON.stringify(e.message),
showCancel: false
});
})
},
showLastMsg() {
this.$nextTick(() => {
this.scrollIntoView = "last-msg-item"
this.$nextTick(() => {
this.scrollIntoView = ""
})
})
},
msgStateIcon(msg) {
switch (msg.state) {
case 0:
// 发送中
return 'spinner-cycle'
break;
case -100:
// 发送失败
return 'refresh-filled'
break;
case -200:
// 禁止发送(内容不合法)
return 'info-filled'
break;
default:
return false
break;
}
},
clear() {
uni.showModal({
title: "确认要清空聊天记录?",
content: '本操作不可撤销',
complete: (e) => {
if (e.confirm) {
this.msgList = []
}
}
});
}
}
}
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
view,
textarea,
}
}
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
view,
textarea,
button,
.page
{
display: flex;
{
display: flex;
box-sizing: border-box;
}
}
/* #endif */
/* #ifndef APP-NVUE */
page,
/* #endif */
.page,
.container {
/* #ifndef APP-NVUE */
page,
/* #endif */
.page,
.container {
background-color: #efefef;
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
......@@ -466,147 +466,147 @@
height: calc(100vh - 44px);
/* #endif */
flex-direction: column;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* #ifndef APP-NVUE */
.container {
/* #ifndef APP-NVUE */
.container {
background-color: #FAFAFA;
}
/* #endif */
.foot-box {
width: 750rpx;
display: flex;
}
/* #endif */
.foot-box {
width: 750rpx;
display: flex;
flex-direction: column;
padding: 10px 0px;
background-color: #FFF;
padding: 10px 0px;
background-color: #FFF;
}
.foot-box-content{
justify-content: space-around;
}
}
.textarea-box {
padding: 8px 10px;
background-color: #f9f9f9;
border-radius: 5px;
}
.textarea-box .textarea {
max-height: 100px;
font-size: 14px;
/* #ifndef APP-NVUE */
overflow: auto;
padding: 8px 10px;
background-color: #f9f9f9;
border-radius: 5px;
}
.textarea-box .textarea {
max-height: 100px;
font-size: 14px;
/* #ifndef APP-NVUE */
overflow: auto;
/* #endif */
width: 450rpx;
}
/* #ifdef H5 */
/*隐藏滚动条*/
.textarea-box .textarea::-webkit-scrollbar {
width: 0;
}
/* #endif */
.input-placeholder {
color: #bbb;
}
.trash,
.send {
width: 50px;
height: 30px;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.trash {
}
/* #ifdef H5 */
/*隐藏滚动条*/
.textarea-box .textarea::-webkit-scrollbar {
width: 0;
}
/* #endif */
.input-placeholder {
color: #bbb;
}
.trash,
.send {
width: 50px;
height: 30px;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.trash {
width: 30rpx;
margin-left: 10rpx;
}
.send {
color: #FFF;
border-radius: 4px;
display: flex;
margin: 0;
padding: 0;
font-size: 14px;
margin-right: 20rpx;
}
/* #ifndef APP-NVUE */
.send::after {
display: none;
}
/* #endif */
.msg-list {
flex: 1;
height: 1px;
width: 750rpx;
}
.userInfo {
flex-direction: column;
}
.msg-item {
position: relative;
width: 750rpx;
flex-direction: column;
padding: 0 15px;
padding-bottom: 15px;
}
.msgStateIcon {
position: relative;
top: 5px;
right: 5px;
align-self: center;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 2px;
}
.create_time {
font-size: 12px;
padding: 5px 0;
padding-top: 0;
color: #aaa;
margin-left: 10rpx;
}
.send {
color: #FFF;
border-radius: 4px;
display: flex;
margin: 0;
padding: 0;
font-size: 14px;
margin-right: 20rpx;
}
/* #ifndef APP-NVUE */
.send::after {
display: none;
}
/* #endif */
.msg-list {
flex: 1;
height: 1px;
width: 750rpx;
}
.userInfo {
flex-direction: column;
}
.msg-item {
position: relative;
width: 750rpx;
flex-direction: column;
padding: 0 15px;
padding-bottom: 15px;
}
.msgStateIcon {
position: relative;
top: 5px;
right: 5px;
align-self: center;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 2px;
}
.create_time {
font-size: 12px;
padding: 5px 0;
padding-top: 0;
color: #aaa;
text-align: center;
width:750rpx;
/* #ifdef MP */
display: flex;
/* #endif */
justify-content: center;
}
.content {
justify-content: center;
}
.content {
/* #ifndef APP-NVUE */
max-width: 550rpx;
/* #endif */
background-color: #FFF;
border-radius: 5px;
padding: 12px 10px;
margin-left: 10px;
/* #ifndef APP-NVUE */
word-break: break-all;
user-select: text;
cursor: text;
/* #endif */
background-color: #FFF;
border-radius: 5px;
padding: 12px 10px;
margin-left: 10px;
/* #ifndef APP-NVUE */
word-break: break-all;
user-select: text;
cursor: text;
/* #endif */
}
/* #ifndef APP-NVUE */
.content {
display: inline;
/* #ifndef APP-NVUE */
.content {
display: inline;
}
.content ::v-deep rich-text{
......@@ -619,29 +619,29 @@
.content * {
display: inline;
}
/* #endif */
.reverse {
flex-direction: row-reverse;
}
.reverse .content {
margin-left: 0;
margin-right: 10px;
}
.reverse-align {
align-items: flex-end;
}
.noData {
margin-top: 15px;
text-align: center;
width: 750rpx;
color: #aaa;
font-size: 12px;
justify-content: center;
}
/* #endif */
.reverse {
flex-direction: row-reverse;
}
.reverse .content {
margin-left: 0;
margin-right: 10px;
}
.reverse-align {
align-items: flex-end;
}
.noData {
margin-top: 15px;
text-align: center;
width: 750rpx;
color: #aaa;
font-size: 12px;
justify-content: center;
}
.tip-ai-ing {
align-items: center;
......
......@@ -61,8 +61,8 @@ module.exports = {
requestId: this.getUniCloudRequestId()
})
this.textSecCheck = async (content)=>{
let {SSEChannel} = this.getParams()[0]||{}
if(SSEChannel){
let {sseChannel} = this.getParams()[0]||{}
if(sseChannel){
throw "流式响应模式,内容安全识别功能无效"
}
// 检测文本
......@@ -109,9 +109,9 @@ module.exports = {
}
}else if(error == 'insufficientPoints'){
let reply = "积分不足,请看完激励视频广告后再试"
let {SSEChannel} = this.getParams()[0]||{}
if(SSEChannel){
const channel = uniCloud.deserializeSSEChannel(SSEChannel)
let {sseChannel} = this.getParams()[0]||{}
if(sseChannel){
const channel = uniCloud.deserializeSSEChannel(sseChannel)
await channel.write(reply)
await channel.end({
"insufficientPoints":true
......@@ -147,7 +147,7 @@ module.exports = {
},
async send({
messages,
SSEChannel
sseChannel
}) {
// 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据
// messages = [{
......@@ -165,13 +165,13 @@ module.exports = {
let {llm,chatCompletionOptions} = config
return await chatCompletion({
messages, //消息内容
SSEChannel, //sse渠道对象
sseChannel, //sse渠道对象
llm
})
async function chatCompletion({
messages,
summarize = false,
SSEChannel = false,
sseChannel = false,
llm
}) {
......@@ -179,13 +179,13 @@ module.exports = {
let res = await llmManager.chatCompletion({
...chatCompletionOptions,
messages,
stream: SSEChannel !== false
stream: sseChannel !== false
})
if (SSEChannel) {
if (sseChannel) {
let reply = ""
return new Promise((resolve, reject) => {
const channel = uniCloud.deserializeSSEChannel(SSEChannel)
const channel = uniCloud.deserializeSSEChannel(sseChannel)
res.on('message', async (message) => {
// await channel.write(message)
// console.log('---message----', message)
......@@ -256,7 +256,7 @@ module.exports = {
messages,
summarize: true,
stream: false,
SSEChannel: false
sseChannel: false
})
return res.reply
}
......
{
"bsonType": "object",
"required": [
"_id",
"value"
],
"properties": {
"_id": {
"bsonType": "string",
"description": "自动生成的id"
},
"app_id": {
"bsonType": "string",
"description": "客户端DCloud AppId"
},
"device_id": {
"bsonType": "string",
"description": "客户端设备id"
},
"private_key": {
"bsonType": "string",
"description": "私钥,仅保存在云端"
},
"public_key": {
"bsonType": "string",
"description": "公钥,下发给客户端使用"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间"
}
},
"version": "0.0.1"
}
\ No newline at end of file
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": false,
"create": false,
"update": false,
"delete": false
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"user_id":{
"bsonType": "string",
"description": "用户id"
},
"balance":{
"bsonType": "int",
"description": "剩余可对话的次数"
},
"update_time":{
"bsonType": "timestamp",
"forceDefaultValue":{
"$env": "now"
}
}
}
}
\ No newline at end of file
{
"bsonType": "object",
"required": [
"user_id",
"score",
"balance"
],
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"user_id": {
"bsonType": "string",
"description": "用户id,参考uni-id-users表"
},
"score": {
"bsonType": "int",
"description": "本次变化的积分"
},
"type": {
"bsonType": "int",
"enum": [
1,
2
],
"description": "积分类型 1:收入 2:支出"
},
"balance": {
"bsonType": "int",
"description": "变化后的积分余额"
},
"comment": {
"bsonType": "string",
"description": "备注,说明积分新增、消费的缘由",
"trim": "both"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
}
},
"version": "0.0.1"
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册