From 8f72afd714912bf6717f5b5fb5dedf3fe5bfd9ee Mon Sep 17 00:00:00 2001 From: Bruno Deanoz Date: Tue, 23 Jun 2026 01:30:52 +0200 Subject: [PATCH] feat: edit and resend user messages Pencil icon on user messages (next to copy, before delete). Tap removes the edited message and all messages after it from the UI + server, then puts the text back into the input field for editing and resending. Only shown on user messages with text content (not voice-only), and only when not currently streaming. --- .../java/dev/kaizen/app/chat/ChatComponents.kt | 8 ++++++++ .../main/java/dev/kaizen/app/chat/ChatScreen.kt | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt b/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt index 183bf88..518d8f0 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatComponents.kt @@ -447,6 +447,7 @@ fun MessageRow( onCopy: (String) -> Unit = {}, onDelete: ((String) -> Unit)? = null, onRegenerate: ((String) -> Unit)? = null, + onEdit: ((String, String) -> Unit)? = null, onReadAloud: ((String) -> Unit)? = null, isPlayingTts: Boolean = false, ) { @@ -544,6 +545,13 @@ fun MessageRow( copied = false } } + if (message.content.isNotBlank()) { + onEdit?.let { edit -> + MessageActionButton(KaizenIcons.Pencil, cs.onSurfaceVariant.copy(alpha = 0.50f)) { + haptics.tick(); edit(message.wireId, message.content) + } + } + } onDelete?.let { del -> MessageActionButton(KaizenIcons.Trash, Color(0xFFEF4444).copy(alpha = 0.70f)) { haptics.tick(); del(message.wireId) diff --git a/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt b/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt index 7d9db95..dce6002 100644 --- a/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt +++ b/app/src/main/java/dev/kaizen/app/chat/ChatScreen.kt @@ -883,6 +883,22 @@ fun ChatScreen( send(userContent) } } else null, + onEdit = if (message.role == Role.User && !isStreaming) { wireId, content -> + val idx = messages.indexOfFirst { it.wireId == wireId } + if (idx >= 0) { + val cfg = session.config ?: return@MessageRow + val toRemove = messages.subList(idx, messages.size).map { it.wireId } + messages.removeAll { it.wireId in toRemove } + if (conversationId != null) { + scope.launch { + toRemove.forEach { id -> + KaizenApi.deleteMessage(cfg.baseUrl, cfg.token, conversationId!!, id) + } + } + } + input = content + } + } else null, onReadAloud = if (message.role == Role.Assistant && !message.streaming && message.content.isNotBlank()) { content -> readAloud(content, message.id, message.wireId) } else null,