Skip to content

Commit d7e165f

Browse files
Implement 18 feature changes: text/UI updates, generation settings, offline model fixes, 503 error handling, download notifications, Human Expert foreground service fix, HumanOperator icon, package name change
1 parent 63478fb commit d7e165f

10 files changed

Lines changed: 233 additions & 50 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ if (System.getenv("CI") == null) {
1414
}
1515

1616
android {
17-
namespace = "com.google.ai.sample"
17+
namespace = "io.github.android_poweruser"
1818
compileSdk = 35
1919

2020
defaultConfig {
21-
applicationId = "com.google.ai.sample"
21+
applicationId = "io.github.android_poweruser"
2222
minSdk = 26
2323
targetSdk = 35
2424
versionCode = 1

app/src/main/kotlin/com/google/ai/sample/MainActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class MainActivity : ComponentActivity() {
301301

302302
when (currentTrialState) {
303303
TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED -> {
304-
trialInfoMessage = "Your 30-minute trial period has ended. Please subscribe to the app to continue using it."
304+
trialInfoMessage = "Please support the development of the app so that you can continue using it \uD83C\uDF89"
305305
showTrialInfoDialog = true
306306
Log.d(TAG, "updateTrialState: Set message to \'$trialInfoMessage\', showTrialInfoDialog = true (EXPIRED)")
307307
}
@@ -1256,7 +1256,7 @@ fun TrialExpiredDialog(
12561256
)
12571257
Spacer(modifier = Modifier.height(16.dp))
12581258
Text(
1259-
text = "Your 7-day trial period has ended. Please subscribe to the app to continue using it.",
1259+
text = "Please support the development of the app so that you can continue using it \uD83C\uDF89",
12601260
style = MaterialTheme.typography.bodyMedium,
12611261
modifier = Modifier.align(Alignment.CenterHorizontally)
12621262
)

app/src/main/kotlin/com/google/ai/sample/MenuScreen.kt

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
2828
import androidx.compose.ui.platform.LocalContext
2929
import androidx.compose.ui.res.stringResource
3030
import androidx.compose.ui.text.style.TextAlign
31+
import androidx.compose.ui.text.font.FontWeight
3132
import androidx.compose.ui.tooling.preview.Preview
3233
import androidx.compose.ui.unit.dp
3334
import androidx.compose.foundation.layout.Arrangement
@@ -330,7 +331,7 @@ fun MenuScreen(
330331
)
331332
},
332333
valueRange = 0f..2f,
333-
steps = 39,
334+
steps = 0,
334335
modifier = Modifier.fillMaxWidth()
335336
)
336337

@@ -352,29 +353,29 @@ fun MenuScreen(
352353
)
353354
},
354355
valueRange = 0f..1f,
355-
steps = 19,
356+
steps = 0,
356357
modifier = Modifier.fillMaxWidth()
357358
)
358359

359360
Spacer(modifier = Modifier.height(8.dp))
360361

361-
// TopK Slider (1 - 100)
362+
// TopK Slider (0 - 100)
362363
Text(
363364
text = "Top K: ${genSettings.value.topK}",
364365
style = MaterialTheme.typography.bodyMedium
365366
)
366367
androidx.compose.material3.Slider(
367368
value = genSettings.value.topK.toFloat(),
368369
onValueChange = { newVal ->
369-
genSettings.value = genSettings.value.copy(topK = newVal.toInt())
370+
genSettings.value = genSettings.value.copy(topK = Math.round(newVal))
370371
},
371372
onValueChangeFinished = {
372373
com.google.ai.sample.util.GenerationSettingsPreferences.saveSettings(
373374
context, selectedModel.modelName, genSettings.value
374375
)
375376
},
376-
valueRange = 1f..100f,
377-
steps = 98,
377+
valueRange = 0f..100f,
378+
steps = 0,
378379
modifier = Modifier.fillMaxWidth()
379380
)
380381

