feat: web search provider picker for Vertex models (Google vs Enterprise)
When search mode is active and a vertex: model is selected, a second pill appears next to "Suche" with a dropdown to choose between Google Search (default) and Enterprise Web Search grounding. The selection is sent as webSearchProvider in the chat request body. Matches the web frontend's OptionPill for webSearchProvider. Only visible for Vertex models — OpenRouter uses agentive web_search server-tool which has no provider toggle.
This commit is contained in:
parent
4150936b18
commit
fb6bc981d1
5 changed files with 62 additions and 0 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
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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