import ClawdisNodeKit import SwiftUI import WebKit @MainActor final class ScreenController: ObservableObject { let webView: WKWebView @Published var mode: ClawdisScreenMode = .canvas @Published var urlString: String = "" @Published var errorText: String? init() { let config = WKWebViewConfiguration() config.websiteDataStore = .nonPersistent() self.webView = WKWebView(frame: .zero, configuration: config) self.reload() } func setMode(_ mode: ClawdisScreenMode) { self.mode = mode self.reload() } func navigate(to urlString: String) { self.urlString = urlString self.reload() } func reload() { switch self.mode { case .web: guard let url = URL(string: self.urlString.trimmingCharacters(in: .whitespacesAndNewlines)) else { return } self.webView.load(URLRequest(url: url)) case .canvas: self.webView.loadHTMLString(Self.canvasScaffoldHTML, baseURL: nil) } } func eval(javaScript: String) async throws -> String { try await withCheckedThrowingContinuation { cont in self.webView.evaluateJavaScript(javaScript) { result, error in if let error { cont.resume(throwing: error) return } if let result { cont.resume(returning: String(describing: result)) } else { cont.resume(returning: "") } } } } func snapshotPNGBase64(maxWidth: CGFloat? = nil) async throws -> String { let config = WKSnapshotConfiguration() if let maxWidth { config.snapshotWidth = NSNumber(value: Double(maxWidth)) } let image: UIImage = try await withCheckedThrowingContinuation { cont in self.webView.takeSnapshot(with: config) { image, error in if let error { cont.resume(throwing: error) return } guard let image else { cont.resume(throwing: NSError(domain: "Screen", code: 2, userInfo: [ NSLocalizedDescriptionKey: "snapshot failed", ])) return } cont.resume(returning: image) } } guard let data = image.pngData() else { throw NSError(domain: "Screen", code: 1, userInfo: [ NSLocalizedDescriptionKey: "snapshot encode failed", ]) } return data.base64EncodedString() } private static let canvasScaffoldHTML = """