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 828be93..d656440 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt @@ -110,7 +110,17 @@ fun foldEvent(items: List, event: SessionEvent): List updateTool(items, event.id) { it.copy(output = event.output) } is SessionEvent.ToolEnd -> - updateTool(items, event.id) { it.copy(output = event.output, done = true) } + // Created when its start is not here, rather than dropped. A + // fold that only ever *updates* loses the whole call when the + // start fell outside the loaded window, and a tool call that + // renders as nothing is indistinguishable from one that never + // happened. The name is unknown from an end alone; loading the + // page before this one replaces the row with the real thing. + if (items.any { it is TranscriptItem.ToolRun && it.id == event.id }) { + updateTool(items, event.id) { it.copy(output = event.output, done = true) } + } else { + items + TranscriptItem.ToolRun(event.id, "tool", "", event.output, done = true) + } is SessionEvent.Question -> items + TranscriptItem.QuestionCard(event.id, event.prompt, event.options, answer = null) @@ -169,6 +179,9 @@ fun SessionScreen( // screen starts with the end of the conversation and fetches earlier // pages only when somebody scrolls to them. var oldestSeq by remember { mutableLongStateOf(0L) } + // Every transcript event loaded, in order, beside the rows they folded + // into. See `apply`. + var loaded by remember { mutableStateOf(listOf()) } var moreHistory by remember { mutableStateOf(true) } var loadingHistory by remember { mutableStateOf(false) } var ready by remember { mutableStateOf(false) } @@ -179,7 +192,14 @@ fun SessionScreen( when (val event = entry.event) { is SessionEvent.Status -> status = event.state is SessionEvent.UsageDelta -> totalTokens += event.tokens - else -> items = foldEvent(items, event) + else -> { + // Kept as well as folded. Folding is one-way -- a tool's + // start and end become one row -- so a page arriving in + // front of what is already here cannot be stitched on + // without the events themselves. + loaded = loaded + event + items = foldEvent(items, event) + } } }