mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(android): localize connection chat and voice states
This commit is contained in:
@@ -126,8 +126,9 @@ class NodeForegroundService : Service() {
|
||||
state.connected -> nativeString("OpenClaw Node · Connected")
|
||||
else -> nativeString("OpenClaw Node")
|
||||
}
|
||||
val displayStatus = gatewayConnectionStatusForDisplay(state.status)
|
||||
val text =
|
||||
(state.server?.let { "${state.status} · $it" } ?: state.status) +
|
||||
(state.server?.let { nativeString("\$status · \$server", displayStatus, it) } ?: displayStatus) +
|
||||
voiceNotificationSuffix(
|
||||
mode = state.mode,
|
||||
manualMicEnabled = state.capture.micEnabled,
|
||||
|
||||
@@ -319,6 +319,29 @@ data class GatewayConnectionDisplay(
|
||||
val problem: GatewayConnectionProblem?,
|
||||
)
|
||||
|
||||
private const val GATEWAY_STATUS_OFFLINE = "Offline"
|
||||
private const val GATEWAY_STATUS_CONNECTED = "Connected"
|
||||
private const val GATEWAY_STATUS_NODE_OFFLINE = "Connected (node offline)"
|
||||
private const val GATEWAY_STATUS_OPERATOR_OFFLINE = "Connected (operator offline)"
|
||||
|
||||
private fun gatewayOperatorConnectionState(operator: String): String = "Connected (operator: $operator)"
|
||||
|
||||
internal fun gatewayConnectionStatusForDisplay(statusText: String): String {
|
||||
val status = statusText.trim()
|
||||
return when {
|
||||
status.isEmpty() || status == GATEWAY_STATUS_OFFLINE -> nativeString("Offline")
|
||||
status == GATEWAY_STATUS_CONNECTED -> nativeString("Connected")
|
||||
status == GATEWAY_STATUS_NODE_OFFLINE -> nativeString("Connected (node offline)")
|
||||
status == GATEWAY_STATUS_OPERATOR_OFFLINE -> nativeString("Connected (operator offline)")
|
||||
status.startsWith("Connected (operator: ") && status.endsWith(")") ->
|
||||
nativeString(
|
||||
"Connected (operator: \$operator)",
|
||||
status.removePrefix("Connected (operator: ").dropLast(1),
|
||||
)
|
||||
else -> status
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatewayProblemAfterDisconnect(
|
||||
problem: GatewayConnectionProblem?,
|
||||
statusText: String,
|
||||
@@ -337,16 +360,16 @@ internal fun gatewayConnectionDisplay(
|
||||
val operator = operatorStatusText.trim()
|
||||
val node = nodeStatusText.trim()
|
||||
return when {
|
||||
operatorConnected && nodeConnected -> GatewayConnectionDisplay(true, "Connected", null)
|
||||
operatorConnected -> GatewayConnectionDisplay(true, "Connected (node offline)", nodeProblem)
|
||||
operatorConnected && nodeConnected -> GatewayConnectionDisplay(true, GATEWAY_STATUS_CONNECTED, null)
|
||||
operatorConnected -> GatewayConnectionDisplay(true, GATEWAY_STATUS_NODE_OFFLINE, nodeProblem)
|
||||
nodeConnected ->
|
||||
GatewayConnectionDisplay(
|
||||
isConnected = false,
|
||||
statusText =
|
||||
if (operator.isNotEmpty() && operator != "Offline") {
|
||||
"Connected (operator: $operator)"
|
||||
gatewayOperatorConnectionState(operator)
|
||||
} else {
|
||||
"Connected (operator offline)"
|
||||
GATEWAY_STATUS_OPERATOR_OFFLINE
|
||||
},
|
||||
problem = operatorProblem,
|
||||
)
|
||||
@@ -697,9 +720,9 @@ class NodeRuntime private constructor(
|
||||
private val _nodeCapabilityApproval = MutableStateFlow<GatewayNodeCapabilityApproval>(GatewayNodeCapabilityApproval.Loading)
|
||||
val nodeCapabilityApproval: StateFlow<GatewayNodeCapabilityApproval> = _nodeCapabilityApproval.asStateFlow()
|
||||
|
||||
private val _gatewayConnectionDisplay = MutableStateFlow(GatewayConnectionDisplay(false, "Offline", null))
|
||||
private val _gatewayConnectionDisplay = MutableStateFlow(GatewayConnectionDisplay(false, GATEWAY_STATUS_OFFLINE, null))
|
||||
val gatewayConnectionDisplay: StateFlow<GatewayConnectionDisplay> = _gatewayConnectionDisplay.asStateFlow()
|
||||
private val _statusText = MutableStateFlow("Offline")
|
||||
private val _statusText = MutableStateFlow(GATEWAY_STATUS_OFFLINE)
|
||||
val statusText: StateFlow<String> = _statusText.asStateFlow()
|
||||
private val _gatewayConnectionProblem = MutableStateFlow<GatewayConnectionProblem?>(null)
|
||||
val gatewayConnectionProblem: StateFlow<GatewayConnectionProblem?> = _gatewayConnectionProblem.asStateFlow()
|
||||
|
||||
@@ -424,9 +424,9 @@ private fun gatewayManualTransportPresentation(
|
||||
effectiveTls = effectiveTls,
|
||||
helperText =
|
||||
when {
|
||||
requiresTls -> "Secure connection is required for this host."
|
||||
requiresTls -> nativeString("Secure connection is required for this host.")
|
||||
effectiveTls -> null
|
||||
else -> "Use only on a trusted private network."
|
||||
else -> nativeString("Use only on a trusted private network.")
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import ai.openclaw.app.BuildConfig
|
||||
import ai.openclaw.app.GatewayConnectionProblem
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.GatewayNodeCapabilityApproval
|
||||
import ai.openclaw.app.gatewayConnectionStatusForDisplay
|
||||
import ai.openclaw.app.gateway.normalizeGatewayApprovalRequestId
|
||||
import ai.openclaw.app.i18n.nativeString
|
||||
import android.content.ClipData
|
||||
@@ -23,7 +24,7 @@ internal fun openClawAndroidVersionLabel(): String {
|
||||
}
|
||||
|
||||
/** Normalizes blank gateway status text for display and diagnostics copy. */
|
||||
internal fun gatewayStatusForDisplay(statusText: String): String = statusText.trim().ifEmpty { nativeString("Offline") }
|
||||
internal fun gatewayStatusForDisplay(statusText: String): String = gatewayConnectionStatusForDisplay(statusText)
|
||||
|
||||
/** Resolves the best non-secret endpoint label available to diagnostics surfaces. */
|
||||
internal fun gatewayDiagnosticsEndpoint(
|
||||
@@ -38,7 +39,7 @@ internal fun gatewayDiagnosticsEndpoint(
|
||||
|
||||
/** Detects pairing/approval status text so UI can offer pairing-specific actions. */
|
||||
internal fun gatewayStatusLooksLikePairing(statusText: String): Boolean {
|
||||
val lower = gatewayStatusForDisplay(statusText).lowercase()
|
||||
val lower = statusText.trim().lowercase()
|
||||
return lower.contains("pair") || lower.contains("approve")
|
||||
}
|
||||
|
||||
@@ -136,7 +137,7 @@ internal fun buildGatewayDiagnosticsReport(
|
||||
.orEmpty()
|
||||
.ifEmpty { Build.VERSION.SDK_INT.toString() }
|
||||
val endpoint = gatewayAddress.trim().ifEmpty { "unknown" }
|
||||
val status = gatewayStatusForDisplay(statusText)
|
||||
val status = statusText.trim().ifEmpty { "Offline" }
|
||||
return """
|
||||
Help diagnose this OpenClaw Android gateway connection failure.
|
||||
|
||||
|
||||
@@ -532,7 +532,7 @@ fun OnboardingFlow(
|
||||
) {
|
||||
val trimmed = code.trim()
|
||||
if (trimmed.isEmpty()) {
|
||||
setupError = "Enter the setup code from openclaw qr."
|
||||
setupError = nativeString("Enter the setup code from openclaw qr.")
|
||||
return
|
||||
}
|
||||
val plan =
|
||||
@@ -556,7 +556,7 @@ fun OnboardingFlow(
|
||||
setupError =
|
||||
endpointError?.let {
|
||||
gatewayEndpointValidationMessage(it, GatewayEndpointInputSource.SETUP_CODE)
|
||||
} ?: "Setup code was not accepted. Generate a fresh code with openclaw qr."
|
||||
} ?: nativeString("Setup code was not accepted. Generate a fresh code with openclaw qr.")
|
||||
return
|
||||
}
|
||||
connectGateway(plan = plan, inputSource = inputSource)
|
||||
@@ -574,7 +574,7 @@ fun OnboardingFlow(
|
||||
GatewayEndpointValidationError.IPV6_ZONE_ID_UNSUPPORTED,
|
||||
->
|
||||
gatewayEndpointValidationMessage(scanned.error, GatewayEndpointInputSource.QR_SCAN)
|
||||
else -> "That QR code is not an OpenClaw setup QR. Generate a fresh code with openclaw qr, then try again."
|
||||
else -> nativeString("That QR code is not an OpenClaw setup QR. Generate a fresh code with openclaw qr, then try again.")
|
||||
}
|
||||
showSetupScanError(message)
|
||||
return
|
||||
@@ -586,7 +586,7 @@ fun OnboardingFlow(
|
||||
|
||||
fun pairFromManualFields() {
|
||||
if (manualTokenLooksLikeSetupCode(token)) {
|
||||
setupError = "That looks like a setup code. Go back and choose Setup Gateway, then Use setup code."
|
||||
setupError = nativeString("That looks like a setup code. Go back and choose Setup Gateway, then Use setup code.")
|
||||
return
|
||||
}
|
||||
val transport =
|
||||
@@ -637,7 +637,7 @@ fun OnboardingFlow(
|
||||
try {
|
||||
InputImage.fromFilePath(context, uri)
|
||||
} catch (_: Exception) {
|
||||
showSetupScanError("Could not read that image. Choose a clear screenshot or image of the QR from openclaw qr.")
|
||||
showSetupScanError(nativeString("Could not read that image. Choose a clear screenshot or image of the QR from openclaw qr."))
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
setupBarcodeScanner
|
||||
@@ -645,12 +645,12 @@ fun OnboardingFlow(
|
||||
.addOnSuccessListener { barcodes ->
|
||||
val rawValue = barcodes.firstNotNullOfOrNull { barcode -> barcode.rawValue?.takeIf { it.isNotBlank() } }
|
||||
if (rawValue == null) {
|
||||
showSetupScanError("No setup QR code was found in that image. Choose the QR generated by openclaw qr, or enter the setup code manually.")
|
||||
showSetupScanError(nativeString("No setup QR code was found in that image. Choose the QR generated by openclaw qr, or enter the setup code manually."))
|
||||
return@addOnSuccessListener
|
||||
}
|
||||
handleScannedSetupCode(rawValue, inputSource = OnboardingGatewayInputSource.SetupGallery)
|
||||
}.addOnFailureListener {
|
||||
showSetupScanError("Could not read a QR code from that image. Choose a clearer image or enter the setup code manually.")
|
||||
showSetupScanError(nativeString("Could not read a QR code from that image. Choose a clearer image or enter the setup code manually."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,7 +754,7 @@ fun OnboardingFlow(
|
||||
onRequestCameraPermission = { cameraPermissionLauncher.launch(Manifest.permission.CAMERA) },
|
||||
onCodeScanned = { rawValue -> handleScannedSetupCode(rawValue, inputSource = OnboardingGatewayInputSource.SetupScanner) },
|
||||
onCameraError = {
|
||||
showSetupScanError("Could not start the camera. Choose a QR image from gallery or enter the setup code manually.")
|
||||
showSetupScanError(nativeString("Could not start the camera. Choose a QR image from gallery or enter the setup code manually."))
|
||||
},
|
||||
onCloseScanner = { inlineQrScannerActive = false },
|
||||
onChooseFromGallery = {
|
||||
@@ -2559,13 +2559,13 @@ private fun finishingGatewayProgressItems(
|
||||
|
||||
/** Detects gateway-approved states where the Android node is still coming online. */
|
||||
internal fun gatewayStatusLooksLikePartialConnect(statusText: String): Boolean {
|
||||
val lower = gatewayStatusForDisplay(statusText).lowercase()
|
||||
val lower = statusText.trim().lowercase()
|
||||
return lower.contains("operator offline") || lower.contains("node offline")
|
||||
}
|
||||
|
||||
/** Detects explicit endpoint/auth failures surfaced as status text without structured details. */
|
||||
internal fun gatewayStatusLooksLikeFailure(statusText: String): Boolean {
|
||||
val lower = gatewayStatusForDisplay(statusText).lowercase()
|
||||
val lower = statusText.trim().lowercase()
|
||||
return lower.startsWith("failed:") || lower.startsWith("error:") || lower.startsWith("gateway error:")
|
||||
}
|
||||
|
||||
@@ -2616,25 +2616,27 @@ internal fun recoveryGatewayDetail(
|
||||
gatewayConnectionProblem: GatewayConnectionProblem?,
|
||||
): String =
|
||||
if (ready) {
|
||||
remoteAddress?.takeIf { it.isNotBlank() } ?: "Ready for chat and voice"
|
||||
remoteAddress?.takeIf { it.isNotBlank() } ?: nativeString("Ready for chat and voice")
|
||||
} else if (nodeCapabilityApprovalNeedsUserAction(nodeCapabilityApproval)) {
|
||||
"Gateway paired. Waiting for node capability approval."
|
||||
nativeString("Gateway paired. Waiting for node capability approval.")
|
||||
} else if (gatewayConnectionProblem?.isPairingRequired == true && !gatewayConnectionProblem.canAutoRetry) {
|
||||
recoveryGatewayApprovalCommand(gatewayConnectionProblem)
|
||||
?.let { "Gateway approval is pending. Run this on the gateway host:" }
|
||||
?: "Gateway approval is pending. Run openclaw devices list on the gateway host, approve this phone, then retry."
|
||||
?.let { nativeString("Gateway approval is pending. Run this on the gateway host:") }
|
||||
?: nativeString(
|
||||
"Gateway approval is pending. Run openclaw devices list on the gateway host, approve this phone, then retry.",
|
||||
)
|
||||
} else if (gatewayConnectionProblem?.isPairingRequired == true && gatewayConnectionProblem.canAutoRetry) {
|
||||
"Gateway approval is in progress. OpenClaw will retry automatically."
|
||||
nativeString("Gateway approval is in progress. OpenClaw will retry automatically.")
|
||||
} else if (gatewayConnectionProblem != null) {
|
||||
recoveryGatewayAuthDetail(gatewayConnectionProblem)
|
||||
} else if (nodeCapabilityApproval == GatewayNodeCapabilityApproval.Loading) {
|
||||
"Gateway paired. Checking node capability approval."
|
||||
nativeString("Gateway paired. Checking node capability approval.")
|
||||
} else if (statusText.contains("operator offline", ignoreCase = true)) {
|
||||
"Gateway paired. Waiting for operator access."
|
||||
nativeString("Gateway paired. Waiting for operator access.")
|
||||
} else if (gatewayStatusLooksLikePairing(statusText)) {
|
||||
"Gateway approval is in progress. OpenClaw will retry automatically."
|
||||
nativeString("Gateway approval is in progress. OpenClaw will retry automatically.")
|
||||
} else {
|
||||
remoteAddress?.takeIf { it.isNotBlank() } ?: "Gateway unreachable"
|
||||
remoteAddress?.takeIf { it.isNotBlank() } ?: nativeString("Gateway unreachable")
|
||||
}
|
||||
|
||||
internal fun recoveryGatewayAuthDetail(gatewayConnectionProblem: GatewayConnectionProblem): String =
|
||||
@@ -2655,7 +2657,7 @@ internal fun recoveryGatewayAuthDetail(gatewayConnectionProblem: GatewayConnecti
|
||||
"update_auth_credentials" -> nativeString("Saved authentication is invalid. Re-authenticate or reset this gateway connection.")
|
||||
"update_auth_configuration" -> nativeString("Gateway authentication is not configured. Edit this connection and try again.")
|
||||
"review_auth_configuration" -> nativeString("Gateway authentication needs review. Check gateway settings, then retry.")
|
||||
else -> gatewayConnectionProblem.message.takeIf { it.isNotBlank() } ?: "Gateway authentication needs attention."
|
||||
else -> gatewayConnectionProblem.message.takeIf { it.isNotBlank() } ?: nativeString("Gateway authentication needs attention.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2666,12 +2668,12 @@ private fun recoveryGatewayProtocolMismatchDetail(gatewayConnectionProblem: Gate
|
||||
val summary =
|
||||
when {
|
||||
clientMax != null && expected != null && clientMax < expected ->
|
||||
"This app is older than the Gateway. Update OpenClaw on this device, then retry."
|
||||
nativeString("This app is older than the Gateway. Update OpenClaw on this device, then retry.")
|
||||
clientMin != null && expected != null && clientMin > expected ->
|
||||
"The Gateway is older than this app. Update OpenClaw on the Gateway host, then retry."
|
||||
else -> "The app and Gateway use incompatible protocol versions. Update OpenClaw on both, then retry."
|
||||
nativeString("The Gateway is older than this app. Update OpenClaw on the Gateway host, then retry.")
|
||||
else -> nativeString("The app and Gateway use incompatible protocol versions. Update OpenClaw on both, then retry.")
|
||||
}
|
||||
return protocolMismatchVersions(clientMin, clientMax, expected)?.let { "$summary $it" } ?: summary
|
||||
return protocolMismatchVersions(clientMin, clientMax, expected)?.let { nativeString("\$summary \$details", summary, it) } ?: summary
|
||||
}
|
||||
|
||||
internal fun recoveryGatewayProtocolMismatchCommand(
|
||||
@@ -2863,9 +2865,9 @@ internal fun cameraPermissionRowStatusText(
|
||||
androidCameraPermissionGranted: Boolean,
|
||||
): String =
|
||||
when {
|
||||
capabilityEnabled -> "Enabled"
|
||||
androidCameraPermissionGranted -> "Off"
|
||||
else -> "Not allowed"
|
||||
capabilityEnabled -> nativeString("Enabled")
|
||||
androidCameraPermissionGranted -> nativeString("Off")
|
||||
else -> nativeString("Not allowed")
|
||||
}
|
||||
|
||||
internal fun cameraCapabilityAfterRowTap(
|
||||
|
||||
@@ -722,7 +722,7 @@ private fun InlineBase64Image(
|
||||
if (image != null) {
|
||||
Image(
|
||||
bitmap = image,
|
||||
contentDescription = mimeType ?: "image",
|
||||
contentDescription = mimeType ?: nativeString("Image"),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -433,19 +433,19 @@ fun ChatOutboxBubble(
|
||||
val statusColor = if (failed) mobileDanger else mobileWarning
|
||||
val statusLabel =
|
||||
when (item.status) {
|
||||
ChatOutboxStatus.Queued -> "Queued — sends when reconnected"
|
||||
ChatOutboxStatus.Sending -> "Sending…"
|
||||
ChatOutboxStatus.Accepted -> "Sent — confirming delivery…"
|
||||
ChatOutboxStatus.Queued -> nativeString("Queued — sends when reconnected")
|
||||
ChatOutboxStatus.Sending -> nativeString("Sending…")
|
||||
ChatOutboxStatus.Accepted -> nativeString("Sent — confirming delivery…")
|
||||
ChatOutboxStatus.Failed ->
|
||||
item.lastError
|
||||
?.trim()
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { "Failed — $it" } ?: "Failed"
|
||||
?.let { nativeString("Failed — \$it", it) } ?: nativeString("Failed")
|
||||
}
|
||||
|
||||
ChatBubbleContainer(
|
||||
style = bubbleStyle("user").copy(borderColor = statusColor.copy(alpha = 0.6f)),
|
||||
roleLabel = "You",
|
||||
roleLabel = nativeString("You"),
|
||||
) {
|
||||
if (item.text.isNotBlank()) {
|
||||
ChatMarkdown(text = item.text, textColor = mobileText)
|
||||
@@ -566,7 +566,7 @@ internal fun ChatBase64Image(
|
||||
Box {
|
||||
Image(
|
||||
bitmap = image,
|
||||
contentDescription = mimeType ?: "attachment",
|
||||
contentDescription = mimeType ?: nativeString("Attachment"),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -309,7 +309,7 @@ fun ChatScreen(
|
||||
attachments.addAll(merged.attachments)
|
||||
shareImportNotice =
|
||||
if (merged.failedImageCount + merged.droppedImageCount > 0) {
|
||||
"Some shared images were omitted or could not be added."
|
||||
nativeString("Some shared images were omitted or could not be added.")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -653,9 +653,9 @@ private fun ChatHeader(
|
||||
ModelPill(
|
||||
text =
|
||||
when {
|
||||
pendingRunCount > 0 -> "Working"
|
||||
healthOk -> "Ready"
|
||||
else -> "Offline"
|
||||
pendingRunCount > 0 -> nativeString("Working")
|
||||
healthOk -> nativeString("Ready")
|
||||
else -> nativeString("Offline")
|
||||
},
|
||||
status =
|
||||
when {
|
||||
@@ -1039,9 +1039,9 @@ private fun ChatBubble(
|
||||
Text(
|
||||
text =
|
||||
when {
|
||||
live -> "OpenClaw · Live"
|
||||
isUser -> "You"
|
||||
normalizedRole == "system" -> "System"
|
||||
live -> nativeString("OpenClaw · Live")
|
||||
isUser -> nativeString("You")
|
||||
normalizedRole == "system" -> nativeString("System")
|
||||
else -> nativeString("OpenClaw")
|
||||
},
|
||||
style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp, fontWeight = FontWeight.SemiBold),
|
||||
@@ -1056,7 +1056,7 @@ private fun ChatBubble(
|
||||
base64 = checkNotNull(part.base64),
|
||||
mimeType = part.mimeType,
|
||||
)
|
||||
else -> Text(text = part.fileName ?: "Attachment", style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
|
||||
else -> Text(text = part.fileName ?: nativeString("Attachment"), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
|
||||
}
|
||||
}
|
||||
if (messageId != null) {
|
||||
@@ -1569,7 +1569,7 @@ private fun SlashCommandRow(
|
||||
)
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||
Text(
|
||||
text = command.description.ifBlank { command.category ?: "Command" },
|
||||
text = command.description.ifBlank { command.category ?: nativeString("Command") },
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
maxLines = 1,
|
||||
@@ -1786,7 +1786,7 @@ private fun AttachmentChip(
|
||||
}
|
||||
Text(
|
||||
text =
|
||||
attachment.durationMs?.let { duration -> "Voice note · ${formatVoiceNoteDuration(duration)}" }
|
||||
attachment.durationMs?.let { duration -> nativeString("Voice note · \${formatVoiceNoteDuration(duration)}", formatVoiceNoteDuration(duration)) }
|
||||
?: attachment.fileName,
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
@@ -1817,7 +1817,7 @@ private fun chatSessionChipText(
|
||||
): String {
|
||||
val mainKey = mainSessionKey.trim().ifEmpty { "main" }
|
||||
if (entry.key == mainKey || (entry.key == "main" && mainKey == "main")) return nativeString("Main")
|
||||
val name = entry.displayName?.takeIf { it.isNotBlank() } ?: entry.key.takeIf { entry.updatedAtMs != null } ?: "Current"
|
||||
val name = entry.displayName?.takeIf { it.isNotBlank() } ?: entry.key.takeIf { entry.updatedAtMs != null } ?: nativeString("Current")
|
||||
return friendlySessionName(name)
|
||||
}
|
||||
|
||||
@@ -1894,9 +1894,9 @@ internal fun userFacingChatError(
|
||||
): String {
|
||||
val lower = error.lowercase(Locale.US)
|
||||
return when {
|
||||
lower.contains("not connected") && gatewayConnected -> "Chat is still checking Gateway health."
|
||||
lower.contains("not connected") -> "Gateway is offline. Fix the connection below or copy diagnostics."
|
||||
lower.contains("unauthorized") || lower.contains("auth") -> "Gateway authentication needs attention."
|
||||
lower.contains("not connected") && gatewayConnected -> nativeString("Chat is still checking Gateway health.")
|
||||
lower.contains("not connected") -> nativeString("Gateway is offline. Fix the connection below or copy diagnostics.")
|
||||
lower.contains("unauthorized") || lower.contains("auth") -> nativeString("Gateway authentication needs attention.")
|
||||
else -> error
|
||||
}
|
||||
}
|
||||
@@ -1913,7 +1913,10 @@ internal fun contextMeterLabel(
|
||||
thinkingLevel: String,
|
||||
thinkingSupported: Boolean = true,
|
||||
): String {
|
||||
val contextLabel = contextMeterWidth(usage)?.let { "Context ${(it * 100).roundToInt()}%" } ?: "Context --"
|
||||
val contextLabel =
|
||||
contextMeterWidth(usage)?.let {
|
||||
nativeString("Context \${(it * 100).roundToInt()}%", (it * 100).roundToInt())
|
||||
} ?: nativeString("Context --")
|
||||
return if (thinkingSupported) nativeString("\$contextLabel · \${contextMeterThinkingLabel(thinkingLevel)}", contextLabel, contextMeterThinkingLabel(thinkingLevel)) else contextLabel
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ internal class MicCaptureManager(
|
||||
_statusText.value =
|
||||
when {
|
||||
_micEnabled.value && _isSending.value -> nativeString("Listening · sending queued voice")
|
||||
_micEnabled.value -> "Listening"
|
||||
_micEnabled.value -> nativeString("Listening")
|
||||
_isSending.value -> nativeString("Mic off · sending…")
|
||||
else -> nativeString("Mic off")
|
||||
}
|
||||
@@ -602,7 +602,8 @@ internal class MicCaptureManager(
|
||||
sendQueuedIfIdle()
|
||||
}
|
||||
|
||||
private fun queuedWaitingStatus(): String = "${queuedMessageCount()} queued · waiting for gateway"
|
||||
private fun queuedWaitingStatus(): String =
|
||||
nativeString("\${queuedMessageCount()} queued · waiting for gateway", queuedMessageCount())
|
||||
|
||||
private fun appendConversation(
|
||||
role: VoiceConversationRole,
|
||||
@@ -783,8 +784,8 @@ internal class MicCaptureManager(
|
||||
private fun listeningStatus(): String =
|
||||
when {
|
||||
_isSending.value -> nativeString("Listening · sending queued voice")
|
||||
hasQueuedMessages() -> "Listening · ${queuedMessageCount()} queued"
|
||||
else -> "Listening"
|
||||
hasQueuedMessages() -> nativeString("Listening · \${queuedMessageCount()} queued", queuedMessageCount())
|
||||
else -> nativeString("Listening")
|
||||
}
|
||||
|
||||
private fun pcm16ToPcmu(pcm16: ByteArray): ByteArray {
|
||||
|
||||
@@ -2798,21 +2798,21 @@ class TalkModeManager internal constructor(
|
||||
if (stopRequested) return
|
||||
_isListening.value = false
|
||||
if (error == SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS) {
|
||||
setStatus("Microphone permission required")
|
||||
setStatus(nativeString("Microphone permission required"))
|
||||
return
|
||||
}
|
||||
|
||||
setStatus(
|
||||
when (error) {
|
||||
SpeechRecognizer.ERROR_AUDIO -> "Audio error"
|
||||
SpeechRecognizer.ERROR_CLIENT -> "Client error"
|
||||
SpeechRecognizer.ERROR_NETWORK -> "Network error"
|
||||
SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> "Network timeout"
|
||||
SpeechRecognizer.ERROR_AUDIO -> nativeString("Audio error")
|
||||
SpeechRecognizer.ERROR_CLIENT -> nativeString("Client error")
|
||||
SpeechRecognizer.ERROR_NETWORK -> nativeString("Network error")
|
||||
SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> nativeString("Network timeout")
|
||||
SpeechRecognizer.ERROR_NO_MATCH -> nativeString("Listening")
|
||||
SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> "Recognizer busy"
|
||||
SpeechRecognizer.ERROR_SERVER -> "Server error"
|
||||
SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> nativeString("Recognizer busy")
|
||||
SpeechRecognizer.ERROR_SERVER -> nativeString("Server error")
|
||||
SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> nativeString("Listening")
|
||||
else -> "Speech error ($error)"
|
||||
else -> nativeString("Speech error (\$error)", error)
|
||||
},
|
||||
)
|
||||
scheduleRestart(delayMs = 600)
|
||||
|
||||
Reference in New Issue
Block a user