研途灵伴 · 阶段开发博客:5 月 29 日至 6 月 3 日的跟课上下文与 AI 富文本体验推进
作者:郝子旭(组长)
阶段时间:2026-05-29 至 2026-06-03
本阶段关键词:跟课模式、ASR、课程上下文、会话追问、图片题、富文本渲染
一、这一阶段为什么转向跟课和上下文
5 月 29 日到 6 月 3 日这段时间,我主要在补两类能力:一类是跟课模式里的实时上下文,另一类是 AI 回答在前端的表达质量。
项目做到这个阶段后,普通聊天和计划、错题、复习这些链路已经能跑起来。但“研途灵伴”如果只是一个能问问题的聊天窗口,还不够贴近真实考研场景。用户学习时经常不是单独打开一道题,而是在看课、听讲解、截屏、追问上一问。系统如果不能理解“我现在正在看的内容”和“我刚才问过的题”,很多回答就会显得割裂。
所以这一阶段的重点变成了上下文:跟课时的窗口、截图、OCR、转写、笔记如何形成短期记忆;聊天时用户无图追问,系统如何判断是不是在问上一道图片题;AI 输出的公式、表格、代码和解析,如何在各个页面都正常展示。
这段工作的难点在于,它不属于单个页面或单个服务。跟课上下文要同时经过 Electron、前端 store、WebSocket、后端模型、数据库和 Agent;富文本渲染则会影响几乎所有展示 AI 内容的页面。改这种东西时,不能只看某个功能点是否完成,还要看它会不会破坏别的入口。
我当时给自己的判断标准是:跟课要能更接近真实学习现场,答疑要能承接上一轮上下文,AI 输出要在不同页面里保持一致。只要这三件事能往前推进,系统就会比原来更像一个连续陪伴的学习工具。
二、跟课逻辑从截图扩展到声音和转写
这一阶段前半段,我对跟课模式做了比较大的调整。
原来的跟课更偏向截图和 OCR:用户选择窗口后,系统采样关键帧,后端识别文字,再把这些内容放到课程上下文里。这个方向是对的,但真实课堂里,很多信息来自老师讲解,不一定全部出现在屏幕上。只看画面,系统会少掉一部分语义。
这次我在桌面端增加了课程音频采集相关能力,包括系统音频和麦克风输入,并新增 ASR WebSocket 链路。前端负责采集、降采样、推送音频片段,后端通过 ASR 客户端处理转写结果,再把 transcript 写入课程上下文。为了让用户知道系统是否正在工作,前端也加了实时转写状态和 LiveTranscript 展示。
这部分改动跨了很多层:Electron 主进程要处理权限和窗口能力,preload 要暴露接口,React store 要维护跟课状态,后端要有 ws_asr 路由、ASR schema、课程 transcript 模型和迁移,课程上下文服务也要能把转写内容纳入缓冲。
它让我感受到桌面端项目和普通 Web 项目的区别。Web 页面里加一个输入框就能收集文本,但桌面端想拿到“用户正在学习的现场”,就要碰到系统音频、窗口采集、权限、实时通信和后端存储这一整串问题。
这里面最容易低估的是实时性。ASR 不是用户点一次按钮、后端返回一个结果这么简单。前端要持续拿到音频片段,处理采样率和格式,再通过 WebSocket 发给后端;后端要保持连接状态,转写完成后又要及时推回给前端,同时写入课程上下文。任何一步断了,用户看到的就是“系统好像没在听”。
我也给前端加了 transcript store,用来维护实时转写片段和连接状态。这样页面不用直接理解底层 WebSocket 的每个细节,只要订阅状态和文本即可。这个拆分很重要,因为跟课页面、悬浮窗、状态提示都可能用到转写信息,如果每个地方都直接操作 socket,后面会很难维护。
后端新增 ws_asr 路由、ASR schema、ASR client 和 transcript 表之后,跟课数据的形态也变了。以前主要是 frame 和 OCR block,现在 transcript 也成为一等数据。它能被课程上下文读取,也能进入课程记忆生成材料。这让跟课模块从“看屏幕”往“听课过程”靠近了一步。
当然,这里还有很多现实边界。系统音频和麦克风权限在不同环境下会有差异,ASR 服务也可能不可用或返回不稳定。所以这一阶段我更强调链路先成立,同时保留降级路径。即使转写失败,截图、OCR、笔记和用户问题仍然能支撑基本的课程上下文。
三、课程上下文开始更像学习现场
有了 transcript 以后,课程上下文不再只是最近几张截图的 OCR 结果,而是可以同时包含窗口标题、关键帧、识别文本、实时转写、用户笔记和最近一次问答摘要。
我在课程上下文服务里继续整理了缓冲策略。跟课会话要能记录最近一段时间内的有效信息,也要避免重复画面不断堆积。课程记忆生成时,转写内容也应该成为更重要的材料,因为它更接近老师讲解的连续语义。
截图答疑也和课程上下文继续靠近。用户在跟课时提问,不一定会把题目完整描述出来,很多时候只会说“这个为什么这样变形”“这里没听懂”。如果后端能拿到最近的课程上下文,Tutor 的回答就不必完全依赖用户这一句话。
这个阶段我更清楚地认识到,跟课模式的价值不是“自动截屏”本身,而是把学习现场变成可被后续问答和复盘使用的材料。截图、OCR、ASR、笔记只是入口,真正有用的是它们被整理进同一个上下文结构里。
课程上下文服务里有一个比较实际的取舍:信息不能无限堆。跟课过程中截图和转写都会持续产生,如果全部塞给后面的问答或记忆生成,既浪费模型上下文,也会让重点变模糊。所以服务端需要维护一个滚动缓冲,只保留最近一段时间内更有用的信息。
重复帧处理也属于这个问题。上课时屏幕可能长时间不变,如果每隔几秒都上传一张几乎相同的图,数据库和上下文都会被重复信息占满。通过相似度判断跳过静止画面,可以让系统更关注真正变化的内容。
课程记忆生成也因此有了更可靠的素材。原来如果只看 OCR,模型可能更像是在总结课件;加入 transcript 后,它能看到老师讲解过程、用户标记的问题和最近答疑摘要。这样生成的课程记忆更接近“这节课实际学了什么”,而不是只复述屏幕上的文字。
我觉得这个地方体现了项目里一个很重要的思路:AI 不应该只在最后生成一段总结,它前面需要有结构化、可筛选、可追溯的数据。上下文整理得越清楚,后面模型越不容易乱发挥。
四、会话追问让图片题不再断掉
这一阶段后半段,重点转到会话上下文追问和全局富文本。
先说追问。图片题答疑里经常出现一个很自然的场景:用户先发一张题图,AI 讲了一遍,用户接着问“第二步为什么这样来”“这个式子怎么变的”“继续讲”。这时候用户通常不会再上传一次图片。如果系统只看当前消息,就会发现没有图片,然后给出很泛的回答。
我这次扩展了 AgentContext,加入 context_images、active_context、context_reference 等字段,并在 agent service 和语义服务里判断当前消息是否需要复用上一题图片。规则也比较克制:当前轮有新图时优先新图;无新图时,只有语义上像追问上一题,才使用当前会话里的 active context;不跨会话、不跨 persona 复用。
这里的边界很重要。用户在学习助手里追问上一题,可以复用题图;但如果用户突然说“明天计划怎么排”或去生活助手聊饮食,就不能把上一张数学题图带过去。上下文能让系统更聪明,也可能让系统误会用户,所以我宁愿保守一点。
Tutor 本身也做了调整。它会优先使用当前图片,其次在语义判断成立时使用 active context 图片;同时保留最近文字历史。对于模型只返回知识点目录、没有真正讲解的低质量回答,也加入了重试逻辑。这个改动的目标很明确:让用户连续追问一道题时,体验更接近真人讲题。
这部分最难的是“该不该复用”的判断。复用太少,用户追问上一题时系统接不上;复用太多,又会把上一张题图带到不相关的问题里。比如用户先问数学题,下一句说“今天晚上怎么安排复习”,这句话可能仍然发生在学习助手里,但它不应该继续绑定上一张图片。
所以我没有把 active context 设计成一个永远自动带上的隐藏参数,而是让语义服务判断 uses_active_context。模型可用时由语义模型辅助判断,模型不可用时用保守规则兜底。保守的意思是宁愿少复用,也不要在不相关场景里错误复用。
这个地方也加了测试。测试里会覆盖“第二步为什么”“继续讲”这类应该复用的表达,也会覆盖计划、饮食、休息、新题等不该复用的表达。对我来说,这种测试很必要,因为上下文问题一旦错了,用户很难描述成具体 bug,他只会觉得系统“听岔了”。
Tutor 的低质量回答重试也是这次的一个细节。模型有时候会只列知识点,不真正讲步骤。对学习助手来说,这种回答虽然不是空文本,但用户很难从里面学会题目。我把这类回答识别出来重新请求一次,是为了让系统不要只满足“有回复”,而要尽量给出可学习的解释。
五、AI 富文本终于统一起来
另一条线是富文本渲染。
AI 回答里会出现 Markdown、表格、数学公式、代码、引用、图片链接。之前各个页面对这些内容的处理不统一:聊天页有自己的 MarkdownRenderer,错题、复习、小测、课堂记忆、报告、状态页又各自用不同方式展示。结果就是同一类 AI 内容,在不同页面里可能一会儿公式不显示,一会儿表格挤出边界,一会儿代码块难看。
这次我新增了公共 RichTextRenderer 和 normalizeRichText。它统一处理 GFM 表格、数学公式、KaTeX、代码高亮,也对行内公式、块级公式、代码块、链接和 URL 做了保护,避免错误替换。聊天页原来的 MarkdownRenderer 也改成公共渲染器的薄包装,减少两套逻辑并存。
迁移范围比较广,覆盖聊天消息和卡片、错题详情、复习讲解、小测题干和解析、课堂记忆、课程上下文、报告、状态页、餐食推荐理由、计划调整原因、后台资源卡片等位置。
这件事看似是前端展示,其实也影响学习体验。数学公式如果显示不出来,答疑就不可信;表格和代码如果撑破页面,报告和后台就会显得粗糙。AI 内容越多,统一渲染层就越重要。
这里有一个小但麻烦的问题:公式格式并不总是标准 Markdown。模型可能输出 \( ... \) 或 \[ ... \],也可能把公式混在普通段落里。如果直接交给渲染器,部分内容会显示异常;如果粗暴替换,又可能把代码块、链接或 URL 里的字符改坏。所以 normalizeRichText 需要先保护代码块、行内代码、链接、图片地址,再处理公式。
我在这个地方加了小测试,主要是为了防止以后改富文本规则时误伤已有内容。富文本处理很容易“修一个显示,坏另一个显示”,尤其是公式、代码和 URL 混在一起时。如果没有测试,很难靠肉眼把所有页面都看完。
这次迁移范围很广,也让我重新确认了一个前端原则:只要是 AI 生成正文,就应该走统一渲染层。不要让聊天页一套、报告页一套、错题页一套。短期复制组件好像快,长期一定会出现同一种内容在不同页面表现不同的问题。
六、验证和剩下的问题
这阶段提交里记录了 normalizeRichText 测试、前端构建和 git diff --check。这些验证说明语法和构建层面过了,也能保证富文本归一化的一些关键规则没有明显退化。
但跟课和上下文这类能力,只靠构建通过还不够。ASR 要看真实环境下的权限、音频输入、网络和服务稳定性;课程上下文要看真实课程里信息是否够用;会话追问要看用户自然表达是否会被正确识别。也就是说,这一阶段更多是把技术链路搭起来,后面还需要在实际使用里继续调。
我也意识到,上下文越强,系统越要有边界。用户不希望每一句话都被上一段内容绑定,也不希望切换角色后旧上下文继续影响新会话。后续继续做时,除了让系统“记得更多”,还要让它“知道什么时候忘掉”。
七、阶段小结
这段时间的工作,让系统更接近“能理解学习过程”的方向。
跟课模式不再只看截图,而是开始接入音频和转写;课程上下文能同时承接画面、文字、语音、笔记和问答摘要;图片题可以在同一会话里自然追问;AI 输出也开始在各个页面用同一套富文本规则展示。
我觉得这一阶段最大的变化,是系统开始从“用户输入一句话,系统回答一句话”,往“系统记得刚才发生了什么”走。这个方向会带来更多边界问题,比如上下文什么时候该复用,什么时候必须断开;但只要边界守住,它会让研途灵伴更像一个真正陪着用户学习的桌面助手。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)