Skip to content

Commit 817d074

Browse files
Update InfoText: Swap GLM-5 for API Key switching info, remove Mistral Large 3 sentence
1 parent 2c6fc99 commit 817d074

4 files changed

Lines changed: 73 additions & 16 deletions

File tree

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.google.ai.sample
22

3+
import android.content.Context
4+
import android.content.ClipData
5+
import android.content.ClipboardManager
36
import android.content.Intent
47
import android.net.Uri
8+
import android.widget.Toast
59
import androidx.compose.foundation.layout.*
610
import androidx.compose.foundation.lazy.LazyColumn
711
import androidx.compose.foundation.lazy.itemsIndexed
@@ -97,17 +101,33 @@ fun ApiKeyDialog(
97101
ApiProvider.CEREBRAS -> "https://cloud.cerebras.ai/"
98102
ApiProvider.VERCEL -> "https://vercel.com/ai-gateway"
99103
ApiProvider.MISTRAL -> "https://console.mistral.ai/home?profile_dialog=api-keys"
100-
ApiProvider.PUTER -> "https://puter.com/dashboard"
104+
ApiProvider.PUTER -> "https://puter.com/dashboard#account"
101105
ApiProvider.HUMAN_EXPERT -> return@Button
102106
}
107+
108+
if (selectedProvider == ApiProvider.PUTER) {
109+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
110+
val clip = ClipData.newPlainText("Puter Link", url)
111+
clipboard.setPrimaryClip(clip)
112+
Toast.makeText(context, "Link is in the clipboard.", Toast.LENGTH_SHORT).show()
113+
Toast.makeText(context, "After the sign up paste the link in the Browser", Toast.LENGTH_LONG).show()
114+
}
115+
103116
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
104117
context.startActivity(intent)
105118
},
106119
modifier = Modifier
107120
.fillMaxWidth()
108121
.padding(bottom = 16.dp)
109122
) {
110-
Text("Get API Key for ${selectedProvider.name.replaceFirstChar { it.uppercase() }}")
123+
} {
124+
val buttonText = if (selectedProvider == ApiProvider.PUTER) {
125+
"Get Auth Token for Puter"
126+
} else {
127+
"Get API Key for ${selectedProvider.name.replaceFirstChar { it.uppercase() }}"
128+
}
129+
Text(buttonText)
130+
}
111131
}
112132

