@@ -31,8 +31,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
3131import kotlinx.coroutines.Job
3232import kotlinx.coroutines.flow.StateFlow
3333import kotlinx.coroutines.flow.asStateFlow
34- // Removed duplicate StateFlow import
35- // Removed duplicate asStateFlow import
3634// import kotlinx.coroutines.isActive // Removed as we will use job.isActive
3735import kotlinx.coroutines.launch
3836import kotlinx.coroutines.withContext
@@ -50,17 +48,9 @@ class PhotoReasoningViewModel(
5048 MutableStateFlow (PhotoReasoningUiState .Initial )
5149 val uiState: StateFlow <PhotoReasoningUiState > =
5250 _uiState .asStateFlow()
53-
54- private val _isInitialized = MutableStateFlow (false )
55- val isInitialized: StateFlow <Boolean > = _isInitialized .asStateFlow()
56-
57- private val _showStopNotificationFlow = MutableStateFlow (false )
58- val showStopNotificationFlow: StateFlow <Boolean > = _showStopNotificationFlow .asStateFlow()
5951
6052 // Keep track of the latest screenshot URI
6153 private var latestScreenshotUri: Uri ? = null
62- private var lastProcessedScreenshotUri: Uri ? = null
63- private var lastProcessedScreenshotTime: Long = 0L
6454
6555 // Keep track of the current selected images
6656 private var currentSelectedImages: List <Bitmap > = emptyList()
@@ -106,27 +96,15 @@ class PhotoReasoningViewModel(
10696
10797 fun reason (
10898 userInput : String ,
109- selectedImages : List <Bitmap >,
110- screenInfoForPrompt : String? = null,
111- imageUrisForChat : List <String >? = null
99+ selectedImages : List <Bitmap >
112100 ) {
113- Log .d(TAG , " reason() called. User input: '$userInput ', Image count: ${selectedImages.size} , ScreenInfo: ${screenInfoForPrompt != null } , ImageUris: ${imageUrisForChat != null } " )
114101 _uiState .value = PhotoReasoningUiState .Loading
115- Log .d(TAG , " Setting _showStopNotificationFlow to true" )
116- _showStopNotificationFlow .value = true
117- Log .d(TAG , " _showStopNotificationFlow value is now: ${_showStopNotificationFlow .value} " )
118- stopExecutionFlag.set(false )
119-
120- val combinedPromptTextBuilder = StringBuilder (userInput)
121- if (screenInfoForPrompt != null && screenInfoForPrompt.isNotBlank()) { // Added isNotBlank check
122- combinedPromptTextBuilder.append(" \n\n Screen Context:\n $screenInfoForPrompt " )
123- }
124- val aiPromptText = combinedPromptTextBuilder.toString()
102+ stopExecutionFlag.set(false ) // Reset flag at the beginning of a new reason call
125103
126- val prompt = " FOLLOW THE INSTRUCTIONS STRICTLY: $aiPromptText "
104+ val prompt = " FOLLOW THE INSTRUCTIONS STRICTLY: $userInput "
127105
128106 // Store the current user input and selected images
129- currentUserInput = userInput // This should ideally store aiPromptText or handle context separately if needed for retry. For now, task is specific to prompt to AI and chat.
107+ currentUserInput = userInput
130108 currentSelectedImages = selectedImages
131109
132110 // Clear previous commands
@@ -135,9 +113,8 @@ class PhotoReasoningViewModel(
135113
136114 // Add user message to chat history
137115 val userMessage = PhotoReasoningMessage (
138- text = aiPromptText, // Use the combined text
116+ text = userInput,
139117 participant = PhotoParticipant .USER ,
140- imageUris = imageUrisForChat ? : emptyList(), // Use the new parameter here
141118 isPending = false
142119 )
143120 _chatState .addMessage(userMessage)
@@ -198,7 +175,6 @@ class PhotoReasoningViewModel(
198175 }
199176
200177 fun onStopClicked () {
201- _showStopNotificationFlow .value = false // Hide notification immediately on stop
202178 stopExecutionFlag.set(true )
203179 currentReasoningJob?.cancel()
204180 commandProcessingJob?.cancel()
@@ -252,12 +228,11 @@ class PhotoReasoningViewModel(
252228 if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()) {
253229 if (stopExecutionFlag.get()) {
254230 // User initiated stop, onStopClicked will handle UI and message
255- return // _showStopNotificationFlow is already false from onStopClicked
231+ return
256232 } else {
257233 // Cancellation not by user stop button
258234 _uiState .value = PhotoReasoningUiState .Error (" Operation cancelled unexpectedly before sending." )
259235 updateAiMessage(" Operation cancelled unexpectedly." )
260- _showStopNotificationFlow .value = false
261236 return
262237 }
263238 }
@@ -269,12 +244,11 @@ class PhotoReasoningViewModel(
269244 if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()) {
270245 if (stopExecutionFlag.get()) {
271246 // User initiated stop, onStopClicked will handle UI and message
272- return // _showStopNotificationFlow is already false from onStopClicked
247+ return
273248 } else {
274249 // Cancellation not by user stop button
275250 _uiState .value = PhotoReasoningUiState .Error (" Operation cancelled unexpectedly after sending." )
276251 updateAiMessage(" Operation cancelled unexpectedly." )
277- _showStopNotificationFlow .value = false
278252 return
279253 }
280254 }
@@ -289,12 +263,10 @@ class PhotoReasoningViewModel(
289263 if (stopExecutionFlag.get()) {
290264 // User initiated stop, onStopClicked will handle UI and message
291265 shouldProceed = false // Signal to skip further processing
292- // _showStopNotificationFlow handled by onStopClicked or subsequent checks if shouldProceed is false
293266 } else {
294267 // Cancellation not by user stop button
295268 _uiState .value = PhotoReasoningUiState .Error (" Operation cancelled unexpectedly during response processing." )
296269 updateAiMessage(" Operation cancelled unexpectedly." )
297- _showStopNotificationFlow .value = false
298270 shouldProceed = false // Signal to skip further processing
299271 }
300272 }
@@ -305,17 +277,16 @@ class PhotoReasoningViewModel(
305277 if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()) { // Re-check for cancellation
306278 if (stopExecutionFlag.get()) {
307279 // User initiated stop, onStopClicked will handle UI and message
308- // _showStopNotificationFlow handled by onStopClicked
280+ // No action needed here, shouldProceed will prevent further execution if already false
281+ // or the outer checks in this function will catch it.
309282 } else {
310283 _uiState .value = PhotoReasoningUiState .Error (" Operation cancelled unexpectedly before UI update." )
311284 updateAiMessage(" Operation cancelled unexpectedly." )
312- _showStopNotificationFlow .value = false
313285 }
314286 // No return@withContext, logic will naturally skip due to outer 'if (shouldProceed)' and this check
315287 // or if shouldProceed was set to false earlier.
316288 } else {
317289 _uiState .value = PhotoReasoningUiState .Success (outputContent)
318- _showStopNotificationFlow .value = false // Operation successful
319290
320291 // Update the AI message in chat history
321292 updateAiMessage(outputContent)
@@ -328,12 +299,6 @@ class PhotoReasoningViewModel(
328299 }
329300 }
330301 }
331- } else {
332- // If shouldProceed is false, and it wasn't due to stopExecutionFlag, ensure notification is cancelled.
333- // If stopExecutionFlag was true, onStopClicked already handled it.
334- if (! stopExecutionFlag.get()) {
335- _showStopNotificationFlow .value = false
336- }
337302 }
338303
339304
@@ -344,7 +309,6 @@ class PhotoReasoningViewModel(
344309 // Ensure we are still active before saving
345310 if (currentReasoningJob?.isActive == true && ! stopExecutionFlag.get()) {
346311 saveChatHistory(MainActivity .getInstance()?.applicationContext)
347- // _showStopNotificationFlow already set to false if successful
348312 }
349313 }
350314 }
@@ -353,15 +317,13 @@ class PhotoReasoningViewModel(
353317 // If user already stopped, just log the error and return.
354318 // Do not update UI or send chat messages as onStopClicked handles this.
355319 Log .w(TAG , " Exception caught after stop flag was set: ${e.message} " , e)
356- // _showStopNotificationFlow is already false from onStopClicked
357320 return
358321 }
359322 // If the stop flag is not set, but the job is inactive (cancelled by other means)
360323 if (currentReasoningJob?.isActive != true ) {
361324 _uiState .value = PhotoReasoningUiState .Error (" Operation cancelled and then an error occurred." ) // Or a more fitting message
362325 updateAiMessage(" Operation cancelled, error during cleanup: ${e.message} " )
363326 Log .e(TAG , " Error generating content after job was cancelled: ${e.message} " , e)
364- _showStopNotificationFlow .value = false
365327 return
366328 }
367329
@@ -373,46 +335,30 @@ class PhotoReasoningViewModel(
373335 // The check currentReasoningJob?.isActive != true is still relevant if the job gets cancelled
374336 // by other means after the top checks in this catch block.
375337 // stopExecutionFlag.get() is less likely to be true here due to the top check, but kept for defense.
376- if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()){
377- if (! stopExecutionFlag.get()) _showStopNotificationFlow .value = false
378- return
379- }
380- handleQuotaExceededError(e, inputContent, retryCount) // This might retry or be terminal
381- // If handleQuotaExceededError doesn't lead to a retry (i.e., it's terminal), we need to set flow to false.
382- // This logic is tricky as handleQuotaExceededError has its own returns.
383- // For now, assume if it returns here, it might retry. If it's terminal, it sets UI state and should set flow.
384- // This will be refined by inspecting handleQuotaExceededError.
385- // For now, if it's truly terminal and doesn't retry, the general error path below will catch it.
386- // Let's assume retries handle the flow, and terminal errors are handled below or within the handler.
387- return // if handleQuotaExceededError is not terminal and retries, it will reset the flow value at reason() start
338+ if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()) return
339+ handleQuotaExceededError(e, inputContent, retryCount)
340+ return
388341 }
389342
390343 // Check for other 503 errors
391344 if (is503Error(e) && apiKeyManager != null ) {
392- if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()){
393- if (! stopExecutionFlag.get()) _showStopNotificationFlow .value = false
394- return
395- }
396- handle503Error(e, inputContent, retryCount) // Similar to above, assumes retries handle flow
345+ if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()) return
346+ handle503Error(e, inputContent, retryCount)
397347 return
398348 }
399349
400- // If we get here, it's not a 503 error or quota exceeded error that led to a retry path
350+ // If we get here, it's not a 503 error or quota exceeded error
401351 // The stopExecutionFlag.get() check here is mostly redundant due to the top check,
402352 // but currentReasoningJob?.isActive is still a valid check.
403353 if (currentReasoningJob?.isActive == true && ! stopExecutionFlag.get()) {
404354 withContext(Dispatchers .Main ) {
405355 if (currentReasoningJob?.isActive != true || stopExecutionFlag.get()) {
406- if (! stopExecutionFlag.get()) {
407- _showStopNotificationFlow .value = false
408- }
409356 // If cancelled (possibly between the outer check and here, or due to job inactivity)
410357 // Potentially log or update UI to reflect this specific state if desired.
411358 // For now, this means the error e won't be set as the primary UI state.
412359 } else {
413360 _uiState .value = PhotoReasoningUiState .Error (e.localizedMessage ? : " Unknown error" )
414361 _commandExecutionStatus .value = " Error during generation: ${e.localizedMessage} "
415- _showStopNotificationFlow .value = false // Terminal error
416362
417363 // Update chat with error message
418364 _chatState .replaceLastPendingMessage()
@@ -428,13 +374,17 @@ class PhotoReasoningViewModel(
428374 saveChatHistory(MainActivity .getInstance()?.applicationContext)
429375 }
430376 }
431- } else { // This 'else' covers cases where job became inactive or stop flag was set concurrently
432- if (! stopExecutionFlag.get()) { // If not stopped by user, then it's a cancellation
377+ } else {
378+ // This branch could be reached if:
379+ // 1. stopExecutionFlag was set by another thread between the top check and here.
380+ // 2. currentReasoningJob became inactive for reasons other than the stop flag.
381+ // If stopExecutionFlag is true, onStopClicked handles the message.
382+ // If only job inactive, a general "cancelled during error processing" might be okay,
383+ // but the specific check for job inactivity at the top of the catch block should handle most cases.
384+ if (! stopExecutionFlag.get()) {
433385 _uiState .value = PhotoReasoningUiState .Error (" Operation error processing or cancelled." )
434386 updateAiMessage(" Operation error processing or cancelled." )
435- _showStopNotificationFlow .value = false
436387 }
437- // If stopExecutionFlag.get() is true, onStopClicked handles the notification flow.
438388 }
439389 }
440390 }
@@ -713,9 +663,7 @@ class PhotoReasoningViewModel(
713663 _systemMessage .value = message
714664
715665 // Also load chat history
716- loadChatHistory(context) // This line calls rebuildChatHistory internally
717-
718- _isInitialized .value = true // Add this line
666+ loadChatHistory(context)
719667 }
720668
721669 /* *
@@ -943,14 +891,6 @@ class PhotoReasoningViewModel(
943891 context : Context ,
944892 screenInfo : String? = null
945893 ) {
946- val currentTime = System .currentTimeMillis()
947- if (screenshotUri == lastProcessedScreenshotUri && (currentTime - lastProcessedScreenshotTime) < 2000 ) { // 2-second debounce window
948- Log .w(TAG , " addScreenshotToConversation: Debouncing duplicate/rapid call for URI $screenshotUri " )
949- return // Exit the function early if it's a duplicate call within the window
950- }
951- lastProcessedScreenshotUri = screenshotUri
952- lastProcessedScreenshotTime = currentTime
953-
954894 PhotoReasoningApplication .applicationScope.launch(Dispatchers .Main ) {
955895 try {
956896 Log .d(TAG , " Adding screenshot to conversation: $screenshotUri " )
@@ -972,6 +912,25 @@ class PhotoReasoningViewModel(
972912 // Show toast
973913 Toast .makeText(context, " Processing screenshot..." , Toast .LENGTH_SHORT ).show()
974914
915+ // Create message text with screen information if available
916+ val messageText = if (screenInfo != null ) {
917+ " Screenshot captured\n\n $screenInfo "
918+ } else {
919+ " Screenshot captured"
920+ }
921+
922+ // Add screenshot message to chat history
923+ val screenshotMessage = PhotoReasoningMessage (
924+ text = messageText,
925+ participant = PhotoParticipant .USER ,
926+ imageUris = listOf (screenshotUri.toString())
927+ )
928+ _chatState .addMessage(screenshotMessage)
929+ _chatMessagesFlow .value = chatMessages
930+
931+ // Save chat history after adding screenshot
932+ saveChatHistory(context)
933+
975934 // Process the screenshot
976935 val imageRequest = imageRequestBuilder!!
977936 .data(screenshotUri)
@@ -998,15 +957,14 @@ class PhotoReasoningViewModel(
998957 Toast .makeText(context, " Screenshot added, sending to AI..." , Toast .LENGTH_SHORT ).show()
999958
1000959 // Create prompt with screen information if available
1001- val genericAnalysisPrompt = " Analyze the provided screenshot and its context."
960+ val prompt = if (screenInfo != null ) {
961+ " Analyze this screenshot. Here is the available screen information: $screenInfo "
962+ } else {
963+ " Analyze this screenshot"
964+ }
1002965
1003966 // Re-send the query with only the latest screenshot
1004- reason(
1005- userInput = genericAnalysisPrompt,
1006- selectedImages = listOf (bitmap),
1007- screenInfoForPrompt = screenInfo,
1008- imageUrisForChat = listOf (screenshotUri.toString()) // Add this argument
1009- )
967+ reason(prompt, listOf (bitmap))
1010968
1011969 // Show a toast to indicate the screenshot was added
1012970 Toast .makeText(context, " Screenshot added to conversation" , Toast .LENGTH_SHORT ).show()
0 commit comments