feat: 优化消息列表和创建记忆屏幕逻辑

- 在MessageList组件中改进自动滚动逻辑,支持流式消息和时间分隔线的处理
- 在CreateMemoryScreen中移除冗余的agentResponse处理,直接使用historyMessages构建消息列表
- 在CreateMemoryViewModel中优化Agent回复处理,确保每条消息作为单独气泡显示并更新历史消息
This commit is contained in:
penghanyuan
2026-01-29 20:18:06 +01:00
parent 41ceb3dad8
commit c3c8eb2e6e
3 changed files with 72 additions and 38 deletions

View File

@@ -11,6 +11,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -36,17 +37,57 @@ fun MessageList(
) {
val listState = rememberLazyListState()
// 自动滚动到底部 - 改进:流式输出时实时滚动
LaunchedEffect(messages.size, isStreaming, streamingText) {
val targetIndex = messages.size + if (isStreaming) 1 else 0
if (targetIndex > 0) {
// 流式输出时使用平滑滚动,其他情况使用动画滚动
if (isStreaming && streamingText.isNotEmpty()) {
// 流式输出时,每次文本更新都滚动到底部
kotlinx.coroutines.delay(50) // 短暂延迟确保内容已渲染
listState.animateScrollToItem(targetIndex)
// 计算实际的列表项数量(考虑分割消息和附加项)
val estimatedItemCount = remember(messages, isStreaming, streamingText, isTyping) {
var count = 0
var lastTimestamp: Long? = null
messages.forEachIndexed { index, message ->
// 时间分隔线
if (index > 0 && lastTimestamp != null && (message.timestamp - lastTimestamp!!) > 300000) {
count++
}
// 消息气泡(考虑分割)
if (message.senderType == "assistant") {
val parts = message.content.split("[SPLIT]").filter { it.trim().isNotEmpty() }
count += if (parts.size > 1) parts.size else 1
} else {
listState.animateScrollToItem(targetIndex)
count++
}
lastTimestamp = message.timestamp
}
// 流式消息
if (isStreaming) {
val streamingParts = streamingText.split("[SPLIT]").filter { it.trim().isNotEmpty() }
count += if (streamingParts.size > 1) streamingParts.size else 1
}
// 输入指示器
if (isTyping || (isStreaming && streamingText.isEmpty())) {
count++
}
count
}
// 自动滚动到底部 - 当消息变化或流式内容更新时滚动
LaunchedEffect(messages.size, messages.lastOrNull()?.id, isStreaming, streamingText, isTyping) {
// 短暂延迟确保内容已渲染
delay(100)
// 滚动到最后一项
if (estimatedItemCount > 0) {
try {
listState.animateScrollToItem(estimatedItemCount - 1)
} catch (e: Exception) {
// 如果索引超出范围,尝试滚动到实际的最后一项
val actualCount = listState.layoutInfo.totalItemsCount
if (actualCount > 0) {
listState.animateScrollToItem(actualCount - 1)
}
}
}
}

View File

@@ -38,7 +38,6 @@ fun CreateMemoryScreen(
) {
val isRecording by viewModel.isRecording.collectAsState()
val transcript by viewModel.transcript.collectAsState()
val agentResponse by viewModel.agentResponse.collectAsState()
val connectionStatus by viewModel.connectionStatus.collectAsState()
val userMessages by viewModel.userMessages.collectAsState()
val historyMessages by viewModel.historyMessages.collectAsState()
@@ -69,7 +68,8 @@ fun CreateMemoryScreen(
val keyboardController = LocalSoftwareKeyboardController.current
// 构建消息列表(包含历史消息和当前消息)
val messages = remember(historyMessages, userMessages, agentResponse) {
// 注意AI回复已经直接添加到 historyMessages 中,不需要额外处理 agentResponse
val messages = remember(historyMessages, userMessages) {
buildList {
// 先添加历史消息
addAll(historyMessages)
@@ -88,18 +88,6 @@ fun CreateMemoryScreen(
))
}
}
// 添加AI回复如果不在历史消息中
if (agentResponse.isNotEmpty() && !historyMessages.any { it.content == agentResponse && it.senderType == "assistant" }) {
add(MessageDto(
id = "ai_response_${historyMessages.size + userMessages.size}",
conversationId = conversationId,
content = agentResponse,
senderType = "assistant",
timestamp = System.currentTimeMillis(),
messageType = "text"
))
}
}.sortedBy { it.timestamp } // 按时间排序
}

View File

@@ -294,7 +294,7 @@ class CreateMemoryViewModel(
transcript.value = message.getString("text") ?: ""
}
MessageType.agent_response -> {
// 处理Agent回复可能有多条消息
// 处理Agent回复可能有多条消息,每条作为单独气泡显示
val text = message.getString("text") ?: ""
val index = message.getInt("index") ?: 0
val total = message.getInt("total") ?: 1
@@ -302,25 +302,30 @@ class CreateMemoryViewModel(
// 收到第一条回复时,隐藏打字指示器
if (index == 0) {
isTyping.value = false
}
// 每条消息立即作为单独的气泡添加到历史消息
conversationId.value?.let { id ->
val aiMessage = MessageDto(
id = "ai_${System.currentTimeMillis()}_$index",
conversationId = id,
content = text,
senderType = "assistant",
timestamp = System.currentTimeMillis(),
messageType = "text"
)
historyMessages.value = historyMessages.value + aiMessage
}
// 更新 agentResponse用于显示最新回复
if (index == 0) {
agentResponse.value = text
} else {
// 追加后续消息
agentResponse.value += "\n\n$text"
}
// 如果是最后一条消息,结束流式状态并添加到历史消息
// 如果是最后一条消息,结束流式状态
if (index >= total - 1) {
conversationId.value?.let { id ->
val aiMessage = MessageDto(
id = "ai_${System.currentTimeMillis()}",
conversationId = id,
content = agentResponse.value,
senderType = "assistant",
timestamp = System.currentTimeMillis(),
messageType = "text"
)
historyMessages.value = historyMessages.value + aiMessage
}
isStreaming.value = false
streamingText.value = ""
isTyping.value = false