feat(bots): smart streaming auto-scroll and scroll-to-bottom FAB in group chat (#1013)
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
97b6641d84
commit
5c8ab7ed6c
1 file changed
+44
-6
@@ -77,6 +77,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.m57.hermescontrol.R
|
import com.m57.hermescontrol.R
|
||||||
import com.m57.hermescontrol.theme.LocalChatFontScale
|
import com.m57.hermescontrol.theme.LocalChatFontScale
|
||||||
import com.m57.hermescontrol.ui.chat.MarkdownText
|
import com.m57.hermescontrol.ui.chat.MarkdownText
|
||||||
|
import com.m57.hermescontrol.ui.chat.components.ChatScrollToBottomFab
|
||||||
|
import com.m57.hermescontrol.ui.chat.components.rememberChatScrollController
|
||||||
|
import com.m57.hermescontrol.ui.chat.components.tailContentKey
|
||||||
import com.m57.hermescontrol.ui.chat.formatTimestamp
|
import com.m57.hermescontrol.ui.chat.formatTimestamp
|
||||||
import com.m57.hermescontrol.ui.common.BotAvatar
|
import com.m57.hermescontrol.ui.common.BotAvatar
|
||||||
import com.m57.hermescontrol.ui.common.EmptyState
|
import com.m57.hermescontrol.ui.common.EmptyState
|
||||||
@@ -95,6 +98,8 @@ fun GroupChatScreen(
|
|||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsState()
|
val state by viewModel.uiState.collectAsState()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
val scrollScope = rememberCoroutineScope()
|
||||||
|
val scrollController = rememberChatScrollController(listState, scrollScope)
|
||||||
var inputFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
var inputFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||||
mutableStateOf(TextFieldValue(""))
|
mutableStateOf(TextFieldValue(""))
|
||||||
}
|
}
|
||||||
@@ -104,6 +109,7 @@ fun GroupChatScreen(
|
|||||||
val handleSend = {
|
val handleSend = {
|
||||||
val text = inputFieldValue.text
|
val text = inputFieldValue.text
|
||||||
if (text.isNotBlank()) {
|
if (text.isNotBlank()) {
|
||||||
|
scrollController.jumpToBottom()
|
||||||
viewModel.sendMessage(text)
|
viewModel.sendMessage(text)
|
||||||
inputFieldValue = TextFieldValue("")
|
inputFieldValue = TextFieldValue("")
|
||||||
}
|
}
|
||||||
@@ -113,10 +119,32 @@ fun GroupChatScreen(
|
|||||||
viewModel.setGroup(groupName)
|
viewModel.setGroup(groupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.messages.size) {
|
LaunchedEffect(Unit) {
|
||||||
if (state.messages.isNotEmpty()) {
|
scrollController.observeUserScrollPosition()
|
||||||
listState.animateScrollToItem(state.messages.size - 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val streamingMessage = state.messages.find { it.isStreaming }
|
||||||
|
val tailKey =
|
||||||
|
remember(
|
||||||
|
state.messages.size,
|
||||||
|
streamingMessage?.id,
|
||||||
|
streamingMessage?.text?.length,
|
||||||
|
state.activeSpeaker,
|
||||||
|
) {
|
||||||
|
tailContentKey(
|
||||||
|
messages = state.messages,
|
||||||
|
streamingMessage = streamingMessage?.text,
|
||||||
|
isThinking = state.activeSpeaker != null,
|
||||||
|
subagentIndicators = emptyList<Any>(),
|
||||||
|
clarifyRequest = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(tailKey) {
|
||||||
|
scrollController.onTailChanged(
|
||||||
|
tailKey = tailKey,
|
||||||
|
messageCount = state.messages.size,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HermesScaffold(
|
HermesScaffold(
|
||||||
@@ -204,13 +232,16 @@ fun GroupChatScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(LocalDensity provides chatDensity) {
|
Box(
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(LocalDensity provides chatDensity) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
@@ -242,6 +273,13 @@ fun GroupChatScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatScrollToBottomFab(
|
||||||
|
show = !scrollController.isFollowingBottom && state.messages.isNotEmpty(),
|
||||||
|
pendingCount = scrollController.pendingCount,
|
||||||
|
onScrollToBottom = { scrollController.resumeFollowing() },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user