ฟร้อนท์เอนด์เคยเป็นสิ่งที่ตายตัว นักออกแบบเป็นคนวาด วิศวกรเป็นคนสร้าง ผู้ใช้ก็ได้สิ่งที่ถูกปล่อยออกไป
แต่ตอนนี้มันจบแล้ว
อินเทอร์เฟสที่ถูกปล่อยในปี 2026 ถูกวาดขึ้นบางส่วนโดย agent ตัวมันเอง แบบเรียลไทม์ จากสิ่งที่ผู้ใช้ร้องขอจริงๆ ขอตาราง ก็ได้ตาราง ไม่ใช่ย่อหน้าที่อธิบายตาราง
Generative UI คือเลเยอร์ที่ทำให้ agent หยุดอธิบายและเริ่มแสดงผลได้ รูปแบบการสร้างสามรูปแบบได้เกิดขึ้นแล้ว และความแตกต่างระหว่างรูปแบบเหล่านี้สำคัญกว่าที่ทีมส่วนใหญ่คิด
แต่มันไม่มีวิธีเดียวในการสร้างสิ่งนี้ มีสามวิธี และทีมส่วนใหญ่เลือกวิธีใดวิธีหนึ่งโดยไม่รู้ตัวว่าตัวเองได้เลือกไปแล้ว
สแต็กโปรโตคอล
สามโปรโตคอล แต่ละอย่างทำหน้าที่เดียว
MCP เชื่อมต่อ agent กับเครื่องมือ A2A เชื่อมต่อ agent เข้าหากัน AG-UI เชื่อมต่อ agent กับผู้ใช้
AG-UI คือเลเยอร์สตรีมมิ่งที่นำพาทุกอย่างที่คุณจะเห็นด้านล่างนี้: การเรียกเครื่องมือ, สคีมา A2UI, อีเวนต์ MCP App, สถานะเดลต้า ทำงานผ่าน SSE สถานะไหลทั้งสองทิศทางบนสตรีมเดียวกัน ผู้ใช้แก้ไข agent เห็น agent เปลี่ยนแปลง ผู้ใช้เห็น
A2UI คือสเปกของ Google สำหรับการให้ agent ปล่อย UI เป็นสคีมา มันทำงานบน AG-UI CopilotKit ส่งมอบมันในระบบ production
คุณไม่ต้องเขียน parser สำหรับสิ่งเหล่านี้ CopilotKit คือไคลเอนต์ AG-UI และถอดรหัสสตรีมให้คุณ
สามรูปแบบที่ทีมส่วนใหญ่สับสน
ถามนักพัฒนาสิบคนว่า Generative UI คืออะไร คุณจะได้คำตอบสิบข้อ ส่วนใหญ่จะอธิบายรูปแบบที่เฟรมเวิร์กปัจจุบันของพวกเขาส่งมาให้
มีเพียงสามรูปแบบเท่านั้น สเปกตรัมนี้ไล่จากการควบคุมที่มากขึ้นไปจนถึงความยืดหยุ่นที่มากขึ้น
- Controlled: คุณสร้างคอมโพเนนต์ไว้ล่วงหน้า agent เลือกว่าจะเรนเดอร์อันไหน
- Declarative: agent ปล่อยสคีมา แอปของคุณแมปมันกับคอมโพเนนต์
- Open-ended: agent เขียน HTML ดิบ แอปของคุณเรนเดอร์มันในแซนด์บอกซ์

ทุกเฟรมเวิร์ก Gen UI ในปี 2026 อยู่ somewhere บนเส้นนี้ ความแตกต่างเป็นเชิงสถาปัตยกรรม ไม่ใช่แค่รูปลักษณ์ แต่ละรูปแบบจะทำให้แอปของคุณพังในแบบที่แตกต่างกันเมื่อขยายขนาด
ผมลองสแต็กต่างๆ มา ส่วนใหญ่ครอบคลุมรูปแบบเดียวได้ดี ลงเอยที่ CopilotKit เพราะรองรับทั้งสามแบบบน runtime เดียวกัน ทำงานบน AG-UI นั่นคือสแต็กที่ทุกอย่างด้านล่างทำงานบน
รูปแบบที่ 1: Controlled, ฟร้อนท์เอนด์เป็นเจ้าของ UI

