Compare commits

...

4 Commits

Author SHA1 Message Date
68c5ffdb64 feat: merge code 2025-06-06 11:33:11 +08:00
6296bc5e19 feat: ai聊天开始提交测试 2025-06-06 11:12:09 +08:00
af586485f0 feat: 剩余切换模型问题 2025-05-27 17:05:29 +08:00
550a35effc feat: backup code 2025-05-23 15:05:31 +08:00
34 changed files with 3554 additions and 2560 deletions

View File

@ -54,5 +54,6 @@ module.exports = {
'scss/comment-no-empty': null,
'selector-class-pattern': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-property-value-no-unknown': null,
},
}

View File

@ -4,3 +4,5 @@ NODE_ENV = 'dev'
VITE_DELETE_CONSOLE = false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
VITE_SERVER_BASEURL = 'http://114.218.158.24:9020'

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,6 @@
"spacing": "3px",
"list": []
},
"__esModule": true,
"pages": [
{
"path": "pages/index/index",
@ -41,14 +40,6 @@
"navigationBarTitleText": "关于"
}
},
{
"path": "pages/index/index1",
"type": "page",
"layout": "default",
"style": {
"navigationBarHidden": true
}
},
{
"path": "pages/preview/index",
"type": "page"

File diff suppressed because it is too large Load Diff

View File

@ -1,553 +0,0 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarHidden: true,
},
}
</route>
<template>
<div class="flex flex-col h-screen bg-gray-50">
<!-- Navigation Bar -->
<div class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-10">
<image src="/static/aichat/back.png" class="w-2 h-4" @click="goBack" />
<div class="text-lg font-medium">小墨</div>
<div class="flex items-center space-x-3">
<image src="/static/aichat/time.png" class="w-5 h-5" @click="viewHistory" />
<image src="/static/aichat/new.png" class="w-5 h-5" @click="newChat" />
</div>
</div>
<!-- 消息区 -->
<div :class="['flex relative', showActions ? 'h-105' : 'h-130']">
<!-- 背景层 -->
<div class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
<image src="/static/aichat/logo.png" class="w-20 h-24 mb-4" @click="newChat" />
<view class="text-xl font-medium mb-1"> 我是小墨</view>
<view class="text-gray-400">开启新的聊天吧</view>
</div>
z
<div
ref="scrollEl"
class="flex-1 overflow-y-auto bg-gray-50"
:class="showActions ? 'pb-44' : 'pb-16'"
>
<div :class="['relative z-10 px-4 py-6', showActions ? 'mb--11 h-105' : 'mb--21 h-135']">
<template v-for="(msg, idx) in messages" :key="idx">
<view v-if="shouldShowTimestamp(idx)" class="text-center text-xs text-gray-500 my-2">
{{ formatDayGroup(msg.timestamp) }}
</view>
<view
class="flex items-start"
:class="msg.role === 'assistant' ? 'justify-start' : 'justify-end'"
>
<image
v-if="msg.role === 'assistant'"
:src="assistantAvatar"
class="w-8 h-8 rounded-full mr-2 mt-1"
/>
<view class="relative max-w-[70%] mt-4 mb-3">
<view
:class="[
'absolute -top-4 text-xs text-gray-400',
msg.role === 'assistant' ? 'left-0' : 'right-0',
]"
>
{{ formatTimeShort(msg.timestamp) }}
</view>
<view
:class="[
'py-2 px-3 rounded-lg break-words mt-1',
msg.role === 'assistant'
? 'bg-[#f9f8fd] text-black shadow'
: 'bg-[#45299e] text-white',
]"
>
{{ msg.content }}
</view>
<view
v-if="msg.role === 'assistant' && msg.type === 'text'"
class="absolute bottom-0 flex space-x-3"
>
<image src="/static/aichat/copy.png" class="w-4 h-4" @click="copyText(msg)" />
<image
src="/static/aichat/resect.png"
class="w-4 h-4"
@click="refreshText(msg)"
/>
</view>
</view>
<image
v-if="msg.role === 'user'"
:src="userAvatar"
class="w-8 h-8 rounded-full ml-2 mt-1"
/>
</view>
</template>
</div>
</div>
</div>
<!-- 底部上传预览 + 输入区 -->
<div
:class="[
'fixed bottom-0 left-0 right-0 bg-white z-[80] overflow-hidden transition-all duration-300',
showActions ? 'h-45' : 'h-20',
]"
>
<!-- 上传列表 -->
<div v-if="uploadList.length" class="flex px-4 py-2 overflow-x-auto space-x-3 bg-white">
<div
v-for="item in uploadList"
:key="item.id"
class="relative w-16 h-16 rounded overflow-hidden"
>
<!-- 预览图成功后用后端返回的 URL上传中可以先用本地预览 -->
<img
:src="item.url || item.localPath"
class="w-full h-full object-cover"
@click="previewImage(item.url || item.localPath)"
/>
<!-- 关闭按钮 -->
<div
class="absolute top-1 right-1 w-4 h-4 rounded-full bg-black bg-opacity-50 flex items-center justify-center cursor-pointer text-white text-xs"
@click="removeImage(item.id)"
>
×
</div>
<!-- 进度 / 成功 / 失败 -->
<div
class="absolute bottom-0 left-0 w-full text-xs text-center text-white py-1"
:class="{
'bg-black bg-opacity-50': item.status === 'uploading',
'bg-green-600 bg-opacity-50': item.status === 'success',
'bg-red-600 bg-opacity-50': item.status === 'fail',
}"
>
<template v-if="item.status === 'uploading'">{{ item.progress }}%</template>
<template v-else-if="item.status === 'success'"> 成功</template>
<template v-else>
失败
<span class="cursor-pointer" @click.stop="retry(item)"></span>
</template>
</div>
</div>
</div>
<!-- 输入 + 切换 -->
<view class="flex items-center px-4 py-2.5 border-t border-solid border-[#E7E7E7]">
<input
v-model="inputText"
@keyup.enter="sendText"
placeholder="想对我说点什么~"
class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-full focus:outline-none"
/>
<image src="/static/aichat/add-circle.png" class="w-7 h-7 mx-3" @click="toggleActions" />
<image
src="/static/aichat/enter.png"
class="w-7 h-7"
@click="sendText"
:disabled="loading"
/>
</view>
<!-- 操作面板 -->
<transition name="slide-up">
<view
v-show="showActions"
class="flex justify-around items-center h-20 border-t border-solid border-[#E7E7E7] bg-white"
>
<view class="flex flex-col items-center">
<image src="/static/aichat/phone-img.png" class="w-13 h-13" @click="onPickImage" />
<span class="text-xs mt-1 text-gray-500">照片</span>
</view>
<view class="flex flex-col items-center">
<image src="/static/aichat/photo.png" class="w-13 h-13" @click="onTakePhoto" />
<span class="text-xs mt-1 text-gray-500">拍摄</span>
</view>
<view class="flex flex-col items-center">
<image src="/static/aichat/files.png" class="w-13 h-13" @click="onPickFile" />
<span class="text-xs mt-1 text-gray-500">文件</span>
</view>
</view>
</transition>
</div>
</div>
1
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue'
import dayjs from 'dayjs'
import { useUserStore } from '@/store'
import { getEnvBaseUrl } from '@/utils'
import type { IGptRequestBody } from '@/service/index/foo'
interface IUpload {
id: number
url: string
filePath: string
status: 'uploading' | 'success' | 'fail'
progress: number
detail: string
mask: string
}
interface IMessage {
role: 'user' | 'assistant'
type: 'text' | 'images'
content: string | string[]
timestamp: Date
}
interface UploadItem {
id: string
localPath: string //
url: string // 线 URL
status: 'uploading' | 'success' | 'fail'
progress: number // %
}
const assistantAvatar =
'https://dci-file-new.bj.bcebos.com/fonchain-main/test/runtime/image/avatar/40/b8ed6fea-6662-416d-8bb3-1fd8a8197061.jpg'
const userAvatar = assistantAvatar
const baseUrl = getEnvBaseUrl()
const token = useUserStore().userInfo.token || import.meta.env.VITE_DEV_TOKEN || ''
const messages = reactive<IMessage[]>([])
const inputText = ref('')
const loading = ref(false)
const showActions = ref(false)
const scrollEl = ref<HTMLElement>()
const uploadList = reactive<IUpload[]>([])
const uploadId = ref(0)
function scrollToBottom() {
const el = scrollEl.value!
nextTick(() => {
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })
})
}
function addMessage(msg: IMessage) {
messages.push(msg)
scrollToBottom()
}
const shouldShowTimestamp = (i: number) => {
if (i === 0) return true
return !dayjs(messages[i].timestamp).isSame(messages[i - 1].timestamp, 'day')
}
const formatDayGroup = (d: Date) => dayjs(d).format('YYYY/MM/DD HH:mm')
const formatTimeShort = (d: Date) => dayjs(d).format('MM/DD HH:mm')
function goBack() {
window.history.back()
}
function viewHistory() {
uni.navigateTo({ url: '/pages/history/history' })
}
function newChat() {
messages.splice(0)
inputText.value = ''
}
function toggleActions() {
showActions.value = !showActions.value
scrollToBottom()
}
//
function onPickImage() {
uni.chooseImage({
count: 10,
success: (res: any) => {
res.tempFilePaths.forEach((path) => {
uploadId.value += 1
const id = uploadId.value
const upload: IUpload = { id, url: path, filePath: path, status: 'uploading', progress: 0 }
uploadList.push(upload)
uploadFile(path, upload)
})
},
})
}
//
function onTakePhoto() {
uni.chooseImage({
sourceType: ['camera'],
count: 1,
success: (res: any) => {
const path = res.tempFilePaths[0]
uploadId.value += 1
const id = uploadId.value
const upload: IUpload = { id, url: path, filePath: path, status: 'uploading', progress: 0 }
uploadList.push(upload)
uploadFile(path, upload)
},
})
}
//
//
function onPickFile(path: string, detail = '') {
const id = Date.now().toString()
const item: UploadItem = {
id,
localPath: path,
url: '',
status: 'uploading',
progress: 0,
}
uploadList.push(item)
startUpload(item, detail)
}
interface UploadFile {
uid: string
name: string
size: number
progress: number // 0-100
status: 'uploading' | 'success' | 'error'
file: File
url?: string // URLURL
}
const filesList = ref<UploadFile[]>([])
async function startUpload(item: UploadItem, detail: string) {
const userStore = useUserStore()
//
item.status = 'uploading'
item.progress = 0
// ( success/fail )
// @ts-ignore: uni.uploadFile UploadTask Promise
const uploadTask: UniApp.UploadTask & Promise<UniApp.UploadFileRes> = uni.uploadFile({
// url: 'http://114.218.158.24:9020/upload/multi',
url: 'https://ukw0y1.laf.run/upload',
filePath: item.localPath,
name: 'k1', // name formData key
formData: {
source: 'chat',
mask: '2076',
detail,
type: 'image',
},
})
//
uploadTask.onProgressUpdate((res: { progress: number }) => {
item.progress = res.progress
})
// 3s abort
const timeoutId = setTimeout(() => {
if (item.status === 'uploading') {
uploadTask.abort()
item.status = 'fail'
}
}, 3000)
try {
//
const res = await uploadTask
clearTimeout(timeoutId)
// JSON
const resp = JSON.parse(res.data) as {
code: number
data: Record<string, string>
}
console.log(resp, 'resp')
if (resp.code === 0 && resp.data) {
// data
const urls = Object.values(resp.data).filter((u) => !!u)
if (urls.length > 0) {
item.url = urls[0]
item.status = 'success'
} else {
console.warn('没有拿到任何上传后的 URL')
item.status = 'fail'
}
} else {
console.warn('后端返回异常 code=', resp.code)
item.status = 'fail'
}
} catch (err) {
clearTimeout(timeoutId)
console.error('uploadFile 出错:', err)
item.status = 'fail'
}
}
//
function retry(item: UploadItem) {
item.status = 'uploading'
item.progress = 0
startUpload(item, '') // detail
}
//
function removeImage(id: string) {
const idx = uploadList.findIndex((i) => i.id === id)
if (idx >= 0) uploadList.splice(idx, 1)
}
// uni.previewImage
function previewImage(url: string) {
uni.previewImage({ urls: [url] })
}
// 使 postUpload
async function uploadFile(path: string, upload: IUpload) {
//
upload.status = 'uploading'
try {
// @ts-ignore uni.uploadFile Promise
const res: UniApp.UploadFileRes = await uni.uploadFile({
// url: 'http://114.218.158.24:9020/upload/multi',
url: 'https://ukw0y1.laf.run/upload',
filePath: path,
name: 'file', // name formData key data.data
formData: {
source: 'chat',
mask: '2076',
type: 'image',
k1: 'xxxx.png',
k2: 'xxxx.png',
},
})
// JSON
const resp = JSON.parse(res.data) as {
code: number
data: Record<string, string>
}
if (resp.code === 0 && resp.data) {
// data upload.url
let found = false
for (const key in resp.data) {
const url = resp.data[key]
if (url) {
upload.url = url
found = true
break
}
}
if (found) {
upload.status = 'success'
} else {
console.warn('没有取到任何上传后的 URL')
upload.status = 'fail'
}
} else {
console.warn('后端返回异常 code=', resp.code)
upload.status = 'fail'
}
} catch (err) {
console.error('uploadFile 出错:', err)
upload.status = 'fail'
}
}
async function sendText() {
const text = inputText.value.trim()
if (!text || loading.value) return
addMessage({ role: 'user', type: 'text', content: text, timestamp: new Date() })
inputText.value = ''
loading.value = true
const aiMsg: IMessage = {
role: 'assistant',
type: 'text',
content: '',
timestamp: new Date(),
}
addMessage(aiMsg)
const body: IGptRequestBody = {
model: 'gpt-4-vision-preview',
max_tokens: 1000,
temperature: 1,
top_p: 1,
presence_penalty: 0,
frequency_penalty: 0,
messages: [{ role: 'user', content: [{ type: 'text', text }] }],
stream: true,
}
try {
const resp = await fetch(baseUrl + '/chat/completion', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: token },
body: JSON.stringify(body),
})
const reader = resp.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''
let done = false
while (!done) {
const { value, done: streamDone } = await reader.read()
done = streamDone
if (value) {
buffer += decoder.decode(value, { stream: true })
const parts = buffer.split('data: ')
buffer = parts.pop()!
for (const part of parts) {
scrollToBottom()
console.log('1')
const chunk = part.trim()
if (chunk === '[DONE]') {
done = true
break
}
try {
const json = JSON.parse(chunk)
const delta = json.choices?.[0]?.delta?.content
if (delta) {
aiMsg.content += delta
scrollToBottom()
console.log('2')
}
} catch {}
}
}
}
scrollToBottom()
} catch (err) {
console.error(err)
} finally {
loading.value = false
showActions.value = false
}
}
function copyText(msg: IMessage) {
if (typeof msg.content === 'string') {
navigator.clipboard.writeText(msg.content)
alert('已复制')
}
}
function refreshText(msg: IMessage) {
if (typeof msg.content === 'string') {
inputText.value = msg.content
const idx = messages.indexOf(msg)
messages.splice(idx, 1)
sendText()
}
}
</script>
<style lang="scss" scoped>
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.3s ease-out;
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(100%);
}
.slide-up-enter-to,
.slide-up-leave-from {
transform: translateY(0%);
}
.font-pf {
font-family: PingFangSC-Medium, sans-serif;
}
</style>

View File

@ -0,0 +1,57 @@
import { getEnvBaseUrl } from '@/utils'
import {httpPost} from "@/utils/http"
import { TOKEN } from './test';
import {apis,uploadFile} from "@/utils/tools"
const baseUrl = getEnvBaseUrl();
// /artwork/get-chunk-list 获取文件分断数据
// /artwork/upload-chunk 分断上传画作图片
export const uploadFiles = (url,params)=>{
let token = uni.getStorageSync('authorization');
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl+url,
filePath: params.tempFilePath,
name: "Chunk",
formData:params.formData,
header: {
'authorization': token,
},
complete: (res) => {
// console.log('res: ',res);
if(res.statusCode == 200) {
resolve(res)
} else {
reject(res)
}
}
})
})
}
const getChunkList=(file)=>{
return new Promise((resolve,reject)=>{
uni.request({
url:baseUrl+"/artwork/get-chunk-list",
data:file,
header:{
"Content-Type":"application/json", //"multipart/form-data"
},
success(res){
resolve(res)
},
fail(rej){
reject(Jrej)
}
})
})
}
export const uploadFileChunk=(data)=>{
const header={
authorization:TOKEN
}
let token = uni.getStorageSync('authorization');
return uploadFiles('/artwork/upload-chunk',data)
}

View File

@ -0,0 +1,205 @@
// 发送消息
async function sendText1(msgData = '') {
// let msgData=''
// if(msg===''){
// msgData=msg
// }else if(msg.detail && msg.detail.value){
// msgData=msg.detail.value
// }
// console.log('msgData',msg)
// uni.showToast({ title: inputText.value, icon: 'error' })
msgLoading.value = true
const text = inputText.value.trim()
const dataBlo = text //toRaw(msgData)
console.log('dataBlo', dataBlo)
if (!text) {
msgLoading.value = false
uni.showToast({
title: '请输入信息',
icon: 'error'
})
return
}
if (loading.value) return
inputText.value = ''
loading.value = true
const tempUploadList = Object.assign([], uploadList)
let fileList: any[] = []
if (tempUploadList.length > 0) {
fileList = tempUploadList.map((item: UploadFile) => {
if (item.uploadFileType === uploadFileTypeEm.image) {
// 图片格式
return {
type: 'image_url',
image_url: {
url: item.url,
},
}
} else if (item.uploadFileType === uploadFileTypeEm.video) {
console.log(item, '====item=====')
// 视频格式(改成和图片一样的结构)
return {
type: 'video_url',
video_url: {
url: item.ori_url,
},
}
} else {
// 其他文件类型保持原样
const fileType = `${item.uploadFileType}_url`
return {
type: fileType,
[fileType]: item.url,
}
}
})
}
// 添加用户文本消息
addMessage({
role: 'user',
type: 'text',
content: text,
timestamp: new Date(),
})
//图片、视频、文件分开发送
if (tempUploadList.length > 0) {
Object.values(uploadFileTypeEm).forEach((item: any) => {
if (tempUploadList.find((v: UploadFile) => v.uploadFileType === item)) {
addMessage({
role: 'user',
type: item,
content: tempUploadList.filter((v) => v.uploadFileType === item),
timestamp: new Date(),
})
}
})
//更新上下文消息
historyUserMsgs.push({
role: 'user',
content: [{
type: 'text',
text
}, ...fileList],
timestamp: new Date(),
})
//显示更多图片遮罩
const showMoreImgMask = tempUploadList.filter((v: UploadFile) => v.uploadFileType === uploadFileTypeEm.image).length > 4
showImageMask.value = showMoreImgMask
} else {
//更新上下文消息
historyUserMsgs.push({
role: 'user',
content: text,
timestamp: new Date(),
})
}
// AI消息
// const aiMsg: IMessage = {
const aiMsg = {
role: 'assistant',
type: 'text',
content: inputText.value,
timestamp: new Date(),
}
addMessage(aiMsg)
//清除上传列表
uploadList.splice(0, uploadList.length)
// console.log('[msgData] : historyUserMsgs: ',msgData);
// console.log('[msgData] : historyUserMsgs: ', historyUserMsgs);
const body: IGptRequestBody = {
model: chatMode.value,
max_tokens: 1000,
temperature: 1,
listUuid: listUuid.value,
top_p: 1,
presence_penalty: 0,
frequency_penalty: 0,
messages: historyUserMsgs, // text ? [aiMsg] : historyUserMsgs,
stream: true,
}
try {
// aiMsg.content = ''
// 发送问题到后端
const resp = await fetch(baseUrl + '/chat/app/completion', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token.value
},
body: JSON.stringify(body),
})
const reader = resp.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''
let done = false
while (!done) {
const {
value,
done: streamDone
} = await reader.read()
done = streamDone
if (value) {
buffer += decoder.decode(value, {
stream: true
})
const lines = buffer.split(/\r?\n/)
buffer = lines.pop() !
for (const line of lines) {
// 只处理以 "data: " 开头的行
if (!line.startsWith('data: ')) continue
const chunk = line.slice(6).trim()
if (chunk === '[DONE]') {
done = true
console.log('sss')
break
}
try {
const json = JSON.parse(chunk)
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')
}
} catch {}
}
//更新上下文消息
done && historyUserMsgs.push(aiMsg)
}
}
scrollToBottom()
} catch (err) {
aiMsg.content = '请重新发送'
//更新messages消息
messages[messages.length - 1] = {
...aiMsg
}
console.error(err)
} finally {
loading.value = false
showActions.value = false
}
}

View File

@ -0,0 +1,185 @@
export const fileSuffix = (str) => {
if (!str) {
return str
}
let reg = /\.\w*$/
return str.match(reg)[0]
}
export const officeFileTypeList = ['.docx', '.doc', '.xls', '.xlsx', '.pdf', '.txt']
export const videoFileType = ['.mp4', '.mov', '.wmv']
export const picFileType = ['.jpg', '.png', '.jpeg']
export const formatParams = (uploadList) => {
// 上传文件formData类型
let photoList = [] // 媒体文件 参数中的content
let videoList = []
let fileList = [] // 文档文件
uploadList.forEach((item) => {
if (picFileType.includes(fileSuffix(item.ori_url))) {
// 图片
let media = {
type: 'image_url',
image_url: {
url: item.ori_url,
},
}
photoList.push(media)
} else if (videoFileType.includes(fileSuffix(item.ori_url))) {
// 视频
let media = {
type: 'video_url',
video_url: {
url: item.ori_url,
poster: item.url,
},
}
videoList.push(media)
} else if (officeFileTypeList.includes(fileSuffix(item.ori_url))) {
let file = {
role: 'system',
name: item.name,
content: item.url,
size: item.size,
mask: 'new',
}
fileList.push(file)
}
})
return { photoList: photoList, videoList: videoList, file: fileList }
}
export const calcFileSize = (size: number) => {
const type = ['B', 'KB', 'MB', 'GB', 'TB']
// for(let i=0;i<type.length && size>1024;i++){
// size/=1024
// }
let unit = 'B'
while (size > 1024) {
size /= 1024
unit = type.shift()
}
return `${Math.ceil(size)}${unit}`
}
// 格式化请求数据,从页面渲染的数据改为请求数据
export function formatData(list) {
let result = []
let msg = null
list.forEach((item, index) => {
// if (index === list.length - 1) {
// return
// }
if (item.type === 'text') {
result.push({
role: item.role,
content: item.content,
type: item.type,
mask: item.mask,
})
if (item.role !== 'assistant') {
msg = {
role: item.role,
content: item.content,
type: item.type,
}
}
} else if (item.type === 'image' || item.type === 'video') {
// 图片与视频混合在一起
const content = []
item.content.forEach((child) => {
if (child.type === 'image_url') {
content.push({
type: 'image_url',
image_url: {
url: child.image_url.url,
},
})
} else if (child.type === 'video_url') {
content.push({
type: 'video_url',
video_url: {
url: child.video_url.url,
},
})
}
})
if (msg) {
content.push({
type: 'text',
text: msg.content,
})
msg = null
}
result.push({
role: 'user',
content: content,
type: 'image',
mask: item.mask,
})
} else if (item.type === 'file') {
let content = []
item.content.forEach((child) => {
if (child.role === 'system') {
content.push({
role: 'system',
content: child.content,
type: 'file',
mask: item.mask,
})
} else {
console.log(child)
}
})
if (msg) {
content.push(msg)
msg = null
}
result = result.concat(content)
}
})
return result
}
function sliceFile(blob: Blob, chunkSize: number): Blob[] {
const chunks: Blob[] = []
let cursor = 0
while (cursor < blob.size) {
chunks.push(blob.slice(cursor, cursor + chunkSize))
cursor += chunkSize
}
return chunks
}
/**
* Base64
*/
export function sliceBase64(base64: string, chunkSize: number = 1024 * 1024): string[] {
// 提取实际 Base64 数据部分
// const base64Data = base64.split(',')[1]
const byteStringLength = base64.length
const slices = []
console.log(base64)
for (let i = 0; i < byteStringLength; i += chunkSize) {
const chunk = base64.slice(i, i + chunkSize)
slices.push(chunk) // 可选:保留前缀
}
return slices
}
export async function readFile(file, chunkSize = 10 * 1024 * 1024) {
const blob = await fetch(file.tempFilePath)
const buffer = await blob.blob()
return sliceFile(buffer, chunkSize)
}
function uploadChunkFile({ chunk, fileName }, index, total, fileId) {
const formData = new FormData()
formData.append('Chunk', chunk)
formData.append('ChunkFileName', `${fileName}_${index}`)
formData.append('total', total)
formData.append('UseType', 100)
formData.append('FileName', fileName)
formData.append('Source', 'aiChat')
formData.append('UseType', 100)
return
}

