diff --git a/README.md b/README.md index 7049379..8029803 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ <p align="center"> <a href="https://github.com/feige996/unibest"> - <img width="160" src="./src/static/logo.svg"> + <img width="160" src="./src/static/logo.png"> </a> </p> diff --git a/src/manifest.json b/src/manifest.json index 692398c..30e9fa2 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -6,7 +6,6 @@ "versionCode": "100", "transformPx": false, "app-plus": { - "usingComponents": true, "nvueStyleCompiler": "uni-app", "compilerVersion": 3, diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 3094c6b..72b3a2d 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -8,10 +8,10 @@ </route> <template> - <div class="flex flex-col h-screen bg-gray-50"> + <div class="flex flex-col h-screen bg-#ffffff"> <!-- Navigation Bar --> <div - class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-10 pt-10 z-999" + class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-20 pt-10 z-999 fixed top-0 w-full box-border" > <image src="/static/aichat/black.png" class="w-3 h-4" @click="goBack" /> <div class="text-lg font-medium ml-12">小墨</div> @@ -25,14 +25,14 @@ <!-- 消息区 --> <div :class="[ - 'flex relative p-b-10 box-border', - showActions ? (uploadList.length ? 'h-118' : 'h-137') : 'h-167', + 'flex fixed top-0 w-full p-b-10 box-border mt-20', + showActions ? (uploadList.length ? 'h-118' : 'h-151') : 'h-171', ]" > <!-- 背景层 --> <div v-if="!messages.length" - class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none" + class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none bg-#ffffff" > <image src="/static/aichat/logo.png" class="w-20 h-24 mb-4" @click="newChat" /> <view class="text-xl font-medium mb-1">嗨! 我是小墨</view> @@ -41,7 +41,7 @@ <div ref="scrollEl" - class="flex-1 overflow-y-auto bg-gray-50" + class="flex-1 overflow-y-auto bg-#ffffff" :class="showActions ? 'pb-44' : 'pb-16'" > <div :class="['relative z-10 px-4 py-6', showActions ? 'mb--11 h-105' : 'mb--21 h-135']"> @@ -60,11 +60,11 @@ /> <view class="relative max-w-[76.5%] mt-5" - :class="idx === messages.length - 1 ? 'mb-10' : 'mb-3'" + :class="idx === messages.length - 1 ? 'mb-20' : 'mb-3'" > <view :class="[ - 'absolute -top-5 text-xs text-gray-400 w-20 text-right', + 'absolute -top-5 text-xs text-gray-400 w-20 text-right ', msg.role === 'assistant' ? 'left--4' : 'right-0', ]" > @@ -72,12 +72,22 @@ </view> <view :class="[ - 'py-3 pl-3 rounded-md break-words mb-3 tracking-[2rpx] ', + 'py-3 pl-3 rounded-md break-words mb-3 tracking-[2rpx] min-w-10 min-h-2 ', msg.role === 'assistant' ? 'bg-[#f9f8fd] text-black shadow ' : 'bg-[#45299e] text-white ', ]" > + <wd-loading + v-show="msg.role === 'assistant' && msgLoading && idx === messages.length - 1" + :size="20" + color="#e3e3e3" + custom-class="loading-black" + :class="[ + 'absolute top-1.5 text-xs text-gray-400 w-20 text-right', + msg.role === 'assistant' ? 'left-1' : 'right-0', + ]" + /> <!-- 图片消息 --> <scroll-view scroll-x @@ -88,21 +98,31 @@ <view v-for="(file, fileIdx) in msg.content" :key="fileIdx" - style="flex: 0 0 2.5rem" - class="relative h-10 rounded-md overflow-hidden mr-1" + class="relative rounded-md overflow-hidden mr-1" + :class="{ + 'w-60 h-60': msg.content.length == 1, + 'w-15 h-15': msg.content.length == 2, + 'w-30 h-15 ': msg.content.length == 3, + ' h-15 flex-grow-0 flex-shrink-0 basis-10': msg.content.length >= 4, + }" > <view + v-if="showImageMask && fileIdx === 4" class="absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center" - :class="{ 'bg-black bg-opacity-70': showImageMask && fileIdx === 3 }" - v-if="showImageMask && fileIdx === 3" + :class="{ 'bg-black bg-opacity-70': showImageMask && fileIdx === 4 }" > - +{{ msg.content.length - 3 }} + +{{ msg.content.length - 4 }} </view> - <img + <image v-if="file.uploadFileType === uploadFileTypeEm.image" :src="file.url || file.tempFilePath" - class="w-full h-full object-cover" + :class="{ + 'w-100% h-100%': msg.content.length == 1, + 'w-15 h-15': msg.content.length == 2, + 'w-40 h-15 ': msg.content.length == 3, + 'w-40 h-15 object-cover': msg.content.length >= 4, + }" /> </view> </view> @@ -155,11 +175,7 @@ class="absolute bottom--3.5 flex space-x-3 ml-1" > <image src="/static/aichat/copy.png" class="w-4 h-4" @click="copyText(msg)" /> - <image - src="/static/aichat/resect.png" - class="w-4.3 h-4" - @click="refreshText(msg)" - /> + <image src="/static/aichat/resect.png" class="w-4.3 h-4" @click="refreshText()" /> </view> </view> <image @@ -177,7 +193,7 @@ <div :class="[ 'fixed bottom-0 left-0 right-0 bg-white z-[80] overflow-hidden transition-all duration-300', - showActions ? (uploadList.length ? 'h-58' : 'h-40') : 'h-20', + showActions ? (uploadList.length ? 'h-60' : 'h-40') : 'h-20', ]" > <!-- 上传列表 --> @@ -234,16 +250,42 @@ </div> </div> </div> + <!-- 知识库--> + + <div + @click="toggleKnowledge" + v-if="!uploadList.length" + class="fixed left-[32rpx] right-0 z-[90] h-8 w-23 flex items-center justify-between px-3 box-border rounded-1 transition-all duration-300" + :class="[ + knowledgeOpen ? 'bg-#eee9f8' : 'bg-[#F9F9F9]', + showActions ? (uploadList.length ? 'bottom-31' : 'bottom-41') : 'bottom-21', + ]" + > + <image + :src=" + knowledgeOpen + ? '/static/aichat/Knowledge-open.png' + : '/static/aichat/Knowledge-close.png' + " + class="w-4 h-3.5 mt-1" + /> + <div + :class="['text-[26rpx] transition-colors', knowledgeOpen ? 'text-#46299D' : 'text-black']" + > + 知识库 + </div> + </div> <!-- 输入 + 切换 --> <view class="flex items-center px-4 py-2.5 border-t border-t-solid border-[#E7E7E7]"> <input v-model="inputText" - @keyup.enter="sendText" + @keyup.enter="sendText('')" placeholder="想对我说点什么~" - class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-full focus:outline-none" + class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-1 focus:outline-none" /> <image + v-show="!knowledgeOpen" src="/static/aichat/add-circle.png" class="w-7 h-7 mx-3" @click="toggleActions" @@ -255,8 +297,9 @@ <image src="/static/aichat/enter.png" class="w-7 h-7" - @click="sendText" + @click="sendText('')" :disabled="loading" + :class="[knowledgeOpen ? 'ml-2' : 'ml-0']" /> </view> @@ -264,7 +307,10 @@ <transition name="slide-up"> <view v-show="showActions" - class="flex justify-around items-center h-20 bg-white border-t border-t-solid border-[#E7E7E7]" + :class="[ + 'flex justify-around items-center h-20 bg-white border-t border-t-solid border-[#E7E7E7]', + showActions ? (uploadList.length ? 'pt-1' : 'pt-1') : 'pt-0', + ]" > <view class="flex flex-col items-center"> <image src="/static/aichat/phone-img.png" class="w-13 h-13" @click="onPickImage" /> @@ -433,13 +479,12 @@ async function goChat(listUuid: string) { const listUuid = ref('') async function createChatSession() { - console.log(token.value, 'wwww') try { const createResp: any = await uni.request({ url: `${baseUrl}/chat/create`, method: 'POST', data: { - gptModel: 'gpt-3.5-turbo', + gptModel: 'gpt-4-vision-preview', }, header: { Authorization: token.value, @@ -487,7 +532,7 @@ async function fetchHistoryDiets(value) { method: 'POST', data: { listUuid: value, - gptModel: 'gpt-3.5-turbo', + gptModel: 'gpt-4-vision-preview', }, header: { Authorization: token.value, @@ -501,7 +546,9 @@ async function fetchHistoryDiets(value) { console.error('fetchHistoryList error:', err) } } -const token = ref<string>('') +const token = ref<string>( + '79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfc3ee452912e5908e2c574b766448c260449904f2a3c5ab9ed0235cfc8d5ba01f5d7c370733b136f7775849b03324fc6532d108779f9a7ff8dc40f235c56fec1b7a11ad61ac7e0a6a86f198079f4b92d61', +) const userInfo = ref<any>({}) const refreshToken = ref<string>('') const statusBarHeight = ref<number>(0) @@ -513,7 +560,7 @@ onMounted(() => { const init = async () => { const wv = plus.webview.currentWebview() - token.value = wv.token || uni.getStorageSync('token') || import.meta.env.VITE_DEV_TOKEN + // token.value = wv.token || uni.getStorageSync('token') || import.meta.env.VITE_DEV_TOKEN userInfo.value = JSON.parse(wv.userInfo) || {} refreshToken.value = wv.refreshToken || uni.getStorageSync('refreshToken') statusBarHeight.value = wv.statusBarHeight || uni.getSystemInfoSync().statusBarHeight @@ -558,6 +605,9 @@ const onScroll = (e) => { //查看更多图片 const previewMoreImg = (files) => { + uni.removeStorageSync('previewImages') + uni.removeStorageSync('previewVideos') + uni.setStorageSync('previewImages', files) uni.navigateTo({ @@ -567,6 +617,9 @@ const previewMoreImg = (files) => { //查看视频 const previewVideo = (files) => { + uni.removeStorageSync('previewVideos') + uni.removeStorageSync('previewImages') + uni.setStorageSync('previewVideos', files) uni.navigateTo({ @@ -769,7 +822,6 @@ const uploadFile = (file: UploadFile) => { // 照片 const onPickImage = () => { loading.value = true - uni.chooseImage({ count: 10, // 最多选择9张 sizeType: ['original', 'compressed'], @@ -818,7 +870,7 @@ const onPickVideo = () => { const onPickFile = () => { uni.chooseFile({ count: 10, - extension: uploadConfig.file.supportType, + type: uploadConfig.file.supportType, success: (res: any) => { console.log(res) // 开始上传 @@ -859,11 +911,17 @@ const previewFile = (url: string) => { url: '/pages/webview/index?link=' + encodeURIComponent(url), }) } - +const msgLoading = ref(true) // 发送消息 -async function sendText(msg) { +async function sendText(msgData) { + msgLoading.value = true const text = inputText.value.trim() - if (!text || loading.value) return + if (!text) { + uni.showToast({ title: '请输入信息', icon: 'error' }) + + return + } + if (loading.value) return //获取本次发送的消息和文件 const tempUploadList = Object.assign([], uploadList) @@ -930,9 +988,10 @@ async function sendText(msg) { content: text, timestamp: new Date(), } - + addMessage(aiMsg) //清除上传列表 uploadList.splice(0, uploadList.length) + // resds.value = historyUserMsgs const body: IGptRequestBody = { model: 'gpt-4-vision-preview', @@ -942,7 +1001,7 @@ async function sendText(msg) { top_p: 1, presence_penalty: 0, frequency_penalty: 0, - messages: historyUserMsgs, + messages: msgData || historyUserMsgs, stream: true, } @@ -979,7 +1038,10 @@ async function sendText(msg) { const delta = json.choices?.[0]?.delta?.content if (delta) { + msgLoading.value = false aiMsg.content += delta + //每次更新messages消息,实现流式输出 + messages[messages.length - 1] = { ...aiMsg } scrollToBottom() console.log('2') } @@ -988,8 +1050,6 @@ async function sendText(msg) { } } - // 添加AI消息 - addMessage(aiMsg) //更新上下文消息 historyUserMsgs.push(aiMsg) scrollToBottom() @@ -1016,10 +1076,19 @@ function copyText(msg: IMessage) { } } -function refreshText(msg: IMessage) { - const lastUserMsg = historyUserMsgs[historyUserMsgs.length - 1] +function refreshText() { + const lastUserMsg = historyUserMsgs[historyUserMsgs.length - 2] + // resds.value = lastUserMsg + sendText(lastUserMsg) } + +const knowledgeOpen = ref(false) +function toggleKnowledge() { + knowledgeOpen.value = !knowledgeOpen.value + showActions.value = false + rotation.value = 0 +} </script> <style lang="scss" scoped> diff --git a/src/pages/preview/index.vue b/src/pages/preview/index.vue index 8a52f18..4390a0d 100644 --- a/src/pages/preview/index.vue +++ b/src/pages/preview/index.vue @@ -1,22 +1,31 @@ <template> - <view class="image-gallery grid grid-cols-4 gap-1 p-4" v-if="imageList.length > 0"> + <view class="flex flex-col h-screen bg-#ffffff"> <view - v-for="(image, index) in imageList" - :key="index" - class="aspect-square overflow-hidden group" + class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-20 pt-10 z-999 fixed top-0 w-full box-border" > - <image - :src="image.url" - mode="aspectFill" - class="w-full h-full" - @click="handleImageClick(index)" - /> + <image src="/static/aichat/black.png" class="w-3 h-4 mt-1" @click="goBack" /> </view> - </view> + <view class="image-gallery grid grid-cols-4 gap-1 p-4 mt-20" v-if="imageList.length > 0"> + <view + v-for="(image, index) in imageList" + :key="index" + class="aspect-square overflow-hidden group" + > + <!-- <image + :src="image.url" + mode="aspectFill" + class="w-full h-full" + @click="handleImageClick(index)" + /> --> - <view v-if="videoList.length > 0" class="flex items-center justify-center"> - <view v-for="(video, index) in videoList" :key="index" class="w-full h-50"> - <video :src="video" class="w-full h-full" controls :poster="video.url"></video> + <wd-img :width="100" :height="100" :src="image.url" :enable-preview="true" /> + </view> + </view> + + <view v-if="videoList.length > 0" class="flex items-center justify-center mt-30"> + <view v-for="(video, index) in videoList" :key="index" class="w-full h-50"> + <video :src="video" class="w-full h-full" controls :poster="video.url"></video> + </view> </view> </view> </template> @@ -30,8 +39,12 @@ const imageList = ref([]) const videoList = ref([]) // 图片点击处理 -const handleImageClick = (index) => { - uni.previewImage({ urls: [imageList.value[index].url] }) +// const handleImageClick = (index) => { +// uni.previewImage({ urls: [imageList.value[index].url] }) +// } +// 返回上一个页面 +const goBack = () => { + uni.navigateBack({ delta: 1 }) } // 生命周期钩子 diff --git a/src/static/aichat/Knowledge-close.png b/src/static/aichat/Knowledge-close.png new file mode 100644 index 0000000..c1f8c90 Binary files /dev/null and b/src/static/aichat/Knowledge-close.png differ diff --git a/src/static/aichat/Knowledge-open.png b/src/static/aichat/Knowledge-open.png new file mode 100644 index 0000000..e38da37 Binary files /dev/null and b/src/static/aichat/Knowledge-open.png differ diff --git a/src/static/logo.png b/src/static/logo.png new file mode 100644 index 0000000..6ce55ae Binary files /dev/null and b/src/static/logo.png differ