นี่คือจุดที่ทีมส่วนใหญ่เริ่มต้น และก็เป็นจุดที่ทีมส่วนใหญ่ติดอยู่ด้วย
คุณสร้าง React component ไว้ล่วงหน้า คุณผูกมันกับชื่อเครื่องมือ agent เลือกเครื่องมือนั้น และคอมโพเนนต์จะเรนเดอร์ inline ในแชทโดยใช้ args ของ agent เป็น props
ฮุกฟร้อนท์เอนด์หนึ่งตัว โค้ด agent ศูนย์บรรทัด เท่านั้นเอง
1"use client";2import { z } from "zod";3import { useComponent } from "@copilotkit/react-core/v2";45const expenseChartSchema = z.object({6 title: z.string(),7 data: z.array(z.object({ label: z.string(), value: z.number() })),8});910function ExpenseChart({ title, data }: z.infer<typeof expenseChartSchema>) {11 return (12 <section className="rounded-xl border p-4">13 <h3 className="text-sm font-medium">{title}</h3>14 <ul className="mt-2 grid gap-1">15 {data.map((d) => (16 <li key={d.label} className="flex justify-between text-sm">17 <span>{d.label}</span>18 <span>${d.value}</span>19 </li>20 ))}21 </ul>22 </section>23 );24}2526export function ExpensesCopilot() {27 useComponent({28 name: "showExpenseChart",29 description: "Render a breakdown of expenses by category.",30 parameters: expenseChartSchema,31 render: ExpenseChart,32 });3334 return null;35}
ฮุกจะลงทะเบียนเครื่องมือกับ runtime ของ CopilotKit runtime จะประกาศเครื่องมือให้ agent ทราบผ่าน AG-UI เมื่อ agent เรียกมัน args จะสตรีมเข้ามาและคอมโพเนนต์ของคุณจะเรนเดอร์ inline คุณไม่ต้องเขียน Python tool, ไม่ต้องต่อสคีมา, ไม่ต้องเพิ่ม API route
ระบบดีไซน์ของคุณยังคงเป็นผู้ควบคุม
แผนภูมิค่าใช้จ่ายนั้นไม่ใช่ม็อกอัป AI Financial Coach Agent เรนเดอร์การ์ดแบบเดียวกันนี้สำหรับงบประมาณจริง แผนออมเงิน และการชำระหนี้
อยากได้ฮุกเปล่าก่อนใช่ไหม? มันคือ 'use-generative-ui-examples.tsx' ใน โปรเจกต์เริ่มต้น Generative UI
ภาระโทเคน
ทุกคอมโพเนนต์ที่คุณลงทะเบียนจะอยู่ใน context window ของ agent ก่อนที่ผู้ใช้จะพูดอะไรสักคำ คำอธิบายเครื่องมือทั่วไปพร้อม JSON schema มักใช้ประมาณ 400 โทเคน 25 คอมโพเนนต์ก็ 10,000 โทเคนในทุกเทิร์น คุณจ่ายภาระนั้นทุกคำขอ
agent ก็เลือกคอมโพเนนต์ผิดได้เหมือนกัน ถ้ามันดูคล้ายกันเกินไป Pie chart และ donut chart ต่างก็ "แสดงสัดส่วน" มันเดา
เมื่อใดควรเพิ่มสถานะฝั่ง agent
สถานะที่แชร์กันคือกรณีเดียวที่การเขียน Python tool นั้นคุ้มค่า agent เขียนไปยังสถานะเซสชัน ส่วนอื่นๆ ของ UI สมัครสมาชิกและเรนเดอร์ใหม่โดยไม่ต้องเรียก LLM อีกครั้ง ปักหมุดเมตริก แดชบอร์ดก็อัปเดต เพิ่มแถว ตารางก็วาดใหม่
1from google.adk.agents import LlmAgent2from google.adk.tools import ToolContext34def pin_metric(tool_context: ToolContext, label: str, value: float) -> dict:5 """Pin a metric to the user's dashboard."""6 pinned = tool_context.state.get("pinnedMetrics", [])7 tool_context.state["pinnedMetrics"] = pinned + [{"label": label, "value": value}]8 return {"status": "pinned"}910agent = LlmAgent(name="dashboard_agent", model="gemini-3.5-flash", tools=[pin_metric])
ฟร้อนท์เอนด์อ่านเมตริกที่ปักหมุดผ่านฮุกสถานะที่แชร์ของ CopilotKit คอมโพเนนต์แชทยังคงเรนเดอร์ inline เพราะชื่อเครื่องมือเดียวกันถูกเชื่อมด้วยฮุกฟร้อนท์เอนด์
ปักหมุดเมตริกในแชท แผงก็วาดใหม่โดยไม่ต้องเรียกโมเดลอีกครั้ง นั่นคือ AI Dashboard Canvas Agent
AI Deep Research Agent ไปไกลกว่านั้น แผน, การค้นหาแต่ละครั้ง, การเขียนไฟล์แต่ละครั้ง, ทุกอย่างสตรีมเข้ามาเป็นการ์ดสด สำหรับอย่างอื่น ฮุกฟร้อนท์เอนด์ก็คือเรื่องราวทั้งหมด
เมื่อใดควรส่ง Controlled: มีไม่เกินสิบกระแสที่มีมูลค่าสูง ความแม่นยำในการออกแบบสำคัญ คุณรู้ UI ที่ต้องการแน่ชัด
เมื่อใดไม่ควร: โค้ดเบสของคุณเติบโตเป็นเส้นตรงตามจำนวนกรณีการใช้งาน 25 คอมโพเนนต์หมายถึง 25 คำจำกัดความของเครื่องมือในทุกเทิร์นของ agent
อะไรที่พัง: agent เลือกคอมโพเนนต์ผิด คำอธิบายเครื่องมือสองอันทับซ้อนกันในเชิงความหมาย เมื่อเกิน 15 เครื่องมือ สองในนั้นอาจจะอ่านว่า "แสดงข้อมูล" วิธีแก้: เขียนคำอธิบายใหม่เพื่อระบุเจตนาของผู้ใช้ ไม่ใช่ลักษณะที่เห็น "ใช้เมื่อผู้ใช้ขอเปรียบเทียบสัดส่วนของทั้งหมด" ดีกว่า "เรนเดอร์ pie chart"
รูปแบบที่ 2: Declarative (A2UI), agent ปล่อยสคีมา

