Matthias Nott
2026-03-07 c23dfe16e95713e7058137308bdbc28419609a39
app/chat.tsx
....@@ -11,7 +11,7 @@
1111 import { ImageCaptionModal } from "../components/chat/ImageCaptionModal";
1212 import { StatusDot } from "../components/ui/StatusDot";
1313 import { SessionDrawer } from "../components/SessionDrawer";
14
-import { playSingle, stopPlayback, isPlaying, onPlayingChange } from "../services/audio";
14
+import { playAudio, stopPlayback, isPlaying, onPlayingChange } from "../services/audio";
1515
1616 interface StagedImage {
1717 base64: string;
....@@ -20,7 +20,7 @@
2020 }
2121
2222 export default function ChatScreen() {
23
- const { messages, sendTextMessage, sendVoiceMessage, sendImageMessage, clearMessages, requestScreenshot, sessions } =
23
+ const { messages, sendTextMessage, sendVoiceMessage, sendImageMessage, deleteMessage, clearMessages, isTyping, requestScreenshot, sessions } =
2424 useChat();
2525 const { status } = useConnection();
2626 const { colors, mode, cycleMode } = useTheme();
....@@ -130,17 +130,37 @@
130130 [stagedImage, sendImageMessage],
131131 );
132132
133
- const handleReplay = useCallback(() => {
133
+ const handleReplay = useCallback(async () => {
134134 if (isPlaying()) {
135135 stopPlayback();
136136 return;
137137 }
138
+ // Find the last assistant voice message, then walk back to the first chunk in that group
139
+ let lastIdx = -1;
138140 for (let i = messages.length - 1; i >= 0; i--) {
139
- const msg = messages[i];
140
- if (msg.role === "assistant" && msg.audioUri) {
141
- playSingle(msg.audioUri).catch(() => {});
142
- return;
141
+ if (messages[i].role === "assistant" && messages[i].type === "voice" && messages[i].audioUri) {
142
+ lastIdx = i;
143
+ break;
143144 }
145
+ }
146
+ if (lastIdx === -1) return;
147
+
148
+ // Walk back to find the start of this chunk group
149
+ let startIdx = lastIdx;
150
+ while (startIdx > 0) {
151
+ const prev = messages[startIdx - 1];
152
+ if (prev.role === "assistant" && prev.type === "voice" && prev.audioUri) {
153
+ startIdx--;
154
+ } else {
155
+ break;
156
+ }
157
+ }
158
+
159
+ // Queue all chunks from start to last
160
+ await stopPlayback();
161
+ for (let i = startIdx; i <= lastIdx; i++) {
162
+ const m = messages[i];
163
+ if (m.audioUri) playAudio(m.audioUri);
144164 }
145165 }, [messages]);
146166
....@@ -267,7 +287,7 @@
267287 </View>
268288 </View>
269289 ) : (
270
- <MessageList messages={messages} />
290
+ <MessageList messages={messages} isTyping={isTyping} onDeleteMessage={deleteMessage} />
271291 )}
272292 </View>
273293