fix: 进入对话之后默认展示最新消息
This commit is contained in:
@@ -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
|
||||||
@@ -36,6 +38,7 @@ 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) {
|
||||||
// 短暂延迟确保内容已渲染
|
// 短暂延迟确保内容已渲染
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user