นี่คือรูปแบบที่แอป agent ในระบบ production ส่วนใหญ่ต้องการในที่สุด
agent ปล่อย JSON schema ที่อธิบาย UI แอปของคุณมีแคตตาล็อกของคอมโพเนนต์ที่แมปโหนดสคีมากับ React (หรือ Svelte, Flutter, อะไรก็ได้) เครื่องมือเดียว UI มากมาย
A2UI คือสเปกมาตรฐาน CopilotKit ส่งมอบ runtime ADK เรียกใช้ agent AG-UI คือสายส่ง
เครื่องมือ agent ส่งคืนสามการดำเนินการตามลำดับ: สร้างเซอร์เฟส, ผลักโครงสร้างคอมโพเนนต์, ผลักข้อมูล
1def search_flights(flights: list[Flight]) -> dict[str, Any]:2 """Search flights and display them as rich cards."""3 return {4 "a2ui_operations": [5 {"type": "create_surface", "surfaceId": SURFACE_ID, "catalogId": CATALOG_ID},6 {"type": "update_components", "surfaceId": SURFACE_ID, "components": FLIGHT_SCHEMA},7 {"type": "update_data_model", "surfaceId": SURFACE_ID, "data": {"flights": flights}},8 ]9 }
นี่คือฟังก์ชันจริง ไม่ใช่ pseudocode runtime middleware เห็นคอนเทนเนอร์ a2ui_operations ในผลลัพธ์ของเครื่องมือ แล้วส่งต่อเซอร์เฟสไปยังฟร้อนท์เอนด์ เพิ่มโรงแรม? ไฟล์สคีมาใหม่ ฟังก์ชันอีกหนึ่งอันที่มี surface ID ต่างกัน งานฟร้อนท์เอนด์เป็นศูนย์
สคีมาคงที่ vs สคีมาไดนามิก
โครงสร้างคอมโพเนนต์ด้านบนอยู่ใน flights.json คุณเขียนมัน agent กรอกเฉพาะข้อมูล นั่นคือสคีมาคงที่
สคีมาไดนามิกกลับด้าน: LLM ตัวที่สองเขียนโครงสร้างคอมโพเนนต์ในแต่ละเทิร์นจากบริบทการสนทนา คอนเทนเนอร์ a2ui_operations เดียวกันในตอนท้าย ตัวอย่างของ Google ADK มีทั้งสองแบบ
แคตตาล็อกคือสัญญา
คำจำกัดความแสดงรายการคอมโพเนนต์ที่ agent ได้รับอนุญาตให้ปล่อย พร้อมสคีมา Zod สำหรับ props ตัวเรนเดอร์เติม React ลงไป ข้อผิดพลาดการพิมพ์กลายเป็น build errors แทนที่จะเป็นหน้าจอว่าง
1const renderers: CatalogRenderers<TravelDefinitions> = {2 FlightCard: ({ props }) => (3 <article className="rounded-xl border p-4">4 <header className="flex justify-between">5 <span>{(props as any).airline}</span>6 <span>{(props as any).price}</span>7 </header>8 <div className="text-sm text-muted-foreground">9 {(props as any).origin} → {(props as any).destination} · {(props as any).departureTime}10 </div>11 </article>12 ),13};1415export const travelCatalog = createCatalog(travelDefinitions, renderers, {16 catalogId: "copilotkit://travel-catalog",17 includeBasicCatalog: true,18});
ทั้งสองส่วนอยู่ใน โปรเจกต์เริ่มต้น Generative UI ที่เชื่อมต่อและจับคู่กันแล้ว search_flights ใน 'a2ui_fixed_schema.py', แคตตาล็อก FlightCard ใน 'renderers.tsx' ขอเที่ยวบิน ดูการ์ดสตรีมเข้ามาในแชท
ปุ่มและคอมโพเนนต์แบบโต้ตอบอื่นๆ มี action ในสคีมา แคตตาล็อกพื้นฐานเชื่อมมันกับ onClick คลิกจะยิงอีเวนต์กลับไปยัง agent ผ่าน AG-UI agent ตัดสินใจว่าจะเรนเดอร์อะไรต่อไป ไม่มีตัวจัดการคลิก
คณิตศาสตร์โทเคน
50 ประเภทการ์ดหรือ 500 ประเภท agent เห็นฟังก์ชันเดียว โทเคนต่อเทิร์นคงที่ในขณะที่ไลบรารีคอมโพเนนต์ของคุณเติบโต
ขยายได้กับเฟรมเวิร์กการเรนเดอร์ใดๆ เพราะมันเป็นแค่ JSON Agent ใดๆ ที่พูด AG-UI อยู่แล้วสามารถขับเคลื่อน A2UI ได้ตั้งแต่วันศูนย์ คุณไม่ต้องแตะโค้ด agent เพื่อเชื่อมต่อสิ่งนี้
ข้อแลกเปลี่ยน: LLM เป็นเจ้าของเลย์เอาต์ ผลลัพธ์แปรผันในแต่ละรันภายในแคตตาล็อกของคุณ ถ้าคุณกำลังส่งเอกสารทางกฎหมาย, พื้นผิวการตลาด, หรืออะไรก็ตามที่ตำแหน่งพิกเซลที่แน่นอนสำคัญ นี่ไม่ใช่ทางของคุณ
Declarative คือรูปแบบที่สร้างมาเพื่อส่วนท้ายยาว แดชบอร์ด, ผลลัพธ์, ฟอร์ม, การ์ด, วิดเจ็ต
เมื่อใดควรส่ง Declarative: คุณมีกรณีการใช้งานมากกว่าเวลาที่จะสร้างไว้ล่วงหน้า คุณใส่ใจเรื่องเศรษฐศาสตร์โทเคนหลังจากขั้นตอนต้นแบบ
อะไรที่พัง: สร้าง FlightCard ที่กำหนดเองแล้ว ทุกเที่ยวบินเรนเดอร์เป็นการ์ดทั่วไปของแคตตาล็อกพื้นฐาน ไม่มีข้อผิดพลาดในคอนโซล CATALOG_ID บน agent และ catalogId ใน createCatalog บนฟร้อนท์เอนด์ไม่ตรงกัน ฟร้อนท์เอนด์ไม่รู้จักแคตตาล็อกที่ agent กำลังกำหนดเป้าหมาย จึงถอยไปใช้พื้นฐาน จับคู่สตริงให้ตรงกันทั้งสองฝั่ง
รูปแบบที่ 3: Open-ended, ไม่มีแคตตาล็อก, ไม่มีกฎ

