diff --git a/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt b/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt index e5b81ad..af25bda 100644 --- a/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt +++ b/app/src/main/java/dev/kaizen/app/chat/StreamBlocks.kt @@ -26,7 +26,9 @@ import androidx.compose.material.icons.rounded.ExpandLess import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.Language import androidx.compose.material.icons.rounded.Psychology +import androidx.compose.material.icons.rounded.AutoAwesome import androidx.compose.material.icons.rounded.Build +import androidx.compose.material.icons.rounded.Description import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -45,8 +47,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import dev.kaizen.app.net.SearchSource import dev.kaizen.app.net.ToolEvent +import dev.kaizen.app.R import dev.kaizen.app.ui.shape.KaizenShapes import dev.kaizen.app.ui.theme.LocalKaizenAccent +import androidx.compose.ui.res.stringResource @Composable fun ReasoningBlock(reasoning: String, streaming: Boolean = false) { @@ -103,52 +107,66 @@ fun ToolEventsBlock(tools: List, streaming: Boolean = false) { val cs = MaterialTheme.colorScheme val isDark = isSystemInDarkTheme() val accent = LocalKaizenAccent.current - val bg = if (isDark) Color.White.copy(alpha = 0.04f) else Color.Black.copy(alpha = 0.02f) - val borderColor = if (isDark) Color.White.copy(alpha = 0.06f) else Color.Black.copy(alpha = 0.04f) - Column( - Modifier - .fillMaxWidth() - .clip(KaizenShapes.sm) - .background(bg) - .border(1.dp, borderColor, KaizenShapes.sm) - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - tools.forEach { tool -> + val completed = mutableListOf() + val inProgress = mutableListOf() + val seen = mutableSetOf() + var thinking = false + var analyzing: String? = null + + for (ev in tools) { + when (ev.type) { + "thinking" -> { thinking = true; analyzing = null } + "analyzing" -> { analyzing = ev.name; thinking = false } + "start" -> { + analyzing = null; thinking = false + if (ev.name !in seen) { seen.add(ev.name); inProgress.add(ev.name) } + } + "end", "error" -> { + inProgress.remove(ev.name) + completed.add(ev) + } + } + } + + val hasContent = analyzing != null || thinking || completed.isNotEmpty() || inProgress.isNotEmpty() + if (!hasContent) return + + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + if (analyzing != null) { Row(verticalAlignment = Alignment.CenterVertically) { - val icon = when (tool.type) { - "end" -> Icons.Rounded.Check - "error" -> Icons.Rounded.Error - else -> Icons.Rounded.Build - } - val tint = when (tool.type) { - "end" -> Color(0xFF10B981) - "error" -> Color(0xFFEF4444) - else -> accent.primary - } + Icon(Icons.Rounded.Description, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp)) + Spacer(Modifier.width(6.dp)) + Text(stringResource(R.string.stream_tool_analyzing, analyzing!!), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp) + } + } + if (thinking && analyzing == null && completed.isEmpty() && inProgress.isEmpty()) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Rounded.AutoAwesome, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp)) + Spacer(Modifier.width(6.dp)) + Text(stringResource(R.string.stream_tool_thinking), color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp) + } + } + completed.forEach { tool -> + Row(verticalAlignment = Alignment.CenterVertically) { + val icon = if (tool.type == "error") Icons.Rounded.Error else Icons.Rounded.Check + val tint = if (tool.type == "error") Color(0xFFEF4444) else Color(0xFF10B981) Icon(icon, null, tint = tint, modifier = Modifier.size(14.dp)) - Spacer(Modifier.width(8.dp)) - Text( - tool.name.ifEmpty { "Tool" }, - color = cs.onBackground, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - ) + Spacer(Modifier.width(6.dp)) + Text(tool.name, color = cs.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp) if (tool.elapsed != null) { - Spacer(Modifier.width(6.dp)) - Text( - "${tool.elapsed}ms", - color = cs.onSurfaceVariant.copy(alpha = 0.5f), - fontSize = 11.sp, - ) - } - if (tool.error != null) { - Spacer(Modifier.width(6.dp)) - Text(tool.error, color = Color(0xFFEF4444), fontSize = 11.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) + Spacer(Modifier.width(4.dp)) + Text("${tool.elapsed}ms", color = cs.onSurfaceVariant.copy(alpha = 0.35f), fontSize = 11.sp) } } } + inProgress.forEach { name -> + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Rounded.Build, null, tint = accent.primary.copy(alpha = 0.6f), modifier = Modifier.size(14.dp)) + Spacer(Modifier.width(6.dp)) + Text(name, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 12.sp, fontWeight = FontWeight.Medium) + } + } } } diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index faebf64..eee8728 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -22,6 +22,8 @@ Reasoning Sources + Processing … + Analyzing %1$s … Session expired. Please sign in again. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7866a1f..1a9b95e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,8 @@ Gedankengang Quellen + Verarbeitet … + Analysiert %1$s … Sitzung abgelaufen. Bitte erneut anmelden.