Two things about the box at the bottom of a session. **A half-typed message survived nothing.** It lived in `remember`, so leaving the screen threw it away, and so did the system reclaiming the app. `Drafts.kt` keeps it per session id and the box is seeded from it. On the device rather than the backend, which is where this app otherwise puts state so every device sees it: this is the contents of a text box on the phone somebody is holding, written on every keystroke, and half a sentence surfacing on another device would be a surprise rather than a convenience. What has been *sent* is the server's, and that is the part which has to outlive this phone. **Switching model quietly re-reads the whole conversation.** The picker did it on the tap, and the cost only showed up as the next turn being expensive. It now asks first, in words, with no number: what it will cost depends on how long this conversation is, and the screen does not know that -- the running total beside it counts what has been spent, which is a different quantity, and a figure derived from it would be a guess wearing a measurement's clothes. The picker beside it deliberately gets no dialog, and that is measured rather than assumed. Driving one session through both changes and reading the CLI's own usage: a warm turn read 30,771 tokens from cache and created 87; after a *permission mode* change it read 30,858 and created 75 -- still a hit; after a *model* change it read nothing at all and created 41,509. So the model picker is the whole of the set, and warning on both would teach that these dialogs can be clicked through, which is what stops the one that matters from working. Nothing is asked when there is nothing to lose either: choosing the model already set, or switching before the session has said anything, applies straight through. Checked on the emulator. A draft survived leaving the session and a force-stop; the dialog names both models and both buttons; declining left the model where it was; and the permission picker still applies on the tap with no dialog in the way.
37 lines
1.8 KiB
Kotlin
37 lines
1.8 KiB
Kotlin
package com.example.aiapp
|
|
|
|
import android.content.Context
|
|
import androidx.core.content.edit
|
|
|
|
private const val DRAFTS = "session-drafts"
|
|
|
|
/**
|
|
* A message typed into a session and not sent yet.
|
|
*
|
|
* On this device rather than on the backend, which is where this app otherwise keeps state so that
|
|
* every device sees it. A draft is the case that rule is not about: it is the contents of a text
|
|
* box on the phone somebody is holding, written on every keystroke, and half a sentence surfacing
|
|
* on another device would be a surprise rather than a convenience. What has been *sent* is the
|
|
* server's, and that is the part which has to outlive this phone.
|
|
*
|
|
* Kept per session id, because the thing being typed belongs to the conversation it is aimed at:
|
|
* one shared box would hand a message meant for one session to whichever was opened next.
|
|
*/
|
|
fun loadDraft(context: Context, sessionId: String): String =
|
|
context.getSharedPreferences(DRAFTS, Context.MODE_PRIVATE).getString(sessionId, "").orEmpty()
|
|
|
|
/**
|
|
* Records [text] as the draft for [sessionId], or forgets it when there is nothing left to keep.
|
|
*
|
|
* The path out is emptying the box, which is what sending does -- so a sent message removes its own
|
|
* entry and nothing accumulates for a session in ordinary use. A session *deleted* while it held a
|
|
* draft does leave its key behind: pruning those means a pass over the live session list, which
|
|
* this file would otherwise have no reason to know about, and the residue is a few bytes per
|
|
* session ever abandoned mid-sentence. That is a trade rather than an oversight.
|
|
*/
|
|
fun saveDraft(context: Context, sessionId: String, text: String) {
|
|
context.getSharedPreferences(DRAFTS, Context.MODE_PRIVATE).edit {
|
|
if (text.isEmpty()) remove(sessionId) else putString(sessionId, text)
|
|
}
|
|
}
|