前端+AI实战:用React搭建资料AI问答助手的完整记录
说一个略微扎心但很真实的事前两天和一个做了多年后端的哥们儿吃饭他问我你们前端现在是不是很慌我说慌什么他说AI都能自己写前端了。我没接话因为我心里清楚AI不仅能写前端也能写后端甚至写得比很多人都快。真正该慌的不是某条技术栈而是“只会照着原型切页面”这种工作方式本身。我决定认真把“前端AI”啃一遍今天是第四天。这几天踩了不少坑也想通了不少事顺手把刚跑通的一个小项目写出来给同样在观望的前端同学一个参考。这个项目背景很简单我手上有一批零散的内部资料和培训文档想快速做一个“资料AI问答助手”的网页版。前端负责对话界面和历史记录AI能力直接走大模型API再加一层轻量的本地检索逻辑。听起来不难但真正做完涉及的东西比想象中多提示词设计、流式渲染、上下文管理、错误降级以及一堆只有在真实联调里才会遇到的坑。这篇笔记就按我这几天实际经历的节奏来写不绕弯子。1. 为什么前端要啃AI我的动机和行业观察1.1 前端正在经历从“画页面”到“接大脑”的转型前几天我刷到一个招聘帖岗位名称直接叫“AI应用前端工程师”。要求写得很直白除了React和TypeScript还得熟悉大模型API的调用方式、会处理流式响应、理解RAG的基本流程、能把对话式交互做得跟ChatGPT一样顺滑。这个岗位给到的薪资比同级别的普通前端岗位高出不少。这件事给了我挺大触动。过去两年前端圈流行一个说法业务复杂度都往后端塞前端就是“调接口的皮肤”。但现在情况变了大模型把“接口”这个词的含义彻底扩大了。以前的接口返回结构化JSON你拿到数据渲染就好现在的接口返回的是一段还在往外蹦的文字流你不仅要在页面上展示还得管理它的状态、中断、重试、上下文长度甚至要在本地做缓存和索引。这些能力传统前端开发里根本不会遇到。所以我的判断是前端学AI不是去卷算法也不是非要把Transformer原理啃得底朝天。更现实的路是去掌握那些“让AI能力真正落地到业务里”的技能。模型是别人的数据是企业的而把两者粘起来、做成好用的产品界面恰恰是前端最擅长的事情。这是机会比焦虑实在得多。1.2 AI编程没有那么玄先搞清楚它改变的是什么另一个让我下决心学AI的原因是AI编程工具已经深度融入了我的日常工作。最开始我用AI辅助写代码也就是让它补全函数、解释报错后来慢慢开始让它写整个页面、改样式、做数据联调。实话说前半个月我踩过不少坑比如它生成的东西看起来能用一上线就崩再比如它特别容易“一本正经地胡说八道”尤其在组件版本和接口字段这种细节上容易出错。但用得多了之后我总结出一个规律AI编程工具的产出质量90%取决于你怎么描述需求。同样一个对话框有人能几句话让它写出可用的组件有人来回扯皮半小时还拿不到能跑的结果。这其实就是“提示词”的能力。在AI编程场景里提示词远远不只是一句话它包含了角色设定、约束条件、输入输出格式、错误处理逻辑甚至要给它看完整的报错信息和相关代码。今天我练手时特意给AI编程助手定义了一份简单的“skills”——也就是一套固定格式的角色与行为说明让它在帮我写前端代码时自动遵循。效果还挺明显至少它不再默认用哪些老掉牙的写法也能主动帮我考虑边界情况。这个方向我想后面继续深入因为AI Agent时代前端开发者可能要经常跟“技能定义”这种东西打交道。2. 今日实战给本地知识库加一个AI问答界面2.1 项目目标与选型思路既然要学光看教程肯定不行。我的做法是拿一个真实要用的场景练手给手头一沓内部培训资料做一个网页版问答助手。用户输入问题系统从资料里检索相关内容连同问题一起发给大模型生成答案并展示在页面上同时保留会话历史。前端技术栈选择了React Vite TailwindCSS。之所以这么选一是现在很多中后台项目都是这套组合二是Vite的冷启动快适合我这种每天只能挤出一两个小时的学习节奏。组件库直接用了Ant Design没有重新造轮子。AI交互部分没有用第三方现成的对话组件因为这类组件的抽象程度普遍不够在流式渲染和消息状态上自己写反而更可控。后端我简单搭了一个Node.js服务主要负责把前端传来的问题拼接成完整提示词调大模型API再把流式返回转发给前端。按理说应该用Java写这也是我之前计划补的方向但今天为了快速验证前端链路先用自己最熟的环境跑通再说。这里我自己的体会是学习阶段千万不要在技术选型上过度纠结能最快验证想法的组合就是好组合。2.2 前端聊天界面的核心设计一个AI问答界面表面看起来就是一个聊天窗但真做起来有几个环节不能省。我把今天实现的界面拆成三个核心区域左侧是会话列表中间是消息流底部是输入区。会话列表支持新建会话、切换历史和删除消息流区分用户消息和AI消息输入区带发送按钮和“停止生成”按钮。消息的数据结构我设计得比较朴素interface Message { id: string; role: user | assistant; content: string; status: pending | streaming | done | error; createdAt: number; }这个status字段很关键尤其对AI对话场景。一开始我没设计这个字段导致AI在流式返回时UI要么不断重渲染要么干脆不更新。后来加上status渲染逻辑才清晰起来pending表示请求刚发出在等待首个tokenstreaming表示正在流式输出done表示输出完成error则是请求失败或超时。组件里只需要根据status切换不同的展示形态就可以了。页面的整体布局我用了一个简单的flex结构外层容器高度撑满视口左侧会话栏固定宽度220px右侧主区域flex: 1。消息区用了overflow-y: auto配合滚动到底部的逻辑保证新消息能自动出现在视野里。这个滚动逻辑看着简单但如果不做长对话时体验会非常差。2.3 对接模型的请求封装与数据流AI问答和普通接口请求最大的不同在于响应是持续到达的。我封装了一个请求方法用fetch的ReadableStream逐段读取服务端转发回来的数据再用TextDecoder按UTF-8解码。async function fetchAIResponse(messages: Message[]) { const res await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ messages }), }); if (!res.ok || !res.body) { throw new Error(请求失败${res.status}); } const reader res.body.getReader(); const decoder new TextDecoder(utf-8); let content ; while (true) { const { done, value } await reader.read(); if (done) break; content decoder.decode(value, { stream: true }); // 每拿到一段数据就回调更新UI onChunk(content); } return content; }这段代码是今天整个项目的核心。最开始我用的是axios但axios对流式响应支持不够友好后来换成原生fetch配合ReadableStream问题迎刃而解。解码时那个{ stream: true }参数也别漏如果漏了遇到中文多字节字符跨段传输时会出现乱码。这个问题我踩了一下午后面在排查实录里会细说。3. 关键细节提示词、流式输出与skills的落地3.1 提示词工程先给角色再给约束提示词这块我一开始的理解很简单把用户问题丢给大模型就行。跑了几个例子之后发现这种直接问法在纯知识问答场景还凑合但要结合具体的资料内容提问效果就一言难尽了。模型会自由发挥引用不存在的资料甚至编造根本不存在的文档内容。后来我改成了一套比较稳定的提示词模板先给模型定义角色和任务边界再说明回答要求最后把检索到的相关资料作为上下文拼进去。你是企业内部文档助手。请基于“参考资料”回答用户问题。 要求只依据参考资料回答参考资料中没有的内容明确说明“资料中未找到相关信息”回答内容控制在300字以内使用简洁的中文如用户问题与文档无关请引导用户询问文档相关内容。参考资料 {retrievedContext}用户问题 {question}这样处理后模型胡编乱造的情况明显减少。核心原因也好理解大模型本质上是一个“接龙游戏”你给它的上下文越明确、边界越清晰它自由发挥的空间就越小。提示词不是写作文是在划定模型的思考范围。这个理解对我后面写各种AI功能帮助很大。3.2 流式输出的体验优化技巧流式输出做完之后页面能实时显示文字了但体验还是有点问题。我遇到的第一个问题是文字更新频率太快页面肉眼可见地发卡。查了一下才知道每读到一小段数据就setStateReact会频繁调度更新尤其当内容越来越长渲染成本会持续升高。我把更新策略改成了“节流更新”用一个定时器每100到150毫秒才把最新内容批量写入state。这样既不影响文字展示的流畅度也大幅减少了渲染次数。另一个小技巧是让已渲染内容尽量少变化比如AI消息的内容用一个稳定的ref来存累计文本只在定时器触发时更新一次UI中的文本。还有一个细节是“停止生成”功能。用户点击停止后需要真正中断请求而不是只在UI上把按钮变个颜色。我这里用AbortController实现了中断逻辑const controller new AbortController(); // 发起请求时传入 signal fetch(/api/chat, { signal: controller.signal }); // 点击“停止生成”时调用 controller.abort();中断之后要把当前消息的status置为done并把已经生成的部分内容保留下来。很多AI产品都有这个逻辑看起来是个小功能但对用户体感影响非常大。不加的话用户在等待过程中只能干瞪眼想停又停不下来体验会打折扣。3.3 AI Agent与skills前端可以提前布局的方向今天研究AI Agent相关概念时我越发觉得前端在这个领域能做的事情一点都不少。AI Agent说白了就是让大模型不仅能“回答问题”还能“调用工具、完成动作”。比如用户说“帮我查一下本周的排期并整理成表格”Agent会拆解任务、查询数据、生成表格甚至直接更新页面。这里跟前端最贴近的一个概念就是skills。AI编程工具里的skills简单理解就是预先定义好的角色能力和工作规范。我在项目里试着给AI编程助手定义了一个前端开发skill内容包括优先使用最新稳定版的React、组件样式统一用TailwindCSS、接口调用统一走封装好的请求方法。定义之后AI生成的代码风格稳定多了至少不会再出现一个项目里混着三种组件写法的场面。我给自己的计划是后面几天继续研究怎么把AI Agent接入到前端项目里。对前端来说最现实的应用方向有两个一是把对话式交互做得更智能让AI能操作页面元素二是做“AI助手面板”让它在网页上帮用户完成填写、查询、总结这类操作。这些场景里前端不只是UI层而是整个AI能力的交互入口。前端同学如果现在开始积累这些能力后面会很有竞争力。4. 学习路线与工具清单给想转AI的前端一份可执行清单4.1 我整理的前端AI最小学习路径这几天我查了不少资料也问了几位已经在做AI应用开发的朋友结合自己的实践整理出一条对前端比较友好的学习路径今天写出来供参考。这条路径我分成四个阶段每个阶段都有明确的目标和产出不会让人越学越迷茫。第一阶段先把大模型API用起来建立最小闭环。目标不是理解模型原理而是能写一个网页成功调用大模型接口并得到回复。这个阶段只需要会发HTTP请求、会处理JSON就够了一般一两天能跑通。第二阶段掌握流式响应和对话上下文管理。目标是做一个能连续对话的聊天界面把输入框、消息列表、加载状态、发送/停止按钮这些交互细节都打磨好。这一步是前端区别于纯后端同学的核心优势所在也是后面做AI应用的基本功。第三阶段学习RAG的基本思路也就是检索增强生成。理解如何把本地资料切成片段、存进向量库或做关键词索引然后在用户提问时把相关资料检索出来拼进提示词。这个阶段不用深入算法细节重点是理解数据从哪里来、怎么跟提示词结合。第四阶段再研究AI Agent和工具调用。试着让模型根据用户意图调用不同的函数或接口比如查询天气、创建任务、生成报表。这一步触及到“AI替用户干活”的本质也是目前行业需求增长最快的方向。4.2 工具与资源清单工具方面我目前用的几样东西都是经过实际验证的前端框架用React Vite样式层用TailwindCSS配合组件库AI编程辅助用Cursor和GitHub Copilot大模型API先用的在线服务后面有需要再接本地模型。调试方面Chrome DevTools还是主力特别是Network面板看请求耗时和流式数据的返回情况。AI编程插件的选择上我个人偏爱能用自然语言描述需求、并且能同时读取项目上下文的那种。用的时候建议养成一个习惯先让AI阅读项目里的关键文件比如package.json、路由配置、组件目录结构再让它改代码或写代码。这样生成的代码往往更贴合工程现状而不是凭空造一套。资料这块我推荐几个方向官方文档优先大模型厂商的API文档和示例代码是最准确的其次是开源项目GitHub上有很多“chatgpt clone”或“ai-chat-ui”之类的项目把前端、后端、部署全链路都跑一遍比自己闭门造车快得多。此外日常开发时多留意那些用了AI能力的网站或产品拆解它们的交互设计再对照自己实现一遍收获很大。5. 今日踩坑记录与排查实录5.1 三个典型问题与解决过程第一个坑就是前面提到的axios流式返回乱码。我最初通过axios的onDownloadProgress去读取数据结果发现拿到的响应体是乱码尤其是中文内容几乎没法用。查了半天发现axios在处理流式响应时对分段数据的解码逻辑不太透明很容易遇到编码问题。后来换用fetch的ReadableStream手动解码问题才解决。这个经验放在这里给后面做类似需求的人提个醒流式场景优先用fetch不要一上来就套axios。第二个坑是Node服务端在转发流式数据时默认的响应头少了一项。前端一直拿不到数据排查了很久才发现是Content-Type设置成了application/json导致的。改成text/event-stream之后数据就能正常到达了。其实这里的本质问题是流式响应的Content-Type需要按照流式协议来设置不然浏览器端拿到的响应体不会按预期作为可读取的流来处理。第三个坑是长内容场景下的串字问题。到聊天记录超过二十轮的时候我发现AI偶尔会把上一轮的内容和这一轮的混在一起排查后发现是服务端拼接历史消息时没有把每一轮的完整消息体都带上而是只带了部分内容。这种问题在调试阶段很难发现因为短对话时上下文短模型不会出问题一旦上下文变长拼接不完整的消息就会被模型当成“正常输入”去理解答案就会跑偏。5.2 问题排查速查表与避坑技巧今天的问题不少但好在都定位到了原因。我把几个常见问题整理成一张速查表方便以后遇到类似情况快速对照现象可能原因解决思路页面一直不显示AI回复响应头Content-Type错误确认流式接口使用text/event-stream中文内容乱码解码未按流式处理fetch TextDecoder并带上{stream: true}文字滚动卡顿React频繁setState使用节流控制UI更新频率停止按钮点了没用请求未真正中断使用AbortController并传入fetch signal上下文长了回答跑偏历史消息拼接不完整审查服务端拼接逻辑带上完整消息体处理AI接口时还有几个小技巧我也总结一下第一所有AI请求都要设超时。大模型接口的响应时间波动很大没有超时机制的话页面可能一直转圈。第二要区分“网络失败”和“模型返回异常”最好把错误类型分开处理至少用户看到的提示信息要友好不能直接把一堆技术报错拍在用户脸上。第三接口返回的数据在做类型定义时不要只写一个any尤其是流式数据要针对状态字段做联合类型后面维护起来会省很多事。最后再分享一条这几天最深的感受学前端AI最重要的是先跑通一个端到端的小项目再回过头补理论效率和动力都会高很多。今天这个项目虽然还比较粗糙但当我第一次看到页面上一个字一个字蹦出AI回答的时候还是有点兴奋的因为我知道这条路走对了。明天打算继续把AI Agent的编排逻辑再啃一啃到时候有新发现再写一篇笔记出来。