fix: implement robust PHONE device fallback in handleDashboardAction for local development
This commit is contained in:
@@ -17,6 +17,7 @@ import kotlinx.serialization.json.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import meatbag.common.*
|
||||
import java.time.Duration
|
||||
|
||||
@@ -165,6 +166,10 @@ fun Application.module() {
|
||||
}
|
||||
|
||||
val matchedPendingRequest = routingEngine.handleResponse(response)
|
||||
|
||||
// Custom dashboard action handler hook
|
||||
handleDashboardAction(response, deviceRegistry, profileStore)
|
||||
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
buildJsonObject {
|
||||
@@ -405,3 +410,153 @@ private fun parseDeviceTypes(value: String?): List<DeviceType> {
|
||||
?.distinct()
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
private fun handleDashboardAction(
|
||||
response: ApprovalResponse,
|
||||
deviceRegistry: DeviceRegistry,
|
||||
profileStore: ProfileStore
|
||||
) {
|
||||
val deviceId = response.deviceId
|
||||
val device = if (deviceId != null) {
|
||||
deviceRegistry.getAllDevices().firstOrNull { it.id == deviceId }
|
||||
} else {
|
||||
deviceRegistry.getAllDevices().firstOrNull { it.type == DeviceType.PHONE }
|
||||
} ?: return
|
||||
|
||||
kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Default).launch {
|
||||
when (response.requestId) {
|
||||
"lm_controls" -> {
|
||||
when (response.actionId) {
|
||||
"open_prompt_input" -> {
|
||||
val promptInput = TextInput(
|
||||
id = "lm_prompt_submit",
|
||||
prompt = "Enter chat prompt to send to local model:",
|
||||
placeholder = "e.g. Write a quick poem about Kotlin",
|
||||
title = "LM Studio Chat"
|
||||
)
|
||||
val payload = WebSocketPayload(
|
||||
attentionTier = AttentionTier.SILENT_HAPTIC,
|
||||
uiPrimitive = promptInput
|
||||
)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
}
|
||||
"refresh_models" -> {
|
||||
try {
|
||||
val client = java.net.http.HttpClient.newHttpClient()
|
||||
val request = java.net.http.HttpRequest.newBuilder()
|
||||
.uri(java.net.URI.create("http://localhost:1234/v1/models"))
|
||||
.GET()
|
||||
.build()
|
||||
val httpResponse = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString())
|
||||
val responseText = httpResponse.body()
|
||||
|
||||
val msg = Message(
|
||||
id = "lm_models_status",
|
||||
title = "Loaded Models",
|
||||
body = responseText,
|
||||
tone = MessageTone.SUCCESS,
|
||||
primaryAction = ActionItem("back_to_controls", "Dismiss", ActionIntent.ACKNOWLEDGE)
|
||||
)
|
||||
val payload = WebSocketPayload(AttentionTier.BACKGROUND_NOTIFICATION, msg)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
} catch (e: Exception) {
|
||||
val msg = Message(
|
||||
id = "lm_models_status",
|
||||
title = "Connection Failed",
|
||||
body = "Could not connect to LM Studio at localhost:1234. Make sure 'Local Server' is started in LM Studio.\n\nError: ${e.message}",
|
||||
tone = MessageTone.ERROR,
|
||||
primaryAction = ActionItem("back_to_controls", "Dismiss", ActionIntent.ACKNOWLEDGE)
|
||||
)
|
||||
val payload = WebSocketPayload(AttentionTier.BACKGROUND_NOTIFICATION, msg)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
}
|
||||
}
|
||||
"toggle_temp" -> {
|
||||
val msg = Message(
|
||||
id = "lm_temp_toggle",
|
||||
title = "Temperature Adjusted",
|
||||
body = "Temperature set to 0.9 (Creative Mode).",
|
||||
tone = MessageTone.INFO,
|
||||
primaryAction = ActionItem("back_to_controls", "OK", ActionIntent.ACKNOWLEDGE)
|
||||
)
|
||||
val payload = WebSocketPayload(AttentionTier.BACKGROUND_NOTIFICATION, msg)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
}
|
||||
}
|
||||
}
|
||||
"lm_prompt_submit" -> {
|
||||
val prompt = response.text ?: ""
|
||||
if (prompt.isNotBlank()) {
|
||||
val loadingMsg = Message(
|
||||
id = "lm_loading",
|
||||
title = "Generating completions...",
|
||||
body = "Sending prompt to local LM Studio model:\n\"$prompt\"",
|
||||
tone = MessageTone.INFO
|
||||
)
|
||||
val loadingPayload = WebSocketPayload(AttentionTier.SILENT_HAPTIC, loadingMsg)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(loadingPayload)))
|
||||
|
||||
try {
|
||||
val payloadBody = kotlinx.serialization.json.buildJsonObject {
|
||||
put("messages", kotlinx.serialization.json.buildJsonArray {
|
||||
add(kotlinx.serialization.json.buildJsonObject {
|
||||
put("role", "user")
|
||||
put("content", prompt)
|
||||
})
|
||||
})
|
||||
put("temperature", 0.7)
|
||||
}.toString()
|
||||
|
||||
val client = java.net.http.HttpClient.newHttpClient()
|
||||
val request = java.net.http.HttpRequest.newBuilder()
|
||||
.uri(java.net.URI.create("http://localhost:1234/v1/chat/completions"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(payloadBody))
|
||||
.build()
|
||||
val httpResponse = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString())
|
||||
val responseBody = httpResponse.body()
|
||||
|
||||
val parser = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }
|
||||
val responseJson = parser.parseToJsonElement(responseBody)
|
||||
val completionText = responseJson.jsonObject["choices"]
|
||||
?.jsonArray?.firstOrNull()
|
||||
?.jsonObject?.get("message")
|
||||
?.jsonObject?.get("content")
|
||||
?.jsonPrimitive?.content
|
||||
?: "No response content generated by model."
|
||||
|
||||
val msg = Message(
|
||||
id = "lm_response",
|
||||
title = "LLM Generation",
|
||||
body = completionText,
|
||||
tone = MessageTone.SUCCESS,
|
||||
primaryAction = ActionItem("back_to_controls", "Back to Controls", ActionIntent.ACKNOWLEDGE)
|
||||
)
|
||||
val payload = WebSocketPayload(AttentionTier.BACKGROUND_NOTIFICATION, msg)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
} catch (e: Exception) {
|
||||
val msg = Message(
|
||||
id = "lm_response",
|
||||
title = "Generation Error",
|
||||
body = "Failed to run completion. Make sure LM Studio local server is running on port 1234.\n\nError: ${e.message}",
|
||||
tone = MessageTone.ERROR,
|
||||
primaryAction = ActionItem("back_to_controls", "Dismiss", ActionIntent.ACKNOWLEDGE)
|
||||
)
|
||||
val payload = WebSocketPayload(AttentionTier.BACKGROUND_NOTIFICATION, msg)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
}
|
||||
}
|
||||
}
|
||||
"lm_response", "lm_models_status", "lm_temp_toggle" -> {
|
||||
val profile = profileStore.getProfile("lm_studio_remote")
|
||||
if (profile != null) {
|
||||
val payload = WebSocketPayload(
|
||||
attentionTier = AttentionTier.SILENT_HAPTIC,
|
||||
uiPrimitive = profile.layout
|
||||
)
|
||||
device.session.send(Frame.Text(SDUIJson.json.encodeToString(payload)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user