113133
// Input and Add section
@@ -118,7 +138,14 @@ fun ApiKeyDialog(
118138
apiKeyInput = it
119139
errorMessage = ""
120140
},
121-
label = { Text("Enter ${selectedProvider.name.replaceFirstChar { it.uppercase() }} API Key") },
141+
label = {
142+
val labelText = if (selectedProvider == ApiProvider.PUTER) {
143+
"Enter PUTER Auth Token"
144+
} else {
145+
"Enter ${selectedProvider.name.replaceFirstChar { it.uppercase() }} API Key"
146+
}
147+
Text(labelText)
148+
},
122149
modifier = Modifier.weight(1f),
123150
singleLine = true
124151
)

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,9 @@ fun MenuScreen(
582582
withStyle(boldStyle) { append("Preview Models") }
583583
append(" could be deactivated by Google without being handed over to the final release.\n")
584584
append("")
585-
withStyle(boldStyle) { append("GLM-5 (Puter)") }
586-
append(" is provided by Puter API. Enter your Puter Auth-Token as the API Key.\n")
587-
append("")
588-
withStyle(boldStyle) { append("Mistral Large 3") }
589-
append(" is a multimodal model (supports screenshots) and requires an API key.\n")
585+
withStyle(boldStyle) { append("API Keys") }
586+
append(" are automatically switched if multiple are inserted and one is exhausted.\n")
587+
590588
append("")
591589
withStyle(boldStyle) { append("GPT-oss 120b") }
592590
append(" is a pure text model.\n")

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ internal fun PhotoReasoningRoute(
174174
val userInput by viewModel.userInput.collectAsState()
175175
val isGenerationRunning by viewModel.isGenerationRunningFlow.collectAsState()
176176
val isOfflineGpuModelLoaded by viewModel.isOfflineGpuModelLoadedFlow.collectAsState()
177+
val isInitializingOfflineModel by viewModel.isInitializingOfflineModelFlow.collectAsState()
177178

178179
// Hoisted: var showNotificationRationaleDialog by rememberSaveable { mutableStateOf(false) }
179180
// This state will now be managed in PhotoReasoningRoute and passed down.
@@ -255,7 +256,8 @@ internal fun PhotoReasoningRoute(
255256
userQuestion = userInput,
256257
onUserQuestionChanged = { viewModel.updateUserInput(it) },
257258
isGenerationRunning = isGenerationRunning,
258-
isOfflineGpuModelLoaded = isOfflineGpuModelLoaded
259+
isOfflineGpuModelLoaded = isOfflineGpuModelLoaded,
260+
isInitializingOfflineModel = isInitializingOfflineModel
259261
)
260262
}
261263

@@ -281,7 +283,8 @@ fun PhotoReasoningScreen(
281283
userQuestion: String = "",
282284
onUserQuestionChanged: (String) -> Unit = {},
283285
isGenerationRunning: Boolean = false,
284-
isOfflineGpuModelLoaded: Boolean = false
286+
isOfflineGpuModelLoaded: Boolean = false,
287+
isInitializingOfflineModel: Boolean = false
285288
) {
286289
val imageUris = rememberSaveable(saver = UriSaver()) { mutableStateListOf() }
287290
var isSystemMessageFocused by rememberSaveable { mutableStateOf(false) }
@@ -501,9 +504,10 @@ fun PhotoReasoningScreen(
501504
)
502505
}
503506

504-
val showStopButton = isGenerationRunning || isOfflineGpuModelLoaded
505-
val stopButtonText = if (isGenerationRunning) "Stop" else "Modell entladen"
506-
val showTextFieldRow = !isGenerationRunning
507+
val isGemma = modelName == "gemma-3n-e4b-it"
508+
val showStopButton = isGenerationRunning || isOfflineGpuModelLoaded || isGemma
509+
val stopButtonText = if (isGenerationRunning) "Stop" else "Model Unload"
510+
val showTextFieldRow = !isGenerationRunning || isInitializingOfflineModel
507511

508512
if (showTextFieldRow) {
509513
Card(modifier = Modifier.fillMaxWidth()) {
@@ -574,7 +578,7 @@ fun PhotoReasoningScreen(
574578
} // Closes Card
575579
}
576580

577-
// Stop button: zeigt 'Stop' bei aktiver Generierung, 'Modell entladen' bei geladenem GPU-Modell
581+
// Stop button: zeigt 'Stop' bei aktiver Generierung, 'Model Unload' bei geladenem GPU-Modell oder Offline-Selektion
578582
if (showStopButton) {
579583
Spacer(modifier = Modifier.height(8.dp))
580584
Button(

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ class PhotoReasoningViewModel(
123123

124124
private val _isOfflineGpuModelLoadedFlow = MutableStateFlow(false)
125125
val isOfflineGpuModelLoadedFlow: StateFlow<Boolean> = _isOfflineGpuModelLoadedFlow.asStateFlow()
126+
127+
private val _isInitializingOfflineModelFlow = MutableStateFlow(false)
128+
val isInitializingOfflineModelFlow: StateFlow<Boolean> = _isInitializingOfflineModelFlow.asStateFlow()
126129

127130
// Keep track of the latest screenshot URI
128131
private var latestScreenshotUri: Uri? = null
@@ -300,13 +303,16 @@ class PhotoReasoningViewModel(
300303
withContext(Dispatchers.Main) {
301304
_uiState.value = PhotoReasoningUiState.Loading
302305
}
306+
_isInitializingOfflineModelFlow.value = true
303307
val error = initializeOfflineModel(context)
308+
_isInitializingOfflineModelFlow.value = false
304309
withContext(Dispatchers.Main) {
305310
if (error != null) {
306311
_uiState.value = PhotoReasoningUiState.Error(error)
307312
} else {
308313
_uiState.value = PhotoReasoningUiState.Success("Model initialized.")
309314
}
315+
refreshStopButtonState()
310316
}
311317
}
312318
}
@@ -388,12 +394,14 @@ class PhotoReasoningViewModel(
388394
Log.e(TAG, "Failed to reinitialize offline model: $initError")
389395
_uiState.value = PhotoReasoningUiState.Error(initError)
390396
} else {
391-
val backend = GenerativeAiViewModelFactory.getBackend()
392-
Log.d(TAG, "Offline model re-initialized with backend: $backend")
397+
refreshStopButtonState()
393398
}
394399
}
395400
} catch (e: Exception) {
396401
Log.e(TAG, "Failed to reinitialize offline model", e)
402+
} finally {
403+
_isInitializingOfflineModelFlow.value = false
404+
refreshStopButtonState()
397405
}
398406
}
399407
}
@@ -601,7 +609,9 @@ class PhotoReasoningViewModel(
601609

602610
if (currentReasoningJob?.isActive != true) return@launch // Check for cancellation outside content block
603611
sendMessageWithRetry(inputContent, 0)
612+
refreshStopButtonState()
604613
}
614+
refreshStopButtonState()
605615
}
606616

