Fix sageclient startup URL direct rendering

This commit is contained in:
yumoqing 2026-05-19 07:44:58 +08:00
parent f642667bab
commit 51b207626a
2 changed files with 63 additions and 45 deletions

View File

@ -44,7 +44,8 @@ cd /path/to/bricks-mp/test/sageclient
-Dsage.centerUi=/center.ui -Dsage.centerUi=/center.ui
``` ```
Run with a startup URL to enter that UI directly: Run with a startup URL to enter that UI directly without showing the sample
base URL / login input shell:
```bash ```bash
cd /path/to/bricks-mp/test/sageclient cd /path/to/bricks-mp/test/sageclient
@ -57,7 +58,7 @@ The packaged macOS app also accepts the URL as its first argument:
open build/compose/binaries/main/app/sageclient.app --args https://ai.atvoe.com/center.ui open build/compose/binaries/main/app/sageclient.app --args https://ai.atvoe.com/center.ui
``` ```
If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and loads the path/query part as the initial Bricks UI. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. The actual HTTP request is still issued through `BricksHttp`, so startup URLs ending in `.ui` / `.dspy` are requested with `_webbricks_=1`, `_width`, `_height`, `_is_mobile` and `_lang`; they must not be fetched as raw HTML/template pages. If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and dispatches that same absolute URL through the Bricks `urlwidget` loading path. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. The actual HTTP request is still issued through `BricksHttp`, so startup URLs ending in `.ui` / `.dspy` are requested with `_webbricks_=1`, `_width`, `_height`, `_is_mobile` and `_lang`; they must not be fetched as raw HTML/template pages.
Optional properties: Optional properties:

View File

@ -123,45 +123,55 @@ fun main(args: Array<String>) = application {
val currentWidget by context.currentWidget.collectAsState() val currentWidget by context.currentWidget.collectAsState()
LaunchedEffect(actionDispatcher, startupUrl) { val startupTarget = remember(startupUrl) { startupUrl?.let(::toWidgetUrl) }
startupUrl?.let { url ->
LaunchedEffect(actionDispatcher, startupTarget) {
startupTarget?.let { url ->
configureContextFromStartupUrl(context, url)
actionDispatcher.dispatch( actionDispatcher.dispatch(
BricksBind( BricksBind(
event = "startup", event = "startup",
actiontype = "urlwidget", actiontype = "urlwidget",
url = toWidgetPath(url) url = url
) )
) )
} }
} }
SageClientScreen( if (startupTarget != null) {
baseUrl = context.baseUrl, BricksStartupScreen(
widget = currentWidget, widget = currentWidget,
actionDispatcher = actionDispatcher, actionDispatcher = actionDispatcher
onBaseUrlChange = { context.baseUrl = it.trimEnd('/') }, )
onLoadCenter = { } else {
actionDispatcher.dispatch( SageClientScreen(
BricksBind( baseUrl = context.baseUrl,
event = "click", widget = currentWidget,
actiontype = "urlwidget", actionDispatcher = actionDispatcher,
url = System.getProperty("sage.centerUi", DEFAULT_CENTER_UI) onBaseUrlChange = { context.baseUrl = it.trimEnd('/') },
) onLoadCenter = {
) actionDispatcher.dispatch(
}, BricksBind(
onLogin = { username, password -> event = "click",
scope.launch { actiontype = "urlwidget",
loginAndLoadCenter( url = System.getProperty("sage.centerUi", DEFAULT_CENTER_UI)
context = context, )
http = http,
actionDispatcher = actionDispatcher,
username = username,
password = password,
onMessage = { title, body, isError -> message = Triple(title, body, isError) }
) )
},
onLogin = { username, password ->
scope.launch {
loginAndLoadCenter(
context = context,
http = http,
actionDispatcher = actionDispatcher,
username = username,
password = password,
onMessage = { title, body, isError -> message = Triple(title, body, isError) }
)
}
} }
} )
) }
dialogWidget?.let { widget -> dialogWidget?.let { widget ->
AlertDialog( AlertDialog(
@ -188,6 +198,27 @@ fun main(args: Array<String>) = application {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BricksStartupScreen(
widget: BricksWidget?,
actionDispatcher: ActionDispatcher
) {
Scaffold { padding ->
Box(modifier = Modifier.padding(padding).fillMaxSize()) {
if (widget == null) {
Text("Loading...")
} else {
RenderWidget(
widget = widget,
actionDispatcher = actionDispatcher,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun SageClientScreen( private fun SageClientScreen(
@ -274,11 +305,7 @@ private fun configureContextFromStartupUrl(context: BricksContext, startupUrl: S
} }
} }
private fun toWidgetPath(startupUrl: String): String { private fun toWidgetUrl(startupUrl: String): String = startupUrl.ifBlank { "/" }
if (!startupUrl.startsWith("http://") && !startupUrl.startsWith("https://")) return startupUrl
val pathWithQuery = startupUrl.pathAndQueryPart()
return pathWithQuery.ifBlank { "/" }
}
private fun String.originPart(): String { private fun String.originPart(): String {
val schemeEnd = indexOf("://") val schemeEnd = indexOf("://")
@ -288,16 +315,6 @@ private fun String.originPart(): String {
return substring(0, authorityEnd) return substring(0, authorityEnd)
} }
private fun String.pathAndQueryPart(): String {
val schemeEnd = indexOf("://")
val pathStart = if (schemeEnd >= 0) {
indexOf('/', startIndex = schemeEnd + 3).let { if (it < 0) return "" else it }
} else {
0
}
return substring(pathStart).substringBefore('#')
}
private suspend fun loginAndLoadCenter( private suspend fun loginAndLoadCenter(
context: BricksContext, context: BricksContext,
http: BricksHttp, http: BricksHttp,