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