feat: ai聊天完成

This commit is contained in:
常东方 2025-06-19 17:06:34 +08:00
parent fe63a12e8e
commit 09510a2400
8 changed files with 166 additions and 99 deletions

13
env/.env.development vendored
View File

@ -5,7 +5,18 @@ VITE_DELETE_CONSOLE = false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
# 开发环境
# VITE_SERVER_BASEURL = 'http://114.218.158.24:9020'
VITE_SERVER_BASEURL = 'https://erpapi.fontree.cn'
# VITE_SERVER_BASEURL = 'http://192.168.88.80:9040/api'
# 正式环境 作为测试使用
# VITE_SERVER_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
#体制外
# VITE_SERVER_BASEURL = 'https://oa-a.szjixun.cn/api'
# 体制内
VITE_SERVER_BASEURL = 'https://oa-b.szjixun.cn/api'
# VITE_SERVER_BASEURL = 'https://oa-b.szjixun.cn/api'
# VITE_SERVER_BASEURL = 'https://erpapi.fontree.cn'
# VITE_SERVER_BASEURL = 'https://erptest.fontree.cn:9020'

11
env/.env.production vendored
View File

@ -5,6 +5,15 @@ VITE_DELETE_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = false
VITE_SERVER_BASEURL = 'https://erpapi.fontree.cn' # 体制外 OA
# VITE_SERVER_BASEURL = 'https://erpapi.fontree.cn' # 体制外 OA
# 正式环境 作为测试使用
# VITE_SERVER_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
# VITE_SERVER_BASEURL = 'https://oa-a.szjixun.cn/api'
#体制外
# VITE_SERVER_BASEURL = 'https://oa-a.szjixun.cn/api'
# 体制内
VITE_SERVER_BASEURL = 'https://oa-b.szjixun.cn/api'

View File

@ -107,6 +107,7 @@
"pinia": "2.0.36",
"pinia-plugin-persistedstate": "3.2.1",
"qs": "6.5.3",
"vconsole": "^3.15.1",
"vue": "3.4.21",
"wot-design-uni": "^1.4.0",
"z-paging": "^2.8.4"

View File

@ -70,6 +70,9 @@ dependencies:
qs:
specifier: 6.5.3
version: 6.5.3
vconsole:
specifier: ^3.15.1
version: 3.15.1
vue:
specifier: 3.4.21
version: 3.4.21(typescript@5.8.3)
@ -5555,6 +5558,11 @@ packages:
engines: {node: '>= 0.6'}
dev: true
/copy-text-to-clipboard@3.2.0:
resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==}
engines: {node: '>=12'}
dev: false
/core-js-compat@3.42.0:
resolution: {integrity: sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==}
dependencies:
@ -8325,6 +8333,10 @@ packages:
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
dev: true
/mutation-observer@1.0.3:
resolution: {integrity: sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==}
dev: false
/nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -10495,6 +10507,15 @@ packages:
engines: {node: '>= 0.8'}
dev: true
/vconsole@3.15.1:
resolution: {integrity: sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==}
dependencies:
'@babel/runtime': 7.27.1
copy-text-to-clipboard: 3.2.0
core-js: 3.42.0
mutation-observer: 1.0.3
dev: false
/vite-plugin-restart@0.4.2(vite@5.2.8):
resolution: {integrity: sha512-9aWN2ScJ8hbT7aC8SDeZnsbWapnslz1vhNq6Vgf2GU9WdN4NExlrWhtnu7pmtOUG3Guj8y6lPcUZ+ls7SVP33w==}
peerDependencies:

View File