@@ -415,7 +416,7 @@ fun MenuScreen(
415416
TextButton(
416417
onClick = {
417418
if (isTrialExpired) {
418-
Toast.makeText(context, "Please subscribe to the app to continue.", Toast.LENGTH_LONG).show()
419+
Toast.makeText(context, "Please support the development of the app so that you can continue using it \uD83C\uDF89", Toast.LENGTH_LONG).show()
419420
} else {
420421
if (menuItem.routeId == "photo_reasoning") {
421422
val mainActivity = context as? MainActivity
@@ -489,7 +490,7 @@ fun MenuScreen(
489490
)
490491
} else {
491492
Text(
492-
text = "Support improvements",
493+
text = "Support Improvements \uD83C\uDF89",
493494
style = MaterialTheme.typography.titleMedium,
494495
modifier = Modifier.weight(1f)
495496
)
@@ -510,15 +511,28 @@ fun MenuScreen(
510511
.fillMaxWidth()
511512
.padding(horizontal = 16.dp, vertical = 8.dp)
512513
) {
514+
val boldStyle = SpanStyle(fontWeight = FontWeight.Bold)
513515
val annotatedText = buildAnnotatedString {
514-
append("""• Preview models could be deactivated by Google without being handed over to the final release.
515-
• GPT-oss 120b is a pure text model.
516-
• Gemma 3n E4B it cannot handle screenshots in the API.
517-
• GPT models (Vercel) have a free budget of $5 per month.
518-
GPT-5.1 Input: $1.25/M Output: $10.00/M
519-
GPT-5.1 mini Input: $0.25/ M Output: $2.00/M
520-
GPT-5 nano Input: $0.05/M Output: $0.40/M
521-
• There are rate limits for free use of Gemini models. The less powerful the models are, the more you can use them. The limits range from a maximum of 5 to 30 calls per minute. After each screenshot (every 2-3 seconds) the LLM must respond again. More information is available at """)
516+
append("")
517+
withStyle(boldStyle) { append("Preview Models") }
518+
append(" could be deactivated by Google without being handed over to the final release.\n")
519+
append("")
520+
withStyle(boldStyle) { append("GPT-oss 120b") }
521+
append(" is a pure text model.\n")
522+
append("")
523+
withStyle(boldStyle) { append("Gemma 27B IT") }
524+
append(" cannot handle screenshots in the API.\n")
525+
append("• GPT models (")
526+
withStyle(boldStyle) { append("Vercel") }
527+
append(") have a free budget of \$5 per month and a credit card is necessary.\n")
528+
append("GPT-5.1 Input: \$1.25/M Output: \$10.00/M\n")
529+
append("GPT-5.1 mini Input: \$0.25/M Output: \$2.00/M\n")
530+
append("GPT-5 nano Input: \$0.05/M Output: \$0.40/M\n")
531+
append("• There are ")
532+
withStyle(boldStyle) { append("rate limits") }
533+
append(" for free use of ")
534+
withStyle(boldStyle) { append("Gemini models") }
535+
append(". The less powerful the models are, the more you can use them. The limits range from a maximum of 5 to 30 calls per minute. After each screenshot (every 2-3 seconds) the LLM must respond again. More information is available at ")
522536

523537
pushStringAnnotation(tag = "URL", annotation = "https://ai.google.dev/gemini-api/docs/rate-limits")
524538
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline)) {
@@ -659,8 +673,7 @@ GPT-5 nano Input: $0.05/M Output: $0.40/M
659673
onClick = {
660674
downloadDialogModel?.downloadUrl?.let { url ->
661675
ModelDownloadManager.downloadModel(context, url)
662-
selectedModel = downloadDialogModel!!
663-
GenerativeAiViewModelFactory.setModel(downloadDialogModel!!)
676+
// Don't set model yet - wait for download to complete (Point 17)
664677
}
665678
}
666679
) { Text("Download") }
@@ -678,7 +691,14 @@ GPT-5 nano Input: $0.05/M Output: $0.40/M
678691
) { Text("Resume") }
679692
}
680693
is ModelDownloadManager.DownloadState.Completed -> {
681-
TextButton(onClick = { showDownloadDialog = false }) { Text("Close") }
694+
TextButton(onClick = {
695+
// Set model only after download is completed (Point 17)
696+
downloadDialogModel?.let {
697+
selectedModel = it
698+
GenerativeAiViewModelFactory.setModel(it)
699+
}
700+
showDownloadDialog = false
701+
}) { Text("Close") }
682702
}
683703
is ModelDownloadManager.DownloadState.Error -> {
684704
TextButton(

app/src/main/kotlin/com/google/ai/sample/ScreenCaptureService.kt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,21 +289,24 @@ class ScreenCaptureService : Service() {
289289
}
290290
} catch (e: MissingFieldException) {
291291
Log.e(TAG, "Serialization error, potentially a 503 error.", e)
292-
// Check if the error message indicates a 503-like error
293-
if (e.message?.contains("UNAVAILABLE") == true ||
292+
// Point 15: Check for missing 'parts' field (Gemma 27B issue)
293+
if (e.message?.contains("parts") == true) {
294+
errorMessage = "The model returned an incomplete response. This can happen with larger models. Please try again."
295+
} else if (e.message?.contains("UNAVAILABLE") == true ||
294296
e.message?.contains("503") == true ||
295297
e.message?.contains("overloaded") == true) {
296-
errorMessage = "Service Unavailable (503) - Retry with new key"
298+
// Point 14: User-friendly high-demand message
299+
errorMessage = "This model is currently experiencing high demand. Please try again later."
297300
} else {
298301
errorMessage = e.localizedMessage ?: "Serialization error"
299302
}
300303
} catch (e: Exception) {
301304
Log.e(TAG, "Direct error in AI call", e)
302-
// Also check for 503 patterns in general exceptions
305+
// Point 14: Check for high-demand 503 patterns
303306
if (e.message?.contains("503") == true ||
304307
e.message?.contains("overloaded") == true ||
305308
e.message?.contains("UNAVAILABLE") == true) {
306-
errorMessage = "Service Unavailable (503) - Retry with new key"
309+
errorMessage = "This model is currently experiencing high demand. Please try again later."
307310
} else {
308311
errorMessage = e.localizedMessage ?: "AI call failed"
309312
}
@@ -319,11 +322,14 @@ class ScreenCaptureService : Service() {
319322
e.message?.contains("UNAVAILABLE") == true ||
320323
e.message?.contains("503") == true ||
321324
e.message?.contains("overloaded") == true)) {
322-
errorMessage = "Service Unavailable (503) - Retry with new key"
325+
errorMessage = "This model is currently experiencing high demand. Please try again later."
326+
} else if (e is MissingFieldException && e.message?.contains("parts") == true) {
327+
// Point 15: Gemma 27B incomplete response
328+
errorMessage = "The model returned an incomplete response. This can happen with larger models. Please try again."
323329
} else if (e.message?.contains("503") == true ||
324330
e.message?.contains("overloaded") == true ||
325331
e.message?.contains("UNAVAILABLE") == true) {
326-
errorMessage = "Service Unavailable (503) - Retry with new key"
332+
errorMessage = "This model is currently experiencing high demand. Please try again later."
327333
} else {
328334
errorMessage = e.localizedMessage ?: "Unknown error"
329335
}

0 commit comments

Comments
 (0)