Quellcode durchsuchen

上传+实时进度

kagg886 vor 1 Monat
Ursprung
Commit
158781061c
4 geänderte Dateien mit 82 neuen und 36 gelöschten Zeilen
  1. 1 8
      src/api/assist/type.ts
  2. 27 5
      src/api/common/index.ts
  3. 8 0
      src/api/common/type.ts
  4. 46 23
      src/views/assistant/index.vue

+ 1 - 8
src/api/assist/type.ts

@@ -13,6 +13,7 @@
  *     "UserId": 10
  * }
  */
+import { UploadFile } from '/@/api/common/type'
 
 // 消息类型定义
 export type Message = {
@@ -36,14 +37,6 @@ export type Message = {
 	files?: Array<UploadFile> | undefined
 }
 
-export type UploadFile = {
-	size: number
-	path: string
-	name: string
-	type: string
-	full_path: string
-}
-
 export type FunctionCall = {
 	id: string
 	type: 'function'

+ 27 - 5
src/api/common/index.ts

@@ -1,16 +1,38 @@
-import {post} from '/@/utils/request'
+import service from '/@/utils/request'
+import { UploadFile } from '/@/api/common/type'
 
 export default {
 	upload: {
-		single: (blob: File) => {
+		// eslint-disable-next-line no-unused-vars
+		single: (blob: File,progress: ((progress: number) => void) | undefined = undefined):Promise<UploadFile> => {
 			const data = new FormData()
 			data.append('file', blob)
-			return post('/common/singleFile', data)
+			return service({
+				url: '/common/singleFile',
+				method: "post",
+				data,
+				onUploadProgress: (progressEvent) => {
+					if (progress) {
+						progress(progressEvent.loaded / progressEvent.total)
+					}
+				}
+			}) as unknown as Promise<UploadFile>
 		},
-		multi: (blob: File[]) => {
+		// eslint-disable-next-line no-unused-vars
+		multi: (blob: File[],progress: ((progress: number) => void) | undefined = undefined):Promise<UploadFile[]> => {
 			const data = new FormData()
 			blob.forEach(file => data.append('file', file))
-			return post('/common/multipleFile', data)
+			// return post('/common/multipleFile', data)
+			return service({
+				url: '/common/multipleFile',
+				method: "post",
+				data,
+				onUploadProgress: (progressEvent) => {
+					if (progress) {
+						progress(progressEvent.loaded / progressEvent.total)
+					}
+				}
+			}) as unknown as Promise<UploadFile[]>
 		}
 	}
 }

+ 8 - 0
src/api/common/type.ts

@@ -0,0 +1,8 @@
+
+export type UploadFile = {
+	size: number
+	path: string
+	name: string
+	type: string
+	full_path: string
+}

+ 46 - 23
src/views/assistant/index.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 import { computed, isReactive, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
 import { Local } from '/@/utils/storage'
-import common from '/@/api/common/index'
 import {
 	ArrowDown,
 	ChatDotRound,
@@ -9,10 +8,14 @@ import {
 	Close,
 	CopyDocument,
 	Delete,
+	Document,
 	Download,
 	Edit,
+	Files,
+	Headset,
 	Loading,
 	MoreFilled,
+	Picture,
 	Plus,
 	Promotion,
 	Search,
@@ -22,11 +25,7 @@ import {
 	StarFilled,
 	User,
 	VideoPause,
-	Document,
-	Picture,
 	VideoPlay,
-	Headset,
-	Files,
 } from '@element-plus/icons-vue'
 import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
 import EChartsPlugin from '/@/components/markdown/plugins/echarts'
@@ -41,6 +40,8 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import StructDataPlugin from '/@/components/markdown/plugins/struct-data'
 import { useFileDialog } from '@vueuse/core'
 import download from 'downloadjs'
+import common from '/@/api/common'
+import { UploadFile } from '/@/api/common/type'
 
 const plugins: Array<MarkdownPlugin<any>> = [EChartsPlugin(), ToolsLoadingPlugin(), TablePlugin(), StructDataPlugin()]
 
@@ -60,14 +61,29 @@ const {
 })
 
 // 附件管理:使用本地列表以支持单个移除
-const selectedFiles = ref<File[]>([])
+const selectedFiles = ref<UploadFile[]>([])
+
+const uploadProgress = ref<number>(-1)
+const { loading: loadingUpload, doLoading: doUpload } = useLoading(async (file: File[]) => {
+	uploadProgress.value = -1
+
+	return (await common.upload
+		.multi(file, (progress) => (uploadProgress.value = progress * 100))
+		.finally(() => (uploadProgress.value = -1))) as UploadFile[]
+})
 
 watch(
 	attachments,
-	(newFiles) => {
+	async (newFiles) => {
 		if (newFiles === null || newFiles.length === 0) return
 
-		selectedFiles.value = [...selectedFiles.value, ...newFiles]
+		const data = await doUpload(Array.from(newFiles)).catch(() => undefined)
+		if (data === undefined) {
+			reset()
+			return
+		}
+
+		selectedFiles.value = [...selectedFiles.value, ...data]
 		reset()
 	},
 	{ immediate: true }
@@ -220,7 +236,7 @@ watch(selectPromptId, (newVal) => {
 		render_content: placeholder,
 		timestamp: Date.now(),
 		role: 'assistant',
-		content: placeholder
+		content: placeholder,
 	})
 	// inputMessage.value = displayPromptList.value.find((i) => i.id === newVal)?.placeholder ?? ''
 })
@@ -265,7 +281,7 @@ const promptDialogVisible = ref(false)
 const chatInstance = ref<(() => void) | undefined>(undefined)
 onUnmounted(() => chatInstance.value?.())
 // 是否正在对话
-const isConversationActive = computed(() => chatInstance.value !== undefined || loadingUpload.value)
+const isConversationActive = computed(() => chatInstance.value !== undefined)
 
 const { loading: loadingClearMessage, doLoading: clearMessage } = useLoading(async () => {
 	stopConversation()
@@ -283,10 +299,6 @@ const { loading: loadingClearMessage, doLoading: clearMessage } = useLoading(asy
 	}
 	messages.value = []
 })
-
-const {loading:loadingUpload,doLoading: doUpload} = useLoading(async ()=> {
-	return selectedFiles.value.length === 0 ? undefined : (await common.upload.multi(selectedFiles.value)) as Message['files']
-})
 // 发送消息
 const sendMessage = async () => {
 	if (!inputMessage.value.trim()) return
@@ -302,7 +314,7 @@ const sendMessage = async () => {
 		render_content: inputMessage.value,
 		content: inputMessage.value,
 		timestamp: Date.now(),
-		files: await doUpload(),
+		files: selectedFiles.value,
 	})
 	selectedFiles.value = []
 
@@ -745,7 +757,15 @@ const getUserInfos = ref<{
 }>(Local.get('userInfo') || {})
 
 const canSendMessage = computed(() => {
-	return !inputMessage.value.trim() || loadingModels.value || loadingEmbeddingModels.value || loadingPromptList.value || loadConversations.value || loadingMessage.value || loadingUpload.value
+	return (
+		!inputMessage.value.trim() ||
+		loadingModels.value ||
+		loadingEmbeddingModels.value ||
+		loadingPromptList.value ||
+		loadConversations.value ||
+		loadingMessage.value ||
+		loadingUpload.value
+	)
 })
 
 const router = useRouter()
@@ -930,7 +950,7 @@ const formatFileSize = (bytes: number): string => {
 				<div
 					v-for="conv in displayConversations"
 					:key="conv.session_id"
-					@click="(editingConversationId !== conv.session_id && !multiDeleteConversationModel.visible) ? selectConversation(conv.session_id) : () => {}"
+					@click="editingConversationId !== conv.session_id && !multiDeleteConversationModel.visible ? selectConversation(conv.session_id) : () => {}"
 					:class="['conversation-item', { active: activeConversationId === conv.session_id, editing: editingConversationId === conv.session_id }]"
 				>
 					<!-- 非编辑状态 -->
@@ -1195,15 +1215,16 @@ const formatFileSize = (bytes: number): string => {
 							<div class="attachments-inline-scroll">
 								<div
 									v-for="(file, fIdx) in selectedFiles"
-									:key="file.name + '_' + file.size + '_' + (file as any).lastModified"
+									:key="file.name + '_' + file.size"
 									class="attachment-card"
+									@click="openFile(file.full_path)"
 									title=""
 								>
 									<button class="control-btn attachment-item" :title="`${file.name}`">
 										<el-icon :size="10"><CopyDocument /></el-icon>
 										<span class="attachment-name">{{ file.name }}</span>
 									</button>
-									<button class="remove-attachment-icon" @click="removeAttachment(fIdx)">
+									<button class="remove-attachment-icon" @click.stop="removeAttachment(fIdx)">
 										<el-icon><Close /></el-icon>
 									</button>
 								</div>
@@ -1213,8 +1234,11 @@ const formatFileSize = (bytes: number): string => {
 								</div>
 							</div>
 						</el-scrollbar>
-						<button class="control-btn add-attachment-btn" @click="open">
-							<el-icon :size="10"><Plus /></el-icon>
+						<button class="control-btn add-attachment-btn" @click="open" :disabled="loadingUpload">
+							<el-icon :size="10">
+								<Plus v-if="!loadingUpload" />
+								<div v-else>{{ uploadProgress.toFixed(2) }}</div>
+							</el-icon>
 						</button>
 					</div>
 					<!-- 输入框 -->
@@ -2044,7 +2068,6 @@ const formatFileSize = (bytes: number): string => {
 }
 
 .attachment-card {
-
 	position: relative;
 	display: inline-flex;
 	align-items: center;
@@ -2060,7 +2083,7 @@ const formatFileSize = (bytes: number): string => {
 
 	font-size: 10px !important;
 
-	padding: 4px 8px!important;
+	padding: 4px 8px !important;
 }
 
 .attachment-name {