| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- import { message } from 'ant-design-vue';
- import { fetchAttachmentBlob, getSessionAttachments } from '/@/views/ventAI/manageAssistent/api';
- import type { SessionAttachment } from '/@/views/ventAI/manageAssistent/api';
- import { isCsvFile, isDwgFile, isDxfFile, isExcelFile, isTextFile, isWordFile, getPreviewMimeType } from '/@/views/ventAI/manageAssistent/lib';
- import type { AttachedFile, Message, Task } from '/@/views/ventAI/manageAssistent/types/index';
- /**
- * 会话附件历史:加载后按 message_id 挂回用户消息,无法匹配的进入兜底列表;
- * 附件内容按需下载(预览时才取二进制),下载/预览都带令牌走附件下载接口。
- */
- const toAttachedFile = (item: SessionAttachment): AttachedFile => ({
- id: item.file_id,
- fileId: item.file_id,
- messageId: item.message_id,
- name: item.filename,
- size: item.size_bytes,
- type: item.content_type || '',
- uploadTime: item.created_at,
- createdAt: item.created_at,
- downloadUrl: item.download_url,
- });
- const fileKey = (file: AttachedFile): string => file.fileId || file.id;
- const mergeAttachedFiles = (existing: AttachedFile[], incoming: AttachedFile[]): AttachedFile[] => {
- const map = new Map<string, AttachedFile>();
- for (const file of [...existing, ...incoming]) {
- map.set(fileKey(file), file);
- }
- return Array.from(map.values());
- };
- const saveBlob = (blob: Blob, filename: string) => {
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename || '附件';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- // 让浏览器先拿到下载任务再释放对象 URL
- setTimeout(() => URL.revokeObjectURL(url), 0);
- };
- const hasContent = (file: AttachedFile): boolean =>
- file.content !== undefined || file.arrayBuffer !== undefined || file.preview !== undefined || !!file.originalFile;
- /**
- * 预览用的 blob 换成真实类型:附件下载接口统一回 application/octet-stream(还带 Content-Disposition: attachment),
- * 浏览器凭这个类型认不出内容——PDF 放进 iframe 会被当成下载而不是显示。
- */
- const withPreviewType = (file: AttachedFile, blob: Blob): Blob => {
- const type = getPreviewMimeType(file.name, file.type || blob.type);
- return type && type !== blob.type ? new Blob([blob], { type }) : blob;
- };
- /** 历史附件没有原始内容,首次预览时按文件类型取回并缓存到附件对象上 */
- const ensureAttachmentContent = async (file: AttachedFile): Promise<AttachedFile> => {
- if (hasContent(file)) return file;
- if (!file.fileId) throw new Error('附件缺少下载标识');
- // dwg 历史附件不用把原始文件下载回来:预览面板会按 fileId 直接取后端已转换好的 svg
- //(见 DwgPreview 的 requestSvgText),下载几百 KB 只为了再上传转换一遍属于纯浪费。
- if (isDwgFile(file.name)) return file;
- const blob = await fetchAttachmentBlob(file.fileId, file.downloadUrl);
- if (isTextFile(file.name) || isCsvFile(file.name) || isDxfFile(file.name)) {
- file.content = await blob.text();
- } else if (isWordFile(file.name) || isExcelFile(file.name)) {
- file.arrayBuffer = await blob.arrayBuffer();
- } else {
- file.preview = URL.createObjectURL(withPreviewType(file, blob));
- }
- file.originalFile = new File([blob], file.name, {
- type: file.type || blob.type || 'application/octet-stream',
- });
- return file;
- };
- export const useChatAttachments = () => {
- /**
- * 拉取会话附件并挂到历史消息上。
- * - message_id 能匹配到用户消息:挂到该消息的 attachedFiles;
- * - message_id 为 null 或消息已合并/跳过:进入 task.orphanAttachments,由消息区底部兜底展示。
- * 附件接口失败不阻断历史消息展示(旧环境可能还没有这个接口)。
- */
- const loadHistoryAttachments = async (task: Task, sessionId: string, messages: Message[]): Promise<AttachedFile[]> => {
- try {
- const res = await getSessionAttachments(sessionId);
- const attachments = (res?.attachments || []).map(toAttachedFile);
- if (!attachments.length) {
- task.orphanAttachments = [];
- return [];
- }
- const userMessageById = new Map<number, Message>();
- for (const msg of messages) {
- if (msg.type === 'user' && msg.id != null) userMessageById.set(msg.id, msg);
- }
- const orphans: AttachedFile[] = [];
- for (const file of attachments) {
- const target = file.messageId != null ? userMessageById.get(file.messageId) : undefined;
- if (target) {
- if (!target.attachedFiles) target.attachedFiles = [];
- if (!target.attachedFiles.some((f) => fileKey(f) === fileKey(file))) {
- target.attachedFiles.push(file);
- }
- } else {
- orphans.push(file);
- }
- }
- task.orphanAttachments = orphans;
- task.attachedFiles = mergeAttachedFiles(task.attachedFiles, attachments);
- return attachments;
- } catch (e) {
- // 历史消息是主内容,附件是增强信息:失败只记录,不打断会话打开
- console.error('加载会话附件失败:', e);
- return [];
- }
- };
- const handleAttachmentPreview = async (file: AttachedFile, openPreview: (target: AttachedFile) => void) => {
- if (hasContent(file)) {
- openPreview(file);
- return;
- }
- if (!file.fileId) {
- message.warning('该附件暂不支持预览');
- return;
- }
- const closeLoading = message.loading('正在加载附件…', 0);
- try {
- await ensureAttachmentContent(file);
- openPreview(file);
- } catch (e) {
- console.error('加载附件失败:', e);
- message.error((e as Error)?.message || '附件加载失败');
- } finally {
- closeLoading();
- }
- };
- const handleAttachmentDownload = async (file: AttachedFile) => {
- try {
- if (file.fileId) {
- const closeLoading = message.loading('正在下载附件…', 0);
- try {
- const blob = await fetchAttachmentBlob(file.fileId, file.downloadUrl);
- saveBlob(blob, file.name);
- } finally {
- closeLoading();
- }
- return;
- }
- if (file.originalFile) {
- saveBlob(file.originalFile, file.name);
- return;
- }
- if (file.preview?.startsWith('blob:') || file.preview?.startsWith('data:')) {
- const blob = await (await fetch(file.preview)).blob();
- saveBlob(blob, file.name);
- return;
- }
- message.warning('该附件暂不支持下载');
- } catch (e) {
- console.error('下载附件失败:', e);
- message.error((e as Error)?.message || '附件下载失败');
- }
- };
- return { loadHistoryAttachments, handleAttachmentPreview, handleAttachmentDownload };
- };
|