From c23dfe16e95713e7058137308bdbc28419609a39 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sat, 07 Mar 2026 11:54:15 +0100
Subject: [PATCH] feat: typing indicator, message deletion, chain playback, autoplay guard

---
 app/chat.tsx |   36 ++++++++++++++++++++++++++++--------
 1 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/app/chat.tsx b/app/chat.tsx
index 8d4e95e..665d0f6 100644
--- a/app/chat.tsx
+++ b/app/chat.tsx
@@ -11,7 +11,7 @@
 import { ImageCaptionModal } from "../components/chat/ImageCaptionModal";
 import { StatusDot } from "../components/ui/StatusDot";
 import { SessionDrawer } from "../components/SessionDrawer";
-import { playSingle, stopPlayback, isPlaying, onPlayingChange } from "../services/audio";
+import { playAudio, stopPlayback, isPlaying, onPlayingChange } from "../services/audio";
 
 interface StagedImage {
   base64: string;
@@ -20,7 +20,7 @@
 }
 
 export default function ChatScreen() {
-  const { messages, sendTextMessage, sendVoiceMessage, sendImageMessage, clearMessages, requestScreenshot, sessions } =
+  const { messages, sendTextMessage, sendVoiceMessage, sendImageMessage, deleteMessage, clearMessages, isTyping, requestScreenshot, sessions } =
     useChat();
   const { status } = useConnection();
   const { colors, mode, cycleMode } = useTheme();
@@ -130,17 +130,37 @@
     [stagedImage, sendImageMessage],
   );
 
-  const handleReplay = useCallback(() => {
+  const handleReplay = useCallback(async () => {
     if (isPlaying()) {
       stopPlayback();
       return;
     }
+    // Find the last assistant voice message, then walk back to the first chunk in that group
+    let lastIdx = -1;
     for (let i = messages.length - 1; i >= 0; i--) {
-      const msg = messages[i];
-      if (msg.role === "assistant" && msg.audioUri) {
-        playSingle(msg.audioUri).catch(() => {});
-        return;
+      if (messages[i].role === "assistant" && messages[i].type === "voice" && messages[i].audioUri) {
+        lastIdx = i;
+        break;
       }
+    }
+    if (lastIdx === -1) return;
+
+    // Walk back to find the start of this chunk group
+    let startIdx = lastIdx;
+    while (startIdx > 0) {
+      const prev = messages[startIdx - 1];
+      if (prev.role === "assistant" && prev.type === "voice" && prev.audioUri) {
+        startIdx--;
+      } else {
+        break;
+      }
+    }
+
+    // Queue all chunks from start to last
+    await stopPlayback();
+    for (let i = startIdx; i <= lastIdx; i++) {
+      const m = messages[i];
+      if (m.audioUri) playAudio(m.audioUri);
     }
   }, [messages]);
 
@@ -267,7 +287,7 @@
             </View>
           </View>
         ) : (
-          <MessageList messages={messages} />
+          <MessageList messages={messages} isTyping={isTyping} onDeleteMessage={deleteMessage} />
         )}
       </View>
 

--
Gitblit v1.3.1