《会聊天的文件筐:用 Next.js 打造“图音双绝”的上传组件》

开场三句话

  1. 用户说:"发张图。"
  2. 用户说:"发段语音。"
  3. 你说:"稍等,我让浏览器先开个 AI 小灶。"

今天,我们要写一个聊天 UI 的上传组件 ,它既能识图 又能辨音 ,还要保持界面优雅,像一位会魔法的管家。

(配图:一只端着托盘的小机器人,托盘上躺着一张猫咪照片和一只麦克风)


一、需求拆解:到底要上传什么?

类型 浏览器能做什么 我们要做什么
图片 <input type="file" accept="image/*"> 预览、压缩、OCR/打标签
音频 <input type="file" accept="audio/*"> or MediaRecorder 波形预览、转文字、情绪分析

一句话:浏览器负责"拿",我们负责"看/听"


二、技术地图:从点击到 AI 的大脑

css 复制代码
┌────────────┐     ┌──────────────┐     ┌──────────┐
│ 用户点击   │──→──│ 前端预览     │──→──│ 后端识别  │
│ input file │     │ canvas /    │     │ OCR /    │
└────────────┘     │ Web Audio   │     │ Whisper  │
                   └──────────────┘     └──────────┘

三、前端实现:React + TypeScript(Next.js 亦可)

3.1 组件骨架:一个 Hook 统治所有上传

ts 复制代码
// hooks/useUploader.ts
import { useState, useCallback } from 'react';

type FileType = 'image' | 'audio';

export function useUploader() {
  const [file, setFile] = useState<File | null>(null);
  const [preview, setPreview] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleChange = useCallback(
    (type: FileType) => (e: React.ChangeEvent<HTMLInputElement>) => {
      const f = e.target.files?.[0];
      if (!f) return;
      setFile(f);
      setPreview(URL.createObjectURL(f));
      setLoading(true);
      // ⭐ 交给识别函数
      recognize(type, f).then((result) => {
        console.log('识别结果', result);
        setLoading(false);
      });
    },
    []
  );

  return { file, preview, loading, handleChange };
}

3.2 图片识别:浏览器端就能 OCR(tesseract.js)

ts 复制代码
// utils/recognize.ts
import Tesseract from 'tesseract.js';

export async function recognize(type: 'image' | 'audio', file: File) {
  if (type === 'image') {
    const { data: { text } } = await Tesseract.recognize(file, 'eng+chi_sim');
    return { text };
  }
  if (type === 'audio') {
    // 音频先上传,后端 Whisper 转文字,下文细讲
    const form = new FormData();
    form.append('audio', file);
    const res = await fetch('/api/transcribe', { method: 'POST', body: form });
    return res.json();
  }
}

浏览器里跑 OCR 就像让小学生在操场上背圆周率------能背,但跑不快。

所以我们只在小图离线场景用 tesseract.js,大图还是走后端 GPU。


3.3 音频录制:边录边传,体验拉满

tsx 复制代码
// components/AudioRecorder.tsx
import { useState } from 'react';

export default function AudioRecorder({ onDone }: { onDone: (f: File) => void }) {
  const [recording, setRecording] = useState(false);
  const mediaRef = useRef<MediaRecorder | null>(null);

  const start = async () => {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const mr = new MediaRecorder(stream, { mimeType: 'audio/webm' });
    const chunks: BlobPart[] = [];
    mr.ondataavailable = (e) => chunks.push(e.data);
    mr.onstop = () => {
      const blob = new Blob(chunks, { type: 'audio/webm' });
      onDone(new File([blob], 'speech.webm'));
    };
    mr.start();
    mediaRef.current = mr;
    setRecording(true);
  };

  const stop = () => {
    mediaRef.current?.stop();
    setRecording(false);
  };

  return (
    <>
      <button onClick={recording ? stop : start}>
        {recording ? '⏹️ 停止' : '🎤 录音'}
      </button>
    </>
  );
}

浏览器录音使用的是 MediaDevices.getUserMedia → MediaRecorder → Blob 这条"黄金管道"。

数据在内存里是 PCM 原始波形,压缩成 webm/opus 后才上传,节省 90% 流量。


