fix: 进入对话之后默认展示最新消息

This commit is contained in:
yangshilin
2026-03-11 17:08:42 +08:00
parent f3d26c9d0e
commit 0cf1d295a4
2 changed files with 21 additions and 14 deletions

View File

@@ -12,6 +12,8 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -34,8 +36,9 @@ import kotlinx.coroutines.launch
/** /**
* 消息列表组件 * 消息列表组件
* *
* @param messages 消息列表 * @param messages 消息列表
* @param conversationId 当前对话 ID用于切换对话时重置滚动状态使每次打开都从底部开始
* @param isStreaming 是否正在流式接收 * @param isStreaming 是否正在流式接收
* @param streamingText 流式文本内容 * @param streamingText 流式文本内容
* @param isTyping 是否正在输入 * @param isTyping 是否正在输入
@@ -46,6 +49,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun MessageList( fun MessageList(
messages: List<MessageDto>, messages: List<MessageDto>,
conversationId: String? = null,
isStreaming: Boolean = false, isStreaming: Boolean = false,
streamingText: String = "", streamingText: String = "",
isTyping: Boolean = false, isTyping: Boolean = false,
@@ -57,46 +61,48 @@ fun MessageList(
audioFilePaths: Map<String, String> = emptyMap(), audioFilePaths: Map<String, String> = emptyMap(),
audioDurations: Map<String, Int> = emptyMap() // messageId -> 时长(秒) audioDurations: Map<String, Int> = emptyMap() // messageId -> 时长(秒)
) { ) {
val listState = rememberLazyListState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var lastHeightPx by remember { mutableIntStateOf(0) } var lastHeightPx by remember { mutableIntStateOf(0) }
// 计算实际的列表项数量(考虑分割消息和附加项) // 计算实际的列表项数量(考虑分割消息和附加项)- 需在 listState 之前计算,用于初始滚动位置
val estimatedItemCount = remember(messages, isStreaming, streamingText, isTyping) { val estimatedItemCount = remember(messages, isStreaming, streamingText, isTyping) {
var count = 0 var count = 0
var lastTimestamp: Long? = null var lastTimestamp: Long? = null
messages.forEachIndexed { index, message -> messages.forEachIndexed { index, message ->
// 时间分隔线
if (index > 0 && lastTimestamp != null && (message.timestamp - lastTimestamp!!) > 300000) { if (index > 0 && lastTimestamp != null && (message.timestamp - lastTimestamp!!) > 300000) {
count++ count++
} }
// 消息气泡(考虑分割)
if (message.senderType == "assistant") { if (message.senderType == "assistant") {
val parts = message.content.split("[SPLIT]").filter { it.trim().isNotEmpty() } val parts = message.content.split("[SPLIT]").filter { it.trim().isNotEmpty() }
count += if (parts.size > 1) parts.size else 1 count += if (parts.size > 1) parts.size else 1
} else { } else {
count++ count++
} }
lastTimestamp = message.timestamp lastTimestamp = message.timestamp
} }
// 流式消息
if (isStreaming) { if (isStreaming) {
val streamingParts = streamingText.split("[SPLIT]").filter { it.trim().isNotEmpty() } val streamingParts = streamingText.split("[SPLIT]").filter { it.trim().isNotEmpty() }
count += if (streamingParts.size > 1) streamingParts.size else 1 count += if (streamingParts.size > 1) streamingParts.size else 1
} }
// 输入指示器
if (isTyping || (isStreaming && streamingText.isEmpty())) { if (isTyping || (isStreaming && streamingText.isEmpty())) {
count++ count++
} }
count count
} }
// 当前对话是否已加载过消息(用于首次显示时直接定位到底部,避免从顶部再滑到底部)
var hasReceivedMessages by remember(conversationId) { mutableStateOf(false) }
LaunchedEffect(conversationId, messages) {
if (messages.isNotEmpty()) hasReceivedMessages = true
}
// 用 key 在「首次有消息」时重建列表状态,使 initialFirstVisibleItemIndex 生效,打开对话即显示底部
val initialIndex = if (hasReceivedMessages && estimatedItemCount > 0) (estimatedItemCount - 1).coerceAtLeast(0) else 0
val listState = key(conversationId, hasReceivedMessages) {
rememberLazyListState(initialFirstVisibleItemIndex = initialIndex, initialFirstVisibleItemScrollOffset = 0)
}
// 自动滚动到底部 - 当消息变化或流式内容更新时滚动 // 自动滚动到底部 - 当消息变化或流式内容更新时滚动
LaunchedEffect(messages.size, messages.lastOrNull()?.id, isStreaming, streamingText, isTyping) { LaunchedEffect(messages.size, messages.lastOrNull()?.id, isStreaming, streamingText, isTyping) {
// 短暂延迟确保内容已渲染 // 短暂延迟确保内容已渲染

View File

@@ -189,6 +189,7 @@ fun CreateMemoryScreen(
// 使用新的MessageList组件包含所有消息、流式内容和输入指示器 // 使用新的MessageList组件包含所有消息、流式内容和输入指示器
MessageList( MessageList(
messages = messages, messages = messages,
conversationId = conversationId,
isStreaming = isStreaming, isStreaming = isStreaming,
streamingText = streamingText, streamingText = streamingText,
isTyping = isTyping, isTyping = isTyping,