-
Notifications
You must be signed in to change notification settings - Fork 480
feat(nano): Implement cross-platform action dispatch system #490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
365b539
236ab0b
1cdcf65
96a5207
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,200 @@ | ||||||||||||||||||||||||
| package cc.unitmesh.devins.ui.nano | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import cc.unitmesh.xuiper.action.* | ||||||||||||||||||||||||
| import kotlinx.coroutines.CoroutineScope | ||||||||||||||||||||||||
| import kotlinx.coroutines.Dispatchers | ||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||
| import java.awt.Desktop | ||||||||||||||||||||||||
| import java.net.URI | ||||||||||||||||||||||||
| import java.net.http.HttpClient | ||||||||||||||||||||||||
| import java.net.http.HttpRequest | ||||||||||||||||||||||||
| import java.net.http.HttpResponse | ||||||||||||||||||||||||
| import javax.swing.JOptionPane | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Compose/Desktop implementation of NanoActionHandler | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Handles NanoUI actions in a Compose Desktop environment. | ||||||||||||||||||||||||
| * Provides platform-specific implementations for navigation, toast, and fetch. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Example: | ||||||||||||||||||||||||
| * ```kotlin | ||||||||||||||||||||||||
| * val handler = ComposeActionHandler( | ||||||||||||||||||||||||
| * scope = rememberCoroutineScope(), | ||||||||||||||||||||||||
| * onNavigate = { route -> navController.navigate(route) }, | ||||||||||||||||||||||||
| * onToast = { message -> snackbarHostState.showSnackbar(message) } | ||||||||||||||||||||||||
| * ) | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * handler.registerCustomAction("AddTask") { payload, context -> | ||||||||||||||||||||||||
| * val title = payload["title"] as? String ?: "" | ||||||||||||||||||||||||
| * taskRepository.add(Task(title)) | ||||||||||||||||||||||||
| * ActionResult.Success | ||||||||||||||||||||||||
| * } | ||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
| class ComposeActionHandler( | ||||||||||||||||||||||||
| private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO), | ||||||||||||||||||||||||
| private val onNavigate: ((String) -> Unit)? = null, | ||||||||||||||||||||||||
| private val onToast: ((String) -> Unit)? = null, | ||||||||||||||||||||||||
| private val onFetchComplete: ((String, Boolean, String?) -> Unit)? = null | ||||||||||||||||||||||||
| ) : BaseNanoActionHandler() { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private val httpClient = HttpClient.newBuilder().build() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| override fun handleNavigate( | ||||||||||||||||||||||||
| navigate: NanoAction.Navigate, | ||||||||||||||||||||||||
| context: NanoActionContext | ||||||||||||||||||||||||
| ): ActionResult { | ||||||||||||||||||||||||
| return try { | ||||||||||||||||||||||||
| if (onNavigate != null) { | ||||||||||||||||||||||||
| onNavigate.invoke(navigate.to) | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| // Default: open in browser if it's a URL | ||||||||||||||||||||||||
| if (navigate.to.startsWith("http://") || navigate.to.startsWith("https://")) { | ||||||||||||||||||||||||
| Desktop.getDesktop().browse(URI(navigate.to)) | ||||||||||||||||||||||||
|
Comment on lines
+53
to
+54
|
||||||||||||||||||||||||
| if (navigate.to.startsWith("http://") || navigate.to.startsWith("https://")) { | |
| Desktop.getDesktop().browse(URI(navigate.to)) | |
| try { | |
| val uri = URI(navigate.to) | |
| if (uri.scheme == "http" || uri.scheme == "https") { | |
| Desktop.getDesktop().browse(uri) | |
| } else { | |
| return ActionResult.Error("Unsupported URL scheme: ${uri.scheme}") | |
| } | |
| } catch (e: Exception) { | |
| return ActionResult.Error("Invalid URL: ${navigate.to}", e) |
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ActionResult.Pending is returned with an empty callback (line 148: ActionResult.Pending { /* async operation */ }), but the callback is never invoked when the async fetch operation completes. This means code waiting on the Pending result will never be notified of completion.
Either:
- Store the callback and invoke it when the async operation completes (success or error), or
- If async completion notification isn't needed, document that Pending results don't support completion callbacks in this implementation
The same issue exists in the base class where Pending results aren't being properly tracked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: phodal/auto-dev
Length of output: 4405
🏁 Script executed:
Repository: phodal/auto-dev
Length of output: 13916
🏁 Script executed:
Repository: phodal/auto-dev
Length of output: 7681
🏁 Script executed:
Repository: phodal/auto-dev
Length of output: 9537
Task-boundary handling is inconsistent across CodingAgentRenderer implementations
Only
ComposeRendererandJewelRendererhandle thetask-boundarytool in theirrenderToolCallmethods. The other three implementations—DefaultCodingAgentRenderer,ServerSideRenderer, andJsRendererAdapter—lack this handler. SinceCodingAgentRendererdoes not define task-boundary as a required interface method, either all implementations should add consistent task-boundary handling, or it should be removed from the two that currently have it to avoid platform discrepancies.🤖 Prompt for AI Agents