feat: wire ChatMode.STEERING toggle in TUI input bar
- Add ChatMode.STEERING toggle via ctrl+s keybinding - Add KeyEvent.ToggleSteeringMode, Action.CycleChatMode - TambouiKeyMapper: map ctrl+s to ToggleSteeringMode - KeyResolver: route ToggleSteeringMode to CycleChatMode (global) - InputReducer: toggle chatMode between CHAT and STEERING - TuiState: add chatMode field (default CHAT) - SessionsReducer: use ctx.chatMode instead of hardcoded CHAT - RootReducer: pass afterInput.chatMode to SessionsReducerContext - InputBar: show blue 'chat' or magenta 'steer' label in status line Activation: ctrl+s toggles mode, 'steer' label appears, submitted ChatInput uses ChatMode.STEERING → router emits SteeringNoteAddedEvent → context builder folds into next stage's context pack
This commit is contained in:
@@ -20,4 +20,5 @@ sealed class KeyEvent {
|
||||
object CursorLeft : KeyEvent()
|
||||
object CursorRight : KeyEvent()
|
||||
object ToggleWorkflows : KeyEvent()
|
||||
object ToggleSteeringMode : KeyEvent()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ private fun promptStyle(inputMode: InputMode): Style = when (inputMode) {
|
||||
InputMode.FILTER -> Style.create().yellow()
|
||||
}
|
||||
|
||||
private fun chatModeLabel(state: TuiState): Span = when (state.chatMode) {
|
||||
com.correx.core.router.ChatMode.CHAT -> Span.styled("chat", Style.create().blue())
|
||||
com.correx.core.router.ChatMode.STEERING -> Span.styled("steer", Style.create().magenta())
|
||||
}
|
||||
|
||||
private fun cursorLine(buffer: String, cursor: Int, inputMode: InputMode): Line {
|
||||
val prompt = Span.styled("▌", promptStyle(inputMode))
|
||||
if (buffer.isEmpty()) return Line.from(prompt, Span.raw(""))
|
||||
@@ -77,7 +82,9 @@ private fun createRowsForIdleRouter(
|
||||
}
|
||||
return row1 to Line.from(
|
||||
Span.styled(sessionName, Style.create().cyan()),
|
||||
Span.styled(" · router ", dimStyle),
|
||||
Span.styled(" · ", dimStyle),
|
||||
chatModeLabel(state),
|
||||
Span.styled(" ", dimStyle),
|
||||
Span.styled("tab", Style.create().blue()),
|
||||
Span.styled(" filter ", dimStyle),
|
||||
Span.styled("ctrl+n", Style.create().blue()),
|
||||
@@ -101,7 +108,9 @@ private fun createRowsForInSessionRouter(
|
||||
}
|
||||
return row1 to Line.from(
|
||||
Span.styled(sessionName, Style.create().cyan()),
|
||||
Span.styled(" · router ", dimStyle),
|
||||
Span.styled(" · ", dimStyle),
|
||||
chatModeLabel(state),
|
||||
Span.styled(" ", dimStyle),
|
||||
Span.styled("tab", Style.create().blue()),
|
||||
Span.styled(" filter ", dimStyle),
|
||||
Span.styled("↑↓", Style.create().blue()),
|
||||
|
||||
@@ -27,5 +27,6 @@ sealed interface Action {
|
||||
data object CursorLeft : Action
|
||||
data object CursorRight : Action
|
||||
data object ToggleWorkflows : Action
|
||||
data object CycleChatMode : Action
|
||||
data object NoOp : Action
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ object KeyResolver {
|
||||
KeyEvent.Quit -> Action.Quit
|
||||
KeyEvent.Approve -> Action.ApproveActive
|
||||
KeyEvent.Reject -> Action.RejectActive
|
||||
KeyEvent.ToggleSteeringMode -> Action.CycleChatMode
|
||||
else -> null
|
||||
}
|
||||
if (global != null) return global
|
||||
|
||||
@@ -24,6 +24,7 @@ fun mapKey(event: TambouiKeyEvent): KeyEvent? {
|
||||
event.isChar('e') -> KeyEvent.ToggleEventStrip
|
||||
event.isChar('h') -> KeyEvent.ShowPendingApproval
|
||||
event.isChar('w') -> KeyEvent.ToggleWorkflows
|
||||
event.isChar('s') -> KeyEvent.ToggleSteeringMode
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.correx.apps.tui.state.InputMode
|
||||
import com.correx.apps.tui.state.TuiState
|
||||
import com.correx.apps.tui.state.selectedPendingApproval
|
||||
import com.correx.core.events.types.ApprovalRequestId
|
||||
import com.correx.core.router.ChatMode
|
||||
|
||||
object InputReducer {
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@@ -21,6 +22,13 @@ object InputReducer {
|
||||
},
|
||||
) to emptyList()
|
||||
|
||||
is Action.CycleChatMode -> state.copy(
|
||||
chatMode = when (state.chatMode) {
|
||||
ChatMode.CHAT -> ChatMode.STEERING
|
||||
ChatMode.STEERING -> ChatMode.CHAT
|
||||
},
|
||||
) to emptyList()
|
||||
|
||||
is Action.CursorLeft -> state.copy(
|
||||
inputCursor = (state.inputCursor - 1).coerceAtLeast(0),
|
||||
) to emptyList()
|
||||
|
||||
@@ -69,6 +69,7 @@ object RootReducer {
|
||||
inputText = prevInputBuffer,
|
||||
action = action,
|
||||
clock = clock,
|
||||
chatMode = afterInput.chatMode,
|
||||
),
|
||||
)
|
||||
log.debug("connection state before reducers={}", state.connection)
|
||||
|
||||
@@ -29,6 +29,7 @@ data class SessionsReducerContext(
|
||||
val inputText: String,
|
||||
val action: Action,
|
||||
val clock: () -> Long = System::currentTimeMillis,
|
||||
val chatMode: ChatMode = ChatMode.CHAT,
|
||||
)
|
||||
|
||||
object SessionsReducer {
|
||||
@@ -89,7 +90,7 @@ object SessionsReducer {
|
||||
ClientMessage.ChatInput(
|
||||
sessionId = SessionId(sid),
|
||||
text = ctx.inputText,
|
||||
mode = ChatMode.CHAT,
|
||||
mode = ctx.chatMode,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.correx.apps.tui.state
|
||||
|
||||
import com.correx.apps.server.protocol.ApprovalDecision
|
||||
import com.correx.apps.server.protocol.ServerMessage
|
||||
import com.correx.core.router.ChatMode
|
||||
|
||||
enum class InputMode { ROUTER, FILTER }
|
||||
|
||||
@@ -46,6 +47,7 @@ data class TuiState(
|
||||
val providerType: ProviderType = ProviderType.LOCAL,
|
||||
val routerConnected: Boolean = false,
|
||||
val routerMessages: Map<String, List<String>> = emptyMap(),
|
||||
val chatMode: ChatMode = ChatMode.CHAT,
|
||||
/**
|
||||
* True from connect until SnapshotComplete observed. Reset to true on Disconnected.
|
||||
* When true, event-bearing messages are buffered in [pendingEvents]; snapshots are applied immediately.
|
||||
|
||||
Reference in New Issue
Block a user