- Auto-threading (Telegram topic-mode workflow): message.send {auto_thread}
mints a fresh AI-named thread (instant derived title, LLM upgrade via
channel.renamed); channel.created {auto:true}; the app jumps into the new
thread and relocates the optimistic pending bubble.
- history frame: paged full message history for initial channel open /
scroll-up pagination (reconstructed from the outbox log).
- Streaming on/off: gateway side (display.platforms.android.streaming) plus a
per-device app toggle (Settings → Streaming); reasoning/model/tokens carried
on message frames.
- Context menu: long-press (touch) / right-click (desktop) thread affordances
via a KMP rightClick expect/actual.
- Settings → Reasoning: auto-collapse long reasoning blocks (default on).
- Tool detail: the gateway now always supplies full tool data — it forces
verbose tool progress (full args → tool.start.args) and captures each
completed call via the post_tool_call hook (output/duration/ok → tool.end).
The app reveals the full call + output on expand (Truncated) and
auto-expands cards in Everything mode.
71 lines
2.6 KiB
Kotlin
71 lines
2.6 KiB
Kotlin
package iris
|
|
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.material3.Surface
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.DisposableEffect
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.collectAsState
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.ui.Modifier
|
|
import iris.data.SecureStore
|
|
import iris.net.GatewayClient
|
|
import iris.platform.setActiveController
|
|
import iris.state.IrisController
|
|
import iris.ui.screens.ChatScreen
|
|
import iris.ui.screens.ConnectScreen
|
|
import iris.ui.theme.IrisColors
|
|
import iris.ui.theme.IrisTheme
|
|
|
|
/**
|
|
* Root composable shared by the Android and Desktop shells.
|
|
*
|
|
* M1: routes between the Connect screen (unpaired / auth failed) and the
|
|
* Chat screen (paired). Later milestones add the channel list, search,
|
|
* settings, and media (docs/10-android-app.md).
|
|
*/
|
|
@Composable
|
|
fun IrisApp(
|
|
store: SecureStore,
|
|
deepLinkChatId: String? = null,
|
|
deepLinkThreadId: String? = null,
|
|
) {
|
|
val controller = remember(store) { IrisController(store) }
|
|
DisposableEffect(controller) {
|
|
setActiveController(controller)
|
|
onDispose {
|
|
setActiveController(null)
|
|
controller.dispose()
|
|
}
|
|
}
|
|
// M5: open the chat referenced by a push notification tap (applied now or
|
|
// on the next hello.ack, whichever is later).
|
|
LaunchedEffect(deepLinkChatId, deepLinkThreadId) {
|
|
if (!deepLinkChatId.isNullOrBlank()) {
|
|
controller.openDeepLink(deepLinkChatId, deepLinkThreadId)
|
|
}
|
|
}
|
|
val state by controller.client.state.collectAsState()
|
|
|
|
IrisTheme {
|
|
Surface(modifier = Modifier.background(IrisColors.background)) {
|
|
val s = state
|
|
when (s) {
|
|
GatewayClient.State.Disconnected ->
|
|
ConnectScreen(controller, prefillUrl = store.serverUrl, prefillToken = store.token)
|
|
is GatewayClient.State.AuthFailed ->
|
|
ConnectScreen(
|
|
controller,
|
|
prefillUrl = store.serverUrl,
|
|
prefillToken = store.token,
|
|
initialError = "Pairing rejected: ${s.message}",
|
|
)
|
|
// Connecting / Reconnecting / Connected all render the chat; the header
|
|
// StatusChip + connection banner show the link state without
|
|
// blocking the view (M7's full-screen spinner is gone).
|
|
else -> ChatScreen(controller)
|
|
}
|
|
}
|
|
}
|
|
} |