refactor(chat): tag local feedback with displayKind and refine logical message matching (#1241)

This commit is contained in:
M57 committed 2026-09-24 02:38:31 +03:00
1 parent c9bbadf5d5
commit 9ae7b6a520
5 files changed
+85 -25

No files matched your search

@@ -169,9 +169,21 @@ internal class TranscriptComparison(
val key = toolKey(a.content)
return key != null && key == toolKey(b.content)
}
// Rows generated only by Mobile never have a canonical REST counterpart.
// Do not let coincidentally identical server text claim their identity.
if (a.role == MessageRole.ASSISTANT &&
(a.displayKind == "local_feedback" || b.displayKind == "local_feedback")
) {
return false
}
if (a.role == MessageRole.USER &&
(a.displayKind == "clarify_response" || b.displayKind == "clarify_response")
) {
return false
}
val ta = trimmed.getOrPut(a.content) { a.content.trim() }
val tb = trimmed.getOrPut(b.content) { b.content.trim() }
if (ta.startsWith("/") || tb.startsWith("/")) return false
if (a.role == MessageRole.USER && (ta.startsWith("/") || tb.startsWith("/"))) return false
if (ta == tb) return true
if (a.role == MessageRole.USER &&
captions.getOrPut(ta) { stripAttachmentRefLines(ta) } ==
@@ -458,27 +470,7 @@ private fun List<ChatMessage>.inTranscriptOrder(
internal fun ChatMessage.isPermanentlyLocal(): Boolean =
role == MessageRole.SYSTEM ||
(role == MessageRole.USER && (content.startsWith("/") || displayKind == "clarify_response")) ||
(role == MessageRole.ASSISTANT && canonicalRestId == null && isLocalFeedbackRow(this))
private fun isLocalFeedbackRow(message: ChatMessage): Boolean =
message.localOrder != null || isLocalFeedbackContent(message.content)
private fun isLocalFeedbackContent(content: String): Boolean =
content.endsWith(" is not supported on mobile") ||
content.startsWith("usage: /queue") ||
content.startsWith("reasoning: ") ||
content.startsWith("Could not read reasoning status:") ||
content == "Reasoning is not supported for the current model." ||
content == "Reasoning cannot be disabled for this model (always on)." ||
content.startsWith("Could not change reasoning:") ||
content == "Reasoning controls require an active session." ||
content == "No active session. Use `/new` to create one." ||
content == "No active session to undo." ||
content == "No active session for side questions. Start a chat first." ||
content.startsWith("Bot chats are one continuous conversation — compacting instead.") ||
content.startsWith("⚠ ") ||
(content.startsWith("/") && content.contains(": ")) ||
content == "Failed to undo."
(role == MessageRole.ASSISTANT && displayKind == "local_feedback")
internal fun ChatMessage.isSessionStartMarker(): Boolean =
role == MessageRole.SYSTEM && (content == "Session created" || content == "Session branched")
@@ -2655,7 +2655,12 @@ class ChatViewModel(
}
private fun addAssistantMessage(text: String) {
val msg = ChatMessage(role = MessageRole.ASSISTANT, content = text)
val msg =
ChatMessage(
role = MessageRole.ASSISTANT,
content = text,
displayKind = "local_feedback",
)
_uiState.update { it.copy(messages = it.messages + msg) }
// Persist — OUTSIDE update{}
@@ -58,7 +58,10 @@ private fun ChatMessage.isSyntheticSystemRow(): Boolean =
content.startsWith(MAX_ITERATIONS_SYSTEM_MARKER)
internal fun ChatMessage.isTimelineMarker(): Boolean =
displayKind != null && displayKind != "steer" && displayKind != "clarify_response"
displayKind != null &&
displayKind != "steer" &&
displayKind != "clarify_response" &&
displayKind != "local_feedback"
/**
* Split a flat message list into turns for the full-bleed renderer.