fix(canvas): load A2UI resources across platforms
parent
95ea67de28
commit
b8012a2281
|
|
@ -285,6 +285,7 @@ class NodeRuntime(context: Context) {
|
||||||
add(ClawdisCanvasCommand.Eval.rawValue)
|
add(ClawdisCanvasCommand.Eval.rawValue)
|
||||||
add(ClawdisCanvasCommand.Snapshot.rawValue)
|
add(ClawdisCanvasCommand.Snapshot.rawValue)
|
||||||
add(ClawdisCanvasA2UICommand.Push.rawValue)
|
add(ClawdisCanvasA2UICommand.Push.rawValue)
|
||||||
|
add(ClawdisCanvasA2UICommand.PushJSONL.rawValue)
|
||||||
add(ClawdisCanvasA2UICommand.Reset.rawValue)
|
add(ClawdisCanvasA2UICommand.Reset.rawValue)
|
||||||
if (cameraEnabled.value) {
|
if (cameraEnabled.value) {
|
||||||
add(ClawdisCameraCommand.Snap.rawValue)
|
add(ClawdisCameraCommand.Snap.rawValue)
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ final class BridgeConnectionController {
|
||||||
ClawdisCanvasCommand.evalJS.rawValue,
|
ClawdisCanvasCommand.evalJS.rawValue,
|
||||||
ClawdisCanvasCommand.snapshot.rawValue,
|
ClawdisCanvasCommand.snapshot.rawValue,
|
||||||
ClawdisCanvasA2UICommand.push.rawValue,
|
ClawdisCanvasA2UICommand.push.rawValue,
|
||||||
|
ClawdisCanvasA2UICommand.pushJSONL.rawValue,
|
||||||
ClawdisCanvasA2UICommand.reset.rawValue,
|
ClawdisCanvasA2UICommand.reset.rawValue,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,13 +184,25 @@ final class ScreenController {
|
||||||
return data.base64EncodedString()
|
return data.base64EncodedString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SwiftPM flattens resource directories; ensure resource filenames are unique.
|
private static func bundledResourceURL(
|
||||||
private static let canvasScaffoldURL: URL? = ClawdisKitResources.bundle.url(
|
name: String,
|
||||||
forResource: "scaffold",
|
ext: String,
|
||||||
withExtension: "html")
|
subdirectory: String)
|
||||||
private static let a2uiIndexURL: URL? = ClawdisKitResources.bundle.url(
|
-> URL?
|
||||||
forResource: "index",
|
{
|
||||||
withExtension: "html")
|
let bundle = ClawdisKitResources.bundle
|
||||||
|
return bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
|
||||||
|
?? bundle.url(forResource: name, withExtension: ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let canvasScaffoldURL: URL? = Self.bundledResourceURL(
|
||||||
|
name: "scaffold",
|
||||||
|
ext: "html",
|
||||||
|
subdirectory: "CanvasScaffold")
|
||||||
|
private static let a2uiIndexURL: URL? = Self.bundledResourceURL(
|
||||||
|
name: "index",
|
||||||
|
ext: "html",
|
||||||
|
subdirectory: "CanvasA2UI")
|
||||||
|
|
||||||
func isTrustedCanvasUIURL(_ url: URL) -> Bool {
|
func isTrustedCanvasUIURL(_ url: URL) -> Bool {
|
||||||
guard url.isFileURL else { return false }
|
guard url.isFileURL else { return false }
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,7 @@ struct SettingsTab: View {
|
||||||
ClawdisCanvasCommand.evalJS.rawValue,
|
ClawdisCanvasCommand.evalJS.rawValue,
|
||||||
ClawdisCanvasCommand.snapshot.rawValue,
|
ClawdisCanvasCommand.snapshot.rawValue,
|
||||||
ClawdisCanvasA2UICommand.push.rawValue,
|
ClawdisCanvasA2UICommand.push.rawValue,
|
||||||
|
ClawdisCanvasA2UICommand.pushJSONL.rawValue,
|
||||||
ClawdisCanvasA2UICommand.reset.rawValue,
|
ClawdisCanvasA2UICommand.reset.rawValue,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -226,12 +226,10 @@ final class CanvasManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func hasBundledA2UIShell() -> Bool {
|
private static func hasBundledA2UIShell() -> Bool {
|
||||||
guard let base = ClawdisKitResources.bundle.resourceURL?
|
let bundle = ClawdisKitResources.bundle
|
||||||
.appendingPathComponent("CanvasA2UI", isDirectory: true)
|
if bundle.url(forResource: "index", withExtension: "html", subdirectory: "CanvasA2UI") != nil {
|
||||||
else {
|
return true
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
let index = base.appendingPathComponent("index.html", isDirectory: false)
|
return bundle.url(forResource: "index", withExtension: "html") != nil
|
||||||
return FileManager.default.fileExists(atPath: index.path)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||||
|
|
||||||
private func a2uiShellPage(sessionRoot: URL) -> CanvasResponse {
|
private func a2uiShellPage(sessionRoot: URL) -> CanvasResponse {
|
||||||
// Default Canvas UX: when no index exists, show the built-in scaffold page.
|
// Default Canvas UX: when no index exists, show the built-in scaffold page.
|
||||||
if let data = self.loadBundledResourceData(relativePath: "scaffold.html") {
|
if let data = self.loadBundledResourceData(relativePath: "CanvasScaffold/scaffold.html") {
|
||||||
return CanvasResponse(mime: "text/html", data: data)
|
return CanvasResponse(mime: "text/html", data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +234,7 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||||
return self.html("Forbidden", title: "Canvas: 403")
|
return self.html("Forbidden", title: "Canvas: 403")
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let data = self.loadBundledResourceData(relativePath: relative) else {
|
guard let data = self.loadBundledResourceData(relativePath: "CanvasA2UI/\(relative)") else {
|
||||||
return self.html("Not Found", title: "Canvas: 404")
|
return self.html("Not Found", title: "Canvas: 404")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,14 +244,24 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadBundledResourceData(relativePath: String) -> Data? {
|
private func loadBundledResourceData(relativePath: String) -> Data? {
|
||||||
// SwiftPM flattens resource directories; treat bundled canvas resources as uniquely-named files.
|
let trimmed = relativePath.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if relativePath.contains("/") { return nil }
|
guard !trimmed.isEmpty else { return nil }
|
||||||
let url = URL(fileURLWithPath: relativePath)
|
if trimmed.contains("..") || trimmed.contains("\\") { return nil }
|
||||||
let ext = url.pathExtension
|
|
||||||
let name = url.deletingPathExtension().lastPathComponent
|
let parts = trimmed.split(separator: "/")
|
||||||
|
guard let filename = parts.last else { return nil }
|
||||||
|
let subdirectory =
|
||||||
|
parts.count > 1 ? parts.dropLast().joined(separator: "/") : nil
|
||||||
|
let fileURL = URL(fileURLWithPath: String(filename))
|
||||||
|
let ext = fileURL.pathExtension
|
||||||
|
let name = fileURL.deletingPathExtension().lastPathComponent
|
||||||
guard !name.isEmpty, !ext.isEmpty else { return nil }
|
guard !name.isEmpty, !ext.isEmpty else { return nil }
|
||||||
guard let resourceURL = ClawdisKitResources.bundle.url(forResource: name, withExtension: ext)
|
|
||||||
else { return nil }
|
let bundle = ClawdisKitResources.bundle
|
||||||
|
let resourceURL =
|
||||||
|
bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
|
||||||
|
?? bundle.url(forResource: name, withExtension: ext)
|
||||||
|
guard let resourceURL else { return nil }
|
||||||
return try? Data(contentsOf: resourceURL)
|
return try? Data(contentsOf: resourceURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue