拖文档进AI对话框解析,前端要处理哪些脏活

把一份 PDF 或 Word 拖进对话框,让 AI 读完再回答------这个交互体验上一秒钟的事,前端背后却有一堆脏活:拖拽态、类型校验、大文件、上传进度、解析中的占位。我按真实做下来的顺序记一遍,TypeScript。

拖拽区:别只监听 drop

新手最容易漏的是:只绑 drop 不绑 dragover,浏览器默认行为会直接把文件当新页面打开,你的回调根本不触发。四个事件都得管,而且要处理「拖拽进入子元素时 leave 误触发」。

ini 复制代码
function useDropzone(onFiles: (f: File[]) => void) {
  const dragging = ref(false);
  let depth = 0;   // 计数解决 enter/leave 抖动

  const onEnter = (e: DragEvent) => { e.preventDefault(); depth++; dragging.value = true; };
  const onLeave = (e: DragEvent) => { e.preventDefault(); if (--depth <= 0) dragging.value = false; };
  const onOver  = (e: DragEvent) => { e.preventDefault(); };
  const onDrop  = (e: DragEvent) => {
    e.preventDefault();
    depth = 0; dragging.value = false;
    const files = Array.from(e.dataTransfer?.files ?? []);
    onFiles(files);
  };
  return { dragging, onEnter, onLeave, onOver, onDrop };
}

那个 depth 计数器是重点。不用它的话,鼠标从拖拽区滑过里面任何一个子元素都会触发一次 dragleave,高亮边框疯狂闪。

类型和大小:前端先拦一道

别信文件后缀,.pdf 改个名也能传上来。但前端拿不到完整 magic number 又不想读整个文件,我的折中是后缀 + MIME 双重粗筛,真正的内容校验交给后端:

javascript 复制代码
const ALLOW = ['.pdf', '.docx', '.md', '.txt', '.pptx'];
const MAX = 20 * 1024 * 1024;   // 20MB

function check(f: File): string | null {
  const ext = '.' + f.name.split('.').pop()!.toLowerCase();
  if (!ALLOW.includes(ext)) return `不支持的格式:${ext}`;
  if (f.size > MAX) return `文件太大(${(f.size / 1048576).toFixed(1)}MB)`;
  if (f.size === 0) return '这是个空文件';
  return null;
}

f.size === 0 那行是血泪。有用户从某网盘下载到一半的占位文件拖进来,0 字节,后端解析器直接抛异常,前端转圈不动。前面拦一下,体验好太多。

上传带进度:用 XHR 不用 fetch

到现在 fetch 的上传进度支持依然不靠谱,要进度条还得回去用 XMLHttpRequestupload.onprogress

typescript 复制代码
function upload(file: File, onProgress: (p: number) => void): Promise<{ docId: string }> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const form = new FormData();
    form.append('file', file);
    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable) onProgress(e.loaded / e.total);
    };
    xhr.onload = () => xhr.status < 300
      ? resolve(JSON.parse(xhr.responseText))
      : reject(new Error('上传失败 ' + xhr.status));
    xhr.onerror = () => reject(new Error('网络错误'));
    xhr.open('POST', '/api/docs');
    xhr.send(form);
  });
}

解析是异步的,前端要会等

文件传完不等于能用了------后端还要抽取文本、切片、做向量化。这步可能要几秒到几十秒。前端别傻等,先把文档以「解析中」的灰色卡片塞进对话框,轮询状态:

javascript 复制代码
async function waitReady(docId: string) {
  for (let i = 0; i < 60; i++) {
    const s = await fetch(`/api/docs/${docId}`).then(r => r.json());
    if (s.status === 'ready') return s;
    if (s.status === 'failed') throw new Error(s.error ?? '解析失败');
    await new Promise(r => setTimeout(r, 1500));
  }
  throw new Error('解析超时');
}

体验上的关键:让用户在解析没完成时也能先打字。把问题先存住,文档 ready 了再连同 docId 一起发出去。不然用户拖完文件干等着,以为卡死了。

取舍

我没在前端做 PDF 文本抽取(pdf.js 能做,但大文件会把主线程卡死,还得处理扫描件 OCR),统一扔给后端。代价是多一次上传往返,换来前端逻辑干净、手机端也不发烫。

那「后端解析」这块我也没自己写。文本抽取、切片、向量化、检索,我整条交给了讯飞Agent------它是 MaaS,把这套 RAG 流程封成了上传 + 检索接口,我前端只管拖拽、校验、轮询状态,模型和算力都不用我操心。

相关推荐
IT_陈寒17 分钟前
为什么我的Vue组件总是莫名其妙重渲染?
前端·人工智能·后端
欧特克_Glodon1 小时前
OpenCV计算机视觉开发入门与实践<二十七>:图像分割概述
c++·人工智能·opencv·计算机视觉
lhldsg1 小时前
AI零售系统实战指南:从架构设计到落地部署全解析
java·人工智能·小程序·uni-app·零售
richard_first1 小时前
Transformer与大语言模型:第13章 Decoder
人工智能·机器学习
东坡肘子1 小时前
举手之劳 -- 肘子的 Swift 周报 #151
人工智能·swiftui·swift
Henry-SAP1 小时前
AI产业迎来标准化与商业化双突破
人工智能·云原生·sap·erp
火山引擎开发者社区1 小时前
Seed-Evolving再升级:更强多模态能力,更低幻觉
人工智能
港股研究社1 小时前
专注履约底座,顺丰同城在即时零售效率时代提升增长动能
大数据·人工智能
Warren2Lynch1 小时前
告别静态图片:利用 VPasCode AI 功能将 C4 模型图像一键转换为可编辑代码
人工智能
AI砖家2 小时前
Codex 客户端频繁提示「正在重连 / Reconnecting」的原因与完整解决方案
人工智能·chatgpt·ai编程·codex