Skip to content

Commit 10b3bb3

Browse files
authored
Merge pull request #20979 from wordpress-mobile/issue/v2c-error-logging-tracking
[Voice to Content] Error logging, tracking, and error state
2 parents d2199d4 + 028cb1e commit 10b3bb3

11 files changed

Lines changed: 226 additions & 48 deletions

File tree

WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,11 @@ class ActivityNavigator @Inject constructor() {
201201
.addNextIntent(intent)
202202
.startActivities()
203203
}
204+
205+
fun openIneligibleForVoiceToContent(
206+
context: Context,
207+
url: String
208+
) {
209+
WPWebViewActivity.openUrlByUsingGlobalWPCOMCredentials(context, url)
210+
}
204211
}

WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,38 @@ import org.wordpress.android.fluxc.model.SiteModel
66
import org.wordpress.android.fluxc.model.jetpackai.JetpackAIAssistantFeature
77
import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAIAssistantFeatureResponse
88
import org.wordpress.android.fluxc.store.jetpackai.JetpackAIStore
9+
import org.wordpress.android.ui.voicetocontent.PrepareVoiceToContentResult.Success
10+
import org.wordpress.android.ui.voicetocontent.PrepareVoiceToContentResult.Failure.NetworkUnavailable
11+
import org.wordpress.android.ui.voicetocontent.PrepareVoiceToContentResult.Failure.RemoteRequestFailure
12+
import org.wordpress.android.util.NetworkUtilsWrapper
913
import javax.inject.Inject
1014

1115
class PrepareVoiceToContentUseCase @Inject constructor(
12-
private val jetpackAIStore: JetpackAIStore
16+
private val jetpackAIStore: JetpackAIStore,
17+
private val networkUtilsWrapper: NetworkUtilsWrapper,
18+
private val logger: VoiceToContentTelemetry
1319
) {
1420
suspend fun execute(site: SiteModel): PrepareVoiceToContentResult =
1521
withContext(Dispatchers.IO) {
22+
if (!networkUtilsWrapper.isNetworkAvailable()) {
23+
return@withContext NetworkUnavailable
24+
}
1625
when (val response = jetpackAIStore.fetchJetpackAIAssistantFeature(site)) {
1726
is JetpackAIAssistantFeatureResponse.Success -> {
18-
PrepareVoiceToContentResult.Success(model = response.model)
27+
Success(model = response.model)
1928
}
2029
is JetpackAIAssistantFeatureResponse.Error -> {
21-
PrepareVoiceToContentResult.Error
30+
logger.logError("${response.type.name} - ${response.message}")
31+
RemoteRequestFailure
2232
}
2333
}
2434
}
2535
}
2636

2737
sealed class PrepareVoiceToContentResult {
2838
data class Success(val model: JetpackAIAssistantFeature) : PrepareVoiceToContentResult()
29-
data object Error : PrepareVoiceToContentResult()
39+
sealed class Failure: PrepareVoiceToContentResult() {
40+
data object NetworkUnavailable: Failure()
41+
data object RemoteRequestFailure: Failure()
42+
}
3043
}

WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ import org.wordpress.android.R
1717
import org.wordpress.android.util.audio.IAudioRecorder.Companion.REQUIRED_RECORDING_PERMISSIONS
1818
import android.provider.Settings
1919
import androidx.compose.material.ExperimentalMaterialApi
20+
import org.wordpress.android.ui.ActivityNavigator
21+
import javax.inject.Inject
2022

2123
@AndroidEntryPoint
2224
class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
25+
@Inject
26+
lateinit var activityNavigator: ActivityNavigator
27+
2328
private val viewModel: VoiceToContentViewModel by viewModels()
2429

2530
@ExperimentalMaterialApi
@@ -49,6 +54,10 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
4954
viewModel.dismiss.observe(viewLifecycleOwner) {
5055
dismiss()
5156
}
57+
58+
viewModel.onIneligibleForVoiceToContent.observe(viewLifecycleOwner) { url ->
59+
launchIneligibleForVoiceToContent(url)
60+
}
5261
}
5362