四、后端识别:GPU 才是第一生产力

4.1 图片:OCR + 打标签(Python 示例,Next.js API Route 可调用)

py 复制代码
# api/ocr.py  (FastAPI 伪代码)
from fastapi import UploadFile
import pytesseract, torch, timm

@app.post("/ocr")
async def ocr(file: UploadFile):
    img = await file.read()
    text = pytesseract.image_to_string(img, lang='eng+chi_sim')
    labels = model(img)  # timm 预训练 ResNet
    return {"text": text, "labels": labels}

4.2 音频:用 Whisper 转文字(OpenAI 开源版)

py 复制代码
# api/transcribe.py
import whisper, tempfile, os

model = whisper.load_model("base")

@app.post("/transcribe")
async def transcribe(file: UploadFile):
    with tempfile.NamedTemporaryFile(delete=False, suffix=".webm") as tmp:
        tmp.write(await file.read())
        tmp.flush()
        result = model.transcribe(tmp.name, language='zh')
        os.unlink(tmp.name)
        return {"text": result["text"]}

Whisper 的「魔法」:把 30 秒音频切成 mel 频谱 → Transformer 编码 → 解码文字。

在 A100 上,转 30 秒音频只需 100 ms,比你泡咖啡还快。


五、前端 UI:让文件像聊天泡泡一样优雅

css 复制代码
┌────────────────────────────┐
│  用户 A                   │
│  [猫咪照片预览]           │
│  🖼️ 识别:一只橘猫在打盹 │
└────────────────────────────┘

实现思路:

  1. 上传成功 → 本地先渲染占位泡泡(带 spinner)。
  2. 后端返回结果 → 更新泡泡内容(图片 + 文字 / 语音 + 文字)。
  3. 失败 → 泡泡变红色,重试按钮出现。

六、性能 & 体验小贴士

问题 解法
大图片 10 MB+ 浏览器 canvas.toBlob(file, 'image/jpeg', 0.8) 压缩
音频长 5 min+ 分片上传 + 后端流式转写
弱网 上传前存 IndexedDB,网络恢复后重试
隐私 敏感图片走本地 OCR,不上传

七、彩蛋:一行代码让上传支持拖拽

tsx 复制代码
<div
  onDrop={(e) => {
    e.preventDefault();
    const f = e.dataTransfer.files[0];
    // 复用前面 useUploader 的逻辑
  }}
  onDragOver={(e) => e.preventDefault()}
  className="border-2 border-dashed border-gray-400 rounded p-8"
>
  📂 把文件扔进来
</div>

八、结语:上传的尽头,是理解

当 AI 把猫咪照片识别成"一只橘猫在打盹",把语音转成"今晚吃什么?"时,

上传组件就不再是冷冰冰的 <input>,而是人类与算法握手言欢的桥梁

愿你写的每一个上传按钮,都能把比特变成诗。

祝你编码愉快,文件永不 413!

相关推荐
LaiYoung_1 分钟前
深入解析 single-spa 微前端框架核心原理
前端·javascript·面试
Danny_FD1 小时前
Vue2 + Node.js 快速实现带心跳检测与自动重连的 WebSocket 案例
前端
uhakadotcom1 小时前
将next.js的分享到twitter.com之中时,如何更新分享卡片上的图片?
前端·javascript·面试
韦小勇1 小时前
el-table 父子数据层级嵌套表格
前端
奔赴_向往1 小时前
为什么 PWA 至今没能「掘进」主流?
前端
小小愿望1 小时前
微信小程序开发实战:图片转 Base64 全解析
前端·微信小程序
掘金安东尼1 小时前
2分钟创建一个“不依赖任何外部库”的粒子动画背景
前端·面试·canvas
电商API大数据接口开发Cris1 小时前
基于 Flink 的淘宝实时数据管道设计:商品详情流式处理与异构存储
前端·数据挖掘·api
小小愿望1 小时前
解锁前端新技能:让JavaScript与CSS变量共舞
前端·javascript·css
程序员鱼皮1 小时前
爆肝2月,我的 AI 代码生成平台上线了!
java·前端·编程·软件开发·项目