รูปแบบที่สามคือขั้วตรงข้าม ไม่มีแคตตาล็อก ไม่มีสคีมา แค่ผืนผ้าใบเปล่า
มีสองรูปแบบย่อยในกลุ่มนี้
MCP Apps
เซิร์ฟเวอร์ MCP เปิดเผยพื้นผิว UI ที่ agent ขับเคลื่อน Excalidraw คือตัวอย่างที่ติดตาผม agent ได้ควบคุม canvas อย่างเต็มที่ วาดไดอะแกรมจากบริบทของคุณ เป็นเจ้าของทุกพิกเซลบนบอร์ด

การเขียนโปรโตคอลไคลเอนต์จากศูนย์นั้นเจ็บปวด ดังนั้น CopilotKit จึงมาพร้อม MCPAppsMiddleware แนบมันกับ agent ของคุณแล้วชี้ไปยังเซิร์ฟเวอร์ MCP Apps ใดๆ
1const agent = new BuiltInAgent({2 model: "openai/gpt-5.5",3 prompt: "You are a helpful assistant.",4}).use(5 new MCPAppsMiddleware({6 mcpServers: [{ type: "http", url: "https://mcp.excalidraw.com/mcp", serverId: "my-server" }],7 }),8);
เปิด MCP Apps Showcase แล้วคุณจะจองเที่ยวบินและจองโรงแรมภายในหน้าต่างแชท มิดเดิลแวร์เดียวกัน, เซิร์ฟเวอร์ MCP จริง หรือจะไปไกลกว่านั้น
AI MCP App Builder ให้ agent เขียนแอปใหม่ทั้งหมดลงในแซนด์บอกซ์ E2B แล้วเรนเดอร์มันแบบสด
0:45
HTML ในแซนด์บอกซ์
agent เขียน HTML ดิบ แอปของคุณเรนเดอร์มันภายใน iframe ในแซนด์บอกซ์เพื่อป้องกันไม่ให้มันแย่งเซสชัน
runtime ลงทะเบียนเครื่องมือเรนเดอร์ HTML และส่งไปยัง agent ผ่าน AG-UI agent เรียกมันด้วยมาร์กอัปที่ต้องการ ไม่มี HTML tool ที่ต้องกำหนดในฝั่ง agent runtime จะฉีดมันเข้าไป
คำสั่งฝั่ง agent กำลังทำงานจริง:
1canvas_agent = LlmAgent(2 name="canvas_agent",3 model="gemini-3.5-flash",4 instruction=(5 "You are a visualization assistant. When the user asks to see, "6 "draw, or visualize anything, generate an interactive HTML UI. "7 "Use Tailwind classes only. No external fonts. Stick to neutral "8 "colors unless the user names one."9 ),10)
หากไม่มีกฎสไตล์เหล่านั้น โมเดลจะใช้ค่าเริ่มต้นเป็นความสวยงามที่ดังที่สุดในข้อมูลเทรนของสัปดาห์นั้น เมื่อมีกฎ คุณจะได้สิ่งที่ใกล้เคียงกับแบรนด์ของคุณบ่อยครั้ง แต่ไม่เสมอไป
ปัญหาความไม่สม่ำเสมอของแบรนด์
ผมลองใช้ Open-ended เป็น UI หลักสำหรับ agent ตัวหนึ่ง ถอนออกภายในสัปดาห์เดียว
"Neo-brutalist" ในวันอังคาร "iOS 4 clone" ในวันพุธ กฎสไตล์ในพรอมต์ช่วยกระตุ้นให้ agent เข้าหาแบรนด์ของคุณ แต่ไม่รับประกัน แบรนด์เปลี่ยนไปเรื่อยๆ ผลิตภัณฑ์ดูไม่จริงจัง

