Keep the image on screen when its tool call joins a group
A `Read` that returns an image is a row of one call, and the moment the session makes its next call the two become a group -- which is a different composable in a different part of the tree, so the old subtree goes and everything it remembered goes with it. The full-screen viewer was inside that subtree, so somebody looking at a screenshot was thrown back to the transcript because the session carried on working. A page of history landing does the same thing to the same row. What is open is a property of the screen rather than of whichever row happened to draw the thumbnail, so it is held there now and drawn beside the other two dialogs. Nothing that happens to rows can reach it. The cost is one fetch when it opens, since the thumbnail's decoded bitmap belongs to a row this no longer goes through. Paid deliberately rather than plumbed around: it is one request for a picture somebody asked to see, and the viewer draws the same two empty states the thumbnail does -- still coming, and never coming -- which it previously could not have, since it only ever opened on a bitmap already in hand. `/tools n gap` now puts a screenshot on its first call, so the case is reproducible rather than argued about: that command already existed to make a run *grow* while somebody watches, and the image is what made growing matter. Checked on the emulator with `/tools 3 30` -- opened the image on the lone call, and it was still open a minute later with the row by then inside a group of three, and back returned to the transcript rather than leaving the app.
This commit is contained in:
1 parent
ed88bdb31f
commit
bfaf5e6f38
4 files changed
+146
-42
No files matched your search
@@ -264,6 +264,10 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
// like everything else new in this transcript, and held here rather than in the card so a
|
||||
// note opened and scrolled past is still open on the way back.
|
||||
var openMemories by remember { mutableStateOf(setOf<String>()) }
|
||||
// The image being looked at full screen, by ref. Here rather than in the row that drew the
|
||||
// thumbnail: a row regrouped underneath the reader takes its whole subtree with it, and the
|
||||
// dialog with it -- see [SessionImageViewer].
|
||||
var fullImage by remember { mutableStateOf<String?>(null) }
|
||||
// Uploaded-but-not-yet-sent attachment ids; sent with the next message.
|
||||
var pendingAttachments by remember { mutableStateOf(listOf<String>()) }
|
||||
// What this session is set to now, seeded from the row that opened it and
|
||||
@@ -930,6 +934,11 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens one image full screen, from whichever row drew it; see [SessionImageViewer]. */
|
||||
fun openImage(ref: String) {
|
||||
fullImage = ref
|
||||
}
|
||||
|
||||
/** Opens or closes one memory note, wherever it is drawn; see [MemoryNote]. */
|
||||
fun toggleMemory(text: String) {
|
||||
openMemories = if (text in openMemories) openMemories - text else openMemories + text
|
||||
@@ -1205,6 +1214,7 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
sessionId = summary.id,
|
||||
text = waiting.text,
|
||||
images = waiting.images,
|
||||
onOpenImage = ::openImage,
|
||||
pending = true,
|
||||
refusal = waiting.refusal,
|
||||
// The bubble goes away on the `messageDropped` this
|
||||
@@ -1315,7 +1325,12 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
}
|
||||
},
|
||||
image = { ref ->
|
||||
SessionImage(settings, summary.id, ref)
|
||||
SessionImage(
|
||||
settings,
|
||||
summary.id,
|
||||
ref,
|
||||
::openImage,
|
||||
)
|
||||
},
|
||||
)
|
||||
is TranscriptRow.Single ->
|
||||
@@ -1326,6 +1341,7 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
sessionId = summary.id,
|
||||
text = item.text,
|
||||
images = item.images,
|
||||
onOpenImage = ::openImage,
|
||||
)
|
||||
is TranscriptItem.AssistantMsg ->
|
||||
// A whole assistant row is only ever the reply
|
||||
@@ -1368,7 +1384,12 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
}
|
||||
},
|
||||
image = { ref ->
|
||||
SessionImage(settings, summary.id, ref)
|
||||
SessionImage(
|
||||
settings,
|
||||
summary.id,
|
||||
ref,
|
||||
::openImage,
|
||||
)
|
||||
},
|
||||
)
|
||||
is TranscriptItem.QuestionCard ->
|
||||
@@ -1389,7 +1410,12 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
is TranscriptItem.ImageItem ->
|
||||
SessionImage(settings, summary.id, item.ref)
|
||||
SessionImage(
|
||||
settings,
|
||||
summary.id,
|
||||
item.ref,
|
||||
::openImage,
|
||||
)
|
||||
is TranscriptItem.Note ->
|
||||
Text(
|
||||
item.text,
|
||||
@@ -1689,6 +1715,9 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
|
||||
}
|
||||
}
|
||||
|
||||
// Beside the other two dialogs, and outside the list for the same reason as them: what is
|
||||
// open is the screen's business rather than any row's. See [SessionImageViewer].
|
||||
fullImage?.let { ref -> SessionImageViewer(settings, summary.id, ref) { fullImage = null } }
|
||||
if (usageOpen) {
|
||||
UsageDialog(settings = settings, onDismiss = { usageOpen = false })
|
||||
}
|
||||
@@ -1762,6 +1791,7 @@ private fun UserBubble(
|
||||
sessionId: String,
|
||||
text: String,
|
||||
images: List<String> = emptyList(),
|
||||
onOpenImage: (String) -> Unit,
|
||||
pending: Boolean = false,
|
||||
refusal: String? = null,
|
||||
onTakeBack: (() -> Unit)? = null,
|
||||
@@ -1808,7 +1838,7 @@ private fun UserBubble(
|
||||
// same place down the transcript, whether or not there is an image in it.
|
||||
images.forEachIndexed { index, ref ->
|
||||
if (index > 0 || text.isNotEmpty()) Spacer(Modifier.height(4.dp))
|
||||
SessionImage(settings, sessionId, ref)
|
||||
SessionImage(settings, sessionId, ref, onOpenImage)
|
||||
}
|
||||
refusal?.let {
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
Reference in new issue
Block a user