Auto-search detection + live reasoning preview
Auto-search: - detectSearchQuery() pattern-matches common search prompts in DE+EN (weather, news, prices, scores, current events, etc.) - send() enables webSearch automatically when detected, no manual pill needed. Manual pill still works as explicit override. - 30+ patterns ported from backend lib/chat/detection.ts Live reasoning preview: - ReasoningBlock shows first meaningful line as preview label when collapsed (max 80 chars, ellipsis). Updates live during streaming. - "Gedankengang" header + preview line stacked vertically. - Preview hidden when block is expanded (full text visible). Tests: 4 new tests for detectSearchQuery (DE patterns, EN patterns, normal prompts not triggered, short input ignored).
This commit is contained in:
parent
77d2d8e5df
commit
feb326fc40
4 changed files with 95 additions and 9 deletions
|
|
@ -65,6 +65,24 @@ val chatModes = listOf(
|
|||
ChatMode(R.string.mode_audio, KaizenIcons.Music),
|
||||
)
|
||||
|
||||
private val SEARCH_PATTERNS = listOf(
|
||||
"such nach", "suche nach", "was ist aktuell", "aktuelle", "neueste",
|
||||
"wie ist das wetter", "wetter in", "was passiert gerade", "was ist heute",
|
||||
"aktuelle news", "nachrichten", "was kostet", "aktienkurs", "kurs von",
|
||||
"wer hat gewonnen", "ergebnis von", "stand von", "tabelle von",
|
||||
"öffnungszeiten", "wie spät", "uhrzeit in", "einwohner von",
|
||||
"search for", "look up", "find me", "what is happening",
|
||||
"latest news", "current events", "weather in", "weather today",
|
||||
"stock price", "who won", "what happened", "how much is",
|
||||
"when did", "when was", "who is the current", "what are the latest",
|
||||
"score of", "results of", "opening hours", "population of",
|
||||
).map { Regex(it, RegexOption.IGNORE_CASE) }
|
||||
|
||||
fun detectSearchQuery(text: String): Boolean {
|
||||
if (text.length < 8) return false
|
||||
return SEARCH_PATTERNS.any { it.containsMatchIn(text) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun greeting(hour: Int): String = when (hour) {
|
||||
in 5..11 -> stringResource(R.string.greeting_morning)
|
||||
|
|
|
|||
|
|
@ -255,13 +255,17 @@ class ChatScreenViewModel(
|
|||
async { KaizenApi.createConversation(cfg.baseUrl, cfg.token) }
|
||||
} else null
|
||||
|
||||
val searchManual = activeMode == R.string.mode_search
|
||||
val searchAuto = !searchManual && detectSearchQuery(trimmed)
|
||||
val useSearch = searchManual || searchAuto
|
||||
|
||||
KaizenApi.chat(
|
||||
cfg.baseUrl, cfg.token, cfg.model, history,
|
||||
reasoningEffort = reasoningPreset.effort,
|
||||
preferThroughput = reasoningPreset.throughput,
|
||||
attachments = uploadedAtts.takeIf { it.isNotEmpty() },
|
||||
webSearch = activeMode == R.string.mode_search,
|
||||
webSearchProvider = if (activeMode == R.string.mode_search && cfg.model.startsWith("vertex:")) webSearchProvider else null,
|
||||
webSearch = useSearch,
|
||||
webSearchProvider = if (useSearch && cfg.model.startsWith("vertex:")) webSearchProvider else null,
|
||||
temperature = samplingPrefs.temperature.takeIf { it.enabled }?.value,
|
||||
topP = samplingPrefs.topP.takeIf { it.enabled }?.value,
|
||||
topK = samplingPrefs.topK.takeIf { it.enabled }?.value?.toInt(),
|
||||
|
|
|
|||
|
|
@ -54,6 +54,14 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
|
|||
val accent = LocalKaizenAccent.current
|
||||
var expanded by remember { mutableStateOf(streaming) }
|
||||
|
||||
val previewLine = remember(reasoning) {
|
||||
reasoning.lineSequence()
|
||||
.map { it.trim() }
|
||||
.firstOrNull { it.length > 3 }
|
||||
?.take(80)
|
||||
?.let { if (it.length == 80) "$it…" else it }
|
||||
}
|
||||
|
||||
val bg = accent.primary.copy(alpha = if (isDark) 0.06f else 0.04f)
|
||||
val borderBrush = remember(isDark, accent) {
|
||||
Brush.verticalGradient(
|
||||
|
|
@ -80,13 +88,24 @@ fun ReasoningBlock(reasoning: String, streaming: Boolean = false) {
|
|||
) {
|
||||
Icon(KaizenIcons.Brain, null, tint = accent.primary.copy(alpha = 0.75f), modifier = Modifier.size(17.dp))
|
||||
Spacer(Modifier.width(9.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.stream_reasoning),
|
||||
color = cs.onSurfaceVariant.copy(alpha = 0.75f),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (!expanded && previewLine != null) {
|
||||
Text(
|
||||
previewLine,
|
||||
color = cs.onSurfaceVariant.copy(alpha = 0.40f),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.W300,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
if (expanded) KaizenIcons.ChevronUp else KaizenIcons.ChevronDown,
|
||||
null, tint = cs.onSurfaceVariant.copy(alpha = 0.40f), modifier = Modifier.size(15.dp),
|
||||
|
|
|
|||
45
app/src/test/java/dev/kaizen/app/DetectSearchTest.kt
Normal file
45
app/src/test/java/dev/kaizen/app/DetectSearchTest.kt
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package dev.kaizen.app
|
||||
|
||||
import dev.kaizen.app.chat.detectSearchQuery
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class DetectSearchTest {
|
||||
|
||||
@Test
|
||||
fun detectsGermanSearchPatterns() {
|
||||
assertTrue(detectSearchQuery("Was ist aktuell in der Politik los?"))
|
||||
assertTrue(detectSearchQuery("Suche nach den neuesten Nachrichten"))
|
||||
assertTrue(detectSearchQuery("Wie ist das Wetter in Berlin?"))
|
||||
assertTrue(detectSearchQuery("Was kostet ein Tesla Model 3?"))
|
||||
assertTrue(detectSearchQuery("Aktuelle News zu Apple"))
|
||||
assertTrue(detectSearchQuery("Aktienkurs von Nvidia"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun detectsEnglishSearchPatterns() {
|
||||
assertTrue(detectSearchQuery("Search for the latest news about AI"))
|
||||
assertTrue(detectSearchQuery("What is happening in Ukraine?"))
|
||||
assertTrue(detectSearchQuery("Weather in Tokyo today"))
|
||||
assertTrue(detectSearchQuery("Who won the Champions League?"))
|
||||
assertTrue(detectSearchQuery("Stock price of Apple"))
|
||||
assertTrue(detectSearchQuery("Look up population of Germany"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doesNotTriggerOnNormalPrompts() {
|
||||
assertFalse(detectSearchQuery("Erkläre mir Quantencomputing"))
|
||||
assertFalse(detectSearchQuery("Schreib mir ein Gedicht"))
|
||||
assertFalse(detectSearchQuery("Was ist der Unterschied zwischen Java und Kotlin?"))
|
||||
assertFalse(detectSearchQuery("Fasse diesen Text zusammen"))
|
||||
assertFalse(detectSearchQuery("Hello"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shortInputIsIgnored() {
|
||||
assertFalse(detectSearchQuery("hi"))
|
||||
assertFalse(detectSearchQuery(""))
|
||||
assertFalse(detectSearchQuery("abc"))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue