diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt index 21cab85..f992901 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt @@ -2024,6 +2024,7 @@ fun SessionScreen( is TranscriptItem.ThinkingRow -> ThinkingCard( item = item, + replies = replies, expanded = item.seq in expandedThinking, onToggle = { toggleAnchored( diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/ThinkingCard.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/ThinkingCard.kt index 05eb75c..97415d0 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/ThinkingCard.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/ThinkingCard.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -32,6 +33,7 @@ import androidx.compose.ui.unit.dp @Composable fun ThinkingCard( item: TranscriptItem.ThinkingRow, + replies: ParsedReplies, expanded: Boolean, onToggle: () -> Unit, modifier: Modifier = Modifier, @@ -48,16 +50,22 @@ fun ThinkingCard( ) } } + // Markdown, like every other thing the model wrote: a model reasons in the same + // lists, headings and fenced code it answers in, and drawn plainly those arrive as + // rows of hashes and asterisks around the working the reader opened the card to read. + // Still arriving means the incremental parse -- see [MarkdownText] -- since an open + // block gains a delta at a time. + // + // The words shut the card too, and have to do it themselves -- see [LocalMarkdownTap]. 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), - ) + CompositionLocalProvider(LocalMarkdownTap provides rememberMarkdownTap(onToggle)) { + MarkdownText( + item.text, + replies, + Modifier.padding(top = 6.dp), + live = item.open, + ) + } } } } diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptItems.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptItems.kt index e6f6101..da71e92 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptItems.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptItems.kt @@ -718,6 +718,10 @@ suspend fun warm(replies: ParsedReplies, rows: List) { // transcript often enough that leaving it out was the whole of why one cost a fifth // of a second to open. is TranscriptItem.PeerNote -> listOf(row.text) + // A model's working is markdown too, and only once it is settled: an open block + // gains a delta at a time and is drawn by the incremental parse, so warming one + // would hold a parse of every prefix of it. + is TranscriptItem.ThinkingRow -> if (row.open) emptyList() else listOf(row.text) else -> emptyList() } } diff --git a/server/src/session/echo.rs b/server/src/session/echo.rs index 3f47a36..32b498e 100644 --- a/server/src/session/echo.rs +++ b/server/src/session/echo.rs @@ -941,10 +941,19 @@ impl EchoDriver { if let Some(think) = think { let started = std::time::Instant::now(); for remaining in (1..=think.as_secs()).rev() { + // Markdown, because the card draws it as markdown and a + // fixture of flat prose exercises none of that: a heading, a + // list, a fence and some inline code are what a model's + // working actually looks like. send(Event::Thinking { delta: format!( - "Considering what to echo back, {remaining}s of it left. \ - The reply is the message, which took some working out.\n\n" + "## Considering what to echo back\n\n\ + `{remaining}s` of it left, and the plan is:\n\n\ + - read the message\n\ + - say it back, *exactly*\n\n\ + ```rust\n\ + let reply = message.clone();\n\ + ```\n\n" ), }); tokio::time::sleep(Duration::from_secs(1)).await;