feat: 增加知识库

This commit is contained in:
韩庆伟 2025-05-20 16:55:52 +08:00
parent 8a636bfde3
commit 065d1b556f
7 changed files with 141 additions and 60 deletions

View File

@ -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>

View File

@ -6,7 +6,6 @@
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,

View File

@ -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>

View File

@ -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 })
}
//

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

BIN
src/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB