Prevent deepseek from recalling response and cache the recalled message locally
< Feedback on DeepSeek Anti-recall
这个问题出现的原因在于脚本拦截和渲染的时效性差异。
在 DeepSeek 的深度思考 (Reasoning) 模式下,服务器通过 SSE (Server-Sent Events) 流式推送数据。当内容触发审查被撤回(Recall)时,脚本虽然拦截到了信号并修改了数据,但前端 UI 框架(通常是 React 或 Vue)由于其内部状态管理机制,可能无法实时识别到被脚本篡改后的数据流,导致页面卡住或显示异常。
刷新网页能恢复是因为 onHistoryMessageResp 函数发挥了作用——它在页面重新加载、获取历史消息记录时,直接从本地缓存读取并强制替换了消息内容。
onHistoryMessageResp
要实现“不刷新也能即时显示”,需要优化对流式输出的拦截逻辑。以下是针对该脚本的两个改进点:
目前的脚本在检测到撤回时,虽然修改了消息内容,但没有明确告诉前端“流已经结束”。如果前端还在等待 FINISH 信号,它就不会渲染最后一段数据。
FINISH
在 DSState.prototype.preCheck 中,当你检测到 TEMPLATE_RESPONSE 时,除了修改 data.v,建议尝试模拟一个结束信号。
DSState.prototype.preCheck
TEMPLATE_RESPONSE
data.v
handleEventItem
目前脚本在 onEventStreamResp 中处理字符串拼接。对于深度思考这种长文本,建议在检测到撤回的瞬间,立即触发一个自定义的 DOM 操作或者更彻底地拦截 XMLHttpRequest.responseText 的 getter。
onEventStreamResp
XMLHttpRequest.responseText
你可以尝试在 onEventStreamResp 函数中,针对检测到 recalled 的情况,添加一个更激进的 finish 信号模拟:
recalled
finish
// 在 DSState.prototype.preCheck 内部修改 if (v.p == "fragments" && v.v[0].type == TEMPLATE_RESPONSE) { this.recalled = true; modified = true; // 1. 保存原始片段 saveRecalledMessage(this.sessId, this.fields.response.message_id, this.fields.response.fragments); // 2. 构造一个能被前端理解的“结束且显示”的消息包 // 强制将状态改为 FINISHED,防止前端一直处于 loading 状态 data.v[i] = { "v": [ {"id": 999, "type": "TIP", "style": "WARNING", "content": getRecalledTipMessage(this.locale)} ], "p": "fragments", "o": "APPEND" }; // 补做一个 BATCH 操作,尝试强制关闭思考动画 data.v.push({"p": "status", "v": "FINISHED", "o": "SET"}); }
深度思考模式的渲染逻辑通常分为两个容器:thinking 块和 message 块。
thinking
message
Thinking
建议: 如果不希望代码过于复杂,目前的“刷新即现”其实是最稳妥的方案,因为它利用了 DeepSeek 官方的历史记录加载逻辑,兼容性最好。
这是因为 DeepSeek 的前端渲染逻辑是根据 status 字段来切换显示模式的。
status
在正常的“深度思考”流程中:
SEARCHING
THINKING
FINISHED
fragments
违禁词被拦截时,DeepSeek 的原始信号通常会强制改变消息状态,导致前端立刻“跳出”思考模式。由于脚本是在检测到拦截后强行追加提示并修改状态为 FINISHED 以确保内容显示,这就导致了前端认为“思考已结束”,从而关闭了思考框,将缓存的内容直接显示在了主回答区。
要让思考框保留,同时在框内显示内容,需要针对深度思考的 process 字段进行特殊处理。
process
你需要修改脚本中的 onHistoryMessageResp 函数和 DSState.prototype.preCheck。核心思路是:如果发现消息包含思考内容,不仅要恢复 fragments(主回答),还要尝试恢复或保留 process(思考过程)字段。
请尝试将 onHistoryMessageResp 函数替换为以下版本:
/** * History message response handler, 修复思考框消失的问题 */ function onHistoryMessageResp(req, res) { let json = JSON.parse(res); if (!json.data || !json.data.biz_data) { return res; } let data = json.data.biz_data; let sessId = data.chat_session.id; let modified = false; for (let i = 0; i < data.chat_messages.length; i++) { let msg = data.chat_messages[i]; // 如果消息被拦截 (CONTENT_FILTER) if (msg.status == CONTENT_FILTER) { // 1. 恢复主回答内容 msg.fragments = getRecalledMessage(req, sessId, msg.message_id); // 2. 尝试保持思考框状态 // 如果该消息之前有思考内容(假设本地缓存或原始数据中存在 process) // 确保 status 为 FINISHED 才能让内容渲染出来 msg.status = "FINISHED"; // 如果发现 process 字段为空,但你希望它显示(通常拦截会清空此字段) // 可以在这里手动补回一个占位符,或者如果之前缓存了思考过程,则在此处还原 if (!msg.process || msg.process.length === 0) { // 注意:这里只能恢复被拦截那一刻之前的思考文字 // 如果需要完美恢复思考框,需要在流式传输阶段也缓存 process 字段 } modified = true; } } if (modified) { json.data.biz_data = data; res = JSON.stringify(json); } return res; }
CONTENT_FILTER
msg.process
msg.fragments
你可以尝试在脚本的 preCheck 逻辑中,观察 v.p == "process" 的变化。如果想保留思考框,不要让脚本在检测到拦截时立即发送 status: FINISHED 的覆盖指令,但这可能会导致界面卡在“思考中”的小圆圈转不停。
preCheck
v.p == "process"
status: FINISHED
目前的折中办法: 现在的逻辑确保了“内容不丢失”。由于拦截动作是服务器端发起的强制中断,前端为了安全会销毁当前的思考组件实例,脚本能强行把文字捞回来显示在主回答区已经是目前最稳定的兼容做法了。
Sign in to post a reply.
进行以下修改,可以使得开启深度思考要刷新的问题得到解决
这个问题出现的原因在于脚本拦截和渲染的时效性差异。
在 DeepSeek 的深度思考 (Reasoning) 模式下,服务器通过 SSE (Server-Sent Events) 流式推送数据。当内容触发审查被撤回(Recall)时,脚本虽然拦截到了信号并修改了数据,但前端 UI 框架(通常是 React 或 Vue)由于其内部状态管理机制,可能无法实时识别到被脚本篡改后的数据流,导致页面卡住或显示异常。
刷新网页能恢复是因为
onHistoryMessageResp函数发挥了作用——它在页面重新加载、获取历史消息记录时,直接从本地缓存读取并强制替换了消息内容。核心改进建议
要实现“不刷新也能即时显示”,需要优化对流式输出的拦截逻辑。以下是针对该脚本的两个改进点:
1. 强制结束流式状态
目前的脚本在检测到撤回时,虽然修改了消息内容,但没有明确告诉前端“流已经结束”。如果前端还在等待
FINISH信号,它就不会渲染最后一段数据。在
DSState.prototype.preCheck中,当你检测到TEMPLATE_RESPONSE时,除了修改data.v,建议尝试模拟一个结束信号。2. 修改
handleEventItem的即时反馈目前脚本在
onEventStreamResp中处理字符串拼接。对于深度思考这种长文本,建议在检测到撤回的瞬间,立即触发一个自定义的 DOM 操作或者更彻底地拦截XMLHttpRequest.responseText的 getter。优化后的代码片段(建议替换或添加):
你可以尝试在
onEventStreamResp函数中,针对检测到recalled的情况,添加一个更激进的finish信号模拟:为什么深度思考模式下反应慢?
深度思考模式的渲染逻辑通常分为两个容器:
thinking块和message块。Thinking动画。如果脚本返回的模拟数据不符合前端最新的 Component 协议,界面就会看起来像卡住了,直到你刷新页面重新加载历史记录。你可以尝试的操作:
建议: 如果不希望代码过于复杂,目前的“刷新即现”其实是最稳妥的方案,因为它利用了 DeepSeek 官方的历史记录加载逻辑,兼容性最好。
这是因为 DeepSeek 的前端渲染逻辑是根据
status字段来切换显示模式的。在正常的“深度思考”流程中:
status为SEARCHING或THINKING,内容输出在思考框。status变为FINISHED,前端会隐藏思考框(或折叠),并开始在主回答区输出fragments。违禁词被拦截时,DeepSeek 的原始信号通常会强制改变消息状态,导致前端立刻“跳出”思考模式。由于脚本是在检测到拦截后强行追加提示并修改状态为
FINISHED以确保内容显示,这就导致了前端认为“思考已结束”,从而关闭了思考框,将缓存的内容直接显示在了主回答区。要让思考框保留,同时在框内显示内容,需要针对深度思考的
process字段进行特殊处理。改进方案
你需要修改脚本中的
onHistoryMessageResp函数和DSState.prototype.preCheck。核心思路是:如果发现消息包含思考内容,不仅要恢复fragments(主回答),还要尝试恢复或保留process(思考过程)字段。请尝试将
onHistoryMessageResp函数替换为以下版本:为什么很难完美回到“思考框内”输出?
process数组中,将“正式回答”存在fragments数组中。拦截通常发生在从process转向fragments的瞬间。CONTENT_FILTER,官方代码会清空这两个数组。脚本目前能做到的是把消失的文字找回来。msg.process而不是msg.fragments。但这样做会导致文字带有“思考中”的灰色样式,且无法使用 Markdown 渲染。建议操作
你可以尝试在脚本的
preCheck逻辑中,观察v.p == "process"的变化。如果想保留思考框,不要让脚本在检测到拦截时立即发送status: FINISHED的覆盖指令,但这可能会导致界面卡在“思考中”的小圆圈转不停。目前的折中办法: 现在的逻辑确保了“内容不丢失”。由于拦截动作是服务器端发起的强制中断,前端为了安全会销毁当前的思考组件实例,脚本能强行把文字捞回来显示在主回答区已经是目前最稳定的兼容做法了。