View File

@ -0,0 +1,3 @@
export const TOKEN="79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941ca1430937103230a1e32a1715f569f3efdbe6f8cb8b7b8642bd679668081b9b08f693d1b5be6002d936ec51e1e3e0c4927de9e32ac99a109b326e5d2bda27ec87624bb416ec70d2a95a2e190feeba9f0d6bae8571b3dfe89c824712344759a8f2bff9d70747c52525cf6a5614f9c770bca461a9b9c247b6dca97bcf83bbaf99bb726752c4fe1e9a4aa7de5c4cf3e88a3e480801280d45cdc124f9d8221105d852945dc6ce10bc1647e4f09dff4d52ffdfc7eec89db303f76bb398a9e990517855cc34a9b4b5f8ebb42741e3f2c66d25790e78ad4d101c615554bbe75fdc3c97ddfe1a175322a675f7f0f55870b0222814de6998a4e9f7b24aaf9e07396389c2ec7"
export const AVATAR="https://ts1.tc.mm.bing.net/th/id/R-C.66d7b796377883a92aad65b283ef1f84?rik=sQ%2fKoYAcr%2bOwsw&riu=http%3a%2f%2fwww.quazero.com%2fuploads%2fallimg%2f140305%2f1-140305131415.jpg&ehk=Hxl%2fQ9pbEiuuybrGWTEPJOhvrFK9C3vyCcWicooXfNE%3d&risl=&pid=ImgRaw&r=0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/static/aichat/txt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/static/aichat/word.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -6,7 +6,6 @@
interface NavigateToOptions {
url: "/pages/index/index" |
"/pages/about/about" |
"/pages/index/index1" |
"/pages/preview/index" |
"/pages/webview/index";
}

