Skip to content

Commit 917ae92

Browse files
Fix scrollbar bounds and loading-state stop button behavior.
Prevents VerticalScrollbar crashes on invalid coerceIn ranges and makes stop/loading UI react immediately while preserving Mistral 429 fallback retries.
1 parent 007cc00 commit 917ae92

2 files changed

Lines changed: 38 additions & 12 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,10 @@ fun PhotoReasoningScreen(
506506
}
507507

508508
val isGemma = modelName == "gemma-3n-e4b-it"
509-
val showStopButton = isGenerationRunning || isOfflineGpuModelLoaded || isGemma
510-
val stopButtonText = if (isGenerationRunning) "Stop" else "Model Unload"
511-
val showTextFieldRow = !isGenerationRunning || isInitializingOfflineModel
509+
val isLoading = uiState is PhotoReasoningUiState.Loading
510+
val showStopButton = isGenerationRunning || isLoading || isOfflineGpuModelLoaded || isGemma
511+
val stopButtonText = if (isGenerationRunning || isLoading) "Stop" else "Model Unload"
512+
val showTextFieldRow = (!isGenerationRunning && !isLoading) || isInitializingOfflineModel
512513

513514
if (showTextFieldRow) {
514515
Card(modifier = Modifier.fillMaxWidth()) {
@@ -1371,19 +1372,23 @@ fun VerticalScrollbar(
13711372

13721373
if (visibleItemCount >= totalItems) return@derivedStateOf null // All items visible, no scrollbar
13731374

1374-
// Simple ratio-based calculation for even behavior
1375-
val thumbHeight = (visibleItemCount.toFloat() / totalItems * viewportHeight).coerceAtLeast(20f)
1376-
1377-
// Calculate scroll progress considering sub-item offset
1375+
val thumbHeight = (visibleItemCount.toFloat() / totalItems * viewportHeight)
1376+
.coerceAtLeast(20f)
1377+
.coerceAtMost(viewportHeight)
1378+
1379+
val maxScrollOffset = (viewportHeight - thumbHeight).coerceAtLeast(0f)
1380+
if (maxScrollOffset == 0f) return@derivedStateOf null
1381+
13781382
val firstItemOffset = if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
13791383
val firstItem = layoutInfo.visibleItemsInfo.first()
13801384
if (firstItem.size > 0) listState.firstVisibleItemScrollOffset.toFloat() / firstItem.size else 0f
13811385
} else 0f
1382-
1383-
val scrollProgress = (firstVisibleItemIndex + firstItemOffset) / (totalItems - visibleItemCount).coerceAtLeast(1)
1384-
val scrollOffset = scrollProgress * (viewportHeight - thumbHeight)
13851386

1386-
Pair(scrollOffset.coerceIn(0f, viewportHeight - thumbHeight), thumbHeight)
1387+
val scrollProgress = (firstVisibleItemIndex + firstItemOffset) /
1388+
(totalItems - visibleItemCount).coerceAtLeast(1)
1389+
val scrollOffset = (scrollProgress * maxScrollOffset).coerceIn(0f, maxScrollOffset)
1390+
1391+
Pair(scrollOffset, thumbHeight)
13871392
}
13881393
}
13891394

app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1174,13 +1174,34 @@ private fun reasonWithMistral(
11741174
apiKeyManager.markKeyAsFailed(currentKey, ApiProvider.MISTRAL)
11751175
val nextKey = apiKeyManager.switchToNextAvailableKey(ApiProvider.MISTRAL)
11761176
if (nextKey != null && nextKey != currentKey) {
1177+
// Anderer Key verfugbar -> sofort wechseln wie bisher
11771178
currentKey = nextKey
11781179
val elapsed2 = System.currentTimeMillis() - lastMistralRequestTimeMs
11791180
if (elapsed2 < MISTRAL_MIN_INTERVAL_MS) delay(MISTRAL_MIN_INTERVAL_MS - elapsed2)
11801181
response = client.newCall(buildRequest(currentKey)).execute()
11811182
lastMistralRequestTimeMs = System.currentTimeMillis()
11821183
} else {
1183-
throw IOException("Mistral rate limit reached. Please add another API key.")
1184+
// Kein anderer Key -> 5 Sekunden lang sofort wiederholen
1185+
apiKeyManager.resetFailedKeys(ApiProvider.MISTRAL)
1186+
withContext(Dispatchers.Main) {
1187+
replaceAiMessageText("Rate limit erreicht. Wiederhole...", isPending = true)
1188+
}
1189+
val retryDeadline = System.currentTimeMillis() + 5000L
1190+
var retryResponse: okhttp3.Response? = null
1191+
while (System.currentTimeMillis() < retryDeadline) {
1192+
if (stopExecutionFlag.get()) break
1193+
val retryResp = client.newCall(buildRequest(currentKey)).execute()
1194+
lastMistralRequestTimeMs = System.currentTimeMillis()
1195+
if (retryResp.code != 429) {
1196+
retryResponse = retryResp
1197+
break
1198+
}
1199+
retryResp.close()
1200+
}
1201+
if (retryResponse == null || stopExecutionFlag.get()) {
1202+
throw IOException("Mistral rate limit: Kein Erfolg innerhalb von 5 Sekunden.")
1203+
}
1204+
response = retryResponse
11841205
}
11851206
}
11861207

0 commit comments

Comments
 (0)