@@ -48,6 +48,7 @@ import android.util.Log
4848import android.os.Environment
4949import android.os.StatFs
5050import com.google.ai.sample.feature.multimodal.ModelDownloadManager
51+ import androidx.compose.runtime.collectAsState
5152import java.io.File
5253
5354data class MenuItem (
@@ -285,6 +286,111 @@ fun MenuScreen(
285286 }
286287 }
287288
289+ // Generation Settings (TopK, TopP, Temperature) for current model
290+ if (selectedModel.supportsGenerationSettings) {
291+ item {
292+ val genSettings = remember(selectedModel) {
293+ mutableStateOf(
294+ com.google.ai.sample.util.GenerationSettingsPreferences .loadSettings(
295+ context, selectedModel.modelName
296+ )
297+ )
298+ }
299+
300+ Card (
301+ modifier = Modifier
302+ .fillMaxWidth()
303+ .padding(horizontal = 16 .dp, vertical = 8 .dp)
304+ ) {
305+ Column (
306+ modifier = Modifier
307+ .padding(all = 16 .dp)
308+ .fillMaxWidth()
309+ ) {
310+ Text (
311+ text = " Generation Settings (${selectedModel.displayName} )" ,
312+ style = MaterialTheme .typography.titleMedium
313+ )
314+
315+ Spacer (modifier = Modifier .height(12 .dp))
316+
317+ // Temperature Slider (0.0 - 2.0)
318+ Text (
319+ text = " Temperature: ${" %.2f" .format(genSettings.value.temperature)} " ,
320+ style = MaterialTheme .typography.bodyMedium
321+ )
322+ androidx.compose.material3.Slider (
323+ value = genSettings.value.temperature,
324+ onValueChange = { newVal ->
325+ genSettings.value = genSettings.value.copy(temperature = newVal)
326+ },
327+ onValueChangeFinished = {
328+ com.google.ai.sample.util.GenerationSettingsPreferences .saveSettings(
329+ context, selectedModel.modelName, genSettings.value
330+ )
331+ },
332+ valueRange = 0f .. 2f ,
333+ steps = 39 ,
334+ modifier = Modifier .fillMaxWidth()
335+ )
336+
337+ Spacer (modifier = Modifier .height(8 .dp))
338+
339+ // TopP Slider (0.0 - 1.0)
340+ Text (
341+ text = " Top P: ${" %.2f" .format(genSettings.value.topP)} " ,
342+ style = MaterialTheme .typography.bodyMedium
343+ )
344+ androidx.compose.material3.Slider (
345+ value = genSettings.value.topP,
346+ onValueChange = { newVal ->
347+ genSettings.value = genSettings.value.copy(topP = newVal)
348+ },
349+ onValueChangeFinished = {
350+ com.google.ai.sample.util.GenerationSettingsPreferences .saveSettings(
351+ context, selectedModel.modelName, genSettings.value
352+ )
353+ },
354+ valueRange = 0f .. 1f ,
355+ steps = 19 ,
356+ modifier = Modifier .fillMaxWidth()
357+ )
358+
359+ Spacer (modifier = Modifier .height(8 .dp))
360+
361+ // TopK Slider (1 - 100)
362+ Text (
363+ text = " Top K: ${genSettings.value.topK} " ,
364+ style = MaterialTheme .typography.bodyMedium
365+ )
366+ androidx.compose.material3.Slider (
367+ value = genSettings.value.topK.toFloat(),
368+ onValueChange = { newVal ->
369+ genSettings.value = genSettings.value.copy(topK = newVal.toInt())
370+ },
371+ onValueChangeFinished = {
372+ com.google.ai.sample.util.GenerationSettingsPreferences .saveSettings(
373+ context, selectedModel.modelName, genSettings.value
374+ )
375+ },
376+ valueRange = 1f .. 100f ,
377+ steps = 98 ,
378+ modifier = Modifier .fillMaxWidth()
379+ )
380+
381+ if (selectedModel == ModelOption .GEMMA_3N_E4B_IT ) {
382+ Spacer (modifier = Modifier .height(4 .dp))
383+ Text (
384+ text = " Note: LlmInference (offline model) may not support all generation parameters." ,
385+ style = MaterialTheme .typography.bodySmall,
386+ color = MaterialTheme .colorScheme.onSurfaceVariant
387+ )
388+ }
389+ }
390+ }
391+ }
392+ }
393+
288394 // Menu Items
289395 items(menuItems) { menuItem ->
290396 Card (
@@ -489,31 +595,121 @@ GPT-5 nano Input: $0.05/M Output: $0.40/M
489595 val bytesAvailable = statFs.availableBlocksLong * statFs.blockSizeLong
490596 val gbAvailable = bytesAvailable.toDouble() / (1024 * 1024 * 1024 )
491597 val formattedGbAvailable = String .format(" %.2f" , gbAvailable)
598+
599+ val dlState by ModelDownloadManager .downloadState.collectAsState()
492600
493601 AlertDialog (
494- onDismissRequest = { showDownloadDialog = false },
495- title = { Text (" Download Model? (4.92 GB)" ) },
496- text = { Text (" Should the Gemma 3n E4B be downloaded?\n\n $formattedGbAvailable GB of storage available." ) },
497- confirmButton = {
498- TextButton (
499- onClick = {
500- showDownloadDialog = false
501- downloadDialogModel?.downloadUrl?.let { url ->
502- ModelDownloadManager .downloadModel(context, url)
503- // We set the model, but the user will have to wait for download
504- selectedModel = downloadDialogModel!!
505- GenerativeAiViewModelFactory .setModel(downloadDialogModel!! )
602+ onDismissRequest = {
603+ if (dlState is ModelDownloadManager .DownloadState .Idle || dlState is ModelDownloadManager .DownloadState .Completed || dlState is ModelDownloadManager .DownloadState .Error ) {
604+ showDownloadDialog = false
605+ }
606+ // Don't dismiss while downloading/paused
607+ },
608+ title = { Text (" Download Model (4.92 GB)" ) },
609+ text = {
610+ Column {
611+ when (val state = dlState) {
612+ is ModelDownloadManager .DownloadState .Idle -> {
613+ Text (" Should the Gemma 3n E4B be downloaded?\n\n $formattedGbAvailable GB of storage available." )
614+ }
615+ is ModelDownloadManager .DownloadState .Downloading -> {
616+ Text (" Downloading..." )
617+ Spacer (modifier = Modifier .height(8 .dp))
618+ androidx.compose.material3.LinearProgressIndicator (
619+ progress = { state.progress },
620+ modifier = Modifier .fillMaxWidth()
621+ )
622+ Spacer (modifier = Modifier .height(4 .dp))
623+ Text (
624+ text = " ${ModelDownloadManager .formatBytes(state.bytesDownloaded)} / ${if (state.totalBytes > 0 ) ModelDownloadManager .formatBytes(state.totalBytes) else " ?" } " ,
625+ style = MaterialTheme .typography.bodySmall
626+ )
627+ Text (
628+ text = " ${" %.1f" .format(state.progress * 100 )} %" ,
629+ style = MaterialTheme .typography.bodySmall
630+ )
631+ }
632+ is ModelDownloadManager .DownloadState .Paused -> {
633+ Text (" Download paused." )
634+ Spacer (modifier = Modifier .height(8 .dp))
635+ val progress = if (state.totalBytes > 0 ) state.bytesDownloaded.toFloat() / state.totalBytes else 0f
636+ androidx.compose.material3.LinearProgressIndicator (
637+ progress = { progress },
638+ modifier = Modifier .fillMaxWidth()
639+ )
640+ Spacer (modifier = Modifier .height(4 .dp))
641+ Text (
642+ text = " ${ModelDownloadManager .formatBytes(state.bytesDownloaded)} / ${if (state.totalBytes > 0 ) ModelDownloadManager .formatBytes(state.totalBytes) else " ?" } " ,
643+ style = MaterialTheme .typography.bodySmall
644+ )
645+ }
646+ is ModelDownloadManager .DownloadState .Completed -> {
647+ Text (" Download complete! ✅" )
648+ }
649+ is ModelDownloadManager .DownloadState .Error -> {
650+ Text (" Error: ${state.message} " )
506651 }
507652 }
508- ) { Text (" OK" ) }
653+ }
654+ },
655+ confirmButton = {
656+ when (dlState) {
657+ is ModelDownloadManager .DownloadState .Idle -> {
658+ TextButton (
659+ onClick = {
660+ downloadDialogModel?.downloadUrl?.let { url ->
661+ ModelDownloadManager .downloadModel(context, url)
662+ selectedModel = downloadDialogModel!!
663+ GenerativeAiViewModelFactory .setModel(downloadDialogModel!! )
664+ }
665+ }
666+ ) { Text (" Download" ) }
667+ }
668+ is ModelDownloadManager .DownloadState .Downloading -> {
669+ TextButton (onClick = { ModelDownloadManager .pauseDownload() }) { Text (" Pause" ) }
670+ }
671+ is ModelDownloadManager .DownloadState .Paused -> {
672+ TextButton (
673+ onClick = {
674+ downloadDialogModel?.downloadUrl?.let { url ->
675+ ModelDownloadManager .resumeDownload(context, url)
676+ }
677+ }
678+ ) { Text (" Resume" ) }
679+ }
680+ is ModelDownloadManager .DownloadState .Completed -> {
681+ TextButton (onClick = { showDownloadDialog = false }) { Text (" Close" ) }
682+ }
683+ is ModelDownloadManager .DownloadState .Error -> {
684+ TextButton (
685+ onClick = {
686+ downloadDialogModel?.downloadUrl?.let { url ->
687+ ModelDownloadManager .downloadModel(context, url)
688+ }
689+ }
690+ ) { Text (" Retry" ) }
691+ }
692+ }
509693 },
510694 dismissButton = {
511- TextButton (
512- onClick = {
513- showDownloadDialog = false
514- // Do not change model
695+ when (dlState) {
696+ is ModelDownloadManager .DownloadState .Idle -> {
697+ TextButton (onClick = { showDownloadDialog = false }) { Text (" Cancel" ) }
515698 }
516- ) { Text (" ABORT" ) }
699+ is ModelDownloadManager .DownloadState .Downloading ,
700+ is ModelDownloadManager .DownloadState .Paused -> {
701+ TextButton (
702+ onClick = {
703+ ModelDownloadManager .cancelDownload(context)
704+ showDownloadDialog = false
705+ }
706+ ) { Text (" Cancel Download" ) }
707+ }
708+ is ModelDownloadManager .DownloadState .Completed -> { /* No dismiss button */ }
709+ is ModelDownloadManager .DownloadState .Error -> {
710+ TextButton (onClick = { showDownloadDialog = false }) { Text (" Close" ) }
711+ }
712+ }
517713 }
518714 )
519715 }
0 commit comments