@ -11,7 +11,7 @@
<div class="flex flex-col h-screen bg-#ffffff tops">
<!-- Navigation Bar -->
<div
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"
class="flex-none flex items-center justify-between px-5 pb-6 bg-white shadow-md h-20 pt-15 z-999 fixed top-0 w-full box-border"
>
<image src="/static/aichat/black.png" class="w-3 h-4.5" @click="goBack" />
<div class="text-lg font-medium ml-12">小墨</div>
@ -164,11 +164,23 @@
>
<!-- @click="previewVideo(msg.content)"-->
<video
v-if="isVideo(file?.video_url?.poster)"
:src="file?.video_url?.url"
class="w-60 h-30"
controls
:poster="
file?.video_url?.poster +
'?x-oss-process=video/snapshot,t_1,f_jpg,w_750,h_0,m_fast'
"
></video>
<video
v-else
:src="file?.video_url?.url"
class="w-60 h-30"
controls
:poster="file?.video_url?.poster"
></video>
<!-- isVideo -->
<!-- <video
:src="file.tempFilePath"
class="w-60 h-30"
@ -213,7 +225,7 @@
<!-- 底部上传预览 + 输入区 -->
<div
:class="[
'fixed bottom-0 left-0 right-0 bg-white z-[80] overflow-hidden transition-all duration-300',
'fixed bottom-0 left-0 right-0 bg-white z-[80] overflow-hidden transition-all duration-300 pb-40rpx',
showActions ? (uploadList.length ? 'h-40' : 'h-40') : 'h-20',
]"
>
@ -309,10 +321,11 @@
<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"
style="line-height: 62rpx"
class="fixed left-[32rpx] right-0 z-[90] 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',
showActions ? (uploadList.length ? 'bottom-31' : 'bottom-46') : 'bottom-26',
]"
>
<image
@ -381,7 +394,7 @@
<view
v-show="showActions"
:class="[
'flex justify-around items-center h-10 bg-white border-t border-t-solid border-[#E7E7E7]',
'flex justify-around items-center h-10 bg-white border-t border-t-solid border-[#E7E7E7] pb-20',
showActions ? (uploadList.length ? 'pt-10' : 'pt-10') : 'pt-0',
]"
>
@ -503,6 +516,7 @@ import { showToastErr, showToastOk, time_format3 } from '@/utils/tools'
import { uploadFileChunk } from './utils/api.js'
// import { TOKEN, AVATAR } from './utils/test'
import { deepClone } from 'wot-design-uni/components/common/util'
import VConsole from 'vconsole'
dayjs.locale('zh-cn')
const store = useUserStore()
@ -528,8 +542,8 @@ interface IMessage {
content: string | any[]
timestamp: Date
}
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 5mb
const FILE_SLICE_SIZE = 5 * 1024 * 1024 //
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5mb
const FILE_SLICE_SIZE = 2 * 1024 * 1024 //
const userAvatar = ref()
const chatMode = ref('qwen-vl-plus')
let isUserOk = false // 使 tongyi-app
@ -606,18 +620,23 @@ const listUuid = ref('')
async function createChatSession() {
try {
const createResp: any = await uni.request({
const res: any = await uni.request({
url: `${baseUrl}/chat/app/create`,
method: 'POST',
data: {
gptModel: chatMode.value,
},
header: {
// Authorization: token.value,
Authorization: token.value,
},
})
// listUuid
listUuid.value = createResp.data.data.listUuid
if (res.data.code === 0 && res?.data?.data?.listUuid) {
listUuid.value = res?.data?.data?.listUuid
} else {
showToastErr(res.data.msg)
}
} catch (err) {
console.log(err)
}
@ -654,16 +673,20 @@ async function fetchHistoryList() {
// Authorization: token.value,
},
})
if (resp.data && resp.data.data) {
if (state.total === null) {
rawList.value = resp.data.data.data
state.total = resp.data.data.count
} else {
if (resp.data.code == 0 && resp.data.data) {
if (rawList.value.length > 0) {
rawList.value = rawList.value.concat(resp.data.data.data)
state.total = resp.data.data.count //Math.ceil(resp.data.count/state.page)
} else {
if (resp?.data?.data?.data) {
rawList.value = resp.data.data.data
}
state.total = resp.data.data.count
}
// scrollTop.value+=60;
} else {
showToastErr(resp.data.msg || '')
}
} catch (err) {
console.log('err: ', err)
@ -685,14 +708,14 @@ watch(
{ deep: true },
)
async function openPopup() {
state.page++
showPopup.value = true
rawList.value = []
state.page++
}
function closePopup() {
showPopup.value = false
state.page = 0
state.total = null
rawList.value = []
}
function toggleFullscreen() {
fullscreen.value = !fullscreen.value
@ -712,12 +735,14 @@ async function fetchHistoryDiets(value) {
// Authorization: token.value,
},
})
if (resp && resp.data && resp.data.data) {
if (resp.data.status === 0 && resp.data && resp.data.data) {
const rawList = resp?.data?.data?.detail //
listUuid.value = resp?.data?.data?.listUuid
// const newMessages = parseBackendMessages(JSON.parse(rawList))
//
messages.splice(0, messages.length, ...JSON.parse(rawList))
} else {
showToastErr(resp.msg || '')
}
} catch (err) {
console.log('err: ', err)
@ -1007,15 +1032,21 @@ onMounted(async () => {
refreshToken: refreshToken.value,
statusBarHeight: statusBarHeight.value,
})
if (userInfo.value.TelNum === '18639432358') {
new VConsole()
}
await createChatSession()
}
init()
} catch (e) {
console.error('onMounted e: ', e)
} finally {
token.value = store.userInfo.token
await createChatSession()
// store.userInfo.token = ''
// token.value = store.userInfo.token
}
// if (JSON.parse(uni.getStorageSync('userInfo'))?.NickName === '') {
// new VConsole()
// }
})
function scrollToBottom() {
@ -1056,7 +1087,6 @@ const previewVideo = (files) => {
}
function addMessage(msg) {
console.log('msg: ', msg)
messages.push(msg)
scrollToBottom()
}
@ -1191,9 +1221,8 @@ const startUploads = () => {
if (file.size > MAX_FILE_SIZE) {
bigFileUpload(file)
.then((res) => {
console.log('全部成功 res: ', res)
// const length=res.length;
url = res.FullFileUrl
// file.url = res.FullFileUrl
file.status = 'success'
uploadingCount.value--
})
@ -1203,7 +1232,23 @@ const startUploads = () => {
// uploadingCount.value--
})
} else {
uploadFile(file)
bigFileUpload(file)
.then((res) => {
console.log('全部成功 res: ', res)
// const length=res.length;
file.url = res.FullFileUrl
file.status = 'success'
uploadingCount.value--
console.log(file)
})
.catch((rej) => {
console.log('失败rej: ', rej)
file.status = 'error'
// uploadingCount.value--
})
.finally(() => {
console.log('filesToUpload', filesToUpload)
})
}
})
}
@ -1287,7 +1332,6 @@ function bigFileUpload(file) {
file.status = 'uploading'
uploadingCount.value++
console.log('file: ', file) //progress
return new Promise(async (resolve, reject) => {
const md5 = file.id
const fileName = file.name
@ -1296,7 +1340,7 @@ function bigFileUpload(file) {
const chunksList = await readFile(file, maxSize)
const chunksLength = chunksList.length
const requestList = []
const maxConcurrency = 3 // 3
const maxConcurrency = 1 // 3
let currentRunning = 0
let index = 0
let success = null
@ -1361,10 +1405,17 @@ function bigFileUpload(file) {
currentRunning--
if (chunkIndex === chunksLength - 1) {
if (errors.length === 0) {
file.status = 'success'
resolve(success)
if (success.FullFileUrl || success.CoverUrl) {
file.status = 'success'
file.FullFileUrl = success.FullFileUrl || success.CoverUrl
resolve(success)
} else {
file.status = 'error'
}
} else {
file.status = 'error'
reject(errors)
}
} else if (index < chunksLength) {
@ -1374,48 +1425,6 @@ function bigFileUpload(file) {
}
}
next()
// const request = (chunks, i) => {
// const chunk = chunks.shift()
// const params = {
// FileMd5: md5,
// Chunk: chunk,
// ChunkFileName: `${fileName}_${i}`,
// Total: chunksLength,
// UseType: 100,
// FileName: fileName,
// Source: 'aiChat',
// }
// uploadFileChunk({ formData: params, tempFilePath: tempFilePath })
// .then((res) => {
// // requestList.push(Promise.resolve(res))
// if (res.status === 0) {
// file.progress = Math.ceil(((i + 1) / chunksLength) * 100)
// Promise.resolve(JSON.parse(res.data))
// } else {
// Promise.reject(res)
// }
// })
// .catch((rej) => {
// // requestList.push(Promise.resolve(rej))
// Promise.reject(rej)
// })
// .finally(() => {
// if (chunks.length > 0) {
// request(chunks, ++i)
// }
// })
// }
// console.log('success,next: ', success, errors);
// const promiseList=chunksList.map(request);
// request(chunksList, 0)
// return Promise.all(chunksList)
// .then((res) => {
// console.log('all res: ', res)
// })
// .catch((rej) => {
// console.log('all rej: ', rej)
// })
})
}
@ -1445,25 +1454,6 @@ const onPickImage = () => {
}
// Android API
const onPickVideo3 = () => {
var cmr = plus.camera.getCamera()
try {
cmr.startVideoCapture(
() => {
alert('ok')
},
() => {
alert('err')
},
{},
)
} catch (e) {
} finally {
cmr.stopVideoCapture()
}
}
const onPickVideo = () => {
uni.chooseVideo({
// sourceType: ['album', 'camera'],
@ -1533,7 +1523,6 @@ const onPickFile = () => {
type: 'all',
success: (res: any) => {
console.log(res)
//
addUploadQueue(res.tempFiles, uploadFileTypeEm.file)
},
@ -1866,6 +1855,8 @@ async function sendText() {
body.detail = JSON.stringify(messages)
aiMsg.content = ''
addMessage(aiMsg)
console.log('api body', body)
send(body)
// return
// return
@ -1897,6 +1888,14 @@ const send = async (body) => {
const [aiMsg] = messages.slice(-1)
const recordList = messages.slice(0, messages.length - 1)
body.detail = JSON.stringify(recordList)
// body.model = chatMode.value //
// body.max_tokens = 1000
// body.top_p = 1
// body.presence_penalty = 0
// body.frequency_penalty = 0
// messages: deepClone(historyUserMsgs), // text ? [aiMsg] : historyUserMsgs,
// body.stream = true
// body.listUuid = listUuid.value
// console.log('body: ',body);
// body.messages = spliceMsg(body.messages, chatMode.value)
try {
@ -1987,7 +1986,9 @@ const send = async (body) => {
if (isJsonObject(buffer)) {
const response = JSON.parse(buffer)
if (response.code === 401) {
showToastErr('请重新登录')
showToastErr(response.msg)
} else {
showToastErr(response.msg)
}
}
@ -2162,6 +2163,9 @@ const isFileType = (name: string) => {
let match = name.match(reg)
return match ? match[0] : null
}
const isVideo = (video) => {
return videoFileType.value.includes(isFileType(video))
}
</script>
<style lang="scss" scoped>

View File

@ -2,7 +2,8 @@ import { getEnvBaseUrl } from '@/utils'
import { httpPost } from '@/utils/http'
// import { TOKEN } from './test';
import { apis, uploadFile } from '@/utils/tools'
const baseUrl = getEnvBaseUrl()
const baseUrl = 'https://erpapi.fontree.cn' // 临时使用接口
// const baseUrl = getEnvBaseUrl()
// /artwork/get-chunk-list 获取文件分断数据
// /artwork/upload-chunk 分断上传画作图片
@ -13,8 +14,7 @@ export const uploadFiles = (url, params) => {
filePath: params.tempFilePath,
name: 'Chunk',
formData: params.formData,
header: {
},
header: {},
complete: (res) => {
// console.log('res: ',res);
if (res.statusCode == 200) {
@ -46,5 +46,5 @@ const getChunkList = (file) => {
export const uploadFileChunk = (data) => {
let token = uni.getStorageSync('authorization')
return uploadFiles('/artwork/upload-chunk', data)
return uploadFiles('/upload/upload-chunk', data)
}

View File

@ -204,4 +204,21 @@ async function sendText1(msgData = '') {
}
}
export const TOKEN="79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfcde73053dc1f269841c964c3b0779ceae38fcd1ac41220de5941cafd00664ae15bb706dfecc00972d1cf3c94b3ddec7758e514d8c0b32e2195e3bcb802d58861ca93e98cf322b9824623cfba4820be34e"
export const TOKEN="79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfcde73053dc1f269841c964c3b0779ceae38fcd1ac41220de5941cafd00664ae15bb706dfecc00972d1cf3c94b3ddec7758e514d8c0b32e2195e3bcb802d58861ca93e98cf322b9824623cfba4820be34e"
// if (errors.length === 0) {
// if (success.FullFileUrl || success.CoverUrl) {
// file.status = 'success'
// console.log('success', success)
// resolve(success)
// } else {
// file.status = 'error'
// }
// console.log(success)
// } else {
// file.status = 'error'
// reject(errors)
// }

View File

@ -48,7 +48,7 @@ export const formatParams = (uploadList) => {
role: 'system',
name: item.name,
// content: item.url,
content: {}, // item.url,
content: item.url, // item.url,
size: item.size,
mask: 'new',
}
@ -93,6 +93,8 @@ export function formatData(list) {
}
}
} else if (item.type === 'image' || item.type === 'video') {
console.log('media', item)
// 图片与视频混合在一起
const content = []
item.content.forEach((child) => {
@ -126,6 +128,8 @@ export function formatData(list) {
mask: item.mask,
})
} else if (item.type === 'file') {
console.log('file', item)
let content = []
item.content.forEach((child) => {
if (child.role === 'system') {