






















@@ -102,6 +102,7 @@ class TalkModeManager internal constructor(
102102private const val chatFinalWaitWithSubscribeMs = 45_000L
103103private const val chatFinalWaitWithoutSubscribeMs = 6_000L
104104private const val maxCachedRunCompletions = 128
105+private const val maxConversationEntries = 40
105106 }
106107107108private val mainHandler = Handler(Looper.getMainLooper())
@@ -121,6 +122,9 @@ class TalkModeManager internal constructor(
121122private val _lastAssistantText = MutableStateFlow<String?>(null)
122123val lastAssistantText: StateFlow<String?> = _lastAssistantText
123124125+private val _conversation = MutableStateFlow<List<VoiceConversationEntry>>(emptyList())
126+val conversation: StateFlow<List<VoiceConversationEntry>> = _conversation
127+124128private var recognizer: SpeechRecognizer? = null
125129private var restartJob: Job? = null
126130private var stopRequested = false
@@ -158,6 +162,8 @@ class TalkModeManager internal constructor(
158162private val realtimeToolRuns = LinkedHashMap<String, RealtimeToolRun>()
159163private val pendingRealtimeToolCalls = LinkedHashSet<String>()
160164private val pendingRealtimeToolCompletions = LinkedHashMap<String, RealtimeToolCompletion>()
165+private var realtimeUserEntryId: String? = null
166+private var realtimeAssistantEntryId: String? = null
161167private val realtimePlaybackLock = Any()
162168private var realtimeAudioTrack: AudioTrack? = null
163169private var realtimePlaybackIdleJob: Job? = null
@@ -791,6 +797,12 @@ class TalkModeManager internal constructor(
791797val role = obj["role"].asStringOrNull()
792798val text = obj["text"].asStringOrNull()?.trim().orEmpty()
793799val isFinal = obj["final"].asBooleanOrNull() == true
800+if (text.isNotEmpty()) {
801+when (role) {
802+"user" -> upsertRealtimeConversation(VoiceConversationRole.User, text, isFinal)
803+"assistant" -> upsertRealtimeConversation(VoiceConversationRole.Assistant, text, isFinal)
804+ }
805+ }
794806if (role == "assistant" && text.isNotEmpty()) {
795807_lastAssistantText.value = text
796808 }
@@ -936,6 +948,8 @@ class TalkModeManager internal constructor(
936948 realtimeToolRuns.clear()
937949 pendingRealtimeToolCalls.clear()
938950 pendingRealtimeToolCompletions.clear()
951+ realtimeUserEntryId = null
952+ realtimeAssistantEntryId = null
939953 stopRealtimePlayback()
940954if (preserveStatus) {
941955_statusText.value = status
@@ -1084,6 +1098,61 @@ class TalkModeManager internal constructor(
10841098 }
10851099 }
108611001101+private fun upsertRealtimeConversation(
1102+role: VoiceConversationRole,
1103+text: String,
1104+isFinal: Boolean,
1105+ ) {
1106+val entryId =
1107+when (role) {
1108+VoiceConversationRole.User -> realtimeUserEntryId
1109+VoiceConversationRole.Assistant -> realtimeAssistantEntryId
1110+ }
1111+val resolvedEntryId =
1112+if (entryId == null) {
1113+ appendConversation(role = role, text = text, isStreaming = !isFinal)
1114+ } else {
1115+ updateConversationEntry(id = entryId, text = text, isStreaming = !isFinal)
1116+ entryId
1117+ }
1118+when (role) {
1119+VoiceConversationRole.User -> realtimeUserEntryId = if (isFinal) null else resolvedEntryId
1120+VoiceConversationRole.Assistant -> realtimeAssistantEntryId = if (isFinal) null else resolvedEntryId
1121+ }
1122+ }
1123+1124+private fun appendConversation(
1125+role: VoiceConversationRole,
1126+text: String,
1127+isStreaming: Boolean,
1128+ ): String {
1129+val id = UUID.randomUUID().toString()
1130+_conversation.value =
1131+ (_conversation.value + VoiceConversationEntry(id = id, role = role, text = text, isStreaming = isStreaming))
1132+ .takeLast(maxConversationEntries)
1133+return id
1134+ }
1135+1136+private fun updateConversationEntry(
1137+id: String,
1138+text: String,
1139+isStreaming: Boolean,
1140+ ) {
1141+val current = _conversation.value
1142+val targetIndex =
1143+when {
1144+ current.isEmpty() -> -1
1145+ current[current.lastIndex].id == id -> current.lastIndex
1146+else -> current.indexOfFirst { it.id == id }
1147+ }
1148+if (targetIndex < 0) return
1149+val entry = current[targetIndex]
1150+if (entry.text == text && entry.isStreaming == isStreaming) return
1151+val updated = current.toMutableList()
1152+ updated[targetIndex] = entry.copy(text = text, isStreaming = isStreaming)
1153+_conversation.value = updated
1154+ }
1155+10871156private fun startListeningInternal(markListening: Boolean) {
10881157val r = recognizer ?: return
10891158val intent =
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。