How a JSON or JSON-like language enables the next generation of safe human and AI-generated UIs การแนะนํา ใน , ฉันแสดงให้เห็นว่า JavaScript ที่สร้างขึ้นโดยรหัสที่สร้างขึ้นโดย AI เป็นปัญหาด้านความปลอดภัยและความยืดหยุ่นของ JavaScript โดยไม่มีกรอบสามารถนําไปสู่รหัสที่ยากที่จะจัดการได้ ฉันอ้างว่าเราจําเป็นต้องใช้รูปแบบที่ประกาศแบบ sandboxed เช่น หรือ A (CDOM) กับ (JPRX ถ้า) ถ้าเราต้องการไว้วางใจ LLM เพื่อสร้างอินเตอร์เฟซของเรา อนาคตของ AI-generated UI A2UI บ้านคอมพิวเตอร์ JSON Pointer ตัวอักษรปกติ นอกจากนี้ยังช่วยถ้าวิธีการนี้ขึ้นอยู่กับมาตรฐานอุตสาหกรรมซึ่งมีเอกสารและตัวอย่างมากมายซึ่งอาจมีการบริโภคโดย LLMs ในระหว่างการฝึกอบรม cCOM และ JPRX ทําเช่นนี้ พวกเขารวมแนวคิดและคําอธิบายจาก JSON Pointers, JSON Schema และ XPath ในบทความก่อนหน้านี้ของฉันเพื่อแสดงวิธีการทํางานของ cDOM และ JPRX ฉันใช้ตัวนับแบบปฏิสัมพันธ์ แต่ให้เป็นจริง: ตัวนับแบบปฏิสัมพันธ์และรายการการทําเป็นเรื่องง่าย กรอบใด ๆ ดูสง่างามเมื่อกลยุทธ์พอดีกับผ้าเช็ดตัว เพื่อพิสูจน์ให้เห็นว่าวิธีการตาม JSON มีอยู่จริงคุณต้องมีปัญหาเกี่ยวกับสถานะสกปรกกรณีขอบและโหมดการทํางานที่แตกต่างกัน คุณต้องใช้เครื่องคํานวณ การคํานวณเป็นเรื่องยากในร่ม: โหมดการป้อนข้อมูล: เราป้อนหมายเลขใหม่หรือยึดติดกับหมายเลขที่มีอยู่หรือไม่ Chaining: สิ่งที่เกิดขึ้นเมื่อคุณตี '+' แล้ว '-' แล้ว '*' โดยไม่ต้องตีเท่าไหร่? DRY Logic: วิธีการลดความแตกต่างของรหัสระหว่าง 10 ตัวจัดการสําหรับปุ่ม 0-9 ดังนั้นฉันขอให้ Claude Opus สร้างเครื่องคํานวณแบบ iOS แบบเต็มรูปแบบโดยใช้ - การแสดงออก cDOM และ JPRX เท่านั้น zero custom JavaScript functions ความจริงที่ว่า AI สามารถผลิตเครื่องคํานวณที่ประกาศได้โดยไม่มีคําแนะนําเพียงจากเอกสารแสดงให้เห็นถึงจุดอื่นที่ฉันได้ทําในบทความก่อนหน้านี้: cDOM และ JPRX ไม่เพียง แต่เป็นซินเท็กซ์ใหม่ พวกเขาสามารถเป็นโปรโตคอลสําหรับความร่วมมือของมนุษย์และเครื่อง รหัส เพื่อลดการอ้างอิงและเสียงรบกวนในขณะที่อนุญาตให้มีการอธิบายในบรรทัดผมใช้ cDOMC ซึ่งเป็นรุ่นที่บีบอัดของ cDOM cDOM ธรรมดาไม่สนับสนุนความคิดเห็นและต้องอ้างอิงเกี่ยวกับคุณสมบัติและคําอธิบาย JPRX เมื่อแสดงด้วยอ้างอิงและไม่มีความคิดเห็น cDOM สามารถปฏิบัติตามเป็น JSON ธรรมดา { div: { class: "calculator", // A calculator feels stateless, but it's actually a strict state machine. // You're never just "typing a number"; you're either entering the first operand, // waiting for an operator, or entering the next operand. onmount: =state({ display: "0", // What you see on the screen expr: "", // History string, (e.g. "8 + 5 =") prev: "", // value stored before an operation op: "", // the active operator waiting: false // true when expecting a new number vs operator },{ name: "c", // the root name of our state, so we can express things like: /c/display schema: "polymorphic", // allow type changes, e.g. "0" or 0 scope: $this // scope the path to the current element }), children: [ // Display area { div: { class: "display", children: [ { div: { class: "expression",children[=/c/expr] }}, { div: { class: "result",children[=/c/display] }} ] } }, // Button grid { div: { class: "buttons", children: [ // Row 1: AC, ±, %, ÷ { button: { class: "btn btn-clear", onclick: =/c = { display: "0", expr: "", prev: "", op: "", waiting: false }, children: ["AC"] } }, { button: { class: "btn btn-function", onclick: =/c = { display: negate(/c/display), waiting: true, expr: "" }, children: ["±"] } }, { button: { class: "btn btn-function", onclick: =/c = { display: toPercent(/c/display), waiting: true, expr: "" }, children: ["%"] } }, // Divison is our first operator. This is where it gets tricky. // When you click `+`, you can't just link `prev` to `display`. // If you did, `prev` would update every time you selected a new digit for the**second**number, // breaking the math. We need a snapshot of the value at that exact moment. // Excel solves this with INDIRECT, effectively dereferencing a cell. JPRX borrows the same concept: { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), // Capture the value right now expr: concat(/c/display, " ÷"), op: "/", waiting: true }, children: ["÷"] } }, // Row 2: 7, 8, 9, × // I have 10 number buttons. Do I write 10 handlers? Do I write a loop? In React or Vue, // you'd probably map over an array. With JPRX, the DOM is the data key and although map is available, // I represent the calculator using literals in this example. In a future article I will cover map. // By giving each button an `id` (e.g., `id: "7"`), we write a uniform logic expression that adapts // to whichever element triggered it. We just reference $this.id in JPRX and use an xpath to get the text // content for the child node, #../@id. In cDOM (not JPRX) '#' delimits the start of an xpath expression { button: { id: "7", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] // use xpath (starting char #) to get the text for the button from parent id } }, // Here's what is happening: // Waiting for input? (e.g., just hit `+`) → Replace the display with the button's ID. // Displaying "0"? → Replace it (avoids "07"). // Otherwise: → Append the button's ID. // This is replicated identically for every number button. No loops, no external helper functions. { button: { id: "8", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0), $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { id: "9", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " ×"), op: "*", waiting: true }, children: ["×"] } }, // Row 3: 4, 5, 6, − { button: { id: "4", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { id: "5", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { id: "6", class: "btn btn-number", onclick: =/c = { display: if(/c/waiting, $this.id, if(/c/display==0, $this.id, concat(/c/display, $this.id))), waiting: false }, children: [#../@id] } }, { button: { class: "btn btn-operator", onclick: =/c = { prev: indirect(/c/display), expr: concat(/c/display, " −"), op: "-", waiting: true }, children: ["−"] } }, // Row 4: 1, 2, 3, +, use set and eq just to demonstrate equivalence with = and == // the buttons below use 'set' in place of the infix operator '=', just to show a different way of doing things { button: { id: "1", class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { id: "2", class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { id: "3", class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), $this.id, concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { class: "btn btn-operator", onclick: =set(/c, { prev: indirect(/c/display), expr: concat(/c/display, " +"), op: "+", waiting: true }), children: ["+"] } }, // Row 5: 0, ., = { button: { id: "0", class: "btn btn-number btn-wide", onclick: =set(/c, { display: if(/c/waiting, $this.id, if(eq(/c/display, "0"), "0", concat(/c/display, $this.id))), waiting: false }), children: [#../@id] } }, { button: { class: "btn btn-number", onclick: =set(/c, { display: if(/c/waiting, "0.", if(contains(/c/display, "."), /c/display, concat(/c/display, "."))), waiting: false }), children: ["."] } }, // Finally, the math. We need to say: // 1. Take the snapshot we stored // 2. Apply the current operator // 3. combine it with what's on screen now // This is the job of calc(). If prev == 8 and op == * and display = 5, then calc would be evaluated as calc("8 * 5") // To keep the syntax a little cleaner we also use $(<path>) as a shorthand for indirect. { button: { class: "btn btn-equals", onclick: =set(/c, { display: if(eq(/c/op, ""), /c/display, calc(concat("$('/c/prev') ", /c/op, " $('/c/display')"))), expr: concat(/c/expr, " ", /c/display, " ="), prev: "", op: "", waiting: true }), children: ["="] } } ] } }, // Branding { div: { class: "branding", children: [ { span: { children: [ "Built with ", { a: { href: "https://github.com/anywhichway/lightview", target: "_blank", children: ["Lightview"] } }, " cDOM • No custom JS!" ] } } ] } } ] } } การโหลด cDOM via Lightview Hypermedia Lightview รองรับความสามารถของ hypermedia ที่คล้ายกับ โดยการอนุญาตให้ใช้ คุณสมบัติในเกือบทุกองค์ประกอบ HTMX src เพียงแค่อ้างอิง a ไฟล์ที่ใช้ : cDOM src <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="A beautiful calculator built with Lightview cDOM and JPRX reactive expressions - no custom JavaScript!"> <title>Calculator | Lightview cDOM</title> <link rel="stylesheet" href="calculator.css"> <!-- Load Lightview scripts --> <script src="/lightview.js"></script> <!-- DOM as JSON and reactivity support --> <script src="/lightview-x.js"></script> <!-- hypermedia support --> <script src="/lightview-cdom.js"></script> <-- cDOM/JPRX support --> </head> <body> <!-- The calculator cDOM is loaded via Lightview's hypermedia src attribute --> <div id="app" src="./calculator.cdomc"></div> </body> </html> โซ คุณสมบัติทํางานเหมือน HTML หรือ แท็ก - Lightview จะจับภาพโดยอัตโนมัติ ไฟล์, สารสกัดมันและทําให้เนื้อหาปฏิกิริยาเข้าสู่องค์ประกอบเป้าหมาย วิธีการนี้: src <img> <script> .cdomc ทําไมต้องสร้างวิธีนี้ คุณสามารถดู และถาม: concat("$('/c/prev') ...") ทําไมในโลกคุณไม่เพียงแค่เขียน parseFloat(prev) + parseFloat(curr) ? หากคุณเป็นคนเขียนโค้ดสําหรับตัวคุณเอง? คุณอาจจะเป็น Lightview สนับสนุนตัวจัดการ JS มาตรฐานสําหรับเหตุผลที่แน่นอน แต่ถ้าคุณกําลังสร้างโครงสร้างพื้นฐานสําหรับ การยึดติดกับเส้นทางที่ประกาศขึ้นอยู่กับ JSON เสนอสิ่งที่รหัสดิบไม่สามารถ: AI Agents Sandboxing: มันดําเนินการในสภาพแวดล้อมที่มีการควบคุม โลจิกไม่สามารถเข้าถึง 'หน้าต่าง' ทําคําขอการเรียกเก็บข้อมูลทั่วโลก หรือเรียกใช้รหัสที่สองอย่างใดอย่างหนึ่ง สิ่งนี้ทําให้มันปลอดภัยที่จะ "แลกเปลี่ยนร้อน" โลจิก UI ที่สร้างขึ้นโดย LLM ในเวลาจริง การพกพา: อินเทอร์เฟซอินเทอร์เฟซทั้งหมดนี้ – โลจิกและทั้งหมด – เป็นข้อมูลเท่านั้น มันสามารถส่งจากเซิร์ฟเวอร์จัดเก็บในฐานข้อมูลหรือสตรีมจากรุ่น AI โมเดลจิต: มันบังคับให้แยกกันอย่างชัดเจนระหว่างการเปลี่ยนแปลงของรัฐและโครงสร้างมุมมองซึ่งเป็นวิธีที่ LLMs คิดดีที่สุด เครื่องคํานวณนี้พิสูจน์ว่า " Declarative" ไม่จําเป็นต้องหมายถึง "dumb" ด้วยต้นฉบับที่ถูกต้อง - สถานะ, เงื่อนไขและการอ้างอิงตามเส้นทาง - คุณสามารถสร้างการโต้ตอบที่อุดมสมบูรณ์และซับซ้อนโดยไม่เคยออกจากโครงสร้างข้อมูล ภาพใหญ่ ซีรี่ส์นี้ไม่ได้เป็นเรื่องเกี่ยวกับห้องสมุดใหม่เท่านั้น มันเป็นเรื่องเกี่ยวกับการค้นหาชั้นการอักเสบที่เหมาะสมสําหรับยุค AI ใน เราได้พิจารณาความเสี่ยงของการอนุญาตให้ LLMs เขียนสคริปต์ดิบและนําเสนอฟิสิกส์ของ "Data as UI" อนาคตของ AI-generated UI ในบทความนี้เราแสดงให้เห็นว่า "Data as UI" ไม่หมายความว่า "Dumb UI" เราจัดการสถานะ, กรณี, snapshots ของข้อมูล, คณิตศาสตร์และการนําทาง DOM ด้วย 'xpath' โดยไม่ต้องเรียกใช้แถวเดียวของ JavaScript ที่กําหนดเอง cDOM ระบุโครงสร้าง JPRX ระบุพฤติกรรม มันเป็นปฏิกิริยาโดยไม่มีการประกอบและ UI โดยไม่มีความเสี่ยงด้านความปลอดภัย ลองตัวเอง เครื่องคํานวณเต็มรูปแบบสามารถใช้ได้ที่: การแสดงออกสด: https://lightview.dev/docs/calculator.html รหัสที่มา: https://github.com/anywhichway/lightview/blob/main/docs/calculator.html https://lightview.dev/docs/calculator.html https://github.com/anywhichway/lightview/blob/main/docs/calculator.html