refactor(android): unify chat status label
This commit is contained in:
@@ -15,6 +15,7 @@ import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.material.icons.filled.FolderOpen
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
@@ -41,6 +42,7 @@ import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun ChatComposer(
|
||||
sessionKey: String,
|
||||
healthOk: Boolean,
|
||||
thinkingLevel: String,
|
||||
pendingRunCount: Int,
|
||||
@@ -49,6 +51,7 @@ fun ChatComposer(
|
||||
onPickImages: () -> Unit,
|
||||
onRemoveAttachment: (id: String) -> Unit,
|
||||
onSetThinkingLevel: (level: String) -> Unit,
|
||||
onShowSessions: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onAbort: () -> Unit,
|
||||
onSend: (text: String) -> Unit,
|
||||
@@ -88,6 +91,10 @@ fun ChatComposer(
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
FilledTonalIconButton(onClick = onShowSessions, modifier = Modifier.size(42.dp)) {
|
||||
Icon(Icons.Default.FolderOpen, contentDescription = "Sessions")
|
||||
}
|
||||
|
||||
FilledTonalIconButton(onClick = onRefresh, modifier = Modifier.size(42.dp)) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Refresh")
|
||||
}
|
||||
@@ -111,6 +118,7 @@ fun ChatComposer(
|
||||
)
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
ConnectionPill(sessionKey = sessionKey, healthOk = healthOk)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (pendingRunCount > 0) {
|
||||
@@ -147,6 +155,32 @@ fun ChatComposer(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConnectionPill(sessionKey: String, healthOk: Boolean) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(7.dp),
|
||||
shape = androidx.compose.foundation.shape.CircleShape,
|
||||
color = if (healthOk) Color(0xFF2ECC71) else Color(0xFFF39C12),
|
||||
) {}
|
||||
Text(sessionKey, style = MaterialTheme.typography.labelSmall)
|
||||
Text(
|
||||
if (healthOk) "Connected" else "Connecting…",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThinkingMenuItem(
|
||||
value: String,
|
||||
|
||||
@@ -3,45 +3,32 @@ package com.steipete.clawdis.node.ui.chat
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowCircleDown
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.FolderOpen
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.steipete.clawdis.node.chat.ChatMessage
|
||||
import com.steipete.clawdis.node.chat.ChatPendingToolCall
|
||||
|
||||
@Composable
|
||||
fun ChatMessageListCard(
|
||||
sessionKey: String,
|
||||
isBridgeConnected: Boolean,
|
||||
healthOk: Boolean,
|
||||
messages: List<ChatMessage>,
|
||||
pendingRunCount: Int,
|
||||
pendingToolCalls: List<ChatPendingToolCall>,
|
||||
streamingAssistantText: String?,
|
||||
onShowSessions: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
@@ -70,7 +57,7 @@ fun ChatMessageListCard(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(top = 44.dp, bottom = 12.dp, start = 12.dp, end = 12.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(top = 12.dp, bottom = 12.dp, start = 12.dp, end = 12.dp),
|
||||
) {
|
||||
items(count = messages.size, key = { idx -> messages[idx].id }) { idx ->
|
||||
ChatMessageBubble(message = messages[idx])
|
||||
@@ -96,15 +83,6 @@ fun ChatMessageListCard(
|
||||
}
|
||||
}
|
||||
|
||||
ChatStatusPill(
|
||||
sessionKey = sessionKey,
|
||||
isBridgeConnected = isBridgeConnected,
|
||||
healthOk = healthOk,
|
||||
onShowSessions = onShowSessions,
|
||||
onRefresh = onRefresh,
|
||||
modifier = Modifier.align(Alignment.TopStart).padding(10.dp),
|
||||
)
|
||||
|
||||
if (messages.isEmpty() && pendingRunCount == 0 && pendingToolCalls.isEmpty() && streamingAssistantText.isNullOrBlank()) {
|
||||
EmptyChatHint(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
@@ -112,70 +90,6 @@ fun ChatMessageListCard(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChatStatusPill(
|
||||
sessionKey: String,
|
||||
isBridgeConnected: Boolean,
|
||||
healthOk: Boolean,
|
||||
onShowSessions: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val statusText =
|
||||
when {
|
||||
!isBridgeConnected -> "Offline"
|
||||
healthOk -> "Connected"
|
||||
else -> "Connecting…"
|
||||
}
|
||||
val statusColor =
|
||||
when {
|
||||
!isBridgeConnected -> Color(0xFF9E9E9E)
|
||||
healthOk -> Color(0xFF2ECC71)
|
||||
else -> Color(0xFFF39C12)
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.96f),
|
||||
shadowElevation = 1.dp,
|
||||
tonalElevation = 0.dp,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(7.dp),
|
||||
shape = androidx.compose.foundation.shape.CircleShape,
|
||||
color = statusColor,
|
||||
) {}
|
||||
|
||||
Text(
|
||||
text = sessionKey,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
Text(
|
||||
text = statusText,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.alpha(0.9f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
FilledTonalIconButton(onClick = onShowSessions, modifier = Modifier.size(34.dp)) {
|
||||
Icon(Icons.Default.FolderOpen, contentDescription = "Sessions")
|
||||
}
|
||||
|
||||
FilledTonalIconButton(onClick = onRefresh, modifier = Modifier.size(34.dp)) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Refresh")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyChatHint(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
|
||||
@@ -33,7 +33,6 @@ fun ChatSheetContent(viewModel: MainViewModel) {
|
||||
val messages by viewModel.chatMessages.collectAsState()
|
||||
val errorText by viewModel.chatError.collectAsState()
|
||||
val pendingRunCount by viewModel.pendingRunCount.collectAsState()
|
||||
val isBridgeConnected by viewModel.isConnected.collectAsState()
|
||||
val healthOk by viewModel.chatHealthOk.collectAsState()
|
||||
val sessionKey by viewModel.chatSessionKey.collectAsState()
|
||||
val thinkingLevel by viewModel.chatThinkingLevel.collectAsState()
|
||||
@@ -79,19 +78,15 @@ fun ChatSheetContent(viewModel: MainViewModel) {
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
ChatMessageListCard(
|
||||
sessionKey = sessionKey,
|
||||
isBridgeConnected = isBridgeConnected,
|
||||
healthOk = healthOk,
|
||||
messages = messages,
|
||||
pendingRunCount = pendingRunCount,
|
||||
pendingToolCalls = pendingToolCalls,
|
||||
streamingAssistantText = streamingAssistantText,
|
||||
onShowSessions = { showSessions = true },
|
||||
onRefresh = { viewModel.refreshChat() },
|
||||
modifier = Modifier.weight(1f, fill = true),
|
||||
)
|
||||
|
||||
ChatComposer(
|
||||
sessionKey = sessionKey,
|
||||
healthOk = healthOk,
|
||||
thinkingLevel = thinkingLevel,
|
||||
pendingRunCount = pendingRunCount,
|
||||
@@ -100,6 +95,7 @@ fun ChatSheetContent(viewModel: MainViewModel) {
|
||||
onPickImages = { pickImages.launch("image/*") },
|
||||
onRemoveAttachment = { id -> attachments.removeAll { it.id == id } },
|
||||
onSetThinkingLevel = { level -> viewModel.setChatThinkingLevel(level) },
|
||||
onShowSessions = { showSessions = true },
|
||||
onRefresh = { viewModel.refreshChat() },
|
||||
onAbort = { viewModel.abortChat() },
|
||||
onSend = { text ->
|
||||
|
||||
Reference in New Issue
Block a user