提交 96d615d7 编写于 作者: DCloud_JSON's avatar DCloud_JSON

适配pc端

上级 f732c299
......@@ -2,6 +2,23 @@
export default {
onLaunch: function() {
console.log('App Launch')
let version = uni.getSystemInfoSync().uniRuntimeVersion
function toNum(a){
const c = a.toString().split('.');
const num_place = ["", "0", "00", "000", "0000"],
r = num_place.reverse();
for (let i = 0; i < c.length; i++){
const len=c[i].length;
c[i]=r[len]+c[i];
}
return c.join('');
}
if(toNum(version) < toNum("3.8.0")){
uni.showModal({
content: '本示例的HBuilderX版本不得低于3.8.0,请升级',
showCancel: false
});
}
},
onShow: function() {
console.log('App Show')
......@@ -12,5 +29,17 @@
}
</script>
<style>
<style lang="scss">
/*每个页面公共css */
/* #ifdef H5 */
@media screen and (min-width:650px){
/* pc宽屏 隐藏会话页面头部 && 全局底部导航 以下兼容了Vue2和3两种模式的样式*/
uni-page[data-page="pages/chat/chat"] uni-page-head,
{
display: none !important;
background-color: red !important;
}
/* #endif */
}
</style>
......@@ -68,10 +68,10 @@
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2",
"vueVersion" : "3",
"h5" : {
"unipush" : {
"enable" : true
"enable" : false
}
}
}
......@@ -4,7 +4,7 @@
"path" : "pages/chat/chat",
"style" :
{
"navigationBarTitleText": "uni-ai",
"navigationBarTitleText": "uni-ai-chat",
"enablePullDownRefresh": false
}
}
......
<template>
<view class="page">
<view class="container">
<view v-if="isWidescreen" class="header">uni-im-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">
<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 style="flex-direction: column;">
<view class="content">
<uni-ai-msg :md="msg.content" :show-cursor="index == msgList.length-1 && msg.isAi && sseIndex"></uni-ai-msg>
<!-- <text :selectable="true">{{msg.content}}</text> -->
</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'"
......@@ -25,22 +25,44 @@
</scroll-view>
<view class="foot-box">
<view class="trash">
<view class="menu" v-if="isWidescreen">
<view class="trash menu-item">
<image @click="clear" src="@/static/remove.png" mode="heightFix"></image>
</view>
<view class="set-stream menu-item">
<view class="title">
<text>流式响应</text>
<uni-icons @click="toStreamMD" class="help" type="help"></uni-icons>
<text>:</text>
</view>
<switch :checked="stream" @change="changeStream" />
</view>
</view>
<view class="foot-box-content">
<view v-if="!isWidescreen" class="trash">
<uni-icons @click="clear" type="trash" size="24" color="#888"></uni-icons>
</view>
<view class="textarea-box">
<textarea v-model="content" class="textarea" :auto-height="true" :disabled="inputBoxDisabled"
<textarea v-model="content" class="textarea" :auto-height="!isWidescreen" :disabled="inputBoxDisabled"
:placeholder="placeholderText" :maxlength="-1" placeholder-class="input-placeholder"></textarea>
</view>
<view class="send-btn-box">
<button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send">发送</button>
<text v-if="isWidescreen" class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<button @click="beforeSendMsg" :disabled="inputBoxDisabled || !content" class="send" type="primary">发送</button>
</view>
</view>
</view>
<label class="set-stream">
<text style="font-size: 12px;">流式响应:</text>
<switch style="transform: scale(0.7);" :checked="stream" @change="changeStream" />
</label>
<view v-if="!isWidescreen" id="set-stream">
<view class="title">
<text>流式响应</text>
<uni-icons @click="toStreamMD" class="help" type="help"></uni-icons>
<text>:</text>
</view>
<switch :checked="stream" @change="changeStream" />
</view>
</view>
</view>
</template>
......@@ -52,7 +74,8 @@
msgList: [],
content: "",
sseIndex: 0,
stream:false
stream:false,
isWidescreen:false
}
},
computed: {
......@@ -113,7 +136,7 @@
//按下了shift ctrl alt windows键
adjunctKeydown = true;
}
if (e.keyCode == 13 && adjunctKeydown) {
if (e.keyCode == 13 && !adjunctKeydown) {
this.beforeSendMsg();
}
};
......@@ -125,12 +148,58 @@
};
}
// #endif
// 添加惰性函数,检查是否开通push
this.changeStream.check = async ()=>{
uni.getPushClientId({
fail:()=> {
this.stream = false
uni.showModal({
content: '你暂未开通uni-push。不支持此功能',
showCancel: false,
confirmText:"查看详情",
complete() {
let url = "https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"
// #ifndef H5
uni.setClipboardData({
data:url,
showToast:false,
success() {
uni.showToast({
title: '已复制文档链接,请到浏览器粘贴浏览',
icon: 'none',
duration:5000
});
}
})
// #endif
// #ifdef H5
window.open(url)
// #endif
}
});
console.log('你暂未开通uni-push。不支持此功能。详情:https://uniapp.dcloud.net.cn/uniCloud/uni-ai-chat.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9');
},
success:()=>{
this.changeStream.check = ()=>{}
}
})
}
uni.createMediaQueryObserver(this).observe({
minWidth: 650,
}, matches => {
this.isWidescreen = matches;
})
},
methods: {
// updateLastMsg(){
// },
changeStream(e){
this.changeStream.check()
// console.log('e',e.detail.value);
this.stream = e.detail.value
},
......@@ -302,6 +371,25 @@
}
});
},
toStreamMD(){
let url = "https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion-stream"
// #ifndef H5
uni.setClipboardData({
data:url,
showToast:false,
success() {
uni.showToast({
title: '已复制文档链接,请到浏览器粘贴浏览',
icon: 'none',
duration:5000
});
}
})
// #endif
// #ifdef H5
window.open(url)
// #endif
}
}
}
......@@ -312,10 +400,7 @@
view,
textarea,
button,
.page,
/* #ifdef H5 */
.page *
/* #endif */
.page
{
display: flex;
box-sizing: border-box;
......@@ -329,36 +414,32 @@
.page,
.container {
background-color: #efefef;
width: 750rpx;
flex: 1;
/* #ifdef H5 */
height: calc(100vh - 44px);
/* #endif */
/* #ifndef H5 */
height: 100vh;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
}
/* #ifndef APP-NVUE */
.container {
background-color: #FAFAFA;
}
.container,
.container * {
max-width: 550px;
}
/* #endif */
.foot-box {
width: 750rpx;
display: flex;
justify-content: space-around;
align-items: self-start;
flex-direction: column;
padding: 10px 0px;
background-color: #FFF;
}
.foot-box-content{
justify-content: space-around;
}
.textarea-box {
padding: 8px 10px;
......@@ -368,11 +449,11 @@
.textarea-box .textarea {
max-height: 100px;
width: 460rpx;
font-size: 14px;
/* #ifndef APP-NVUE */
overflow: auto;
/* #endif */
width: 450rpx;
}
/* #ifndef APP-NVUE */
......@@ -388,7 +469,6 @@
.trash,
.send {
/* border: 1px solid #000; */
width: 50px;
height: 30px;
justify-content: center;
......@@ -398,11 +478,10 @@
.trash {
width: 30rpx;
margin-left: 20rpx;
margin-left: 10rpx;
}
.send {
background-color: #2eaf4d;
color: #FFF;
border-radius: 4px;
display: flex;
......@@ -463,7 +542,9 @@
}
.content {
max-width: 500rpx;
/* #ifndef APP-NVUE */
max-width: 550rpx;
/* #endif */
background-color: #FFF;
border-radius: 5px;
padding: 12px 10px;
......@@ -507,33 +588,134 @@
justify-content: center;
}
.set-stream{
position: fixed;
left: 0px;
top: 0;
#set-stream{
z-index: 999;
padding:0 5px;
justify-content: center;
align-items: center;
position: fixed;
bottom: 50px;
right: 0;
}
#set-stream .title{
font-size: 12px;
position: relative;
left: 10rpx;
}
#set-stream switch{
transform: scale(0.5);
}
/* #ifdef H5 */
@media screen and (min-width:600px){
.textarea-box{
flex:1
@media screen and (min-width:650px){
.foot-box{
border-top: solid 1px #dde0e2;
}
.trash,
.send-btn-box{
flex-shrink: 0;
width: 100px;
.page{
width: 100vw;
flex-direction: row;
}
.page * {
max-width: 950px;
}
.container, {
box-shadow: 0 0 5px #e0e1e7;
margin-top: 44px;
border-radius: 10px;
overflow: hidden;
}
.container .header{
height: 44px;
line-height: 44px;
border-bottom: 1px solid #F0F0F0;
width: 100vw;
justify-content: center;
padding: 0;
margin: 0;
font-weight: 500;
}
.content {
background-color: #f9f9f9;
}
.trash{
width: 60px;
.foot-box,
.foot-box-content,
.msg-list,
.msg-item,
// .create_time,
.noData,
.textarea-box,
.textarea,
textarea-box {
width: 100% !important;
}
.textarea-box .textarea {
width: 100%;
.create_time-box {
margin-top: 15px;
justify-content: center;
}
.create_time{
display: flex;
}
.textarea-box,
.textarea,
textarea,
textarea-box {
height: 120px;
}
.container,
.foot-box,
.textarea-box {
background-color: #FFF;
}
.foot-box-content{
flex-direction: column;
justify-content: center;
align-items: flex-end;
padding-bottom: 0;
}
.menu {
padding:0 10px;
}
.menu-item{
height:20px;
justify-content: center;
align-items: center;
align-content: center;
display: flex;
margin-right: 10px;
cursor: pointer;
}
.trash {
opacity: 0.8;
}
.trash image{
height: 15px;
}
.set-stream .title {
font-size: 12px;
}
.set-stream switch {
transform: scale(0.6);
position: relative;
left: -5px;
}
.textarea-box,.textarea-box *{
// border: 1px solid #000;
}
.send-btn-box .send-btn-tip{
color: #919396;
margin-right: 8px;
font-size: 12px;
line-height: 28px;
}
}
/* #endif */
......
## 简介
> 仅支持3.8.0及以上版本的HBuilderX
uni-ai-chat是基于[uni-ai](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html)的云端一体AI项目模板
视频效果:
......@@ -6,6 +9,16 @@ uni-ai-chat是基于[uni-ai](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html)
包含一个前端页面(路径:`/pages/chat/chat.vue`)和一个云对象(路径:`uniCloud/cloudfunctions/uni-ai-chat/index.obj.js`)
## 响应流程图
<img width="400px" src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-ai-chat/20230424211201.jpg">
- 普通响应的流程较为简单,全流程基于 HTTP 网络请求。其缺陷是,当访问 AI 聊天接口时,如果生成的回复内容过大,响应时间会很长,导致前端用户需要等待很长时间才能收到结果。详情请参考:[stream的优势](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion-stream)
- 流式响应的使用需客户端先通过 `new uniCloud.SSEChannel()` 创建 SSE 通道,并获得 `channel` 值(详情请参考:[https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html#create-sse-channel](https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html#create-sse-channel))。在客户端向 uniCloud 云对象发起请求时,需要将该 `channel` 值作为参数一同携带;然后 uniCloud 云对象与 uni-ai 建立 流式响应[(stream)](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion-stream) 通讯,云对象监听 uni-ai 返回的分片数据,在接收到数据后再通过 sse-channel ([反序列化消息通道](https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html#cloud-deserialize-channel))向客户端推送消息。
### 注意事项 @heed
- 使用`sse-channel`需要先[开通uni-push](https://uniapp.dcloud.net.cn/unipush-v2.html#%E7%AC%AC%E4%B8%80%E6%AD%A5-%E5%BC%80%E9%80%9A)
- 目前uni-push2.0不支持本地调试(后续版本会支持),需要在HBuilderX控制台,更改`连接本地云函数``连接云端云函数`
## 体验步骤
......@@ -15,10 +28,3 @@ uni-ai-chat是基于[uni-ai](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html)
4. 点击`使用HBuilderX导入示例项目`
5. 对项目根目录uniCloud点右键选择“云服务空间初始化向导”界面按提示部署项目
6. 在uni-app项目点右键,关联之前创建的服务空间。
\ No newline at end of file
## 注意事项
uni-ai-chat支持[流式响应](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#%E6%B5%81%E5%BC%8F%E5%93%8D%E5%BA%94-chat-completion-stream)
模式,该模式会接收uni-ai的流式响应的数据,通过sse-channel[(云函数(云对象)请求中的中间状态通知通道)](https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html)向客户端推送消息,
而使用`sse-channel`需要先[开通uni-push](https://uniapp.dcloud.net.cn/unipush-v2.html#%E7%AC%AC%E4%B8%80%E6%AD%A5-%E5%BC%80%E9%80%9A)
,目前uni-push2.0不支持本地调试(后续版本会支持),需要在HBuilderX控制台,更改`连接本地云函数``连接云端云函数`
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册