@@ -50,6 +50,12 @@ import androidx.compose.runtime.rememberCoroutineScope
50import androidx.compose.runtime.setValue
50import androidx.compose.runtime.setValue
51import androidx.compose.ui.Alignment
51import androidx.compose.ui.Alignment
52import androidx.compose.ui.Modifier
52import androidx.compose.ui.Modifier
53+import androidx.compose.ui.input.key.Key
54+import androidx.compose.ui.input.key.KeyEventType
55+import androidx.compose.ui.input.key.isShiftPressed
56+import androidx.compose.ui.input.key.key
57+import androidx.compose.ui.input.key.onPreviewKeyEvent
58+import androidx.compose.ui.input.key.type
53import androidx.compose.ui.platform.LocalHapticFeedback
59import androidx.compose.ui.platform.LocalHapticFeedback
54import androidx.compose.ui.unit.dp
60import androidx.compose.ui.unit.dp
55import androidx.lifecycle.compose.collectAsStateWithLifecycle
61import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -79,12 +85,12 @@ fun ChatScreen(
79 val commands by vm.commands.collectAsStateWithLifecycle()
85 val commands by vm.commands.collectAsStateWithLifecycle()
80 val sessionTitle by vm.sessionTitle.collectAsStateWithLifecycle()
86 val sessionTitle by vm.sessionTitle.collectAsStateWithLifecycle()
81 val failedDraft by vm.failedDraft.collectAsStateWithLifecycle()
87 val failedDraft by vm.failedDraft.collectAsStateWithLifecycle()
88+ val draft by vm.draft.collectAsStateWithLifecycle()
82 val haptics = LocalHapticFeedback.current
89 val haptics = LocalHapticFeedback.current
83 val scope = rememberCoroutineScope()
90 val scope = rememberCoroutineScope()
85 val listState = rememberLazyListState()
92 val listState = rememberLazyListState()
86 val snackbar = remember { SnackbarHostState() }
93 val snackbar = remember { SnackbarHostState() }
87− var input by remember { mutableStateOf("") }
88 var showModelPicker by remember { mutableStateOf(false) }
94 var showModelPicker by remember { mutableStateOf(false) }
89 var showAgentPicker by remember { mutableStateOf(false) }
95 var showAgentPicker by remember { mutableStateOf(false) }
90 var showCommandPicker by remember { mutableStateOf(false) }
96 var showCommandPicker by remember { mutableStateOf(false) }
@@ -117,8 +123,7 @@ fun ChatScreen(
119 fun doSend() {
125 fun doSend() {
120− if (vm.send(input)) {
126+ if (vm.send(draft)) {
122 haptics.performHapticFeedback(androidx.compose.ui.hapticfeedback.HapticFeedbackType.LongPress)
127 haptics.performHapticFeedback(androidx.compose.ui.hapticfeedback.HapticFeedbackType.LongPress)
@@ -164,8 +169,8 @@ fun ChatScreen(
164 snackbarHost = { SnackbarHost(snackbar) },
169 snackbarHost = { SnackbarHost(snackbar) },
165 bottomBar = {
170 bottomBar = {
166 ChatInputBar(
171 ChatInputBar(
167− value = input,
172+ value = draft,
168− onValueChange = { input = it },
173+ onValueChange = vm::updateDraft,
169 running = running,
174 running = running,
170 enabled = vm.connected,
175 enabled = vm.connected,
171 onSend = ::doSend,
176 onSend = ::doSend,
@@ -323,7 +328,20 @@ private fun ChatInputBar(
323 OutlinedTextField(
328 OutlinedTextField(
324 value = value,
329 value = value,
325 onValueChange = onValueChange,
330 onValueChange = onValueChange,
326− modifier = Modifier.weight(1f),
331+ modifier = Modifier
333+ .onPreviewKeyEvent { event ->
334+ if (event.type == KeyEventType.KeyDown &&
335+ event.key == Key.Enter &&
336+ !event.isShiftPressed &&
337+ enabled && value.isNotBlank()
327 placeholder = { Text("Message opencode…") },
345 placeholder = { Text("Message opencode…") },
328 enabled = enabled,
346 enabled = enabled,
329 maxLines = 6,
347 maxLines = 6,