fix: 修复按钮问题
This commit is contained in:
parent
9f24fec9d6
commit
316e8101c3
BIN
AIchat.rar
BIN
AIchat.rar
Binary file not shown.
15
src/App.vue
15
src/App.vue
@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||||
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
||||||
|
const { statusBarHeight } = useStatus()
|
||||||
|
import { useStatus } from '@/store/status'
|
||||||
|
|
||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
console.log('App Launch')
|
console.log('App Launch')
|
||||||
@ -18,7 +20,18 @@ onHide(() => {
|
|||||||
button::after {
|
button::after {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
/*解决阅览图片关闭按钮会显示在状态栏区域的问题*/
|
||||||
|
#u-a-p > div > div {
|
||||||
|
margin-top: var(--statusBarHeight);
|
||||||
|
}
|
||||||
|
/*不显示滚动条的类*/
|
||||||
|
.no-scroll {
|
||||||
|
-ms-overflow-style: none; /* IE 和 Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
.no-scroll::-webkit-scrollbar {
|
||||||
|
display: none; /* Webkit 浏览器 */
|
||||||
|
}
|
||||||
swiper,
|
swiper,
|
||||||
scroll-view {
|
scroll-view {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</route>
|
</route>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-screen bg-#ffffff">
|
<div class="flex flex-col h-screen bg-#ffffff tops">
|
||||||
<!-- Navigation Bar -->
|
<!-- Navigation Bar -->
|
||||||
<div
|
<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 py-3 bg-white shadow-md h-20 pt-10 z-999 fixed top-0 w-full box-border"
|
||||||
@ -76,6 +76,7 @@
|
|||||||
msg.role === 'assistant'
|
msg.role === 'assistant'
|
||||||
? 'bg-[#f9f8fd] text-black shadow '
|
? 'bg-[#f9f8fd] text-black shadow '
|
||||||
: 'bg-[#45299e] text-white ',
|
: 'bg-[#45299e] text-white ',
|
||||||
|
msg.type === 'text' ? 'pr-0' : 'pr-3',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<wd-loading
|
<wd-loading
|
||||||
@ -140,6 +141,12 @@
|
|||||||
:key="fileIdx"
|
:key="fileIdx"
|
||||||
style="flex: 0 0 6rem"
|
style="flex: 0 0 6rem"
|
||||||
class="relative text-xs h-12 px-2 py-2 rounded-md overflow-hidden mr-1 bg-white c-black"
|
class="relative text-xs h-12 px-2 py-2 rounded-md overflow-hidden mr-1 bg-white c-black"
|
||||||
|
:class="{
|
||||||
|
'w-25 h-15': msg.content.length == 1,
|
||||||
|
'w-50 h-15': msg.content.length == 2,
|
||||||
|
'w-30 h-15 ': msg.content.length == 3,
|
||||||
|
'h-15 flex-grow-0 flex-shrink-0 basis-10 mr-1': msg.content.length >= 4,
|
||||||
|
}"
|
||||||
@click="previewFile(file.url)"
|
@click="previewFile(file.url)"
|
||||||
>
|
>
|
||||||
<view>{{ file.name }}</view>
|
<view>{{ file.name }}</view>
|
||||||
@ -193,11 +200,25 @@
|
|||||||
<div
|
<div
|
||||||
:class="[
|
: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',
|
||||||
showActions ? (uploadList.length ? 'h-60' : 'h-40') : 'h-20',
|
showActions ? (uploadList.length ? 'h-40' : 'h-40') : 'h-20',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<!-- 上传列表 -->
|
<!-- 上传列表 -->
|
||||||
<div v-if="uploadList.length" class="flex px-4 py-2 overflow-x-auto space-x-3 bg-transparent">
|
<div
|
||||||
|
v-if="uploadList.length"
|
||||||
|
:class="[
|
||||||
|
'flex px-4 py-2 overflow-x-auto space-x-3 bg-transparent',
|
||||||
|
showActions
|
||||||
|
? uploadList.length
|
||||||
|
? ' fixed bottom-40'
|
||||||
|
: 'fixed bottom-19'
|
||||||
|
: 'fixed bottom-19',
|
||||||
|
]"
|
||||||
|
:style="{
|
||||||
|
transform: `rotate(0deg)`,
|
||||||
|
transition: 'transform 0.3s ease',
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="item in uploadList"
|
v-for="item in uploadList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@ -250,8 +271,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 知识库-->
|
|
||||||
|
|
||||||
|
<!-- 知识库-->
|
||||||
<div
|
<div
|
||||||
@click="toggleKnowledge"
|
@click="toggleKnowledge"
|
||||||
v-if="!uploadList.length"
|
v-if="!uploadList.length"
|
||||||
@ -280,6 +301,7 @@
|
|||||||
<view class="flex items-center px-4 py-2.5 border-t border-t-solid border-[#E7E7E7]">
|
<view class="flex items-center px-4 py-2.5 border-t border-t-solid border-[#E7E7E7]">
|
||||||
<input
|
<input
|
||||||
v-model="inputText"
|
v-model="inputText"
|
||||||
|
@focus="onFocus"
|
||||||
@keyup.enter="sendText('')"
|
@keyup.enter="sendText('')"
|
||||||
placeholder="想对我说点什么~"
|
placeholder="想对我说点什么~"
|
||||||
class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-1 focus:outline-none"
|
class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-1 focus:outline-none"
|
||||||
@ -308,8 +330,8 @@
|
|||||||
<view
|
<view
|
||||||
v-show="showActions"
|
v-show="showActions"
|
||||||
:class="[
|
:class="[
|
||||||
'flex justify-around items-center h-20 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]',
|
||||||
showActions ? (uploadList.length ? 'pt-1' : 'pt-1') : 'pt-0',
|
showActions ? (uploadList.length ? 'pt-10' : 'pt-10') : 'pt-0',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<view class="flex flex-col items-center">
|
<view class="flex flex-col items-center">
|
||||||
@ -401,6 +423,7 @@ interface IMessage {
|
|||||||
timestamp: Date
|
timestamp: Date
|
||||||
}
|
}
|
||||||
const userAvatar = ref('')
|
const userAvatar = ref('')
|
||||||
|
const chatMode = ref('tongyi-app')
|
||||||
|
|
||||||
const baseUrl = getEnvBaseUrl()
|
const baseUrl = getEnvBaseUrl()
|
||||||
const messages = reactive<IMessage[]>([])
|
const messages = reactive<IMessage[]>([])
|
||||||
@ -474,7 +497,7 @@ async function goChat(listUuid: string) {
|
|||||||
await fetchHistoryDiets(listUuid)
|
await fetchHistoryDiets(listUuid)
|
||||||
showPopup.value = false
|
showPopup.value = false
|
||||||
}
|
}
|
||||||
|
// qwen-vl-plus
|
||||||
/** 1. 创建聊天会话 */
|
/** 1. 创建聊天会话 */
|
||||||
const listUuid = ref('')
|
const listUuid = ref('')
|
||||||
|
|
||||||
@ -484,18 +507,15 @@ async function createChatSession() {
|
|||||||
url: `${baseUrl}/chat/create`,
|
url: `${baseUrl}/chat/create`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
gptModel: 'gpt-4-vision-preview',
|
gptModel: chatMode.value,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
Authorization: token.value,
|
Authorization: token.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// 如果后台返回新的会话信息,可以在这里处理,比如拿到 listUuid 等
|
// 如果后台返回新的会话信息,可以在这里处理,比如拿到 listUuid 等
|
||||||
console.log('createChatSession →', createResp)
|
|
||||||
listUuid.value = createResp.data.data.listUuid
|
listUuid.value = createResp.data.data.listUuid
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
console.error('createChatSession error:', err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ------------------
|
// ------------------
|
||||||
// 拉取历史记录接口(替换为你自己的 API)
|
// 拉取历史记录接口(替换为你自己的 API)
|
||||||
@ -532,21 +552,145 @@ async function fetchHistoryDiets(value) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
listUuid: value,
|
listUuid: value,
|
||||||
gptModel: 'gpt-4-vision-preview',
|
gptModel: chatMode.value,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
Authorization: token.value,
|
Authorization: token.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (resp.data) {
|
console.log(resp, '/**************resp*********************/')
|
||||||
rawList.value = resp.data
|
if (resp && resp.data) {
|
||||||
console.log('fetchHistoryLisssst →', rawList.value)
|
const rawList = resp.data.data // 假设后端直接返回消息数组
|
||||||
|
const newMessages = parseBackendMessages(rawList)
|
||||||
|
// 用解析后的消息替换当前消息列表
|
||||||
|
messages.splice(0, messages.length, ...newMessages)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('fetchHistoryList error:', err)
|
console.error('fetchHistoryList error:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const token = ref<string>('')
|
|
||||||
|
function parseBackendMessages(rawList: any[]): IMessage[] {
|
||||||
|
const messageList: IMessage[] = []
|
||||||
|
|
||||||
|
rawList.forEach((item) => {
|
||||||
|
const rawContent: string = item.text ?? item.content ?? item.message ?? ''
|
||||||
|
// 先尝试 map[...] 的老逻辑
|
||||||
|
let parts: ParsedPart[] | null = tryParseMapFormat(rawContent)
|
||||||
|
|
||||||
|
// 如果没命中 map[...],再看看是不是一个标准的 JSON 数组字符串
|
||||||
|
if (!parts && rawContent.startsWith('[') && rawContent.endsWith(']')) {
|
||||||
|
try {
|
||||||
|
const arr = JSON.parse(rawContent) as Array<{ text: string; type: string }>
|
||||||
|
if (Array.isArray(arr)) {
|
||||||
|
parts = arr.map((el) => {
|
||||||
|
if (el.type === 'text') {
|
||||||
|
return { type: 'text', content: el.text }
|
||||||
|
}
|
||||||
|
if (el.type === 'image_url' || el.type === 'image') {
|
||||||
|
return {
|
||||||
|
type: 'image',
|
||||||
|
content: [{ url: el.text, uploadFileType: 'image' }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 其他类型按需扩展
|
||||||
|
return { type: el.type, content: el.text }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 解析失败就继续下面的 fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts) {
|
||||||
|
// 拆分成多条消息推入
|
||||||
|
parts.forEach((part) => {
|
||||||
|
messageList.push({
|
||||||
|
role: item.role,
|
||||||
|
type: part.type as any, // 'text' 或 'image'
|
||||||
|
content: part.content,
|
||||||
|
timestamp: (item.CreatedAt ?? 0) * 1000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 你原来的 fallback 分支(单条,直接塞 content,type 也补成 'text')
|
||||||
|
messageList.push({
|
||||||
|
role: item.role,
|
||||||
|
type: 'text',
|
||||||
|
content: rawContent,
|
||||||
|
timestamp: (item.CreatedAt ?? 0) * 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return messageList
|
||||||
|
}
|
||||||
|
interface ParsedPart {
|
||||||
|
type: string
|
||||||
|
content: string | UploadFile[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryParseMapFormat(str: string): ParsedPart[] | null {
|
||||||
|
if (!str || !str.startsWith('[') || !str.endsWith(']') || !str.includes('map[')) {
|
||||||
|
return null // 不符合 [map[...] ...] 格式
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ParsedPart[] = []
|
||||||
|
// 提取所有 map[...] 子串
|
||||||
|
const pattern = /map\[([^\]]+)\]/g
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
while ((match = pattern.exec(str)) !== null) {
|
||||||
|
const contentBlock = match[1] // 如 "text:这种 type:text"
|
||||||
|
const typeIndex = contentBlock.indexOf(' type:')
|
||||||
|
if (typeIndex === -1) {
|
||||||
|
continue // 安全检查,正常情况下一定有 ' type:' 分隔
|
||||||
|
}
|
||||||
|
// 拆分内容部分和类型部分
|
||||||
|
const contentPart = contentBlock.substring(0, typeIndex).trim() // "text:这种"
|
||||||
|
const typeValue = contentBlock.substring(typeIndex + 6).trim() // 跳过 " type:" 得到 "text" 或 "image_url"
|
||||||
|
// 解析内容键和值
|
||||||
|
const colonIndex = contentPart.indexOf(':')
|
||||||
|
if (colonIndex === -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const key = contentPart.substring(0, colonIndex).trim() // 键,比如 "text" 或 "image_url"
|
||||||
|
let value = contentPart.substring(colonIndex + 1).trim() // 值,比如 "这种" 或 "https://cdn.xx/xxx.jpg"
|
||||||
|
// 去掉包裹在值两端的引号(如果有的话)
|
||||||
|
if (
|
||||||
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))
|
||||||
|
) {
|
||||||
|
value = value.substring(1, value.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型构造 ParsedPart 对象
|
||||||
|
if (typeValue === 'text' && key === 'text') {
|
||||||
|
result.push({
|
||||||
|
type: 'text',
|
||||||
|
content: value,
|
||||||
|
})
|
||||||
|
} else if (typeValue === 'image_url' && key === 'image_url') {
|
||||||
|
// 将图片URL封装为 UploadFile 对象数组
|
||||||
|
result.push({
|
||||||
|
type: 'image', // 前端统一用 'image' 作为图片消息类型
|
||||||
|
content: [{ url: value, uploadFileType: 'image' }],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 其他类型的内容,可根据需要扩展处理
|
||||||
|
result.push({
|
||||||
|
type: typeValue,
|
||||||
|
content: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果成功解析出至少一个片段,则返回数组;否则返回 null
|
||||||
|
return result.length > 0 ? result : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = ref<string>(
|
||||||
|
'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfc878a18b3738809e20de39acfd5430450f2bc3741057dd4ce71ccf64ea02f6a91fd001fa7bde90187008f19e848c70a002c37df28be05b4790e962f001a1361e90f1423dfc5b018ca9fac85ad2fafaef6',
|
||||||
|
)
|
||||||
const userInfo = ref<any>({})
|
const userInfo = ref<any>({})
|
||||||
const refreshToken = ref<string>('')
|
const refreshToken = ref<string>('')
|
||||||
const statusBarHeight = ref<number>(0)
|
const statusBarHeight = ref<number>(0)
|
||||||
@ -558,7 +702,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const wv = plus.webview.currentWebview()
|
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) || {}
|
userInfo.value = JSON.parse(wv.userInfo) || {}
|
||||||
refreshToken.value = wv.refreshToken || uni.getStorageSync('refreshToken')
|
refreshToken.value = wv.refreshToken || uni.getStorageSync('refreshToken')
|
||||||
statusBarHeight.value = wv.statusBarHeight || uni.getSystemInfoSync().statusBarHeight
|
statusBarHeight.value = wv.statusBarHeight || uni.getSystemInfoSync().statusBarHeight
|
||||||
@ -568,23 +712,6 @@ onMounted(() => {
|
|||||||
await fetchHistoryList()
|
await fetchHistoryList()
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
|
|
||||||
// // 2. 如果在 Plus 环境里,等 plusready
|
|
||||||
// if (window.plus && plus.webview) {
|
|
||||||
// document.addEventListener('plusready', init, false)
|
|
||||||
// // plusready 可能已经触发过,直接再调用一次以防万一
|
|
||||||
// if (plus.webview.currentWebview()) {
|
|
||||||
// init()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // 3. 普通 H5 调试,直接从 storage/SystemInfo 拿
|
|
||||||
// else {
|
|
||||||
// token.value = uni.getStorageSync('token') || import.meta.env.VITE_DEV_TOKEN
|
|
||||||
// userInfo.value = uni.getStorageSync('userInfo')
|
|
||||||
// refreshToken.value = uni.getStorageSync('refreshToken')
|
|
||||||
// statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight
|
|
||||||
// createChatSession().then(fetchHistoryList)
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
@ -651,6 +778,7 @@ async function newChat() {
|
|||||||
}
|
}
|
||||||
const rotation = ref(0)
|
const rotation = ref(0)
|
||||||
function toggleActions() {
|
function toggleActions() {
|
||||||
|
closeKeyboard()
|
||||||
showActions.value = !showActions.value
|
showActions.value = !showActions.value
|
||||||
rotation.value += 45
|
rotation.value += 45
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
@ -661,6 +789,7 @@ const uploadFileTypeEm = {
|
|||||||
image: 'image',
|
image: 'image',
|
||||||
video: 'video',
|
video: 'video',
|
||||||
file: 'file',
|
file: 'file',
|
||||||
|
text: 'text',
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传配置
|
// 上传配置
|
||||||
@ -914,7 +1043,11 @@ const msgLoading = ref(true)
|
|||||||
async function sendText(msgData) {
|
async function sendText(msgData) {
|
||||||
msgLoading.value = true
|
msgLoading.value = true
|
||||||
const text = inputText.value.trim()
|
const text = inputText.value.trim()
|
||||||
if (!text) {
|
const dataBlo = toRaw(msgData)
|
||||||
|
console.log(dataBlo)
|
||||||
|
if (!text && dataBlo == '') {
|
||||||
|
msgLoading.value = false
|
||||||
|
|
||||||
uni.showToast({ title: '请输入信息', icon: 'error' })
|
uni.showToast({ title: '请输入信息', icon: 'error' })
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -935,12 +1068,14 @@ async function sendText(msgData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加用户文本消息
|
// 添加用户文本消息
|
||||||
addMessage({
|
addMessage(
|
||||||
role: 'user',
|
msgData || {
|
||||||
type: 'text',
|
role: 'user',
|
||||||
content: text,
|
type: 'text',
|
||||||
timestamp: new Date(),
|
content: text,
|
||||||
})
|
timestamp: new Date(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
//图片、视频、文件分开发送
|
//图片、视频、文件分开发送
|
||||||
if (tempUploadList.length > 0) {
|
if (tempUploadList.length > 0) {
|
||||||
@ -969,11 +1104,12 @@ async function sendText(msgData) {
|
|||||||
showImageMask.value = showMoreImgMask
|
showImageMask.value = showMoreImgMask
|
||||||
} else {
|
} else {
|
||||||
//更新上下文消息
|
//更新上下文消息
|
||||||
historyUserMsgs.push({
|
!msgData &&
|
||||||
role: 'user',
|
historyUserMsgs.push({
|
||||||
content: text,
|
role: 'user',
|
||||||
timestamp: new Date(),
|
content: text,
|
||||||
})
|
timestamp: new Date(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
inputText.value = ''
|
inputText.value = ''
|
||||||
@ -987,19 +1123,19 @@ async function sendText(msgData) {
|
|||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
}
|
}
|
||||||
addMessage(aiMsg)
|
addMessage(aiMsg)
|
||||||
|
|
||||||
//清除上传列表
|
//清除上传列表
|
||||||
uploadList.splice(0, uploadList.length)
|
uploadList.splice(0, uploadList.length)
|
||||||
// resds.value = historyUserMsgs
|
|
||||||
|
|
||||||
const body: IGptRequestBody = {
|
const body: IGptRequestBody = {
|
||||||
model: 'gpt-4-vision-preview',
|
model: chatMode.value,
|
||||||
max_tokens: 1000,
|
max_tokens: 1000,
|
||||||
temperature: 1,
|
temperature: 1,
|
||||||
listUuid: listUuid.value,
|
listUuid: listUuid.value,
|
||||||
top_p: 1,
|
top_p: 1,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
frequency_penalty: 0,
|
frequency_penalty: 0,
|
||||||
messages: msgData || historyUserMsgs,
|
messages: msgData ? [msgData] : historyUserMsgs,
|
||||||
stream: true,
|
stream: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1011,6 +1147,7 @@ async function sendText(msgData) {
|
|||||||
headers: { 'Content-Type': 'application/json', Authorization: token.value },
|
headers: { 'Content-Type': 'application/json', Authorization: token.value },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
})
|
})
|
||||||
|
|
||||||
const reader = resp.body!.getReader()
|
const reader = resp.body!.getReader()
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
let buffer = ''
|
let buffer = ''
|
||||||
@ -1021,14 +1158,17 @@ async function sendText(msgData) {
|
|||||||
done = streamDone
|
done = streamDone
|
||||||
if (value) {
|
if (value) {
|
||||||
buffer += decoder.decode(value, { stream: true })
|
buffer += decoder.decode(value, { stream: true })
|
||||||
const parts = buffer.split('data: ')
|
const lines = buffer.split(/\r?\n/)
|
||||||
buffer = parts.pop()!
|
buffer = lines.pop()!
|
||||||
for (const part of parts) {
|
|
||||||
scrollToBottom()
|
for (const line of lines) {
|
||||||
console.log('1', aiMsg.content)
|
// 只处理以 "data: " 开头的行
|
||||||
const chunk = part.trim()
|
if (!line.startsWith('data: ')) continue
|
||||||
|
const chunk = line.slice(6).trim()
|
||||||
|
|
||||||
if (chunk === '[DONE]') {
|
if (chunk === '[DONE]') {
|
||||||
done = true
|
done = true
|
||||||
|
console.log('sss')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -1045,13 +1185,16 @@ async function sendText(msgData) {
|
|||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//更新上下文消息
|
||||||
|
done && historyUserMsgs.push(aiMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//更新上下文消息
|
|
||||||
historyUserMsgs.push(aiMsg)
|
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
aiMsg.content = '请重新发送'
|
||||||
|
//更新messages消息
|
||||||
|
messages[messages.length - 1] = { ...aiMsg }
|
||||||
console.error(err)
|
console.error(err)
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@ -1075,18 +1218,35 @@ function copyText(msg: IMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshText() {
|
function refreshText() {
|
||||||
const lastUserMsg = historyUserMsgs[historyUserMsgs.length - 2]
|
const lastUserMsg = historyUserMsgs[historyUserMsgs.length - 1]
|
||||||
// resds.value = lastUserMsg
|
lastUserMsg.timestamp = new Date()
|
||||||
|
|
||||||
sendText(lastUserMsg)
|
sendText(lastUserMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const knowledgeOpen = ref(false)
|
const knowledgeOpen = ref(false)
|
||||||
function toggleKnowledge() {
|
function toggleKnowledge() {
|
||||||
|
console.error('44444', chatMode.value)
|
||||||
|
if (chatMode.value == 'tongyi-app') {
|
||||||
|
chatMode.value = 'qwen-vl-plus'
|
||||||
|
} else {
|
||||||
|
chatMode.value = 'tongyi-app'
|
||||||
|
}
|
||||||
knowledgeOpen.value = !knowledgeOpen.value
|
knowledgeOpen.value = !knowledgeOpen.value
|
||||||
showActions.value = false
|
showActions.value = false
|
||||||
rotation.value = 0
|
rotation.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//收起键盘
|
||||||
|
const closeKeyboard = () => {
|
||||||
|
uni.hideKeyboard()
|
||||||
|
rotation.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//输入框聚焦事件
|
||||||
|
const onFocus = () => {
|
||||||
|
showActions.value = false
|
||||||
|
rotation.value = 0
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -1191,4 +1351,8 @@ function toggleKnowledge() {
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tops {
|
||||||
|
padding-top: var(--status-bar-height);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="flex flex-col h-screen bg-#ffffff">
|
<view class="flex flex-col h-screen bg-#ffffff tops">
|
||||||
<view
|
<view
|
||||||
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 py-3 bg-white shadow-md h-20 pt-10 z-999 fixed top-0 w-full box-border"
|
||||||
>
|
>
|
||||||
@ -64,4 +64,8 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.tops {
|
||||||
|
padding-top: var(--status-bar-height);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
15
src/store/status/index.js
Normal file
15
src/store/status/index.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {ref} from 'vue'
|
||||||
|
import {createGlobalState, useStorage} from "@vueuse/core";
|
||||||
|
import {uniStorage} from "@/utils/uniStorage";
|
||||||
|
export const useStatus =createGlobalState(()=>{
|
||||||
|
const currentNavbar=ref({title:'',url:''})
|
||||||
|
const applyTabbarIndex=ref(0)
|
||||||
|
const statusBarHeight = ref(window?.plus?.navigator?.getStatusbarHeight() ?? 0)
|
||||||
|
const tabBarIndex = useStorage('tabBarIndex', 0, uniStorage)
|
||||||
|
return {
|
||||||
|
statusBarHeight,
|
||||||
|
applyTabbarIndex,
|
||||||
|
currentNavbar,
|
||||||
|
tabBarIndex
|
||||||
|
}
|
||||||
|
})
|
11
src/utils/uniStorage.js
Normal file
11
src/utils/uniStorage.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const uniStorage = {
|
||||||
|
getItem(key) {
|
||||||
|
return uni.getStorageSync(key) || null
|
||||||
|
},
|
||||||
|
setItem(key, value) {
|
||||||
|
return uni.setStorageSync(key, value)
|
||||||
|
},
|
||||||
|
removeItem(key) {
|
||||||
|
return uni.removeStorageSync(key)
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user