使用Jetpack Compose构建用户与ai流式交互界面
Creato il: 24 gennaio 2025
Creato il: 24 gennaio 2025
使用Jetpack Compose构建用户与ai流式交互界面
下面给出一个示例性思路,演示如何使用 Jetpack Compose 来构建一个类似「用户与AI流式交互」的聊天界面。示例中会包含以下要点:
注意:这里的示例仅用于演示 Compose + 协程/Flow/State 等的基本用法,实际接入 AI 大模型或 OpenAI 流式接口(SSE / WebSocket / chunked response 等)时,需要根据具体协议或网络实现进行替换。
一个常见的做法是定义一个 Message
数据类,用于描述聊天中的一条消息。通常需要包含:
text
: 消息内容isUser
: 是否是用户发送的消息(以此区分左右布局)kotlindata class Message( val text: String, val isUser: Boolean )
在聊天界面中,我们需要维护:
一种常见做法是使用 ViewModel
+ MutableStateFlow
/MutableState
来管理这些状态,这样在进行流式更新时,Compose 界面可以自动重组并展示最新内容。
kotlinclass ChatViewModel : ViewModel() { // 用于记录当前聊天列表 private val _messageList = MutableStateFlow<List<Message>>(emptyList()) val messageList: StateFlow<List<Message>> = _messageList // 当前输入框内容 var inputText by mutableStateOf("") private set // 更新输入框 fun onInputTextChange(newText: String) { inputText = newText } // 模拟发送用户消息和AI流式回复 fun sendMessage() { val userText = inputText.trim() if (userText.isNotEmpty()) { // 1. 将用户的消息添加到列表 addMessage(Message(text = userText, isUser = true)) // 2. 置空输入框 inputText = "" // 3. 开始请求AI或模拟流式响应 simulateStreamingAIResponse(userText) } } // 将新消息追加到列表 private fun addMessage(msg: Message) { _messageList.value = _messageList.value + msg } /** * 这里演示的是模拟流式响应。实际使用中可调用: * - OpenAI API 的 streaming 接口 * - SSE / WebSocket 接口 * - 或者自己封装的 chunked http response 等 */ private fun simulateStreamingAIResponse(userQuery: String) { viewModelScope.launch { // 用 Flow 来模拟逐字(或逐句)返回数据的场景 getMockAIFlow(userQuery).collect { partialText -> // 将最新的部分文本追加到最后一条 AI 消息 updateOrCreateAiMessage(partialText) } } } /** * 将最新的 partialText 更新到最后一条 AI 消息上。 * 如果是第一次收集,就先创建 AI 消息;后续收集时则继续更新同一条消息。 */ private fun updateOrCreateAiMessage(partialText: String) { val currentList = _messageList.value // 如果最后一条消息不是 AI 消息,则先创建一条新的 AI 消息 if (currentList.isEmpty() || currentList.last().isUser) { _messageList.value = currentList + Message(text = partialText, isUser = false) } else { // 否则,在最后一条 AI 消息的基础上,累加文本 val lastMessage = currentList.last() val newLastMessage = lastMessage.copy(text = partialText) _messageList.value = currentList.dropLast(1) + newLastMessage } } /** * 模拟一个 Flow,分多次发射逐渐生成的回复内容 */ private fun getMockAIFlow(userQuery: String): Flow<String> = flow { val fullResponse = "针对 \"$userQuery\" 的AI回复,这里是一个模拟的流式内容……" var buffer = "" // 逐字发送 for (c in fullResponse) { buffer += c emit(buffer) delay(100) // 模拟网络或生成延迟 } } }
要点解析:
simulateStreamingAIResponse
内部演示通过Flow
的collect
逐步拿到文本,然后更新到最后一条 AI 消息里。- 真实环境下,请将
getMockAIFlow
替换为实际调用,如 SSE、WebSocket 或官方提供的 ChatGPT 流式接口等。
在 ChatScreen
中订阅 ChatViewModel
暴露的 messageList
,并通过 LazyColumn
显示消息列表,再配合一个输入框和发送按钮完成交互。代码示例如下:
kotlin@Composable fun ChatScreen( viewModel: ChatViewModel = viewModel() ) { val messageList by viewModel.messageList.collectAsState() val inputText = viewModel.inputText // 整体布局 Column( modifier = Modifier.fillMaxSize() ) { // 聊天记录显示 LazyColumn( modifier = Modifier .weight(1f) .padding(8.dp), reverseLayout = false ) { items(messageList) { message -> MessageBubble(message) } } // 底部输入栏 Row( modifier = Modifier .padding(8.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { TextField( value = inputText, onValueChange = { viewModel.onInputTextChange(it) }, modifier = Modifier.weight(1f), placeholder = { Text("请输入...") } ) Button(onClick = { viewModel.sendMessage() }) { Text("发送") } } } }
为了让用户和AI的消息区分明显,可以在气泡布局上做样式区分。常见做法包括:
示例:
kotlin@Composable fun MessageBubble(message: Message) { val bubbleColor = if (message.isUser) Color(0xFFD0E8FF) else Color(0xFFECECEC) val alignment = if (message.isUser) Alignment.End else Alignment.Start val shape = if (message.isUser) { RoundedCornerShape(8.dp, 0.dp, 8.dp, 8.dp) } else { RoundedCornerShape(0.dp, 8.dp, 8.dp, 8.dp) } Row( modifier = Modifier .fillMaxWidth(), horizontalArrangement = if (message.isUser) Arrangement.End else Arrangement.Start ) { Box( modifier = Modifier .background(bubbleColor, shape) .padding(8.dp) .widthIn(max = 280.dp) // 限制最大宽度 ) { Text( text = message.text, modifier = Modifier.align(Alignment.CenterStart), color = Color.Black ) } } Spacer(modifier = Modifier.height(4.dp)) // 消息之间的空隙 }
如果是一个简单的单页应用,可以直接在 setContent
中调用 ChatScreen
:
kotlinclass MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { ChatScreen() } } } }
如果使用 Navigation
组件,可以在 NavHost 中把 ChatScreen
作为一个目的地。
ViewModel
中以协程的方式收集流式数据,不断更新 StateFlow
/MutableState
,从而触发 Jetpack Compose 的重组。LaunchedEffect
或手动管理 LazyListState
。例如:
kotlinval listState = rememberLazyListState() LazyColumn( state = listState, ... ) { ... } // 当 messageList 变化时,滚动到底部 LaunchedEffect(messageList) { listState.animateScrollToItem(messageList.size - 1) }
emit()
向上游发送一段段内容ViewModel
中不断地更新 StateFlow
/MutableState
,Compose 自动完成界面重绘。LazyColumn
,并配合单独的输入控件和发送按钮,保证布局简洁易用。这样,我们就可以在 Jetpack Compose 中构建一个用户与 AI 进行流式交互的聊天界面。上面的示例只是一个起点,实际项目中可以进一步完善,例如:消息撤回、出错处理、富文本显示(如代码片段、高亮)、上下文管理等。希望对你有所帮助!