607617
fun reason(
@@ -719,9 +729,12 @@ class PhotoReasoningViewModel(
719729
replaceAiMessageText("Initializing offline model...", isPending = true)
720730
}
721731
// Use Default dispatcher for CPU-intensive model loading
732+
_isInitializingOfflineModelFlow.value = true
733+
refreshStopButtonState()
722734
initError = withContext(Dispatchers.Default) {
723735
initializeOfflineModel(context)
724736
}
737+
_isInitializingOfflineModelFlow.value = false
725738
}
726739

727740
if (llmInference == null) {
@@ -736,10 +749,13 @@ class PhotoReasoningViewModel(
736749
)
737750
)
738751
_chatMessagesFlow.value = _chatState.getAllMessages()
752+
refreshStopButtonState()
739753
}
740754
return@launch
741755
}
742756

757+
refreshStopButtonState()
758+
743759
Log.d(TAG, "Sending streaming prompt to offline model (length: ${fullPrompt.length})")
744760

745761
// Use generateResponseAsync with ProgressListener for streaming
@@ -762,6 +778,12 @@ class PhotoReasoningViewModel(
762778
processCommands(finalResponse)
763779
saveChatHistory(context)
764780
}
781+
} catch (e: Exception) {
782+
Log.e(TAG, "Offline inference failed", e)
783+
withContext(Dispatchers.Main) {
784+
saveChatHistory(context)
785+
refreshStopButtonState()
786+
}
765787
} catch (e: Exception) {
766788
Log.e(TAG, "Offline inference failed", e)
767789
withContext(Dispatchers.Main) {
@@ -775,7 +797,11 @@ class PhotoReasoningViewModel(
775797
)
776798
_chatMessagesFlow.value = _chatState.getAllMessages()
777799
saveChatHistory(context)
800+
refreshStopButtonState()
778801
}
802+
} finally {
803+
_isInitializingOfflineModelFlow.value = false
804+
refreshStopButtonState()
779805
}
780806
}
781807
return
@@ -1792,6 +1818,7 @@ class PhotoReasoningViewModel(
17921818
_chatMessagesFlow.value = _chatState.getAllMessages()
17931819
}
17941820
saveChatHistory(getApplication<Application>())
1821+
refreshStopButtonState()
17951822
}
17961823

17971824
private fun updateAiMessage(text: String, isPending: Boolean = false) {
@@ -2044,6 +2071,7 @@ private fun processCommands(text: String) {
20442071
if (stopExecutionFlag.get()){
20452072
_commandExecutionStatus.value = "Command processing finished after stop request."
20462073
}
2074+
refreshStopButtonState()
20472075
}
20482076
}
20492077
}

0 commit comments

Comments
 (0)