Open-ended ไม่ได้ไร้ประโยชน์ แต่มันถูกใช้ผิดที่
เหมาะสำหรับสิ่งเดียว: การโต้ตอบที่ใช้แล้วทิ้งที่ผู้ใช้ไม่สนว่าอินเทอร์เฟซจะหน้าตาเป็นยังไงและจะไม่เห็นมันอีก "แสดงให้ฉันดูว่าอิเล็กตรอนทำงานยังไง" "ให้แผนภูมิแท่งแปลกๆ ของ 10 คำถามล่าสุดของฉัน" "เห็นภาพการตอบสนอง API นี้" แบบที่คุณเห็นในภาพรวมของ Google AI
เมื่อใดควรส่ง Open-ended: คำถามครั้งเดียว การแสดงผลที่ใช้แล้วทิ้ง การทดลองในแซนด์บอกซ์ อย่าใช้เป็นพื้นผิวหลัก
อะไรที่พัง: iframe เรนเดอร์แล้ว แต่ปุ่มคลิกไม่ได้ ฟอร์มไม่ส่ง แฟล็กแซนด์บอกซ์แน่นเกินไป หรือหลวมเกินไปในแบบที่เบราว์เซอร์ปฏิเสธ ตั้งค่า iframe sandbox ให้ allow scripts และ allow forms เท่านั้น อย่าให้ allow-same-origin
วิธีเลือก
เดินตามแผนผังการตัดสินใจก่อนเขียนโค้ด
นักออกแบบมีม็อกอัปเป๊ะทุกพิกเซลสำหรับกระแสนี้หรือไม่? ใช้ Controlled
ต้องส่งการ์ดหรือวิดเจ็ตหลายสิบแบบ? ใช้ Declarative
การแสดงผลแบบใช้ครั้งเดียวที่ผู้ใช้จะไม่เห็นอีก? ใช้ Open-ended
ตัดสินใจไม่ได้? ใช้ Declarative เป็นค่าเริ่มต้น อัปเกรดเป็น Controlled สำหรับ 3 กระแสหลัก อย่าใช้ Open-ended เป็นค่าเริ่มต้น
ถ้าคุณกำลังส่งผลิตภัณฑ์อยู่แล้วและไม่แน่ใจว่าอยู่ในรูปแบบไหน ให้นับเครื่องมือเรนเดอร์ ถ้าเกิน 15 แสดงว่าคุณอยู่ใน Controlled และกำลังจะเจอกำแพง เริ่มเชื่อมต่อ A2UI สัปดาห์นี้
สามรูปแบบ สามเดิมพัน
Controlled เดิมพันที่คุณ คอมโพเนนต์ที่สร้างไว้ล่วงหน้า เป๊ะทุกพิกเซล แต่แพงเมื่อเกิน 25 ชิ้น
Declarative เดิมพันที่สคีมา สคีมาคือสัญญา agent เป็นผู้กรอกข้อมูล ขยายตัวแบบราบ
Open-ended เดิมพันที่โมเดล ไม่มีแคตตาล็อก ไม่มีสคีมา HTML ดิบ เหมาะสำหรับใช้แล้วทิ้ง เปราะบางสำหรับอะไรก็ตามที่ส่งมากกว่าหนึ่งครั้ง
ความผิดพลาดไม่ใช่การเลือกรูปแบบที่ผิด แต่คือการไม่รู้ว่าคุณได้เลือกไปแล้ว
ทีมส่วนใหญ่ใช้ Controlled เป็นค่าเริ่มต้นเพราะเฟรมเวิร์กตั้งค่าเริ่มต้นเป็น Controlled พวกเขาชนกำแพงที่ 25 คอมโพเนนต์แล้วหันไปหา Open-ended เพราะมันดูน่าสนใจในเดโม ทั้งสองอย่างไม่ใช่การตัดสินใจ แต่เป็นการเลื่อนไหล
เลือกอย่างมีจุดประสงค์ จับคู่รูปแบบกับปัญหา Controlled สำหรับกระแสที่ต้องการความแม่นยำ Declarative สำหรับส่วนท้ายยาว Open-ended สำหรับสิ่งที่ใช้แล้วทิ้ง
เทมเพลต Generative UI Agent แบบโอเพนซอร์ส
ตัวอย่างอ้างอิงสำหรับทั้งสามแบบอยู่ในส่วน Generative UI Agents ใหม่ของ awesome-llm-apps โคลนสิ่งที่คุณต้องการ ทิ้งสิ่งที่คุณไม่ต้องการ
ผมจะเผยแพร่เพิ่มเติมเกี่ยวกับการส่ง agent สู่ระบบ production, AG-UI, และรูปแบบที่ขยายตัวได้ ติดตามผม @Saboo_Shubham_ เพื่อรับข้อมูลอัปเดต










