wire search mode, single-select pills, remove Standard pill
Mode pills are now single-select (tap to activate, tap again to deactivate) matching the web frontend behavior. Removed the "Standard" pill — it's the implicit default when no mode is active. Search mode sends webSearch: true in the ChatRequest body, which triggers the backend's agentive web search (openrouter:web_search or Google Search grounding). Sources and query are already parsed by StreamConsumer and rendered via SourcesBlock/SourceChip. Active pills now highlighted with accent color + accent border instead of generic surface alpha, making the active state visually obvious.
This commit is contained in:
parent
a16dfffd5c
commit
30d3f2807d
3 changed files with 19 additions and 14 deletions
|
|
@ -142,14 +142,10 @@ fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = M
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ModePillsRow(selected: Set<Int>, onToggle: (Int) -> Unit, modifier: Modifier = Modifier) {
|
||||
fun ModePillsRow(activeMode: Int?, onToggle: (Int) -> Unit, modifier: Modifier = Modifier) {
|
||||
val cs = MaterialTheme.colorScheme
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
val glassBorder = remember(isDark) {
|
||||
if (isDark) Brush.verticalGradient(listOf(Color.White.copy(alpha = 0.14f), Color.White.copy(alpha = 0.04f)))
|
||||
else Brush.verticalGradient(listOf(Color.White.copy(alpha = 0.50f), Color.Black.copy(alpha = 0.06f)))
|
||||
}
|
||||
val accent = LocalKaizenAccent.current
|
||||
|
||||
Row(
|
||||
modifier
|
||||
|
|
@ -158,25 +154,30 @@ fun ModePillsRow(selected: Set<Int>, onToggle: (Int) -> Unit, modifier: Modifier
|
|||
.padding(horizontal = 14.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
chatModes.forEach { mode ->
|
||||
chatModes.filter { it.labelRes != R.string.mode_standard }.forEach { mode ->
|
||||
val label = stringResource(mode.labelRes)
|
||||
val active = mode.labelRes in selected
|
||||
val active = mode.labelRes == activeMode
|
||||
val backgroundFill = if (active) {
|
||||
if (isDark) cs.surface.copy(alpha = 0.8f) else cs.surface.copy(alpha = 0.88f)
|
||||
accent.primary.copy(alpha = if (isDark) 0.18f else 0.14f)
|
||||
} else {
|
||||
if (isDark) cs.surface.copy(alpha = 0.35f) else cs.surface.copy(alpha = 0.55f)
|
||||
}
|
||||
val borderColor = if (active) {
|
||||
accent.primary.copy(alpha = 0.4f)
|
||||
} else {
|
||||
if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.clip(KaizenShapes.pill)
|
||||
.background(backgroundFill)
|
||||
.border(1.2.dp, glassBorder, KaizenShapes.pill)
|
||||
.border(1.2.dp, borderColor, KaizenShapes.pill)
|
||||
.clickable { onToggle(mode.labelRes) }
|
||||
.padding(horizontal = 13.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(mode.icon, null, tint = if (active) cs.onBackground else cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
|
||||
Icon(mode.icon, null, tint = if (active) accent.primary else cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(label, color = if (active) cs.onBackground else cs.onSurfaceVariant, fontSize = 13.sp, fontWeight = if (active) FontWeight.SemiBold else FontWeight.Medium)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ fun ChatScreen(
|
|||
var input by remember { mutableStateOf("") }
|
||||
var isStreaming by remember { mutableStateOf(false) }
|
||||
var nextId by remember { mutableStateOf(0L) }
|
||||
var selectedModes by remember { mutableStateOf(setOf(R.string.mode_standard)) }
|
||||
var activeMode by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
// Navigation State
|
||||
var currentScreen by remember { mutableStateOf(AppScreen.Chat) }
|
||||
|
|
@ -339,6 +339,7 @@ fun ChatScreen(
|
|||
KaizenApi.chat(
|
||||
cfg.baseUrl, cfg.token, cfg.model, history, cfg.speed,
|
||||
attachments = uploadedAtts.takeIf { it.isNotEmpty() },
|
||||
webSearch = activeMode == R.string.mode_search,
|
||||
).collect { state ->
|
||||
val idx = messages.indexOfLast { it.id == assistantId }
|
||||
if (idx < 0) return@collect
|
||||
|
|
@ -621,9 +622,9 @@ fun ChatScreen(
|
|||
) {
|
||||
Spacer(Modifier.height(24.dp))
|
||||
ModePillsRow(
|
||||
selected = selectedModes,
|
||||
activeMode = activeMode,
|
||||
onToggle = { mode ->
|
||||
selectedModes = if (mode in selectedModes) selectedModes - mode else selectedModes + mode
|
||||
activeMode = if (activeMode == mode) null else mode
|
||||
haptics.tick()
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit
|
|||
val reasoningEffort: String? = null,
|
||||
val preferThroughput: Boolean? = null,
|
||||
val attachments: List<Attachment>? = null,
|
||||
val webSearch: Boolean? = null,
|
||||
)
|
||||
|
||||
/** One entry from GET /api/v1/models — the picker's row. `provider == "vertex"` drives the hoster badge. */
|
||||
|
|
@ -440,6 +441,7 @@ object KaizenApi {
|
|||
messages: List<WireMessage>,
|
||||
speed: ChatSpeed = ChatSpeed.Normal,
|
||||
attachments: List<Attachment>? = null,
|
||||
webSearch: Boolean = false,
|
||||
): Flow<StreamState> = flow {
|
||||
val payload = json.encodeToString(
|
||||
ChatRequest(
|
||||
|
|
@ -448,6 +450,7 @@ object KaizenApi {
|
|||
reasoningEffort = speed.reasoningEffort,
|
||||
preferThroughput = if (speed.preferThroughput) true else null,
|
||||
attachments = attachments?.takeIf { it.isNotEmpty() },
|
||||
webSearch = if (webSearch) true else null,
|
||||
),
|
||||
).toRequestBody(jsonMedia)
|
||||
val req = Request.Builder()
|
||||
|
|
|
|||
Loading…
Reference in a new issue