拖文档进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 流程封成了上传 + 检索接口,我前端只管拖拽、校验、轮询状态,模型和算力都不用我操心。

相关推荐
姗姗来迟了1 小时前
AI回答里的引用来源卡片,前端怎么做
人工智能
用户7106207733401 小时前
Codex-端口配置错误排查案例(stream disconnected before completion)
人工智能
IT_陈寒2 小时前
JavaScript的默认参数挖坑实录,我掉进去了
前端·人工智能·后端
米小虾2 小时前
多Agent系统编排详解:从架构设计到代码实现
人工智能·agent
米小虾2 小时前
多Agent系统的编排:架构、协议与企业级应用
人工智能·agent
To_OC12 小时前
搞懂 Token 和 Embedding 后,我终于明白大模型是怎么 "读" 文字的
人工智能·llm·agent
冬奇Lab14 小时前
每日一个开源项目(第139篇):Voicebox - 本地运行的开源 ElevenLabs 替代品
人工智能·开源·资讯
冬奇Lab14 小时前
Skill 系列(03):Skill 设计范式——5 个模式让输出从混沌到可预测
人工智能·开源·agent