5463
private val requestMultiplePermissionsLauncher = registerForActivityResult(
@@ -84,6 +93,12 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
8493
.show()
8594
}
8695

96+
private fun launchIneligibleForVoiceToContent(url: String) {
97+
context?.let {
98+
activityNavigator.openIneligibleForVoiceToContent(it, url)
99+
}
100+
}
101+
87102
companion object {
88103
const val TAG = "voice_to_content_fragment_tag"
89104

WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.material.MaterialTheme
2424
import androidx.compose.material.Text
2525
import androidx.compose.material.icons.Icons
2626
import androidx.compose.material.icons.filled.Close
27+
import androidx.compose.material.icons.filled.Refresh
2728
import androidx.compose.material3.Surface
2829
import androidx.compose.runtime.Composable
2930
import androidx.compose.runtime.collectAsState
@@ -117,7 +118,12 @@ fun ErrorView(model: VoiceToContentUiState) {
117118
) {
118119
Header(model.header)
119120
Spacer(modifier = Modifier.height(16.dp))
120-
Text("Unable to use Voice to Content at the moment, please try again later")
121+
Text(stringResource(id = model.errorPanel?.errorMessage?:R.string.voice_to_content_generic_error))
122+
if (model.errorPanel?.allowRetry == true) {
123+
IconButton(onClick = model.errorPanel.onRetryTap?:{}) {
124+
Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
125+
}
126+
}
121127
}
122128
}
123129

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.wordpress.android.ui.voicetocontent
2+
3+
import org.wordpress.android.analytics.AnalyticsTracker.Stat
4+
import org.wordpress.android.fluxc.utils.AppLogWrapper
5+
import org.wordpress.android.util.AppLog.T.POSTS
6+
import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
7+
import javax.inject.Inject
8+
9+
class VoiceToContentTelemetry @Inject constructor(
10+
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper,
11+
private val appLogWrapper: AppLogWrapper
12+
) {
13+
fun track(stat: Stat) {
14+
analyticsTrackerWrapper.track(stat)
15+
}
16+
17+
fun track(stat: Stat, properties: Map<String, Any?>) {
18+
analyticsTrackerWrapper.track(stat, properties)
19+
}
20+
21+
fun logError(message: String) {
22+
appLogWrapper.e(POSTS, "Voice to content $message")
23+
}
24+
}

WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ data class RecordingPanelUIModel(
3232
@StringRes val actionLabel: Int
3333
)
3434

