Browse Source

fix:修改无缝加载;新增知识库;优化图像分析;

dxj
李朋徽 1 year ago
parent
commit
cc8dc80622
  1. 10
      src/api/base/repository.ts
  2. 1
      src/assets/svg/file_only.svg
  3. 47
      src/components/AppMessage/index.vue
  4. 5
      src/components/AppRepositoryFile/index.d.ts
  5. 3
      src/components/AppRepositoryFile/index.ts
  6. 56
      src/components/AppRepositoryFile/index.vue
  7. 2
      src/components/AppTextarea/index.vue
  8. 2
      src/design/public.scss
  9. 7
      src/enums/repositoryEnum.ts
  10. 31
      src/hooks/useMqtt.ts
  11. 7
      src/store/moules/messageStore/index.ts
  12. 1
      src/utils/axios/index.ts
  13. 56
      src/views/conversation/index.vue
  14. 49
      src/views/repository/index.vue
  15. 7
      src/views/role/index.vue
  16. 6
      src/views/textToImage/index.vue
  17. 54
      src/views/visualAnalysis/index.vue

10
src/api/base/repository.ts

@ -0,0 +1,10 @@
import { defHttp } from '@/utils/axios/index'
/**
* @description
*/
export async function repositoryFileList(id: string) {
return defHttp.get({
url: `/open-gpts/qanything/getFileList?kbId=${id}`,
})
}

1
src/assets/svg/file_only.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1706006406687" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2440" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M763.424 912H260.576A99.312 99.312 0 0 1 160 814V324a99.312 99.312 0 0 1 100.576-98H336v-48.992h67.728A134.56 134.56 0 0 1 512 128a134.576 134.576 0 0 1 108.272 49.008H688v48.992h75.424A99.312 99.312 0 0 1 864 324v490A99.312 99.312 0 0 1 763.424 912z m-125.712-686h-55.872c0-27.056-31.264-48.992-69.84-48.992s-69.84 21.936-69.84 48.992h-55.872v98h251.424v-98z m176 98a49.6 49.6 0 0 0-50.288-49.008H688v98H336v-97.984h-75.424a49.6 49.6 0 0 0-50.288 49.008v489.984a49.6 49.6 0 0 0 50.288 49.008h502.848a49.6 49.6 0 0 0 50.288-48.992V324zM688 765.008H336a24.512 24.512 0 1 1 0-48.992h352a24.512 24.512 0 1 1 0 48.992z m0-122.496H336a24.512 24.512 0 1 1 0-49.008h352a24.512 24.512 0 1 1 0 48.992z m0-122.512H336a24.512 24.512 0 1 1 0-49.008h352a24.512 24.512 0 1 1 0 49.008z" p-id="2441"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

47
src/components/AppMessage/index.vue

@ -11,9 +11,13 @@ import { copyText } from '@/utils/copyTextToClipboard'
import { useMessageStore } from '@/store/moules/messageStore/index'
const props = defineProps({
elIndex: {
type: Number,
default: 0,
},
height: {
type: String,
default: 'calc(100% - 120px)',
default: '100%',
},
list: {
type: Array as PropType<MessageItem[]>,
@ -27,12 +31,13 @@ const props = defineProps({
const emit = defineEmits(['onScrollTop', 'reloadMessage'])
defineExpose({ getScrollTopDistance, seamlessScrollToTop })
defineExpose({ seamlessScrollToTop, getElementOffsetTop })
const messageStore = useMessageStore()
const messageRef = ref<Element | null>(null)
const elHeight = ref(0) //
const messageRef = ref<any>(null)
const filstDivTop = ref(0)
const defaultScrollTop = ref(0) //
const isAutoScroll = ref(true) //
const conversationData = computed(() => messageStore.getConversationData)
@ -86,26 +91,16 @@ async function seamlessScrollToTop() {
if (!messageRef.value) {
return
}
await getScrollTopDistance()
messageRef.value.scrollTo(0, elHeight.value)
messageRef.value.scrollTo(0, filstDivTop.value)
}
/**
* @description: 获取前10个元素的总高度用于无缝滚动顶部的距离
* @param size 获取的元素个数默认为10
* @description: 获取某个元素的滚动条位置
*/
async function getScrollTopDistance(size = 10) {
if (!messageRef.value || messageRef.value.children.length < size) {
return
async function getElementOffsetTop() {
if (messageRef.value) {
filstDivTop.value = messageRef.value.children[props.elIndex].offsetTop
}
await nextTick()
elHeight.value = 0
Array.from(messageRef.value.children).slice(0, size).forEach((item) => {
elHeight.value = elHeight.value + item.clientHeight
})
// +130
elHeight.value += 130
}
/**
@ -126,7 +121,7 @@ onMounted(async () => {
<div v-if="item.messageType === MessageTypeEnum.USER" class="user">
<div class="content">
<MdPreview
editor-id="preview-only-ai"
:editor-id="`preview-only-ai${index}`"
:model-value="item.content"
/>
</div>
@ -145,7 +140,7 @@ onMounted(async () => {
|| conversationData?.type === MenuTypeEnum.REPOSITORY
|| conversationData?.type === MenuTypeEnum.VISUAL_ANALYSIS
"
editor-id="preview-only-ai"
:editor-id="`preview-only-ai${index}`"
:model-value="item.content"
/>
<div v-if="conversationData?.type === MenuTypeEnum.TEXT_TO_IMAGE" class="pt-2 pb-2">
@ -183,7 +178,7 @@ onMounted(async () => {
<style lang="scss" scoped>
@include app('message') {
height: v-bind(height);
max-height: v-bind(height);
overflow: auto;
box-sizing: border-box;
:deep(.md-editor-preview) {
@ -210,6 +205,14 @@ onMounted(async () => {
padding: 5px 15px;
background: #edf3ff;
border-radius: 10px;
:deep(.md-editor-preview-wrapper) {
img {
width: 100px;
height: 100px;
display: block;
}
}
div {
background: #edf3ff;
}

5
src/components/AppRepositoryFile/index.d.ts vendored

@ -0,0 +1,5 @@
export interface RepositoryItem {
fileId: string
fileName: string
status: string
}

3
src/components/AppRepositoryFile/index.ts

@ -0,0 +1,3 @@
import AppRepositoryFile from './index.vue'
export { AppRepositoryFile }

56
src/components/AppRepositoryFile/index.vue

@ -0,0 +1,56 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Spin } from 'ant-design-vue'
import type { RepositoryItem } from './index.d'
import { SvgIcon } from '@/components/SvgIcon'
import { repositoryFileList } from '@/api/base/repository'
import { RepositoryTypeEnum } from '@/enums/repositoryEnum'
const spinning = ref(true)
const list = ref<RepositoryItem[]>([])
function getList() {
repositoryFileList(RepositoryTypeEnum.PUBLIC).then((res) => {
list.value = res
spinning.value = false
})
}
getList()
</script>
<template>
<div class="app-repository-box">
<p class=" text-center font-bold">
知识库
</p>
<Spin :spinning="spinning" tip="加载中...">
<div class="item-box">
<div v-for="item in list" :key="item.fileId" class="item flex items-center">
<SvgIcon class="icon" name="file_only"></SvgIcon>
<p class="mb-0 ml-2 truncate">
{{ item.fileName }}
</p>
</div>
</div>
</Spin>
</div>
</template>
<style lang="scss" scoped>
@include app('repository-box') {
.item-box {
height: 300px;
overflow-y: auto;
.item {
padding: 10px;
border-bottom: 1px solid #eee;
.icon {
color: #4670e3;
width: 25px;
height: 25px;
}
}
}
}
</style>

2
src/components/AppTextarea/index.vue

@ -69,7 +69,7 @@ function stopMessage() {
</script>
<template>
<div class="app-textarea absolute right-0 bottom-5 w-full">
<div class="app-textarea right-0 bottom-5 w-full">
<div class="relative">
<div
v-if="stopShow"

2
src/design/public.scss

@ -17,7 +17,7 @@
}
::-webkit-scrollbar-thumb {
background-color: var(--border-color);
background-color: rgba($color: #eeeeee, $alpha: 0.8);
border-radius: 2px;
// box-shadow: inset 0 0 6px rgba($color: #000000, $alpha: 0.2);
}

7
src/enums/repositoryEnum.ts

@ -0,0 +1,7 @@
/**
* @description: id
*/
export enum RepositoryTypeEnum {
// 公用(目前是IOT平台的知识库)
PUBLIC = 'KB52df1744bff5455c943b1c8195f75472',
}

31
src/hooks/useMqtt.ts

@ -3,7 +3,7 @@ import type { Options } from '@/utils/mqtt'
import { MqttService } from '@/utils/mqtt'
import { useUserStore } from '@/store/moules/userStore/index'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { MessageStatusEnum, MessageTypeEnum, ModelTypeEnum } from '@/enums/messageEnum'
import { MenuTypeEnum } from '@/enums/menuEnum'
export function useMqtt() {
@ -34,17 +34,26 @@ export function useMqtt() {
.subscribe(topicKey)
.then(() => {
mqttService.onMessage(topicKey, (message: any) => {
console.log(message)
if (message.message_type === MessageStatusEnum.ACTICON) {
messageStore.setMessageStatus(MessageStatusEnum.ACTICON)
messageStore.setMessageLastItem({
messageType: MessageTypeEnum.AI,
content: message.message_content,
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.ACTICON,
})
// 这里主要是数据库的数据更新,因为数据库回答是每次都是一个字,并不是通常的不断叠加,需要前端手动叠加
if (
messageStore.getLastMessageList.messageStatus === MessageStatusEnum.ACTICON
&& messageStore.getConversationData
&& Number(messageStore.getConversationData.modelType) === ModelTypeEnum.QANYTHING) {
messageStore.setMessageStatus(MessageStatusEnum.ACTICON)
messageStore.setMessageLastPushItem(message.message_content)
}
else {
// 这里是通用的数据更新
messageStore.setMessageStatus(MessageStatusEnum.ACTICON)
messageStore.setMessageLastItem({
messageType: MessageTypeEnum.AI,
content: message.message_content,
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.ACTICON,
})
}
}
if (message.message_type === MessageStatusEnum.END) {
messageStore.setMessageStatus(MessageStatusEnum.END)

7
src/store/moules/messageStore/index.ts

@ -25,6 +25,9 @@ export const useMessageStore = defineStore('useMessageStore', {
getMessageList(): MessageItem[] {
return this.messageList
},
getLastMessageList(): MessageItem {
return this.messageList[this.messageList.length - 1]
},
},
actions: {
setMessageStatus(status: MessageStatusEnum): void {
@ -47,6 +50,10 @@ export const useMessageStore = defineStore('useMessageStore', {
this.messageList[this.messageList.length - 1] = item
},
setMessageLastPushItem(content: string) {
this.messageList[this.messageList.length - 1].content = this.messageList[this.messageList.length - 1].content + content
},
setMessageClear() {
this.messageList = []
},

1
src/utils/axios/index.ts

@ -28,6 +28,7 @@ const notDecryptWhiteList = [
'/open-gpts/gpts/getDallEImages',
'/open-chat/chat/stopGenerate',
'/hulk-system/hulk-resource/oss/endpoint/put-file',
'/open-gpts/gpts/getQanythingStreamChat',
]
/**

56
src/views/conversation/index.vue

@ -63,6 +63,7 @@ const historyMessageParams = ref({
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
const elIndex = ref(0) //
watch(
() => messageStatus.value,
@ -223,13 +224,9 @@ async function getHistoryMessage() {
messageStore.setMessageUnshiftItem(itemData)
})
conversationDefaultShow.value = false
elIndex.value = res.records.length
//
// appMessageShow.value = false
// setTimeout(() => {
// appMessageShow.value = true
// }, 0)
conversationDefaultShow.value = false
}
/**
@ -246,10 +243,12 @@ async function onScrollTop(scrollTop: number) {
if (historyMessageParams.value.current < historyMessageParams.value.total) {
historyMessageParams.value.current++
await getHistoryMessage()
//
appMessageRef.value.seamlessScrollToTop()
await appMessageRef.value.getElementOffsetTop()
await appMessageRef.value.seamlessScrollToTop()
}
}
@ -439,27 +438,30 @@ onBeforeRouteLeave(() => {
>
</AppConversationDefault>
<!-- 消息列表 -->
<AppMessage
v-if="!conversationDefaultShow && appMessageShow"
ref="appMessageRef"
:key="MenuTypeEnum.TEXT_TO_TEXT"
class="pl-27 pr-5"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>
</AppMessage>
<div class="h-full flex flex-col">
<!-- 消息列表 -->
<AppMessage
v-if="!conversationDefaultShow && appMessageShow"
ref="appMessageRef"
:key="MenuTypeEnum.TEXT_TO_TEXT"
class="pl-27 pr-5"
:el-index="elIndex"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>
</AppMessage>
<!-- 发送框 -->
<AppTextarea
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
:is-stop="false"
@send="handleSend"
@stop-message="stopMessageFun"
></AppTextarea>
</div>
</Spin>
<!-- 发送框 -->
<AppTextarea
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
:is-stop="false"
@send="handleSend"
@stop-message="stopMessageFun"
></AppTextarea>
</template>
</AppContainerBox>
</template>

49
src/views/repository/index.vue

@ -1,8 +1,10 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { Button, Spin, message } from 'ant-design-vue'
import { Button, Modal, Spin, message } from 'ant-design-vue'
import DefaultImage from '@/assets/images/conversation/default_img3.png'
import { SvgIcon } from '@/components/SvgIcon'
import { AppRepositoryFile } from '@/components/AppRepositoryFile'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
@ -10,7 +12,6 @@ import { AppConversationDefault } from '@/components/AppConversationDefault'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import type { PictureType } from '@/components/AppPicture/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
@ -22,7 +23,6 @@ import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum, ModelTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendRepository, stopMessage, updateMessage } from '@/api/base/message'
import { getAppList, getRole } from '@/api/base/role'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
@ -67,6 +67,8 @@ const leadData = ref({
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
const fileModelShow = ref(false)
const elIndex = ref(0) //
watch(
() => messageStatus.value,
@ -226,14 +228,9 @@ async function getHistoryMessage() {
historyMessageParams.value.total = res.total
messageStore.setMessageUnshiftItem(itemData)
})
elIndex.value = res.records.length
conversationDefaultShow.value = false
//
// appMessageShow.value = false
// setTimeout(() => {
// appMessageShow.value = true
// }, 0)
}
/**
@ -253,7 +250,8 @@ async function onScrollTop(scrollTop: number) {
await getHistoryMessage()
//
appMessageRef.value.seamlessScrollToTop()
await appMessageRef.value.getElementOffsetTop()
await appMessageRef.value.seamlessScrollToTop()
}
}
@ -403,6 +401,11 @@ onBeforeRouteLeave(() => {
@input-blur="handleSubMenuInputBlur"
>
</AppSubMenuList>
<div class="file-box" @click="fileModelShow = true">
<SvgIcon class="icon" name="file"></SvgIcon>
<span>知识库</span>
</div>
<AppUserInfo />
</template>
<template #content>
@ -429,6 +432,7 @@ onBeforeRouteLeave(() => {
ref="appMessageRef"
:key="MenuTypeEnum.REPOSITORY"
class="pl-27 pr-5"
:el-index="elIndex"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
@ -444,10 +448,33 @@ onBeforeRouteLeave(() => {
@send="handleSend"
@stop-message="stopMessageFun"
></AppTextarea>
<Modal v-model:open="fileModelShow" :footer="false">
<AppRepositoryFile></AppRepositoryFile>
</Modal>
</template>
</AppContainerBox>
</template>
<style lang="scss" scoped>
.file-box {
position: absolute;
bottom: 130px;
left: 10px;
width: 60px;
height: 60px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
font-size: 12px;
background: linear-gradient(131deg, #4670e3 0%, #8ca8f4 100%);
border-radius: 32px;
cursor: pointer;
.icon {
width: 22px;
height: 22px;
}
}
</style>

7
src/views/role/index.vue

@ -60,6 +60,7 @@ const historyMessageParams = ref({
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
const elIndex = ref(0) //
watch(
() => messageStatus.value,
@ -206,6 +207,8 @@ async function getHistoryMessage() {
messageStore.setMessageUnshiftItem(itemData)
})
elIndex.value = res.records.length
conversationDefaultShow.value = false
}
@ -226,7 +229,8 @@ async function onScrollTop(scrollTop: number) {
await getHistoryMessage()
//
appMessageRef.value.seamlessScrollToTop()
await appMessageRef.value.getElementOffsetTop()
await appMessageRef.value.seamlessScrollToTop()
}
}
@ -428,6 +432,7 @@ onBeforeRouteLeave(() => {
ref="appMessageRef"
:key="MenuTypeEnum.ROLE"
class="pl-27 pr-5"
:el-index="elIndex"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"

6
src/views/textToImage/index.vue

@ -64,6 +64,7 @@ const topPickList = ref([
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
const elIndex = ref(0) //
watch(
() => messageStatus.value,
@ -216,6 +217,7 @@ async function getHistoryMessage() {
messageStore.setMessageUnshiftItem(itemData)
})
elIndex.value = res.records.length
conversationDefaultShow.value = false
}
@ -236,7 +238,8 @@ async function onScrollTop(scrollTop: number) {
await getHistoryMessage()
//
appMessageRef.value.seamlessScrollToTop()
await appMessageRef.value.getElementOffsetTop()
await appMessageRef.value.seamlessScrollToTop()
}
}
@ -389,6 +392,7 @@ onBeforeRouteLeave(() => {
ref="appMessageRef"
class="pl-27 pr-5"
:list="messageList"
:el-index="elIndex"
ai-width-type="auto"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"

54
src/views/visualAnalysis/index.vue

@ -22,7 +22,7 @@ import type { ModelSelect } from '@/components/AppModelSelect/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum, ModelTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendVisualAnalysis, stopMessage, updateMessage } from '@/api/base/message'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendVisualAnalysis, updateMessage } from '@/api/base/message'
import { uploadApi } from '@/api/base/file'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
@ -61,6 +61,7 @@ const historyMessageParams = ref({
const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
const elIndex = ref(0) //
watch(
() => messageStatus.value,
@ -201,13 +202,13 @@ async function getHistoryMessage() {
spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
messageStore.setMessageClear()
spinning.value = false
if (!res || !res.records || !res.records.length) {
conversationDefaultShow.value = true
if (!res.records.length && historyMessageParams.value.total === 0) {
conversationDefaultShow.value = true
}
return
}
console.log(res.records)
res.records.forEach((item: any) => {
const itemData: MessageItem = {
@ -220,14 +221,9 @@ async function getHistoryMessage() {
historyMessageParams.value.total = res.total
messageStore.setMessageUnshiftItem(itemData)
})
elIndex.value = res.records.length
conversationDefaultShow.value = false
//
// appMessageShow.value = false
// setTimeout(() => {
// appMessageShow.value = true
// }, 0)
}
/**
@ -238,7 +234,7 @@ async function onScrollTop(scrollTop: number) {
return
}
if (historyMessageParams.value.current * historyMessageParams.value.size > historyMessageParams.value.total) {
if (historyMessageParams.value.current * historyMessageParams.value.size >= historyMessageParams.value.total) {
return
}
@ -247,7 +243,8 @@ async function onScrollTop(scrollTop: number) {
await getHistoryMessage()
//
appMessageRef.value.seamlessScrollToTop()
await appMessageRef.value.getElementOffsetTop()
await appMessageRef.value.seamlessScrollToTop()
}
}
@ -322,26 +319,25 @@ function handleModel(index: number) {
modelIndex.value = index
}
function handleUploadChange(info: UploadChangeParam) {
const status = info.file.status
if (status !== 'uploading') {
console.log(info.file, info.fileList)
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`)
/**
* @description: 图片上传前的处理
*/
function handleBeforeUpload(file: File) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
if (!isJpgOrPng) {
message.error('只能上传JPG、PNG格式图片!')
}
else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`)
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('图片尺寸不能超过10MB!')
}
return isJpgOrPng && isLt10M
}
function handleUploadDrop(e: DragEvent) {
console.log('drop', e)
}
/**
* @description: 自定义图片上传服务器
*/
async function handleCustomRequest(option: any) {
console.log(option)
const formData = new FormData()
formData.append('file', option.file)
@ -445,6 +441,7 @@ onBeforeRouteLeave(() => {
ref="appMessageRef"
class="pl-27 pr-5"
height="calc(100% - 160px)"
:el-index="elIndex"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
@ -460,8 +457,7 @@ onBeforeRouteLeave(() => {
:max-count="1"
:show-upload-list="false"
:custom-request="handleCustomRequest"
@change="handleUploadChange"
@drop="handleUploadDrop"
:before-upload="handleBeforeUpload"
>
<SvgIcon class="upload-icon" name="image"></SvgIcon>
<p class="ant-upload-text">

Loading…
Cancel
Save