fix(chat): 并行路角标改以 turn_begin.kind 为准,不再拿 leg 冒充并行
leg 是服务端**会话内的路序号**:beginLegLocked 对每一条新路都自增 legSeq, 普通串行回合照样占号——会话里发第二条普通消息,leg 就是 2。而角标旧判据是 leg >= 2,于是那条串行消息被挂上「并行路 2」,凭空声称存在并不存在的并行。 「是不是并行路」真正的判据是 turn_begin.kind(turn = 普通串行回合, parallel = 忙时另起的一路);解析层本来就把它读出来了,只是绑定时丢掉了。 - TurnBinding 新增 parallel;withTurnBegin 收下 kind,由 isParallelLegKind 判定:只有逐字等于 parallel 才算,turn、缺席、空串、大小写不同一律不算。 - 合并规则:新建按 kind 定;同 turnId 重复到达时取或(old.parallel || 本帧 是并行),一旦为 true 就永远是 true。与 closed 同一条纪律——单调的既成事实 不许被后到的帧翻回去;也顾及 steer_ack 可能先建一条没有 kind 的占位表、 随后才由这一路自己的 turn_begin 补上并行的现实顺序。 - 降级:kind 不来的老服务端上 parallel 恒为 false,角标一枚都不挂——宁可少挂 一枚,也不拿 leg 去猜、去多说一句假话。 - legBadgeOf 改成同时看 parallel 与 leg,三处调用点(提问侧、回复侧、流式气泡) 都跟着传 parallel;文案仍是「并行路 N」。 测试:ParallelTurnsTest 补 kind=turn / kind=parallel / kind 缺席三档、重复帧的 合并(含「已收口的并行路不被重新打开也不被降级」);TurnStreamsTest 把 「leg>=2 就挂标」的断言改成以 kind 为准,并补上那条真机回归——会话里第二条 普通消息(leg=2、kind=turn)在提问侧与回复侧都不挂角标。
This commit is contained in:
1 parent
6e00697110
commit
39a63f8d30
7 files changed
+233
-32
No files matched your search
@@ -205,10 +205,31 @@ class TurnStreamsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the second leg carries its badge on the question and on the stream`() {
|
||||
fun `a second serial turn carries no badge even though its leg is two`() {
|
||||
// 真机那个 bug:会话里第二条**普通**消息串行走,服务端照样给它 leg=2(leg 是会话内第几条路),
|
||||
// 而 kind 是 "turn"。角标必须以 kind 为准 ⇒ 一枚都不挂。
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 1, kind = "turn")
|
||||
val state = ChatUiState(messages = listOf(message("m-1")), turns = turns)
|
||||
|
||||
assertNull(state.legBadgeForMessage(state.messages.single()))
|
||||
assertNull(state.legBadgeForTurn("t2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a second turn without any kind carries no badge`() {
|
||||
// 老服务端:整键不给。认不出来 ⇒ 不挂角标(降级方向是「少挂」,不是「照 leg 猜」)。
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 2)
|
||||
val state = ChatUiState(messages = listOf(message("m-1")), turns = turns)
|
||||
|
||||
assertNull(state.legBadgeForMessage(state.messages.single()))
|
||||
assertNull(state.legBadgeForTurn("t2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the second leg carries its badge on the question and on the stream`() {
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 2, kind = "parallel")
|
||||
val state = ChatUiState(messages = listOf(message("m-1")), turns = turns)
|
||||
|
||||
assertEquals("并行路 2", state.legBadgeForMessage(state.messages.single()))
|
||||
assertEquals("并行路 2", state.legBadgeForTurn("t2"))
|
||||
}
|
||||
@@ -216,17 +237,27 @@ class TurnStreamsTest {
|
||||
@Test
|
||||
fun `the answer of a leg carries the same badge through its own turn id`() {
|
||||
// 回复侧:历史行的可选 turnId(规范 §2.7)指出它是哪一路产出的 ⇒ 落库之后小标仍在。
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 2)
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 2, kind = "parallel")
|
||||
val answer = message("m-9", role = "assistant", turnId = "t2")
|
||||
val state = ChatUiState(messages = listOf(message("m-1"), answer), turns = turns)
|
||||
|
||||
assertEquals("并行路 2", state.legBadgeForMessage(answer))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the answer of a serial leg carries no badge through its own turn id`() {
|
||||
// 同一条路在回复侧:leg=3 的串行回合也不许挂标(判据是 kind,不是 leg)。
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t3", 3, listOf("m-1"), 1, kind = "turn")
|
||||
val answer = message("m-9", role = "assistant", turnId = "t3")
|
||||
val state = ChatUiState(messages = listOf(message("m-1"), answer), turns = turns)
|
||||
|
||||
assertNull(state.legBadgeForMessage(answer))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an old answer row without a turn id carries no badge`() {
|
||||
// v0.65 之前历史行没有 turnId ⇒ 挂不出标,也就一个像素都不多画。
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 2)
|
||||
val turns = emptyList<TurnBinding>().withTurnBegin("t2", 2, listOf("m-1"), 2, kind = "parallel")
|
||||
val answer = message("m-9", role = "assistant")
|
||||
val state = ChatUiState(messages = listOf(message("m-1"), answer), turns = turns)
|
||||
|
||||
|
||||
Reference in new issue
Block a user