fix: resolve all build compilation and style check warnings (#597)
* fix: resolve all build compilation and style check warnings * fix: remove unused import in SystemViewModel
This commit is contained in:
28 files changed
+293
-192
No files matched your search
+6
-1
@@ -25,7 +25,12 @@ internal object DashboardSessionTokenRefresher {
|
||||
client: OkHttpClient,
|
||||
): String? =
|
||||
try {
|
||||
val request = Request.Builder().url(baseUrl).get().build()
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.url(baseUrl)
|
||||
.get()
|
||||
.build()
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) return null
|
||||
// Cap the read — the SPA HTML can be large, and we only need the
|
||||
|
||||
@@ -276,7 +276,7 @@ object HermesWsClient {
|
||||
}
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body?.string() ?: ""
|
||||
val body = response.body.string()
|
||||
val ticketMatch = Regex("""\"ticket\":\"([^\"]+)\"""").find(body)
|
||||
val ticket = ticketMatch?.groupValues?.getOrNull(1)
|
||||
if (!ticket.isNullOrBlank()) {
|
||||
|
||||
@@ -73,26 +73,31 @@ fun AnalyticsScreen(
|
||||
modifier = modifier,
|
||||
) {
|
||||
when {
|
||||
state.isLoading && state.usage == null -> LoadingState()
|
||||
state.isLoading && state.usage == null -> {
|
||||
LoadingState()
|
||||
}
|
||||
|
||||
state.errorMessage != null && state.usage == null ->
|
||||
state.errorMessage != null && state.usage == null -> {
|
||||
ErrorState(
|
||||
message = state.errorMessage ?: stringResource(R.string.error_unknown),
|
||||
onRetry = { viewModel.load() },
|
||||
)
|
||||
}
|
||||
|
||||
state.usage == null ->
|
||||
state.usage == null -> {
|
||||
EmptyState(
|
||||
title = stringResource(R.string.analytics_empty_title),
|
||||
subtitle = stringResource(R.string.analytics_empty_desc),
|
||||
icon = Icons.Filled.BarChart,
|
||||
)
|
||||
}
|
||||
|
||||
else ->
|
||||
else -> {
|
||||
AnalyticsContent(
|
||||
state = state,
|
||||
onDaysSelected = viewModel::setDays,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,13 +105,21 @@ class AnalyticsViewModel : ViewModel() {
|
||||
}
|
||||
val error =
|
||||
when {
|
||||
usage == null && models == null ->
|
||||
usage == null && models == null -> {
|
||||
failure?.message ?: "Failed to load analytics"
|
||||
usage == null ->
|
||||
}
|
||||
|
||||
usage == null -> {
|
||||
failure?.message ?: "Failed to load usage analytics"
|
||||
models == null ->
|
||||
}
|
||||
|
||||
models == null -> {
|
||||
failure?.message ?: "Failed to load model analytics"
|
||||
else -> null
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
// Only update UI if this is still the active day window.
|
||||
if (_uiState.value.days == days) {
|
||||
|
||||
@@ -228,7 +228,7 @@ class AuthLoginViewModel(
|
||||
.get()
|
||||
.build()
|
||||
val resp = probeClient.newCall(req).execute()
|
||||
val body = resp.body?.string() ?: ""
|
||||
val body = resp.body.string()
|
||||
// Extract __HERMES_SESSION_TOKEN__ from the SPA HTML
|
||||
val tokenMatch = Regex("""__HERMES_SESSION_TOKEN__\s*=\s*"([^"]+)"""").find(body)
|
||||
extractedToken = tokenMatch?.groupValues?.getOrNull(1)
|
||||
@@ -453,7 +453,7 @@ class AuthLoginViewModel(
|
||||
return null
|
||||
}
|
||||
|
||||
val ticketBody = ticketResp.body?.string() ?: ""
|
||||
val ticketBody = ticketResp.body.string()
|
||||
val ticketMatch = Regex(""""ticket":"([^"]+)"""").find(ticketBody)
|
||||
val ticket = ticketMatch?.groupValues?.getOrNull(1)
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ class ChannelsViewModel :
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
platforms = data?.platforms.orEmpty(),
|
||||
gatewayStartCommand = data?.gatewayStartCommand ?: "hermes gateway start",
|
||||
envPath = data?.envPath ?: "~/.hermes/.env",
|
||||
platforms = data.platforms.orEmpty(),
|
||||
gatewayStartCommand = data.gatewayStartCommand,
|
||||
envPath = data.envPath,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -218,7 +218,7 @@ class ChannelsViewModel :
|
||||
apiCall = { safeApiCall { ApiClient.hermesApi.testMessagingPlatform(platformId) } },
|
||||
onStart = {},
|
||||
onSuccess = { result ->
|
||||
val msg = result?.message ?: "Test complete"
|
||||
val msg = result.message
|
||||
_uiState.update {
|
||||
it.copy(testingId = null, toastMessage = msg)
|
||||
}
|
||||
@@ -297,8 +297,8 @@ class ChannelsViewModel :
|
||||
it.copy(
|
||||
onboardingPhase = OnboardingPhase.WAITING,
|
||||
onboardingSetup = setup,
|
||||
onboardingQrDataUrl = setup?.qrPayload,
|
||||
onboardingExpiresIn = formatExpiry(setup?.expiresAt),
|
||||
onboardingQrDataUrl = setup.qrPayload,
|
||||
onboardingExpiresIn = formatExpiry(setup.expiresAt),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ class ChannelsViewModel :
|
||||
when (result) {
|
||||
is com.m57.hermescontrol.data.remote.NetworkResult.Success -> {
|
||||
val status = result.data
|
||||
if (status?.status == "ready") {
|
||||
if (status.status == "ready") {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
onboardingPhase = OnboardingPhase.READY,
|
||||
@@ -466,7 +466,7 @@ class ChannelsViewModel :
|
||||
)
|
||||
}
|
||||
resetOnboarding()
|
||||
if (applyResult?.needsRestart == true) {
|
||||
if (applyResult.needsRestart) {
|
||||
restartGateway()
|
||||
} else {
|
||||
loadPlatforms()
|
||||
|
||||
@@ -655,7 +655,7 @@ fun parseToolOutput(
|
||||
val truncated = if (raw.length > 100) raw.take(100) + "…" else raw
|
||||
if (truncated.isNotBlank()) "${config.summaryPrefix}$truncated" else null
|
||||
} else {
|
||||
null ?: obj
|
||||
obj
|
||||
.get("context")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
@@ -764,7 +764,7 @@ fun parseToolOutput(
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = factSummaryText,
|
||||
@@ -900,7 +900,7 @@ fun parseToolOutput(
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = ssSummaryText,
|
||||
@@ -922,13 +922,24 @@ fun parseToolOutput(
|
||||
val mainOutput =
|
||||
when {
|
||||
errorMsg != null -> "❌ $errorMsg"
|
||||
|
||||
!success -> "❌ Failed"
|
||||
|
||||
usage != null -> "✅ $action in ${target ?: "memory"}\n $usage"
|
||||
else -> "✅ ${target?.capitalize() ?: "Memory"} $action done"
|
||||
|
||||
else -> "✅ ${target?.replaceFirstChar {
|
||||
if (it.isLowerCase()) {
|
||||
it.titlecase(
|
||||
java.util.Locale.getDefault(),
|
||||
)
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
} ?: "Memory"} $action done"
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -985,7 +996,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1047,7 +1058,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1081,7 +1092,7 @@ fun parseToolOutput(
|
||||
}?.joinToString("\n\n") ?: "📚 No skills found"
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1120,7 +1131,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1181,7 +1192,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1229,7 +1240,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1256,7 +1267,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1273,7 +1284,7 @@ fun parseToolOutput(
|
||||
val success = dataSource.get("success")?.takeIf { !it.isJsonNull }?.asBoolean ?: true
|
||||
val errorMsg = dataSource.get("error")?.takeIf { !it.isJsonNull }?.asString
|
||||
|
||||
val urlSummary = urls?.joinToString(", ") { it.asString?.take(40) ?: "?" } ?: ""
|
||||
val urlSummary = urls?.joinToString(", ") { it.asString.take(40) } ?: ""
|
||||
val summaryText = "🕸️ ${urls?.size() ?: 0} page(s)"
|
||||
val mainOutput =
|
||||
when {
|
||||
@@ -1293,7 +1304,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1328,7 +1339,7 @@ fun parseToolOutput(
|
||||
}?.joinToString("\n\n") ?: "🔍 No matching tools"
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1372,7 +1383,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1410,7 +1421,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1443,7 +1454,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1488,7 +1499,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryText,
|
||||
@@ -1557,75 +1568,80 @@ fun parseToolOutput(
|
||||
buildString {
|
||||
if (errorMsg != null) {
|
||||
append("❌ $errorMsg")
|
||||
} else if (multimodalText != null) {
|
||||
append(multimodalText)
|
||||
if (textSummary != null && multimodalText != textSummary) {
|
||||
append("\n\n$textSummary")
|
||||
}
|
||||
} else if (summaryText != null) {
|
||||
append(summaryText)
|
||||
} else if (textSummary != null) {
|
||||
append(textSummary)
|
||||
} else if (appsArr != null) {
|
||||
appsArr.forEachIndexed { idx, el ->
|
||||
if (el.isJsonObject) {
|
||||
val obj = el.asJsonObject
|
||||
val name =
|
||||
obj
|
||||
.get("name")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString ?: "?"
|
||||
val pid =
|
||||
obj
|
||||
.get("pid")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asInt
|
||||
append("${idx + 1}. $name")
|
||||
if (pid != null) append(" (PID: $pid)")
|
||||
append("\n")
|
||||
} else {
|
||||
val textPart = multimodalText ?: summaryText ?: textSummary
|
||||
if (textPart != null) {
|
||||
append(textPart)
|
||||
if (textPart == multimodalText && textSummary != null && multimodalText != textSummary) {
|
||||
append("\n\n$textSummary")
|
||||
}
|
||||
}
|
||||
} else if (elementsArr != null) {
|
||||
append("🖥️ ${elementsArr.size()} interactable elements")
|
||||
if (mode != null) append(" (mode: $mode)")
|
||||
if (width != null && height != null) append(" • ${width}x$height")
|
||||
if (textSummary != null) append("\n\n$textSummary")
|
||||
// Show first few elements as preview
|
||||
val previewCount = minOf(elementsArr.size(), 5)
|
||||
if (previewCount > 0) {
|
||||
append("\n\n")
|
||||
for (i in 0 until previewCount) {
|
||||
val el = elementsArr[i]
|
||||
|
||||
if (appsArr != null) {
|
||||
if (isNotEmpty()) append("\n\n")
|
||||
appsArr.forEachIndexed { idx, el ->
|
||||
if (el.isJsonObject) {
|
||||
val obj = el.asJsonObject
|
||||
val idx =
|
||||
val name =
|
||||
obj
|
||||
.get("index")
|
||||
.get("name")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString ?: "?"
|
||||
val pid =
|
||||
obj
|
||||
.get("pid")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asInt
|
||||
val role =
|
||||
obj
|
||||
.get("role")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
val label =
|
||||
obj
|
||||
.get("label")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
append(" #$idx ${role ?: ""}${label?.let { " '$it'" } ?: ""}\n")
|
||||
append("${idx + 1}. $name")
|
||||
if (pid != null) append(" (PID: $pid)")
|
||||
append("\n")
|
||||
}
|
||||
}
|
||||
if (elementsArr.size() > previewCount) {
|
||||
append(" ... and ${elementsArr.size() - previewCount} more")
|
||||
} else if (elementsArr != null) {
|
||||
if (isNotEmpty()) append("\n\n")
|
||||
append("🖥️ ${elementsArr.size()} interactable elements")
|
||||
if (mode != null) append(" (mode: $mode)")
|
||||
if (width != null && height != null) append(" • ${width}x$height")
|
||||
if (textSummary != null) append("\n\n$textSummary")
|
||||
// Show first few elements as preview
|
||||
val previewCount = minOf(elementsArr.size(), 5)
|
||||
if (previewCount > 0) {
|
||||
append("\n\n")
|
||||
for (i in 0 until previewCount) {
|
||||
val el = elementsArr[i]
|
||||
if (el.isJsonObject) {
|
||||
val obj = el.asJsonObject
|
||||
val idx =
|
||||
obj
|
||||
.get("index")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asInt
|
||||
val role =
|
||||
obj
|
||||
.get("role")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
val label =
|
||||
obj
|
||||
.get("label")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
append(" #$idx ${role ?: ""}${label?.let { " '$it'" } ?: ""}\n")
|
||||
}
|
||||
}
|
||||
if (elementsArr.size() > previewCount) {
|
||||
append(" ... and ${elementsArr.size() - previewCount} more")
|
||||
}
|
||||
}
|
||||
} else if (action != null &&
|
||||
(multimodalText == null && summaryText == null && textSummary == null)
|
||||
) {
|
||||
append("$action:")
|
||||
if (ok != null) append(" ${if (ok) "✅ ok" else "❌ failed"}")
|
||||
if (message != null) append("\n$message")
|
||||
} else if (isEmpty()) {
|
||||
append("🖥️ Computer use action")
|
||||
}
|
||||
} else if (action != null) {
|
||||
append("$action:")
|
||||
if (ok != null) append(" ${if (ok) "✅ ok" else "❌ failed"}")
|
||||
if (message != null) append("\n$message")
|
||||
} else {
|
||||
append("🖥️ Computer use action")
|
||||
}
|
||||
if (visionAnalysis != null) {
|
||||
append("\n\n━━━ Vision Analysis ━━━\n$visionAnalysis")
|
||||
@@ -1633,7 +1649,7 @@ fun parseToolOutput(
|
||||
}
|
||||
val duration = obj.get("duration_s")?.takeIf { !it.isJsonNull }?.asDouble
|
||||
return ParsedToolData(
|
||||
toolName = resolvedToolName ?: "",
|
||||
toolName = resolvedToolName,
|
||||
args = args,
|
||||
result = dataSource.entrySet().associate { it.key to it.value.toString() },
|
||||
summaryText = summaryLine,
|
||||
@@ -1657,8 +1673,16 @@ fun parseToolOutput(
|
||||
(
|
||||
dataSource.get("output")?.takeIf { !it.isJsonNull }?.asString
|
||||
?: listOfNotNull(
|
||||
dataSource.get("stdout")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() },
|
||||
dataSource.get("stderr")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() },
|
||||
dataSource
|
||||
.get("stdout")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
?.takeIf { it.isNotEmpty() },
|
||||
dataSource
|
||||
.get("stderr")
|
||||
?.takeIf { !it.isJsonNull }
|
||||
?.asString
|
||||
?.takeIf { it.isNotEmpty() },
|
||||
).joinToString("\n").takeIf { it.isNotEmpty() }
|
||||
)?.takeIf { it.isNotEmpty() }
|
||||
val exitCode = (dataSource.get("exit_code") ?: dataSource.get("exitCode"))?.toIntOrNull()
|
||||
@@ -2331,6 +2355,9 @@ private fun JsonElement?.toIntOrNull(): Int? {
|
||||
is JsonPrimitive -> {
|
||||
content.toIntOrNull() ?: content.toDoubleOrNull()?.toInt()
|
||||
}
|
||||
else -> null
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1197,7 +1197,7 @@ private fun SudoPromptDialog(
|
||||
modifier = Modifier.fillMaxWidth().testTag("sudo_password_input"),
|
||||
singleLine = true,
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Password),
|
||||
keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -1247,7 +1247,7 @@ private fun SecretPromptDialog(
|
||||
modifier = Modifier.fillMaxWidth().testTag("secret_value_input"),
|
||||
singleLine = true,
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Password),
|
||||
keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Password),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -2156,9 +2156,13 @@ private fun ReactionHeartsOverlay(
|
||||
val emojis =
|
||||
remember(reactionKind) {
|
||||
when (reactionKind) {
|
||||
"ily", "<3", "good bot" ->
|
||||
"ily", "<3", "good bot" -> {
|
||||
listOf("💗", "❤️", "💕", "💖", "🩷", "💘", "💝")
|
||||
else -> listOf("💗", "❤️", "💕", "💖")
|
||||
}
|
||||
|
||||
else -> {
|
||||
listOf("💗", "❤️", "💕", "💖")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -649,7 +649,7 @@ class ChatViewModel(
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
errorMessage = "Error${if (method != null) " ($method)" else ""}: $errorMsg",
|
||||
errorMessage = "Error ($method): $errorMsg",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1084,8 +1084,10 @@ class ChatViewModel(
|
||||
val body = trimmed.removePrefix("/").trimStart()
|
||||
// Must be exactly "model" with no argument (or just whitespace).
|
||||
return body.equals("model", ignoreCase = true) ||
|
||||
body.startsWith("model ", ignoreCase = true) &&
|
||||
body.substringAfter("model").trim().isEmpty()
|
||||
(
|
||||
body.startsWith("model ", ignoreCase = true) &&
|
||||
body.substringAfter("model").trim().isEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
/** Preload model options so the picker opens instantly (no spinner on /model). */
|
||||
@@ -1343,7 +1345,7 @@ class ChatViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkResult.Failure -> Unit
|
||||
is NetworkResult.Failure -> {}
|
||||
}
|
||||
} finally {
|
||||
isSyncingMessages = false
|
||||
@@ -1352,7 +1354,10 @@ class ChatViewModel(
|
||||
}
|
||||
|
||||
private suspend fun fetchServerMessageCount(sessionId: String): Int {
|
||||
val known = _uiState.value.sessions.find { it.id == sessionId }?.messageCount
|
||||
val known =
|
||||
_uiState.value.sessions
|
||||
.find { it.id == sessionId }
|
||||
?.messageCount
|
||||
if (known != null) return known
|
||||
val result =
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -1762,7 +1767,7 @@ class ChatViewModel(
|
||||
return@launch
|
||||
}
|
||||
|
||||
val body = ticketResp.body?.string().orEmpty()
|
||||
val body = ticketResp.body.string()
|
||||
val ticket = JSONObject(body).optString("ticket").takeIf { it.isNotBlank() }
|
||||
|
||||
if (ticket.isNullOrBlank()) {
|
||||
|
||||
@@ -104,13 +104,16 @@ fun MarkdownText(
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
for (block in blocks) {
|
||||
when (block) {
|
||||
is MdBlock.Code -> CodeBlockCard(code = block.code, textColor = textColor)
|
||||
is MdBlock.Code -> {
|
||||
CodeBlockCard(code = block.code, textColor = textColor)
|
||||
}
|
||||
|
||||
is MdBlock.Hr ->
|
||||
is MdBlock.Hr -> {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
color = textColor.copy(alpha = 0.25f),
|
||||
)
|
||||
}
|
||||
|
||||
is MdBlock.Heading -> {
|
||||
val fontSize =
|
||||
@@ -291,7 +294,9 @@ fun MarkdownText(
|
||||
}
|
||||
}
|
||||
|
||||
is MdBlock.Table -> MarkdownTable(block = block, textColor = textColor)
|
||||
is MdBlock.Table -> {
|
||||
MarkdownTable(block = block, textColor = textColor)
|
||||
}
|
||||
|
||||
is MdBlock.Footnotes -> {
|
||||
HorizontalDivider(
|
||||
@@ -510,7 +515,9 @@ internal fun parseBlocks(src: String): List<MdBlock> {
|
||||
}
|
||||
}
|
||||
|
||||
line.isBlank() -> i++
|
||||
line.isBlank() -> {
|
||||
i++
|
||||
}
|
||||
|
||||
isHorizontalRule(line) -> {
|
||||
blocks.add(MdBlock.Hr)
|
||||
@@ -944,19 +951,36 @@ internal fun parseInline(
|
||||
}
|
||||
|
||||
internal sealed interface MdBlock {
|
||||
data class Code(val code: String) : MdBlock
|
||||
data class Code(
|
||||
val code: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Heading(val level: Int, val text: String) : MdBlock
|
||||
data class Heading(
|
||||
val level: Int,
|
||||
val text: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Bullet(val text: String) : MdBlock
|
||||
data class Bullet(
|
||||
val text: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Task(val checked: Boolean, val text: String) : MdBlock
|
||||
data class Task(
|
||||
val checked: Boolean,
|
||||
val text: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Ordered(val index: Int, val text: String) : MdBlock
|
||||
data class Ordered(
|
||||
val index: Int,
|
||||
val text: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Quote(val text: String) : MdBlock
|
||||
data class Quote(
|
||||
val text: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Paragraph(val text: String) : MdBlock
|
||||
data class Paragraph(
|
||||
val text: String,
|
||||
) : MdBlock
|
||||
|
||||
data class Table(
|
||||
val header: List<String>,
|
||||
@@ -966,9 +990,13 @@ internal sealed interface MdBlock {
|
||||
|
||||
object Hr : MdBlock
|
||||
|
||||
data class DefList(val items: List<DefItem>) : MdBlock
|
||||
data class DefList(
|
||||
val items: List<DefItem>,
|
||||
) : MdBlock
|
||||
|
||||
data class Footnotes(val notes: List<FnNote>) : MdBlock
|
||||
data class Footnotes(
|
||||
val notes: List<FnNote>,
|
||||
) : MdBlock
|
||||
}
|
||||
|
||||
internal data class DefItem(
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -32,7 +33,7 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.SecondaryScrollableTabRow
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
@@ -494,7 +495,7 @@ private fun ConfigTabs(
|
||||
selectedCategory: String,
|
||||
onCategorySelected: (String) -> Unit,
|
||||
) {
|
||||
ScrollableTabRow(
|
||||
SecondaryScrollableTabRow(
|
||||
selectedTabIndex = categories.indexOf(selectedCategory).coerceAtLeast(0),
|
||||
edgePadding = 0.dp,
|
||||
) {
|
||||
@@ -637,7 +638,6 @@ private fun ConfigField(
|
||||
is JsonPrimitive -> it.content
|
||||
is JsonObject -> it.toString()
|
||||
is JsonArray -> it.toString()
|
||||
else -> ""
|
||||
}
|
||||
} ?: ""
|
||||
|
||||
@@ -676,7 +676,7 @@ private fun DropdownField(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
|
||||
label = { Text(label) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
singleLine = true,
|
||||
|
||||
@@ -190,7 +190,7 @@ class CronJobsViewModel :
|
||||
}
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
val job = result.data ?: return@launch
|
||||
val job = result.data
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
editorState =
|
||||
|
||||
@@ -34,9 +34,9 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SecondaryTabRow
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -223,7 +223,7 @@ private fun AddServerSection(
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(spacing.md)) {
|
||||
TabRow(selectedTabIndex = if (state.addMode == AddServerMode.HTTP) 0 else 1) {
|
||||
SecondaryTabRow(selectedTabIndex = if (state.addMode == AddServerMode.HTTP) 0 else 1) {
|
||||
Tab(
|
||||
selected = state.addMode == AddServerMode.HTTP,
|
||||
onClick = { viewModel.setAddMode(AddServerMode.HTTP) },
|
||||
@@ -562,7 +562,7 @@ private fun CatalogSection(
|
||||
|
||||
state.catalogError != null -> {
|
||||
ErrorState(
|
||||
message = state.catalogError ?: "",
|
||||
message = state.catalogError,
|
||||
onRetry = { viewModel.loadCatalog() },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -500,7 +500,7 @@ private fun AuxTasksDialog(
|
||||
if (isAuto) {
|
||||
"auto (use main model)"
|
||||
} else {
|
||||
"${cur?.provider} · ${cur?.model ?: "(provider default)"}"
|
||||
"${cur.provider} · ${cur.model.ifBlank { "(provider default)" }}"
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -601,7 +601,7 @@ private fun PinnedSectionCard(
|
||||
pinnedModels.forEach { pinned ->
|
||||
val isActive =
|
||||
activeProfile?.provider == pinned.providerSlug &&
|
||||
activeProfile?.model == pinned.modelName
|
||||
activeProfile.model == pinned.modelName
|
||||
|
||||
Card(
|
||||
onClick = { onModelClick(pinned.providerSlug, pinned.modelName) },
|
||||
@@ -778,7 +778,7 @@ private fun ProviderCard(
|
||||
filteredModels.forEach { model ->
|
||||
val isActive =
|
||||
activeProfile?.provider == provider.slug &&
|
||||
activeProfile?.model == model
|
||||
activeProfile.model == model
|
||||
Card(
|
||||
onClick = { onModelClick(provider.slug, model) },
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -322,7 +323,7 @@ private fun PluginProviderDropdown(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
|
||||
label = { Text(label) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
singleLine = true,
|
||||
|
||||
@@ -76,30 +76,34 @@ fun ProcessesScreen(
|
||||
modifier = modifier,
|
||||
) {
|
||||
when {
|
||||
state.sessionId == null ->
|
||||
state.sessionId == null -> {
|
||||
EmptyState(
|
||||
title = stringResource(R.string.processes_no_session_title),
|
||||
subtitle = stringResource(R.string.processes_no_session_desc),
|
||||
icon = Icons.Filled.Memory,
|
||||
)
|
||||
}
|
||||
|
||||
state.isLoading && state.processes.isEmpty() ->
|
||||
state.isLoading && state.processes.isEmpty() -> {
|
||||
LoadingState()
|
||||
}
|
||||
|
||||
state.errorMessage != null ->
|
||||
state.errorMessage != null -> {
|
||||
ErrorState(
|
||||
message = state.errorMessage ?: stringResource(R.string.error_unknown),
|
||||
onRetry = { viewModel.load() },
|
||||
)
|
||||
}
|
||||
|
||||
state.processes.isEmpty() ->
|
||||
state.processes.isEmpty() -> {
|
||||
EmptyState(
|
||||
title = stringResource(R.string.processes_empty_title),
|
||||
subtitle = stringResource(R.string.processes_empty_desc),
|
||||
icon = Icons.Filled.Memory,
|
||||
)
|
||||
}
|
||||
|
||||
else ->
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = listContentPadding,
|
||||
@@ -113,6 +117,7 @@ fun ProcessesScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,8 +243,14 @@ private fun formatUptime(seconds: Int): String {
|
||||
val m = seconds / 60
|
||||
val s = seconds % 60
|
||||
return when {
|
||||
m <= 0 -> "${s}s"
|
||||
m < 60 -> "${m}m ${s}s"
|
||||
m <= 0 -> {
|
||||
"${s}s"
|
||||
}
|
||||
|
||||
m < 60 -> {
|
||||
"${m}m ${s}s"
|
||||
}
|
||||
|
||||
else -> {
|
||||
val h = m / 60
|
||||
"${h}h ${m % 60}m"
|
||||
|
||||
@@ -332,7 +332,7 @@ class ProfilesViewModel :
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isSearchingHub = false,
|
||||
hubSearchResults = result.data?.results.orEmpty(),
|
||||
hubSearchResults = result.data.results.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,21 +274,25 @@ private fun OAuthFlowDialog(
|
||||
Text(stringResource(R.string.providers_action_submit_code))
|
||||
}
|
||||
}
|
||||
|
||||
OAuthFlowPhase.DONE -> {
|
||||
Button(onClick = onDismissFlow) {
|
||||
Text(stringResource(R.string.action_done))
|
||||
}
|
||||
}
|
||||
|
||||
OAuthFlowPhase.ERROR -> {
|
||||
Button(onClick = onDismissFlow) {
|
||||
Text(stringResource(R.string.action_close))
|
||||
}
|
||||
}
|
||||
|
||||
OAuthFlowPhase.SHOW_CLI -> {
|
||||
Button(onClick = onDismissFlow) {
|
||||
Text(stringResource(R.string.action_close))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
@@ -301,8 +305,6 @@ private fun OAuthFlowDialog(
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
},
|
||||
title = { Text(provider.name, style = MaterialTheme.typography.titleLarge) },
|
||||
@@ -312,6 +314,7 @@ private fun OAuthFlowDialog(
|
||||
provider.flow == "external" -> {
|
||||
ExternalFlowContent(provider = provider)
|
||||
}
|
||||
|
||||
state.flowPhase == OAuthFlowPhase.WAITING_CODE && start?.authUrl != null -> {
|
||||
PkceFlowContent(
|
||||
authUrl = start.authUrl,
|
||||
@@ -321,6 +324,7 @@ private fun OAuthFlowDialog(
|
||||
error = state.flowErrorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
state.flowPhase == OAuthFlowPhase.POLLING -> {
|
||||
DeviceCodeFlowContent(
|
||||
start = start,
|
||||
@@ -329,15 +333,17 @@ private fun OAuthFlowDialog(
|
||||
error = state.flowErrorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
state.flowPhase == OAuthFlowPhase.DONE -> {
|
||||
Text(
|
||||
text = stringResource(R.string.providers_flow_success),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
state.flowErrorMessage != null -> {
|
||||
Text(
|
||||
text = state.flowErrorMessage!!,
|
||||
text = state.flowErrorMessage,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ class ProvidersViewModel :
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
providers = data?.providers.orEmpty(),
|
||||
providers = data.providers.orEmpty(),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -161,16 +161,7 @@ class ProvidersViewModel :
|
||||
val result = safeApiCall { ApiClient.hermesApi.startOAuthLogin(provider.id) }
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
val start =
|
||||
result.data ?: run {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
flowPhase = OAuthFlowPhase.IDLE,
|
||||
flowErrorMessage = "Empty start response",
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
val start = result.data
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
flowPhase =
|
||||
@@ -224,13 +215,13 @@ class ProvidersViewModel :
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
val submit = result.data
|
||||
if (submit?.ok == true) {
|
||||
if (submit.ok) {
|
||||
finishFlow("approved")
|
||||
} else {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
flowStatus = submit?.status ?: "error",
|
||||
flowErrorMessage = submit?.message ?: "Submission rejected",
|
||||
flowStatus = submit.status ?: "error",
|
||||
flowErrorMessage = submit.message ?: "Submission rejected",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -271,6 +262,7 @@ class ProvidersViewModel :
|
||||
finishFlow("approved")
|
||||
return@launch
|
||||
}
|
||||
|
||||
"denied", "expired", "error" -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -283,6 +275,7 @@ class ProvidersViewModel :
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
else -> { /* still pending — keep polling */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
@@ -37,7 +38,6 @@ import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SelectAll
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material.icons.filled.Terminal
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
@@ -115,7 +115,7 @@ import kotlin.math.abs
|
||||
*/
|
||||
private fun sourceIcon(source: String?): ImageVector? =
|
||||
when (source?.lowercase()) {
|
||||
"telegram", "tg" -> Icons.Filled.Send
|
||||
"telegram", "tg" -> Icons.AutoMirrored.Filled.Send
|
||||
"web", "dashboard" -> Icons.Filled.Language
|
||||
"api", "rest" -> Icons.Filled.Code
|
||||
"cli", "terminal" -> Icons.Filled.Terminal
|
||||
@@ -254,10 +254,6 @@ private fun JsonElement.searchText(): String? =
|
||||
.firstNotNullOfOrNull { fieldName -> this[fieldName]?.searchText() }
|
||||
?: wrapperFieldNames.firstNotNullOfOrNull { fieldName -> this[fieldName]?.searchText() }
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayedSessions(state: SessionsUiState): List<SessionTreeItem> =
|
||||
|
||||
@@ -85,8 +85,8 @@ class SessionsViewModel : ViewModel() {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
isLoadingMore = false,
|
||||
sessions = data?.sessions.orEmpty(),
|
||||
total = data?.total ?: 0,
|
||||
sessions = data.sessions.orEmpty(),
|
||||
total = data.total,
|
||||
selectedIds = emptySet(),
|
||||
)
|
||||
}
|
||||
@@ -173,7 +173,7 @@ class SessionsViewModel : ViewModel() {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isSearching = false,
|
||||
searchResults = result.data?.results.orEmpty(),
|
||||
searchResults = result.data.results.orEmpty(),
|
||||
searchError = null,
|
||||
)
|
||||
}
|
||||
@@ -207,8 +207,7 @@ class SessionsViewModel : ViewModel() {
|
||||
it.copy(
|
||||
isLoadingStats = false,
|
||||
stats =
|
||||
data?.let { SessionStats(total = it.total, active = it.active) }
|
||||
?: SessionStats(),
|
||||
SessionStats(total = data.total, active = data.active),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -318,7 +317,7 @@ class SessionsViewModel : ViewModel() {
|
||||
}
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
val promptText = result.data?.prompt ?: "No prompt available"
|
||||
val promptText = result.data.prompt ?: "No prompt available"
|
||||
_uiState.update { it.copy(toastMessage = promptText) }
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ class SkillsViewModel(
|
||||
},
|
||||
onStart = { _uiState.update { it.copy(isHubSearching = true) } },
|
||||
onSuccess = { data ->
|
||||
_uiState.update { it.copy(isHubSearching = false, hubResults = data?.results.orEmpty()) }
|
||||
_uiState.update { it.copy(isHubSearching = false, hubResults = data.results.orEmpty()) }
|
||||
},
|
||||
onError = { errorMsg ->
|
||||
_uiState.update {
|
||||
@@ -313,7 +313,7 @@ class SkillsViewModel(
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoadingPreview = false,
|
||||
previewSkillContent = result.data?.content,
|
||||
previewSkillContent = result.data.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,7 @@ class SkillsViewModel(
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoadingContent = false,
|
||||
skillContent = result.data?.content.orEmpty(),
|
||||
skillContent = result.data.content.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1299,6 +1299,7 @@ private fun LazyListScope.operationsSection(
|
||||
// Debug share results
|
||||
state.debugShare?.let { share ->
|
||||
Spacer(modifier = Modifier.height(spacing.sm))
|
||||
@Suppress("DEPRECATION")
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
Row(
|
||||
@@ -1637,7 +1638,8 @@ private fun LazyListScope.actionLogSection(
|
||||
}
|
||||
|
||||
private val actionButtonPadding =
|
||||
androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp, vertical = 8.dp)
|
||||
androidx.compose.foundation.layout
|
||||
.PaddingValues(horizontal = 8.dp, vertical = 8.dp)
|
||||
|
||||
@Composable
|
||||
private fun ActionButtonContent(
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.m57.hermescontrol.data.model.ActionResponse
|
||||
import com.m57.hermescontrol.data.model.ActionStatusResponse
|
||||
import com.m57.hermescontrol.data.model.CheckpointsResponse
|
||||
import com.m57.hermescontrol.data.model.CredentialPoolProvider
|
||||
import com.m57.hermescontrol.data.model.CredentialPoolResponse
|
||||
import com.m57.hermescontrol.data.model.CuratorResponse
|
||||
import com.m57.hermescontrol.data.model.DebugShareResponse
|
||||
import com.m57.hermescontrol.data.model.DoctorResponse
|
||||
@@ -128,10 +127,7 @@ class SystemViewModel :
|
||||
curator = (curatorResult as? NetworkResult.Success)?.data,
|
||||
memory = (memoryResult as? NetworkResult.Success)?.data,
|
||||
credentials =
|
||||
(
|
||||
(credResult as? NetworkResult.Success)?.data
|
||||
as? CredentialPoolResponse
|
||||
)?.providers ?: emptyList(),
|
||||
((credResult as? NetworkResult.Success)?.data)?.providers ?: emptyList(),
|
||||
checkpoints = (checkpointsResult as? NetworkResult.Success)?.data,
|
||||
hooks = (hooksResult as? NetworkResult.Success)?.data,
|
||||
updateInfo = (updateResult as? NetworkResult.Success)?.data,
|
||||
@@ -414,7 +410,7 @@ class SystemViewModel :
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
toastMessage = "Backup triggered",
|
||||
backupArchive = result.data?.archive,
|
||||
backupArchive = result.data.archive,
|
||||
)
|
||||
}
|
||||
loadAll()
|
||||
@@ -641,7 +637,7 @@ class SystemViewModel :
|
||||
val result = withContext(Dispatchers.IO) { apiCall() }
|
||||
when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
result.data?.name?.let { pollActionStatus(it) }
|
||||
result.data.name?.let { pollActionStatus(it) }
|
||||
_uiState.update { it.copy(toastMessage = "$label started") }
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user