Compare commits
2 commits
d289466276
...
fb6bc981d1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb6bc981d1 | ||
|
|
4150936b18 |
5 changed files with 79 additions and 18 deletions
|
|
@ -151,6 +151,9 @@ fun ModePillsRow(
|
||||||
onReasoningChange: (ReasoningPreset) -> Unit,
|
onReasoningChange: (ReasoningPreset) -> Unit,
|
||||||
samplingPrefs: SamplingPrefs,
|
samplingPrefs: SamplingPrefs,
|
||||||
onSamplingChange: (SamplingPrefs) -> Unit,
|
onSamplingChange: (SamplingPrefs) -> Unit,
|
||||||
|
currentModelId: String = "",
|
||||||
|
webSearchProvider: String = "google",
|
||||||
|
onWebSearchProviderChange: (String) -> Unit = {},
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val cs = MaterialTheme.colorScheme
|
val cs = MaterialTheme.colorScheme
|
||||||
|
|
@ -159,6 +162,8 @@ fun ModePillsRow(
|
||||||
|
|
||||||
var showReasoningMenu by remember { mutableStateOf(false) }
|
var showReasoningMenu by remember { mutableStateOf(false) }
|
||||||
var showSamplingPopup by remember { mutableStateOf(false) }
|
var showSamplingPopup by remember { mutableStateOf(false) }
|
||||||
|
var showSearchProviderMenu by remember { mutableStateOf(false) }
|
||||||
|
val isVertexModel = currentModelId.startsWith("vertex:")
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier
|
modifier
|
||||||
|
|
@ -224,19 +229,19 @@ fun ModePillsRow(
|
||||||
shadowElevation = 16.dp,
|
shadowElevation = 16.dp,
|
||||||
shape = KaizenShapes.md,
|
shape = KaizenShapes.md,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp).width(260.dp)) {
|
Column(Modifier.padding(horizontal = 14.dp, vertical = 10.dp).width(220.dp)) {
|
||||||
SamplingRow(
|
SamplingRow(
|
||||||
label = stringResource(R.string.sampling_temperature),
|
label = stringResource(R.string.sampling_temperature),
|
||||||
param = samplingPrefs.temperature, min = 0f, max = 2f, steps = 200,
|
param = samplingPrefs.temperature, min = 0f, max = 2f, steps = 200,
|
||||||
onUpdate = { onSamplingChange(samplingPrefs.copy(temperature = it)) },
|
onUpdate = { onSamplingChange(samplingPrefs.copy(temperature = it)) },
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(14.dp))
|
Spacer(Modifier.height(6.dp))
|
||||||
SamplingRow(
|
SamplingRow(
|
||||||
label = stringResource(R.string.sampling_top_p),
|
label = stringResource(R.string.sampling_top_p),
|
||||||
param = samplingPrefs.topP, min = 0f, max = 1f, steps = 100,
|
param = samplingPrefs.topP, min = 0f, max = 1f, steps = 100,
|
||||||
onUpdate = { onSamplingChange(samplingPrefs.copy(topP = it)) },
|
onUpdate = { onSamplingChange(samplingPrefs.copy(topP = it)) },
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(14.dp))
|
Spacer(Modifier.height(6.dp))
|
||||||
SamplingRow(
|
SamplingRow(
|
||||||
label = stringResource(R.string.sampling_top_k),
|
label = stringResource(R.string.sampling_top_k),
|
||||||
param = samplingPrefs.topK, min = 1f, max = 100f, steps = 99,
|
param = samplingPrefs.topK, min = 1f, max = 100f, steps = 99,
|
||||||
|
|
@ -255,6 +260,51 @@ fun ModePillsRow(
|
||||||
active = active,
|
active = active,
|
||||||
onClick = { onToggle(mode.labelRes) },
|
onClick = { onToggle(mode.labelRes) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Search provider dropdown — only for Vertex models when search is active
|
||||||
|
if (mode.labelRes == R.string.mode_search && active && isVertexModel) {
|
||||||
|
Box {
|
||||||
|
val providerLabel = stringResource(
|
||||||
|
if (webSearchProvider == "enterprise") R.string.search_provider_enterprise
|
||||||
|
else R.string.search_provider_google
|
||||||
|
)
|
||||||
|
ModePill(
|
||||||
|
icon = KaizenIcons.Globe,
|
||||||
|
label = providerLabel,
|
||||||
|
active = true,
|
||||||
|
showChevron = true,
|
||||||
|
onClick = { showSearchProviderMenu = true },
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showSearchProviderMenu,
|
||||||
|
onDismissRequest = { showSearchProviderMenu = false },
|
||||||
|
containerColor = if (isDark) Color(0xFF1A2030) else Color(0xFFF8F9FB),
|
||||||
|
shadowElevation = 16.dp,
|
||||||
|
shape = KaizenShapes.md,
|
||||||
|
) {
|
||||||
|
listOf("google" to R.string.search_provider_google, "enterprise" to R.string.search_provider_enterprise).forEach { (value, labelRes) ->
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onWebSearchProviderChange(value); showSearchProviderMenu = false }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(labelRes),
|
||||||
|
fontSize = 13.5.sp,
|
||||||
|
color = if (value == webSearchProvider) accent.primary else cs.onBackground.copy(alpha = 0.85f),
|
||||||
|
fontWeight = if (value == webSearchProvider) FontWeight.SemiBold else FontWeight.Normal,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
if (value == webSearchProvider) {
|
||||||
|
Icon(KaizenIcons.Check, null, tint = accent.primary, modifier = Modifier.size(15.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,22 +322,22 @@ private fun ModePill(
|
||||||
val accent = LocalKaizenAccent.current
|
val accent = LocalKaizenAccent.current
|
||||||
|
|
||||||
val bg = when {
|
val bg = when {
|
||||||
active -> accent.primary.copy(alpha = if (isDark) 0.15f else 0.10f)
|
active -> accent.primary.copy(alpha = if (isDark) 0.20f else 0.12f)
|
||||||
else -> if (isDark) Color.White.copy(alpha = 0.06f) else Color.Black.copy(alpha = 0.04f)
|
else -> if (isDark) Color.White.copy(alpha = 0.10f) else Color.Black.copy(alpha = 0.07f)
|
||||||
}
|
}
|
||||||
val borderBrush = if (active) {
|
val borderBrush = if (active) {
|
||||||
Brush.verticalGradient(listOf(
|
Brush.verticalGradient(listOf(
|
||||||
accent.primary.copy(alpha = if (isDark) 0.30f else 0.22f),
|
accent.primary.copy(alpha = if (isDark) 0.40f else 0.30f),
|
||||||
accent.primary.copy(alpha = if (isDark) 0.08f else 0.06f),
|
accent.primary.copy(alpha = if (isDark) 0.12f else 0.08f),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Brush.verticalGradient(listOf(
|
Brush.verticalGradient(listOf(
|
||||||
if (isDark) Color.White.copy(alpha = 0.08f) else Color.White.copy(alpha = 0.40f),
|
if (isDark) Color.White.copy(alpha = 0.14f) else Color.Black.copy(alpha = 0.10f),
|
||||||
if (isDark) Color.White.copy(alpha = 0.02f) else Color.Black.copy(alpha = 0.03f),
|
if (isDark) Color.White.copy(alpha = 0.05f) else Color.Black.copy(alpha = 0.04f),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
val iconTint = if (active) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.60f)
|
val iconTint = if (active) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.75f)
|
||||||
val textColor = if (active) cs.onBackground else cs.onSurfaceVariant.copy(alpha = 0.70f)
|
val textColor = if (active) cs.onBackground else cs.onSurfaceVariant.copy(alpha = 0.80f)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -317,7 +367,7 @@ private fun SamplingRow(label: String, param: SamplingParam, min: Float, max: Fl
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.size(20.dp)
|
.size(16.dp)
|
||||||
.clip(KaizenShapes.xs)
|
.clip(KaizenShapes.xs)
|
||||||
.border(
|
.border(
|
||||||
1.dp,
|
1.dp,
|
||||||
|
|
@ -329,18 +379,17 @@ private fun SamplingRow(label: String, param: SamplingParam, min: Float, max: Fl
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
if (param.enabled) {
|
if (param.enabled) {
|
||||||
Icon(KaizenIcons.Check, null, tint = accent.primary, modifier = Modifier.size(13.dp))
|
Icon(KaizenIcons.Check, null, tint = accent.primary, modifier = Modifier.size(11.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text(label, color = cs.onBackground.copy(alpha = if (param.enabled) 0.90f else 0.50f), fontSize = 13.sp, modifier = Modifier.weight(1f))
|
Text(label, color = cs.onBackground.copy(alpha = if (param.enabled) 0.90f else 0.50f), fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
if (max > 2f) param.value.toInt().toString() else "%.2f".format(param.value),
|
if (max > 2f) param.value.toInt().toString() else "%.2f".format(param.value),
|
||||||
color = if (param.enabled) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.40f),
|
color = if (param.enabled) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.40f),
|
||||||
fontSize = 12.sp, fontWeight = FontWeight.Medium,
|
fontSize = 11.sp, fontWeight = FontWeight.Medium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
Slider(
|
Slider(
|
||||||
value = param.value, onValueChange = { onUpdate(param.copy(value = it)) },
|
value = param.value, onValueChange = { onUpdate(param.copy(value = it)) },
|
||||||
valueRange = min..max, steps = steps,
|
valueRange = min..max, steps = steps,
|
||||||
|
|
@ -353,7 +402,7 @@ private fun SamplingRow(label: String, param: SamplingParam, min: Float, max: Fl
|
||||||
disabledActiveTrackColor = cs.onSurfaceVariant.copy(alpha = 0.15f),
|
disabledActiveTrackColor = cs.onSurfaceVariant.copy(alpha = 0.15f),
|
||||||
disabledInactiveTrackColor = if (isDark) Color.White.copy(alpha = 0.05f) else Color.Black.copy(alpha = 0.04f),
|
disabledInactiveTrackColor = if (isDark) Color.White.copy(alpha = 0.05f) else Color.Black.copy(alpha = 0.04f),
|
||||||
),
|
),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().height(28.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ fun ChatScreen(
|
||||||
var activeMode by remember { mutableStateOf<Int?>(null) }
|
var activeMode by remember { mutableStateOf<Int?>(null) }
|
||||||
var reasoningPreset by remember { mutableStateOf(ReasoningPreset.Standard) }
|
var reasoningPreset by remember { mutableStateOf(ReasoningPreset.Standard) }
|
||||||
var samplingPrefs by remember { mutableStateOf(SamplingPrefs()) }
|
var samplingPrefs by remember { mutableStateOf(SamplingPrefs()) }
|
||||||
|
var webSearchProvider by remember { mutableStateOf("google") }
|
||||||
var usedTokens by remember { mutableStateOf(0) }
|
var usedTokens by remember { mutableStateOf(0) }
|
||||||
var chatModel by remember { mutableStateOf<String?>(null) }
|
var chatModel by remember { mutableStateOf<String?>(null) }
|
||||||
var streamJob by remember { mutableStateOf<Job?>(null) }
|
var streamJob by remember { mutableStateOf<Job?>(null) }
|
||||||
|
|
@ -584,6 +585,7 @@ fun ChatScreen(
|
||||||
preferThroughput = reasoningPreset.throughput,
|
preferThroughput = reasoningPreset.throughput,
|
||||||
attachments = uploadedAtts.takeIf { it.isNotEmpty() },
|
attachments = uploadedAtts.takeIf { it.isNotEmpty() },
|
||||||
webSearch = activeMode == R.string.mode_search,
|
webSearch = activeMode == R.string.mode_search,
|
||||||
|
webSearchProvider = if (activeMode == R.string.mode_search && cfg.model.startsWith("vertex:")) webSearchProvider else null,
|
||||||
temperature = samplingPrefs.temperature.takeIf { it.enabled }?.value,
|
temperature = samplingPrefs.temperature.takeIf { it.enabled }?.value,
|
||||||
topP = samplingPrefs.topP.takeIf { it.enabled }?.value,
|
topP = samplingPrefs.topP.takeIf { it.enabled }?.value,
|
||||||
topK = samplingPrefs.topK.takeIf { it.enabled }?.value?.toInt(),
|
topK = samplingPrefs.topK.takeIf { it.enabled }?.value?.toInt(),
|
||||||
|
|
@ -1055,6 +1057,9 @@ fun ChatScreen(
|
||||||
onReasoningChange = { reasoningPreset = it; haptics.tick() },
|
onReasoningChange = { reasoningPreset = it; haptics.tick() },
|
||||||
samplingPrefs = samplingPrefs,
|
samplingPrefs = samplingPrefs,
|
||||||
onSamplingChange = { samplingPrefs = it },
|
onSamplingChange = { samplingPrefs = it },
|
||||||
|
currentModelId = session.config?.model ?: "",
|
||||||
|
webSearchProvider = webSearchProvider,
|
||||||
|
onWebSearchProviderChange = { webSearchProvider = it; haptics.tick() },
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(if (imeVisible) 6.dp else 10.dp))
|
Spacer(Modifier.height(if (imeVisible) 6.dp else 10.dp))
|
||||||
ChatInput(
|
ChatInput(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit
|
||||||
val preferThroughput: Boolean? = null,
|
val preferThroughput: Boolean? = null,
|
||||||
val attachments: List<Attachment>? = null,
|
val attachments: List<Attachment>? = null,
|
||||||
val webSearch: Boolean? = null,
|
val webSearch: Boolean? = null,
|
||||||
|
val webSearchProvider: String? = null,
|
||||||
val temperature: Float? = null,
|
val temperature: Float? = null,
|
||||||
val topP: Float? = null,
|
val topP: Float? = null,
|
||||||
val topK: Int? = null,
|
val topK: Int? = null,
|
||||||
|
|
@ -487,6 +488,7 @@ object KaizenApi {
|
||||||
preferThroughput: Boolean = false,
|
preferThroughput: Boolean = false,
|
||||||
attachments: List<Attachment>? = null,
|
attachments: List<Attachment>? = null,
|
||||||
webSearch: Boolean = false,
|
webSearch: Boolean = false,
|
||||||
|
webSearchProvider: String? = null,
|
||||||
temperature: Float? = null,
|
temperature: Float? = null,
|
||||||
topP: Float? = null,
|
topP: Float? = null,
|
||||||
topK: Int? = null,
|
topK: Int? = null,
|
||||||
|
|
@ -499,6 +501,7 @@ object KaizenApi {
|
||||||
preferThroughput = if (preferThroughput) true else null,
|
preferThroughput = if (preferThroughput) true else null,
|
||||||
attachments = attachments?.takeIf { it.isNotEmpty() },
|
attachments = attachments?.takeIf { it.isNotEmpty() },
|
||||||
webSearch = if (webSearch) true else null,
|
webSearch = if (webSearch) true else null,
|
||||||
|
webSearchProvider = if (webSearch) webSearchProvider else null,
|
||||||
temperature = temperature,
|
temperature = temperature,
|
||||||
topP = topP,
|
topP = topP,
|
||||||
topK = topK,
|
topK = topK,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@
|
||||||
<string name="mode_search">Search</string>
|
<string name="mode_search">Search</string>
|
||||||
<string name="mode_video">Video</string>
|
<string name="mode_video">Video</string>
|
||||||
<string name="mode_audio">Audio</string>
|
<string name="mode_audio">Audio</string>
|
||||||
|
<string name="search_provider_google">Google</string>
|
||||||
|
<string name="search_provider_enterprise">Enterprise</string>
|
||||||
|
|
||||||
<!-- Suggestion pills -->
|
<!-- Suggestion pills -->
|
||||||
<string name="suggest_brainstorm">Brainstorm</string>
|
<string name="suggest_brainstorm">Brainstorm</string>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@
|
||||||
<string name="mode_search">Suche</string>
|
<string name="mode_search">Suche</string>
|
||||||
<string name="mode_video">Video</string>
|
<string name="mode_video">Video</string>
|
||||||
<string name="mode_audio">Audio</string>
|
<string name="mode_audio">Audio</string>
|
||||||
|
<string name="search_provider_google">Google</string>
|
||||||
|
<string name="search_provider_enterprise">Enterprise</string>
|
||||||
|
|
||||||
<!-- Suggestion pills -->
|
<!-- Suggestion pills -->
|
||||||
<string name="suggest_brainstorm">Brainstormen</string>
|
<string name="suggest_brainstorm">Brainstormen</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue