Show every option a question offers, on the call that asked
Reported by Iris through the dev-updater session: a two-question AskUserQuestion arrived with only one option visible per question, so the answer she sent was the only one she had been offered. The cause was a `Row`. It hands out intrinsic widths in order and clips whatever runs past the edge, so the first option or two drew and the rest went off the side of the screen -- which does not read as a bug, it reads as those having been the only choices. The same Row was in the permission ask beside it; both wrap now. That pairing is the reason to look: a rule stated on one member of a set is usually missing from the others. The rest of what she asked for, and what each was: - It drew twice, as the tool call and again as loose question cards, because the backend marked these questions as belonging to no call. They belong to the call that asked, and now say so. - So it renders like any other tool: one card, its own heading, opened because a decision cannot be made from a closed row. - Each option shows its description and its `preview` block, which is the part a reader is deciding on and none of which was reaching them. - "Other" is a field on every question. The harness always offers it, so leaving it out narrowed a question that was never that narrow. - A multi-select sends the labels it collected as one string, which is the tool's own schema rather than a guess -- its answers map is string-valued. - No spinner while it waits. A spinner says the machine is working; here the machine is idle and the turn is stopped on the person, so the card says "your turn" in the colour this app already uses for that. Verified against a real session as well as the echo fixture: haiku asked two questions with three described options each, both were answered from the phone, and the model carried on with the answers. Echo grew `/ask` so the shape can be looked at without paying a model to produce one, and its option cards are outlined rather than tinted -- as one surface step up they were three paragraphs where three things to press should be.
This commit is contained in:
1 parent
cae04c2559
commit
fea8e7e92b
5 files changed
+518
-61
No files matched your search
@@ -13,7 +13,6 @@ 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.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -103,7 +102,7 @@ fun ToolGroup(
|
||||
onToggle: () -> Unit,
|
||||
isToolExpanded: (String) -> Boolean,
|
||||
onToolToggle: (String) -> Unit,
|
||||
onAnswer: (TranscriptItem.ToolRun, String) -> Unit,
|
||||
onAnswer: (questionId: String, answer: String) -> Unit,
|
||||
image: @Composable (String) -> Unit,
|
||||
) {
|
||||
if (!expanded) {
|
||||
@@ -127,7 +126,7 @@ fun ToolGroup(
|
||||
tool = call,
|
||||
expanded = isToolExpanded(call.id),
|
||||
onToggle = { onToolToggle(call.id) },
|
||||
onAnswer = { answer -> onAnswer(call, answer) },
|
||||
onAnswer = onAnswer,
|
||||
image = image,
|
||||
)
|
||||
}
|
||||
@@ -169,11 +168,11 @@ fun ToolCard(
|
||||
tool: TranscriptItem.ToolRun,
|
||||
expanded: Boolean,
|
||||
onToggle: () -> Unit,
|
||||
onAnswer: (String) -> Unit,
|
||||
onAnswer: (questionId: String, answer: String) -> Unit,
|
||||
image: @Composable (String) -> Unit = {},
|
||||
) {
|
||||
val parsed = remember(tool.tool, tool.input) { parseToolInput(tool.tool, tool.input) }
|
||||
val deciding = tool.ask != null && tool.ask.answer == null
|
||||
val deciding = tool.asks.any { it.answer == null }
|
||||
val open = expanded || deciding
|
||||
Card(Modifier.fillMaxWidth().clickable(onClick = onToggle)) {
|
||||
Column(Modifier.padding(12.dp)) {
|
||||
@@ -200,7 +199,18 @@ fun ToolCard(
|
||||
)
|
||||
} ?: Spacer(Modifier.weight(1f))
|
||||
}
|
||||
if (!tool.done) {
|
||||
// A spinner says the machine is working. While this call is waiting on an
|
||||
// answer the machine is doing nothing at all -- the turn is stopped on the
|
||||
// person reading it -- so it says whose move it is instead, in the colour this
|
||||
// app uses everywhere for that.
|
||||
if (deciding) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
"your turn",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = awaitingColor,
|
||||
)
|
||||
} else if (!tool.done) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.width(16.dp).height(16.dp),
|
||||
@@ -217,7 +227,12 @@ fun ToolCard(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
ToolInputView(tool.tool, tool.input, Modifier.padding(top = 4.dp))
|
||||
// Everything AskUserQuestion carries is the questions, and those are drawn
|
||||
// below as something answerable; dumping the same JSON above them would be the
|
||||
// decision stated twice, once unreadably.
|
||||
if (tool.tool != ASK_USER_QUESTION) {
|
||||
ToolInputView(tool.tool, tool.input, Modifier.padding(top = 4.dp))
|
||||
}
|
||||
if (tool.output.isNotEmpty()) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Output", style = MaterialTheme.typography.labelSmall)
|
||||
@@ -229,7 +244,15 @@ fun ToolCard(
|
||||
// less than the one line it replaced -- unlike a command, which
|
||||
// is what the closed line already summarises.
|
||||
tool.images.forEach { ref -> image(ref) }
|
||||
tool.ask?.let { ask -> PermissionAsk(ask, onAnswer) }
|
||||
if (tool.asks.isNotEmpty()) {
|
||||
if (tool.tool == ASK_USER_QUESTION) {
|
||||
AskUserQuestionBody(tool.input, tool.asks, onAnswer)
|
||||
} else {
|
||||
tool.asks.forEach { ask ->
|
||||
PermissionAsk(ask) { answer -> onAnswer(ask.id, answer) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,10 +278,9 @@ private fun PermissionAsk(ask: TranscriptItem.QuestionCard, onAnswer: (String) -
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
} else {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
ask.options.forEach { option ->
|
||||
OutlinedButton(onClick = { onAnswer(option) }) { Text(option) }
|
||||
}
|
||||
}
|
||||
AnswerOptions(ask.options, onAnswer)
|
||||
}
|
||||
}
|
||||
|
||||
/** The tool whose input is a question rather than a command; see [AskUserQuestionBody]. */
|
||||
private const val ASK_USER_QUESTION = "AskUserQuestion"
|
||||
Reference in new issue
Block a user