Merge branch 'main' of git.arirex.me:iris/ai-app

This commit is contained in:
iris committed 2026-08-31 02:02:07 -04:00
commit d829a77ddf
6 files changed
+149 -259

No files matched your search

@@ -45,9 +45,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
@@ -142,21 +140,6 @@ private class TopEdgeHold {
var key: Any? = null
}
/**
* Where a row is on screen, so a tap on it can be told which half it landed in.
*
* Not snapshot state, for the same reason as [TopEdgeHold]: written from layout, read from a click,
* and observed by nothing.
*/
private class RowBounds {
var top = 0f
var height = 0f
/** Above this is the row's top half, below it the bottom half. */
val middle
get() = top + height / 2
}
/** One row's height between layouts, so a change in it can be noticed. See [holdTopEdge]. */
private class LastHeight {
var value: Int? = null
@@ -643,10 +626,6 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
// by default, which is the rule for anything new in this transcript: a screen that opens
// everything it can is one nobody can scan.
var expandedNotes by remember { mutableStateOf(setOf<Long>()) }
// Long replies the reader has asked to see the rest of, by the seq of the row. Held here
// rather than in the row so that scrolling away and back does not shut something they
// deliberately opened -- the same reason the sets above it are here.
var expandedReplies by remember { mutableStateOf(setOf<Long>()) }
// 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
@@ -714,12 +693,6 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
// What is actually drawn: the transcript with runs of adjacent tool
// calls folded into one row each.
val rows = remember(items) { groupToolRuns(items) }
// The reply drawn whole however long it is; see the transcript list below. Recomputed with
// `items` rather than tracked as it arrives, because "newest" moves: a reply that was the
// last one becomes history the moment the next turn starts, and a row that kept its
// exemption after that would be the one enormous row this exists to bound.
val newestReply =
remember(items) { items.filterIsInstance<TranscriptItem.AssistantMsg>().lastOrNull()?.seq }
/**
* Everything the transcript list draws, from one event.
@@ -833,8 +806,8 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
*
* The correction itself belongs to the measurement -- see [holdTopEdge].
*/
fun toggleAnchored(key: Any, row: RowBounds, at: Float, toggle: () -> Unit) {
if (at < row.middle) topEdgeHeld.key = key
fun toggleAnchored(row: TranscriptRow, toggle: () -> Unit) {
if (listState.tappedHigh(row.startSeq)) topEdgeHeld.key = row.key
toggle()
}
@@ -1423,21 +1396,14 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
}
},
) { row ->
val bounds = remember { RowBounds() }
Box(
Modifier.onGloballyPositioned {
bounds.top = it.positionInRoot().y
bounds.height = it.size.height.toFloat()
}
.holdTopEdge(row.key, topEdgeHeld) { grew -> listState.by(grew) }
) {
Box(Modifier.holdTopEdge(row.key, topEdgeHeld) { grew -> listState.by(grew) }) {
when (row) {
is TranscriptRow.Tools ->
ToolGroup(
group = row,
expanded = row.id in expandedGroups,
onToggle = { at ->
toggleAnchored(row.key, bounds, at) {
onToggle = {
toggleAnchored(row) {
expandedGroups =
if (row.id in expandedGroups)
expandedGroups - row.id
@@ -1448,8 +1414,8 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
// Anchored on the group, not the call: opening one call makes
// the whole group taller, and the heading the reader is under
// is the group's.
onToolToggle = { id, at ->
toggleAnchored(row.key, bounds, at) {
onToolToggle = { id ->
toggleAnchored(row) {
expandedTools =
if (id in expandedTools) expandedTools - id
else expandedTools + id
@@ -1476,43 +1442,14 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
text = item.text,
images = item.images,
)
is TranscriptItem.AssistantMsg -> {
// The newest reply is never cut. It is the one being read
// as it arrives -- often still arriving -- and putting a
// "show the rest" under a turn somebody is waiting for
// hides the answer they are waiting for. Every reply
// behind it is history, and history is what this is for.
val shortened =
if (
item.seq == newestReply ||
item.seq in expandedReplies
)
null
else remember(item.text) { shortenedReply(item.text) }
if (shortened == null) {
AssistantMessage(item.text, replies)
} else {
CappedReply(
full = item.text,
shortened = shortened,
replies = replies,
onShowMore = {
// Anchored like every other row that changes
// height, so the edge the reader touched
// stays where it is.
toggleAnchored(row.key, bounds, bounds.top) {
expandedReplies = expandedReplies + item.seq
}
},
)
}
}
is TranscriptItem.AssistantMsg ->
AssistantMessage(item.text, replies)
is TranscriptItem.ToolRun ->
ToolCard(
tool = item,
expanded = item.id in expandedTools,
onToggle = { at ->
toggleAnchored(row.key, bounds, at) {
onToggle = {
toggleAnchored(row) {
expandedTools =
if (item.id in expandedTools)
expandedTools - item.id
@@ -1566,8 +1503,8 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
item = item,
expanded = item.seq in expandedNotes,
replies = replies,
onToggle = { at ->
toggleAnchored(row.key, bounds, at) {
onToggle = {
toggleAnchored(row) {
expandedNotes =
if (item.seq in expandedNotes)
expandedNotes - item.seq