feat: 优化聊天附件拖拽上传提示
- 不支持格式改用轻量提示并展示支持范围 - 扩展拖拽区域并增加全区上传覆盖层 - 补充文档和图片格式校验测试
This commit is contained in:
@@ -19,6 +19,7 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
CHAT_DOCUMENT_SUPPORTED_FORMATS,
|
||||
ChatDocumentAttachments,
|
||||
ChatImageAttachments,
|
||||
ChatTimeline,
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
Paperclip,
|
||||
Plus,
|
||||
Promotion,
|
||||
UploadFilled,
|
||||
} from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElButton,
|
||||
@@ -52,6 +54,7 @@ import {
|
||||
loadAgentChatImage,
|
||||
} from '#/components/ai-chat/mediaApi';
|
||||
import { useAgentComposerDraft } from '#/components/ai-chat/useAgentComposerDraft';
|
||||
import { CHAT_IMAGE_SUPPORTED_FORMATS } from '#/components/ai-chat/useChatImageUploads';
|
||||
import ChatCapabilityMenu from '#/components/chat-workspace/ChatCapabilityMenu.vue';
|
||||
import ChatInputTriggerPanel from '#/components/chat-workspace/ChatInputTriggerPanel.vue';
|
||||
import { useChatInputTrigger } from '#/components/chat-workspace/input-triggers/useChatInputTrigger';
|
||||
@@ -114,6 +117,11 @@ const selectedAgentImageSupport = computed(() =>
|
||||
selectedAgent.value?.publishedSnapshotJson?.modelSummary?.supportImage,
|
||||
),
|
||||
);
|
||||
const supportedAttachmentFormats = computed(() =>
|
||||
selectedAgentImageSupport.value === false
|
||||
? CHAT_DOCUMENT_SUPPORTED_FORMATS
|
||||
: `${CHAT_DOCUMENT_SUPPORTED_FORMATS};图片支持 ${CHAT_IMAGE_SUPPORTED_FORMATS}`,
|
||||
);
|
||||
const interactionDisplay = computed(() =>
|
||||
resolveInteractionDisplay(selectedAgent.value),
|
||||
);
|
||||
@@ -747,17 +755,29 @@ async function addImageFiles(files: File[]) {
|
||||
return;
|
||||
}
|
||||
if (selectedAgentImageSupport.value === false) {
|
||||
ElMessage.warning('当前智能体不支持图片');
|
||||
ElMessage.warning({
|
||||
grouping: true,
|
||||
message: `当前智能体不支持图片,支持:${CHAT_DOCUMENT_SUPPORTED_FORMATS}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await composer.ensureSession();
|
||||
const rejected = await composer.images.addFiles(files, {
|
||||
agentId: selectedAgentId.value,
|
||||
mode: 'FORMAL',
|
||||
sessionId: composer.sessionId.value,
|
||||
});
|
||||
const { rejectedCount, unsupportedCount } = await composer.images.addFiles(
|
||||
files,
|
||||
{
|
||||
agentId: selectedAgentId.value,
|
||||
mode: 'FORMAL',
|
||||
sessionId: composer.sessionId.value,
|
||||
},
|
||||
);
|
||||
composer.scheduleSave();
|
||||
if (rejected > 0) {
|
||||
if (unsupportedCount > 0) {
|
||||
ElMessage.warning({
|
||||
grouping: true,
|
||||
message: `不支持该图片格式,支持:${CHAT_IMAGE_SUPPORTED_FORMATS}`,
|
||||
});
|
||||
}
|
||||
if (rejectedCount > 0) {
|
||||
ElMessage.warning('每次最多添加 5 张图片');
|
||||
}
|
||||
}
|
||||
@@ -768,22 +788,38 @@ async function addDocumentFiles(files: File[]) {
|
||||
return;
|
||||
}
|
||||
await composer.ensureSession();
|
||||
const rejected = await composer.documents.addFiles(files, {
|
||||
agentId: selectedAgentId.value,
|
||||
mode: 'FORMAL',
|
||||
sessionId: composer.sessionId.value,
|
||||
});
|
||||
const { rejectedCount, unsupportedCount } = await composer.documents.addFiles(
|
||||
files,
|
||||
{
|
||||
agentId: selectedAgentId.value,
|
||||
mode: 'FORMAL',
|
||||
sessionId: composer.sessionId.value,
|
||||
},
|
||||
);
|
||||
composer.scheduleSave();
|
||||
if (rejected > 0) {
|
||||
if (unsupportedCount > 0) {
|
||||
ElMessage.warning({
|
||||
grouping: true,
|
||||
message: `不支持该文件格式,支持:${supportedAttachmentFormats.value}`,
|
||||
});
|
||||
}
|
||||
if (rejectedCount > 0) {
|
||||
ElMessage.warning('每轮最多添加 3 份文档');
|
||||
}
|
||||
}
|
||||
|
||||
function isImageAttachmentFile(file: File) {
|
||||
return (
|
||||
file.type.startsWith('image/') ||
|
||||
/\.(?:bmp|gif|jpe?g|png|webp)$/i.test(file.name)
|
||||
);
|
||||
}
|
||||
|
||||
function handleAttachmentFiles(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const files = [...(target.files || [])];
|
||||
const imageFiles = files.filter((file) => file.type.startsWith('image/'));
|
||||
const documentFiles = files.filter((file) => !file.type.startsWith('image/'));
|
||||
const imageFiles = files.filter((file) => isImageAttachmentFile(file));
|
||||
const documentFiles = files.filter((file) => !isImageAttachmentFile(file));
|
||||
if (imageFiles.length > 0) void addImageFiles(imageFiles);
|
||||
if (documentFiles.length > 0) void addDocumentFiles(documentFiles);
|
||||
target.value = '';
|
||||
@@ -810,6 +846,13 @@ function handleAttachmentDragEnter(event: DragEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleAttachmentDragOver(event: DragEvent) {
|
||||
handleAttachmentDragEnter(event);
|
||||
if (composerDragActive.value && event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
}
|
||||
|
||||
function handleAttachmentDragLeave(event: DragEvent) {
|
||||
const container = event.currentTarget as HTMLElement;
|
||||
if (
|
||||
@@ -824,17 +867,8 @@ function handleAttachmentDrop(event: DragEvent) {
|
||||
composerDragActive.value = false;
|
||||
if (capabilityDisabled.value) return;
|
||||
const files = [...(event.dataTransfer?.files || [])];
|
||||
const imageFiles = files.filter(
|
||||
(file) =>
|
||||
file.type.startsWith('image/') &&
|
||||
selectedAgentImageSupport.value !== false &&
|
||||
composer.images.items.value.length < 5,
|
||||
);
|
||||
const documentFiles = files.filter(
|
||||
(file) =>
|
||||
!file.type.startsWith('image/') &&
|
||||
composer.documents.items.value.length < 3,
|
||||
);
|
||||
const imageFiles = files.filter((file) => isImageAttachmentFile(file));
|
||||
const documentFiles = files.filter((file) => !isImageAttachmentFile(file));
|
||||
if (imageFiles.length > 0) void addImageFiles(imageFiles);
|
||||
if (documentFiles.length > 0) void addDocumentFiles(documentFiles);
|
||||
}
|
||||
@@ -1151,7 +1185,13 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="agent-chat__main">
|
||||
<main
|
||||
class="agent-chat__main"
|
||||
@dragenter.prevent="handleAttachmentDragEnter"
|
||||
@dragover.prevent="handleAttachmentDragOver"
|
||||
@dragleave.prevent="handleAttachmentDragLeave"
|
||||
@drop.prevent="handleAttachmentDrop"
|
||||
>
|
||||
<header class="agent-chat__main-head">
|
||||
<div>
|
||||
<div class="agent-chat__main-title">
|
||||
@@ -1214,14 +1254,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="agent-chat__composer"
|
||||
:class="{ 'is-dragging': composerDragActive }"
|
||||
@dragenter.prevent="handleAttachmentDragEnter"
|
||||
@dragover.prevent
|
||||
@dragleave.prevent="handleAttachmentDragLeave"
|
||||
@drop.prevent="handleAttachmentDrop"
|
||||
>
|
||||
<div class="agent-chat__composer">
|
||||
<ChatCapabilityMenu
|
||||
:disabled="capabilityDisabled"
|
||||
:extra-knowledge-ids="extraKnowledgeIds"
|
||||
@@ -1353,6 +1386,24 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Transition name="agent-chat-drag-fade">
|
||||
<div
|
||||
v-if="composerDragActive"
|
||||
class="agent-chat__drop-overlay"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="agent-chat__drop-overlay-content">
|
||||
<ElIcon class="agent-chat__drop-overlay-icon">
|
||||
<UploadFilled />
|
||||
</ElIcon>
|
||||
<div class="agent-chat__drop-overlay-title">松手上传文件</div>
|
||||
<div class="agent-chat__drop-overlay-formats">
|
||||
支持 {{ supportedAttachmentFormats }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</main>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1526,9 +1577,53 @@ onBeforeUnmount(() => {
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.agent-chat__composer.is-dragging {
|
||||
.agent-chat__drop-overlay {
|
||||
position: absolute;
|
||||
inset: 16px;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
pointer-events: none;
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
border: 2px dashed var(--el-color-primary-light-5);
|
||||
border-radius: 16px;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.agent-chat__drop-overlay-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
max-width: min(560px, calc(100% - 48px));
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agent-chat__drop-overlay-icon {
|
||||
font-size: 40px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.agent-chat__drop-overlay-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.agent-chat__drop-overlay-formats {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.agent-chat-drag-fade-enter-active,
|
||||
.agent-chat-drag-fade-leave-active {
|
||||
transition: opacity 160ms ease;
|
||||
}
|
||||
|
||||
.agent-chat-drag-fade-enter-from,
|
||||
.agent-chat-drag-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.agent-chat__trigger-panel {
|
||||
|
||||
Reference in New Issue
Block a user