9
src/utils/api.js Normal file
View File

@ -0,0 +1,9 @@
import {httpPost,httpGet} from "./http"
import {getEnvBaseUrl} from "./index"
const baseUrl=getEnvBaseUrl();
// 发送文本消息
const endMsg=async (params)=>{
return await httpGet(baseUrl+url,data)
}

281
src/utils/tools.js Normal file
View File

@ -0,0 +1,281 @@
import { getEnvBaseUrl } from "./index";
export const baseUrl =getEnvBaseUrl();
export const api = (url = '', params = {}, method = 'post') => {
// let authorization = uni.getStorageSync('authorization');
// console.log(authorization);
return new Promise((resolve, reject) => {
uni.request({
url:baseUrl+url,
data: params,
method,
header: {
'Content-Type': 'application/x-www-form-urlencoded'
//'authorization': authorization,
},
complete: (res) => {
if (res.statusCode == 200) {
resolve(res.data);
} else {
reject(res);
}
}
});
});
};
export const api_form = (url = '', params = {}, method = 'post') => {
let authorization = uni.getStorageSync('authorization');
return new Promise((resolve, reject) => {
uni.request({
url: baseUrl + url,
data: params,
method,
header: {
// 'Content-Type': 'application/x-www-form-urlencoded',
'Content-Type': 'application/json',
'authorization': authorization,
},
complete: (res) => {
if (res.statusCode == 200) {
resolve(res.data);
} else {
reject(res);
}
}
});
});
};
// 接口获取
export const apis = (url = '', params = {}, method = 'post') => {
let authorization = uni.getStorageSync('authorization');
return new Promise((resolve, reject) => {
uni.request({
url: baseUrl + url,
data: params,
method,
header: {
// 'Content-Type':'multipart/form-data',//application/x-www-form-urlencoded',
'authorization': authorization,
},
complete: (res) => {
if (res.statusCode == 200) {
resolve(res.data);
} else {
reject(res);
}
}
});
});
};
// 错误toast提示
export const showToastErr = (title) => {
uni.showToast({
title: title,
icon: 'none',
duration: 2000,
mask: true
});
}
// 成功toast提示
export const showToastOk = (title) => {
uni.showToast({
title: title,
icon: 'success',
duration: 2000,
mask: true
});
}
export const showToastOkMask = (title) => {
uni.showToast({
title: title,
icon: 'success',
duration: 2000,
mask: false
});
}
export const showloading=(title)=>{
uni.showLoading({
mask:true,
title,
});
}
export const hideloading=()=>{
uni.hideLoading();
}
export const navigateTo = (url) => {
uni.navigateTo({
url: url
})
}
export const redirectTo = (url) => {
uni.redirectTo({
url: url
})
}
export const reLaunch = (url) => {
uni.reLaunch({
url: url
})
}
// 上传图片(选择图片)
export const selectPic = () => {
return new Promise((resolve, reject) => {
uni.chooseImage({
count: 9, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album", "camera"], //从相册选择
success: function(res) {
resolve(res)
},
fail: function() {
reject("选择文件失败")
}
})
})
}
// 上传图片
export const uploadFile = (url,file) => {
let authorization = uni.getStorageSync('authorization');
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + url,
// filePath: file.tempFilePaths[0],
name: 'file',
header:{
authorization: authorization
},
formData: {
from: file,
},
complete: (res) => {
if (res.statusCode == 200) {
resolve(res)
} else {
reject(res)
}
}
})
})
}
//滚动到元素位置
export const smoothScroll = (element) => {
setTimeout(() => {
document.querySelector(element).scrollIntoView({
behavior: "smooth",
});
}, 1000)
}
export function getCurrentTime() {
var gettime = this
const yy = new Date().getFullYear()
const mm = new Date().getMonth() + 1
const dd = new Date().getDate()
const hh = new Date().getHours()
const mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date().getMinutes()
const ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date().getSeconds()
gettime = yy + '-' + mm + '-' + dd + ' ' + hh + ':' + mf + ':' + ss
return gettime
}
export function getCurrentTime1() {
var gettime = this
const yy = new Date().getFullYear()
const mm = new Date().getMonth() + 1
const dd = new Date().getDate()
const hh = new Date().getHours()
const mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date().getMinutes()
const ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date().getSeconds()
gettime = yy + '-' + mm + '-' + dd
return gettime
}
// 时间戳转日期(年月日)
export const time_format = (time) => {
// 判断时间戳是否为13位数如果不是则*1000时间戳只有13位数(带毫秒)和10(不带毫秒)位数的
if (time.toString().length == 13) {
var tme = new Date(time);
} else {
var tme = new Date(time * 1000);
}
var Y = tme.getFullYear();
var M = (tme.getMonth() + 1 < 10 ? '0' + (tme.getMonth() + 1) : tme.getMonth() + 1);
var D = tme.getDate();
var h = tme.getHours();
var m = tme.getMinutes();
var s = tme.getSeconds();
if(D<10){
D='0'+D;
}
var tem1 = Y + '/' + M + '/' + D
// + h + '时' + m + '分'
// + s +'秒'
return tem1;
}
export const time_format3 = (time,seq="-") =>{
// 判断时间戳是否为13位数如果不是则*1000时间戳只有13位数(带毫秒)和10(不带毫秒)位数的
if(time.toString().length == 13){
var tme = new Date(time);
}else{
var tme = new Date(time * 1000);
}
var Y = tme.getFullYear();
var M = (tme.getMonth() + 1 < 10 ? '0' + (tme.getMonth() + 1) : tme.getMonth() + 1);
var D = tme.getDate();
var h = tme.getHours();
if(h<10){
h='0'+h;
}
var m = tme.getMinutes();
if(m<10){
m='0'+m;
}
var s = tme.getSeconds();
if(s<10){
s='0'+s;
}
var tem1 = Y + seq + M + seq + D +' '+h+':'+m
// + h + '时' + m + '分'
// + s +'秒'
return tem1;
}
// 时间戳转日期(时分)
export const time_format1 = (time) =>{
if(time.toString().length == 13){
var tme = new Date(time);
}else{
var tme = new Date(time * 1000);
}
var Y = tme.getFullYear();
var M = (tme.getMonth() + 1 < 10 ? '0' + (tme.getMonth() + 1) : tme.getMonth() + 1);
var D = tme.getDate();
var h = tme.getHours();
var m = tme.getMinutes();
var s = tme.getSeconds();
var tem2 = + h + '时' + m + '分'
// + s +'秒'
return tem2;
}
// 时间戳转日期(时分 00:00格式
export const toHHmmss= (data)=> {
var time;
var hours = parseInt((data % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = parseInt((data % (1000 * 60 * 60)) / (1000 * 60));
// var seconds = (data % (1000 * 60)) / 1000;
time = (hours < 10 ? ('0' + hours) : hours) + ':' + (minutes < 10 ? ('0' + minutes) : minutes) ;
return time;
}
// 日期转时间戳(10)
export const date_stamp = (time) => new Date(time).getTime() / 1000
// 日期转时间戳(13)
export const date_stamp1 = (time) => new Date(time).getTime()
// 树节点
export function travelTree(tree, arr) {
for (let item of tree) {
arr.push(item.label);
if (item.children && item.children.length) travelTree(item.children, arr);
}
return arr;
}

3
src/utils/uploadFile.ts Normal file
View File

@ -0,0 +1,3 @@
export const uploadFile = (url: string, options) => {
console.log('options: ', options)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
static/app/icons/20x20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
static/app/icons/29x29.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

BIN
static/app/icons/40x40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

BIN
static/app/icons/58x58.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/app/icons/60x60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/app/icons/72x72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/app/icons/76x76.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/app/icons/80x80.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/app/icons/87x87.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
static/app/icons/96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB