使用fetchEventSource构建高效AI智能助手:文件搜索场景的完整实现与深度解析
在当今AI技术飞速发展的浪潮中,如何将大模型的能力无缝集成到企业应用中,成为每个开发者面临的挑战。今天,我将分享一个基于fetchEventSource技术构建的AI智能文件搜索助手的完整实现,该组件已在企业级文档管理系统中成功应用,显著提升了用户检索效率。我们将深入剖析其技术实现,包括流式响应、SSE通信、Markdown渲染等核心功能。
一、技术背景:为什么选择SSE与fetchEventSource
在实现AI对话类应用时,我们通常面临两种技术路线选择:
- WebSocket:双向全双工通信,适合实时交互场景
- Server-Sent Events (SSE):单向服务器推送,轻量级实现
对于AI助手这类服务器持续生成内容,客户端只需接收的场景,SSE具有明显优势:
- 基于HTTP协议,无需特殊协议支持
- 自动重连机制
- 简单的消息格式
- 浏览器原生支持
而@microsoft/fetch-event-source库则为我们提供了现代化、Promise风格的SSE API封装,相比原生EventSource具有更灵活的配置和控制能力。
二、功能需求与产品设计
我们的AI智能文件搜索助手需要实现以下核心功能:
- 自然语言搜索:用户通过对话式提问搜索文件
- 流式响应:AI生成内容时逐步展示,提升用户体验
- 相关文件展示:搜索结果附带相关文件推荐
- 历史记录管理:记录并展示用户最近的搜索记录
- 富文本支持:支持Markdown、数学公式等复杂内容渲染
- 文件预览集成:点击文件可直接查看内容
三、核心代码实现:从设计到落地
1. 组件结构与状态管理
首先,我们定义组件的核心数据结构:- export default class AiIntelligentSearch extends Vue {
- private searchQuery = ''; // 用户输入的搜索内容
- private loading = false; // 加载状态
- private recentSearches = [] as ISearchInfo[]; // 最近搜索记录
- private searchFileList: Array<{ // 搜索结果列表
- question: string;
- content: string;
- count: number;
- fileList: IFile[] | Array;
- }> = [];
- private selectedTag = -1; // 选中的历史记录标签
- private fileListStatus: { [key: number]: boolean } = {}; // 文件列表展开/收起状态
- // ...其他状态
- }
复制代码 2. 流式响应实现:fetchEventSource深度应用
核心在于aiStreamSearchFileList方法,它使用fetchEventSource实现服务器推送:- async aiStreamSearchFileList() {
- const ctrl = new AbortController(); // 用于中断请求
-
- try {
- await fetchEventSource(process.env.VUE_APP_BASE_URL + '/api/aiStreamSearchFileList', {
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'text/event-stream',
- },
- body: JSON.stringify({
- limit: 10,
- offset: 1,
- st: this.searchQuery.trim() + '',
- sort: 0,
- sortType: 1,
- fileAppCode: 'file_management',
- }),
- method: 'POST',
- signal: ctrl.signal, // 传递信号以支持中断请求
- openWhenHidden: true, // 页面退至后台时保持连接
-
- // 连接建立回调
- onopen: async (response) => {
- if (!response.ok) {
- console.error('连接失败:', response.statusText);
- ctrl.abort();
- }
- },
-
- // 收到消息回调
- onmessage: (event) => {
- try {
- const data = JSON.parse(event.data);
- if (data) {
- if (data.status !== 'completed') {
- // 处理流式数据
- if (data.choices && data.choices[0].delta) {
- const content = data.choices[0].delta.content;
- if (content) {
- // 将内容追加到最新的搜索结果
- if (this.searchFileList.length > 0) {
- this.searchFileList[this.searchFileList.length - 1].content += content;
- }
- }
- }
- // 强制更新视图,显示最新内容
- this.$forceUpdate();
- } else {
- // 处理完成状态
- const lastItem = this.searchFileList[this.searchFileList.length - 1];
- if (!lastItem.content) {
- lastItem.content = '未找到符合相似度要求的文档。';
- }
- ctrl.abort(); // 中断请求
- this.loading = false;
- this.$forceUpdate();
- }
- }
- } catch (parseError) {
- console.error('解析消息时出错:', parseError);
- ctrl.abort();
- this.loading = false;
- }
- },
-
- // 错误处理
- onerror: (error) => {
- console.error('发生错误:', error);
- ctrl.abort();
- this.loading = false;
- this.$message.error('服务器连接中断,请稍后重试');
- },
-
- // 连接关闭
- onclose: () => {
- ctrl.abort();
- this.loading = false;
- console.log('连接已关闭');
- },
- });
- } catch (error) {
- console.error('启动流式响应时出错:', error);
- ctrl.abort();
- this.loading = false;
- this.$message.error('无法启动流式响应,请检查网络或服务器状态');
- }
- }
复制代码 关键点解析:
- 使用AbortController实现请求可取消
- onmessage中处理流式数据,追加到最新搜索结果
- this.$forceUpdate()确保Vue及时渲染新增内容
- 错误处理完善,保证用户体验
3. 搜索执行流程整合
将普通搜索API与流式响应API结合使用:- async searchAIFileList() {
- this.loading = true;
- const param = {
- limit: 10,
- offset: 1,
- st: this.searchQuery.trim(),
- sort: 0,
- sortType: 1,
- };
- // 1. 先调用普通搜索API获取文件列表
- const [err, res] = await to(EMSearchFileList(param));
-
- if (err) {
- this.$message.error('搜索失败,请稍后重试');
- this.loading = false;
- return;
- }
- // 2. 构建初始搜索结果
- const fileList = Array.isArray(res) ? res : [];
- const searchResult = {
- fileList,
- content: '',
- count: fileList.length || 0,
- question: this.searchQuery.trim(),
- };
- // 3. 无结果时设置默认提示
- if (!res || fileList.length === 0) {
- searchResult.content = '未找到符合相似度要求的文档。';
- }
-
- // 4. 添加到结果列表
- this.searchFileList.push(searchResult);
- // 5. 等待DOM更新后滚动到底部
- this.$nextTick(() => {
- this.scrollToBottom();
- });
- // 6. 执行流式响应获取AI生成内容
- await this.aiStreamSearchFileList();
-
- // 7. 更新最近搜索记录
- await this.getRecentSearchList();
-
- // 8. 清空输入框
- setTimeout(() => {
- this.searchQuery = '';
- this.loading = false;
- }, 1000);
- }
复制代码 这种两阶段设计确保了:
- 先快速返回文件列表,提供即时反馈
- 再通过流式响应逐步生成详细内容
- 最后更新搜索历史,完善用户体验
4. Markdown与数学公式渲染
为了支持复杂内容展示,我们集成了markdown-it和markdown-it-katex:- renderedMarkdown(content: string) {
- const md = new MarkdownIt({
- html: true, // 启用HTML标签
- linkify: true, // 自动识别URL
- typographer: true, // 启用智能引号等
- });
-
- // 添加数学公式支持
- md.use(markdownItKatex, {
- blockClass: 'katex-block',
- errorColor: '#cc0000',
- throwOnError: false,
- macros: {
- "\\RR": "\\mathbb{R}",
- "\\p": "\\frac{\\partial #1}{\\partial #2}",
- }
- });
-
- // 自定义渲染规则
- md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
- const token = tokens[idx];
- token.attrPush(['target', '_blank']); // 增加target="_blank"
- token.attrPush(['rel', 'noopener noreferrer']); // 增加安全属性
- return self.renderToken(tokens, idx, options);
- };
- return md.render(content || '');
- }
复制代码 在模板中使用:5. UI设计与交互优化
组件采用精心设计的UI布局,确保良好的用户体验:-
-
-
-
- {{ item.question }}
-
-
-
-
-
-
-
-
-
-
-
- 0">
-
- <img src="https://www.cnblogs.com/@/assets/file-ai-list-search.svg" alt="" />
- 共找到{{ item?.fileList.length }}个相关文件
-
-
-
- {{ fileListStatus[index] ? '展开' : '收起' }}
-
-
-
-
-
-
-
-
- <img :src="iconSrc(file.fiType || '')" alt="" />
- {{ file.oldFileName }}
-
-
-
-
-
-
复制代码 关键交互细节:
- 问题内容可点击复制
- 文件列表可展开/收起
- 支持文件图标根据类型动态显示
- 搜索历史标签支持点击重搜
6. 实用工具方法
- // 滚动到最新消息
- scrollToBottom() {
- const sectionList = this.$refs.sectionList as HTMLElement;
- if (sectionList) {
- const lastQaList = sectionList.querySelectorAll('.qa-list')[this.searchFileList.length - 1];
- if (lastQaList) {
- lastQaList.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- }
- }
- }
- // 复制问题内容
- copyQuestion(item: any) {
- const textToCopy = item.question || '';
- navigator.clipboard.writeText(textToCopy).then(() => {
- message.success('复制成功');
- }).catch(() => {
- message.warning('复制失败');
- });
- }
- // 获取文件图标
- get iconSrc() {
- return (suffix: string) => {
- return requireIcon(suffix);
- };
- }
复制代码 四、性能优化与错误处理
1. 请求中断机制
使用AbortController实现请求可取消:- const ctrl = new AbortController();
- // 传递信号
- await fetchEventSource(url, { signal: ctrl.signal });
- // 需要中断时
- ctrl.abort();
复制代码 2. 错误处理策略
- onerror: (error) => {
- console.error('发生错误:', error);
- ctrl.abort(); // 确保请求终止
- this.loading = false;
- // 显示用户友好错误
- this.$message.error('服务器连接中断,请稍后重试');
- },
复制代码 3. 空状态处理
- // 处理完成状态
- if (data.status === 'completed') {
- const lastItem = this.searchFileList[this.searchFileList.length - 1];
- // 确保空内容时有提示
- if (!lastItem.content) {
- lastItem.content = '未找到符合相似度要求的文档。';
- }
- // 确保fileList为数组
- if (!Array.isArray(lastItem.fileList)) {
- lastItem.fileList = [];
- }
- lastItem.count = lastItem.fileList.length || 0;
- }
复制代码 五、部署与集成
组件完整集成到企业文档管理系统中,需要处理的集成点:
- 认证与授权:在请求头中添加token
- 文件预览:集成现有文件预览组件
- viewFile(item: IFile) {
- this.detailFileModal.open(item.fileID);
- // 添加文件访问记录
- ManageModule.addFileRecord(item.fileID);
- }
复制代码
- 环境变量配置:使用process.env.VUE_APP_BASE_URL配置API地址
六、总结与展望
通过fetchEventSource实现的AI智能文件搜索助手,完美解决了传统搜索体验的不足:
- 实时反馈:流式响应让等待感消失
- 自然交互:对话式搜索降低使用门槛
- 精准结果:AI理解用户真实意图
- 无缝集成:与现有文件系统完美融合
未来优化方向:
- 增加上下文感知,支持多轮对话
- 优化提示工程,提高搜索准确率
- 添加个性化推荐,基于用户历史行为
- 支持多语言处理,满足国际化需求
七、完整代码示例
以下是组件的核心部分,完整代码请参考文末GitHub链接:- <template>
-
- <main-header-component
- :is-show-store-btn="false"
- :is-show-search="false"
- :is-show-upload-btn="false"
- ></main-header-component>
-
-
-
-
-
-
- <img src="https://www.cnblogs.com/@/assets/file-ai-search-logo.svg" alt="" />
-
-
-
- 文件搜索助手
-
- {{ data.isShowSearch ? '收起' : '展开' }}
-
-
-
-
-
- AI搜索以自然语言处理和语义理解为核心,通过智能分类、个性化推荐及跨平台实时检索技术,精准捕捉用户意图并快速定位目标内容,同时支持多模态数据的关联分析,实现"对话式"高效搜索体验。
-
- 0">
- 最近搜索
-
-
- <img v-if="selectedTag === tag.siId" src="https://www.cnblogs.com/@/assets/file-ai-question-search.svg" alt="" />
- {{ tag.content?.length > 10 ? tag.content.slice(0, 10) + '...' : tag.content }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <img src="https://www.cnblogs.com/@/assets/file-ai-send.svg" alt="发送" />
-
-
-
- 内容由各平台大模型生成,不能完全保证准确性和完整性,不代表我们的态度或观点
-
-
-
- </a-spin>
- <file-detail-component ref="detailFileModal"></file-detail-component>
-
- </template>
复制代码 这个实现不仅展示了fetchEventSource在AI应用中的强大能力,更提供了一套完整的企业级解决方案。通过精心设计的UI/UX和稳定的错误处理机制,为用户提供了流畅、可靠的智能搜索体验。希望这篇文章能为你的AI应用开发提供有价值的参考!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |