A Rust backend that owns the sessions and an Android app that reads them. The server spawns and adopts CLI processes, normalises everything they emit into one event model, keeps the transcript, and serves it over pinned TLS on a WireGuard interface; the phone streams that, replies, sends images, and imports conversations the machine already has. `AGENTS.md` is the working guide -- what runs where, what has been measured, and the faults that were expensive to find. `PLAN.md` is the design record. History before this point was squashed away. It was a personal project's running commentary and carried a name and a couple of machine paths that have no business in a public repository; the tree is what mattered and the tree is here.
55 lines
2.2 KiB
Kotlin
55 lines
2.2 KiB
Kotlin
package com.example.aiapp
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.material3.HorizontalDivider
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
/**
|
|
* A line across the transcript saying what left the session's context.
|
|
*
|
|
* Centred between two rules, because it is a divider rather than something anybody said. Two things
|
|
* produce one -- a compaction and a clear -- and they are drawn the same way on purpose: to a
|
|
* reader scrolling back, both mean "the session no longer has what is above this", and which of the
|
|
* two it was is said by the words and the colour.
|
|
*
|
|
* The rules take [color] too, so the whole divider reads as one mark of one kind rather than a
|
|
* coloured phrase sitting in an unrelated grey line.
|
|
*
|
|
* Written once here rather than styled at each of them, so the two cannot drift into looking like
|
|
* different kinds of thing.
|
|
*/
|
|
@Composable
|
|
fun TranscriptDivider(text: String, color: Color, modifier: Modifier = Modifier) {
|
|
Row(
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
modifier = modifier.fillMaxWidth().padding(vertical = 8.dp),
|
|
) {
|
|
HorizontalDivider(Modifier.weight(1f), color = color)
|
|
Text(text, style = MaterialTheme.typography.bodySmall, color = color)
|
|
HorizontalDivider(Modifier.weight(1f), color = color)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The mark a clear leaves.
|
|
*
|
|
* Red, and no counts: a clear takes the conversation out of what the session is given, and unlike a
|
|
* compaction it summarises nothing and measures nothing, so there is nothing to report but the
|
|
* fact. Everything above stays on screen and stays scrollable -- the reader can see that, which is
|
|
* why this does not say it.
|
|
*/
|
|
@Composable
|
|
fun ClearedRow(modifier: Modifier = Modifier) {
|
|
TranscriptDivider("Context cleared", clearedColor, modifier)
|
|
}
|