第39期 | 多模态AI应用

第39期 | 多模态AI应用
第39期 | 多模态AI应用 今天你将学会理解多模态 AI 的交互模式图片文字语音如何协同工作设计多模态交互界面用户输入多种模态 → AI 理解并多模态回答实现图片理解前端用户上传图片 → AI 识别内容 → 描述/分析实现多模态对话界面文字图片混合消息 → 多模态回答 核心知识多模态是什么为什么是前端开发者的新机会单模态 vs 多模态模态输入输出例子纯文字文字文字ChatGPT文字图片文字图片文字GPT-4V看图说话文字→图片文字图片DALL-E语音→文字语音文字Whisper文字→语音文字语音TTS多模态文字图片语音文字图片语音GPT-4o全能多模态 AI 的核心价值让 AI 能像人一样同时理解文字、看图片、听声音。对前端开发者来说这意味着你需要设计能处理多种输入输出类型的交互界面。多模态交互的三种模式模式1图片理解Image Understanding用户上传/拍照 → AI 识别图片内容 → 文字描述/分析用户[上传一张呼吸机面板照片] 帮我识别这台设备的型号和序列号 AI根据图片分析这是一台 Respironics DreamStation 呼吸机 SN: 123456789SW Version: 3.14模式2混合对话Mixed Modal Chat文字图片混合消息 → AI 理解所有模态 → 多模态回答用户[上传UI截图] 这个页面布局有什么问题 AI分析截图后发现3个问题 1. 顶部导航和内容区之间间距过大约48px建议16px 2. 右侧卡片排列不对齐 3. 搜索框位置不够突出 [可能附带一张标注了问题的截图]模式3多模态创作Multimodal Creation文字描述 参考图片 → AI 生成新内容用户[上传一张日落照片] 帮我生成一张同样风格但城市天际线的图片 AI[生成一张城市天际线的日落图片]图片理解前端实现组件设计ImageUnderstanding图片理解界面 ├── ImageUploader图片上传/拍照 │ ├── DropZone拖拽上传 │ ├── CameraButton拍照按钮 │ └── PreviewCard图片预览 删除 ├── PromptInput附加文字描述 └── AnalysisResult分析结果展示 ├── TextDescription文字描述 ├── DetailPanel详细信息面板 └── ExportButton导出结构化数据ImageUploader 组件// features/multimodal/components/ImageUploader.tsx import { useState, useRef } from react; import { Upload, Camera, X } from lucide-react; interface ImageUploaderProps { onImageSelected: (imageData: ImageData) void; maxFileSize?: number; // MB } interface ImageData { file: File; previewUrl: string; // 本地预览 URL base64?: string; // Base64 编码发送给 API } export function ImageUploader({ onImageSelected, maxFileSize 20 }: ImageUploaderProps) { const [selectedImage, setSelectedImage] useStateImageData | null(null); const [isDragging, setIsDragging] useState(false); const [error, setError] useStatestring | null(null); const handleFile (file: File) { // 文件类型检查 const validTypes [image/jpeg, image/png, image/webp, image/gif]; if (!validTypes.includes(file.type)) { setError(请上传 JPG/PNG/WebP/GIF 格式的图片); return; } // 文件大小检查 if (file.size maxFileSize * 1024 * 1024) { setError(图片大小不能超过 ${maxFileSize}MB); return; } setError(null); // 生成预览 URL const previewUrl URL.createObjectURL(file); // 转换为 base64给 API 用 const reader new FileReader(); reader.onload (e) { const base64 e.target?.result as string; const imageData { file, previewUrl, base64 }; setSelectedImage(imageData); onImageSelected(imageData); }; reader.readAsDataURL(file); }; const handleRemove () { if (selectedImage) { URL.revokeObjectURL(selectedImage.previewUrl); } setSelectedImage(null); setError(null); }; // 拍照功能调用摄像头 const handleCamera async () { try { const stream await navigator.mediaDevices.getUserMedia({ video: { facingMode: environment } // 后置摄像头 }); // 创建 video 元素拍照 → 转为 File 对象 // ... 实现略 } catch { setError(无法访问摄像头); } }; return ( div {selectedImage ? ( // 已选择图片 → 显示预览 删除按钮 div classNamerelative rounded-lg overflow-hidden img src{selectedImage.previewUrl} alt预览 classNamew-full max-h-64 object-contain / button onClick{handleRemove} classNameabsolute top-2 right-2 rounded-full bg-black/50 p-1 text-white hover:bg-black/70 X size{16} / /button /div ) : ( // 未选择图片 → 上传/拍照按钮 div onDragOver{(e) { e.preventDefault(); setIsDragging(true); }} onDragLeave{() setIsDragging(false)} onDrop{(e) { e.preventDefault(); setIsDragging(false); handleFile(e.dataTransfer.files[0]); }} onClick{() { const input document.createElement(input); input.type file; input.accept image/*; input.onchange (e) handleFile((e.target as HTMLInputElement).files![0]); input.click(); }} className{border-2 rounded-lg p-8 text-center cursor-pointer transition-colors ${isDragging ? border-blue-500 bg-blue-50 : border-gray-200 hover:border-gray-300}} Upload size{32} classNamemx-auto mb-2 text-gray-400 / p classNametext-gray-500拖拽图片到此处或点击上传/p div classNameflex items-center justify-center gap-4 mt-4 button onClick{(e) { e.stopPropagation(); handleCamera(); }} classNameflex items-center gap-1 px-4 py-2 rounded-lg border text-sm hover:bg-gray-50 Camera size{16} / 拍照 /button /div /div )} {error p classNametext-sm text-red-500 mt-2{error}/p} /div ); }多模态对话界面消息类型定义// features/multimodal/types/index.tsinterfaceMultimodalMessage{id:string;role:user|assistant;// 消息内容是多模态的——可以同时包含文字和图片content:MultimodalContent[];timestamp:string;}typeMultimodalContent|TextContent|ImageContent|AudioContent;interfaceTextContent{type:text;text:string;}interfaceImageContent{type:image;url:string;// 图片 URLalt?:string;// 描述文字source?:user|ai;// 图片来源用户上传 or AI生成}interfaceAudioContent{type:audio;url:string;duration?:number;}多模态消息渲染// features/multimodal/components/MultimodalMessage.tsx export function MultimodalMessage({ message }: { message: MultimodalMessage }) { return ( div className{message.role user ? flex-row-reverse : flex-row} {message.content.map((item, idx) { switch (item.type) { case text: return MarkdownRenderer key{idx} content{item.text} /; case image: return ( div key{idx} classNamemy-2 img src{item.url} alt{item.alt || } classNamemax-w-sm rounded-lg cursor-pointer hover:shadow-md onClick{() openImageViewer(item.url)} / {item.source ai ( span classNametext-xs text-gray-400AI 生成/span )} /div ); case audio: return ( div key{idx} classNamemy-2 audio controls src{item.url} classNamew-full / /div ); } })} /div ); }多模态对话的后端接口// app/api/ai/multimodal/route.tsimportOpenAIfromopenai;exportasyncfunctionPOST(req:NextRequest){constformDataawaitreq.formData();constmessageformData.get(message)asstring;constimageFileformData.get(image)asFile|null;// 构建多模态消息constcontent:any[][];if(message){content.push({type:text,text:message});}if(imageFile){// 图片转 base64 发给 GPT-4oconstimageBufferawaitimageFile.arrayBuffer();constbase64ImageBuffer.from(imageBuffer).toString(base64);content.push({type:image_url,image_url:{url:data:${imageFile.type};base64,${base64Image},detail:high,// high detail mode更精确的图片理解},});}constresponseawaitopenai.chat.completions.create({model:gpt-4o,// 多模态模型messages:[{role:user,content,// 同时包含文字和图片},],stream:true,});// SSE 流式返回...}多模态交互的 UX 设计原则原则实现原因多入口文字输入框 图片上传按钮 语音按钮用户选择最方便的输入方式可组合文字图片可以同时发送一张图一段描述比纯图片更有上下文混合展示一条消息中文字和图片按顺序排列AI 的回答可能同时包含文字描述和标注图片可切换输入模式随时切换文字→语音→图片不同场景适合不同模态缩放查看图片可点击放大小图看不清需要放大查看细节常见误区误区1每种模态独立处理多模态的价值在于组合——文字图片一起发送比分开发送更有效。UI 设计要支持同时发送多种模态。误区2图片理解不需要用户文字提示只上传图片不给描述 → AI 可能猜错你的意图。加上帮我识别这台设备的型号 → AI 精准回答。误区3大图片直接发给 APIGPT-4o 对图片有大小限制20MB。前端需要压缩/裁剪后再发送。用 canvas 做客户端压缩// lib/image-compress.tsexportasyncfunctioncompressImage(file:File,maxWidth2048):PromiseFile{constimgawaitcreateImageBitmap(file);if(img.widthmaxWidth)returnfile;// 不需要压缩constratiomaxWidth/img.width;constcanvasnewOffscreenCanvas(maxWidth,img.height*ratio);constctxcanvas.getContext(2d)!;ctx.drawImage(img,0,0,canvas.width,canvas.height);constblobawaitcanvas.convertToBlob({type:image/jpeg,quality:0.85});returnnewFile([blob],file.name,{type:image/jpeg});} AI协作实战实战场景设计多模态分析界面场景呼吸机设备信息识别用户上传呼吸机面板照片 → AI 识别 SN 号和 SW 版本 → 提取为结构化数据我给 AI 的 prompt设计一个「呼吸机设备信息识别」界面 功能流程 1. 用户上传呼吸机面板照片拖拽/拍照 2. 用户可以附加文字描述帮我识别 SN 和 SW 版本 3. 图片发送给 GPT-4o Vision 进行分析 4. AI 返回结构化结果{ SN: xxx, SW: xxx, model: xxx } 5. 结果展示为卡片格式每个字段单独一行可复制 6. 支持「导出为 JSON」按钮 组件要求 - ImageUploader拖拽上传 拍照 - PromptInput附加描述输入框 - AnalysisResult结构化结果卡片 - ExportButton导出 JSON/CSV 用 React TypeScript Tailwind CSS。AI 输出的关键设计✅ 上传描述组合界面——图片和文字输入在同一面板中✅ 结果展示为结构化卡片——每个字段有标签值复制按钮❌ 缺少「历史识别记录」——追加要求识别结果持久化到 localStorage❌ 缺少「识别置信度」——追加AI 返回每个字段的置信度分数学到了什么多模态分析界面的关键是结构化输出——AI 不仅描述图片还提取为可用的数据结构。Prompt 中的格式约束很重要“请严格按 JSON 格式输出 {SN, SW, model}”。 动手练习练习1简单实现图片上传 AI 分析用 ImageUploader 基础聊天界面实现上传图片 → 发给 GPT-4o → AI 描述图片内容。练习2中等实现多模态对话界面在聊天界面中支持混合消息文字图片同时发送输入框旁边有图片上传按钮用户消息中图片和文字按顺序渲染AI 回复支持 Markdown 渲染练习3挑战实现完整的结构化识别应用选择一个实际场景如证件识别、产品信息提取实现图片上传/拍照Prompt 输入指定要提取哪些字段AI 返回结构化 JSON 结果结果以卡片形式展示每个字段可复制导出为 JSON/CSV图片压缩大图客户端压缩后再发送 本期要点多模态 同时理解文字图片语音前端需要设计能处理多种输入输出类型的界面图片理解的核心文字图片组合 纯图片加上文字描述让 AI 更精准多模态消息是混合类型一条消息可以包含 TextContent ImageContent AudioContent结构化输出是关键多模态分析不只是描述图片而是提取为可用的数据结构图片压缩是必需的GPT-4o 有 20MB 限制前端用 canvas 做客户端压缩 下期预告下一期是模块四的复盘——AI 应用开发模式总结。你将整理常见架构模式、性能优化策略、安全注意事项建立你的「AI 前端开发模式库」。如果你没有苹果电脑需要上传ios到APPStore可以访问以下网站iPA上传工具 - IPA解析与AppStore提交