package com.example.aiapp import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp /** * A model's working, shut until somebody asks for it. * * Shut by default, like a tool call and a memory note and for the same reason: it is not what the * session said, and left open it puts the reasoning between the question and the answer -- which on * a small model is most of the conversation. * * The heading is the whole of what the reader gets for free, so it carries the one thing worth * knowing without opening anything: whether this is still going, and if not how long it took. A * spinner while it runs, because that is the same fact a running command reports and it is drawn * the same way here. */ @Composable fun ThinkingCard( item: TranscriptItem.ThinkingRow, expanded: Boolean, onToggle: () -> Unit, modifier: Modifier = Modifier, ) { Card(modifier.fillMaxWidth().clickable(onClick = onToggle)) { Column(Modifier.padding(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { Text(thinkingHeadline(item), style = MaterialTheme.typography.titleSmall) Spacer(Modifier.width(8.dp)) if (item.open) { CircularProgressIndicator( modifier = Modifier.width(16.dp).height(16.dp), strokeWidth = 2.dp, ) } } if (expanded) { // Plain text rather than markdown: this is a model talking to itself, so its // half-finished lists and stray backticks are not markup it meant to write, and // rendering them as such makes the working look like an answer. Text( item.text, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(top = 6.dp), ) } } } } /** * "Thinking", "Thought for 12.4s", or "Thought". * * The third is the one worth keeping: a block whose turn ended before the model said anything -- * interrupted, stopped, a process that exited -- was thought about for a length of time nobody * measured. Naming a span there would be this screen inventing one, and the reader has no way to * tell an invented one from the rest. */ fun thinkingHeadline(item: TranscriptItem.ThinkingRow): String = when { item.open -> "Thinking" item.ms != null -> "Thought for ${formatMillis(item.ms)}" else -> "Thought" }