在 SolonCode Web 聊天界面底部,工具栏有三个按钮(从左到右):
- Cop --- 复制上一条回复内容
- Re-run(重新运行) --- 删除旧回答,重新跑一轮
- Continue(继续运行) --- 保留旧回答,继续追加内容
这两个按钮看起来很像,但行为完全不同。今天深入拆解它们的实现。
重新运行(Re-run)
行为: 删除旧的气泡,用同样的输入重新跑一遍。
使用场景:
- AI 答错了,想让它重新回答
- 想换个角度重新问
实现原理:
源码在 RerunCommand.java:
java
// 逆向遍历消息列表,找到最后一个 UserMessage
List<ChatMessage> messageList = session.getMessages();
String lastUserInput = null;
while (!messageList.isEmpty()) {
ChatMessage msg = messageList.get(messageList.size() - 1);
if (msg instanceof UserMessage) {
lastUserInput = msg.getContent();
session.removeLatestMessage(1);
break;
}
session.removeLatestMessage(1);
}
// 重新运行,输入相同
if (lastUserInput != null) {
ctx.runAgentTask(lastUserInput, null);
}
核心逻辑: 逆向遍历找到最后一个用户消息,删除它之后的所有 AI 响应,然后用同样的输入重新跑一遍。
结果: 新回答完全替换旧回答。
继续运行(Continue)
行为: 保留旧的气泡,在旧回答后面继续追加新内容。
使用场景:
- AI 答到一半断了(比如 token 限制)
- 想让 AI 继续写下去
- 旧回答不完整,想让它补充
实现原理:
源码在 ContinueCommand.java:
java
ReActTrace trace = session.getContext().getAs("__main");
if (trace != null) {
if (Agent.ID_END.equals(trace.getRoute())) {
// 说明有结束节点,重新回到思考点
trace.setRoute(ReActAgent.ID_REASON);
trace.setFinalAnswer(null, false);
ChatMessage workMessage = trace.getWorkingMemory().getLastMessage();
if (workMessage instanceof AssistantMessage) {
trace.getWorkingMemory().removeLastMessage();
}
// 回退一条 ai 历史消息(要重新生成)
List<ChatMessage> messageList = session.getMessages();
if (Assert.isNotEmpty(messageList) && messageList.get(messageList.size() - 1) instanceof AssistantMessage) {
session.removeLatestMessage(1);
}
}
}
ctx.runAgentTask(null, null);
核心逻辑:
- 检查 trace 状态必须是
ID_END(已完成) - 将 route 重置为
ID_REASON(回到思考阶段) - 清空
finalAnswer(让 AI 重新生成) - 回退最后一条 AI 消息
- 用空的输入继续运行
结果: 旧气泡保留,新内容追加在后面。
两个按钮的对照表
| 维度 | Re-run(重新运行) | Continue(继续运行) |
|---|---|---|
| 是否删除旧响应 | ✅ 删除 | ❌ 保留 |
| 是否清空历史 | ✅ 剥除后续消息 | ❌ 保留完整历史 |
| 是否重置 trace | ✅ 重置 | ❌ 仅重置 route |
| 输入 | 同一个用户输入 | 空输入 |
| 适用场景 | 答错了想重答 | 答到一半断了 |
| 是否可链式操作 | 可(再 Re-run) | 可(再 Continue) |
三个具体场景
场景 A:AI 答错了
问题: "1 + 1 = ?" AI 回答"3"
操作: 点击 Re-run
结果:
- 旧气泡"3"被删除
- 新气泡"2"出现
- 历史回到问题本身
场景 B:AI 答到一半断了
问题: 长文本生成到一半断了
操作: 点击 Continue
结果:
- 旧气泡保留(已生成的部分)
- 新气泡追加在后面
- 内容无缝衔接
场景 C:先 Re-run 再 Continue
问题: 想换个思路但保留部分内容
操作:
- 点击 Re-run → 得到新回答 A
- 点击 Continue → 在 A 后面追加 B
结果: 最终有两个气泡,A 和 B 都是新内容
runId 联动机制
两个按钮都依赖一个关键字段:runId。
作用: 同一轮对话的所有气泡共享同一个 runId。
联动效果:
- Re-run 时,删除该
runId下的所有气泡 - Continue 时,新气泡也使用同一个
runId
实现:
java
// Web UI 中,每个气泡都有 data-run-id 属性
<div class="message" data-run-id="abc123">
<!-- 内容 -->
</div>
// Re-run 时,JS 会删除所有 data-run-id="abc123" 的元素
$('[data-run-id="' + runId + '"]').remove();
好处:
- 用户看到的是一整轮对话
- 不需要手动删除单个气泡
- 保持了对话的原子性
三端可用
这两个命令不仅存在于 Web UI,还有三个入口:
1. CLI 命令行
bash
soloncode > /rerun # 重新运行
soloncode > /continue # 继续运行
2. Web UI
点击工具栏的 Re-run / Continue 按钮。
3. IM 机器人
在飞书/钉钉/微信中输入 /rerun 或 /continue。
总结
- Re-run = 删除旧回答 + 同输入重跑
- Continue = 保留旧回答 + 空输入续跑
两个命令底层实现完全不同,但都通过 runId 联动保持对话完整性。
掌握这两个"后悔药",能让 AI 对话更流畅,出错也能快速修正。