35+
data class ErrorUiModel(
36+
@StringRes val errorMessage: Int? = null,
37+
val allowRetry: Boolean = false,
38+
val onRetryTap: (() -> Unit)? = null
39+
)
40+
3541
enum class VoiceToContentUIStateType(val trackingName: String) {
3642
INITIALIZING("initializing"),
3743
READY_TO_RECORD("ready_to_record"),
@@ -45,5 +51,6 @@ data class VoiceToContentUiState(
4551
val uiStateType: VoiceToContentUIStateType,
4652
val header: HeaderUIModel,
4753
val secondaryHeader: SecondaryHeaderUIModel? = null,
48-
val recordingPanel: RecordingPanelUIModel? = null
54+
val recordingPanel: RecordingPanelUIModel? = null,
55+
val errorPanel: ErrorUiModel? = null
4956
)
Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package org.wordpress.android.ui.voicetocontent
22

3-
import android.util.Log
43
import kotlinx.coroutines.Dispatchers
54
import kotlinx.coroutines.withContext
65
import org.wordpress.android.fluxc.model.SiteModel
76
import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAIQueryResponse
87
import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAITranscriptionResponse
98
import org.wordpress.android.fluxc.store.jetpackai.JetpackAIStore
9+
import org.wordpress.android.util.NetworkUtilsWrapper
10+
import org.wordpress.android.ui.voicetocontent.VoiceToContentResult.Failure.NetworkUnavailable
11+
import org.wordpress.android.ui.voicetocontent.VoiceToContentResult.Failure.RemoteRequestFailure
12+
import org.wordpress.android.ui.voicetocontent.VoiceToContentResult.Success
1013
import java.io.File
1114
import javax.inject.Inject
1215

1316
class VoiceToContentUseCase @Inject constructor(
14-
private val jetpackAIStore: JetpackAIStore
17+
private val jetpackAIStore: JetpackAIStore,
18+
private val networkUtilsWrapper: NetworkUtilsWrapper,
19+
private val logger: VoiceToContentTelemetry
1520
) {
1621
companion object {
1722
const val FEATURE = "voice_to_content"
@@ -25,6 +30,10 @@ class VoiceToContentUseCase @Inject constructor(
2530
file: File
2631
): VoiceToContentResult =
2732
withContext(Dispatchers.IO) {
33+
if (!networkUtilsWrapper.isNetworkAvailable()) {
34+
return@withContext NetworkUnavailable
35+
}
36+
2837
val transcriptionResponse = jetpackAIStore.fetchJetpackAITranscription(
2938
siteModel,
3039
FEATURE,
@@ -36,21 +45,17 @@ class VoiceToContentUseCase @Inject constructor(
3645
transcriptionResponse.model
3746
}
3847
is JetpackAITranscriptionResponse.Error -> {
39-
val message = "${transcriptionResponse.type} ${transcriptionResponse.message}"
40-
Log.i(
41-
javaClass.simpleName,
42-
"Error transcribing audio file: $message"
43-
)
48+
logger.logError("${transcriptionResponse.type} ${transcriptionResponse.message}")
4449
null
4550
}
4651
}
4752

48-
transcribedText?.let {
53+
transcribedText?.let { transcribed ->
4954
val response = jetpackAIStore.fetchJetpackAIQuery(
5055
site = siteModel,
5156
feature = FEATURE,
5257
role = ROLE,
53-
message = it,
58+
message = transcribed,
5459
stream = false,
5560
type = TYPE
5661
)
@@ -61,22 +66,33 @@ class VoiceToContentUseCase @Inject constructor(
6166
// __JETPACK_AI_ERROR__ is a special marker we ask GPT to add to the request when it can’t
6267
// understand the request for any reason, so maybe something confused GPT on some requests.
6368
if (finalContent == JETPACK_AI_ERROR) {
64-
return@withContext VoiceToContentResult(isError = true)
69+
// Send back the transcribed text here
70+
logger.logError(JETPACK_AI_ERROR)
71+
return@withContext Success(content = transcribed)
6572
} else {
66-
return@withContext VoiceToContentResult(content = response.choices[0].message.content)
73+
return@withContext Success(content = response.choices[0].message.content)
6774
}
6875
}
6976

7077
is JetpackAIQueryResponse.Error -> {
71-
return@withContext VoiceToContentResult(isError = true)
78+
logger.logError("${response.type.name} - ${response.message}")
79+
return@withContext Success(content = transcribed)
7280
}
7381
}
74-
} ?:return@withContext VoiceToContentResult(isError = true)
82+
} ?: run {
83+
logger.logError("Unable to transcribe audio content")
84+
return@withContext RemoteRequestFailure
85+
}
7586
}
7687
}
7788

78-
// todo: build out the result object
79-
data class VoiceToContentResult(
80-
val content: String? = null,
81-
val isError: Boolean = false
82-
)
89+
sealed class VoiceToContentResult {
90+
data class Success(
91+
val content: String
92+
): VoiceToContentResult()
93+
94+
sealed class Failure: VoiceToContentResult() {
95+
data object NetworkUnavailable: Failure()
96+
data object RemoteRequestFailure: Failure()
97+
}
98+
}

0 commit comments

Comments
 (0)