@@ -0,0 +1,5 @@
|
|||||||
|
package com.m57.hermescontrol.data.model
|
||||||
|
|
||||||
|
data class SaveSkillContentRequest(
|
||||||
|
val content: String,
|
||||||
|
)
|
||||||
@@ -5,4 +5,5 @@ data class Skill(
|
|||||||
val description: String?,
|
val description: String?,
|
||||||
val category: String?,
|
val category: String?,
|
||||||
val enabled: Boolean,
|
val enabled: Boolean,
|
||||||
|
val content: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.m57.hermescontrol.data.model
|
||||||
|
|
||||||
|
data class SkillContentResponse(
|
||||||
|
val name: String,
|
||||||
|
val content: String,
|
||||||
|
)
|
||||||
@@ -26,10 +26,12 @@ import com.m57.hermescontrol.data.model.PluginsHubResponse
|
|||||||
import com.m57.hermescontrol.data.model.ProfileSoulResponse
|
import com.m57.hermescontrol.data.model.ProfileSoulResponse
|
||||||
import com.m57.hermescontrol.data.model.ProfilesResponse
|
import com.m57.hermescontrol.data.model.ProfilesResponse
|
||||||
import com.m57.hermescontrol.data.model.RawConfigResponse
|
import com.m57.hermescontrol.data.model.RawConfigResponse
|
||||||
|
import com.m57.hermescontrol.data.model.SaveSkillContentRequest
|
||||||
import com.m57.hermescontrol.data.model.SessionListResponse
|
import com.m57.hermescontrol.data.model.SessionListResponse
|
||||||
import com.m57.hermescontrol.data.model.SessionMessagesResponse
|
import com.m57.hermescontrol.data.model.SessionMessagesResponse
|
||||||
import com.m57.hermescontrol.data.model.SetActiveProfileRequest
|
import com.m57.hermescontrol.data.model.SetActiveProfileRequest
|
||||||
import com.m57.hermescontrol.data.model.Skill
|
import com.m57.hermescontrol.data.model.Skill
|
||||||
|
import com.m57.hermescontrol.data.model.SkillContentResponse
|
||||||
import com.m57.hermescontrol.data.model.StatusResponse
|
import com.m57.hermescontrol.data.model.StatusResponse
|
||||||
import com.m57.hermescontrol.data.model.SystemStatsResponse
|
import com.m57.hermescontrol.data.model.SystemStatsResponse
|
||||||
import com.m57.hermescontrol.data.model.ToggleSkillRequest
|
import com.m57.hermescontrol.data.model.ToggleSkillRequest
|
||||||
@@ -52,6 +54,17 @@ import retrofit2.http.Path
|
|||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface HermesApiService {
|
interface HermesApiService {
|
||||||
|
@GET("api/skills/{name}/content")
|
||||||
|
suspend fun getSkillContent(
|
||||||
|
@Path("name") name: String,
|
||||||
|
): Response<SkillContentResponse>
|
||||||
|
|
||||||
|
@PUT("api/skills/{name}/content")
|
||||||
|
suspend fun saveSkillContent(
|
||||||
|
@Path("name") name: String,
|
||||||
|
@Body body: SaveSkillContentRequest,
|
||||||
|
): Response<Unit>
|
||||||
|
|
||||||
@GET("api/status")
|
@GET("api/status")
|
||||||
suspend fun getStatus(): Response<StatusResponse>
|
suspend fun getStatus(): Response<StatusResponse>
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,32 @@ import retrofit2.Response
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
sealed interface NetworkResult<out T> {
|
sealed interface NetworkResult<out T> {
|
||||||
data class Success<out T>(val data: T) : NetworkResult<T>
|
data class Success<out T>(
|
||||||
|
val data: T,
|
||||||
|
) : NetworkResult<T>
|
||||||
|
|
||||||
data class Failure(val error: NetworkError) : NetworkResult<Nothing>
|
data class Failure(
|
||||||
|
val error: NetworkError,
|
||||||
|
) : NetworkResult<Nothing>
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface NetworkError {
|
sealed interface NetworkError {
|
||||||
val message: String
|
val message: String
|
||||||
|
|
||||||
data class Http(val code: Int, override val message: String) : NetworkError
|
data class Http(
|
||||||
|
val code: Int,
|
||||||
|
override val message: String,
|
||||||
|
) : NetworkError
|
||||||
|
|
||||||
data class Connection(override val message: String, val cause: IOException) : NetworkError
|
data class Connection(
|
||||||
|
override val message: String,
|
||||||
|
val cause: IOException,
|
||||||
|
) : NetworkError
|
||||||
|
|
||||||
data class Unknown(override val message: String, val cause: Throwable) : NetworkError
|
data class Unknown(
|
||||||
|
override val message: String,
|
||||||
|
val cause: Throwable,
|
||||||
|
) : NetworkError
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mapHttpError(code: Int): NetworkError {
|
fun mapHttpError(code: Int): NetworkError {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class AchievementsViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class ChannelsViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false, platforms = result.data.orEmpty()) }
|
_uiState.update { it.copy(isLoading = false, platforms = result.data.orEmpty()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -63,6 +64,7 @@ class ChannelsViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "$platformId configured successfully") }
|
_uiState.update { it.copy(toastMessage = "$platformId configured successfully") }
|
||||||
loadPlatforms()
|
loadPlatforms()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to configure platform: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to configure platform: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,9 @@ private fun parseToolOutput(content: String): ParsedToolOutput? {
|
|||||||
val trimmed = content.trim()
|
val trimmed = content.trim()
|
||||||
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null
|
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null
|
||||||
return try {
|
return try {
|
||||||
val element = com.google.gson.JsonParser.parseString(trimmed)
|
val element =
|
||||||
|
com.google.gson.JsonParser
|
||||||
|
.parseString(trimmed)
|
||||||
if (element.isJsonObject) {
|
if (element.isJsonObject) {
|
||||||
val obj = element.asJsonObject
|
val obj = element.asJsonObject
|
||||||
|
|
||||||
@@ -310,10 +312,25 @@ private fun parseToolOutput(content: String): ParsedToolOutput? {
|
|||||||
val hasExitCode = obj.has("exit_code") || obj.has("exitCode")
|
val hasExitCode = obj.has("exit_code") || obj.has("exitCode")
|
||||||
|
|
||||||
if (hasStdout || hasStderr || hasExitCode) {
|
if (hasStdout || hasStderr || hasExitCode) {
|
||||||
val stdout = obj.get("stdout")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() }
|
val stdout =
|
||||||
val stderr = obj.get("stderr")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() }
|
obj
|
||||||
|
.get("stdout")
|
||||||
|
?.takeIf { !it.isJsonNull }
|
||||||
|
?.asString
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
val stderr =
|
||||||
|
obj
|
||||||
|
.get("stderr")
|
||||||
|
?.takeIf { !it.isJsonNull }
|
||||||
|
?.asString
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
val exitCode = (obj.get("exit_code") ?: obj.get("exitCode"))?.takeIf { !it.isJsonNull }?.asInt
|
val exitCode = (obj.get("exit_code") ?: obj.get("exitCode"))?.takeIf { !it.isJsonNull }?.asInt
|
||||||
val error = obj.get("error")?.takeIf { !it.isJsonNull }?.asString?.takeIf { it.isNotEmpty() }
|
val error =
|
||||||
|
obj
|
||||||
|
.get("error")
|
||||||
|
?.takeIf { !it.isJsonNull }
|
||||||
|
?.asString
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
ParsedToolOutput(
|
ParsedToolOutput(
|
||||||
isTerminal = true,
|
isTerminal = true,
|
||||||
|
|||||||
@@ -450,6 +450,7 @@ class ChatViewModel(
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
addAssistantMessage(statusText)
|
addAssistantMessage(statusText)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
addAssistantMessage("Failed to retrieve status: ${result.error.message}")
|
addAssistantMessage("Failed to retrieve status: ${result.error.message}")
|
||||||
}
|
}
|
||||||
@@ -472,6 +473,7 @@ class ChatViewModel(
|
|||||||
}
|
}
|
||||||
addAssistantMessage("**Sessions List:**\n$sessionsStr")
|
addAssistantMessage("**Sessions List:**\n$sessionsStr")
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
addAssistantMessage("Failed to list sessions: ${result.error.message}")
|
addAssistantMessage("Failed to list sessions: ${result.error.message}")
|
||||||
}
|
}
|
||||||
@@ -500,6 +502,7 @@ class ChatViewModel(
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
addAssistantMessage(statsText)
|
addAssistantMessage(statsText)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
addAssistantMessage("Failed to retrieve system stats: ${result.error.message}")
|
addAssistantMessage("Failed to retrieve system stats: ${result.error.message}")
|
||||||
}
|
}
|
||||||
@@ -627,6 +630,7 @@ class ChatViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class ConfigViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -73,6 +74,7 @@ class ConfigViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class ConnectViewModel : ViewModel() {
|
|||||||
it.copy(isConnecting = false, connectionSuccess = true, errorMessage = null)
|
it.copy(isConnecting = false, connectionSuccess = true, errorMessage = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
val msg =
|
val msg =
|
||||||
when (val err = result.error) {
|
when (val err = result.error) {
|
||||||
@@ -98,10 +99,17 @@ class ConnectViewModel : ViewModel() {
|
|||||||
AuthManager.setToken(null)
|
AuthManager.setToken(null)
|
||||||
"Invalid token (401 Unauthorized)"
|
"Invalid token (401 Unauthorized)"
|
||||||
}
|
}
|
||||||
403 -> "Access denied (403 Forbidden)"
|
|
||||||
else -> "Server returned HTTP ${err.code}"
|
403 -> {
|
||||||
|
"Access denied (403 Forbidden)"
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
"Server returned HTTP ${err.code}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkError.Connection -> {
|
is NetworkError.Connection -> {
|
||||||
val causeMessage = err.cause.message ?: ""
|
val causeMessage = err.cause.message ?: ""
|
||||||
when {
|
when {
|
||||||
@@ -111,6 +119,7 @@ class ConnectViewModel : ViewModel() {
|
|||||||
else -> "Connection failed: ${err.cause.message}"
|
else -> "Connection failed: ${err.cause.message}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkError.Unknown -> {
|
is NetworkError.Unknown -> {
|
||||||
val causeMessage = err.cause.message ?: ""
|
val causeMessage = err.cause.message ?: ""
|
||||||
when {
|
when {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class CronJobsViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false, jobs = result.data.orEmpty()) }
|
_uiState.update { it.copy(isLoading = false, jobs = result.data.orEmpty()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -102,6 +103,7 @@ class CronJobsViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Job triggered successfully") }
|
_uiState.update { it.copy(toastMessage = "Job triggered successfully") }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to trigger cron job: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to trigger cron job: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class GatewayViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false, status = result.data) }
|
_uiState.update { it.copy(isLoading = false, status = result.data) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -78,6 +79,7 @@ class GatewayViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
loadStatus()
|
loadStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class KanbanViewModel : ViewModel() {
|
|||||||
selectBoard(currentBoard)
|
selectBoard(currentBoard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -92,6 +93,7 @@ class KanbanViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -129,6 +131,7 @@ class KanbanViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Task created successfully") }
|
_uiState.update { it.copy(toastMessage = "Task created successfully") }
|
||||||
selectBoard(board)
|
selectBoard(board)
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to create task: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to create task: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class KeysViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -71,6 +72,7 @@ class KeysViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to reveal key: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to reveal key: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
@@ -100,6 +102,7 @@ class KeysViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Key updated successfully") }
|
_uiState.update { it.copy(toastMessage = "Key updated successfully") }
|
||||||
loadKeys()
|
loadKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to update key: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to update key: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class LogsViewModel : ViewModel() {
|
|||||||
val logsList = body.lines ?: body.logs ?: emptyList()
|
val logsList = body.lines ?: body.logs ?: emptyList()
|
||||||
_uiState.update { it.copy(isLoading = false, logs = logsList) }
|
_uiState.update { it.copy(isLoading = false, logs = logsList) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class McpServersViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -83,6 +84,7 @@ class McpServersViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
revertToggle(server.name, originalEnabled, "Failed to toggle server: ${result.error.message}")
|
revertToggle(server.name, originalEnabled, "Failed to toggle server: ${result.error.message}")
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,7 @@ class McpServersViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -135,6 +138,7 @@ class McpServersViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
loadServers()
|
loadServers()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class ModelViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -98,6 +99,7 @@ class ModelViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
val activeProfileName = activeProfileNameResResult.data.active
|
val activeProfileName = activeProfileNameResResult.data.active
|
||||||
val updateResResult =
|
val updateResResult =
|
||||||
@@ -114,6 +116,7 @@ class ModelViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Successfully set model to $modelName") }
|
_uiState.update { it.copy(toastMessage = "Successfully set model to $modelName") }
|
||||||
loadModelOptions()
|
loadModelOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class PairingViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false, pairing = result.data) }
|
_uiState.update { it.copy(isLoading = false, pairing = result.data) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -92,6 +93,7 @@ class PairingViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
loadPairing()
|
loadPairing()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class PluginsViewModel : ViewModel() {
|
|||||||
val plugins = result.data.plugins.orEmpty()
|
val plugins = result.data.plugins.orEmpty()
|
||||||
_uiState.update { it.copy(isLoading = false, plugins = plugins) }
|
_uiState.update { it.copy(isLoading = false, plugins = plugins) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -98,6 +99,7 @@ class PluginsViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Plugin installed successfully") }
|
_uiState.update { it.copy(toastMessage = "Plugin installed successfully") }
|
||||||
loadPlugins()
|
loadPlugins()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to install plugin: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to install plugin: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
@@ -116,6 +118,7 @@ class PluginsViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Plugin uninstalled successfully") }
|
_uiState.update { it.copy(toastMessage = "Plugin uninstalled successfully") }
|
||||||
loadPlugins()
|
loadPlugins()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to uninstall plugin: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to uninstall plugin: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
@@ -134,6 +137,7 @@ class PluginsViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Plugin updated successfully") }
|
_uiState.update { it.copy(toastMessage = "Plugin updated successfully") }
|
||||||
loadPlugins()
|
loadPlugins()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to update plugin: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to update plugin: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class ProfilesViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Switched to profile $name") }
|
_uiState.update { it.copy(toastMessage = "Switched to profile $name") }
|
||||||
loadProfiles()
|
loadProfiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -107,6 +108,7 @@ class ProfilesViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -143,6 +145,7 @@ class ProfilesViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
loadProfiles()
|
loadProfiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -174,6 +177,7 @@ class ProfilesViewModel : ViewModel() {
|
|||||||
_uiState.update { it.copy(toastMessage = "Model settings updated") }
|
_uiState.update { it.copy(toastMessage = "Model settings updated") }
|
||||||
loadProfiles()
|
loadProfiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ class SettingsViewModel : ViewModel() {
|
|||||||
it.copy(isTesting = false, testResult = "✅ Connected successfully")
|
it.copy(isTesting = false, testResult = "✅ Connected successfully")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -2,20 +2,29 @@ package com.m57.hermescontrol.ui.skills
|
|||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.Extension
|
import androidx.compose.material.icons.filled.Extension
|
||||||
|
import androidx.compose.material.icons.filled.Save
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -25,8 +34,12 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.m57.hermescontrol.theme.LocalSpacing
|
import com.m57.hermescontrol.theme.LocalSpacing
|
||||||
@@ -161,10 +174,24 @@ fun SkillsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Switch(
|
Row(
|
||||||
checked = skill.enabled,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onCheckedChange = { viewModel.toggleSkill(skill) },
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
)
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { viewModel.loadSkillContent(skill.name) },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Edit,
|
||||||
|
contentDescription = "Edit Skill File",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Switch(
|
||||||
|
checked = skill.enabled,
|
||||||
|
onCheckedChange = { viewModel.toggleSkill(skill) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,4 +199,155 @@ fun SkillsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.editingSkillName?.let { skillName ->
|
||||||
|
SkillEditorDialog(
|
||||||
|
skillName = skillName,
|
||||||
|
isLoading = state.isLoadingContent,
|
||||||
|
initialContent = state.skillContent,
|
||||||
|
isSaving = state.isSavingContent,
|
||||||
|
saveSuccess = state.saveContentSuccess,
|
||||||
|
onSave = { content -> viewModel.saveSkillContent(skillName, content) },
|
||||||
|
onDismiss = { viewModel.clearEditor() },
|
||||||
|
onClearSaveSuccess = { viewModel.clearSaveSuccess() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SkillEditorDialog(
|
||||||
|
skillName: String,
|
||||||
|
isLoading: Boolean,
|
||||||
|
initialContent: String?,
|
||||||
|
isSaving: Boolean,
|
||||||
|
saveSuccess: Boolean,
|
||||||
|
onSave: (String) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onClearSaveSuccess: () -> Unit,
|
||||||
|
) {
|
||||||
|
var contentText by remember(initialContent) { mutableStateOf(initialContent.orEmpty()) }
|
||||||
|
var showDiscardConfirm by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val hasChanges =
|
||||||
|
remember(initialContent, contentText) {
|
||||||
|
val original = initialContent.orEmpty()
|
||||||
|
original != contentText
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(saveSuccess) {
|
||||||
|
if (saveSuccess) {
|
||||||
|
onClearSaveSuccess()
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
if (hasChanges) {
|
||||||
|
showDiscardConfirm = true
|
||||||
|
} else {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
colors =
|
||||||
|
CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
HermesScaffold(
|
||||||
|
title = { Text("Edit: $skillName") },
|
||||||
|
onBack = {
|
||||||
|
if (hasChanges) {
|
||||||
|
showDiscardConfirm = true
|
||||||
|
} else {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showBack = true,
|
||||||
|
actions = {
|
||||||
|
if (!isLoading) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onSave(contentText) },
|
||||||
|
enabled = !isSaving,
|
||||||
|
) {
|
||||||
|
if (isSaving) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.fillMaxSize(0.6f),
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Save,
|
||||||
|
contentDescription = "Save Changes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding),
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
isLoading -> {
|
||||||
|
LoadingState()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = contentText,
|
||||||
|
onValueChange = { contentText = it },
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
textStyle =
|
||||||
|
TextStyle(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
placeholder = {
|
||||||
|
Text("Enter skill content...")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDiscardConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDiscardConfirm = false },
|
||||||
|
title = { Text("Discard changes?") },
|
||||||
|
text = { Text("You have unsaved changes. Are you sure you want to discard them?") },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showDiscardConfirm = false
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text("Discard")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showDiscardConfirm = false }) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ data class SkillsUiState(
|
|||||||
val skills: List<Skill> = emptyList(),
|
val skills: List<Skill> = emptyList(),
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
val toastMessage: String? = null,
|
val toastMessage: String? = null,
|
||||||
|
val isLoadingContent: Boolean = false,
|
||||||
|
val editingSkillName: String? = null,
|
||||||
|
val skillContent: String? = null,
|
||||||
|
val isSavingContent: Boolean = false,
|
||||||
|
val saveContentSuccess: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
class SkillsViewModel : ViewModel() {
|
class SkillsViewModel : ViewModel() {
|
||||||
@@ -37,6 +42,7 @@ class SkillsViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false, skills = result.data.orEmpty()) }
|
_uiState.update { it.copy(isLoading = false, skills = result.data.orEmpty()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -90,6 +96,99 @@ class SkillsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadSkillContent(skillName: String) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoadingContent = true,
|
||||||
|
editingSkillName = skillName,
|
||||||
|
skillContent = null,
|
||||||
|
saveContentSuccess = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
val result =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
safeApiCall { ApiClient.hermesApi.getSkillContent(skillName) }
|
||||||
|
}
|
||||||
|
when (result) {
|
||||||
|
is NetworkResult.Success -> {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoadingContent = false,
|
||||||
|
skillContent = result.data?.content.orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is NetworkResult.Failure -> {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoadingContent = false,
|
||||||
|
editingSkillName = null,
|
||||||
|
toastMessage = "Failed to load skill content: ${result.error.message}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSkillContent(
|
||||||
|
skillName: String,
|
||||||
|
content: String,
|
||||||
|
) {
|
||||||
|
_uiState.update { it.copy(isSavingContent = true, saveContentSuccess = false) }
|
||||||
|
viewModelScope.launch {
|
||||||
|
val result =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
safeApiCall {
|
||||||
|
ApiClient.hermesApi.saveSkillContent(
|
||||||
|
skillName,
|
||||||
|
com.m57.hermescontrol.data.model
|
||||||
|
.SaveSkillContentRequest(content),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (result) {
|
||||||
|
is NetworkResult.Success -> {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isSavingContent = false,
|
||||||
|
saveContentSuccess = true,
|
||||||
|
skillContent = content,
|
||||||
|
toastMessage = "Skill saved successfully",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is NetworkResult.Failure -> {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isSavingContent = false,
|
||||||
|
toastMessage = "Failed to save skill content: ${result.error.message}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearEditor() {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
editingSkillName = null,
|
||||||
|
skillContent = null,
|
||||||
|
isLoadingContent = false,
|
||||||
|
isSavingContent = false,
|
||||||
|
saveContentSuccess = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSaveSuccess() {
|
||||||
|
_uiState.update { it.copy(saveContentSuccess = false) }
|
||||||
|
}
|
||||||
|
|
||||||
fun clearToast() {
|
fun clearToast() {
|
||||||
_uiState.update { it.copy(toastMessage = null) }
|
_uiState.update { it.copy(toastMessage = null) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class SystemViewModel : ViewModel() {
|
|||||||
// Doctor is optional; many servers do not expose it.
|
// Doctor is optional; many servers do not expose it.
|
||||||
loadDoctorReport()
|
loadDoctorReport()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -87,6 +88,7 @@ class SystemViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Backup triggered successfully") }
|
_uiState.update { it.copy(toastMessage = "Backup triggered successfully") }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update { it.copy(toastMessage = "Failed to trigger backup: ${result.error.message}") }
|
_uiState.update { it.copy(toastMessage = "Failed to trigger backup: ${result.error.message}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class ToolsetsViewModel : ViewModel() {
|
|||||||
is NetworkResult.Success -> {
|
is NetworkResult.Success -> {
|
||||||
_uiState.update { it.copy(isLoading = false, toolsets = result.data.orEmpty()) }
|
_uiState.update { it.copy(isLoading = false, toolsets = result.data.orEmpty()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class WebhooksViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -78,6 +79,7 @@ class WebhooksViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
loadWebhooks()
|
loadWebhooks()
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkResult.Failure -> {
|
is NetworkResult.Failure -> {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|||||||
Reference in New Issue
Block a user