[{"data":1,"prerenderedAt":10143},["ShallowReactive",2],{"learn-\u002Fzh\u002Flearn\u002Fgenerative-ui-react-practical-guide":3,"related-generative-ui-react-practical-guide":3480},{"id":4,"title":5,"author":6,"body":7,"category":3462,"date":3463,"description":3464,"draft":3465,"extension":3466,"featured":3465,"meta":3467,"navigation":156,"path":3470,"readTime":3471,"seo":3472,"stem":3473,"tags":3474,"__hash__":3479},"content\u002Fzh\u002Flearn\u002Fgenerative-ui-react-practical-guide.md","React 中的 Generative UI：实战指南","Alex",{"type":8,"value":9,"toc":3444},"minimark",[10,15,19,23,26,29,33,36,745,761,772,790,794,800,1371,1374,1379,1383,1386,1614,1617,1722,1725,1730,1734,1737,1740,2047,2063,2072,2076,2079,2082,2414,2417,2422,2425,2428,2522,2525,2529,2532,2594,2597,2600,2603,2607,2610,2660,2663,2666,3004,3007,3232,3235,3238,3244,3250,3256,3262,3268,3274,3277,3280,3313,3316,3319,3333,3340,3343,3346,3386,3389,3392,3427,3430,3440],[11,12,14],"h2",{"id":13},"大多数-genui-原型在这五个模式上失败","大多数 GenUI 原型在这五个模式上失败",[16,17,18],"p",{},"Generative UI 的演示看起来很神奇。但生产中的 GenUI 应用会以五种可预见的方式崩溃：工具选择脆弱、流式传输期间出现竞态条件、运行时 prop 不匹配、模型宕机时没有降级、推理成本无边界增长。本指南介绍五个真正能让 GenUI 功能存活过演示阶段的模式——注册表、分离、骨架屏、错误边界和状态管理——以及每个模式背后隐藏的权衡，还有对通常决定是否上线的两类读者（选择技术栈的工程经理，以及在有限预算上部署副业项目的独立开发者）的具体指导。",[11,20,22],{"id":21},"为什么选-react-来做-generative-ui","为什么选 React 来做 Generative UI？",[16,24,25],{},"React 的组件模型天然适合 Generative UI。组件可组合、有类型约束，可以在服务端或客户端渲染。当 AI 模型\"生成 UI\"时，它实际上做的是用特定 props 选取和组合 React 组件。",[16,27,28],{},"本指南涵盖在生产中有效的模式，以及我看到团队在初次构建生成式界面时常犯的错误。我假设你已经有了一个可用的 Next.js 环境并了解 Vercel AI SDK 基础——这是在那个基础之上的实践层。",[11,30,32],{"id":31},"模式一工具注册表","模式一：工具注册表",[16,34,35],{},"任何可维护的 Generative UI 系统的基础，是对 AI 可用组件的显式、集中化注册表。不要把工具定义分散在各个 Server Actions 中。",[37,38,43],"pre",{"className":39,"code":40,"language":41,"meta":42,"style":42},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F lib\u002Fgenui-registry.ts\nimport { z } from 'zod';\nimport { MetricCard } from '@\u002Fcomponents\u002Fmetric-card';\nimport { DataTable } from '@\u002Fcomponents\u002Fdata-table';\nimport { BarChart } from '@\u002Fcomponents\u002Fbar-chart';\nimport { AlertBanner } from '@\u002Fcomponents\u002Falert-banner';\nimport { LineChart } from '@\u002Fcomponents\u002Fline-chart';\n\nexport const tools = {\n  metricCard: {\n    description: 'Display a single KPI metric with a trend indicator. Use for scalar values like revenue, user count, or conversion rate.',\n    parameters: z.object({\n      label: z.string().describe('The metric name, e.g. \"Monthly Revenue\"'),\n      value: z.string().describe('The formatted value, e.g. \"$12,400\"'),\n      change: z.number().describe('Percentage change vs. previous period'),\n      period: z.string().describe('The comparison period, e.g. \"vs last month\"'),\n    }),\n    component: MetricCard,\n  },\n  dataTable: {\n    description: 'Display tabular data with sortable columns. Use when showing lists of items with multiple attributes.',\n    parameters: z.object({\n      columns: z.array(z.object({\n        key: z.string(),\n        label: z.string(),\n        numeric: z.boolean().optional(),\n      })),\n      rows: z.array(z.record(z.string())),\n      caption: z.string().optional(),\n    }),\n    component: DataTable,\n  },\n  barChart: {\n    description: 'Display a bar chart for categorical comparisons. Use when comparing values across discrete categories.',\n    parameters: z.object({\n      title: z.string(),\n      data: z.array(z.object({ label: z.string(), value: z.number() })),\n      yAxisLabel: z.string().optional(),\n    }),\n    component: BarChart,\n  },\n  lineChart: {\n    description: 'Display a line chart for time-series data. Use when showing trends over time.',\n    parameters: z.object({\n      title: z.string(),\n      data: z.array(z.object({ date: z.string(), value: z.number() })),\n      unit: z.string().optional(),\n    }),\n    component: LineChart,\n  },\n  alertBanner: {\n    description: 'Display an important notice, warning, or success message. Use sparingly for genuinely important information.',\n    parameters: z.object({\n      type: z.enum(['info', 'warning', 'error', 'success']),\n      title: z.string(),\n      message: z.string(),\n    }),\n    component: AlertBanner,\n  },\n};\n\nexport type ToolName = keyof typeof tools;\n","typescript","",[44,45,46,55,76,91,106,121,136,151,158,177,183,195,208,232,251,271,290,296,302,308,314,324,333,349,360,370,386,392,412,426,431,437,442,448,458,467,477,502,516,521,527,532,538,548,557,566,588,602,607,613,618,624,634,643,677,686,696,701,707,712,718,723],"code",{"__ignoreMap":42},[47,48,51],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"sJ8bj","\u002F\u002F lib\u002Fgenui-registry.ts\n",[47,56,58,62,66,69,73],{"class":49,"line":57},2,[47,59,61],{"class":60},"szBVR","import",[47,63,65],{"class":64},"sVt8B"," { z } ",[47,67,68],{"class":60},"from",[47,70,72],{"class":71},"sZZnC"," 'zod'",[47,74,75],{"class":64},";\n",[47,77,79,81,84,86,89],{"class":49,"line":78},3,[47,80,61],{"class":60},[47,82,83],{"class":64}," { MetricCard } ",[47,85,68],{"class":60},[47,87,88],{"class":71}," '@\u002Fcomponents\u002Fmetric-card'",[47,90,75],{"class":64},[47,92,94,96,99,101,104],{"class":49,"line":93},4,[47,95,61],{"class":60},[47,97,98],{"class":64}," { DataTable } ",[47,100,68],{"class":60},[47,102,103],{"class":71}," '@\u002Fcomponents\u002Fdata-table'",[47,105,75],{"class":64},[47,107,109,111,114,116,119],{"class":49,"line":108},5,[47,110,61],{"class":60},[47,112,113],{"class":64}," { BarChart } ",[47,115,68],{"class":60},[47,117,118],{"class":71}," '@\u002Fcomponents\u002Fbar-chart'",[47,120,75],{"class":64},[47,122,124,126,129,131,134],{"class":49,"line":123},6,[47,125,61],{"class":60},[47,127,128],{"class":64}," { AlertBanner } ",[47,130,68],{"class":60},[47,132,133],{"class":71}," '@\u002Fcomponents\u002Falert-banner'",[47,135,75],{"class":64},[47,137,139,141,144,146,149],{"class":49,"line":138},7,[47,140,61],{"class":60},[47,142,143],{"class":64}," { LineChart } ",[47,145,68],{"class":60},[47,147,148],{"class":71}," '@\u002Fcomponents\u002Fline-chart'",[47,150,75],{"class":64},[47,152,154],{"class":49,"line":153},8,[47,155,157],{"emptyLinePlaceholder":156},true,"\n",[47,159,161,164,167,171,174],{"class":49,"line":160},9,[47,162,163],{"class":60},"export",[47,165,166],{"class":60}," const",[47,168,170],{"class":169},"sj4cs"," tools",[47,172,173],{"class":60}," =",[47,175,176],{"class":64}," {\n",[47,178,180],{"class":49,"line":179},10,[47,181,182],{"class":64},"  metricCard: {\n",[47,184,186,189,192],{"class":49,"line":185},11,[47,187,188],{"class":64},"    description: ",[47,190,191],{"class":71},"'Display a single KPI metric with a trend indicator. Use for scalar values like revenue, user count, or conversion rate.'",[47,193,194],{"class":64},",\n",[47,196,198,201,205],{"class":49,"line":197},12,[47,199,200],{"class":64},"    parameters: z.",[47,202,204],{"class":203},"sScJk","object",[47,206,207],{"class":64},"({\n",[47,209,211,214,217,220,223,226,229],{"class":49,"line":210},13,[47,212,213],{"class":64},"      label: z.",[47,215,216],{"class":203},"string",[47,218,219],{"class":64},"().",[47,221,222],{"class":203},"describe",[47,224,225],{"class":64},"(",[47,227,228],{"class":71},"'The metric name, e.g. \"Monthly Revenue\"'",[47,230,231],{"class":64},"),\n",[47,233,235,238,240,242,244,246,249],{"class":49,"line":234},14,[47,236,237],{"class":64},"      value: z.",[47,239,216],{"class":203},[47,241,219],{"class":64},[47,243,222],{"class":203},[47,245,225],{"class":64},[47,247,248],{"class":71},"'The formatted value, e.g. \"$12,400\"'",[47,250,231],{"class":64},[47,252,254,257,260,262,264,266,269],{"class":49,"line":253},15,[47,255,256],{"class":64},"      change: z.",[47,258,259],{"class":203},"number",[47,261,219],{"class":64},[47,263,222],{"class":203},[47,265,225],{"class":64},[47,267,268],{"class":71},"'Percentage change vs. previous period'",[47,270,231],{"class":64},[47,272,274,277,279,281,283,285,288],{"class":49,"line":273},16,[47,275,276],{"class":64},"      period: z.",[47,278,216],{"class":203},[47,280,219],{"class":64},[47,282,222],{"class":203},[47,284,225],{"class":64},[47,286,287],{"class":71},"'The comparison period, e.g. \"vs last month\"'",[47,289,231],{"class":64},[47,291,293],{"class":49,"line":292},17,[47,294,295],{"class":64},"    }),\n",[47,297,299],{"class":49,"line":298},18,[47,300,301],{"class":64},"    component: MetricCard,\n",[47,303,305],{"class":49,"line":304},19,[47,306,307],{"class":64},"  },\n",[47,309,311],{"class":49,"line":310},20,[47,312,313],{"class":64},"  dataTable: {\n",[47,315,317,319,322],{"class":49,"line":316},21,[47,318,188],{"class":64},[47,320,321],{"class":71},"'Display tabular data with sortable columns. Use when showing lists of items with multiple attributes.'",[47,323,194],{"class":64},[47,325,327,329,331],{"class":49,"line":326},22,[47,328,200],{"class":64},[47,330,204],{"class":203},[47,332,207],{"class":64},[47,334,336,339,342,345,347],{"class":49,"line":335},23,[47,337,338],{"class":64},"      columns: z.",[47,340,341],{"class":203},"array",[47,343,344],{"class":64},"(z.",[47,346,204],{"class":203},[47,348,207],{"class":64},[47,350,352,355,357],{"class":49,"line":351},24,[47,353,354],{"class":64},"        key: z.",[47,356,216],{"class":203},[47,358,359],{"class":64},"(),\n",[47,361,363,366,368],{"class":49,"line":362},25,[47,364,365],{"class":64},"        label: z.",[47,367,216],{"class":203},[47,369,359],{"class":64},[47,371,373,376,379,381,384],{"class":49,"line":372},26,[47,374,375],{"class":64},"        numeric: z.",[47,377,378],{"class":203},"boolean",[47,380,219],{"class":64},[47,382,383],{"class":203},"optional",[47,385,359],{"class":64},[47,387,389],{"class":49,"line":388},27,[47,390,391],{"class":64},"      })),\n",[47,393,395,398,400,402,405,407,409],{"class":49,"line":394},28,[47,396,397],{"class":64},"      rows: z.",[47,399,341],{"class":203},[47,401,344],{"class":64},[47,403,404],{"class":203},"record",[47,406,344],{"class":64},[47,408,216],{"class":203},[47,410,411],{"class":64},"())),\n",[47,413,415,418,420,422,424],{"class":49,"line":414},29,[47,416,417],{"class":64},"      caption: z.",[47,419,216],{"class":203},[47,421,219],{"class":64},[47,423,383],{"class":203},[47,425,359],{"class":64},[47,427,429],{"class":49,"line":428},30,[47,430,295],{"class":64},[47,432,434],{"class":49,"line":433},31,[47,435,436],{"class":64},"    component: DataTable,\n",[47,438,440],{"class":49,"line":439},32,[47,441,307],{"class":64},[47,443,445],{"class":49,"line":444},33,[47,446,447],{"class":64},"  barChart: {\n",[47,449,451,453,456],{"class":49,"line":450},34,[47,452,188],{"class":64},[47,454,455],{"class":71},"'Display a bar chart for categorical comparisons. Use when comparing values across discrete categories.'",[47,457,194],{"class":64},[47,459,461,463,465],{"class":49,"line":460},35,[47,462,200],{"class":64},[47,464,204],{"class":203},[47,466,207],{"class":64},[47,468,470,473,475],{"class":49,"line":469},36,[47,471,472],{"class":64},"      title: z.",[47,474,216],{"class":203},[47,476,359],{"class":64},[47,478,480,483,485,487,489,492,494,497,499],{"class":49,"line":479},37,[47,481,482],{"class":64},"      data: z.",[47,484,341],{"class":203},[47,486,344],{"class":64},[47,488,204],{"class":203},[47,490,491],{"class":64},"({ label: z.",[47,493,216],{"class":203},[47,495,496],{"class":64},"(), value: z.",[47,498,259],{"class":203},[47,500,501],{"class":64},"() })),\n",[47,503,505,508,510,512,514],{"class":49,"line":504},38,[47,506,507],{"class":64},"      yAxisLabel: z.",[47,509,216],{"class":203},[47,511,219],{"class":64},[47,513,383],{"class":203},[47,515,359],{"class":64},[47,517,519],{"class":49,"line":518},39,[47,520,295],{"class":64},[47,522,524],{"class":49,"line":523},40,[47,525,526],{"class":64},"    component: BarChart,\n",[47,528,530],{"class":49,"line":529},41,[47,531,307],{"class":64},[47,533,535],{"class":49,"line":534},42,[47,536,537],{"class":64},"  lineChart: {\n",[47,539,541,543,546],{"class":49,"line":540},43,[47,542,188],{"class":64},[47,544,545],{"class":71},"'Display a line chart for time-series data. Use when showing trends over time.'",[47,547,194],{"class":64},[47,549,551,553,555],{"class":49,"line":550},44,[47,552,200],{"class":64},[47,554,204],{"class":203},[47,556,207],{"class":64},[47,558,560,562,564],{"class":49,"line":559},45,[47,561,472],{"class":64},[47,563,216],{"class":203},[47,565,359],{"class":64},[47,567,569,571,573,575,577,580,582,584,586],{"class":49,"line":568},46,[47,570,482],{"class":64},[47,572,341],{"class":203},[47,574,344],{"class":64},[47,576,204],{"class":203},[47,578,579],{"class":64},"({ date: z.",[47,581,216],{"class":203},[47,583,496],{"class":64},[47,585,259],{"class":203},[47,587,501],{"class":64},[47,589,591,594,596,598,600],{"class":49,"line":590},47,[47,592,593],{"class":64},"      unit: z.",[47,595,216],{"class":203},[47,597,219],{"class":64},[47,599,383],{"class":203},[47,601,359],{"class":64},[47,603,605],{"class":49,"line":604},48,[47,606,295],{"class":64},[47,608,610],{"class":49,"line":609},49,[47,611,612],{"class":64},"    component: LineChart,\n",[47,614,616],{"class":49,"line":615},50,[47,617,307],{"class":64},[47,619,621],{"class":49,"line":620},51,[47,622,623],{"class":64},"  alertBanner: {\n",[47,625,627,629,632],{"class":49,"line":626},52,[47,628,188],{"class":64},[47,630,631],{"class":71},"'Display an important notice, warning, or success message. Use sparingly for genuinely important information.'",[47,633,194],{"class":64},[47,635,637,639,641],{"class":49,"line":636},53,[47,638,200],{"class":64},[47,640,204],{"class":203},[47,642,207],{"class":64},[47,644,646,649,652,655,658,661,664,666,669,671,674],{"class":49,"line":645},54,[47,647,648],{"class":64},"      type: z.",[47,650,651],{"class":203},"enum",[47,653,654],{"class":64},"([",[47,656,657],{"class":71},"'info'",[47,659,660],{"class":64},", ",[47,662,663],{"class":71},"'warning'",[47,665,660],{"class":64},[47,667,668],{"class":71},"'error'",[47,670,660],{"class":64},[47,672,673],{"class":71},"'success'",[47,675,676],{"class":64},"]),\n",[47,678,680,682,684],{"class":49,"line":679},55,[47,681,472],{"class":64},[47,683,216],{"class":203},[47,685,359],{"class":64},[47,687,689,692,694],{"class":49,"line":688},56,[47,690,691],{"class":64},"      message: z.",[47,693,216],{"class":203},[47,695,359],{"class":64},[47,697,699],{"class":49,"line":698},57,[47,700,295],{"class":64},[47,702,704],{"class":49,"line":703},58,[47,705,706],{"class":64},"    component: AlertBanner,\n",[47,708,710],{"class":49,"line":709},59,[47,711,307],{"class":64},[47,713,715],{"class":49,"line":714},60,[47,716,717],{"class":64},"};\n",[47,719,721],{"class":49,"line":720},61,[47,722,157],{"emptyLinePlaceholder":156},[47,724,726,728,731,734,736,739,742],{"class":49,"line":725},62,[47,727,163],{"class":60},[47,729,730],{"class":60}," type",[47,732,733],{"class":203}," ToolName",[47,735,173],{"class":60},[47,737,738],{"class":60}," keyof",[47,740,741],{"class":60}," typeof",[47,743,744],{"class":64}," tools;\n",[16,746,747,751,752,755,756,760],{},[748,749,750],"strong",{},"关键洞察："," ",[44,753,754],{},"description"," 字段是 AI 读取以决定使用哪个组件的内容。为 AI 而写，而不是为人类而写。明确说明每个组件何时适用，更重要的是，何时",[757,758,759],"em",{},"不","适用。",[16,762,763,764,767,768,771],{},"注意 ",[44,765,766],{},"lineChart"," 写的是\"时间序列\"，",[44,769,770],{},"barChart"," 写的是\"分类\"。没有这个区分，AI 会在两者之间随机选择。描述越精确，组件选择越好。",[16,773,774,777,778,789],{},[748,775,776],{},"这个模式的失败场景。"," 集中注册表假设一个团队拥有目录。如果三个产品团队各自想要自己的组件，注册表就会成为协调瓶颈——每个新工具都需要经过平台团队的 PR。替代方案是每个产品面按产品面建立联邦注册表，代价是描述重复和质量参差不齐。单一产品选集中化，为多个产品面服务的平台选联邦化。参见官方 ",[779,780,784,785,788],"a",{"href":781,"rel":782},"https:\u002F\u002Fsdk.vercel.ai\u002Fdocs\u002Fai-sdk-rsc\u002Fstreaming-react-components",[783],"nofollow","Vercel AI SDK ",[44,786,787],{},"streamUI"," 文档","了解底层 API。",[11,791,793],{"id":792},"模式二分离注册表与流式传输","模式二：分离注册表与流式传输",[16,795,796,797,799],{},"将注册表定义与 ",[44,798,787],{}," 调用分开。这样你就可以在多个 Server Actions 中复用工具定义，并让注册表可以独立测试。",[37,801,803],{"className":39,"code":802,"language":41,"meta":42,"style":42},"\u002F\u002F lib\u002Fstream-with-tools.ts\nimport { streamUI } from 'ai\u002Frsc';\nimport { openai } from '@ai-sdk\u002Fopenai';\nimport { tools } from '.\u002Fgenui-registry';\n\n\u002F\u002F 将注册表格式转换为 streamUI 格式\nfunction buildStreamTools(toolNames: ToolName[]) {\n  return Object.fromEntries(\n    toolNames.map((name: ToolName) => [\n      name,\n      {\n        description: tools[name].description,\n        parameters: tools[name].parameters,\n        generate: async function* (params: unknown) {\n          yield \u003CToolSkeleton name={name} \u002F>;\n          const Component = tools[name].component;\n          \u002F\u002F 空值保护：注册表条目可能配置错误或在请求中途热重载。\n          if (!Component) {\n            return \u003CGenUIFallback error={new Error(`Missing component for tool: ${name}`)} resetErrorBoundary={() => {}} \u002F>;\n          }\n          return \u003CComponent {...(params as Record\u003Cstring, unknown>)} \u002F>;\n        },\n      },\n    ])\n  );\n}\n\n\u002F\u002F 数据仪表板的 Server Action\nexport async function generateDashboard(query: string) {\n  const result = await streamUI({\n    model: openai('gpt-4o'),\n    system: 'You are a data analyst assistant. Display information using the appropriate visualization tool.',\n    prompt: query,\n    tools: buildStreamTools(['metricCard', 'dataTable', 'barChart', 'lineChart', 'alertBanner']),\n  });\n  return result.value;\n}\n\n\u002F\u002F 摘要视图的 Server Action（工具更少 = 聚焦更好）\nexport async function generateSummary(query: string) {\n  const result = await streamUI({\n    model: openai('gpt-4o'),\n    system: 'You are a concise assistant. Show a summary with key metrics only.',\n    prompt: query,\n    tools: buildStreamTools(['metricCard', 'alertBanner']),\n  });\n  return result.value;\n}\n",[44,804,805,810,824,838,852,856,861,883,897,924,929,934,939,944,972,994,1007,1012,1025,1070,1075,1114,1119,1124,1129,1134,1139,1143,1148,1173,1191,1206,1216,1221,1256,1261,1268,1272,1276,1281,1302,1316,1328,1337,1341,1357,1361,1367],{"__ignoreMap":42},[47,806,807],{"class":49,"line":50},[47,808,809],{"class":53},"\u002F\u002F lib\u002Fstream-with-tools.ts\n",[47,811,812,814,817,819,822],{"class":49,"line":57},[47,813,61],{"class":60},[47,815,816],{"class":64}," { streamUI } ",[47,818,68],{"class":60},[47,820,821],{"class":71}," 'ai\u002Frsc'",[47,823,75],{"class":64},[47,825,826,828,831,833,836],{"class":49,"line":78},[47,827,61],{"class":60},[47,829,830],{"class":64}," { openai } ",[47,832,68],{"class":60},[47,834,835],{"class":71}," '@ai-sdk\u002Fopenai'",[47,837,75],{"class":64},[47,839,840,842,845,847,850],{"class":49,"line":93},[47,841,61],{"class":60},[47,843,844],{"class":64}," { tools } ",[47,846,68],{"class":60},[47,848,849],{"class":71}," '.\u002Fgenui-registry'",[47,851,75],{"class":64},[47,853,854],{"class":49,"line":108},[47,855,157],{"emptyLinePlaceholder":156},[47,857,858],{"class":49,"line":123},[47,859,860],{"class":53},"\u002F\u002F 将注册表格式转换为 streamUI 格式\n",[47,862,863,866,869,871,875,878,880],{"class":49,"line":138},[47,864,865],{"class":60},"function",[47,867,868],{"class":203}," buildStreamTools",[47,870,225],{"class":64},[47,872,874],{"class":873},"s4XuR","toolNames",[47,876,877],{"class":60},":",[47,879,733],{"class":203},[47,881,882],{"class":64},"[]) {\n",[47,884,885,888,891,894],{"class":49,"line":153},[47,886,887],{"class":60},"  return",[47,889,890],{"class":64}," Object.",[47,892,893],{"class":203},"fromEntries",[47,895,896],{"class":64},"(\n",[47,898,899,902,905,908,911,913,915,918,921],{"class":49,"line":160},[47,900,901],{"class":64},"    toolNames.",[47,903,904],{"class":203},"map",[47,906,907],{"class":64},"((",[47,909,910],{"class":873},"name",[47,912,877],{"class":60},[47,914,733],{"class":203},[47,916,917],{"class":64},") ",[47,919,920],{"class":60},"=>",[47,922,923],{"class":64}," [\n",[47,925,926],{"class":49,"line":179},[47,927,928],{"class":64},"      name,\n",[47,930,931],{"class":49,"line":185},[47,932,933],{"class":64},"      {\n",[47,935,936],{"class":49,"line":197},[47,937,938],{"class":64},"        description: tools[name].description,\n",[47,940,941],{"class":49,"line":210},[47,942,943],{"class":64},"        parameters: tools[name].parameters,\n",[47,945,946,949,952,955,958,961,964,966,969],{"class":49,"line":234},[47,947,948],{"class":203},"        generate",[47,950,951],{"class":64},": ",[47,953,954],{"class":60},"async",[47,956,957],{"class":60}," function*",[47,959,960],{"class":64}," (",[47,962,963],{"class":873},"params",[47,965,877],{"class":60},[47,967,968],{"class":169}," unknown",[47,970,971],{"class":64},") {\n",[47,973,974,977,980,983,986,989,991],{"class":49,"line":253},[47,975,976],{"class":60},"          yield",[47,978,979],{"class":64}," \u003C",[47,981,982],{"class":203},"ToolSkeleton",[47,984,985],{"class":203}," name",[47,987,988],{"class":64},"={",[47,990,910],{"class":873},[47,992,993],{"class":64},"} \u002F>;\n",[47,995,996,999,1002,1004],{"class":49,"line":273},[47,997,998],{"class":60},"          const",[47,1000,1001],{"class":169}," Component",[47,1003,173],{"class":60},[47,1005,1006],{"class":64}," tools[name].component;\n",[47,1008,1009],{"class":49,"line":292},[47,1010,1011],{"class":53},"          \u002F\u002F 空值保护：注册表条目可能配置错误或在请求中途热重载。\n",[47,1013,1014,1017,1019,1022],{"class":49,"line":298},[47,1015,1016],{"class":60},"          if",[47,1018,960],{"class":64},[47,1020,1021],{"class":60},"!",[47,1023,1024],{"class":64},"Component) {\n",[47,1026,1027,1030,1032,1035,1038,1040,1043,1046,1048,1051,1053,1056,1059,1062,1065,1067],{"class":49,"line":304},[47,1028,1029],{"class":60},"            return",[47,1031,979],{"class":64},[47,1033,1034],{"class":203},"GenUIFallback",[47,1036,1037],{"class":203}," error",[47,1039,988],{"class":64},[47,1041,1042],{"class":203},"new",[47,1044,1045],{"class":203}," Error",[47,1047,225],{"class":64},[47,1049,1050],{"class":71},"`Missing component for tool: ${",[47,1052,910],{"class":64},[47,1054,1055],{"class":71},"}`",[47,1057,1058],{"class":64},")} ",[47,1060,1061],{"class":203},"resetErrorBoundary",[47,1063,1064],{"class":64},"={() ",[47,1066,920],{"class":60},[47,1068,1069],{"class":64}," {}} \u002F>;\n",[47,1071,1072],{"class":49,"line":310},[47,1073,1074],{"class":64},"          }\n",[47,1076,1077,1080,1082,1085,1088,1091,1093,1095,1098,1101,1104,1106,1108,1111],{"class":49,"line":316},[47,1078,1079],{"class":60},"          return",[47,1081,979],{"class":64},[47,1083,1084],{"class":203},"Component",[47,1086,1087],{"class":64}," {",[47,1089,1090],{"class":60},"...",[47,1092,225],{"class":64},[47,1094,963],{"class":203},[47,1096,1097],{"class":203}," as",[47,1099,1100],{"class":203}," Record",[47,1102,1103],{"class":64},"\u003C",[47,1105,216],{"class":169},[47,1107,660],{"class":64},[47,1109,1110],{"class":169},"unknown",[47,1112,1113],{"class":64},">)} \u002F>;\n",[47,1115,1116],{"class":49,"line":326},[47,1117,1118],{"class":64},"        },\n",[47,1120,1121],{"class":49,"line":335},[47,1122,1123],{"class":64},"      },\n",[47,1125,1126],{"class":49,"line":351},[47,1127,1128],{"class":64},"    ])\n",[47,1130,1131],{"class":49,"line":362},[47,1132,1133],{"class":64},"  );\n",[47,1135,1136],{"class":49,"line":372},[47,1137,1138],{"class":64},"}\n",[47,1140,1141],{"class":49,"line":388},[47,1142,157],{"emptyLinePlaceholder":156},[47,1144,1145],{"class":49,"line":394},[47,1146,1147],{"class":53},"\u002F\u002F 数据仪表板的 Server Action\n",[47,1149,1150,1152,1155,1158,1161,1163,1166,1168,1171],{"class":49,"line":414},[47,1151,163],{"class":60},[47,1153,1154],{"class":60}," async",[47,1156,1157],{"class":60}," function",[47,1159,1160],{"class":203}," generateDashboard",[47,1162,225],{"class":64},[47,1164,1165],{"class":873},"query",[47,1167,877],{"class":60},[47,1169,1170],{"class":169}," string",[47,1172,971],{"class":64},[47,1174,1175,1178,1181,1183,1186,1189],{"class":49,"line":428},[47,1176,1177],{"class":60},"  const",[47,1179,1180],{"class":169}," result",[47,1182,173],{"class":60},[47,1184,1185],{"class":60}," await",[47,1187,1188],{"class":203}," streamUI",[47,1190,207],{"class":64},[47,1192,1193,1196,1199,1201,1204],{"class":49,"line":433},[47,1194,1195],{"class":64},"    model: ",[47,1197,1198],{"class":203},"openai",[47,1200,225],{"class":64},[47,1202,1203],{"class":71},"'gpt-4o'",[47,1205,231],{"class":64},[47,1207,1208,1211,1214],{"class":49,"line":439},[47,1209,1210],{"class":64},"    system: ",[47,1212,1213],{"class":71},"'You are a data analyst assistant. Display information using the appropriate visualization tool.'",[47,1215,194],{"class":64},[47,1217,1218],{"class":49,"line":444},[47,1219,1220],{"class":64},"    prompt: query,\n",[47,1222,1223,1226,1229,1231,1234,1236,1239,1241,1244,1246,1249,1251,1254],{"class":49,"line":450},[47,1224,1225],{"class":64},"    tools: ",[47,1227,1228],{"class":203},"buildStreamTools",[47,1230,654],{"class":64},[47,1232,1233],{"class":71},"'metricCard'",[47,1235,660],{"class":64},[47,1237,1238],{"class":71},"'dataTable'",[47,1240,660],{"class":64},[47,1242,1243],{"class":71},"'barChart'",[47,1245,660],{"class":64},[47,1247,1248],{"class":71},"'lineChart'",[47,1250,660],{"class":64},[47,1252,1253],{"class":71},"'alertBanner'",[47,1255,676],{"class":64},[47,1257,1258],{"class":49,"line":460},[47,1259,1260],{"class":64},"  });\n",[47,1262,1263,1265],{"class":49,"line":469},[47,1264,887],{"class":60},[47,1266,1267],{"class":64}," result.value;\n",[47,1269,1270],{"class":49,"line":479},[47,1271,1138],{"class":64},[47,1273,1274],{"class":49,"line":504},[47,1275,157],{"emptyLinePlaceholder":156},[47,1277,1278],{"class":49,"line":518},[47,1279,1280],{"class":53},"\u002F\u002F 摘要视图的 Server Action（工具更少 = 聚焦更好）\n",[47,1282,1283,1285,1287,1289,1292,1294,1296,1298,1300],{"class":49,"line":523},[47,1284,163],{"class":60},[47,1286,1154],{"class":60},[47,1288,1157],{"class":60},[47,1290,1291],{"class":203}," generateSummary",[47,1293,225],{"class":64},[47,1295,1165],{"class":873},[47,1297,877],{"class":60},[47,1299,1170],{"class":169},[47,1301,971],{"class":64},[47,1303,1304,1306,1308,1310,1312,1314],{"class":49,"line":529},[47,1305,1177],{"class":60},[47,1307,1180],{"class":169},[47,1309,173],{"class":60},[47,1311,1185],{"class":60},[47,1313,1188],{"class":203},[47,1315,207],{"class":64},[47,1317,1318,1320,1322,1324,1326],{"class":49,"line":534},[47,1319,1195],{"class":64},[47,1321,1198],{"class":203},[47,1323,225],{"class":64},[47,1325,1203],{"class":71},[47,1327,231],{"class":64},[47,1329,1330,1332,1335],{"class":49,"line":540},[47,1331,1210],{"class":64},[47,1333,1334],{"class":71},"'You are a concise assistant. Show a summary with key metrics only.'",[47,1336,194],{"class":64},[47,1338,1339],{"class":49,"line":550},[47,1340,1220],{"class":64},[47,1342,1343,1345,1347,1349,1351,1353,1355],{"class":49,"line":559},[47,1344,1225],{"class":64},[47,1346,1228],{"class":203},[47,1348,654],{"class":64},[47,1350,1233],{"class":71},[47,1352,660],{"class":64},[47,1354,1253],{"class":71},[47,1356,676],{"class":64},[47,1358,1359],{"class":49,"line":568},[47,1360,1260],{"class":64},[47,1362,1363,1365],{"class":49,"line":590},[47,1364,887],{"class":60},[47,1366,1267],{"class":64},[47,1368,1369],{"class":49,"line":604},[47,1370,1138],{"class":64},[16,1372,1373],{},"向每个 Server Action 传递工具子集很重要。聚焦的工具集能产生更好的 AI 决策。不要在 5 个够用的情况下给 AI 20 个工具。",[16,1375,1376,1378],{},[748,1377,776],{}," 将注册表和流式传输分离增加了一层间接性。对于只有一个工具的单页面原型，这层间接性是开销，不是架构。等第二个 Server Action 出现时再内联工具定义。",[11,1380,1382],{"id":1381},"模式三带骨架屏的流式传输","模式三：带骨架屏的流式传输",[16,1384,1385],{},"在 AI 生成期间永远不要显示空白屏幕。显示与预期输出形状匹配的骨架加载状态。这种视觉连续性能显著降低感知延迟。",[37,1387,1391],{"className":1388,"code":1389,"language":1390,"meta":42,"style":42},"language-tsx shiki shiki-themes github-light github-dark","\u002F\u002F components\u002Ftool-skeleton.tsx\nimport { ToolName } from '@\u002Flib\u002Fgenui-registry';\n\nconst SKELETON_HEIGHTS: Record\u003CToolName, string> = {\n  metricCard: 'h-28',\n  dataTable: 'h-48',\n  barChart: 'h-64',\n  lineChart: 'h-64',\n  alertBanner: 'h-16',\n};\n\nexport function ToolSkeleton({ name }: { name: ToolName }) {\n  return (\n    \u003Cdiv\n      className={`animate-pulse rounded-lg bg-muted ${SKELETON_HEIGHTS[name] ?? 'h-32'} w-full`}\n      aria-label=\"加载中...\"\n      aria-busy=\"true\"\n    \u002F>\n  );\n}\n","tsx",[44,1392,1393,1398,1412,1416,1445,1455,1465,1475,1484,1494,1498,1502,1533,1540,1549,1581,1591,1601,1606,1610],{"__ignoreMap":42},[47,1394,1395],{"class":49,"line":50},[47,1396,1397],{"class":53},"\u002F\u002F components\u002Ftool-skeleton.tsx\n",[47,1399,1400,1402,1405,1407,1410],{"class":49,"line":57},[47,1401,61],{"class":60},[47,1403,1404],{"class":64}," { ToolName } ",[47,1406,68],{"class":60},[47,1408,1409],{"class":71}," '@\u002Flib\u002Fgenui-registry'",[47,1411,75],{"class":64},[47,1413,1414],{"class":49,"line":78},[47,1415,157],{"emptyLinePlaceholder":156},[47,1417,1418,1421,1424,1426,1428,1430,1433,1435,1437,1440,1443],{"class":49,"line":93},[47,1419,1420],{"class":60},"const",[47,1422,1423],{"class":169}," SKELETON_HEIGHTS",[47,1425,877],{"class":60},[47,1427,1100],{"class":203},[47,1429,1103],{"class":64},[47,1431,1432],{"class":203},"ToolName",[47,1434,660],{"class":64},[47,1436,216],{"class":169},[47,1438,1439],{"class":64},"> ",[47,1441,1442],{"class":60},"=",[47,1444,176],{"class":64},[47,1446,1447,1450,1453],{"class":49,"line":108},[47,1448,1449],{"class":64},"  metricCard: ",[47,1451,1452],{"class":71},"'h-28'",[47,1454,194],{"class":64},[47,1456,1457,1460,1463],{"class":49,"line":123},[47,1458,1459],{"class":64},"  dataTable: ",[47,1461,1462],{"class":71},"'h-48'",[47,1464,194],{"class":64},[47,1466,1467,1470,1473],{"class":49,"line":138},[47,1468,1469],{"class":64},"  barChart: ",[47,1471,1472],{"class":71},"'h-64'",[47,1474,194],{"class":64},[47,1476,1477,1480,1482],{"class":49,"line":153},[47,1478,1479],{"class":64},"  lineChart: ",[47,1481,1472],{"class":71},[47,1483,194],{"class":64},[47,1485,1486,1489,1492],{"class":49,"line":160},[47,1487,1488],{"class":64},"  alertBanner: ",[47,1490,1491],{"class":71},"'h-16'",[47,1493,194],{"class":64},[47,1495,1496],{"class":49,"line":179},[47,1497,717],{"class":64},[47,1499,1500],{"class":49,"line":185},[47,1501,157],{"emptyLinePlaceholder":156},[47,1503,1504,1506,1508,1511,1514,1516,1519,1521,1524,1526,1528,1530],{"class":49,"line":197},[47,1505,163],{"class":60},[47,1507,1157],{"class":60},[47,1509,1510],{"class":203}," ToolSkeleton",[47,1512,1513],{"class":64},"({ ",[47,1515,910],{"class":873},[47,1517,1518],{"class":64}," }",[47,1520,877],{"class":60},[47,1522,1523],{"class":64}," { ",[47,1525,910],{"class":873},[47,1527,877],{"class":60},[47,1529,733],{"class":203},[47,1531,1532],{"class":64}," }) {\n",[47,1534,1535,1537],{"class":49,"line":210},[47,1536,887],{"class":60},[47,1538,1539],{"class":64}," (\n",[47,1541,1542,1545],{"class":49,"line":234},[47,1543,1544],{"class":64},"    \u003C",[47,1546,1548],{"class":1547},"s9eBZ","div\n",[47,1550,1551,1554,1556,1559,1562,1565,1568,1570,1573,1576,1579],{"class":49,"line":253},[47,1552,1553],{"class":203},"      className",[47,1555,1442],{"class":60},[47,1557,1558],{"class":64},"{",[47,1560,1561],{"class":71},"`animate-pulse rounded-lg bg-muted ${",[47,1563,1564],{"class":169},"SKELETON_HEIGHTS",[47,1566,1567],{"class":71},"[",[47,1569,910],{"class":64},[47,1571,1572],{"class":71},"] ",[47,1574,1575],{"class":60},"??",[47,1577,1578],{"class":71}," 'h-32'} w-full`",[47,1580,1138],{"class":64},[47,1582,1583,1586,1588],{"class":49,"line":273},[47,1584,1585],{"class":203},"      aria-label",[47,1587,1442],{"class":60},[47,1589,1590],{"class":71},"\"加载中...\"\n",[47,1592,1593,1596,1598],{"class":49,"line":292},[47,1594,1595],{"class":203},"      aria-busy",[47,1597,1442],{"class":60},[47,1599,1600],{"class":71},"\"true\"\n",[47,1602,1603],{"class":49,"line":298},[47,1604,1605],{"class":64},"    \u002F>\n",[47,1607,1608],{"class":49,"line":304},[47,1609,1133],{"class":64},[47,1611,1612],{"class":49,"line":310},[47,1613,1138],{"class":64},[16,1615,1616],{},"对于更精确的骨架屏，匹配组件的内部结构：",[37,1618,1620],{"className":1388,"code":1619,"language":1390,"meta":42,"style":42},"export function MetricCardSkeleton() {\n  return (\n    \u003Cdiv className=\"rounded-lg border bg-card p-6\">\n      \u003Cdiv className=\"h-4 w-24 animate-pulse rounded bg-muted\" \u002F>\n      \u003Cdiv className=\"mt-3 h-8 w-32 animate-pulse rounded bg-muted\" \u002F>\n      \u003Cdiv className=\"mt-2 h-3 w-16 animate-pulse rounded bg-muted\" \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n",[44,1621,1622,1634,1640,1658,1675,1690,1705,1714,1718],{"__ignoreMap":42},[47,1623,1624,1626,1628,1631],{"class":49,"line":50},[47,1625,163],{"class":60},[47,1627,1157],{"class":60},[47,1629,1630],{"class":203}," MetricCardSkeleton",[47,1632,1633],{"class":64},"() {\n",[47,1635,1636,1638],{"class":49,"line":57},[47,1637,887],{"class":60},[47,1639,1539],{"class":64},[47,1641,1642,1644,1647,1650,1652,1655],{"class":49,"line":78},[47,1643,1544],{"class":64},[47,1645,1646],{"class":1547},"div",[47,1648,1649],{"class":203}," className",[47,1651,1442],{"class":60},[47,1653,1654],{"class":71},"\"rounded-lg border bg-card p-6\"",[47,1656,1657],{"class":64},">\n",[47,1659,1660,1663,1665,1667,1669,1672],{"class":49,"line":93},[47,1661,1662],{"class":64},"      \u003C",[47,1664,1646],{"class":1547},[47,1666,1649],{"class":203},[47,1668,1442],{"class":60},[47,1670,1671],{"class":71},"\"h-4 w-24 animate-pulse rounded bg-muted\"",[47,1673,1674],{"class":64}," \u002F>\n",[47,1676,1677,1679,1681,1683,1685,1688],{"class":49,"line":108},[47,1678,1662],{"class":64},[47,1680,1646],{"class":1547},[47,1682,1649],{"class":203},[47,1684,1442],{"class":60},[47,1686,1687],{"class":71},"\"mt-3 h-8 w-32 animate-pulse rounded bg-muted\"",[47,1689,1674],{"class":64},[47,1691,1692,1694,1696,1698,1700,1703],{"class":49,"line":123},[47,1693,1662],{"class":64},[47,1695,1646],{"class":1547},[47,1697,1649],{"class":203},[47,1699,1442],{"class":60},[47,1701,1702],{"class":71},"\"mt-2 h-3 w-16 animate-pulse rounded bg-muted\"",[47,1704,1674],{"class":64},[47,1706,1707,1710,1712],{"class":49,"line":138},[47,1708,1709],{"class":64},"    \u003C\u002F",[47,1711,1646],{"class":1547},[47,1713,1657],{"class":64},[47,1715,1716],{"class":49,"line":153},[47,1717,1133],{"class":64},[47,1719,1720],{"class":49,"line":160},[47,1721,1138],{"class":64},[16,1723,1724],{},"匹配内部形状意味着从骨架屏到加载完成组件的过渡是平滑的——没有布局偏移，没有闪烁。",[16,1726,1727,1729],{},[748,1728,776],{}," 每个组件定制骨架屏会让维护面翻倍：每次组件更新都需要同步更新对应的骨架屏。对于感知延迟不影响业务的低流量内部工具，通用的灰色方块就够了。将精心调整的骨架屏留给每次会话都展示给终端用户的界面。",[11,1731,1733],{"id":1732},"模式四生成-ui-的错误边界","模式四：生成 UI 的错误边界",[16,1735,1736],{},"生成的组件以手工编码的组件不会出现的方式失败。AI 可能传入一个数字字符串在需要数字的地方，一个负值在只接受正值的地方，或者一个空数组给需要至少一项的组件。",[16,1738,1739],{},"始终用错误边界包裹生成的输出：",[37,1741,1743],{"className":1388,"code":1742,"language":1390,"meta":42,"style":42},"\u002F\u002F components\u002Fsafe-genui.tsx\n'use client';\n\nimport { ErrorBoundary } from 'react-error-boundary';\n\nfunction GenUIFallback({ error, resetErrorBoundary }: {\n  error: Error;\n  resetErrorBoundary: () => void;\n}) {\n  return (\n    \u003Cdiv className=\"rounded-lg border border-destructive\u002F50 bg-destructive\u002F5 p-4\">\n      \u003Cp className=\"text-sm font-medium text-destructive\">\n        此组件无法渲染\n      \u003C\u002Fp>\n      \u003Cp className=\"mt-1 text-xs text-muted-foreground\">{error.message}\u003C\u002Fp>\n      \u003Cbutton\n        onClick={resetErrorBoundary}\n        className=\"mt-2 text-xs underline text-muted-foreground\"\n      >\n        重试\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n\nexport function SafeGenUI({ children }: { children: React.ReactNode }) {\n  return (\n    \u003CErrorBoundary FallbackComponent={GenUIFallback}>\n      {children}\n    \u003C\u002FErrorBoundary>\n  );\n}\n",[44,1744,1745,1750,1757,1761,1775,1779,1801,1812,1829,1834,1840,1855,1870,1875,1884,1904,1911,1921,1931,1936,1941,1950,1958,1962,1966,1970,2005,2011,2026,2031,2039,2043],{"__ignoreMap":42},[47,1746,1747],{"class":49,"line":50},[47,1748,1749],{"class":53},"\u002F\u002F components\u002Fsafe-genui.tsx\n",[47,1751,1752,1755],{"class":49,"line":57},[47,1753,1754],{"class":71},"'use client'",[47,1756,75],{"class":64},[47,1758,1759],{"class":49,"line":78},[47,1760,157],{"emptyLinePlaceholder":156},[47,1762,1763,1765,1768,1770,1773],{"class":49,"line":93},[47,1764,61],{"class":60},[47,1766,1767],{"class":64}," { ErrorBoundary } ",[47,1769,68],{"class":60},[47,1771,1772],{"class":71}," 'react-error-boundary'",[47,1774,75],{"class":64},[47,1776,1777],{"class":49,"line":108},[47,1778,157],{"emptyLinePlaceholder":156},[47,1780,1781,1783,1786,1788,1791,1793,1795,1797,1799],{"class":49,"line":123},[47,1782,865],{"class":60},[47,1784,1785],{"class":203}," GenUIFallback",[47,1787,1513],{"class":64},[47,1789,1790],{"class":873},"error",[47,1792,660],{"class":64},[47,1794,1061],{"class":873},[47,1796,1518],{"class":64},[47,1798,877],{"class":60},[47,1800,176],{"class":64},[47,1802,1803,1806,1808,1810],{"class":49,"line":138},[47,1804,1805],{"class":873},"  error",[47,1807,877],{"class":60},[47,1809,1045],{"class":203},[47,1811,75],{"class":64},[47,1813,1814,1817,1819,1822,1824,1827],{"class":49,"line":153},[47,1815,1816],{"class":203},"  resetErrorBoundary",[47,1818,877],{"class":60},[47,1820,1821],{"class":64}," () ",[47,1823,920],{"class":60},[47,1825,1826],{"class":169}," void",[47,1828,75],{"class":64},[47,1830,1831],{"class":49,"line":160},[47,1832,1833],{"class":64},"}) {\n",[47,1835,1836,1838],{"class":49,"line":179},[47,1837,887],{"class":60},[47,1839,1539],{"class":64},[47,1841,1842,1844,1846,1848,1850,1853],{"class":49,"line":185},[47,1843,1544],{"class":64},[47,1845,1646],{"class":1547},[47,1847,1649],{"class":203},[47,1849,1442],{"class":60},[47,1851,1852],{"class":71},"\"rounded-lg border border-destructive\u002F50 bg-destructive\u002F5 p-4\"",[47,1854,1657],{"class":64},[47,1856,1857,1859,1861,1863,1865,1868],{"class":49,"line":197},[47,1858,1662],{"class":64},[47,1860,16],{"class":1547},[47,1862,1649],{"class":203},[47,1864,1442],{"class":60},[47,1866,1867],{"class":71},"\"text-sm font-medium text-destructive\"",[47,1869,1657],{"class":64},[47,1871,1872],{"class":49,"line":210},[47,1873,1874],{"class":64},"        此组件无法渲染\n",[47,1876,1877,1880,1882],{"class":49,"line":234},[47,1878,1879],{"class":64},"      \u003C\u002F",[47,1881,16],{"class":1547},[47,1883,1657],{"class":64},[47,1885,1886,1888,1890,1892,1894,1897,1900,1902],{"class":49,"line":253},[47,1887,1662],{"class":64},[47,1889,16],{"class":1547},[47,1891,1649],{"class":203},[47,1893,1442],{"class":60},[47,1895,1896],{"class":71},"\"mt-1 text-xs text-muted-foreground\"",[47,1898,1899],{"class":64},">{error.message}\u003C\u002F",[47,1901,16],{"class":1547},[47,1903,1657],{"class":64},[47,1905,1906,1908],{"class":49,"line":273},[47,1907,1662],{"class":64},[47,1909,1910],{"class":1547},"button\n",[47,1912,1913,1916,1918],{"class":49,"line":292},[47,1914,1915],{"class":203},"        onClick",[47,1917,1442],{"class":60},[47,1919,1920],{"class":64},"{resetErrorBoundary}\n",[47,1922,1923,1926,1928],{"class":49,"line":298},[47,1924,1925],{"class":203},"        className",[47,1927,1442],{"class":60},[47,1929,1930],{"class":71},"\"mt-2 text-xs underline text-muted-foreground\"\n",[47,1932,1933],{"class":49,"line":304},[47,1934,1935],{"class":64},"      >\n",[47,1937,1938],{"class":49,"line":310},[47,1939,1940],{"class":64},"        重试\n",[47,1942,1943,1945,1948],{"class":49,"line":316},[47,1944,1879],{"class":64},[47,1946,1947],{"class":1547},"button",[47,1949,1657],{"class":64},[47,1951,1952,1954,1956],{"class":49,"line":326},[47,1953,1709],{"class":64},[47,1955,1646],{"class":1547},[47,1957,1657],{"class":64},[47,1959,1960],{"class":49,"line":335},[47,1961,1133],{"class":64},[47,1963,1964],{"class":49,"line":351},[47,1965,1138],{"class":64},[47,1967,1968],{"class":49,"line":362},[47,1969,157],{"emptyLinePlaceholder":156},[47,1971,1972,1974,1976,1979,1981,1984,1986,1988,1990,1992,1994,1997,2000,2003],{"class":49,"line":372},[47,1973,163],{"class":60},[47,1975,1157],{"class":60},[47,1977,1978],{"class":203}," SafeGenUI",[47,1980,1513],{"class":64},[47,1982,1983],{"class":873},"children",[47,1985,1518],{"class":64},[47,1987,877],{"class":60},[47,1989,1523],{"class":64},[47,1991,1983],{"class":873},[47,1993,877],{"class":60},[47,1995,1996],{"class":203}," React",[47,1998,1999],{"class":64},".",[47,2001,2002],{"class":203},"ReactNode",[47,2004,1532],{"class":64},[47,2006,2007,2009],{"class":49,"line":388},[47,2008,887],{"class":60},[47,2010,1539],{"class":64},[47,2012,2013,2015,2018,2021,2023],{"class":49,"line":394},[47,2014,1544],{"class":64},[47,2016,2017],{"class":169},"ErrorBoundary",[47,2019,2020],{"class":203}," FallbackComponent",[47,2022,1442],{"class":60},[47,2024,2025],{"class":64},"{GenUIFallback}>\n",[47,2027,2028],{"class":49,"line":414},[47,2029,2030],{"class":64},"      {children}\n",[47,2032,2033,2035,2037],{"class":49,"line":428},[47,2034,1709],{"class":64},[47,2036,2017],{"class":169},[47,2038,1657],{"class":64},[47,2040,2041],{"class":49,"line":433},[47,2042,1133],{"class":64},[47,2044,2045],{"class":49,"line":439},[47,2046,1138],{"class":64},[16,2048,2049,2050,2053,2054,2062],{},"用 ",[44,2051,2052],{},"\u003CSafeGenUI>"," 包裹每一块生成的输出。一个组件的渲染错误不应该破坏整个响应。",[779,2055,2058,2061],{"href":2056,"rel":2057},"https:\u002F\u002Fgithub.com\u002Fbvaughn\u002Freact-error-boundary",[783],[44,2059,2060],{},"react-error-boundary"," 库","处理底层机制。",[16,2064,2065,2067,2068,2071],{},[748,2066,776],{}," 错误边界会吞掉异常。如果你不将 ",[44,2069,2070],{},"error.message"," 输送到可观测性工具（Sentry、GlitchTip、Datadog），同样的 bug 可能在生产中悄悄触发数周。没有日志记录的错误边界比没有错误边界更糟，因为它隐藏了症状。",[11,2073,2075],{"id":2074},"模式五生成交互的状态管理","模式五：生成交互的状态管理",[16,2077,2078],{},"AI 生成的组件通常需要是可交互的——可排序的表格、带工具提示的图表、提交数据的表单。这种交互性存在于组件本身，不需要特殊考虑。",[16,2080,2081],{},"需要思考的是当生成的 UI 需要影响其自身之外的应用状态时：",[37,2083,2085],{"className":1388,"code":2084,"language":1390,"meta":42,"style":42},"\u002F\u002F 使用 React context 让生成的组件与应用交互\nexport const AppStateContext = createContext\u003C{\n  onDataSelected: (data: unknown) => void;\n  onActionTriggered: (action: string, params: unknown) => void;\n} | null>(null);\n\n\u002F\u002F 在你的生成组件中\nfunction DataTable({ columns, rows }: DataTableProps) {\n  const appState = useContext(AppStateContext);\n\n  function handleRowClick(row: Record\u003Cstring, string>) {\n    appState?.onDataSelected(row);\n  }\n\n  return (\n    \u003Ctable>\n      {\u002F* ... *\u002F}\n      {rows.map((row, i) => (\n        \u003Ctr key={i} onClick={() => handleRowClick(row)} className=\"cursor-pointer hover:bg-muted\">\n          {\u002F* ... *\u002F}\n        \u003C\u002Ftr>\n      ))}\n    \u003C\u002Ftable>\n  );\n}\n",[44,2086,2087,2092,2109,2133,2165,2185,2189,2194,2220,2235,2239,2267,2278,2283,2287,2293,2302,2312,2334,2375,2384,2393,2398,2406,2410],{"__ignoreMap":42},[47,2088,2089],{"class":49,"line":50},[47,2090,2091],{"class":53},"\u002F\u002F 使用 React context 让生成的组件与应用交互\n",[47,2093,2094,2096,2098,2101,2103,2106],{"class":49,"line":57},[47,2095,163],{"class":60},[47,2097,166],{"class":60},[47,2099,2100],{"class":169}," AppStateContext",[47,2102,173],{"class":60},[47,2104,2105],{"class":203}," createContext",[47,2107,2108],{"class":64},"\u003C{\n",[47,2110,2111,2114,2116,2118,2121,2123,2125,2127,2129,2131],{"class":49,"line":78},[47,2112,2113],{"class":203},"  onDataSelected",[47,2115,877],{"class":60},[47,2117,960],{"class":64},[47,2119,2120],{"class":873},"data",[47,2122,877],{"class":60},[47,2124,968],{"class":169},[47,2126,917],{"class":64},[47,2128,920],{"class":60},[47,2130,1826],{"class":169},[47,2132,75],{"class":64},[47,2134,2135,2138,2140,2142,2145,2147,2149,2151,2153,2155,2157,2159,2161,2163],{"class":49,"line":93},[47,2136,2137],{"class":203},"  onActionTriggered",[47,2139,877],{"class":60},[47,2141,960],{"class":64},[47,2143,2144],{"class":873},"action",[47,2146,877],{"class":60},[47,2148,1170],{"class":169},[47,2150,660],{"class":64},[47,2152,963],{"class":873},[47,2154,877],{"class":60},[47,2156,968],{"class":169},[47,2158,917],{"class":64},[47,2160,920],{"class":60},[47,2162,1826],{"class":169},[47,2164,75],{"class":64},[47,2166,2167,2170,2173,2176,2179,2182],{"class":49,"line":108},[47,2168,2169],{"class":64},"} ",[47,2171,2172],{"class":60},"|",[47,2174,2175],{"class":169}," null",[47,2177,2178],{"class":64},">(",[47,2180,2181],{"class":169},"null",[47,2183,2184],{"class":64},");\n",[47,2186,2187],{"class":49,"line":123},[47,2188,157],{"emptyLinePlaceholder":156},[47,2190,2191],{"class":49,"line":138},[47,2192,2193],{"class":53},"\u002F\u002F 在你的生成组件中\n",[47,2195,2196,2198,2201,2203,2206,2208,2211,2213,2215,2218],{"class":49,"line":153},[47,2197,865],{"class":60},[47,2199,2200],{"class":203}," DataTable",[47,2202,1513],{"class":64},[47,2204,2205],{"class":873},"columns",[47,2207,660],{"class":64},[47,2209,2210],{"class":873},"rows",[47,2212,1518],{"class":64},[47,2214,877],{"class":60},[47,2216,2217],{"class":203}," DataTableProps",[47,2219,971],{"class":64},[47,2221,2222,2224,2227,2229,2232],{"class":49,"line":160},[47,2223,1177],{"class":60},[47,2225,2226],{"class":169}," appState",[47,2228,173],{"class":60},[47,2230,2231],{"class":203}," useContext",[47,2233,2234],{"class":64},"(AppStateContext);\n",[47,2236,2237],{"class":49,"line":179},[47,2238,157],{"emptyLinePlaceholder":156},[47,2240,2241,2244,2247,2249,2252,2254,2256,2258,2260,2262,2264],{"class":49,"line":185},[47,2242,2243],{"class":60},"  function",[47,2245,2246],{"class":203}," handleRowClick",[47,2248,225],{"class":64},[47,2250,2251],{"class":873},"row",[47,2253,877],{"class":60},[47,2255,1100],{"class":203},[47,2257,1103],{"class":64},[47,2259,216],{"class":169},[47,2261,660],{"class":64},[47,2263,216],{"class":169},[47,2265,2266],{"class":64},">) {\n",[47,2268,2269,2272,2275],{"class":49,"line":197},[47,2270,2271],{"class":64},"    appState?.",[47,2273,2274],{"class":203},"onDataSelected",[47,2276,2277],{"class":64},"(row);\n",[47,2279,2280],{"class":49,"line":210},[47,2281,2282],{"class":64},"  }\n",[47,2284,2285],{"class":49,"line":234},[47,2286,157],{"emptyLinePlaceholder":156},[47,2288,2289,2291],{"class":49,"line":253},[47,2290,887],{"class":60},[47,2292,1539],{"class":64},[47,2294,2295,2297,2300],{"class":49,"line":273},[47,2296,1544],{"class":64},[47,2298,2299],{"class":1547},"table",[47,2301,1657],{"class":64},[47,2303,2304,2307,2310],{"class":49,"line":292},[47,2305,2306],{"class":64},"      {",[47,2308,2309],{"class":53},"\u002F* ... *\u002F",[47,2311,1138],{"class":64},[47,2313,2314,2317,2319,2321,2323,2325,2328,2330,2332],{"class":49,"line":298},[47,2315,2316],{"class":64},"      {rows.",[47,2318,904],{"class":203},[47,2320,907],{"class":64},[47,2322,2251],{"class":873},[47,2324,660],{"class":64},[47,2326,2327],{"class":873},"i",[47,2329,917],{"class":64},[47,2331,920],{"class":60},[47,2333,1539],{"class":64},[47,2335,2336,2339,2342,2345,2347,2350,2353,2355,2358,2360,2362,2365,2368,2370,2373],{"class":49,"line":304},[47,2337,2338],{"class":64},"        \u003C",[47,2340,2341],{"class":1547},"tr",[47,2343,2344],{"class":203}," key",[47,2346,1442],{"class":60},[47,2348,2349],{"class":64},"{i} ",[47,2351,2352],{"class":203},"onClick",[47,2354,1442],{"class":60},[47,2356,2357],{"class":64},"{() ",[47,2359,920],{"class":60},[47,2361,2246],{"class":203},[47,2363,2364],{"class":64},"(row)} ",[47,2366,2367],{"class":203},"className",[47,2369,1442],{"class":60},[47,2371,2372],{"class":71},"\"cursor-pointer hover:bg-muted\"",[47,2374,1657],{"class":64},[47,2376,2377,2380,2382],{"class":49,"line":310},[47,2378,2379],{"class":64},"          {",[47,2381,2309],{"class":53},[47,2383,1138],{"class":64},[47,2385,2386,2389,2391],{"class":49,"line":316},[47,2387,2388],{"class":64},"        \u003C\u002F",[47,2390,2341],{"class":1547},[47,2392,1657],{"class":64},[47,2394,2395],{"class":49,"line":326},[47,2396,2397],{"class":64},"      ))}\n",[47,2399,2400,2402,2404],{"class":49,"line":335},[47,2401,1709],{"class":64},[47,2403,2299],{"class":1547},[47,2405,1657],{"class":64},[47,2407,2408],{"class":49,"line":351},[47,2409,1133],{"class":64},[47,2411,2412],{"class":49,"line":362},[47,2413,1138],{"class":64},[16,2415,2416],{},"为生成的组件设计清晰的外部交互 API。通过 context 传递回调 props，而不是直接导入全局状态——生成的组件应该是可移植的。",[16,2418,2419,2421],{},[748,2420,776],{}," 基于 context 的状态耦合使生成的组件无法独立测试：你现在需要在每个 Storybook 故事中都挂载一个 provider。如果只有一两个组件需要外部状态，老老实实 prop drilling 更诚实。只有当三个或更多组件共享相同的出站接口时，才引入 context。",[11,2423,2424],{"id":2424},"模式选择矩阵",[16,2426,2427],{},"对于优先规定哪些模式的工程经理，权衡取舍如下：",[2299,2429,2430,2448],{},[2431,2432,2433],"thead",{},[2341,2434,2435,2439,2442,2445],{},[2436,2437,2438],"th",{},"模式",[2436,2440,2441],{},"引入成本",[2436,2443,2444],{},"收益",[2436,2446,2447],{},"可跳过的情况",[2449,2450,2451,2466,2480,2494,2508],"tbody",{},[2341,2452,2453,2457,2460,2463],{},[2454,2455,2456],"td",{},"注册表",[2454,2458,2459],{},"1 天",[2454,2461,2462],{},"随目录增长成倍放大；可测试性所需",[2454,2464,2465],{},"你只有 1 个工具，永远",[2341,2467,2468,2471,2474,2477],{},[2454,2469,2470],{},"注册表\u002F流式传输分离",[2454,2472,2473],{},"2 小时",[2454,2475,2476],{},"跨界面复用；独立单元测试",[2454,2478,2479],{},"单个 Server Action",[2341,2481,2482,2485,2488,2491],{},[2454,2483,2484],{},"骨架屏",[2454,2486,2487],{},"每个组件 1 天（定制），1 小时（通用）",[2454,2489,2490],{},"流式传输时的感知延迟；慢速模型必需",[2454,2492,2493],{},"内部工具，无 SLA",[2341,2495,2496,2499,2502,2505],{},[2454,2497,2498],{},"错误边界",[2454,2500,2501],{},"2 小时 + 日志接入",[2454,2503,2504],{},"生产必需；没有它每个 prop bug 都是白屏",[2454,2506,2507],{},"永远不要——始终发布这个",[2341,2509,2510,2513,2516,2519],{},[2454,2511,2512],{},"外部状态",[2454,2514,2515],{},"0.5–2 天",[2454,2517,2518],{},"GenUI 触发应用动作时必需",[2454,2520,2521],{},"只读展示",[16,2523,2524],{},"错误边界一行是唯一不可协商的。其他四个按团队规模排序：独立开发者最后发布骨架屏；5 人团队第一天就建注册表，因为没有注册表的协调成本高于建注册表的成本。",[11,2526,2528],{"id":2527},"按团队规模估算-tco","按团队规模估算 TCO",[16,2530,2531],{},"12 个月的大致总拥有成本，假设使用 GPT-4o 级别的推理和中等流量产品（每天 10,000 次生成）。这些是一阶估算——在承诺之前请根据你自己的遥测数据进行校准。",[2299,2533,2534,2550],{},[2431,2535,2536],{},[2341,2537,2538,2541,2544,2547],{},[2436,2539,2540],{},"团队规模",[2436,2542,2543],{},"构建（工程周）",[2436,2545,2546],{},"推理（$\u002F月）",[2436,2548,2549],{},"运维 + 值班（工程小时\u002F月）",[2449,2551,2552,2566,2580],{},[2341,2553,2554,2557,2560,2563],{},[2454,2555,2556],{},"独立（个人）",[2454,2558,2559],{},"2–3 周",[2454,2561,2562],{},"$150–$400",[2454,2564,2565],{},"4–8",[2341,2567,2568,2571,2574,2577],{},[2454,2569,2570],{},"小团队（3–5 人）",[2454,2572,2573],{},"4–6 周",[2454,2575,2576],{},"$400–$1,200",[2454,2578,2579],{},"8–16",[2341,2581,2582,2585,2588,2591],{},[2454,2583,2584],{},"中团队（10+ 人）",[2454,2586,2587],{},"8–12 周",[2454,2589,2590],{},"$1,200–$5,000+",[2454,2592,2593],{},"16–40",[16,2595,2596],{},"规模化时推理成本占主导。最便宜的杠杆是减少每个 Server Action 的工具数（模式二）并缓存相同的提示词；其次是将简单查询路由到更小的模型。",[11,2598,2599],{"id":2599},"团队采用路线图",[16,2601,2602],{},"第 1–2 周：在功能标志后面向 5% 的用户发布模式四（错误边界）和模式一（注册表），包含两三个工具。第 3–4 周：添加模式三（骨架屏）和模式二（分离）；扩展到 25%。第 5–8 周：添加模式五（状态）；推广到 100%。在每个关口等到 p95 延迟、错误率和每次会话的推理成本都在你公布的 SLO 内，再继续。在第一批工具稳定之前不要向注册表添加更多工具。",[11,2604,2606],{"id":2605},"部署你的-genui-应用独立开发者路径","部署你的 GenUI 应用（独立开发者路径）",[16,2608,2609],{},"如果你是独立工程师，想这个周末发布一个 GenUI 功能，这是最短的可信路径：",[2611,2612,2613,2635,2638,2641,2647,2657],"ol",{},[2614,2615,2616,2617,2620,2621,2624,2625,2624,2628,2631,2632,2634],"li",{},"从带 App Router 的 ",[44,2618,2619],{},"create-next-app"," 开始。安装 ",[44,2622,2623],{},"ai","、",[44,2626,2627],{},"@ai-sdk\u002Fopenai",[44,2629,2630],{},"zod"," 和 ",[44,2633,2060],{},"。",[2614,2636,2637],{},"第一个版本跳过模式二——直接在 Server Action 中内联两个工具。",[2614,2639,2640],{},"使用模式三的通用灰色方块骨架，而不是定制版本。等功能有用户之后再发布定制版本。",[2614,2642,2643,2644,2646],{},"用模式四的 ",[44,2645,2052],{}," 包裹流式传输输出。这是不可协商的。",[2614,2648,2649,2650,2653,2654,2634],{},"部署到 Vercel 免费或 Pro 套餐。在环境变量中添加 ",[44,2651,2652],{},"OPENAI_API_KEY","。首次部署就是 ",[44,2655,2656],{},"git push",[2614,2658,2659],{},"在 OpenAI 密钥上设置硬性使用上限（OpenAI 仪表板支持月度限制），这样失控的循环就不会在一夜之间耗尽你的预算。",[16,2661,2662],{},"独立开发者的副业项目成本估算（每月 1,000 次生成）：推理约 $5–$15，在 Vercel 爱好者套餐上托管 $0，使用 Vercel 内置日志监控 $0。根据我们的估算，第一张有意义的账单大约在每月约 50,000 次生成时开始出现；那是模式二（拆分 Server Actions）和提示词缓存开始物有所值的时候。",[16,2664,2665],{},"独立开发者规模的简化注册表——一个带有一个工具和骨架屏的 Server Action，可直接粘贴：",[37,2667,2669],{"className":1388,"code":2668,"language":1390,"meta":42,"style":42},"\u002F\u002F app\u002Factions.tsx\n'use server'\nimport { streamUI } from 'ai\u002Frsc'\nimport { openai } from '@ai-sdk\u002Fopenai'\nimport { z } from 'zod'\nimport { MetricCard } from '@\u002Fcomponents\u002Fmetric-card'\nimport { Skeleton } from '@\u002Fcomponents\u002Fskeleton'\n\nconst metricSchema = z.object({\n  value: z.number().describe('current numeric value of the metric'),\n  label: z.string().describe('human-readable metric name'),\n  delta: z.number().describe('percent change vs previous period'),\n})\n\nexport async function generateUI(prompt: string) {\n  const result = await streamUI({\n    model: openai('gpt-4o-mini'),\n    prompt,\n    tools: {\n      metricCard: {\n        description: 'Show a single KPI value with a delta',\n        parameters: metricSchema,\n        generate: async function* (p: z.infer\u003Ctypeof metricSchema>) {\n          yield \u003CSkeleton className=\"h-28 rounded bg-muted\" \u002F>\n          return \u003CMetricCard {...p} period=\"vs last month\" \u002F>\n        },\n      },\n    },\n  })\n  return result.value\n}\n",[44,2670,2671,2676,2681,2692,2703,2714,2725,2737,2741,2757,2775,2793,2811,2816,2820,2842,2856,2869,2874,2879,2884,2894,2899,2931,2949,2975,2979,2983,2988,2993,3000],{"__ignoreMap":42},[47,2672,2673],{"class":49,"line":50},[47,2674,2675],{"class":53},"\u002F\u002F app\u002Factions.tsx\n",[47,2677,2678],{"class":49,"line":57},[47,2679,2680],{"class":71},"'use server'\n",[47,2682,2683,2685,2687,2689],{"class":49,"line":78},[47,2684,61],{"class":60},[47,2686,816],{"class":64},[47,2688,68],{"class":60},[47,2690,2691],{"class":71}," 'ai\u002Frsc'\n",[47,2693,2694,2696,2698,2700],{"class":49,"line":93},[47,2695,61],{"class":60},[47,2697,830],{"class":64},[47,2699,68],{"class":60},[47,2701,2702],{"class":71}," '@ai-sdk\u002Fopenai'\n",[47,2704,2705,2707,2709,2711],{"class":49,"line":108},[47,2706,61],{"class":60},[47,2708,65],{"class":64},[47,2710,68],{"class":60},[47,2712,2713],{"class":71}," 'zod'\n",[47,2715,2716,2718,2720,2722],{"class":49,"line":123},[47,2717,61],{"class":60},[47,2719,83],{"class":64},[47,2721,68],{"class":60},[47,2723,2724],{"class":71}," '@\u002Fcomponents\u002Fmetric-card'\n",[47,2726,2727,2729,2732,2734],{"class":49,"line":138},[47,2728,61],{"class":60},[47,2730,2731],{"class":64}," { Skeleton } ",[47,2733,68],{"class":60},[47,2735,2736],{"class":71}," '@\u002Fcomponents\u002Fskeleton'\n",[47,2738,2739],{"class":49,"line":153},[47,2740,157],{"emptyLinePlaceholder":156},[47,2742,2743,2745,2748,2750,2753,2755],{"class":49,"line":160},[47,2744,1420],{"class":60},[47,2746,2747],{"class":169}," metricSchema",[47,2749,173],{"class":60},[47,2751,2752],{"class":64}," z.",[47,2754,204],{"class":203},[47,2756,207],{"class":64},[47,2758,2759,2762,2764,2766,2768,2770,2773],{"class":49,"line":179},[47,2760,2761],{"class":64},"  value: z.",[47,2763,259],{"class":203},[47,2765,219],{"class":64},[47,2767,222],{"class":203},[47,2769,225],{"class":64},[47,2771,2772],{"class":71},"'current numeric value of the metric'",[47,2774,231],{"class":64},[47,2776,2777,2780,2782,2784,2786,2788,2791],{"class":49,"line":185},[47,2778,2779],{"class":64},"  label: z.",[47,2781,216],{"class":203},[47,2783,219],{"class":64},[47,2785,222],{"class":203},[47,2787,225],{"class":64},[47,2789,2790],{"class":71},"'human-readable metric name'",[47,2792,231],{"class":64},[47,2794,2795,2798,2800,2802,2804,2806,2809],{"class":49,"line":197},[47,2796,2797],{"class":64},"  delta: z.",[47,2799,259],{"class":203},[47,2801,219],{"class":64},[47,2803,222],{"class":203},[47,2805,225],{"class":64},[47,2807,2808],{"class":71},"'percent change vs previous period'",[47,2810,231],{"class":64},[47,2812,2813],{"class":49,"line":210},[47,2814,2815],{"class":64},"})\n",[47,2817,2818],{"class":49,"line":234},[47,2819,157],{"emptyLinePlaceholder":156},[47,2821,2822,2824,2826,2828,2831,2833,2836,2838,2840],{"class":49,"line":253},[47,2823,163],{"class":60},[47,2825,1154],{"class":60},[47,2827,1157],{"class":60},[47,2829,2830],{"class":203}," generateUI",[47,2832,225],{"class":64},[47,2834,2835],{"class":873},"prompt",[47,2837,877],{"class":60},[47,2839,1170],{"class":169},[47,2841,971],{"class":64},[47,2843,2844,2846,2848,2850,2852,2854],{"class":49,"line":273},[47,2845,1177],{"class":60},[47,2847,1180],{"class":169},[47,2849,173],{"class":60},[47,2851,1185],{"class":60},[47,2853,1188],{"class":203},[47,2855,207],{"class":64},[47,2857,2858,2860,2862,2864,2867],{"class":49,"line":292},[47,2859,1195],{"class":64},[47,2861,1198],{"class":203},[47,2863,225],{"class":64},[47,2865,2866],{"class":71},"'gpt-4o-mini'",[47,2868,231],{"class":64},[47,2870,2871],{"class":49,"line":298},[47,2872,2873],{"class":64},"    prompt,\n",[47,2875,2876],{"class":49,"line":304},[47,2877,2878],{"class":64},"    tools: {\n",[47,2880,2881],{"class":49,"line":310},[47,2882,2883],{"class":64},"      metricCard: {\n",[47,2885,2886,2889,2892],{"class":49,"line":316},[47,2887,2888],{"class":64},"        description: ",[47,2890,2891],{"class":71},"'Show a single KPI value with a delta'",[47,2893,194],{"class":64},[47,2895,2896],{"class":49,"line":326},[47,2897,2898],{"class":64},"        parameters: metricSchema,\n",[47,2900,2901,2903,2905,2907,2909,2911,2913,2915,2918,2920,2923,2925,2928],{"class":49,"line":335},[47,2902,948],{"class":203},[47,2904,951],{"class":64},[47,2906,954],{"class":60},[47,2908,957],{"class":60},[47,2910,960],{"class":64},[47,2912,16],{"class":873},[47,2914,877],{"class":60},[47,2916,2917],{"class":203}," z",[47,2919,1999],{"class":64},[47,2921,2922],{"class":203},"infer",[47,2924,1103],{"class":64},[47,2926,2927],{"class":60},"typeof",[47,2929,2930],{"class":64}," metricSchema>) {\n",[47,2932,2933,2935,2937,2940,2942,2944,2947],{"class":49,"line":351},[47,2934,976],{"class":60},[47,2936,979],{"class":64},[47,2938,2939],{"class":169},"Skeleton",[47,2941,1649],{"class":203},[47,2943,1442],{"class":60},[47,2945,2946],{"class":71},"\"h-28 rounded bg-muted\"",[47,2948,1674],{"class":64},[47,2950,2951,2953,2955,2958,2960,2962,2965,2968,2970,2973],{"class":49,"line":362},[47,2952,1079],{"class":60},[47,2954,979],{"class":64},[47,2956,2957],{"class":169},"MetricCard",[47,2959,1087],{"class":64},[47,2961,1090],{"class":60},[47,2963,2964],{"class":64},"p} ",[47,2966,2967],{"class":203},"period",[47,2969,1442],{"class":60},[47,2971,2972],{"class":71},"\"vs last month\"",[47,2974,1674],{"class":64},[47,2976,2977],{"class":49,"line":372},[47,2978,1118],{"class":64},[47,2980,2981],{"class":49,"line":388},[47,2982,1123],{"class":64},[47,2984,2985],{"class":49,"line":394},[47,2986,2987],{"class":64},"    },\n",[47,2989,2990],{"class":49,"line":414},[47,2991,2992],{"class":64},"  })\n",[47,2994,2995,2997],{"class":49,"line":428},[47,2996,887],{"class":60},[47,2998,2999],{"class":64}," result.value\n",[47,3001,3002],{"class":49,"line":433},[47,3003,1138],{"class":64},[16,3005,3006],{},"以及一个单表单客户端调用：",[37,3008,3010],{"className":1388,"code":3009,"language":1390,"meta":42,"style":42},"\u002F\u002F app\u002Fpage.tsx\n'use client'\nimport { useState } from 'react'\nimport { generateUI } from '.\u002Factions'\n\nexport default function Page() {\n  const [ui, setUI] = useState\u003CReact.ReactNode>(null)\n  return (\n    \u003Cform action={async (formData) => {\n      setUI(await generateUI(formData.get('q') as string))\n    }}>\n      \u003Cinput name=\"q\" \u002F>\n      \u003Cbutton>生成\u003C\u002Fbutton>\n      \u003Cdiv>{ui}\u003C\u002Fdiv>\n    \u003C\u002Fform>\n  )\n}\n",[44,3011,3012,3017,3022,3034,3046,3050,3064,3102,3108,3135,3168,3173,3189,3202,3215,3223,3228],{"__ignoreMap":42},[47,3013,3014],{"class":49,"line":50},[47,3015,3016],{"class":53},"\u002F\u002F app\u002Fpage.tsx\n",[47,3018,3019],{"class":49,"line":57},[47,3020,3021],{"class":71},"'use client'\n",[47,3023,3024,3026,3029,3031],{"class":49,"line":78},[47,3025,61],{"class":60},[47,3027,3028],{"class":64}," { useState } ",[47,3030,68],{"class":60},[47,3032,3033],{"class":71}," 'react'\n",[47,3035,3036,3038,3041,3043],{"class":49,"line":93},[47,3037,61],{"class":60},[47,3039,3040],{"class":64}," { generateUI } ",[47,3042,68],{"class":60},[47,3044,3045],{"class":71}," '.\u002Factions'\n",[47,3047,3048],{"class":49,"line":108},[47,3049,157],{"emptyLinePlaceholder":156},[47,3051,3052,3054,3057,3059,3062],{"class":49,"line":123},[47,3053,163],{"class":60},[47,3055,3056],{"class":60}," default",[47,3058,1157],{"class":60},[47,3060,3061],{"class":203}," Page",[47,3063,1633],{"class":64},[47,3065,3066,3068,3071,3074,3076,3079,3081,3083,3086,3088,3091,3093,3095,3097,3099],{"class":49,"line":138},[47,3067,1177],{"class":60},[47,3069,3070],{"class":64}," [",[47,3072,3073],{"class":169},"ui",[47,3075,660],{"class":64},[47,3077,3078],{"class":169},"setUI",[47,3080,1572],{"class":64},[47,3082,1442],{"class":60},[47,3084,3085],{"class":203}," useState",[47,3087,1103],{"class":64},[47,3089,3090],{"class":203},"React",[47,3092,1999],{"class":64},[47,3094,2002],{"class":203},[47,3096,2178],{"class":64},[47,3098,2181],{"class":169},[47,3100,3101],{"class":64},")\n",[47,3103,3104,3106],{"class":49,"line":153},[47,3105,887],{"class":60},[47,3107,1539],{"class":64},[47,3109,3110,3112,3115,3118,3120,3122,3124,3126,3129,3131,3133],{"class":49,"line":160},[47,3111,1544],{"class":64},[47,3113,3114],{"class":1547},"form",[47,3116,3117],{"class":203}," action",[47,3119,1442],{"class":60},[47,3121,1558],{"class":64},[47,3123,954],{"class":60},[47,3125,960],{"class":64},[47,3127,3128],{"class":873},"formData",[47,3130,917],{"class":64},[47,3132,920],{"class":60},[47,3134,176],{"class":64},[47,3136,3137,3140,3142,3145,3147,3150,3153,3155,3158,3160,3163,3165],{"class":49,"line":179},[47,3138,3139],{"class":203},"      setUI",[47,3141,225],{"class":64},[47,3143,3144],{"class":60},"await",[47,3146,2830],{"class":203},[47,3148,3149],{"class":64},"(formData.",[47,3151,3152],{"class":203},"get",[47,3154,225],{"class":64},[47,3156,3157],{"class":71},"'q'",[47,3159,917],{"class":64},[47,3161,3162],{"class":60},"as",[47,3164,1170],{"class":169},[47,3166,3167],{"class":64},"))\n",[47,3169,3170],{"class":49,"line":185},[47,3171,3172],{"class":64},"    }}>\n",[47,3174,3175,3177,3180,3182,3184,3187],{"class":49,"line":197},[47,3176,1662],{"class":64},[47,3178,3179],{"class":1547},"input",[47,3181,985],{"class":203},[47,3183,1442],{"class":60},[47,3185,3186],{"class":71},"\"q\"",[47,3188,1674],{"class":64},[47,3190,3191,3193,3195,3198,3200],{"class":49,"line":210},[47,3192,1662],{"class":64},[47,3194,1947],{"class":1547},[47,3196,3197],{"class":64},">生成\u003C\u002F",[47,3199,1947],{"class":1547},[47,3201,1657],{"class":64},[47,3203,3204,3206,3208,3211,3213],{"class":49,"line":234},[47,3205,1662],{"class":64},[47,3207,1646],{"class":1547},[47,3209,3210],{"class":64},">{ui}\u003C\u002F",[47,3212,1646],{"class":1547},[47,3214,1657],{"class":64},[47,3216,3217,3219,3221],{"class":49,"line":253},[47,3218,1709],{"class":64},[47,3220,3114],{"class":1547},[47,3222,1657],{"class":64},[47,3224,3225],{"class":49,"line":273},[47,3226,3227],{"class":64},"  )\n",[47,3229,3230],{"class":49,"line":292},[47,3231,1138],{"class":64},[16,3233,3234],{},"等功能有付费用户或目录中有三个或以上工具时，再升级到完整模式。",[11,3236,3237],{"id":3237},"常见错误",[16,3239,3240,3243],{},[748,3241,3242],{},"工具太多。"," 如果你给 AI 50 个组件可以选择，它会做出糟糕的选择。我见过团队从 20+ 个工具开始，然后发现 AI 持续选错。从 5–8 个定义良好的工具开始，只在数据显示有未匹配查询时才扩展。",[16,3245,3246,3249],{},[748,3247,3248],{},"描述模糊。"," \"展示数据\"不是有用的工具描述。\"当展示具有多个属性的项目列表时，用可排序列的表格展示表格数据\"才能告诉 AI 何时使用它。",[16,3251,3252,3255],{},[748,3253,3254],{},"没有降级。"," 当 AI 模型宕机或返回错误时，用户看不到任何东西。始终为关键路径提供静态降级 UI。如果你在为数据仪表板使用 Generative UI，要有一个当 AI 不可用时加载的默认静态视图。",[16,3257,3258,3261],{},[748,3259,3260],{},"跳过 Zod 校验。"," AI 偶尔会传入意外的 props——在需要数字的地方传字符串，在需要值的地方传 null。严格的 Zod 校验会在这些到达你的组件之前捕获它们。",[16,3263,3264,3267],{},[748,3265,3266],{},"过度生成。"," 并非每次交互都需要 Generative UI。如果静态组件能用，就用静态的。GenUI 增加 200–800ms 的延迟，而且要花钱。把它用在变化真正有价值的交互上。",[16,3269,3270,3273],{},[748,3271,3272],{},"不记录工具调用。"," 没有记录 AI 选择了哪些工具以及传入了什么参数，你就没有改进的数据。从第一天起就记录所有内容。一周使用后看到的模式会改变你写工具描述的方式。",[11,3275,3276],{"id":3276},"生产清单",[16,3278,3279],{},"在将 Generative UI 发布到生产之前：",[3281,3282,3283,3286,3289,3292,3295,3298,3301,3304,3307,3310],"ul",{},[2614,3284,3285],{},"所有生成的组件都包裹在错误边界中",[2614,3287,3288],{},"每个工具都有骨架加载状态",[2614,3290,3291],{},"AI 不可用或返回错误时的静态降级",[2614,3293,3294],{},"所有工具参数都有严格的 Zod 校验",[2614,3296,3297],{},"工具调用日志已就位（工具名称、参数、延迟）",[2614,3299,3300],{},"延迟监控（如果到第一个组件超过 2 秒则告警）",[2614,3302,3303],{},"每次 AI 推理的成本追踪",[2614,3305,3306],{},"所有生成组件组合的无障碍审计",[2614,3308,3309],{},"生成布局的移动端响应式测试",[2614,3311,3312],{},"Server Action 上的速率限制",[11,3314,3315],{"id":3315},"关于测试的说明",[16,3317,3318],{},"测试 Generative UI 需要与传统 UI 测试不同的方法。简短版本：",[3281,3320,3321,3324,3327,3330],{},[2614,3322,3323],{},"用标准单元测试独立测试你的组件——它们就是普通的 React 组件",[2614,3325,3326],{},"分别测试你的 Zod schema，确保它们接受有效输入并拒绝无效输入",[2614,3328,3329],{},"对于针对 AI 的集成测试，测试结构属性（调用了正确的工具，参数有效），而不是精确内容（温度是 22°）",[2614,3331,3332],{},"在 CI 中 mock AI，每晚运行真实的 AI 集成测试",[16,3334,3335,3336,2634],{},"这个话题值得单独成文，我们已经写了一篇：",[779,3337,3339],{"href":3338},"\u002Flearn\u002Ftesting-generative-ui-applications","测试 Generative UI 应用",[11,3341,3342],{"id":3342},"考虑过的替代方案",[16,3344,3345],{},"上述模式假设使用带有 React Server Components 的 Vercel AI SDK。有两个值得了解的替代方案，在你做出承诺之前：",[3281,3347,3348,3362,3380],{},[2614,3349,3350,3353,3354,3361],{},[748,3351,3352],{},"Tambo \u002F 组件目录运行时。"," 一个用于 React 上 AI 生成 UI 的开源框架（",[779,3355,3358],{"href":3356,"rel":3357},"https:\u002F\u002Fgithub.com\u002Ftambo-ai\u002Ftambo",[783],[44,3359,3360],{},"github.com\u002Ftambo-ai\u002Ftambo","，截至 2026-05 约 11k stars）发布更快（无需编写注册表代码）并集中管理描述质量。当快速出第一个演示比长期单位成本更重要时使用它。",[2614,3363,3364,3367,3368,3373,3374,3379],{},[748,3365,3366],{},"声明式 JSON 协议","，如 ",[779,3369,3372],{"href":3370,"rel":3371},"https:\u002F\u002Fdocs.thesys.dev\u002F",[783],"Thesys C1","（封闭 API）或 ",[779,3375,3378],{"href":3376,"rel":3377},"https:\u002F\u002Fa2ui.org\u002Fspecification\u002Fv0.9-a2ui\u002F",[783],"A2UI v0.9","（Google 的开放规范，2025 年 11 月），将模型与 React 完全解耦；任何客户端（web、移动、语音）都可以渲染相同的载荷。当你有非 web 界面时使用这些，代价是需要构建自己的渲染器。",[2614,3381,3382,3385],{},[748,3383,3384],{},"纯 JSON + 手写分发器。"," 完全不用 SDK。你写一个 switch 语句处理工具名称。小规模时成本最低，超过五个工具后最难维护。",[16,3387,3388],{},"选择轴是护城河与可移植性 vs. 上线速度。对于大多数只有 React 的产品，本文中的 SDK 路径胜出；对于多界面或供应商中立的产品，评估 A2UI。",[11,3390,3391],{"id":3391},"延伸阅读",[3281,3393,3394,3401,3406,3413,3421],{},[2614,3395,3396,3400],{},[779,3397,3399],{"href":3398},"\u002Flearn\u002Fwhat-is-generative-ui","什么是 Generative UI？"," — 概念入门",[2614,3402,3403,3405],{},[779,3404,3339],{"href":3338}," — 测试策略的配套文章",[2614,3407,3408,3412],{},[779,3409,3411],{"href":781,"rel":3410},[783],"Vercel AI SDK：streamUI 文档"," — 官方参考",[2614,3414,3415,3420],{},[779,3416,3419],{"href":3417,"rel":3418},"https:\u002F\u002Fzod.dev",[783],"Zod 文档"," — 校验层",[2614,3422,3423,3426],{},[779,3424,2060],{"href":2056,"rel":3425},[783]," — 用于模式四",[3428,3429],"hr",{},[16,3431,3432],{},[757,3433,3434,3435,3439],{},"正在进行 React Generative UI 实现？",[779,3436,3438],{"href":3437},"\u002Fservices","获取专家指导","，了解架构、性能和生产就绪方面的建议。",[3441,3442,3443],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":42,"searchDepth":57,"depth":57,"links":3445},[3446,3447,3448,3449,3450,3451,3452,3453,3454,3455,3456,3457,3458,3459,3460,3461],{"id":13,"depth":57,"text":14},{"id":21,"depth":57,"text":22},{"id":31,"depth":57,"text":32},{"id":792,"depth":57,"text":793},{"id":1381,"depth":57,"text":1382},{"id":1732,"depth":57,"text":1733},{"id":2074,"depth":57,"text":2075},{"id":2424,"depth":57,"text":2424},{"id":2527,"depth":57,"text":2528},{"id":2599,"depth":57,"text":2599},{"id":2605,"depth":57,"text":2606},{"id":3237,"depth":57,"text":3237},{"id":3276,"depth":57,"text":3276},{"id":3315,"depth":57,"text":3315},{"id":3342,"depth":57,"text":3342},{"id":3391,"depth":57,"text":3391},"tutorial","2026-02-20","学习如何在 React 应用中实现 AI 生成的 UI 组件，包含真实场景中的实用开发模式。",false,"md",{"audit_status":3468,"audit_date":3469},"ship-with-revisions-applied-v2","2026-05-11","\u002Fzh\u002Flearn\u002Fgenerative-ui-react-practical-guide","12 分钟阅读",{"title":5,"description":3464},"zh\u002Flearn\u002Fgenerative-ui-react-practical-guide",[3475,3476,3477,3478],"react","generative-ui","patterns","implementation","_gbYXIopJqEMADPbq9rdQ-hH_lmXOXeM--wJWCn0tSs",[3481,6496,7839],{"id":3482,"title":3483,"author":6,"body":3484,"category":3462,"date":6484,"description":6485,"draft":3465,"extension":3466,"featured":3465,"meta":6486,"navigation":156,"path":6488,"readTime":6489,"seo":6490,"stem":6491,"tags":6492,"__hash__":6495},"content\u002Fel\u002Flearn\u002Fbuilding-generative-ui-vercel-ai-sdk.md","Κατασκευάζοντας το Πρώτο σας Generative UI με το Vercel AI SDK",{"type":8,"value":3485,"toc":6470},[3486,3490,3493,3507,3513,3517,3520,3531,3534,3565,3569,3593,3604,3620,3626,3640,3644,3647,3906,4307,4387,4391,4394,4947,4950,4968,4977,4983,4987,5896,5900,5915,5918,5948,5951,5955,5958,5986,5989,5993,5996,6313,6316,6320,6329,6342,6355,6374,6380,6384,6387,6419,6422,6424,6428,6431,6456,6458,6467],[11,3487,3489],{"id":3488},"προαπαιτούμενα","Προαπαιτούμενα",[16,3491,3492],{},"Πριν ξεκινήσουμε, βεβαιωθείτε ότι έχετε:",[3281,3494,3495,3498,3501,3504],{},[2614,3496,3497],{},"Node.js 18+ εγκατεστημένο",[2614,3499,3500],{},"Ένα έργο Next.js 14+ που χρησιμοποιεί το App Router",[2614,3502,3503],{},"Ένα κλειδί API OpenAI (ή Anthropic — το SDK υποστηρίζει και τα δύο)",[2614,3505,3506],{},"Βασική εξοικείωση με τα React Server Components",[16,3508,3509,3510,3512],{},"Αν είστε νέος στο RSC, αφιερώστε 15 λεπτά στα έγγραφα του Next.js για τα Server Components πρώτα. Η συνάρτηση ",[44,3511,787],{}," του Vercel AI SDK εξαρτάται από το RSC και γίνεται πολύ πιο κατανοητή μόλις κατανοήσετε το μοντέλο.",[11,3514,3516],{"id":3515},"τι-κατασκευάζουμε","Τι Κατασκευάζουμε",[16,3518,3519],{},"Θα κατασκευάσουμε έναν απλό AI-powered βοηθό που παράγει διαδραστική διεπαφή βάσει prompt χρήστη. Στο τέλος αυτού του οδηγού, θα έχετε μια λειτουργική δυνατότητα Generative UI που:",[2611,3521,3522,3525,3528],{},[2614,3523,3524],{},"Λαμβάνει ένα prompt κειμένου από τον χρήστη",[2614,3526,3527],{},"Μεταδίδει με streaming συστατικά React πίσω από τον server",[2614,3529,3530],{},"Αποδίδει διαδραστικές κάρτες και γραφήματα βάσει των αποφάσεων του AI",[16,3532,3533],{},"Το παράδειγμα αφορά έναν χρηματοοικονομικό βοηθό που μπορεί να εμφανίζει τιμές μετοχών και δεδομένα καιρού — αρκετά απλό για γρήγορη κατανόηση, αρκετά σύνθετο για να δείξει πραγματικά μοτίβα.",[3535,3536,3537],"blockquote",{},[16,3538,3539,3540,3546,3547,3550,3551,3554,3555,3559,3560,1999],{},"⚠️ ",[748,3541,3542,3543,3545],{},"Το AI SDK RSC και το ",[44,3544,787],{}," είναι επισημασμένα από τη Vercel ως experimental."," Για production projects η Vercel συνιστά το AI SDK UI (",[44,3548,3549],{},"useChat"," από ",[44,3552,3553],{},"@ai-sdk\u002Freact","). Αυτό το άρθρο δείχνει ένα λειτουργικό μοτίβο RSC streaming για πρωτότυπα, demos και ελεγχόμενα περιβάλλοντα· για production αξιολόγησε τους trade-offs και δες την ενότητα ",[779,3556,3558],{"href":3557},"#%CF%80%CF%8C%CF%84%CE%B5-vercel-ai-sdk-%CE%B4%CE%B5%CE%BD-%CE%B5%CE%AF%CE%BD%CE%B1%CE%B9-%CE%B7-%CE%BA%CE%B1%CF%84%CE%AC%CE%BB%CE%BB%CE%B7%CE%BB%CE%B7-%CE%B5%CF%80%CE%B9%CE%BB%CE%BF%CE%B3%CE%AE","«Πότε το Vercel AI SDK ΔΕΝ είναι η κατάλληλη επιλογή»",". ",[779,3561,3564],{"href":3562,"rel":3563},"https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Fai-sdk-rsc\u002Fmigrating-to-ui",[783],"Migration guide RSC → UI",[11,3566,3568],{"id":3567},"βήμα-1-εγκατάσταση-εξαρτήσεων","Βήμα 1: Εγκατάσταση Εξαρτήσεων",[37,3570,3574],{"className":3571,"code":3572,"language":3573,"meta":42,"style":42},"language-bash shiki shiki-themes github-light github-dark","npm install ai@^4 @ai-sdk\u002Fopenai@^1 zod\n","bash",[44,3575,3576],{"__ignoreMap":42},[47,3577,3578,3581,3584,3587,3590],{"class":49,"line":50},[47,3579,3580],{"class":203},"npm",[47,3582,3583],{"class":71}," install",[47,3585,3586],{"class":71}," ai@^4",[47,3588,3589],{"class":71}," @ai-sdk\u002Fopenai@^1",[47,3591,3592],{"class":71}," zod\n",[16,3594,3595,3596,3599,3600,3603],{},"Pin v4 — η τελευταία σειρά με RSC API στη μορφή ",[44,3597,3598],{},"parameters:"," και import από ",[44,3601,3602],{},"ai\u002Frsc",". Για v5+ δες τη σημείωση στο τέλος του άρθρου.",[16,3605,3606,3607,3609,3610,3612,3613,3616,3617,3619],{},"Το πακέτο ",[44,3608,2623],{}," είναι ο πυρήνας του Vercel AI SDK. Το ",[44,3611,2627],{}," είναι ο πάροχος OpenAI (αντικατέστησε με ",[44,3614,3615],{},"@ai-sdk\u002Fanthropic"," αν προτιμάς Claude). Το ",[44,3618,2630],{}," χειρίζεται την επικύρωση παραμέτρων εργαλείων — έτσι ορίζεις ποιες παραμέτρους μπορεί να περνά το AI σε κάθε συστατικό.",[16,3621,3622,3623,877],{},"Προσθέστε το κλειδί API σας στο ",[44,3624,3625],{},".env.local",[37,3627,3629],{"className":3571,"code":3628,"language":3573,"meta":42,"style":42},"OPENAI_API_KEY=sk-...\n",[44,3630,3631],{"__ignoreMap":42},[47,3632,3633,3635,3637],{"class":49,"line":50},[47,3634,2652],{"class":64},[47,3636,1442],{"class":60},[47,3638,3639],{"class":71},"sk-...\n",[11,3641,3643],{"id":3642},"βήμα-2-δημιουργία-βιβλιοθήκης-συστατικών","Βήμα 2: Δημιουργία Βιβλιοθήκης Συστατικών",[16,3645,3646],{},"Ορίστε τα συστατικά που μπορεί να παράγει το AI. Αυτά είναι κανονικά συστατικά React — δεν έχουν τίποτα ειδικό για AI. Η βασική αρχή σχεδιασμού: κατασκευάστε συστατικά που είναι χρήσιμα αυτόνομα, και θα μπορούν να τα συνθέτει το AI.",[37,3648,3650],{"className":1388,"code":3649,"language":1390,"meta":42,"style":42},"\u002F\u002F components\u002Fweather-card.tsx\ninterface WeatherCardProps {\n  city: string;\n  temperature: number;\n  conditions: string;\n  humidity: number;\n}\n\nexport function WeatherCard({ city, temperature, conditions, humidity }: WeatherCardProps) {\n  return (\n    \u003Cdiv className=\"rounded-lg border bg-card p-6 shadow-sm\">\n      \u003Ch3 className=\"text-lg font-semibold\">{city}\u003C\u002Fh3>\n      \u003Cdiv className=\"mt-2 flex items-baseline gap-2\">\n        \u003Cspan className=\"text-4xl font-bold\">{temperature}°C\u003C\u002Fspan>\n        \u003Cspan className=\"text-muted-foreground\">{conditions}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp className=\"mt-2 text-sm text-muted-foreground\">\n        Humidity: {humidity}%\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n",[44,3651,3652,3657,3667,3678,3690,3701,3712,3716,3720,3757,3763,3778,3799,3814,3834,3854,3862,3877,3882,3890,3898,3902],{"__ignoreMap":42},[47,3653,3654],{"class":49,"line":50},[47,3655,3656],{"class":53},"\u002F\u002F components\u002Fweather-card.tsx\n",[47,3658,3659,3662,3665],{"class":49,"line":57},[47,3660,3661],{"class":60},"interface",[47,3663,3664],{"class":203}," WeatherCardProps",[47,3666,176],{"class":64},[47,3668,3669,3672,3674,3676],{"class":49,"line":78},[47,3670,3671],{"class":873},"  city",[47,3673,877],{"class":60},[47,3675,1170],{"class":169},[47,3677,75],{"class":64},[47,3679,3680,3683,3685,3688],{"class":49,"line":93},[47,3681,3682],{"class":873},"  temperature",[47,3684,877],{"class":60},[47,3686,3687],{"class":169}," number",[47,3689,75],{"class":64},[47,3691,3692,3695,3697,3699],{"class":49,"line":108},[47,3693,3694],{"class":873},"  conditions",[47,3696,877],{"class":60},[47,3698,1170],{"class":169},[47,3700,75],{"class":64},[47,3702,3703,3706,3708,3710],{"class":49,"line":123},[47,3704,3705],{"class":873},"  humidity",[47,3707,877],{"class":60},[47,3709,3687],{"class":169},[47,3711,75],{"class":64},[47,3713,3714],{"class":49,"line":138},[47,3715,1138],{"class":64},[47,3717,3718],{"class":49,"line":153},[47,3719,157],{"emptyLinePlaceholder":156},[47,3721,3722,3724,3726,3729,3731,3734,3736,3739,3741,3744,3746,3749,3751,3753,3755],{"class":49,"line":160},[47,3723,163],{"class":60},[47,3725,1157],{"class":60},[47,3727,3728],{"class":203}," WeatherCard",[47,3730,1513],{"class":64},[47,3732,3733],{"class":873},"city",[47,3735,660],{"class":64},[47,3737,3738],{"class":873},"temperature",[47,3740,660],{"class":64},[47,3742,3743],{"class":873},"conditions",[47,3745,660],{"class":64},[47,3747,3748],{"class":873},"humidity",[47,3750,1518],{"class":64},[47,3752,877],{"class":60},[47,3754,3664],{"class":203},[47,3756,971],{"class":64},[47,3758,3759,3761],{"class":49,"line":179},[47,3760,887],{"class":60},[47,3762,1539],{"class":64},[47,3764,3765,3767,3769,3771,3773,3776],{"class":49,"line":185},[47,3766,1544],{"class":64},[47,3768,1646],{"class":1547},[47,3770,1649],{"class":203},[47,3772,1442],{"class":60},[47,3774,3775],{"class":71},"\"rounded-lg border bg-card p-6 shadow-sm\"",[47,3777,1657],{"class":64},[47,3779,3780,3782,3785,3787,3789,3792,3795,3797],{"class":49,"line":197},[47,3781,1662],{"class":64},[47,3783,3784],{"class":1547},"h3",[47,3786,1649],{"class":203},[47,3788,1442],{"class":60},[47,3790,3791],{"class":71},"\"text-lg font-semibold\"",[47,3793,3794],{"class":64},">{city}\u003C\u002F",[47,3796,3784],{"class":1547},[47,3798,1657],{"class":64},[47,3800,3801,3803,3805,3807,3809,3812],{"class":49,"line":210},[47,3802,1662],{"class":64},[47,3804,1646],{"class":1547},[47,3806,1649],{"class":203},[47,3808,1442],{"class":60},[47,3810,3811],{"class":71},"\"mt-2 flex items-baseline gap-2\"",[47,3813,1657],{"class":64},[47,3815,3816,3818,3820,3822,3824,3827,3830,3832],{"class":49,"line":234},[47,3817,2338],{"class":64},[47,3819,47],{"class":1547},[47,3821,1649],{"class":203},[47,3823,1442],{"class":60},[47,3825,3826],{"class":71},"\"text-4xl font-bold\"",[47,3828,3829],{"class":64},">{temperature}°C\u003C\u002F",[47,3831,47],{"class":1547},[47,3833,1657],{"class":64},[47,3835,3836,3838,3840,3842,3844,3847,3850,3852],{"class":49,"line":253},[47,3837,2338],{"class":64},[47,3839,47],{"class":1547},[47,3841,1649],{"class":203},[47,3843,1442],{"class":60},[47,3845,3846],{"class":71},"\"text-muted-foreground\"",[47,3848,3849],{"class":64},">{conditions}\u003C\u002F",[47,3851,47],{"class":1547},[47,3853,1657],{"class":64},[47,3855,3856,3858,3860],{"class":49,"line":273},[47,3857,1879],{"class":64},[47,3859,1646],{"class":1547},[47,3861,1657],{"class":64},[47,3863,3864,3866,3868,3870,3872,3875],{"class":49,"line":292},[47,3865,1662],{"class":64},[47,3867,16],{"class":1547},[47,3869,1649],{"class":203},[47,3871,1442],{"class":60},[47,3873,3874],{"class":71},"\"mt-2 text-sm text-muted-foreground\"",[47,3876,1657],{"class":64},[47,3878,3879],{"class":49,"line":298},[47,3880,3881],{"class":64},"        Humidity: {humidity}%\n",[47,3883,3884,3886,3888],{"class":49,"line":304},[47,3885,1879],{"class":64},[47,3887,16],{"class":1547},[47,3889,1657],{"class":64},[47,3891,3892,3894,3896],{"class":49,"line":310},[47,3893,1709],{"class":64},[47,3895,1646],{"class":1547},[47,3897,1657],{"class":64},[47,3899,3900],{"class":49,"line":316},[47,3901,1133],{"class":64},[47,3903,3904],{"class":49,"line":326},[47,3905,1138],{"class":64},[37,3907,3909],{"className":1388,"code":3908,"language":1390,"meta":42,"style":42},"\u002F\u002F components\u002Fstock-ticker.tsx\ninterface StockTickerProps {\n  symbol: string;\n  price: number;\n  change: number;\n  changePercent: number;\n}\n\nexport function StockTicker({ symbol, price, change, changePercent }: StockTickerProps) {\n  const isPositive = change >= 0;\n  const sign = isPositive ? '+' : '';\n  const color = isPositive ? 'text-green-600' : 'text-red-600';\n\n  return (\n    \u003Cdiv className=\"rounded-lg border bg-card p-6 shadow-sm\">\n      \u003Cdiv className=\"flex items-center justify-between\">\n        \u003Ch3 className=\"text-xl font-bold\">{symbol}\u003C\u002Fh3>\n        \u003Cspan className={`text-sm font-medium ${color}`}>\n          {sign}{changePercent.toFixed(2)}%\n        \u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv className=\"mt-2 flex items-baseline gap-2\">\n        \u003Cspan className=\"text-3xl font-bold\">${price.toFixed(2)}\u003C\u002Fspan>\n        \u003Cspan className={`text-sm ${color}`}>\n          {sign}{change.toFixed(2)} today\n        \u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n",[44,3910,3911,3916,3925,3936,3947,3958,3969,3973,3977,4014,4034,4060,4083,4087,4093,4107,4122,4142,4165,4181,4189,4197,4211,4240,4261,4275,4283,4291,4299,4303],{"__ignoreMap":42},[47,3912,3913],{"class":49,"line":50},[47,3914,3915],{"class":53},"\u002F\u002F components\u002Fstock-ticker.tsx\n",[47,3917,3918,3920,3923],{"class":49,"line":57},[47,3919,3661],{"class":60},[47,3921,3922],{"class":203}," StockTickerProps",[47,3924,176],{"class":64},[47,3926,3927,3930,3932,3934],{"class":49,"line":78},[47,3928,3929],{"class":873},"  symbol",[47,3931,877],{"class":60},[47,3933,1170],{"class":169},[47,3935,75],{"class":64},[47,3937,3938,3941,3943,3945],{"class":49,"line":93},[47,3939,3940],{"class":873},"  price",[47,3942,877],{"class":60},[47,3944,3687],{"class":169},[47,3946,75],{"class":64},[47,3948,3949,3952,3954,3956],{"class":49,"line":108},[47,3950,3951],{"class":873},"  change",[47,3953,877],{"class":60},[47,3955,3687],{"class":169},[47,3957,75],{"class":64},[47,3959,3960,3963,3965,3967],{"class":49,"line":123},[47,3961,3962],{"class":873},"  changePercent",[47,3964,877],{"class":60},[47,3966,3687],{"class":169},[47,3968,75],{"class":64},[47,3970,3971],{"class":49,"line":138},[47,3972,1138],{"class":64},[47,3974,3975],{"class":49,"line":153},[47,3976,157],{"emptyLinePlaceholder":156},[47,3978,3979,3981,3983,3986,3988,3991,3993,3996,3998,4001,4003,4006,4008,4010,4012],{"class":49,"line":160},[47,3980,163],{"class":60},[47,3982,1157],{"class":60},[47,3984,3985],{"class":203}," StockTicker",[47,3987,1513],{"class":64},[47,3989,3990],{"class":873},"symbol",[47,3992,660],{"class":64},[47,3994,3995],{"class":873},"price",[47,3997,660],{"class":64},[47,3999,4000],{"class":873},"change",[47,4002,660],{"class":64},[47,4004,4005],{"class":873},"changePercent",[47,4007,1518],{"class":64},[47,4009,877],{"class":60},[47,4011,3922],{"class":203},[47,4013,971],{"class":64},[47,4015,4016,4018,4021,4023,4026,4029,4032],{"class":49,"line":179},[47,4017,1177],{"class":60},[47,4019,4020],{"class":169}," isPositive",[47,4022,173],{"class":60},[47,4024,4025],{"class":64}," change ",[47,4027,4028],{"class":60},">=",[47,4030,4031],{"class":169}," 0",[47,4033,75],{"class":64},[47,4035,4036,4038,4041,4043,4046,4049,4052,4055,4058],{"class":49,"line":185},[47,4037,1177],{"class":60},[47,4039,4040],{"class":169}," sign",[47,4042,173],{"class":60},[47,4044,4045],{"class":64}," isPositive ",[47,4047,4048],{"class":60},"?",[47,4050,4051],{"class":71}," '+'",[47,4053,4054],{"class":60}," :",[47,4056,4057],{"class":71}," ''",[47,4059,75],{"class":64},[47,4061,4062,4064,4067,4069,4071,4073,4076,4078,4081],{"class":49,"line":197},[47,4063,1177],{"class":60},[47,4065,4066],{"class":169}," color",[47,4068,173],{"class":60},[47,4070,4045],{"class":64},[47,4072,4048],{"class":60},[47,4074,4075],{"class":71}," 'text-green-600'",[47,4077,4054],{"class":60},[47,4079,4080],{"class":71}," 'text-red-600'",[47,4082,75],{"class":64},[47,4084,4085],{"class":49,"line":210},[47,4086,157],{"emptyLinePlaceholder":156},[47,4088,4089,4091],{"class":49,"line":234},[47,4090,887],{"class":60},[47,4092,1539],{"class":64},[47,4094,4095,4097,4099,4101,4103,4105],{"class":49,"line":253},[47,4096,1544],{"class":64},[47,4098,1646],{"class":1547},[47,4100,1649],{"class":203},[47,4102,1442],{"class":60},[47,4104,3775],{"class":71},[47,4106,1657],{"class":64},[47,4108,4109,4111,4113,4115,4117,4120],{"class":49,"line":273},[47,4110,1662],{"class":64},[47,4112,1646],{"class":1547},[47,4114,1649],{"class":203},[47,4116,1442],{"class":60},[47,4118,4119],{"class":71},"\"flex items-center justify-between\"",[47,4121,1657],{"class":64},[47,4123,4124,4126,4128,4130,4132,4135,4138,4140],{"class":49,"line":292},[47,4125,2338],{"class":64},[47,4127,3784],{"class":1547},[47,4129,1649],{"class":203},[47,4131,1442],{"class":60},[47,4133,4134],{"class":71},"\"text-xl font-bold\"",[47,4136,4137],{"class":64},">{symbol}\u003C\u002F",[47,4139,3784],{"class":1547},[47,4141,1657],{"class":64},[47,4143,4144,4146,4148,4150,4152,4154,4157,4160,4162],{"class":49,"line":298},[47,4145,2338],{"class":64},[47,4147,47],{"class":1547},[47,4149,1649],{"class":203},[47,4151,1442],{"class":60},[47,4153,1558],{"class":64},[47,4155,4156],{"class":71},"`text-sm font-medium ${",[47,4158,4159],{"class":64},"color",[47,4161,1055],{"class":71},[47,4163,4164],{"class":64},"}>\n",[47,4166,4167,4170,4173,4175,4178],{"class":49,"line":304},[47,4168,4169],{"class":64},"          {sign}{changePercent.",[47,4171,4172],{"class":203},"toFixed",[47,4174,225],{"class":64},[47,4176,4177],{"class":169},"2",[47,4179,4180],{"class":64},")}%\n",[47,4182,4183,4185,4187],{"class":49,"line":310},[47,4184,2388],{"class":64},[47,4186,47],{"class":1547},[47,4188,1657],{"class":64},[47,4190,4191,4193,4195],{"class":49,"line":316},[47,4192,1879],{"class":64},[47,4194,1646],{"class":1547},[47,4196,1657],{"class":64},[47,4198,4199,4201,4203,4205,4207,4209],{"class":49,"line":326},[47,4200,1662],{"class":64},[47,4202,1646],{"class":1547},[47,4204,1649],{"class":203},[47,4206,1442],{"class":60},[47,4208,3811],{"class":71},[47,4210,1657],{"class":64},[47,4212,4213,4215,4217,4219,4221,4224,4227,4229,4231,4233,4236,4238],{"class":49,"line":335},[47,4214,2338],{"class":64},[47,4216,47],{"class":1547},[47,4218,1649],{"class":203},[47,4220,1442],{"class":60},[47,4222,4223],{"class":71},"\"text-3xl font-bold\"",[47,4225,4226],{"class":64},">${price.",[47,4228,4172],{"class":203},[47,4230,225],{"class":64},[47,4232,4177],{"class":169},[47,4234,4235],{"class":64},")}\u003C\u002F",[47,4237,47],{"class":1547},[47,4239,1657],{"class":64},[47,4241,4242,4244,4246,4248,4250,4252,4255,4257,4259],{"class":49,"line":351},[47,4243,2338],{"class":64},[47,4245,47],{"class":1547},[47,4247,1649],{"class":203},[47,4249,1442],{"class":60},[47,4251,1558],{"class":64},[47,4253,4254],{"class":71},"`text-sm ${",[47,4256,4159],{"class":64},[47,4258,1055],{"class":71},[47,4260,4164],{"class":64},[47,4262,4263,4266,4268,4270,4272],{"class":49,"line":362},[47,4264,4265],{"class":64},"          {sign}{change.",[47,4267,4172],{"class":203},[47,4269,225],{"class":64},[47,4271,4177],{"class":169},[47,4273,4274],{"class":64},")} today\n",[47,4276,4277,4279,4281],{"class":49,"line":372},[47,4278,2388],{"class":64},[47,4280,47],{"class":1547},[47,4282,1657],{"class":64},[47,4284,4285,4287,4289],{"class":49,"line":388},[47,4286,1879],{"class":64},[47,4288,1646],{"class":1547},[47,4290,1657],{"class":64},[47,4292,4293,4295,4297],{"class":49,"line":394},[47,4294,1709],{"class":64},[47,4296,1646],{"class":1547},[47,4298,1657],{"class":64},[47,4300,4301],{"class":49,"line":414},[47,4302,1133],{"class":64},[47,4304,4305],{"class":49,"line":428},[47,4306,1138],{"class":64},[37,4308,4310],{"className":1388,"code":4309,"language":1390,"meta":42,"style":42},"\u002F\u002F components\u002Floading-skeleton.tsx\nexport function CardSkeleton({ height = 'h-32' }: { height?: string }) {\n  return (\n    \u003Cdiv className={`animate-pulse rounded-lg bg-muted ${height} w-full`} \u002F>\n  );\n}\n",[44,4311,4312,4317,4351,4357,4379,4383],{"__ignoreMap":42},[47,4313,4314],{"class":49,"line":50},[47,4315,4316],{"class":53},"\u002F\u002F components\u002Floading-skeleton.tsx\n",[47,4318,4319,4321,4323,4326,4328,4331,4333,4336,4338,4340,4342,4344,4347,4349],{"class":49,"line":57},[47,4320,163],{"class":60},[47,4322,1157],{"class":60},[47,4324,4325],{"class":203}," CardSkeleton",[47,4327,1513],{"class":64},[47,4329,4330],{"class":873},"height",[47,4332,173],{"class":60},[47,4334,4335],{"class":71}," 'h-32'",[47,4337,1518],{"class":64},[47,4339,877],{"class":60},[47,4341,1523],{"class":64},[47,4343,4330],{"class":873},[47,4345,4346],{"class":60},"?:",[47,4348,1170],{"class":169},[47,4350,1532],{"class":64},[47,4352,4353,4355],{"class":49,"line":78},[47,4354,887],{"class":60},[47,4356,1539],{"class":64},[47,4358,4359,4361,4363,4365,4367,4369,4371,4373,4376],{"class":49,"line":93},[47,4360,1544],{"class":64},[47,4362,1646],{"class":1547},[47,4364,1649],{"class":203},[47,4366,1442],{"class":60},[47,4368,1558],{"class":64},[47,4370,1561],{"class":71},[47,4372,4330],{"class":64},[47,4374,4375],{"class":71},"} w-full`",[47,4377,4378],{"class":64},"} \u002F>\n",[47,4380,4381],{"class":49,"line":108},[47,4382,1133],{"class":64},[47,4384,4385],{"class":49,"line":123},[47,4386,1138],{"class":64},[11,4388,4390],{"id":4389},"βήμα-3-ορισμός-εργαλείων-ai-server-action","Βήμα 3: Ορισμός Εργαλείων AI (Server Action)",[16,4392,4393],{},"Αυτός είναι ο πυρήνας του Generative UI. Δημιουργήστε ένα server action που συνδέει τα συστατικά σας με το AI ως «εργαλεία» — συναρτήσεις που το μοντέλο μπορεί να αποφασίσει να καλέσει:",[37,4395,4397],{"className":1388,"code":4396,"language":1390,"meta":42,"style":42},"\u002F\u002F app\u002Factions.tsx\n'use server';\n\nimport { streamUI } from 'ai\u002Frsc';\nimport { openai } from '@ai-sdk\u002Fopenai';\nimport { z } from 'zod';\nimport { WeatherCard } from '@\u002Fcomponents\u002Fweather-card';\nimport { StockTicker } from '@\u002Fcomponents\u002Fstock-ticker';\nimport { CardSkeleton } from '@\u002Fcomponents\u002Floading-skeleton';\n\nexport async function generateUI(prompt: string) {\n  const result = await streamUI({\n    model: openai('gpt-4o'),\n    system: `You are a helpful financial and information assistant.\n             Use the available tools to display information visually\n             whenever possible. Prefer showing components over text responses.\n             When asked about weather or stocks, always use the appropriate tool.`,\n    prompt,\n    tools: {\n      showWeather: {\n        description: 'Display current weather conditions for a city. Use this when the user asks about weather, temperature, or climate.',\n        parameters: z.object({\n          city: z.string().describe('The city name, e.g. \"Paris\" or \"New York\"'),\n          temperature: z.number().describe('Current temperature in Celsius'),\n          conditions: z.string().describe('Weather description, e.g. \"Partly cloudy\"'),\n          humidity: z.number().min(0).max(100).describe('Relative humidity percentage'),\n        }),\n        generate: async function* (params) {\n          \u002F\u002F Yield skeleton αμέσως ενώ τα δεδομένα «φορτώνουν»\n          yield \u003CCardSkeleton height=\"h-36\" \u002F>;\n          \u002F\u002F Σε πραγματική εφαρμογή, εδώ θα ανακτούσατε live δεδομένα καιρού\n          return \u003CWeatherCard {...params} \u002F>;\n        },\n      },\n      showStock: {\n        description: 'Display a stock price and daily change. Use this when the user asks about stock prices, market data, or a company\\'s shares.',\n        parameters: z.object({\n          symbol: z.string().describe('Stock ticker symbol, e.g. \"AAPL\" or \"TSLA\"'),\n          price: z.number().describe('Current stock price in USD'),\n          change: z.number().describe('Price change today in USD'),\n          changePercent: z.number().describe('Percentage price change today'),\n        }),\n        generate: async function* (params) {\n          yield \u003CCardSkeleton height=\"h-32\" \u002F>;\n          return \u003CStockTicker {...params} \u002F>;\n        },\n      },\n    },\n  });\n\n  return result.value;\n}\n",[44,4398,4399,4403,4410,4414,4426,4438,4450,4464,4478,4492,4496,4516,4530,4542,4549,4554,4559,4566,4570,4574,4579,4588,4597,4615,4633,4651,4690,4695,4711,4716,4736,4741,4757,4761,4765,4770,4785,4793,4811,4829,4847,4865,4869,4885,4902,4917,4921,4925,4929,4933,4937,4943],{"__ignoreMap":42},[47,4400,4401],{"class":49,"line":50},[47,4402,2675],{"class":53},[47,4404,4405,4408],{"class":49,"line":57},[47,4406,4407],{"class":71},"'use server'",[47,4409,75],{"class":64},[47,4411,4412],{"class":49,"line":78},[47,4413,157],{"emptyLinePlaceholder":156},[47,4415,4416,4418,4420,4422,4424],{"class":49,"line":93},[47,4417,61],{"class":60},[47,4419,816],{"class":64},[47,4421,68],{"class":60},[47,4423,821],{"class":71},[47,4425,75],{"class":64},[47,4427,4428,4430,4432,4434,4436],{"class":49,"line":108},[47,4429,61],{"class":60},[47,4431,830],{"class":64},[47,4433,68],{"class":60},[47,4435,835],{"class":71},[47,4437,75],{"class":64},[47,4439,4440,4442,4444,4446,4448],{"class":49,"line":123},[47,4441,61],{"class":60},[47,4443,65],{"class":64},[47,4445,68],{"class":60},[47,4447,72],{"class":71},[47,4449,75],{"class":64},[47,4451,4452,4454,4457,4459,4462],{"class":49,"line":138},[47,4453,61],{"class":60},[47,4455,4456],{"class":64}," { WeatherCard } ",[47,4458,68],{"class":60},[47,4460,4461],{"class":71}," '@\u002Fcomponents\u002Fweather-card'",[47,4463,75],{"class":64},[47,4465,4466,4468,4471,4473,4476],{"class":49,"line":153},[47,4467,61],{"class":60},[47,4469,4470],{"class":64}," { StockTicker } ",[47,4472,68],{"class":60},[47,4474,4475],{"class":71}," '@\u002Fcomponents\u002Fstock-ticker'",[47,4477,75],{"class":64},[47,4479,4480,4482,4485,4487,4490],{"class":49,"line":160},[47,4481,61],{"class":60},[47,4483,4484],{"class":64}," { CardSkeleton } ",[47,4486,68],{"class":60},[47,4488,4489],{"class":71}," '@\u002Fcomponents\u002Floading-skeleton'",[47,4491,75],{"class":64},[47,4493,4494],{"class":49,"line":179},[47,4495,157],{"emptyLinePlaceholder":156},[47,4497,4498,4500,4502,4504,4506,4508,4510,4512,4514],{"class":49,"line":185},[47,4499,163],{"class":60},[47,4501,1154],{"class":60},[47,4503,1157],{"class":60},[47,4505,2830],{"class":203},[47,4507,225],{"class":64},[47,4509,2835],{"class":873},[47,4511,877],{"class":60},[47,4513,1170],{"class":169},[47,4515,971],{"class":64},[47,4517,4518,4520,4522,4524,4526,4528],{"class":49,"line":197},[47,4519,1177],{"class":60},[47,4521,1180],{"class":169},[47,4523,173],{"class":60},[47,4525,1185],{"class":60},[47,4527,1188],{"class":203},[47,4529,207],{"class":64},[47,4531,4532,4534,4536,4538,4540],{"class":49,"line":210},[47,4533,1195],{"class":64},[47,4535,1198],{"class":203},[47,4537,225],{"class":64},[47,4539,1203],{"class":71},[47,4541,231],{"class":64},[47,4543,4544,4546],{"class":49,"line":234},[47,4545,1210],{"class":64},[47,4547,4548],{"class":71},"`You are a helpful financial and information assistant.\n",[47,4550,4551],{"class":49,"line":253},[47,4552,4553],{"class":71},"             Use the available tools to display information visually\n",[47,4555,4556],{"class":49,"line":273},[47,4557,4558],{"class":71},"             whenever possible. Prefer showing components over text responses.\n",[47,4560,4561,4564],{"class":49,"line":292},[47,4562,4563],{"class":71},"             When asked about weather or stocks, always use the appropriate tool.`",[47,4565,194],{"class":64},[47,4567,4568],{"class":49,"line":298},[47,4569,2873],{"class":64},[47,4571,4572],{"class":49,"line":304},[47,4573,2878],{"class":64},[47,4575,4576],{"class":49,"line":310},[47,4577,4578],{"class":64},"      showWeather: {\n",[47,4580,4581,4583,4586],{"class":49,"line":316},[47,4582,2888],{"class":64},[47,4584,4585],{"class":71},"'Display current weather conditions for a city. Use this when the user asks about weather, temperature, or climate.'",[47,4587,194],{"class":64},[47,4589,4590,4593,4595],{"class":49,"line":326},[47,4591,4592],{"class":64},"        parameters: z.",[47,4594,204],{"class":203},[47,4596,207],{"class":64},[47,4598,4599,4602,4604,4606,4608,4610,4613],{"class":49,"line":335},[47,4600,4601],{"class":64},"          city: z.",[47,4603,216],{"class":203},[47,4605,219],{"class":64},[47,4607,222],{"class":203},[47,4609,225],{"class":64},[47,4611,4612],{"class":71},"'The city name, e.g. \"Paris\" or \"New York\"'",[47,4614,231],{"class":64},[47,4616,4617,4620,4622,4624,4626,4628,4631],{"class":49,"line":351},[47,4618,4619],{"class":64},"          temperature: z.",[47,4621,259],{"class":203},[47,4623,219],{"class":64},[47,4625,222],{"class":203},[47,4627,225],{"class":64},[47,4629,4630],{"class":71},"'Current temperature in Celsius'",[47,4632,231],{"class":64},[47,4634,4635,4638,4640,4642,4644,4646,4649],{"class":49,"line":362},[47,4636,4637],{"class":64},"          conditions: z.",[47,4639,216],{"class":203},[47,4641,219],{"class":64},[47,4643,222],{"class":203},[47,4645,225],{"class":64},[47,4647,4648],{"class":71},"'Weather description, e.g. \"Partly cloudy\"'",[47,4650,231],{"class":64},[47,4652,4653,4656,4658,4660,4663,4665,4668,4671,4674,4676,4679,4681,4683,4685,4688],{"class":49,"line":372},[47,4654,4655],{"class":64},"          humidity: z.",[47,4657,259],{"class":203},[47,4659,219],{"class":64},[47,4661,4662],{"class":203},"min",[47,4664,225],{"class":64},[47,4666,4667],{"class":169},"0",[47,4669,4670],{"class":64},").",[47,4672,4673],{"class":203},"max",[47,4675,225],{"class":64},[47,4677,4678],{"class":169},"100",[47,4680,4670],{"class":64},[47,4682,222],{"class":203},[47,4684,225],{"class":64},[47,4686,4687],{"class":71},"'Relative humidity percentage'",[47,4689,231],{"class":64},[47,4691,4692],{"class":49,"line":388},[47,4693,4694],{"class":64},"        }),\n",[47,4696,4697,4699,4701,4703,4705,4707,4709],{"class":49,"line":394},[47,4698,948],{"class":203},[47,4700,951],{"class":64},[47,4702,954],{"class":60},[47,4704,957],{"class":60},[47,4706,960],{"class":64},[47,4708,963],{"class":873},[47,4710,971],{"class":64},[47,4712,4713],{"class":49,"line":414},[47,4714,4715],{"class":53},"          \u002F\u002F Yield skeleton αμέσως ενώ τα δεδομένα «φορτώνουν»\n",[47,4717,4718,4720,4722,4725,4728,4730,4733],{"class":49,"line":428},[47,4719,976],{"class":60},[47,4721,979],{"class":64},[47,4723,4724],{"class":169},"CardSkeleton",[47,4726,4727],{"class":203}," height",[47,4729,1442],{"class":60},[47,4731,4732],{"class":71},"\"h-36\"",[47,4734,4735],{"class":64}," \u002F>;\n",[47,4737,4738],{"class":49,"line":433},[47,4739,4740],{"class":53},"          \u002F\u002F Σε πραγματική εφαρμογή, εδώ θα ανακτούσατε live δεδομένα καιρού\n",[47,4742,4743,4745,4747,4750,4752,4754],{"class":49,"line":439},[47,4744,1079],{"class":60},[47,4746,979],{"class":64},[47,4748,4749],{"class":169},"WeatherCard",[47,4751,1087],{"class":64},[47,4753,1090],{"class":60},[47,4755,4756],{"class":64},"params} \u002F>;\n",[47,4758,4759],{"class":49,"line":444},[47,4760,1118],{"class":64},[47,4762,4763],{"class":49,"line":450},[47,4764,1123],{"class":64},[47,4766,4767],{"class":49,"line":460},[47,4768,4769],{"class":64},"      showStock: {\n",[47,4771,4772,4774,4777,4780,4783],{"class":49,"line":469},[47,4773,2888],{"class":64},[47,4775,4776],{"class":71},"'Display a stock price and daily change. Use this when the user asks about stock prices, market data, or a company",[47,4778,4779],{"class":169},"\\'",[47,4781,4782],{"class":71},"s shares.'",[47,4784,194],{"class":64},[47,4786,4787,4789,4791],{"class":49,"line":479},[47,4788,4592],{"class":64},[47,4790,204],{"class":203},[47,4792,207],{"class":64},[47,4794,4795,4798,4800,4802,4804,4806,4809],{"class":49,"line":504},[47,4796,4797],{"class":64},"          symbol: z.",[47,4799,216],{"class":203},[47,4801,219],{"class":64},[47,4803,222],{"class":203},[47,4805,225],{"class":64},[47,4807,4808],{"class":71},"'Stock ticker symbol, e.g. \"AAPL\" or \"TSLA\"'",[47,4810,231],{"class":64},[47,4812,4813,4816,4818,4820,4822,4824,4827],{"class":49,"line":518},[47,4814,4815],{"class":64},"          price: z.",[47,4817,259],{"class":203},[47,4819,219],{"class":64},[47,4821,222],{"class":203},[47,4823,225],{"class":64},[47,4825,4826],{"class":71},"'Current stock price in USD'",[47,4828,231],{"class":64},[47,4830,4831,4834,4836,4838,4840,4842,4845],{"class":49,"line":523},[47,4832,4833],{"class":64},"          change: z.",[47,4835,259],{"class":203},[47,4837,219],{"class":64},[47,4839,222],{"class":203},[47,4841,225],{"class":64},[47,4843,4844],{"class":71},"'Price change today in USD'",[47,4846,231],{"class":64},[47,4848,4849,4852,4854,4856,4858,4860,4863],{"class":49,"line":529},[47,4850,4851],{"class":64},"          changePercent: z.",[47,4853,259],{"class":203},[47,4855,219],{"class":64},[47,4857,222],{"class":203},[47,4859,225],{"class":64},[47,4861,4862],{"class":71},"'Percentage price change today'",[47,4864,231],{"class":64},[47,4866,4867],{"class":49,"line":534},[47,4868,4694],{"class":64},[47,4870,4871,4873,4875,4877,4879,4881,4883],{"class":49,"line":540},[47,4872,948],{"class":203},[47,4874,951],{"class":64},[47,4876,954],{"class":60},[47,4878,957],{"class":60},[47,4880,960],{"class":64},[47,4882,963],{"class":873},[47,4884,971],{"class":64},[47,4886,4887,4889,4891,4893,4895,4897,4900],{"class":49,"line":550},[47,4888,976],{"class":60},[47,4890,979],{"class":64},[47,4892,4724],{"class":169},[47,4894,4727],{"class":203},[47,4896,1442],{"class":60},[47,4898,4899],{"class":71},"\"h-32\"",[47,4901,4735],{"class":64},[47,4903,4904,4906,4908,4911,4913,4915],{"class":49,"line":559},[47,4905,1079],{"class":60},[47,4907,979],{"class":64},[47,4909,4910],{"class":169},"StockTicker",[47,4912,1087],{"class":64},[47,4914,1090],{"class":60},[47,4916,4756],{"class":64},[47,4918,4919],{"class":49,"line":568},[47,4920,1118],{"class":64},[47,4922,4923],{"class":49,"line":590},[47,4924,1123],{"class":64},[47,4926,4927],{"class":49,"line":604},[47,4928,2987],{"class":64},[47,4930,4931],{"class":49,"line":609},[47,4932,1260],{"class":64},[47,4934,4935],{"class":49,"line":615},[47,4936,157],{"emptyLinePlaceholder":156},[47,4938,4939,4941],{"class":49,"line":620},[47,4940,887],{"class":60},[47,4942,1267],{"class":64},[47,4944,4945],{"class":49,"line":626},[47,4946,1138],{"class":64},[16,4948,4949],{},"Τρία πράγματα αξίζει να κατανοήσετε για αυτόν τον κώδικα:",[16,4951,4952,4959,4960,4963,4964,4967],{},[748,4953,4954,4955,4958],{},"Η συνάρτηση ",[44,4956,4957],{},"generate"," είναι async generator."," Η λέξη-κλειδί ",[44,4961,4962],{},"yield"," αποστέλλει το skeleton αμέσως — πριν το AI τελειώσει την επίλυση παραμέτρων. Το ",[44,4965,4966],{},"return"," αποστέλλει το τελικό συστατικό. Έτσι λειτουργεί το streaming Generative UI.",[16,4969,4970,4973,4974,4976],{},[748,4971,4972],{},"Οι περιγραφές εργαλείων είναι οδηγίες για το AI."," Τα πεδία ",[44,4975,754],{}," είναι αυτό που διαβάζει το μοντέλο για να αποφασίσει ποιο εργαλείο θα καλέσει. Γράψτε τα με σαφήνεια, συμπεριλαμβάνοντας πότε το εργαλείο πρέπει και πότε δεν πρέπει να χρησιμοποιείται.",[16,4978,4979,4982],{},[748,4980,4981],{},"Τα σχήματα Zod επιβάλλουν τη σύμβαση."," Το AI δεν μπορεί να περάσει άκυρες παραμέτρους αν ορίσετε αυστηρά σχήματα Zod. Τα σφάλματα επικύρωσης εντοπίζονται πριν αποδοθεί το συστατικό.",[11,4984,4986],{"id":4985},"βήμα-4-κατασκευή-της-διεπαφής","Βήμα 4: Κατασκευή της Διεπαφής",[37,4988,4990],{"className":1388,"code":4989,"language":1390,"meta":42,"style":42},"\u002F\u002F app\u002Fpage.tsx\n'use client';\n\nimport { useState } from 'react';\nimport { generateUI } from '.\u002Factions';\n\nconst EXAMPLE_PROMPTS = [\n  \"What's the weather like in Tokyo?\",\n  \"Show me Apple's current stock price\",\n  \"Compare the weather in London and New York\",\n  \"How is Tesla stock doing?\",\n];\n\nexport default function Home() {\n  const [prompt, setPrompt] = useState('');\n  const [messages, setMessages] = useState\u003CArray\u003C{ prompt: string; ui: React.ReactNode }>>([]);\n  const [loading, setLoading] = useState(false);\n\n  async function handleSubmit(e: React.FormEvent) {\n    e.preventDefault();\n    if (!prompt.trim() || loading) return;\n\n    const currentPrompt = prompt;\n    setPrompt('');\n    setLoading(true);\n\n    const ui = await generateUI(currentPrompt);\n    setMessages(prev => [...prev, { prompt: currentPrompt, ui }]);\n    setLoading(false);\n  }\n\n  return (\n    \u003Cmain className=\"mx-auto max-w-2xl p-8\">\n      \u003Ch1 className=\"text-3xl font-bold\">Generative UI Demo\u003C\u002Fh1>\n      \u003Cp className=\"mt-2 text-muted-foreground\">\n        Ask about weather or stocks — watch the AI generate the right interface.\n      \u003C\u002Fp>\n\n      {\u002F* Παραδείγματα prompt *\u002F}\n      \u003Cdiv className=\"mt-4 flex flex-wrap gap-2\">\n        {EXAMPLE_PROMPTS.map(p => (\n          \u003Cbutton\n            key={p}\n            onClick={() => setPrompt(p)}\n            className=\"rounded-full border px-3 py-1 text-sm hover:bg-muted\"\n          >\n            {p}\n          \u003C\u002Fbutton>\n        ))}\n      \u003C\u002Fdiv>\n\n      {\u002F* Πεδίο εισαγωγής prompt *\u002F}\n      \u003Cform onSubmit={handleSubmit} className=\"mt-6 flex gap-2\">\n        \u003Cinput\n          value={prompt}\n          onChange={e => setPrompt(e.target.value)}\n          placeholder=\"Ask anything...\"\n          className=\"flex-1 rounded-md border bg-background px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring\"\n        \u002F>\n        \u003Cbutton\n          type=\"submit\"\n          disabled={loading || !prompt.trim()}\n          className=\"rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground disabled:opacity-50\"\n        >\n          {loading ? 'Generating...' : 'Ask'}\n        \u003C\u002Fbutton>\n      \u003C\u002Fform>\n\n      {\u002F* Έξοδος παραγόμενης διεπαφής *\u002F}\n      \u003Cdiv className=\"mt-8 space-y-6\">\n        {messages.map((msg, i) => (\n          \u003Cdiv key={i}>\n            \u003Cp className=\"mb-2 text-sm font-medium text-muted-foreground\">\n              \"{msg.prompt}\"\n            \u003C\u002Fp>\n            {msg.ui}\n          \u003C\u002Fdiv>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002Fmain>\n  );\n}\n",[44,4991,4992,4996,5002,5006,5019,5032,5036,5047,5054,5061,5068,5075,5080,5084,5097,5123,5173,5200,5204,5230,5241,5269,5273,5286,5297,5309,5313,5329,5349,5359,5363,5367,5373,5389,5409,5424,5429,5437,5441,5450,5465,5485,5492,5502,5519,5529,5534,5539,5548,5553,5561,5565,5574,5597,5604,5614,5632,5642,5652,5657,5663,5673,5695,5705,5711,5729,5738,5747,5752,5762,5778,5801,5815,5832,5838,5848,5854,5863,5868,5877,5886,5891],{"__ignoreMap":42},[47,4993,4994],{"class":49,"line":50},[47,4995,3016],{"class":53},[47,4997,4998,5000],{"class":49,"line":57},[47,4999,1754],{"class":71},[47,5001,75],{"class":64},[47,5003,5004],{"class":49,"line":78},[47,5005,157],{"emptyLinePlaceholder":156},[47,5007,5008,5010,5012,5014,5017],{"class":49,"line":93},[47,5009,61],{"class":60},[47,5011,3028],{"class":64},[47,5013,68],{"class":60},[47,5015,5016],{"class":71}," 'react'",[47,5018,75],{"class":64},[47,5020,5021,5023,5025,5027,5030],{"class":49,"line":108},[47,5022,61],{"class":60},[47,5024,3040],{"class":64},[47,5026,68],{"class":60},[47,5028,5029],{"class":71}," '.\u002Factions'",[47,5031,75],{"class":64},[47,5033,5034],{"class":49,"line":123},[47,5035,157],{"emptyLinePlaceholder":156},[47,5037,5038,5040,5043,5045],{"class":49,"line":138},[47,5039,1420],{"class":60},[47,5041,5042],{"class":169}," EXAMPLE_PROMPTS",[47,5044,173],{"class":60},[47,5046,923],{"class":64},[47,5048,5049,5052],{"class":49,"line":153},[47,5050,5051],{"class":71},"  \"What's the weather like in Tokyo?\"",[47,5053,194],{"class":64},[47,5055,5056,5059],{"class":49,"line":160},[47,5057,5058],{"class":71},"  \"Show me Apple's current stock price\"",[47,5060,194],{"class":64},[47,5062,5063,5066],{"class":49,"line":179},[47,5064,5065],{"class":71},"  \"Compare the weather in London and New York\"",[47,5067,194],{"class":64},[47,5069,5070,5073],{"class":49,"line":185},[47,5071,5072],{"class":71},"  \"How is Tesla stock doing?\"",[47,5074,194],{"class":64},[47,5076,5077],{"class":49,"line":197},[47,5078,5079],{"class":64},"];\n",[47,5081,5082],{"class":49,"line":210},[47,5083,157],{"emptyLinePlaceholder":156},[47,5085,5086,5088,5090,5092,5095],{"class":49,"line":234},[47,5087,163],{"class":60},[47,5089,3056],{"class":60},[47,5091,1157],{"class":60},[47,5093,5094],{"class":203}," Home",[47,5096,1633],{"class":64},[47,5098,5099,5101,5103,5105,5107,5110,5112,5114,5116,5118,5121],{"class":49,"line":253},[47,5100,1177],{"class":60},[47,5102,3070],{"class":64},[47,5104,2835],{"class":169},[47,5106,660],{"class":64},[47,5108,5109],{"class":169},"setPrompt",[47,5111,1572],{"class":64},[47,5113,1442],{"class":60},[47,5115,3085],{"class":203},[47,5117,225],{"class":64},[47,5119,5120],{"class":71},"''",[47,5122,2184],{"class":64},[47,5124,5125,5127,5129,5132,5134,5137,5139,5141,5143,5145,5148,5151,5153,5155,5157,5160,5162,5164,5166,5168,5170],{"class":49,"line":273},[47,5126,1177],{"class":60},[47,5128,3070],{"class":64},[47,5130,5131],{"class":169},"messages",[47,5133,660],{"class":64},[47,5135,5136],{"class":169},"setMessages",[47,5138,1572],{"class":64},[47,5140,1442],{"class":60},[47,5142,3085],{"class":203},[47,5144,1103],{"class":64},[47,5146,5147],{"class":203},"Array",[47,5149,5150],{"class":64},"\u003C{ ",[47,5152,2835],{"class":873},[47,5154,877],{"class":60},[47,5156,1170],{"class":169},[47,5158,5159],{"class":64},"; ",[47,5161,3073],{"class":873},[47,5163,877],{"class":60},[47,5165,1996],{"class":203},[47,5167,1999],{"class":64},[47,5169,2002],{"class":203},[47,5171,5172],{"class":64}," }>>([]);\n",[47,5174,5175,5177,5179,5182,5184,5187,5189,5191,5193,5195,5198],{"class":49,"line":292},[47,5176,1177],{"class":60},[47,5178,3070],{"class":64},[47,5180,5181],{"class":169},"loading",[47,5183,660],{"class":64},[47,5185,5186],{"class":169},"setLoading",[47,5188,1572],{"class":64},[47,5190,1442],{"class":60},[47,5192,3085],{"class":203},[47,5194,225],{"class":64},[47,5196,5197],{"class":169},"false",[47,5199,2184],{"class":64},[47,5201,5202],{"class":49,"line":298},[47,5203,157],{"emptyLinePlaceholder":156},[47,5205,5206,5209,5211,5214,5216,5219,5221,5223,5225,5228],{"class":49,"line":304},[47,5207,5208],{"class":60},"  async",[47,5210,1157],{"class":60},[47,5212,5213],{"class":203}," handleSubmit",[47,5215,225],{"class":64},[47,5217,5218],{"class":873},"e",[47,5220,877],{"class":60},[47,5222,1996],{"class":203},[47,5224,1999],{"class":64},[47,5226,5227],{"class":203},"FormEvent",[47,5229,971],{"class":64},[47,5231,5232,5235,5238],{"class":49,"line":310},[47,5233,5234],{"class":64},"    e.",[47,5236,5237],{"class":203},"preventDefault",[47,5239,5240],{"class":64},"();\n",[47,5242,5243,5246,5248,5250,5253,5256,5259,5262,5265,5267],{"class":49,"line":316},[47,5244,5245],{"class":60},"    if",[47,5247,960],{"class":64},[47,5249,1021],{"class":60},[47,5251,5252],{"class":64},"prompt.",[47,5254,5255],{"class":203},"trim",[47,5257,5258],{"class":64},"() ",[47,5260,5261],{"class":60},"||",[47,5263,5264],{"class":64}," loading) ",[47,5266,4966],{"class":60},[47,5268,75],{"class":64},[47,5270,5271],{"class":49,"line":326},[47,5272,157],{"emptyLinePlaceholder":156},[47,5274,5275,5278,5281,5283],{"class":49,"line":335},[47,5276,5277],{"class":60},"    const",[47,5279,5280],{"class":169}," currentPrompt",[47,5282,173],{"class":60},[47,5284,5285],{"class":64}," prompt;\n",[47,5287,5288,5291,5293,5295],{"class":49,"line":351},[47,5289,5290],{"class":203},"    setPrompt",[47,5292,225],{"class":64},[47,5294,5120],{"class":71},[47,5296,2184],{"class":64},[47,5298,5299,5302,5304,5307],{"class":49,"line":362},[47,5300,5301],{"class":203},"    setLoading",[47,5303,225],{"class":64},[47,5305,5306],{"class":169},"true",[47,5308,2184],{"class":64},[47,5310,5311],{"class":49,"line":372},[47,5312,157],{"emptyLinePlaceholder":156},[47,5314,5315,5317,5320,5322,5324,5326],{"class":49,"line":388},[47,5316,5277],{"class":60},[47,5318,5319],{"class":169}," ui",[47,5321,173],{"class":60},[47,5323,1185],{"class":60},[47,5325,2830],{"class":203},[47,5327,5328],{"class":64},"(currentPrompt);\n",[47,5330,5331,5334,5336,5339,5342,5344,5346],{"class":49,"line":394},[47,5332,5333],{"class":203},"    setMessages",[47,5335,225],{"class":64},[47,5337,5338],{"class":873},"prev",[47,5340,5341],{"class":60}," =>",[47,5343,3070],{"class":64},[47,5345,1090],{"class":60},[47,5347,5348],{"class":64},"prev, { prompt: currentPrompt, ui }]);\n",[47,5350,5351,5353,5355,5357],{"class":49,"line":414},[47,5352,5301],{"class":203},[47,5354,225],{"class":64},[47,5356,5197],{"class":169},[47,5358,2184],{"class":64},[47,5360,5361],{"class":49,"line":428},[47,5362,2282],{"class":64},[47,5364,5365],{"class":49,"line":433},[47,5366,157],{"emptyLinePlaceholder":156},[47,5368,5369,5371],{"class":49,"line":439},[47,5370,887],{"class":60},[47,5372,1539],{"class":64},[47,5374,5375,5377,5380,5382,5384,5387],{"class":49,"line":444},[47,5376,1544],{"class":64},[47,5378,5379],{"class":1547},"main",[47,5381,1649],{"class":203},[47,5383,1442],{"class":60},[47,5385,5386],{"class":71},"\"mx-auto max-w-2xl p-8\"",[47,5388,1657],{"class":64},[47,5390,5391,5393,5396,5398,5400,5402,5405,5407],{"class":49,"line":450},[47,5392,1662],{"class":64},[47,5394,5395],{"class":1547},"h1",[47,5397,1649],{"class":203},[47,5399,1442],{"class":60},[47,5401,4223],{"class":71},[47,5403,5404],{"class":64},">Generative UI Demo\u003C\u002F",[47,5406,5395],{"class":1547},[47,5408,1657],{"class":64},[47,5410,5411,5413,5415,5417,5419,5422],{"class":49,"line":460},[47,5412,1662],{"class":64},[47,5414,16],{"class":1547},[47,5416,1649],{"class":203},[47,5418,1442],{"class":60},[47,5420,5421],{"class":71},"\"mt-2 text-muted-foreground\"",[47,5423,1657],{"class":64},[47,5425,5426],{"class":49,"line":469},[47,5427,5428],{"class":64},"        Ask about weather or stocks — watch the AI generate the right interface.\n",[47,5430,5431,5433,5435],{"class":49,"line":479},[47,5432,1879],{"class":64},[47,5434,16],{"class":1547},[47,5436,1657],{"class":64},[47,5438,5439],{"class":49,"line":504},[47,5440,157],{"emptyLinePlaceholder":156},[47,5442,5443,5445,5448],{"class":49,"line":518},[47,5444,2306],{"class":64},[47,5446,5447],{"class":53},"\u002F* Παραδείγματα prompt *\u002F",[47,5449,1138],{"class":64},[47,5451,5452,5454,5456,5458,5460,5463],{"class":49,"line":523},[47,5453,1662],{"class":64},[47,5455,1646],{"class":1547},[47,5457,1649],{"class":203},[47,5459,1442],{"class":60},[47,5461,5462],{"class":71},"\"mt-4 flex flex-wrap gap-2\"",[47,5464,1657],{"class":64},[47,5466,5467,5470,5473,5475,5477,5479,5481,5483],{"class":49,"line":529},[47,5468,5469],{"class":64},"        {",[47,5471,5472],{"class":169},"EXAMPLE_PROMPTS",[47,5474,1999],{"class":64},[47,5476,904],{"class":203},[47,5478,225],{"class":64},[47,5480,16],{"class":873},[47,5482,5341],{"class":60},[47,5484,1539],{"class":64},[47,5486,5487,5490],{"class":49,"line":534},[47,5488,5489],{"class":64},"          \u003C",[47,5491,1910],{"class":1547},[47,5493,5494,5497,5499],{"class":49,"line":540},[47,5495,5496],{"class":203},"            key",[47,5498,1442],{"class":60},[47,5500,5501],{"class":64},"{p}\n",[47,5503,5504,5507,5509,5511,5513,5516],{"class":49,"line":550},[47,5505,5506],{"class":203},"            onClick",[47,5508,1442],{"class":60},[47,5510,2357],{"class":64},[47,5512,920],{"class":60},[47,5514,5515],{"class":203}," setPrompt",[47,5517,5518],{"class":64},"(p)}\n",[47,5520,5521,5524,5526],{"class":49,"line":559},[47,5522,5523],{"class":203},"            className",[47,5525,1442],{"class":60},[47,5527,5528],{"class":71},"\"rounded-full border px-3 py-1 text-sm hover:bg-muted\"\n",[47,5530,5531],{"class":49,"line":568},[47,5532,5533],{"class":64},"          >\n",[47,5535,5536],{"class":49,"line":590},[47,5537,5538],{"class":64},"            {p}\n",[47,5540,5541,5544,5546],{"class":49,"line":604},[47,5542,5543],{"class":64},"          \u003C\u002F",[47,5545,1947],{"class":1547},[47,5547,1657],{"class":64},[47,5549,5550],{"class":49,"line":609},[47,5551,5552],{"class":64},"        ))}\n",[47,5554,5555,5557,5559],{"class":49,"line":615},[47,5556,1879],{"class":64},[47,5558,1646],{"class":1547},[47,5560,1657],{"class":64},[47,5562,5563],{"class":49,"line":620},[47,5564,157],{"emptyLinePlaceholder":156},[47,5566,5567,5569,5572],{"class":49,"line":626},[47,5568,2306],{"class":64},[47,5570,5571],{"class":53},"\u002F* Πεδίο εισαγωγής prompt *\u002F",[47,5573,1138],{"class":64},[47,5575,5576,5578,5580,5583,5585,5588,5590,5592,5595],{"class":49,"line":636},[47,5577,1662],{"class":64},[47,5579,3114],{"class":1547},[47,5581,5582],{"class":203}," onSubmit",[47,5584,1442],{"class":60},[47,5586,5587],{"class":64},"{handleSubmit} ",[47,5589,2367],{"class":203},[47,5591,1442],{"class":60},[47,5593,5594],{"class":71},"\"mt-6 flex gap-2\"",[47,5596,1657],{"class":64},[47,5598,5599,5601],{"class":49,"line":645},[47,5600,2338],{"class":64},[47,5602,5603],{"class":1547},"input\n",[47,5605,5606,5609,5611],{"class":49,"line":679},[47,5607,5608],{"class":203},"          value",[47,5610,1442],{"class":60},[47,5612,5613],{"class":64},"{prompt}\n",[47,5615,5616,5619,5621,5623,5625,5627,5629],{"class":49,"line":688},[47,5617,5618],{"class":203},"          onChange",[47,5620,1442],{"class":60},[47,5622,1558],{"class":64},[47,5624,5218],{"class":873},[47,5626,5341],{"class":60},[47,5628,5515],{"class":203},[47,5630,5631],{"class":64},"(e.target.value)}\n",[47,5633,5634,5637,5639],{"class":49,"line":698},[47,5635,5636],{"class":203},"          placeholder",[47,5638,1442],{"class":60},[47,5640,5641],{"class":71},"\"Ask anything...\"\n",[47,5643,5644,5647,5649],{"class":49,"line":703},[47,5645,5646],{"class":203},"          className",[47,5648,1442],{"class":60},[47,5650,5651],{"class":71},"\"flex-1 rounded-md border bg-background px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring\"\n",[47,5653,5654],{"class":49,"line":709},[47,5655,5656],{"class":64},"        \u002F>\n",[47,5658,5659,5661],{"class":49,"line":714},[47,5660,2338],{"class":64},[47,5662,1910],{"class":1547},[47,5664,5665,5668,5670],{"class":49,"line":720},[47,5666,5667],{"class":203},"          type",[47,5669,1442],{"class":60},[47,5671,5672],{"class":71},"\"submit\"\n",[47,5674,5675,5678,5680,5683,5685,5688,5690,5692],{"class":49,"line":725},[47,5676,5677],{"class":203},"          disabled",[47,5679,1442],{"class":60},[47,5681,5682],{"class":64},"{loading ",[47,5684,5261],{"class":60},[47,5686,5687],{"class":60}," !",[47,5689,5252],{"class":64},[47,5691,5255],{"class":203},[47,5693,5694],{"class":64},"()}\n",[47,5696,5698,5700,5702],{"class":49,"line":5697},63,[47,5699,5646],{"class":203},[47,5701,1442],{"class":60},[47,5703,5704],{"class":71},"\"rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground disabled:opacity-50\"\n",[47,5706,5708],{"class":49,"line":5707},64,[47,5709,5710],{"class":64},"        >\n",[47,5712,5714,5717,5719,5722,5724,5727],{"class":49,"line":5713},65,[47,5715,5716],{"class":64},"          {loading ",[47,5718,4048],{"class":60},[47,5720,5721],{"class":71}," 'Generating...'",[47,5723,4054],{"class":60},[47,5725,5726],{"class":71}," 'Ask'",[47,5728,1138],{"class":64},[47,5730,5732,5734,5736],{"class":49,"line":5731},66,[47,5733,2388],{"class":64},[47,5735,1947],{"class":1547},[47,5737,1657],{"class":64},[47,5739,5741,5743,5745],{"class":49,"line":5740},67,[47,5742,1879],{"class":64},[47,5744,3114],{"class":1547},[47,5746,1657],{"class":64},[47,5748,5750],{"class":49,"line":5749},68,[47,5751,157],{"emptyLinePlaceholder":156},[47,5753,5755,5757,5760],{"class":49,"line":5754},69,[47,5756,2306],{"class":64},[47,5758,5759],{"class":53},"\u002F* Έξοδος παραγόμενης διεπαφής *\u002F",[47,5761,1138],{"class":64},[47,5763,5765,5767,5769,5771,5773,5776],{"class":49,"line":5764},70,[47,5766,1662],{"class":64},[47,5768,1646],{"class":1547},[47,5770,1649],{"class":203},[47,5772,1442],{"class":60},[47,5774,5775],{"class":71},"\"mt-8 space-y-6\"",[47,5777,1657],{"class":64},[47,5779,5781,5784,5786,5788,5791,5793,5795,5797,5799],{"class":49,"line":5780},71,[47,5782,5783],{"class":64},"        {messages.",[47,5785,904],{"class":203},[47,5787,907],{"class":64},[47,5789,5790],{"class":873},"msg",[47,5792,660],{"class":64},[47,5794,2327],{"class":873},[47,5796,917],{"class":64},[47,5798,920],{"class":60},[47,5800,1539],{"class":64},[47,5802,5804,5806,5808,5810,5812],{"class":49,"line":5803},72,[47,5805,5489],{"class":64},[47,5807,1646],{"class":1547},[47,5809,2344],{"class":203},[47,5811,1442],{"class":60},[47,5813,5814],{"class":64},"{i}>\n",[47,5816,5818,5821,5823,5825,5827,5830],{"class":49,"line":5817},73,[47,5819,5820],{"class":64},"            \u003C",[47,5822,16],{"class":1547},[47,5824,1649],{"class":203},[47,5826,1442],{"class":60},[47,5828,5829],{"class":71},"\"mb-2 text-sm font-medium text-muted-foreground\"",[47,5831,1657],{"class":64},[47,5833,5835],{"class":49,"line":5834},74,[47,5836,5837],{"class":64},"              \"{msg.prompt}\"\n",[47,5839,5841,5844,5846],{"class":49,"line":5840},75,[47,5842,5843],{"class":64},"            \u003C\u002F",[47,5845,16],{"class":1547},[47,5847,1657],{"class":64},[47,5849,5851],{"class":49,"line":5850},76,[47,5852,5853],{"class":64},"            {msg.ui}\n",[47,5855,5857,5859,5861],{"class":49,"line":5856},77,[47,5858,5543],{"class":64},[47,5860,1646],{"class":1547},[47,5862,1657],{"class":64},[47,5864,5866],{"class":49,"line":5865},78,[47,5867,5552],{"class":64},[47,5869,5871,5873,5875],{"class":49,"line":5870},79,[47,5872,1879],{"class":64},[47,5874,1646],{"class":1547},[47,5876,1657],{"class":64},[47,5878,5880,5882,5884],{"class":49,"line":5879},80,[47,5881,1709],{"class":64},[47,5883,5379],{"class":1547},[47,5885,1657],{"class":64},[47,5887,5889],{"class":49,"line":5888},81,[47,5890,1133],{"class":64},[47,5892,5894],{"class":49,"line":5893},82,[47,5895,1138],{"class":64},[11,5897,5899],{"id":5898},"βήμα-5-εκτέλεση-και-δοκιμή","Βήμα 5: Εκτέλεση και Δοκιμή",[37,5901,5903],{"className":3571,"code":5902,"language":3573,"meta":42,"style":42},"npm run dev\n",[44,5904,5905],{"__ignoreMap":42},[47,5906,5907,5909,5912],{"class":49,"line":50},[47,5908,3580],{"class":203},[47,5910,5911],{"class":71}," run",[47,5913,5914],{"class":71}," dev\n",[16,5916,5917],{},"Δοκιμάστε αυτά τα prompts με τη σειρά για να δείτε διαφορετικές συμπεριφορές:",[3281,5919,5920,5926,5932,5942],{},[2614,5921,5922,5925],{},[748,5923,5924],{},"«What's the weather in Paris?»"," — μονή WeatherCard",[2614,5927,5928,5931],{},[748,5929,5930],{},"«Show me Apple stock»"," — μονό StockTicker",[2614,5933,5934,5937,5938,5941],{},[748,5935,5936],{},"«Compare the weather in London and New York»"," — το AI καλεί ",[44,5939,5940],{},"showWeather"," δύο φορές, παράγοντας δύο κάρτες δίπλα-δίπλα",[2614,5943,5944,5947],{},[748,5945,5946],{},"«How's Tesla doing and what's the weather in San Francisco?»"," — το AI καλεί και τα δύο εργαλεία, παράγοντας μικτούς τύπους συστατικών",[16,5949,5950],{},"Το τρίτο prompt είναι η βασική επίδειξη: χωρίς επιπλέον κώδικα, το μοντέλο συνθέτει πολλαπλά συστατικά για να απαντήσει σε μια πολυμερή ερώτηση.",[11,5952,5954],{"id":5953},"τι-συμβαίνει-κάτω-από-την-επιφάνεια","Τι Συμβαίνει Κάτω από την Επιφάνεια",[16,5956,5957],{},"Όταν υποβάλλετε ένα prompt:",[2611,5959,5960,5966,5972,5975,5980,5983],{},[2614,5961,5962,5963],{},"Ο client καλεί το server action ",[44,5964,5965],{},"generateUI",[2614,5967,5968,5969,5971],{},"Το ",[44,5970,787],{}," αποστέλλει το prompt + ορισμούς εργαλείων στο OpenAI API",[2614,5973,5974],{},"Το μοντέλο επιλέγει ποια εργαλεία θα καλέσει και με ποιες παραμέτρους",[2614,5976,4954,5977,5979],{},[44,5978,4957],{}," κάθε εργαλείου αποδίδει αμέσως ένα skeleton",[2614,5981,5982],{},"Το AI τελειώνει την επίλυση παραμέτρων, και επιστρέφεται το τελικό συστατικό",[2614,5984,5985],{},"Το React αποδίδει το συστατικό στη θέση του skeleton",[16,5987,5988],{},"Το πρωτόκολλο streaming RSC είναι αυτό που κάνει αυτό να λειτουργεί. Ο server σειριοποιεί δέντρα συστατικών React και τα μεταδίδει στον client σταδιακά. Αυτό διαφέρει από ένα JSON API — ο client λαμβάνει αποδοθέντα συστατικά, όχι ακατέργαστα δεδομένα.",[11,5990,5992],{"id":5991},"διαχείριση-σφαλμάτων","Διαχείριση Σφαλμάτων",[16,5994,5995],{},"Τα παραγόμενα συστατικά μπορεί να αποτύχουν με τρόπους που τα χειροκίνητα συστατικά δεν αποτυγχάνουν. Περιτυλίξτε την παραγόμενη έξοδο σε ένα error boundary:",[37,5997,5999],{"className":1388,"code":5998,"language":1390,"meta":42,"style":42},"\u002F\u002F components\u002Fgenui-error-boundary.tsx\n'use client';\n\nimport { Component, ReactNode } from 'react';\n\ninterface Props { children: ReactNode }\ninterface State { hasError: boolean; error: Error | null }\n\nexport class GenUIErrorBoundary extends Component\u003CProps, State> {\n  constructor(props: Props) {\n    super(props);\n    this.state = { hasError: false, error: null };\n  }\n\n  static getDerivedStateFromError(error: Error) {\n    return { hasError: true, error };\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        \u003Cdiv className=\"rounded-lg border border-destructive\u002F50 bg-destructive\u002F5 p-4\">\n          \u003Cp className=\"text-sm text-destructive\">\n            This component could not render. The AI may have passed unexpected data.\n          \u003C\u002Fp>\n        \u003C\u002Fdiv>\n      );\n    }\n    return this.props.children;\n  }\n}\n",[44,6000,6001,6006,6012,6016,6029,6033,6052,6084,6088,6116,6132,6140,6163,6167,6171,6189,6201,6205,6209,6216,6228,6235,6249,6264,6269,6277,6285,6290,6295,6305,6309],{"__ignoreMap":42},[47,6002,6003],{"class":49,"line":50},[47,6004,6005],{"class":53},"\u002F\u002F components\u002Fgenui-error-boundary.tsx\n",[47,6007,6008,6010],{"class":49,"line":57},[47,6009,1754],{"class":71},[47,6011,75],{"class":64},[47,6013,6014],{"class":49,"line":78},[47,6015,157],{"emptyLinePlaceholder":156},[47,6017,6018,6020,6023,6025,6027],{"class":49,"line":93},[47,6019,61],{"class":60},[47,6021,6022],{"class":64}," { Component, ReactNode } ",[47,6024,68],{"class":60},[47,6026,5016],{"class":71},[47,6028,75],{"class":64},[47,6030,6031],{"class":49,"line":108},[47,6032,157],{"emptyLinePlaceholder":156},[47,6034,6035,6037,6040,6042,6044,6046,6049],{"class":49,"line":123},[47,6036,3661],{"class":60},[47,6038,6039],{"class":203}," Props",[47,6041,1523],{"class":64},[47,6043,1983],{"class":873},[47,6045,877],{"class":60},[47,6047,6048],{"class":203}," ReactNode",[47,6050,6051],{"class":64}," }\n",[47,6053,6054,6056,6059,6061,6064,6066,6069,6071,6073,6075,6077,6080,6082],{"class":49,"line":138},[47,6055,3661],{"class":60},[47,6057,6058],{"class":203}," State",[47,6060,1523],{"class":64},[47,6062,6063],{"class":873},"hasError",[47,6065,877],{"class":60},[47,6067,6068],{"class":169}," boolean",[47,6070,5159],{"class":64},[47,6072,1790],{"class":873},[47,6074,877],{"class":60},[47,6076,1045],{"class":203},[47,6078,6079],{"class":60}," |",[47,6081,2175],{"class":169},[47,6083,6051],{"class":64},[47,6085,6086],{"class":49,"line":153},[47,6087,157],{"emptyLinePlaceholder":156},[47,6089,6090,6092,6095,6098,6101,6103,6105,6108,6110,6113],{"class":49,"line":160},[47,6091,163],{"class":60},[47,6093,6094],{"class":60}," class",[47,6096,6097],{"class":203}," GenUIErrorBoundary",[47,6099,6100],{"class":60}," extends",[47,6102,1001],{"class":203},[47,6104,1103],{"class":64},[47,6106,6107],{"class":203},"Props",[47,6109,660],{"class":64},[47,6111,6112],{"class":203},"State",[47,6114,6115],{"class":64},"> {\n",[47,6117,6118,6121,6123,6126,6128,6130],{"class":49,"line":179},[47,6119,6120],{"class":60},"  constructor",[47,6122,225],{"class":64},[47,6124,6125],{"class":873},"props",[47,6127,877],{"class":60},[47,6129,6039],{"class":203},[47,6131,971],{"class":64},[47,6133,6134,6137],{"class":49,"line":185},[47,6135,6136],{"class":169},"    super",[47,6138,6139],{"class":64},"(props);\n",[47,6141,6142,6145,6148,6150,6153,6155,6158,6160],{"class":49,"line":197},[47,6143,6144],{"class":169},"    this",[47,6146,6147],{"class":64},".state ",[47,6149,1442],{"class":60},[47,6151,6152],{"class":64}," { hasError: ",[47,6154,5197],{"class":169},[47,6156,6157],{"class":64},", error: ",[47,6159,2181],{"class":169},[47,6161,6162],{"class":64}," };\n",[47,6164,6165],{"class":49,"line":210},[47,6166,2282],{"class":64},[47,6168,6169],{"class":49,"line":234},[47,6170,157],{"emptyLinePlaceholder":156},[47,6172,6173,6176,6179,6181,6183,6185,6187],{"class":49,"line":253},[47,6174,6175],{"class":60},"  static",[47,6177,6178],{"class":203}," getDerivedStateFromError",[47,6180,225],{"class":64},[47,6182,1790],{"class":873},[47,6184,877],{"class":60},[47,6186,1045],{"class":203},[47,6188,971],{"class":64},[47,6190,6191,6194,6196,6198],{"class":49,"line":273},[47,6192,6193],{"class":60},"    return",[47,6195,6152],{"class":64},[47,6197,5306],{"class":169},[47,6199,6200],{"class":64},", error };\n",[47,6202,6203],{"class":49,"line":292},[47,6204,2282],{"class":64},[47,6206,6207],{"class":49,"line":298},[47,6208,157],{"emptyLinePlaceholder":156},[47,6210,6211,6214],{"class":49,"line":304},[47,6212,6213],{"class":203},"  render",[47,6215,1633],{"class":64},[47,6217,6218,6220,6222,6225],{"class":49,"line":310},[47,6219,5245],{"class":60},[47,6221,960],{"class":64},[47,6223,6224],{"class":169},"this",[47,6226,6227],{"class":64},".state.hasError) {\n",[47,6229,6230,6233],{"class":49,"line":316},[47,6231,6232],{"class":60},"      return",[47,6234,1539],{"class":64},[47,6236,6237,6239,6241,6243,6245,6247],{"class":49,"line":326},[47,6238,2338],{"class":64},[47,6240,1646],{"class":1547},[47,6242,1649],{"class":203},[47,6244,1442],{"class":60},[47,6246,1852],{"class":71},[47,6248,1657],{"class":64},[47,6250,6251,6253,6255,6257,6259,6262],{"class":49,"line":335},[47,6252,5489],{"class":64},[47,6254,16],{"class":1547},[47,6256,1649],{"class":203},[47,6258,1442],{"class":60},[47,6260,6261],{"class":71},"\"text-sm text-destructive\"",[47,6263,1657],{"class":64},[47,6265,6266],{"class":49,"line":351},[47,6267,6268],{"class":64},"            This component could not render. The AI may have passed unexpected data.\n",[47,6270,6271,6273,6275],{"class":49,"line":362},[47,6272,5543],{"class":64},[47,6274,16],{"class":1547},[47,6276,1657],{"class":64},[47,6278,6279,6281,6283],{"class":49,"line":372},[47,6280,2388],{"class":64},[47,6282,1646],{"class":1547},[47,6284,1657],{"class":64},[47,6286,6287],{"class":49,"line":388},[47,6288,6289],{"class":64},"      );\n",[47,6291,6292],{"class":49,"line":394},[47,6293,6294],{"class":64},"    }\n",[47,6296,6297,6299,6302],{"class":49,"line":414},[47,6298,6193],{"class":60},[47,6300,6301],{"class":169}," this",[47,6303,6304],{"class":64},".props.children;\n",[47,6306,6307],{"class":49,"line":428},[47,6308,2282],{"class":64},[47,6310,6311],{"class":49,"line":433},[47,6312,1138],{"class":64},[16,6314,6315],{},"Περιτυλίξτε το στη σελίδα σας γύρω από την παραγόμενη έξοδο διεπαφής.",[11,6317,6319],{"id":6318},"συμβουλές-ανάπτυξης","Συμβουλές Ανάπτυξης",[16,6321,6322,6325,6326,6328],{},[748,6323,6324],{},"Μεταβλητές περιβάλλοντος:"," Το ",[44,6327,2652],{}," πρέπει να είναι διαθέσιμο στο περιβάλλον παραγωγής σας. Στο Vercel, προσθέστε το στις ρυθμίσεις του έργου στην ενότητα Environment Variables.",[16,6330,6331,6334,6335,6337,6338,6341],{},[748,6332,6333],{},"Edge runtime:"," Η συνάρτηση ",[44,6336,787],{}," λειτουργεί σε Edge runtime, που μειώνει σημαντικά τους χρόνους cold start. Προσθέστε ",[44,6339,6340],{},"export const runtime = 'edge'"," στο αρχείο server action σας.",[16,6343,6344,6347,6348,6350,6351,6354],{},[748,6345,6346],{},"Περιορισμός ρυθμού:"," Χωρίς περιορισμό ρυθμού, ένας χρήστης θα μπορούσε να παράγει χιλιάδες αιτήματα AI. Προσθέστε έναν rate limiter πριν από την κλήση ",[44,6349,787],{},". Το πακέτο ",[44,6352,6353],{},"@upstash\u002Fratelimit"," ενσωματώνεται καλά με το Next.js.",[16,6356,6357,6325,6360,6363,6364,6367,6368,6373],{},[748,6358,6359],{},"Επιλογή μοντέλου:",[44,6361,6362],{},"gpt-4o"," παράγει τις καλύτερες επιλογές συστατικών αλλά κοστίζει περισσότερο. Το ",[44,6365,6366],{},"gpt-4o-mini"," είναι περίπου 15× φθηνότερο (",[779,6369,6372],{"href":6370,"rel":6371},"https:\u002F\u002Fopenai.com\u002Fapi\u002Fpricing",[783],"openai.com\u002Fapi\u002Fpricing",", 2026-05) και λειτουργεί καλά για απλά σύνολα συστατικών. Δοκίμασε και τα δύο με τους συγκεκριμένους ορισμούς εργαλείων σου.",[16,6375,6376,6379],{},[748,6377,6378],{},"Μεθοδολογία υπολογισμού TCO:"," τα νούμερα υπολογίζονται με παραδοχές: μέσο prompt ~800 input + ~300 output tokens στο gpt-4o (ή ~$0,001 στο gpt-4o-mini), 1 αίτημα\u002Fsession, τιμές OpenAI για 2026-05, MAU ≈ DAU × 30%. Βαθμονόμησε με βάση το δικό σου workload.",[11,6381,6383],{"id":6382},"επόμενα-βήματα","Επόμενα Βήματα",[16,6385,6386],{},"Αυτός ο οδηγός κάλυψε τα θεμελιώδη. Για Generative UI παραγωγής:",[3281,6388,6389,6395,6401,6407,6413],{},[2614,6390,6391,6394],{},[748,6392,6393],{},"Προσθέστε περισσότερα εργαλεία"," — κάθε νέο συστατικό που προσθέτετε στο registry επεκτείνει αυτό που μπορεί να απαντήσει το AI",[2614,6396,6397,6400],{},[748,6398,6399],{},"Υλοποιήστε caching αποτελεσμάτων εργαλείων"," — αποθηκεύστε στην κρυφή μνήμη συνηθισμένα ερωτήματα για μείωση καθυστέρησης και κόστους",[2614,6402,6403,6406],{},[748,6404,6405],{},"Προσθέστε streaming κείμενο"," παράλληλα με συστατικά διεπαφής ώστε το AI να μπορεί να εξηγεί τι εμφανίζει",[2614,6408,6409,6412],{},[748,6410,6411],{},"Χρησιμοποιήστε structured outputs"," για πιο αξιόπιστη παραγωγή παραμέτρων",[2614,6414,6415,6418],{},[748,6416,6417],{},"Ρυθμίστε observability"," — καταγράψτε κάθε κλήση εργαλείου, τις παραμέτρους της και τις αλληλεπιδράσεις χρήστη",[16,6420,6421],{},"Η τεκμηρίωση του Vercel AI SDK καλύπτει όλα αυτά τα μοτίβα σε βάθος, και το αποθετήριο παραδειγμάτων διαθέτει αξιόλογα πρότυπα production-grade για μελέτη.",[3428,6423],{},[11,6425,6427],{"id":6426},"σε-ai-sdk-v5v6","Σε AI SDK v5\u002Fv6",[16,6429,6430],{},"Αν χρησιμοποιείς νεότερες εκδόσεις SDK, οι βασικές διαφορές από τον κώδικα αυτού του άρθρου:",[3281,6432,6433,6441,6451],{},[2614,6434,6435,6437,6438],{},[44,6436,3598],{}," στον ορισμό εργαλείου → ",[44,6439,6440],{},"inputSchema:",[2614,6442,6443,6444,6447,6448],{},"Import ",[44,6445,6446],{},"import { streamUI } from 'ai\u002Frsc'"," → ",[44,6449,6450],{},"import { streamUI } from '@ai-sdk\u002Frsc'",[2614,6452,6453,6454,4670],{},"Το RSC παραμένει επισημασμένο ως experimental από τη Vercel — για production συνιστάται το AI SDK UI (",[44,6455,3549],{},[3428,6457],{},[16,6459,6460],{},[757,6461,6462,6463,6466],{},"Θέλεις να υλοποιήσεις Generative UI στο προϊόν σου; ",[779,6464,6465],{"href":3437},"Ας συζητήσουμε την περίπτωσή σου"," — από την εμπειρία μας σε consulting, το GenUI stack ταιριάζει καλά σε dashboards και εσωτερικά εργαλεία· για regulated surfaces και δημόσιες σελίδες με υψηλή κίνηση τα trade-offs συνήθως δεν κλείνουν.",[3441,6468,6469],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":42,"searchDepth":57,"depth":57,"links":6471},[6472,6473,6474,6475,6476,6477,6478,6479,6480,6481,6482,6483],{"id":3488,"depth":57,"text":3489},{"id":3515,"depth":57,"text":3516},{"id":3567,"depth":57,"text":3568},{"id":3642,"depth":57,"text":3643},{"id":4389,"depth":57,"text":4390},{"id":4985,"depth":57,"text":4986},{"id":5898,"depth":57,"text":5899},{"id":5953,"depth":57,"text":5954},{"id":5991,"depth":57,"text":5992},{"id":6318,"depth":57,"text":6319},{"id":6382,"depth":57,"text":6383},{"id":6426,"depth":57,"text":6427},"2026-02-28","Βήμα-βήμα οδηγός για τη δημιουργία της πρώτης σας AI-powered διεπαφής με streaming συστατικά.",{"audit_status":3468,"audit_date":3469,"sdk_version":6487,"last_price_check":3469},"ai@4 (v4-pin)","\u002Fel\u002Flearn\u002Fbuilding-generative-ui-vercel-ai-sdk","18 λεπτά ανάγνωσης",{"title":3483,"description":6485},"el\u002Flearn\u002Fbuilding-generative-ui-vercel-ai-sdk",[6493,3475,3462,6494],"vercel-ai-sdk","streaming","DJ-MuHFk_go8jfrjMKh5jt8sOVQMoUOmlC4Zo3UCJHc",{"id":6497,"title":6498,"author":6,"body":6499,"category":7824,"date":7825,"description":7826,"draft":3465,"extension":3466,"featured":3465,"meta":7827,"navigation":156,"path":7829,"readTime":7830,"seo":7831,"stem":7832,"tags":7833,"__hash__":7838},"content\u002Fel\u002Flearn\u002Fcopilotkit-vs-vercel-ai-sdk-vs-thesys.md","CopilotKit vs Vercel AI SDK vs Thesys: Σύγκριση Frameworks",{"type":8,"value":6500,"toc":7800},[6501,6505,6508,6511,6515,6658,6662,6667,6671,6674,6848,6857,6861,6895,6899,6927,6931,6934,6936,6940,6947,6950,6953,7310,7313,7316,7359,7362,7388,7392,7395,7397,7401,7404,7407,7410,7631,7634,7637,7669,7672,7698,7702,7705,7707,7711,7714,7719,7730,7735,7746,7749,7753,7759,7765,7771,7777,7783,7786,7788,7797],[11,6502,6504],{"id":6503},"το-τοπίο-στις-αρχές-του-2026","Το Τοπίο στις Αρχές του 2026",[16,6506,6507],{},"Τρία frameworks κυριαρχούν στον χώρο του Generative UI αυτή τη στιγμή. Το καθένα ακολουθεί θεμελιωδώς διαφορετική προσέγγιση στο ίδιο πρόβλημα: πώς επιτρέπετε στα μοντέλα AI να παράγουν διαδραστικές διεπαφές χρήστη;",[16,6509,6510],{},"Αυτά είναι τα ευρήματά μου μετά από κατασκευή λειτουργιών παραγωγής και με τα τρία.",[11,6512,6514],{"id":6513},"γρήγορη-σύγκριση","Γρήγορη Σύγκριση",[2299,6516,6517,6533],{},[2431,6518,6519],{},[2341,6520,6521,6524,6527,6530],{},[2436,6522,6523],{},"Χαρακτηριστικό",[2436,6525,6526],{},"Vercel AI SDK",[2436,6528,6529],{},"CopilotKit",[2436,6531,6532],{},"Thesys (json-render)",[2449,6534,6535,6549,6563,6577,6591,6605,6617,6631,6644],{},[2341,6536,6537,6540,6543,6546],{},[2454,6538,6539],{},"Αστέρια GitHub",[2454,6541,6542],{},"~45K",[2454,6544,6545],{},"22K",[2454,6547,6548],{},"13K (3 μηνών)",[2341,6550,6551,6554,6557,6560],{},[2454,6552,6553],{},"Λήψεις npm",[2454,6555,6556],{},"20M+\u002Fμήνα",[2454,6558,6559],{},"~200K\u002Fμήνα",[2454,6561,6562],{},"~50K\u002Fμήνα",[2341,6564,6565,6568,6571,6574],{},[2454,6566,6567],{},"Προσέγγιση",[2454,6569,6570],{},"Stream React μέσω RSC",[2454,6572,6573],{},"Συστατικά μοτίβου Copilot",[2454,6575,6576],{},"Απόδοση σχήματος JSON",[2341,6578,6579,6582,6585,6588],{},[2454,6580,6581],{},"Εξάρτηση framework",[2454,6583,6584],{},"Next.js (κυρίως)",[2454,6586,6587],{},"React (οποιοδήποτε bundler)",[2454,6589,6590],{},"Αγνωστικό ως προς το framework",[2341,6592,6593,6596,6599,6602],{},[2454,6594,6595],{},"Καμπύλη εκμάθησης",[2454,6597,6598],{},"Μέτρια",[2454,6600,6601],{},"Χαμηλή",[2454,6603,6604],{},"Χαμηλή–Μέτρια",[2341,6606,6607,6610,6613,6615],{},[2454,6608,6609],{},"Ετοιμότητα παραγωγής",[2454,6611,6612],{},"Υψηλή",[2454,6614,6612],{},[2454,6616,6598],{},[2341,6618,6619,6622,6625,6628],{},[2454,6620,6621],{},"Κατάλληλο για",[2454,6623,6624],{},"Full-stack Next.js εφαρμογές",[2454,6626,6627],{},"Προσθήκη AI σε υπάρχουσες εφαρμογές",[2454,6629,6630],{},"Έργα με πολλαπλά frameworks",[2341,6632,6633,6636,6639,6642],{},[2454,6634,6635],{},"Άδεια",[2454,6637,6638],{},"Apache 2.0",[2454,6640,6641],{},"MIT",[2454,6643,6641],{},[2341,6645,6646,6649,6652,6655],{},[2454,6647,6648],{},"Επιλογή διαχειριζόμενης φιλοξενίας",[2454,6650,6651],{},"Vercel",[2454,6653,6654],{},"CopilotKit Cloud",[2454,6656,6657],{},"Thesys Cloud",[11,6659,6661],{"id":6660},"vercel-ai-sdk-η-επιλογή-full-stack","Vercel AI SDK: Η Επιλογή Full-Stack",[16,6663,4954,6664,6666],{},[44,6665,787],{}," του Vercel AI SDK είναι η πιο ισχυρή προσέγγιση — και η πιο «γνωμοδοτική». Μεταδίδει πραγματικά React Server Components από τον server, που σημαίνει ότι η έξοδος AI είναι πραγματικός κώδικας React που αποδίδεται από την πλευρά του server.",[3784,6668,6670],{"id":6669},"πώς-λειτουργεί","Πώς Λειτουργεί",[16,6672,6673],{},"Ορίζετε εργαλεία ως async generator συναρτήσεις που αποδίδουν καταστάσεις φόρτωσης και επιστρέφουν συστατικά React. Το SDK χειρίζεται τη σειριοποίηση του δέντρου συστατικών και τη μετάδοσή του με streaming στον client μέσω του πρωτοκόλλου RSC.",[37,6675,6677],{"className":39,"code":6676,"language":41,"meta":42,"style":42},"import { streamUI } from 'ai\u002Frsc';\n\nconst result = await streamUI({\n  model: openai('gpt-4o'),\n  prompt: 'Show revenue for Q1',\n  tools: {\n    revenueChart: {\n      description: 'Display a revenue chart',\n      parameters: z.object({ period: z.string(), data: z.array(...) }),\n      generate: async function* (params) {\n        yield \u003CChartSkeleton \u002F>;          \u002F\u002F άμεση κατάσταση φόρτωσης\n        return \u003CRevenueChart {...params} \u002F>; \u002F\u002F τελικό συστατικό\n      },\n    },\n  },\n});\n",[44,6678,6679,6691,6695,6709,6722,6732,6737,6742,6752,6776,6793,6809,6831,6835,6839,6843],{"__ignoreMap":42},[47,6680,6681,6683,6685,6687,6689],{"class":49,"line":50},[47,6682,61],{"class":60},[47,6684,816],{"class":64},[47,6686,68],{"class":60},[47,6688,821],{"class":71},[47,6690,75],{"class":64},[47,6692,6693],{"class":49,"line":57},[47,6694,157],{"emptyLinePlaceholder":156},[47,6696,6697,6699,6701,6703,6705,6707],{"class":49,"line":78},[47,6698,1420],{"class":60},[47,6700,1180],{"class":169},[47,6702,173],{"class":60},[47,6704,1185],{"class":60},[47,6706,1188],{"class":203},[47,6708,207],{"class":64},[47,6710,6711,6714,6716,6718,6720],{"class":49,"line":93},[47,6712,6713],{"class":64},"  model: ",[47,6715,1198],{"class":203},[47,6717,225],{"class":64},[47,6719,1203],{"class":71},[47,6721,231],{"class":64},[47,6723,6724,6727,6730],{"class":49,"line":108},[47,6725,6726],{"class":64},"  prompt: ",[47,6728,6729],{"class":71},"'Show revenue for Q1'",[47,6731,194],{"class":64},[47,6733,6734],{"class":49,"line":123},[47,6735,6736],{"class":64},"  tools: {\n",[47,6738,6739],{"class":49,"line":138},[47,6740,6741],{"class":64},"    revenueChart: {\n",[47,6743,6744,6747,6750],{"class":49,"line":153},[47,6745,6746],{"class":64},"      description: ",[47,6748,6749],{"class":71},"'Display a revenue chart'",[47,6751,194],{"class":64},[47,6753,6754,6757,6759,6762,6764,6767,6769,6771,6773],{"class":49,"line":160},[47,6755,6756],{"class":64},"      parameters: z.",[47,6758,204],{"class":203},[47,6760,6761],{"class":64},"({ period: z.",[47,6763,216],{"class":203},[47,6765,6766],{"class":64},"(), data: z.",[47,6768,341],{"class":203},[47,6770,225],{"class":64},[47,6772,1090],{"class":60},[47,6774,6775],{"class":64},") }),\n",[47,6777,6778,6781,6783,6785,6787,6789,6791],{"class":49,"line":179},[47,6779,6780],{"class":203},"      generate",[47,6782,951],{"class":64},[47,6784,954],{"class":60},[47,6786,957],{"class":60},[47,6788,960],{"class":64},[47,6790,963],{"class":873},[47,6792,971],{"class":64},[47,6794,6795,6798,6800,6803,6806],{"class":49,"line":185},[47,6796,6797],{"class":60},"        yield",[47,6799,979],{"class":64},[47,6801,6802],{"class":203},"ChartSkeleton",[47,6804,6805],{"class":64}," \u002F>;          ",[47,6807,6808],{"class":53},"\u002F\u002F άμεση κατάσταση φόρτωσης\n",[47,6810,6811,6814,6816,6819,6821,6823,6825,6828],{"class":49,"line":197},[47,6812,6813],{"class":60},"        return",[47,6815,979],{"class":64},[47,6817,6818],{"class":203},"RevenueChart",[47,6820,1087],{"class":64},[47,6822,1090],{"class":60},[47,6824,963],{"class":203},[47,6826,6827],{"class":64},"} \u002F>; ",[47,6829,6830],{"class":53},"\u002F\u002F τελικό συστατικό\n",[47,6832,6833],{"class":49,"line":210},[47,6834,1123],{"class":64},[47,6836,6837],{"class":49,"line":234},[47,6838,2987],{"class":64},[47,6840,6841],{"class":49,"line":253},[47,6842,307],{"class":64},[47,6844,6845],{"class":49,"line":273},[47,6846,6847],{"class":64},"});\n",[16,6849,6850,6851,6853,6854,6856],{},"Το μοτίβο ",[44,6852,4962],{}," \u002F ",[44,6855,4966],{}," είναι το χαρακτηριστικό γνώρισμα. Το skeleton εμφανίζεται αμέσως ενώ το AI επιλύει παραμέτρους. Όταν οι παράμετροι είναι έτοιμες, το πραγματικό συστατικό το αντικαθιστά — όλα σε μία streaming απόκριση.",[3784,6858,6860],{"id":6859},"πλεονεκτήματα","Πλεονεκτήματα",[3281,6862,6863,6871,6877,6883,6889],{},[2614,6864,6865,6325,6868,6870],{},[748,6866,6867],{},"Βαθύτερη ενσωμάτωση Next.js.",[44,6869,787],{}," είναι σχεδιασμένο γύρω από το App Router και το RSC. Αν κατασκευάζετε εφαρμογή Next.js, αυτή είναι η πιο ιδιωματική επιλογή.",[2614,6872,6873,6876],{},[748,6874,6875],{},"Αληθινή απόδοση server-side."," Τα παραγόμενα συστατικά αποδίδονται στον server, που σημαίνει ότι μπορούν να έχουν πρόσβαση σε βάσεις δεδομένων, συστήματα αρχείων και ιδιωτικά API απευθείας στις συναρτήσεις απόδοσής τους.",[2614,6878,6879,6882],{},[748,6880,6881],{},"Μεγαλύτερο οικοσύστημα."," 20M+ μηνιαίες λήψεις σημαίνουν άφθονα παραδείγματα, απαντήσεις στο Stack Overflow και υποστήριξη κοινότητας.",[2614,6884,6885,6888],{},[748,6886,6887],{},"Καλύτερη υποστήριξη TypeScript."," Οι τύποι του SDK είναι εξαντλητικοί. Παράμετροι εργαλείων, αποκρίσεις μοντέλων και τιμές streaming είναι όλες σωστά τυποποιημένες.",[2614,6890,6891,6894],{},[748,6892,6893],{},"Ευελιξία παρόχων."," Το SDK αφαιρεί την πολυπλοκότητα των παρόχων μοντέλων — αλλάξτε από OpenAI σε Anthropic ή Google αλλάζοντας ένα import.",[3784,6896,6898],{"id":6897},"μειονεκτήματα","Μειονεκτήματα",[3281,6900,6901,6909,6915,6921],{},[2614,6902,6903,6325,6906,6908],{},[748,6904,6905],{},"Εξάρτηση από Next.js.",[44,6907,787],{}," απαιτεί React Server Components. Λειτουργεί στο Next.js App Router. Η εκτέλεσή του εκτός αυτού του περιβάλλοντος απαιτεί σημαντικές προσπάθειες παρακαμπτήριας λύσης.",[2614,6910,6911,6914],{},[748,6912,6913],{},"Πολυπλοκότητα εντοπισμού σφαλμάτων RSC."," Όταν κάτι πάει στραβά σε ένα server component που μεταδίδεται, η εμπειρία εντοπισμού σφαλμάτων είναι χειρότερη από ένα κανονικό σφάλμα server. Τα μηνύματα σφαλμάτων μπορεί να είναι κρυπτογραφικά.",[2614,6916,6917,6920],{},[748,6918,6919],{},"Περιορισμοί server component."," Το RSC δεν μπορεί να χρησιμοποιεί hooks, browser APIs ή client-side κατάσταση απευθείας. Η διαδραστική συμπεριφορά απαιτεί προσεκτική κατανομή server και client συστατικών.",[2614,6922,6923,6926],{},[748,6924,6925],{},"Εγγύτητα με Vercel."," Ενώ το SDK λειτουργεί σε οποιαδήποτε πλατφόρμα που υποστηρίζει Node.js, ορισμένα χαρακτηριστικά είναι βελτιστοποιημένα για την υποδομή του Vercel.",[3784,6928,6930],{"id":6929},"πότε-να-επιλέξετε-vercel-ai-sdk","Πότε να Επιλέξετε Vercel AI SDK",[16,6932,6933],{},"Κατασκευάζετε νέα εφαρμογή Next.js. Θέλετε την πιο production-ready, με υψηλή απόδοση υλοποίηση Generative UI. Είστε εξοικειωμένοι με React Server Components και το App Router. Θέλετε τη μεγαλύτερη ποικιλία παραδειγμάτων κοινότητας.",[3428,6935],{},[11,6937,6939],{"id":6938},"copilotkit-η-επιλογή-ολοκλήρωσης","CopilotKit: Η Επιλογή Ολοκλήρωσης",[16,6941,6942,6943,6946],{},"Το CopilotKit ακολουθεί διαφορετική φιλοσοφία. Αντί να μεταδίδει συστατικά από τον server, παρέχει client-side συστατικά React που δημιουργούν εμπειρίες «copilot». Ρίξτε ",[44,6944,6945],{},"\u003CCopilotChat>"," στην υπάρχουσα εφαρμογή σας και έχετε ένα AI sidebar που μπορεί να διαβάζει και να τροποποιεί την κατάσταση της εφαρμογής σας.",[3784,6948,6670],{"id":6949},"πώς-λειτουργεί-1",[16,6951,6952],{},"Το CopilotKit εισάγει δύο κύρια primitives: ενέργειες και αναγνώσιμη κατάσταση. Ορίζετε τι μπορεί να κάνει και τι μπορεί να δει το AI, και το CopilotKit χειρίζεται τα υπόλοιπα.",[37,6954,6956],{"className":1388,"code":6955,"language":1390,"meta":42,"style":42},"import { CopilotKit, CopilotChat } from '@copilotkit\u002Freact-core';\nimport { useCopilotAction, useCopilotReadable } from '@copilotkit\u002Freact-core';\n\nfunction Dashboard() {\n  const [filters, setFilters] = useState({ period: 'month', metric: 'revenue' });\n\n  \u002F\u002F Επιτρέψτε στο AI να διαβάσει την τρέχουσα κατάσταση\n  useCopilotReadable({\n    description: 'Current dashboard filters',\n    value: filters,\n  });\n\n  \u002F\u002F Επιτρέψτε στο AI να τροποποιήσει τα φίλτρα\n  useCopilotAction({\n    name: 'updateFilters',\n    description: 'Update the dashboard view',\n    parameters: [\n      { name: 'period', type: 'string' },\n      { name: 'metric', type: 'string' },\n    ],\n    handler: ({ period, metric }) => setFilters({ period, metric }),\n  });\n\n  return (\n    \u003Cdiv className=\"flex\">\n      \u003CDashboardView filters={filters} \u002F>\n      \u003CCopilotChat instructions=\"Help the user explore their dashboard data.\" \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Περιτυλίξτε την εφαρμογή με CopilotKit\nfunction App() {\n  return (\n    \u003CCopilotKit runtimeUrl=\"\u002Fapi\u002Fcopilotkit\">\n      \u003CDashboard \u002F>\n    \u003C\u002FCopilotKit>\n  );\n}\n",[44,6957,6958,6972,6985,6989,6998,7033,7037,7042,7049,7058,7063,7067,7071,7076,7083,7093,7102,7107,7124,7137,7142,7168,7172,7176,7182,7197,7212,7229,7237,7241,7245,7249,7254,7263,7269,7285,7294,7302,7306],{"__ignoreMap":42},[47,6959,6960,6962,6965,6967,6970],{"class":49,"line":50},[47,6961,61],{"class":60},[47,6963,6964],{"class":64}," { CopilotKit, CopilotChat } ",[47,6966,68],{"class":60},[47,6968,6969],{"class":71}," '@copilotkit\u002Freact-core'",[47,6971,75],{"class":64},[47,6973,6974,6976,6979,6981,6983],{"class":49,"line":57},[47,6975,61],{"class":60},[47,6977,6978],{"class":64}," { useCopilotAction, useCopilotReadable } ",[47,6980,68],{"class":60},[47,6982,6969],{"class":71},[47,6984,75],{"class":64},[47,6986,6987],{"class":49,"line":78},[47,6988,157],{"emptyLinePlaceholder":156},[47,6990,6991,6993,6996],{"class":49,"line":93},[47,6992,865],{"class":60},[47,6994,6995],{"class":203}," Dashboard",[47,6997,1633],{"class":64},[47,6999,7000,7002,7004,7007,7009,7012,7014,7016,7018,7021,7024,7027,7030],{"class":49,"line":108},[47,7001,1177],{"class":60},[47,7003,3070],{"class":64},[47,7005,7006],{"class":169},"filters",[47,7008,660],{"class":64},[47,7010,7011],{"class":169},"setFilters",[47,7013,1572],{"class":64},[47,7015,1442],{"class":60},[47,7017,3085],{"class":203},[47,7019,7020],{"class":64},"({ period: ",[47,7022,7023],{"class":71},"'month'",[47,7025,7026],{"class":64},", metric: ",[47,7028,7029],{"class":71},"'revenue'",[47,7031,7032],{"class":64}," });\n",[47,7034,7035],{"class":49,"line":123},[47,7036,157],{"emptyLinePlaceholder":156},[47,7038,7039],{"class":49,"line":138},[47,7040,7041],{"class":53},"  \u002F\u002F Επιτρέψτε στο AI να διαβάσει την τρέχουσα κατάσταση\n",[47,7043,7044,7047],{"class":49,"line":153},[47,7045,7046],{"class":203},"  useCopilotReadable",[47,7048,207],{"class":64},[47,7050,7051,7053,7056],{"class":49,"line":160},[47,7052,188],{"class":64},[47,7054,7055],{"class":71},"'Current dashboard filters'",[47,7057,194],{"class":64},[47,7059,7060],{"class":49,"line":179},[47,7061,7062],{"class":64},"    value: filters,\n",[47,7064,7065],{"class":49,"line":185},[47,7066,1260],{"class":64},[47,7068,7069],{"class":49,"line":197},[47,7070,157],{"emptyLinePlaceholder":156},[47,7072,7073],{"class":49,"line":210},[47,7074,7075],{"class":53},"  \u002F\u002F Επιτρέψτε στο AI να τροποποιήσει τα φίλτρα\n",[47,7077,7078,7081],{"class":49,"line":234},[47,7079,7080],{"class":203},"  useCopilotAction",[47,7082,207],{"class":64},[47,7084,7085,7088,7091],{"class":49,"line":253},[47,7086,7087],{"class":64},"    name: ",[47,7089,7090],{"class":71},"'updateFilters'",[47,7092,194],{"class":64},[47,7094,7095,7097,7100],{"class":49,"line":273},[47,7096,188],{"class":64},[47,7098,7099],{"class":71},"'Update the dashboard view'",[47,7101,194],{"class":64},[47,7103,7104],{"class":49,"line":292},[47,7105,7106],{"class":64},"    parameters: [\n",[47,7108,7109,7112,7115,7118,7121],{"class":49,"line":298},[47,7110,7111],{"class":64},"      { name: ",[47,7113,7114],{"class":71},"'period'",[47,7116,7117],{"class":64},", type: ",[47,7119,7120],{"class":71},"'string'",[47,7122,7123],{"class":64}," },\n",[47,7125,7126,7128,7131,7133,7135],{"class":49,"line":304},[47,7127,7111],{"class":64},[47,7129,7130],{"class":71},"'metric'",[47,7132,7117],{"class":64},[47,7134,7120],{"class":71},[47,7136,7123],{"class":64},[47,7138,7139],{"class":49,"line":310},[47,7140,7141],{"class":64},"    ],\n",[47,7143,7144,7147,7150,7152,7154,7157,7160,7162,7165],{"class":49,"line":316},[47,7145,7146],{"class":203},"    handler",[47,7148,7149],{"class":64},": ({ ",[47,7151,2967],{"class":873},[47,7153,660],{"class":64},[47,7155,7156],{"class":873},"metric",[47,7158,7159],{"class":64}," }) ",[47,7161,920],{"class":60},[47,7163,7164],{"class":203}," setFilters",[47,7166,7167],{"class":64},"({ period, metric }),\n",[47,7169,7170],{"class":49,"line":326},[47,7171,1260],{"class":64},[47,7173,7174],{"class":49,"line":335},[47,7175,157],{"emptyLinePlaceholder":156},[47,7177,7178,7180],{"class":49,"line":351},[47,7179,887],{"class":60},[47,7181,1539],{"class":64},[47,7183,7184,7186,7188,7190,7192,7195],{"class":49,"line":362},[47,7185,1544],{"class":64},[47,7187,1646],{"class":1547},[47,7189,1649],{"class":203},[47,7191,1442],{"class":60},[47,7193,7194],{"class":71},"\"flex\"",[47,7196,1657],{"class":64},[47,7198,7199,7201,7204,7207,7209],{"class":49,"line":372},[47,7200,1662],{"class":64},[47,7202,7203],{"class":169},"DashboardView",[47,7205,7206],{"class":203}," filters",[47,7208,1442],{"class":60},[47,7210,7211],{"class":64},"{filters} \u002F>\n",[47,7213,7214,7216,7219,7222,7224,7227],{"class":49,"line":388},[47,7215,1662],{"class":64},[47,7217,7218],{"class":169},"CopilotChat",[47,7220,7221],{"class":203}," instructions",[47,7223,1442],{"class":60},[47,7225,7226],{"class":71},"\"Help the user explore their dashboard data.\"",[47,7228,1674],{"class":64},[47,7230,7231,7233,7235],{"class":49,"line":394},[47,7232,1709],{"class":64},[47,7234,1646],{"class":1547},[47,7236,1657],{"class":64},[47,7238,7239],{"class":49,"line":414},[47,7240,1133],{"class":64},[47,7242,7243],{"class":49,"line":428},[47,7244,1138],{"class":64},[47,7246,7247],{"class":49,"line":433},[47,7248,157],{"emptyLinePlaceholder":156},[47,7250,7251],{"class":49,"line":439},[47,7252,7253],{"class":53},"\u002F\u002F Περιτυλίξτε την εφαρμογή με CopilotKit\n",[47,7255,7256,7258,7261],{"class":49,"line":444},[47,7257,865],{"class":60},[47,7259,7260],{"class":203}," App",[47,7262,1633],{"class":64},[47,7264,7265,7267],{"class":49,"line":450},[47,7266,887],{"class":60},[47,7268,1539],{"class":64},[47,7270,7271,7273,7275,7278,7280,7283],{"class":49,"line":460},[47,7272,1544],{"class":64},[47,7274,6529],{"class":169},[47,7276,7277],{"class":203}," runtimeUrl",[47,7279,1442],{"class":60},[47,7281,7282],{"class":71},"\"\u002Fapi\u002Fcopilotkit\"",[47,7284,1657],{"class":64},[47,7286,7287,7289,7292],{"class":49,"line":469},[47,7288,1662],{"class":64},[47,7290,7291],{"class":169},"Dashboard",[47,7293,1674],{"class":64},[47,7295,7296,7298,7300],{"class":49,"line":479},[47,7297,1709],{"class":64},[47,7299,6529],{"class":169},[47,7301,1657],{"class":64},[47,7303,7304],{"class":49,"line":504},[47,7305,1133],{"class":64},[47,7307,7308],{"class":49,"line":518},[47,7309,1138],{"class":64},[16,7311,7312],{},"Το μοτίβο copilot είναι διακριτό: το AI είναι ένας βοηθός sidebar που αλληλεπιδρά με την υπάρχουσα διεπαφή, αντί να παράγει νέα διεπαφή από μηδέν.",[3784,7314,6860],{"id":7315},"πλεονεκτήματα-1",[3281,7317,7318,7324,7330,7336,7350],{},[2614,7319,7320,7323],{},[748,7321,7322],{},"Ταχύτερος χρόνος ολοκλήρωσης."," Η προσθήκη ενός AI sidebar σε μια υπάρχουσα εφαρμογή React διαρκεί ώρες, όχι μέρες. Τα συστατικά λειτουργούν χωρίς πρόσθετη διαμόρφωση.",[2614,7325,7326,7329],{},[748,7327,7328],{},"Λειτουργεί με οποιαδήποτε ρύθμιση React."," Create React App, Vite, Remix, Next.js — το CopilotKit δεν απαιτεί RSC ή συγκεκριμένο bundler.",[2614,7331,7332,7335],{},[748,7333,7334],{},"Φυσικό μοτίβο copilot."," Το AI sidebar που βοηθά με την υπάρχουσα διεπαφή είναι ένα καλά κατανοητό μοτίβο που οι χρήστες καταλαβαίνουν αμέσως.",[2614,7337,7338,7341,7342,7345,7346,7349],{},[748,7339,7340],{},"Ενσωματωμένος συγχρονισμός κατάστασης."," Τα ",[44,7343,7344],{},"useCopilotReadable"," και ",[44,7347,7348],{},"useCopilotAction"," δημιουργούν μια σαφή αμφίδρομη σύμβαση μεταξύ της εφαρμογής σας και του AI.",[2614,7351,7352,7355,7356,7358],{},[748,7353,7354],{},"Ισχυρή προεπιλεγμένη διεπαφή."," Το συστατικό ",[44,7357,6945],{}," είναι production-quality και προσαρμόσιμο χωρίς να χρειάζεται να κατασκευάσετε τη δική σας διεπαφή chat.",[3784,7360,6898],{"id":7361},"μειονεκτήματα-1",[3281,7363,7364,7370,7376,7382],{},[2614,7365,7366,7369],{},[748,7367,7368],{},"Μοντέλο απόδοσης client-side."," Το CopilotKit αποδίδει την έξοδο AI στον client. Δεν υπάρχει SSR για παραγόμενα συστατικά, που επηρεάζει την απόδοση και το SEO για δημόσιο περιεχόμενο.",[2614,7371,7372,7375],{},[748,7373,7374],{},"Το μοτίβο copilot δεν είναι καθολικό."," Αν η περίπτωση χρήσης σας δεν είναι «AI sidebar που βοηθά με την κύρια διεπαφή», το CopilotKit απαιτεί περισσότερη προσαρμογή.",[2614,7377,7378,7381],{},[748,7379,7380],{},"Λιγότερος έλεγχος στη ροή απόδοσης."," Για σύνθετη προσαρμοσμένη παραγωγή συστατικών, το Vercel AI SDK δίνει μεγαλύτερη ευελιξία.",[2614,7383,7384,7387],{},[748,7385,7386],{},"Μέγεθος bundle."," Η απόδοση client-side σημαίνει ότι η βιβλιοθήκη συστατικών αποστέλλεται στο πρόγραμμα περιήγησης. Για εφαρμογές ευαίσθητες στην απόδοση, αυτό απαιτεί προσοχή.",[3784,7389,7391],{"id":7390},"πότε-να-επιλέξετε-copilotkit","Πότε να Επιλέξετε CopilotKit",[16,7393,7394],{},"Έχετε υπάρχουσα εφαρμογή React και θέλετε να προσθέσετε γρήγορα λειτουργίες με AI. Το μοτίβο copilot — ένα AI sidebar που μπορεί να διαβάζει και να τροποποιεί την κύρια διεπαφή — ταιριάζει στο προϊόν σας. Δεν θέλετε να ασχοληθείτε με RSC.",[3428,7396],{},[11,7398,7400],{"id":7399},"thesys-json-render-η-καθολική-επιλογή","Thesys (json-render): Η Καθολική Επιλογή",[16,7402,7403],{},"Το Thesys, που κυκλοφόρησε τον Ιανουάριο 2026 και έχει ήδη 13K αστέρια GitHub, ακολουθεί την πιο αγνωστική ως προς το framework προσέγγιση. Τα μοντέλα AI εξάγουν JSON που περιγράφει ένα δέντρο συστατικών διεπαφής, και ένας renderer μετατρέπει αυτό το JSON σε διαδραστικά συστατικά.",[3784,7405,6670],{"id":7406},"πώς-λειτουργεί-2",[16,7408,7409],{},"Το AI εξάγει JSON αντί να ενεργοποιεί κλήσεις εργαλείων React. Αυτό το JSON περιγράφει μια ιεραρχία συστατικών, και ο renderer Thesys το ερμηνεύει:",[37,7411,7413],{"className":39,"code":7412,"language":41,"meta":42,"style":42},"\u002F\u002F Το AI εξάγει κάτι σαν αυτό:\nconst aiOutput = {\n  type: \"layout\",\n  direction: \"grid\",\n  columns: 2,\n  children: [\n    {\n      type: \"MetricCard\",\n      props: {\n        label: \"Monthly Revenue\",\n        value: \"$84,200\",\n        change: 12.4,\n        period: \"vs last month\"\n      }\n    },\n    {\n      type: \"AlertBanner\",\n      props: {\n        type: \"info\",\n        title: \"New record\",\n        message: \"Best month in company history\"\n      }\n    }\n  ]\n};\n\n\u002F\u002F Ο renderer μετατρέπει το JSON σε διεπαφή\nimport { render } from '@thesys\u002Fjson-render';\nconst ui = render(aiOutput, componentRegistry);\n",[44,7414,7415,7420,7431,7441,7451,7460,7465,7470,7480,7485,7495,7505,7515,7523,7528,7532,7536,7545,7549,7559,7569,7577,7581,7585,7590,7594,7598,7603,7617],{"__ignoreMap":42},[47,7416,7417],{"class":49,"line":50},[47,7418,7419],{"class":53},"\u002F\u002F Το AI εξάγει κάτι σαν αυτό:\n",[47,7421,7422,7424,7427,7429],{"class":49,"line":57},[47,7423,1420],{"class":60},[47,7425,7426],{"class":169}," aiOutput",[47,7428,173],{"class":60},[47,7430,176],{"class":64},[47,7432,7433,7436,7439],{"class":49,"line":78},[47,7434,7435],{"class":64},"  type: ",[47,7437,7438],{"class":71},"\"layout\"",[47,7440,194],{"class":64},[47,7442,7443,7446,7449],{"class":49,"line":93},[47,7444,7445],{"class":64},"  direction: ",[47,7447,7448],{"class":71},"\"grid\"",[47,7450,194],{"class":64},[47,7452,7453,7456,7458],{"class":49,"line":108},[47,7454,7455],{"class":64},"  columns: ",[47,7457,4177],{"class":169},[47,7459,194],{"class":64},[47,7461,7462],{"class":49,"line":123},[47,7463,7464],{"class":64},"  children: [\n",[47,7466,7467],{"class":49,"line":138},[47,7468,7469],{"class":64},"    {\n",[47,7471,7472,7475,7478],{"class":49,"line":153},[47,7473,7474],{"class":64},"      type: ",[47,7476,7477],{"class":71},"\"MetricCard\"",[47,7479,194],{"class":64},[47,7481,7482],{"class":49,"line":160},[47,7483,7484],{"class":64},"      props: {\n",[47,7486,7487,7490,7493],{"class":49,"line":179},[47,7488,7489],{"class":64},"        label: ",[47,7491,7492],{"class":71},"\"Monthly Revenue\"",[47,7494,194],{"class":64},[47,7496,7497,7500,7503],{"class":49,"line":185},[47,7498,7499],{"class":64},"        value: ",[47,7501,7502],{"class":71},"\"$84,200\"",[47,7504,194],{"class":64},[47,7506,7507,7510,7513],{"class":49,"line":197},[47,7508,7509],{"class":64},"        change: ",[47,7511,7512],{"class":169},"12.4",[47,7514,194],{"class":64},[47,7516,7517,7520],{"class":49,"line":210},[47,7518,7519],{"class":64},"        period: ",[47,7521,7522],{"class":71},"\"vs last month\"\n",[47,7524,7525],{"class":49,"line":234},[47,7526,7527],{"class":64},"      }\n",[47,7529,7530],{"class":49,"line":253},[47,7531,2987],{"class":64},[47,7533,7534],{"class":49,"line":273},[47,7535,7469],{"class":64},[47,7537,7538,7540,7543],{"class":49,"line":292},[47,7539,7474],{"class":64},[47,7541,7542],{"class":71},"\"AlertBanner\"",[47,7544,194],{"class":64},[47,7546,7547],{"class":49,"line":298},[47,7548,7484],{"class":64},[47,7550,7551,7554,7557],{"class":49,"line":304},[47,7552,7553],{"class":64},"        type: ",[47,7555,7556],{"class":71},"\"info\"",[47,7558,194],{"class":64},[47,7560,7561,7564,7567],{"class":49,"line":310},[47,7562,7563],{"class":64},"        title: ",[47,7565,7566],{"class":71},"\"New record\"",[47,7568,194],{"class":64},[47,7570,7571,7574],{"class":49,"line":316},[47,7572,7573],{"class":64},"        message: ",[47,7575,7576],{"class":71},"\"Best month in company history\"\n",[47,7578,7579],{"class":49,"line":326},[47,7580,7527],{"class":64},[47,7582,7583],{"class":49,"line":335},[47,7584,6294],{"class":64},[47,7586,7587],{"class":49,"line":351},[47,7588,7589],{"class":64},"  ]\n",[47,7591,7592],{"class":49,"line":362},[47,7593,717],{"class":64},[47,7595,7596],{"class":49,"line":372},[47,7597,157],{"emptyLinePlaceholder":156},[47,7599,7600],{"class":49,"line":388},[47,7601,7602],{"class":53},"\u002F\u002F Ο renderer μετατρέπει το JSON σε διεπαφή\n",[47,7604,7605,7607,7610,7612,7615],{"class":49,"line":394},[47,7606,61],{"class":60},[47,7608,7609],{"class":64}," { render } ",[47,7611,68],{"class":60},[47,7613,7614],{"class":71}," '@thesys\u002Fjson-render'",[47,7616,75],{"class":64},[47,7618,7619,7621,7623,7625,7628],{"class":49,"line":414},[47,7620,1420],{"class":60},[47,7622,5319],{"class":169},[47,7624,173],{"class":60},[47,7626,7627],{"class":203}," render",[47,7629,7630],{"class":64},"(aiOutput, componentRegistry);\n",[16,7632,7633],{},"Το σχήμα JSON είναι το αντικείμενο-αποτέλεσμα. Μπορεί να καταγραφεί, να αποθηκευτεί στην κρυφή μνήμη, να αναπαραχθεί και να αποδοθεί σε οποιαδήποτε πλατφόρμα που διαθέτει renderer Thesys.",[3784,7635,6860],{"id":7636},"πλεονεκτήματα-2",[3281,7638,7639,7645,7651,7657,7663],{},[2614,7640,7641,7644],{},[748,7642,7643],{},"Αγνωστικό ως προς το framework."," Το ίδιο σχήμα JSON αποδίδεται σε React, Vue, Angular ή native mobile. Μία απόκριση AI, πολλοί renderers.",[2614,7646,7647,7650],{},[748,7648,7649],{},"Εύκολος εντοπισμός σφαλμάτων."," Η έξοδος JSON είναι μια απλή δομή δεδομένων που μπορείτε να επιθεωρήσετε σε οποιοδήποτε πρόγραμμα προβολής JSON. Ο εντοπισμός «γιατί το AI παρήγαγε αυτό;» είναι απλός.",[2614,7652,7653,7656],{},[748,7654,7655],{},"Αποθηκεύσιμο στην κρυφή μνήμη."," Αποθηκεύστε βάσει hash prompt και η απόκριση AI επαναχρησιμοποιείται χωρίς κόστος inference. Αυτό είναι δυσκολότερο με RSC streaming.",[2614,7658,7659,7662],{},[748,7660,7661],{},"Επιθεωρήσιμο ιστορικό."," Η αποθήκευση παραγόμενης διεπαφής ως JSON σημαίνει ότι μπορείτε να ελέγξετε ακριβώς τι εμφανίστηκε στους χρήστες, επαναλαμβάνοντας οποιαδήποτε αλληλεπίδραση.",[2614,7664,7665,7668],{},[748,7666,7667],{},"Απλούστερο εννοιολογικό μοντέλο."," JSON μέσα, διεπαφή έξω. Η αφαίρεση είναι εύκολο να εξηγηθεί σε προγραμματιστές που δεν γνωρίζουν React.",[3784,7670,6898],{"id":7671},"μειονεκτήματα-2",[3281,7673,7674,7680,7686,7692],{},[2614,7675,7676,7679],{},[748,7677,7678],{},"Νεότερο έργο."," Το Thesys κυκλοφόρησε τον Ιανουάριο 2026. Λιγότερο δοκιμασμένο σε παραγωγή από τις εναλλακτικές. Οι αλλαγές που σπάνε συμβατότητα είναι πιο πιθανές.",[2614,7681,7682,7685],{},[748,7683,7684],{},"Η αφαίρεση JSON περιορίζει τη διαδραστικότητα."," Σύνθετα διαδραστικά μοτίβα — φόρμες με λογική επικύρωσης, real-time δεδομένα, κινούμενες μεταβάσεις — είναι δυσκολότερο να εκφραστούν σε σχήμα JSON από ό,τι σε κώδικα React.",[2614,7687,7688,7691],{},[748,7689,7690],{},"Απόδοση client-side."," Όπως το CopilotKit, η απόδοση γίνεται στον client. Δεν υπάρχει SSR.",[2614,7693,7694,7697],{},[748,7695,7696],{},"Μικρότερη κοινότητα."," 13K αστέρια σε 3 μήνες είναι εντυπωσιακή ανάπτυξη, αλλά η κοινότητα είναι ένα κλάσμα του μεγέθους του Vercel AI SDK.",[3784,7699,7701],{"id":7700},"πότε-να-επιλέξετε-thesys","Πότε να Επιλέξετε Thesys",[16,7703,7704],{},"Το έργο σας χρησιμοποιεί πολλαπλά frontend frameworks ή χρειάζεται να υποστηρίξει mobile clients. Εκτιμάτε τη δυνατότητα επιθεώρησης, αποθήκευσης στην κρυφή μνήμη και αναπαραγωγής παραγόμενων διεπαφών. Θέλετε ένα απλούστερο εννοιολογικό μοντέλο. Είστε άνετοι να υιοθετείτε νωρίς.",[3428,7706],{},[11,7708,7710],{"id":7709},"εκτιμήσεις-μετανάστευσης","Εκτιμήσεις Μετανάστευσης",[16,7712,7713],{},"Η αλλαγή framework αργότερα δεν είναι δωρεάν, αλλά είναι λιγότερο ακριβή από ό,τι φαίνεται.",[16,7715,7716],{},[748,7717,7718],{},"Τι μεταφέρεται:",[3281,7720,7721,7724,7727],{},[2614,7722,7723],{},"Η βιβλιοθήκη συστατικών σας (καθαρή React, χωρίς εξάρτηση framework)",[2614,7725,7726],{},"Τα system prompts και οι περιγραφές εργαλείων σας",[2614,7728,7729],{},"Τα σχήματα Zod για παραμέτρους εργαλείων",[16,7731,7732],{},[748,7733,7734],{},"Τι απαιτεί ανάγραφη:",[3281,7736,7737,7740,7743],{},[2614,7738,7739],{},"Η δομή server action \u002F API endpoint",[2614,7741,7742],{},"Ο κώδικας ολοκλήρωσης streaming",[2614,7744,7745],{},"Η ρύθμιση απόδοσης client-side",[16,7747,7748],{},"Υπολογίστε 2–5 ημέρες για μετανάστευση μεταξύ frameworks για μια λειτουργία μέτριας πολυπλοκότητας. Η βιβλιοθήκη συστατικών — συνήθως η μεγαλύτερη επένδυση — μεταφέρεται χωρίς αλλαγές.",[11,7750,7752],{"id":7751},"μήτρα-σύστασης","Μήτρα Σύστασης",[16,7754,7755,7758],{},[748,7756,7757],{},"Ξεκινάτε νέα εφαρμογή Next.js από μηδέν:"," Vercel AI SDK. Αδιαμφισβήτητο για καθαρές κατασκευές Next.js.",[16,7760,7761,7764],{},[748,7762,7763],{},"Προσθέτετε AI σε υπάρχουσα εφαρμογή React:"," CopilotKit. Ταχύτερος χρόνος-σε-αξία για την περίπτωση ολοκλήρωσης.",[16,7766,7767,7770],{},[748,7768,7769],{},"Multi-framework ή stack εκτός React:"," Thesys. Η μόνη πρακτική επιλογή για αγνωστικές ανάγκες framework.",[16,7772,7773,7776],{},[748,7774,7775],{},"Αναποφάσιστοι και θέλετε να αρχίσετε εξερεύνηση:"," Vercel AI SDK. Η μεγαλύτερη κοινότητα σημαίνει τα περισσότερα παραδείγματα και τις περισσότερες απαντήσεις.",[16,7778,7779,7782],{},[748,7780,7781],{},"Στοιχηματίζετε στο μέλλον των AI διεπαφών:"," Παρακολουθήστε και τα τρία. Ο χώρος κινείται γρήγορα και οι νικητές του σήμερα μπορεί να μην είναι νικητές σε 18 μήνες.",[16,7784,7785],{},"Ακόμη μια αρχή που αξίζει να αναφερθεί ρητά: μην επενδύετε υπερβολικά στην επιλογή framework νωρίς. Τα συστατικά που κατασκευάζετε είναι το πολύτιμο, ανθεκτικό αποτέλεσμα. Το framework είναι η υδραυλική εγκατάσταση. Κατασκευάστε εξαιρετικά συστατικά, διατηρήστε τα καθαρά από κώδικα ειδικό για framework, και μπορείτε να αλλάξετε framework σε μια εβδομάδα αν χρειαστεί.",[3428,7787],{},[16,7789,7790],{},[757,7791,7792,7793,7796],{},"Κατασκευάζετε με ένα από αυτά τα frameworks και χρειάζεστε καθοδήγηση; ",[779,7794,7795],{"href":3437},"Κλείστε συνεδρία"," — έχω παραγωγική εμπειρία και με τα τρία.",[3441,7798,7799],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":42,"searchDepth":57,"depth":57,"links":7801},[7802,7803,7804,7810,7816,7822,7823],{"id":6503,"depth":57,"text":6504},{"id":6513,"depth":57,"text":6514},{"id":6660,"depth":57,"text":6661,"children":7805},[7806,7807,7808,7809],{"id":6669,"depth":78,"text":6670},{"id":6859,"depth":78,"text":6860},{"id":6897,"depth":78,"text":6898},{"id":6929,"depth":78,"text":6930},{"id":6938,"depth":57,"text":6939,"children":7811},[7812,7813,7814,7815],{"id":6949,"depth":78,"text":6670},{"id":7315,"depth":78,"text":6860},{"id":7361,"depth":78,"text":6898},{"id":7390,"depth":78,"text":7391},{"id":7399,"depth":57,"text":7400,"children":7817},[7818,7819,7820,7821],{"id":7406,"depth":78,"text":6670},{"id":7636,"depth":78,"text":6860},{"id":7671,"depth":78,"text":6898},{"id":7700,"depth":78,"text":7701},{"id":7709,"depth":57,"text":7710},{"id":7751,"depth":57,"text":7752},"framework-guide","2026-02-14","Μια ειλικρινής σύγκριση των τριών κύριων frameworks Generative UI, με πλεονεκτήματα, μειονεκτήματα και πότε να χρησιμοποιείτε το καθένα.",{"audit_status":7828,"audit_date":3469},"ship-with-revisions-applied","\u002Fel\u002Flearn\u002Fcopilotkit-vs-vercel-ai-sdk-vs-thesys","14 λεπτά ανάγνωσης",{"title":6498,"description":7826},"el\u002Flearn\u002Fcopilotkit-vs-vercel-ai-sdk-vs-thesys",[7834,6493,7835,7836,7837],"copilotkit","thesys","comparison","frameworks","HKhE72Z5cmtrSdCUlDrDY69n6nuyIyPmGZ3j3LCcWAQ",{"id":7840,"title":7841,"author":6,"body":7842,"category":3462,"date":10131,"description":10132,"draft":3465,"extension":3466,"featured":3465,"meta":10133,"navigation":156,"path":10134,"readTime":10135,"seo":10136,"stem":10137,"tags":10138,"__hash__":10142},"content\u002Fel\u002Flearn\u002Fgenerative-ui-accessibility-guide.md","Προσβασιμότητα σε Generative UI: Δημιουργία Συμπεριληπτικών AI Διεπαφών",{"type":8,"value":7843,"toc":10117},[7844,7848,7851,7854,7857,7860,7867,7870,7874,7877,7899,7965,7975,7989,8174,8180,8186,8190,8193,8200,8338,8341,8370,8373,8464,8473,8477,8480,8485,8718,8727,8748,8753,8756,8760,8763,8773,8783,9116,9127,9131,9134,9207,9216,9236,9244,9248,9251,9254,9439,9442,9549,9553,9560,9566,9586,9603,9615,9622,9626,9629,9632,9635,9679,9682,9686,9689,9695,9701,9707,9713,9809,9812,9817,9858,9861,9865,9868,9881,10014,10023,10029,10035,10039,10042,10089,10092,10104,10106,10114],[11,7845,7847],{"id":7846},"γιατί-η-προσβασιμότητα-είναι-δυσκολότερη-στο-generative-ui","Γιατί η Προσβασιμότητα Είναι Δυσκολότερη στο Generative UI",[16,7849,7850],{},"Η ομάδα προσβασιμότητάς σας μόλις υπέγραψε τον έλεγχο κάθε οθόνης του προϊόντος. Τρεις εβδομάδες αργότερα, το AI συναρμολογεί μια διάταξη που κανένας σχεδιαστής δεν σχεδίασε — και σε αυτή τη διάταξη η ιεραρχία επικεφαλίδων σπάει για τα screen readers, ένα παράθυρο διαλόγου που παράγεται έχει παγίδα εστίασης, και σε ένα γράφημα το χρώμα παραμένει το μόνο σήμα. Τίποτα από αυτά δεν εντοπίστηκε στον έλεγχο, γιατί τίποτα από αυτά δεν υπήρχε τότε.",[16,7852,7853],{},"Αυτή είναι μια νέα επιφάνεια προσβασιμότητας, και το παλιό εγχειρίδιο δεν την καλύπτει.",[16,7855,7856],{},"Σε ένα παραδοσιακό UI, ένας μηχανικός μπορεί να ελέγξει κάθε οθόνη ως προς τις απαιτήσεις WCAG 2.2. Ο αριθμός των οθονών είναι πεπερασμένος. Η ομάδα προσβασιμότητας (a11y) γνωρίζει ακριβώς τι να δοκιμάσει.",[16,7858,7859],{},"Το Generative UI σπάει αυτό το μοντέλο. Το σύνολο των πιθανών interfaces δεν είναι απαριθμήσιμο — το AI μπορεί να συνθέσει συστατικά με τρόπους που κανένας άνθρωπος δεν σχεδίασε ρητά. Μια οθόνη που περνά τον έλεγχο προσβασιμότητας σήμερα μπορεί αύριο να συνδυαστεί με ένα νέο συστατικό και να παράγει μια μη προσβάσιμη διάταξη.",[16,7861,7862,7863,7866],{},"Η λύση είναι να μεταφέρετε τις απαιτήσεις προσβασιμότητας στο επίπεδο του μεμονωμένου συστατικού. Αν κάθε συστατικό στη βιβλιοθήκη σας είναι ξεχωριστά προσβάσιμο, κάθε σύνθεσή τους θα είναι επίσης προσβάσιμη — ",[757,7864,7865],{},"υπό την προϋπόθεση ότι η σύνθεση η ίδια είναι σωστά δομημένη",". Αυτή η επιφύλαξη είναι σημαντική· θα επιστρέψουμε σε αυτήν στην ενότητα «Συνδυαστικά Προβλήματα Προσβασιμότητας», γιατί εκεί ζουν τα περισσότερα πραγματικά σφάλματα a11y σε γεννητικά συστήματα.",[16,7868,7869],{},"Αυτό είναι στην πραγματικότητα ένα πιο καθαρό μοντέλο από τον χειροκίνητο έλεγχο κάθε οθόνης. Και δεν είναι προαιρετικό: το AI δεν θα προσθέσει ετικέτες ARIA ούτε θα διαχειριστεί την εστίαση για εσάς. Η βιβλιοθήκη συστατικών είναι ο μόνος σας μοχλός.",[11,7871,7873],{"id":7872},"βασικές-απαιτήσεις-σε-επίπεδο-συστατικού","Βασικές Απαιτήσεις σε Επίπεδο Συστατικού",[16,7875,7876],{},"Κάθε συστατικό στο registry εργαλείων Generative UI πρέπει να πληροί τις παρακάτω απαιτήσεις ανεξάρτητα:",[16,7878,7879,7882,7883,7886,7887,7890,7891,7894,7895,7898],{},[748,7880,7881],{},"Σημαντική HTML πρώτα."," Χρησιμοποιήστε ",[44,7884,7885],{},"\u003Cbutton>"," για κουμπιά, ",[44,7888,7889],{},"\u003Cnav>"," για πλοήγηση, ",[44,7892,7893],{},"\u003Ctable>"," για δεδομένα πίνακα. Μην χρησιμοποιείτε ",[44,7896,7897],{},"\u003Cdiv onClick={...}>"," όταν ένα σημαντικό στοιχείο ταιριάζει.",[37,7900,7902],{"className":1388,"code":7901,"language":1390,"meta":42,"style":42},"\u002F\u002F Λάθος: div που μεταμφιέζεται σε κουμπί\n\u003Cdiv className=\"button\" onClick={handleClick}>Submit\u003C\u002Fdiv>\n\n\u002F\u002F Σωστό: πραγματικό στοιχείο button\n\u003Cbutton type=\"button\" onClick={handleClick}>Submit\u003C\u002Fbutton>\n",[44,7903,7904,7909,7934,7938,7943],{"__ignoreMap":42},[47,7905,7906],{"class":49,"line":50},[47,7907,7908],{"class":53},"\u002F\u002F Λάθος: div που μεταμφιέζεται σε κουμπί\n",[47,7910,7911,7913,7915,7917,7919,7922,7925,7927,7930,7932],{"class":49,"line":57},[47,7912,1103],{"class":64},[47,7914,1646],{"class":1547},[47,7916,1649],{"class":203},[47,7918,1442],{"class":60},[47,7920,7921],{"class":71},"\"button\"",[47,7923,7924],{"class":203}," onClick",[47,7926,1442],{"class":60},[47,7928,7929],{"class":64},"{handleClick}>Submit\u003C\u002F",[47,7931,1646],{"class":1547},[47,7933,1657],{"class":64},[47,7935,7936],{"class":49,"line":78},[47,7937,157],{"emptyLinePlaceholder":156},[47,7939,7940],{"class":49,"line":93},[47,7941,7942],{"class":53},"\u002F\u002F Σωστό: πραγματικό στοιχείο button\n",[47,7944,7945,7947,7949,7951,7953,7955,7957,7959,7961,7963],{"class":49,"line":108},[47,7946,1103],{"class":64},[47,7948,1947],{"class":1547},[47,7950,730],{"class":203},[47,7952,1442],{"class":60},[47,7954,7921],{"class":71},[47,7956,7924],{"class":203},[47,7958,1442],{"class":60},[47,7960,7929],{"class":64},[47,7962,1947],{"class":1547},[47,7964,1657],{"class":64},[16,7966,7967,7970,7971,7974],{},[748,7968,7969],{},"Όλες οι εικόνες έχουν alt κείμενο."," Για διακοσμητικές εικόνες: ",[44,7972,7973],{},"alt=\"\"",". Για ενημερωτικές εικόνες, γράψτε μια περιγραφή.",[16,7976,7977,7980,7981,7984,7985,7988],{},[748,7978,7979],{},"Το χρώμα δεν είναι το μόνο σήμα."," Ένα γράφημα που εμφανίζει θετικές τιμές με πράσινο και αρνητικές με κόκκινο χρειάζεται πρόσθετη ένδειξη για χρήστες που δεν διακρίνουν αυτά τα χρώματα — ένα σύμβολο ",[44,7982,7983],{},"+","\u002F",[44,7986,7987],{},"-",", ένα εικονίδιο ή μια ετικέτα κειμένου.",[37,7990,7992],{"className":1388,"code":7991,"language":1390,"meta":42,"style":42},"function TrendIndicator({ value }: { value: number }) {\n  const isPositive = value >= 0;\n  return (\n    \u003Cspan\n      className={isPositive ? 'text-green-600' : 'text-red-600'}\n      aria-label={isPositive ? `Up ${Math.abs(value)}%` : `Down ${Math.abs(value)}%`}\n    >\n      {\u002F* Το εικονίδιο παρέχει οπτικό σήμα πέρα από το χρώμα *\u002F}\n      {isPositive ? '↑' : '↓'} {Math.abs(value)}%\n    \u003C\u002Fspan>\n  );\n}\n",[44,7993,7994,8020,8037,8043,8050,8069,8121,8126,8135,8158,8166,8170],{"__ignoreMap":42},[47,7995,7996,7998,8001,8003,8006,8008,8010,8012,8014,8016,8018],{"class":49,"line":50},[47,7997,865],{"class":60},[47,7999,8000],{"class":203}," TrendIndicator",[47,8002,1513],{"class":64},[47,8004,8005],{"class":873},"value",[47,8007,1518],{"class":64},[47,8009,877],{"class":60},[47,8011,1523],{"class":64},[47,8013,8005],{"class":873},[47,8015,877],{"class":60},[47,8017,3687],{"class":169},[47,8019,1532],{"class":64},[47,8021,8022,8024,8026,8028,8031,8033,8035],{"class":49,"line":57},[47,8023,1177],{"class":60},[47,8025,4020],{"class":169},[47,8027,173],{"class":60},[47,8029,8030],{"class":64}," value ",[47,8032,4028],{"class":60},[47,8034,4031],{"class":169},[47,8036,75],{"class":64},[47,8038,8039,8041],{"class":49,"line":78},[47,8040,887],{"class":60},[47,8042,1539],{"class":64},[47,8044,8045,8047],{"class":49,"line":93},[47,8046,1544],{"class":64},[47,8048,8049],{"class":1547},"span\n",[47,8051,8052,8054,8056,8059,8061,8063,8065,8067],{"class":49,"line":108},[47,8053,1553],{"class":203},[47,8055,1442],{"class":60},[47,8057,8058],{"class":64},"{isPositive ",[47,8060,4048],{"class":60},[47,8062,4075],{"class":71},[47,8064,4054],{"class":60},[47,8066,4080],{"class":71},[47,8068,1138],{"class":64},[47,8070,8071,8073,8075,8077,8079,8082,8085,8087,8090,8092,8094,8097,8100,8102,8105,8107,8109,8111,8113,8115,8117,8119],{"class":49,"line":123},[47,8072,1585],{"class":203},[47,8074,1442],{"class":60},[47,8076,8058],{"class":64},[47,8078,4048],{"class":60},[47,8080,8081],{"class":71}," `Up ${",[47,8083,8084],{"class":64},"Math",[47,8086,1999],{"class":71},[47,8088,8089],{"class":203},"abs",[47,8091,225],{"class":71},[47,8093,8005],{"class":64},[47,8095,8096],{"class":71},")",[47,8098,8099],{"class":71},"}%`",[47,8101,4054],{"class":60},[47,8103,8104],{"class":71}," `Down ${",[47,8106,8084],{"class":64},[47,8108,1999],{"class":71},[47,8110,8089],{"class":203},[47,8112,225],{"class":71},[47,8114,8005],{"class":64},[47,8116,8096],{"class":71},[47,8118,8099],{"class":71},[47,8120,1138],{"class":64},[47,8122,8123],{"class":49,"line":138},[47,8124,8125],{"class":64},"    >\n",[47,8127,8128,8130,8133],{"class":49,"line":153},[47,8129,2306],{"class":64},[47,8131,8132],{"class":53},"\u002F* Το εικονίδιο παρέχει οπτικό σήμα πέρα από το χρώμα *\u002F",[47,8134,1138],{"class":64},[47,8136,8137,8140,8142,8145,8147,8150,8153,8155],{"class":49,"line":160},[47,8138,8139],{"class":64},"      {isPositive ",[47,8141,4048],{"class":60},[47,8143,8144],{"class":71}," '↑'",[47,8146,4054],{"class":60},[47,8148,8149],{"class":71}," '↓'",[47,8151,8152],{"class":64},"} {Math.",[47,8154,8089],{"class":203},[47,8156,8157],{"class":64},"(value)}%\n",[47,8159,8160,8162,8164],{"class":49,"line":179},[47,8161,1709],{"class":64},[47,8163,47],{"class":1547},[47,8165,1657],{"class":64},[47,8167,8168],{"class":49,"line":185},[47,8169,1133],{"class":64},[47,8171,8172],{"class":49,"line":197},[47,8173,1138],{"class":64},[16,8175,8176,8179],{},[748,8177,8178],{},"Τα διαδραστικά στοιχεία είναι προσβάσιμα με πληκτρολόγιο."," Κάθε κουμπί, σύνδεσμος και στοιχείο φόρμας στα συστατικά σας πρέπει να λαμβάνει εστίαση και να λειτουργεί πλήρως με πληκτρολόγιο.",[16,8181,8182,8185],{},[748,8183,8184],{},"Αρκετά μεγάλα touch targets."," Το WCAG 2.2, κριτήριο 2.5.8 (Target Size, Minimum, επίπεδο AA) απαιτεί ελάχιστο 24×24 CSS pixels· το παλαιότερο WCAG 2.1, κριτήριο 2.5.5 (AAA), συνιστά 44×44. Για κύριες ενέργειες σε κινητά, στοχεύστε στο AAA — τα μικρά touch targets παραμένουν από τις κύριες αιτίες αποτυχιών προσβασιμότητας.",[11,8187,8189],{"id":8188},"aria-live-regions-για-streaming-περιεχόμενο","ARIA Live Regions για Streaming Περιεχόμενο",[16,8191,8192],{},"Το streaming είναι το χαρακτηριστικό γνώρισμα του Generative UI — τα συστατικά εμφανίζονται σταδιακά καθώς το AI τα παράγει. Τα screen readers δεν ανακοινώνουν αυτόματα περιεχόμενο που εμφανίζεται δυναμικά. Πρέπει να τους το γνωστοποιήσετε ρητά.",[16,8194,8195,8196,8199],{},"Χρησιμοποιήστε ",[44,8197,8198],{},"aria-live"," για να ανακοινώνετε την άφιξη νέου παραγόμενου περιεχομένου:",[37,8201,8203],{"className":1388,"code":8202,"language":1390,"meta":42,"style":42},"\u002F\u002F components\u002Fgenui-output-region.tsx\nexport function GenUIOutputRegion({ children, isLoading }: {\n  children: React.ReactNode;\n  isLoading: boolean;\n}) {\n  return (\n    \u003Cdiv\n      aria-live=\"polite\"\n      aria-busy={isLoading}\n      aria-label=\"AI-generated content\"\n      aria-atomic=\"false\"\n    >\n      {children}\n    \u003C\u002Fdiv>\n  );\n}\n",[44,8204,8205,8210,8234,8249,8260,8264,8270,8276,8286,8295,8304,8314,8318,8322,8330,8334],{"__ignoreMap":42},[47,8206,8207],{"class":49,"line":50},[47,8208,8209],{"class":53},"\u002F\u002F components\u002Fgenui-output-region.tsx\n",[47,8211,8212,8214,8216,8219,8221,8223,8225,8228,8230,8232],{"class":49,"line":57},[47,8213,163],{"class":60},[47,8215,1157],{"class":60},[47,8217,8218],{"class":203}," GenUIOutputRegion",[47,8220,1513],{"class":64},[47,8222,1983],{"class":873},[47,8224,660],{"class":64},[47,8226,8227],{"class":873},"isLoading",[47,8229,1518],{"class":64},[47,8231,877],{"class":60},[47,8233,176],{"class":64},[47,8235,8236,8239,8241,8243,8245,8247],{"class":49,"line":78},[47,8237,8238],{"class":873},"  children",[47,8240,877],{"class":60},[47,8242,1996],{"class":203},[47,8244,1999],{"class":64},[47,8246,2002],{"class":203},[47,8248,75],{"class":64},[47,8250,8251,8254,8256,8258],{"class":49,"line":93},[47,8252,8253],{"class":873},"  isLoading",[47,8255,877],{"class":60},[47,8257,6068],{"class":169},[47,8259,75],{"class":64},[47,8261,8262],{"class":49,"line":108},[47,8263,1833],{"class":64},[47,8265,8266,8268],{"class":49,"line":123},[47,8267,887],{"class":60},[47,8269,1539],{"class":64},[47,8271,8272,8274],{"class":49,"line":138},[47,8273,1544],{"class":64},[47,8275,1548],{"class":1547},[47,8277,8278,8281,8283],{"class":49,"line":153},[47,8279,8280],{"class":203},"      aria-live",[47,8282,1442],{"class":60},[47,8284,8285],{"class":71},"\"polite\"\n",[47,8287,8288,8290,8292],{"class":49,"line":160},[47,8289,1595],{"class":203},[47,8291,1442],{"class":60},[47,8293,8294],{"class":64},"{isLoading}\n",[47,8296,8297,8299,8301],{"class":49,"line":179},[47,8298,1585],{"class":203},[47,8300,1442],{"class":60},[47,8302,8303],{"class":71},"\"AI-generated content\"\n",[47,8305,8306,8309,8311],{"class":49,"line":185},[47,8307,8308],{"class":203},"      aria-atomic",[47,8310,1442],{"class":60},[47,8312,8313],{"class":71},"\"false\"\n",[47,8315,8316],{"class":49,"line":197},[47,8317,8125],{"class":64},[47,8319,8320],{"class":49,"line":210},[47,8321,2030],{"class":64},[47,8323,8324,8326,8328],{"class":49,"line":234},[47,8325,1709],{"class":64},[47,8327,1646],{"class":1547},[47,8329,1657],{"class":64},[47,8331,8332],{"class":49,"line":253},[47,8333,1133],{"class":64},[47,8335,8336],{"class":49,"line":273},[47,8337,1138],{"class":64},[16,8339,8340],{},"Βασικές επιλογές εδώ:",[3281,8342,8343,8352,8364],{},[2614,8344,8345,8348,8349,1999],{},[44,8346,8347],{},"aria-live=\"polite\""," ανακοινώνει νέο περιεχόμενο την επόμενη κατάλληλη στιγμή — χωρίς να διακόπτει τον χρήστη όπως θα έκανε το ",[44,8350,8351],{},"assertive",[2614,8353,8354,8357,8358,8361,8362,1999],{},[44,8355,8356],{},"aria-busy={isLoading}"," ενημερώνει την υποστηρικτική τεχνολογία ότι η περιοχή ενημερώνεται. Τα screen readers αναβάλλουν τις ανακοινώσεις μέχρι το ",[44,8359,8360],{},"aria-busy"," γίνει ",[44,8363,5197],{},[2614,8365,8366,8369],{},[44,8367,8368],{},"aria-atomic=\"false\""," ανακοινώνει μεμονωμένες προσθήκες καθώς φτάνουν, αντί να επαναδιαβάζει ολόκληρη την περιοχή σε κάθε αλλαγή.",[16,8371,8372],{},"Για την κατάσταση skeleton φόρτωσης:",[37,8374,8376],{"className":1388,"code":8375,"language":1390,"meta":42,"style":42},"function LoadingSkeleton({ label }: { label: string }) {\n  return (\n    \u003Cdiv\n      role=\"status\"\n      aria-label={`Loading ${label}`}\n      className=\"animate-pulse rounded-lg bg-muted h-32\"\n    \u002F>\n  );\n}\n",[44,8377,8378,8404,8410,8416,8426,8443,8452,8456,8460],{"__ignoreMap":42},[47,8379,8380,8382,8385,8387,8390,8392,8394,8396,8398,8400,8402],{"class":49,"line":50},[47,8381,865],{"class":60},[47,8383,8384],{"class":203}," LoadingSkeleton",[47,8386,1513],{"class":64},[47,8388,8389],{"class":873},"label",[47,8391,1518],{"class":64},[47,8393,877],{"class":60},[47,8395,1523],{"class":64},[47,8397,8389],{"class":873},[47,8399,877],{"class":60},[47,8401,1170],{"class":169},[47,8403,1532],{"class":64},[47,8405,8406,8408],{"class":49,"line":57},[47,8407,887],{"class":60},[47,8409,1539],{"class":64},[47,8411,8412,8414],{"class":49,"line":78},[47,8413,1544],{"class":64},[47,8415,1548],{"class":1547},[47,8417,8418,8421,8423],{"class":49,"line":93},[47,8419,8420],{"class":203},"      role",[47,8422,1442],{"class":60},[47,8424,8425],{"class":71},"\"status\"\n",[47,8427,8428,8430,8432,8434,8437,8439,8441],{"class":49,"line":108},[47,8429,1585],{"class":203},[47,8431,1442],{"class":60},[47,8433,1558],{"class":64},[47,8435,8436],{"class":71},"`Loading ${",[47,8438,8389],{"class":64},[47,8440,1055],{"class":71},[47,8442,1138],{"class":64},[47,8444,8445,8447,8449],{"class":49,"line":123},[47,8446,1553],{"class":203},[47,8448,1442],{"class":60},[47,8450,8451],{"class":71},"\"animate-pulse rounded-lg bg-muted h-32\"\n",[47,8453,8454],{"class":49,"line":138},[47,8455,1605],{"class":64},[47,8457,8458],{"class":49,"line":153},[47,8459,1133],{"class":64},[47,8461,8462],{"class":49,"line":160},[47,8463,1138],{"class":64},[16,8465,5968,8466,8469,8470,8472],{},[44,8467,8468],{},"role=\"status\""," είναι μια έμμεση περιοχή ",[44,8471,8347],{}," για σύντομα μηνύματα κατάστασης. Ανακοινώνει την εμφάνισή του χωρίς να διακόπτει την τρέχουσα ομιλία.",[11,8474,8476],{"id":8475},"διαχείριση-εστίασης","Διαχείριση Εστίασης",[16,8478,8479],{},"Όταν εμφανίζεται παραγόμενο περιεχόμενο, η εστίαση πληκτρολογίου παραμένει εκεί που ήταν. Συνήθως αυτό είναι σωστό — δεν θέλετε η εστίαση να πηδά καθώς το AI στέλνει streaming συστατικά. Αλλά σε ορισμένα σενάρια, χρειάζεται να μετακινήσετε ρητά την εστίαση.",[16,8481,8482],{},[748,8483,8484],{},"Μετά από υποβολή φόρμας που αντικαθιστά το περιεχόμενο της σελίδας:",[37,8486,8488],{"className":1388,"code":8487,"language":1390,"meta":42,"style":42},"const outputRef = useRef\u003CHTMLDivElement>(null);\nconst [generatedUI, setGeneratedUI] = useState\u003CReact.ReactNode>(null);\n\nasync function handleSubmit(e: React.FormEvent) {\n  e.preventDefault();\n  const ui = await generateUI(prompt);\n  setGeneratedUI(ui);\n}\n\n\u002F\u002F Μετακινούμε εστίαση ΑΦΟΥ το React έχει δεσμεύσει το νέο DOM — ποτέ με setTimeout.\nuseEffect(() => {\n  if (generatedUI) {\n    outputRef.current?.focus();\n  }\n}, [generatedUI]);\n\n\u002F\u002F tabIndex={-1} κάνει το div εστιάσιμο μέσω προγράμματος\n\u003Cdiv ref={outputRef} tabIndex={-1} aria-label=\"Generated results\">\n  {generatedUI}\n\u003C\u002Fdiv>\n",[44,8489,8490,8513,8547,8551,8573,8582,8597,8605,8609,8613,8618,8630,8638,8648,8652,8657,8661,8666,8704,8709],{"__ignoreMap":42},[47,8491,8492,8494,8497,8499,8502,8504,8507,8509,8511],{"class":49,"line":50},[47,8493,1420],{"class":60},[47,8495,8496],{"class":169}," outputRef",[47,8498,173],{"class":60},[47,8500,8501],{"class":203}," useRef",[47,8503,1103],{"class":64},[47,8505,8506],{"class":203},"HTMLDivElement",[47,8508,2178],{"class":64},[47,8510,2181],{"class":169},[47,8512,2184],{"class":64},[47,8514,8515,8517,8519,8522,8524,8527,8529,8531,8533,8535,8537,8539,8541,8543,8545],{"class":49,"line":57},[47,8516,1420],{"class":60},[47,8518,3070],{"class":64},[47,8520,8521],{"class":169},"generatedUI",[47,8523,660],{"class":64},[47,8525,8526],{"class":169},"setGeneratedUI",[47,8528,1572],{"class":64},[47,8530,1442],{"class":60},[47,8532,3085],{"class":203},[47,8534,1103],{"class":64},[47,8536,3090],{"class":203},[47,8538,1999],{"class":64},[47,8540,2002],{"class":203},[47,8542,2178],{"class":64},[47,8544,2181],{"class":169},[47,8546,2184],{"class":64},[47,8548,8549],{"class":49,"line":78},[47,8550,157],{"emptyLinePlaceholder":156},[47,8552,8553,8555,8557,8559,8561,8563,8565,8567,8569,8571],{"class":49,"line":93},[47,8554,954],{"class":60},[47,8556,1157],{"class":60},[47,8558,5213],{"class":203},[47,8560,225],{"class":64},[47,8562,5218],{"class":873},[47,8564,877],{"class":60},[47,8566,1996],{"class":203},[47,8568,1999],{"class":64},[47,8570,5227],{"class":203},[47,8572,971],{"class":64},[47,8574,8575,8578,8580],{"class":49,"line":108},[47,8576,8577],{"class":64},"  e.",[47,8579,5237],{"class":203},[47,8581,5240],{"class":64},[47,8583,8584,8586,8588,8590,8592,8594],{"class":49,"line":123},[47,8585,1177],{"class":60},[47,8587,5319],{"class":169},[47,8589,173],{"class":60},[47,8591,1185],{"class":60},[47,8593,2830],{"class":203},[47,8595,8596],{"class":64},"(prompt);\n",[47,8598,8599,8602],{"class":49,"line":138},[47,8600,8601],{"class":203},"  setGeneratedUI",[47,8603,8604],{"class":64},"(ui);\n",[47,8606,8607],{"class":49,"line":153},[47,8608,1138],{"class":64},[47,8610,8611],{"class":49,"line":160},[47,8612,157],{"emptyLinePlaceholder":156},[47,8614,8615],{"class":49,"line":179},[47,8616,8617],{"class":53},"\u002F\u002F Μετακινούμε εστίαση ΑΦΟΥ το React έχει δεσμεύσει το νέο DOM — ποτέ με setTimeout.\n",[47,8619,8620,8623,8626,8628],{"class":49,"line":185},[47,8621,8622],{"class":203},"useEffect",[47,8624,8625],{"class":64},"(() ",[47,8627,920],{"class":60},[47,8629,176],{"class":64},[47,8631,8632,8635],{"class":49,"line":197},[47,8633,8634],{"class":60},"  if",[47,8636,8637],{"class":64}," (generatedUI) {\n",[47,8639,8640,8643,8646],{"class":49,"line":210},[47,8641,8642],{"class":64},"    outputRef.current?.",[47,8644,8645],{"class":203},"focus",[47,8647,5240],{"class":64},[47,8649,8650],{"class":49,"line":234},[47,8651,2282],{"class":64},[47,8653,8654],{"class":49,"line":253},[47,8655,8656],{"class":64},"}, [generatedUI]);\n",[47,8658,8659],{"class":49,"line":273},[47,8660,157],{"emptyLinePlaceholder":156},[47,8662,8663],{"class":49,"line":292},[47,8664,8665],{"class":53},"\u002F\u002F tabIndex={-1} κάνει το div εστιάσιμο μέσω προγράμματος\n",[47,8667,8668,8670,8672,8675,8677,8680,8683,8685,8687,8689,8692,8694,8697,8699,8702],{"class":49,"line":298},[47,8669,1103],{"class":64},[47,8671,1646],{"class":1547},[47,8673,8674],{"class":203}," ref",[47,8676,1442],{"class":60},[47,8678,8679],{"class":64},"{outputRef} ",[47,8681,8682],{"class":203},"tabIndex",[47,8684,1442],{"class":60},[47,8686,1558],{"class":64},[47,8688,7987],{"class":60},[47,8690,8691],{"class":169},"1",[47,8693,2169],{"class":64},[47,8695,8696],{"class":203},"aria-label",[47,8698,1442],{"class":60},[47,8700,8701],{"class":71},"\"Generated results\"",[47,8703,1657],{"class":64},[47,8705,8706],{"class":49,"line":304},[47,8707,8708],{"class":64},"  {generatedUI}\n",[47,8710,8711,8714,8716],{"class":49,"line":310},[47,8712,8713],{"class":64},"\u003C\u002F",[47,8715,1646],{"class":1547},[47,8717,1657],{"class":64},[16,8719,5968,8720,8723,8724,1999],{},[44,8721,8722],{},"tabIndex={-1}"," κάνει το στοιχείο εστιάσιμο μέσω προγράμματος χωρίς να το προσθέτει στη σειρά Tab. Ο χρήστης μπορεί να το παρακάμψει φυσικά με Tab, αλλά εσείς μπορείτε να το εστιάσετε μέσω ",[44,8725,8726],{},".focus()",[16,8728,8729,8730,8733,8734,8736,8737,8739,8740,8743,8744,8747],{},"Αποφύγετε το συνηθισμένο αντιπρότυπο ",[44,8731,8732],{},"setTimeout(() => ref.current?.focus(), 50)",". Τα 50ms είναι εκτίμηση· αν η απόδοση διαρκεί περισσότερο σε αργή συσκευή, η κλήση ",[44,8735,8726],{}," θα απευθυνθεί σε παρωχημένο ή ανύπαρκτο στοιχείο. Το ",[44,8738,8622],{}," εκτελείται ",[757,8741,8742],{},"αφού"," το React έχει δεσμεύσει το νέο DOM — ακριβώς η εγγύηση που χρειάζεστε. Αν πρέπει να καθυστερήσετε κατά ένα tick ακόμη (π.χ. περιμένετε portal παιδί), χρησιμοποιήστε ",[44,8745,8746],{},"queueMicrotask",", αλλά ποτέ timeout με μαγικό αριθμό.",[16,8749,8750],{},[748,8751,8752],{},"Αφού ανοίξει dialog ή panel με παραγόμενο περιεχόμενο:",[16,8754,8755],{},"Μετακινήστε εστίαση στο πρώτο εστιάσιμο στοιχείο μέσα στον πίνακα ή στον τίτλο του. Επιστρέψτε την εστίαση στο στοιχείο που τον άνοιξε όταν κλείσει ο πίνακας.",[11,8757,8759],{"id":8758},"πλοήγηση-με-πληκτρολόγιο-στα-παραγόμενα-συστατικά","Πλοήγηση με Πληκτρολόγιο στα Παραγόμενα Συστατικά",[16,8761,8762],{},"Τα συστατικά που εμφανίζονται σε παραγόμενες διατάξεις πρέπει να είναι πλήρως πλοηγήσιμα με πληκτρολόγιο. Ελέγξτε κάθε συστατικό:",[16,8764,8765,8768,8769,8772],{},[748,8766,8767],{},"Πίνακες:"," Οι χρήστες screen reader αναμένουν πλοήγηση με βελάκια σε κελιά πίνακα. Αν το συστατικό ",[44,8770,8771],{},"DataTable"," δεν υλοποιεί αυτό, οι σύνθετοι πίνακες γίνονται εμπόδιο για πλοήγηση με πληκτρολόγιο.",[16,8774,8775,8778,8779,8782],{},[748,8776,8777],{},"Γραφήματα:"," Παρέχετε εναλλακτικό πίνακα. Τα SVG γραφήματα είναι οπτικά πλούσια αλλά σχεδόν ακατανόητα για screen readers. Προσθέστε στοιχείο ",[44,8780,8781],{},"\u003Cdetails>"," ή οπτικά κρυμμένο πίνακα με τα δεδομένα του γραφήματος.",[37,8784,8786],{"className":1388,"code":8785,"language":1390,"meta":42,"style":42},"function BarChart({ title, data }: BarChartProps) {\n  return (\n    \u003Cdiv>\n      \u003Ch3>{title}\u003C\u002Fh3>\n      {\u002F* Οπτικό γράφημα *\u002F}\n      \u003Csvg aria-hidden=\"true\">\n        {\u002F* ... απόδοση γραφήματος ... *\u002F}\n      \u003C\u002Fsvg>\n      {\u002F* Προσβάσιμος πίνακας δεδομένων, κρυμμένος οπτικά *\u002F}\n      \u003Cdetails className=\"sr-only\">\n        \u003Csummary>View data as table\u003C\u002Fsummary>\n        \u003Ctable>\n          \u003Ccaption>{title}\u003C\u002Fcaption>\n          \u003Cthead>\n            \u003Ctr>\u003Cth>Category\u003C\u002Fth>\u003Cth>Value\u003C\u002Fth>\u003C\u002Ftr>\n          \u003C\u002Fthead>\n          \u003Ctbody>\n            {data.map(({ label, value }) => (\n              \u003Ctr key={label}>\n                \u003Ctd>{label}\u003C\u002Ftd>\n                \u003Ctd>{value}\u003C\u002Ftd>\n              \u003C\u002Ftr>\n            ))}\n          \u003C\u002Ftbody>\n        \u003C\u002Ftable>\n      \u003C\u002Fdetails>\n    \u003C\u002Fdiv>\n  );\n}\n",[44,8787,8788,8813,8819,8827,8840,8849,8866,8875,8883,8892,8908,8922,8930,8943,8951,8983,8991,8999,9021,9035,9049,9062,9071,9076,9084,9092,9100,9108,9112],{"__ignoreMap":42},[47,8789,8790,8792,8795,8797,8800,8802,8804,8806,8808,8811],{"class":49,"line":50},[47,8791,865],{"class":60},[47,8793,8794],{"class":203}," BarChart",[47,8796,1513],{"class":64},[47,8798,8799],{"class":873},"title",[47,8801,660],{"class":64},[47,8803,2120],{"class":873},[47,8805,1518],{"class":64},[47,8807,877],{"class":60},[47,8809,8810],{"class":203}," BarChartProps",[47,8812,971],{"class":64},[47,8814,8815,8817],{"class":49,"line":57},[47,8816,887],{"class":60},[47,8818,1539],{"class":64},[47,8820,8821,8823,8825],{"class":49,"line":78},[47,8822,1544],{"class":64},[47,8824,1646],{"class":1547},[47,8826,1657],{"class":64},[47,8828,8829,8831,8833,8836,8838],{"class":49,"line":93},[47,8830,1662],{"class":64},[47,8832,3784],{"class":1547},[47,8834,8835],{"class":64},">{title}\u003C\u002F",[47,8837,3784],{"class":1547},[47,8839,1657],{"class":64},[47,8841,8842,8844,8847],{"class":49,"line":108},[47,8843,2306],{"class":64},[47,8845,8846],{"class":53},"\u002F* Οπτικό γράφημα *\u002F",[47,8848,1138],{"class":64},[47,8850,8851,8853,8856,8859,8861,8864],{"class":49,"line":123},[47,8852,1662],{"class":64},[47,8854,8855],{"class":1547},"svg",[47,8857,8858],{"class":203}," aria-hidden",[47,8860,1442],{"class":60},[47,8862,8863],{"class":71},"\"true\"",[47,8865,1657],{"class":64},[47,8867,8868,8870,8873],{"class":49,"line":138},[47,8869,5469],{"class":64},[47,8871,8872],{"class":53},"\u002F* ... απόδοση γραφήματος ... *\u002F",[47,8874,1138],{"class":64},[47,8876,8877,8879,8881],{"class":49,"line":153},[47,8878,1879],{"class":64},[47,8880,8855],{"class":1547},[47,8882,1657],{"class":64},[47,8884,8885,8887,8890],{"class":49,"line":160},[47,8886,2306],{"class":64},[47,8888,8889],{"class":53},"\u002F* Προσβάσιμος πίνακας δεδομένων, κρυμμένος οπτικά *\u002F",[47,8891,1138],{"class":64},[47,8893,8894,8896,8899,8901,8903,8906],{"class":49,"line":179},[47,8895,1662],{"class":64},[47,8897,8898],{"class":1547},"details",[47,8900,1649],{"class":203},[47,8902,1442],{"class":60},[47,8904,8905],{"class":71},"\"sr-only\"",[47,8907,1657],{"class":64},[47,8909,8910,8912,8915,8918,8920],{"class":49,"line":185},[47,8911,2338],{"class":64},[47,8913,8914],{"class":1547},"summary",[47,8916,8917],{"class":64},">View data as table\u003C\u002F",[47,8919,8914],{"class":1547},[47,8921,1657],{"class":64},[47,8923,8924,8926,8928],{"class":49,"line":197},[47,8925,2338],{"class":64},[47,8927,2299],{"class":1547},[47,8929,1657],{"class":64},[47,8931,8932,8934,8937,8939,8941],{"class":49,"line":210},[47,8933,5489],{"class":64},[47,8935,8936],{"class":1547},"caption",[47,8938,8835],{"class":64},[47,8940,8936],{"class":1547},[47,8942,1657],{"class":64},[47,8944,8945,8947,8949],{"class":49,"line":234},[47,8946,5489],{"class":64},[47,8948,2431],{"class":1547},[47,8950,1657],{"class":64},[47,8952,8953,8955,8957,8960,8962,8965,8967,8969,8971,8974,8976,8979,8981],{"class":49,"line":253},[47,8954,5820],{"class":64},[47,8956,2341],{"class":1547},[47,8958,8959],{"class":64},">\u003C",[47,8961,2436],{"class":1547},[47,8963,8964],{"class":64},">Category\u003C\u002F",[47,8966,2436],{"class":1547},[47,8968,8959],{"class":64},[47,8970,2436],{"class":1547},[47,8972,8973],{"class":64},">Value\u003C\u002F",[47,8975,2436],{"class":1547},[47,8977,8978],{"class":64},">\u003C\u002F",[47,8980,2341],{"class":1547},[47,8982,1657],{"class":64},[47,8984,8985,8987,8989],{"class":49,"line":273},[47,8986,5543],{"class":64},[47,8988,2431],{"class":1547},[47,8990,1657],{"class":64},[47,8992,8993,8995,8997],{"class":49,"line":292},[47,8994,5489],{"class":64},[47,8996,2449],{"class":1547},[47,8998,1657],{"class":64},[47,9000,9001,9004,9006,9009,9011,9013,9015,9017,9019],{"class":49,"line":298},[47,9002,9003],{"class":64},"            {data.",[47,9005,904],{"class":203},[47,9007,9008],{"class":64},"(({ ",[47,9010,8389],{"class":873},[47,9012,660],{"class":64},[47,9014,8005],{"class":873},[47,9016,7159],{"class":64},[47,9018,920],{"class":60},[47,9020,1539],{"class":64},[47,9022,9023,9026,9028,9030,9032],{"class":49,"line":304},[47,9024,9025],{"class":64},"              \u003C",[47,9027,2341],{"class":1547},[47,9029,2344],{"class":203},[47,9031,1442],{"class":60},[47,9033,9034],{"class":64},"{label}>\n",[47,9036,9037,9040,9042,9045,9047],{"class":49,"line":310},[47,9038,9039],{"class":64},"                \u003C",[47,9041,2454],{"class":1547},[47,9043,9044],{"class":64},">{label}\u003C\u002F",[47,9046,2454],{"class":1547},[47,9048,1657],{"class":64},[47,9050,9051,9053,9055,9058,9060],{"class":49,"line":316},[47,9052,9039],{"class":64},[47,9054,2454],{"class":1547},[47,9056,9057],{"class":64},">{value}\u003C\u002F",[47,9059,2454],{"class":1547},[47,9061,1657],{"class":64},[47,9063,9064,9067,9069],{"class":49,"line":326},[47,9065,9066],{"class":64},"              \u003C\u002F",[47,9068,2341],{"class":1547},[47,9070,1657],{"class":64},[47,9072,9073],{"class":49,"line":335},[47,9074,9075],{"class":64},"            ))}\n",[47,9077,9078,9080,9082],{"class":49,"line":351},[47,9079,5543],{"class":64},[47,9081,2449],{"class":1547},[47,9083,1657],{"class":64},[47,9085,9086,9088,9090],{"class":49,"line":362},[47,9087,2388],{"class":64},[47,9089,2299],{"class":1547},[47,9091,1657],{"class":64},[47,9093,9094,9096,9098],{"class":49,"line":372},[47,9095,1879],{"class":64},[47,9097,8898],{"class":1547},[47,9099,1657],{"class":64},[47,9101,9102,9104,9106],{"class":49,"line":388},[47,9103,1709],{"class":64},[47,9105,1646],{"class":1547},[47,9107,1657],{"class":64},[47,9109,9110],{"class":49,"line":394},[47,9111,1133],{"class":64},[47,9113,9114],{"class":49,"line":414},[47,9115,1138],{"class":64},[16,9117,9118,9119,9122,9123,9126],{},"Η κλάση ",[44,9120,9121],{},"sr-only"," αποκρύπτει τον πίνακα οπτικά διατηρώντας τον στο δέντρο προσβασιμότητας. Το ",[44,9124,9125],{},"aria-hidden=\"true\""," στο SVG εμποδίζει τα screen readers από το να προσπαθούν να ερμηνεύσουν το ακατέργαστο SVG markup.",[11,9128,9130],{"id":9129},"μειωμένη-κίνηση","Μειωμένη Κίνηση",[16,9132,9133],{},"Ορισμένοι χρήστες ρυθμίζουν το λειτουργικό τους σύστημα ώστε να προτιμά μειωμένη κίνηση — επειδή τα animations προκαλούν σωματική δυσφορία σε άτομα με αιθουσαία διαταραχή. Τα skeletons φόρτωσης και τα animated transitions πρέπει να σέβονται αυτή την προτίμηση.",[37,9135,9139],{"className":9136,"code":9137,"language":9138,"meta":42,"style":42},"language-css shiki shiki-themes github-light github-dark","\u002F* Στο global CSS ή στη ρύθμιση Tailwind *\u002F\n@media (prefers-reduced-motion: reduce) {\n  .animate-pulse {\n    animation: none;\n  }\n\n  .transition-all {\n    transition: none;\n  }\n}\n","css",[44,9140,9141,9146,9154,9161,9173,9177,9181,9188,9199,9203],{"__ignoreMap":42},[47,9142,9143],{"class":49,"line":50},[47,9144,9145],{"class":53},"\u002F* Στο global CSS ή στη ρύθμιση Tailwind *\u002F\n",[47,9147,9148,9151],{"class":49,"line":57},[47,9149,9150],{"class":60},"@media",[47,9152,9153],{"class":64}," (prefers-reduced-motion: reduce) {\n",[47,9155,9156,9159],{"class":49,"line":78},[47,9157,9158],{"class":203},"  .animate-pulse",[47,9160,176],{"class":64},[47,9162,9163,9166,9168,9171],{"class":49,"line":93},[47,9164,9165],{"class":169},"    animation",[47,9167,951],{"class":64},[47,9169,9170],{"class":169},"none",[47,9172,75],{"class":64},[47,9174,9175],{"class":49,"line":108},[47,9176,2282],{"class":64},[47,9178,9179],{"class":49,"line":123},[47,9180,157],{"emptyLinePlaceholder":156},[47,9182,9183,9186],{"class":49,"line":138},[47,9184,9185],{"class":203},"  .transition-all",[47,9187,176],{"class":64},[47,9189,9190,9193,9195,9197],{"class":49,"line":153},[47,9191,9192],{"class":169},"    transition",[47,9194,951],{"class":64},[47,9196,9170],{"class":169},[47,9198,75],{"class":64},[47,9200,9201],{"class":49,"line":160},[47,9202,2282],{"class":64},[47,9204,9205],{"class":49,"line":179},[47,9206,1138],{"class":64},[16,9208,9209,9210,7345,9213,877],{},"Στο Tailwind μπορείτε να χρησιμοποιήσετε τις παραλλαγές ",[44,9211,9212],{},"motion-safe:",[44,9214,9215],{},"motion-reduce:",[37,9217,9219],{"className":1388,"code":9218,"language":1390,"meta":42,"style":42},"\u003Cdiv className=\"motion-safe:animate-pulse motion-reduce:opacity-50 bg-muted rounded-lg h-32\" \u002F>\n",[44,9220,9221],{"__ignoreMap":42},[47,9222,9223,9225,9227,9229,9231,9234],{"class":49,"line":50},[47,9224,1103],{"class":64},[47,9226,1646],{"class":1547},[47,9228,1649],{"class":203},[47,9230,1442],{"class":60},[47,9232,9233],{"class":71},"\"motion-safe:animate-pulse motion-reduce:opacity-50 bg-muted rounded-lg h-32\"",[47,9235,1674],{"class":64},[16,9237,5968,9238,9240,9241,9243],{},[44,9239,9212],{}," εφαρμόζεται μόνο όταν ο χρήστης δεν έχει ζητήσει μειωμένη κίνηση. Το ",[44,9242,9215],{}," — όταν έχει. Για καταστάσεις φόρτωσης, ένα στατικό ελαφρώς αμυδρό placeholder είναι καλή εναλλακτική στο pulsing animation με ενεργοποιημένη τη μειωμένη κίνηση.",[11,9245,9247],{"id":9246},"ιεραρχία-επικεφαλίδων-σε-συντεθειμένες-διατάξεις","Ιεραρχία Επικεφαλίδων σε Συντεθειμένες Διατάξεις",[16,9249,9250],{},"Το AI συνθέτει συστατικά σε διατάξεις. Κάθε συστατικό μπορεί να έχει τις δικές του επικεφαλίδες. Όταν πολλά συστατικά εμφανίζονται μαζί, οι επικεφαλίδες τους πρέπει να σχηματίζουν μια συνεκτική ιεραρχία — όχι ένα πλήθος ασύνδετων H2.",[16,9252,9253],{},"Αυτό είναι πρόβλημα σύνθεσης που δεν μπορεί να λυθεί σε επίπεδο μεμονωμένου συστατικού. Κάθε συστατικό πρέπει να δέχεται prop επιπέδου επικεφαλίδας:",[37,9255,9257],{"className":1388,"code":9256,"language":1390,"meta":42,"style":42},"interface MetricCardProps {\n  label: string;\n  value: string;\n  change: number;\n  headingLevel?: 'h2' | 'h3' | 'h4';  \u002F\u002F προεπιλογή h3\n}\n\nfunction MetricCard({ label, value, change, headingLevel: Heading = 'h3' }: MetricCardProps) {\n  return (\n    \u003Cdiv className=\"rounded-lg border p-6\">\n      \u003CHeading className=\"text-sm font-medium text-muted-foreground\">{label}\u003C\u002FHeading>\n      {\u002F* ... *\u002F}\n    \u003C\u002Fdiv>\n  );\n}\n",[44,9258,9259,9268,9279,9290,9300,9326,9330,9334,9375,9381,9396,9415,9423,9431,9435],{"__ignoreMap":42},[47,9260,9261,9263,9266],{"class":49,"line":50},[47,9262,3661],{"class":60},[47,9264,9265],{"class":203}," MetricCardProps",[47,9267,176],{"class":64},[47,9269,9270,9273,9275,9277],{"class":49,"line":57},[47,9271,9272],{"class":873},"  label",[47,9274,877],{"class":60},[47,9276,1170],{"class":169},[47,9278,75],{"class":64},[47,9280,9281,9284,9286,9288],{"class":49,"line":78},[47,9282,9283],{"class":873},"  value",[47,9285,877],{"class":60},[47,9287,1170],{"class":169},[47,9289,75],{"class":64},[47,9291,9292,9294,9296,9298],{"class":49,"line":93},[47,9293,3951],{"class":873},[47,9295,877],{"class":60},[47,9297,3687],{"class":169},[47,9299,75],{"class":64},[47,9301,9302,9305,9307,9310,9312,9315,9317,9320,9323],{"class":49,"line":108},[47,9303,9304],{"class":873},"  headingLevel",[47,9306,4346],{"class":60},[47,9308,9309],{"class":71}," 'h2'",[47,9311,6079],{"class":60},[47,9313,9314],{"class":71}," 'h3'",[47,9316,6079],{"class":60},[47,9318,9319],{"class":71}," 'h4'",[47,9321,9322],{"class":64},";  ",[47,9324,9325],{"class":53},"\u002F\u002F προεπιλογή h3\n",[47,9327,9328],{"class":49,"line":123},[47,9329,1138],{"class":64},[47,9331,9332],{"class":49,"line":138},[47,9333,157],{"emptyLinePlaceholder":156},[47,9335,9336,9338,9341,9343,9345,9347,9349,9351,9353,9355,9358,9360,9363,9365,9367,9369,9371,9373],{"class":49,"line":153},[47,9337,865],{"class":60},[47,9339,9340],{"class":203}," MetricCard",[47,9342,1513],{"class":64},[47,9344,8389],{"class":873},[47,9346,660],{"class":64},[47,9348,8005],{"class":873},[47,9350,660],{"class":64},[47,9352,4000],{"class":873},[47,9354,660],{"class":64},[47,9356,9357],{"class":873},"headingLevel",[47,9359,951],{"class":64},[47,9361,9362],{"class":873},"Heading",[47,9364,173],{"class":60},[47,9366,9314],{"class":71},[47,9368,1518],{"class":64},[47,9370,877],{"class":60},[47,9372,9265],{"class":203},[47,9374,971],{"class":64},[47,9376,9377,9379],{"class":49,"line":160},[47,9378,887],{"class":60},[47,9380,1539],{"class":64},[47,9382,9383,9385,9387,9389,9391,9394],{"class":49,"line":179},[47,9384,1544],{"class":64},[47,9386,1646],{"class":1547},[47,9388,1649],{"class":203},[47,9390,1442],{"class":60},[47,9392,9393],{"class":71},"\"rounded-lg border p-6\"",[47,9395,1657],{"class":64},[47,9397,9398,9400,9402,9404,9406,9409,9411,9413],{"class":49,"line":185},[47,9399,1662],{"class":64},[47,9401,9362],{"class":169},[47,9403,1649],{"class":203},[47,9405,1442],{"class":60},[47,9407,9408],{"class":71},"\"text-sm font-medium text-muted-foreground\"",[47,9410,9044],{"class":64},[47,9412,9362],{"class":169},[47,9414,1657],{"class":64},[47,9416,9417,9419,9421],{"class":49,"line":197},[47,9418,2306],{"class":64},[47,9420,2309],{"class":53},[47,9422,1138],{"class":64},[47,9424,9425,9427,9429],{"class":49,"line":210},[47,9426,1709],{"class":64},[47,9428,1646],{"class":1547},[47,9430,1657],{"class":64},[47,9432,9433],{"class":49,"line":234},[47,9434,1133],{"class":64},[47,9436,9437],{"class":49,"line":253},[47,9438,1138],{"class":64},[16,9440,9441],{},"Στον ορισμό του εργαλείου, συμπεριλάβετε το επίπεδο επικεφαλίδας ως παράμετρο που μπορεί να ορίσει το AI:",[37,9443,9445],{"className":39,"code":9444,"language":41,"meta":42,"style":42},"metricCard: {\n  description: 'Display a KPI metric. Use headingLevel h2 for the first metric in a section, h3 for subsequent metrics.',\n  parameters: z.object({\n    label: z.string(),\n    value: z.string(),\n    change: z.number(),\n    headingLevel: z.enum(['h2', 'h3', 'h4']).default('h3'),\n  }),\n}\n",[44,9446,9447,9455,9467,9479,9488,9497,9506,9540,9545],{"__ignoreMap":42},[47,9448,9449,9452],{"class":49,"line":50},[47,9450,9451],{"class":203},"metricCard",[47,9453,9454],{"class":64},": {\n",[47,9456,9457,9460,9462,9465],{"class":49,"line":57},[47,9458,9459],{"class":203},"  description",[47,9461,951],{"class":64},[47,9463,9464],{"class":71},"'Display a KPI metric. Use headingLevel h2 for the first metric in a section, h3 for subsequent metrics.'",[47,9466,194],{"class":64},[47,9468,9469,9472,9475,9477],{"class":49,"line":78},[47,9470,9471],{"class":203},"  parameters",[47,9473,9474],{"class":64},": z.",[47,9476,204],{"class":203},[47,9478,207],{"class":64},[47,9480,9481,9484,9486],{"class":49,"line":93},[47,9482,9483],{"class":64},"    label: z.",[47,9485,216],{"class":203},[47,9487,359],{"class":64},[47,9489,9490,9493,9495],{"class":49,"line":108},[47,9491,9492],{"class":64},"    value: z.",[47,9494,216],{"class":203},[47,9496,359],{"class":64},[47,9498,9499,9502,9504],{"class":49,"line":123},[47,9500,9501],{"class":64},"    change: z.",[47,9503,259],{"class":203},[47,9505,359],{"class":64},[47,9507,9508,9511,9513,9515,9518,9520,9523,9525,9528,9531,9534,9536,9538],{"class":49,"line":138},[47,9509,9510],{"class":64},"    headingLevel: z.",[47,9512,651],{"class":203},[47,9514,654],{"class":64},[47,9516,9517],{"class":71},"'h2'",[47,9519,660],{"class":64},[47,9521,9522],{"class":71},"'h3'",[47,9524,660],{"class":64},[47,9526,9527],{"class":71},"'h4'",[47,9529,9530],{"class":64},"]).",[47,9532,9533],{"class":203},"default",[47,9535,225],{"class":64},[47,9537,9522],{"class":71},[47,9539,231],{"class":64},[47,9541,9542],{"class":49,"line":153},[47,9543,9544],{"class":64},"  }),\n",[47,9546,9547],{"class":49,"line":160},[47,9548,1138],{"class":64},[11,9550,9552],{"id":9551},"συνδυαστικά-προβλήματα-προσβασιμότητας","Συνδυαστικά Προβλήματα Προσβασιμότητας",[16,9554,9555,9556,9559],{},"Το μοντέλο «προσβάσιμο συστατικό → προσβάσιμη σύνθεση» έχει ένα σκληρό όριο: δύο συστατικά που περνούν το axe ξεχωριστά μπορούν, όταν αποδίδονται δίπλα-δίπλα, να παραβιάζουν μαζί το WCAG. Αυτά είναι σφάλματα που ",[757,9557,9558],{},"υπάρχουν μόνο"," σε γεννητικά συστήματα και δεν θα εμφανιστούν σε κανένα per-component test.",[16,9561,9562,9565],{},[748,9563,9564],{},"Κατάρρευση ιεραρχίας επικεφαλίδων."," Το συστατικό Α αποδίδει H2. Το συστατικό Β επίσης αποδίδει H2. Το AI τα τοποθετεί δίπλα-δίπλα σε ένα grid καρτών. Αποτέλεσμα: το screen reader αναφέρει δύο ισοδύναμες ενότητες που έπρεπε να είναι H3 παιδιά ενός γονικού H2. Αντιμετώπιση: παραμετροποίηση επιπέδων επικεφαλίδων (προηγούμενη ενότητα) και integration test που διατρέχει το δέντρο και ελέγχει τη μονοτονία επιπέδων.",[16,9567,9568,7355,9571,9574,9575,9578,9579,9581,9582,9585],{},[748,9569,9570],{},"Συγκρούσεις ιεραρχίας ARIA.",[44,9572,9573],{},"Dialog"," ορίζει ",[44,9576,9577],{},"aria-modal=\"true\"",". Το AI εμφωλεύει μέσα του άλλο ",[44,9580,9573],{}," (στο μοντέλο ανατέθηκε να αποδώσει επιβεβαίωση μέσα σε panel). Στη στοίβα υπάρχουν δύο modal — η συμπεριφορά υποστηρικτικής τεχνολογίας δεν ορίζεται. Αντιμετώπιση: εντοπισμός εμφωλευμένων ",[44,9583,9584],{},"aria-modal"," κατά την απόδοση, άρνηση αποτύπωσης του εσωτερικού dialog και καταγραφή προειδοποίησης σε dev.",[16,9587,9588,9591,9592,9595,9596,9599,9600,9602],{},[748,9589,9590],{},"Διπλές ετικέτες."," Δύο συστατικά ",[44,9593,9594],{},"SearchInput"," στην ίδια παραγόμενη σελίδα αποδίδουν ",[44,9597,9598],{},"\u003Clabel>Search\u003C\u002Flabel>",". Και οι δύο inputs έχουν το ίδιο accessible name — ο χρήστης screen reader δεν μπορεί να τα διακρίνει. Αντιμετώπιση: το prop ",[44,9601,8389],{}," να γίνει υποχρεωτικό (χωρίς default τιμές) και στο prompt για το AI να απαιτείται ρητά ξεχωριστό όνομα για κάθε instance.",[16,9604,9605,9608,9609,9611,9612,9614],{},[748,9606,9607],{},"Συσσώρευση live regions."," Τρία streaming υπο-συστατικά τυλίγουν το καθένα τον εαυτό του σε ",[44,9610,8347],{},". Το screen reader ουρά τρεις επικαλυπτόμενες ανακοινώσεις. Αντιμετώπιση: μόνο η εξωτερικότερη περιοχή γεννητικής εξόδου δηλώνει ",[44,9613,8198],{},"· τα παιδικά συστατικά κάνουν stream μέσα σε αυτήν ως συνηθισμένο DOM.",[16,9616,9617,9618,9621],{},"Αυτά τα σφάλματα δεν είναι θεωρητικά — είναι ο χαρακτηριστικός τρόπος αποτυχίας συστημάτων «φτιάξε οτιδήποτε». Θεραπεύονται σε επίπεδο integration: λήψη snapshots αντιπροσωπευτικού δείγματος ",[757,9619,9620],{},"παραγόμενων"," διατάξεων, εκτέλεση axe στα συνδυασμένα δέντρα και προσθήκη custom ελέγχων για τα τέσσερα παραπάνω πρότυπα.",[11,9623,9625],{"id":9624},"δοκιμές-με-πραγματικούς-χρήστες","Δοκιμές με Πραγματικούς Χρήστες",[16,9627,9628],{},"Τα αυτοματοποιημένα εργαλεία — axe-core, jest-axe, Storybook a11y, Lighthouse — εντοπίζουν περίπου 30% των προβλημάτων προσβασιμότητας. (Αυτή είναι η ίδια εκτίμηση της Deque Systems για το axe-core, και συμφωνεί με όσα θα πουν οι εταιρείες συμβούλων προσβασιμότητας.) Το υπόλοιπο 70% είναι θέματα κρίσης: είναι κατανοητό το κείμενο που ανακοινώνεται; Ταιριάζει η σειρά εστίασης με τη οπτική σειρά που αναμένει ο βλέποντας χρήστης; Μπορεί ο χρήστης screen reader να ολοκληρώσει πραγματικά την εργασία;",[16,9630,9631],{},"Σε αυτές τις ερωτήσεις δεν απαντά κανένα CI task. Χρειάζονται ζωντανοί άνθρωποι.",[16,9633,9634],{},"Ένα πρακτικό checklist δοκιμών με πραγματικούς χρήστες για κυκλοφορία generative UI:",[3281,9636,9637,9643,9649,9655,9661,9667,9673],{},[2614,9638,9639,9642],{},[748,9640,9641],{},"Εκτέλεση screen reader — NVDA σε Windows + Firefox."," Ο πιο διαδεδομένος συνδυασμός στον κόσμο για χρήστες screen reader (έρευνα WebAIM). Εκτελέστε τα 5 κορυφαία γεννητικά σενάρια.",[2614,9644,9645,9648],{},[748,9646,9647],{},"Εκτέλεση screen reader — VoiceOver σε macOS + Safari και VoiceOver σε iOS + Safari."," Η Apple κυριαρχεί στα mobile screen readers.",[2614,9650,9651,9654],{},[748,9652,9653],{},"Εκτέλεση μόνο με πληκτρολόγιο."," Αποσυνδέστε το ποντίκι. Ολοκληρώστε κάθε κύρια εργασία με Tab, Shift+Tab, Enter, Space, Escape και βελάκια. Σημειώστε κάθε εξαφανιζόμενο δείκτη εστίασης και κάθε παγίδα πληκτρολογίου.",[2614,9656,9657,9660],{},[748,9658,9659],{},"Εκτέλεση με φωνητικό έλεγχο."," Voice Control σε macOS ή Dragon. Το Generative UI φημίζεται ότι είναι δύσκολο για φωνητικό έλεγχο — οι ετικέτες παράγονται από AI, και αυτό αποκαλύπτει ελαττώματα ονομασίας που αλλιώς δεν εντοπίζονται.",[2614,9662,9663,9666],{},[748,9664,9665],{},"Πραγματικοί συμμετέχοντες."," Εμπλέξτε 2–4 χρήστες screen reader ανά τρίμηνο — μέσω Fable, AccessWorks ή τοπικής κοινότητας a11y. Μία τέτοια συνεδρία αξίζει περισσότερο από εκατό αυτοματοποιημένες εκτελέσεις.",[2614,9668,9669,9672],{},[748,9670,9671],{},"Υψηλή αντίθεση και zoom."," Windows High Contrast + 200% zoom προγράμματος περιήγησης + 400% zoom με reflow. Οι παραγόμενες διατάξεις συχνά σπάνε σε μεγάλο zoom, γιατί το AI παράγει σταθερά πλάτη.",[2614,9674,9675,9678],{},[748,9676,9677],{},"Μειωμένη κίνηση."," Ενεργοποιήστε την προτίμηση συστήματος και επαναλάβετε τα streaming σενάρια.",[16,9680,9681],{},"Προβλέψτε προϋπολογισμό για αυτό. Λογική συχνότητα για μικρή ομάδα: αυτοματοποιημένοι έλεγχοι σε κάθε PR, τετράωρο χειροκίνητο πέρασμα πριν από κάθε κυκλοφορία και αμειβόμενη εξωτερική συνεδρία με συμμετέχοντες με αναπηρία μία φορά ανά τρίμηνο.",[11,9683,9685],{"id":9684},"roi-πώς-να-δικαιολογήσετε-το-επένδυση-στη-διοίκηση","ROI: Πώς να Δικαιολογήσετε το Επένδυση στη Διοίκηση",[16,9687,9688],{},"Η εργασία προσβασιμότητας ανταγωνίζεται για χρόνο μηχανικού με νέα χαρακτηριστικά. Αν είστε engineering manager, χρειάζεστε αριθμούς — και πρέπει να τους παρουσιάσετε στη γλώσσα που καταλαβαίνει ο CFO.",[16,9690,9691,9694],{},[748,9692,9693],{},"Κόστος."," Η ενσωμάτωση προσβασιμότητας στη βιβλιοθήκη συστατικών κατά τη φάση σχεδιασμού κοστίζει περίπου 5–10% της αξίας ανάπτυξης συστατικού (εκτιμήσεις Forrester, ομάδες a11y της Microsoft). Η εκ των υστέρων διόρθωση μη προσβάσιμης βιβλιοθήκης μετά την κυκλοφορία κοστίζει 30–100%: ξαναχτίζετε συστατικά ενώ ταυτόχρονα ξεπληρώνετε χρέος σε όλους τους downstream χρήστες. Το φθηνότερο προσβάσιμο συστατικό είναι αυτό που γράψατε προσβάσιμο από την αρχή.",[16,9696,9697,9700],{},[748,9698,9699],{},"Κίνδυνος."," Στο πλαίσιο του European Accessibility Act (EAA), η επιβολή ξεκίνησε στις 28 Ιουνίου 2025: οι B2C ψηφιακές υπηρεσίες που πωλούνται στην ΕΕ πρέπει να συμμορφώνονται με το EN 301 549 (εναρμονισμένο με WCAG 2.1 AA). Τα πρόστιμα καθορίζονται σε επίπεδο κράτους μέλους αλλά σε ορισμένες δικαιοδοσίες φτάνουν εξαψήφιους αριθμούς σε ευρώ ανά παραβίαση. Το ADA στις ΗΠΑ παράγει περίπου 4.000+ αγωγές ανά έτος για web προσβασιμότητα (ετήσια έκθεση UsableNet)· το μέσο ποσό διακανονισμού είναι 15–50 χιλ. δολάρια συν υποχρεωτικές διορθώσεις. Το UK Equality Act, ο καναδικός ACA και ο αυστραλιανός DDA προσθέτουν συγκρίσιμη έκθεση. Το Generative UI που παράγει μαζικά μη συμμορφούμενες διατάξεις είναι, ουσιαστικά, ένας πιθανολογικός γεννήτης αγωγών.",[16,9702,9703,9706],{},[748,9704,9705],{},"Έσοδα."," Περίπου το 16% του παγκόσμιου πληθυσμού ζει με σημαντικά προβλήματα υγείας (ΠΟΥ, 2023). Η έρευνα «Click-Away Pound» στο Ηνωμένο Βασίλειο εκτίμησε απώλειες 17,1 δισ. λιρών ετησίως — χρήματα που οι αγοραστές δεν αφήνουν σε μη προσβάσιμα καταστήματα. Οι κρατικές συμβάσεις στην ΕΕ, τις ΗΠΑ και τον Καναδά απαιτούν συμμόρφωση με Section 508 \u002F EN 301 549· ένα μη προσβάσιμο προϊόν δεν μπορεί να συμμετάσχει σε διαγωνισμό.",[16,9708,9709,9712],{},[748,9710,9711],{},"Χρονοδιάγραμμα υλοποίησης, κατά σειρά προτεραιότητας."," 90ήμερο σχέδιο για υπάρχον generative UI:",[2299,9714,9715,9728],{},[2431,9716,9717],{},[2341,9718,9719,9722,9725],{},[2436,9720,9721],{},"Εβδομάδα",[2436,9723,9724],{},"Εργασία",[2436,9726,9727],{},"Ημέρες μηχανικού",[2449,9729,9730,9741,9752,9766,9776,9787,9798],{},[2341,9731,9732,9735,9738],{},[2454,9733,9734],{},"1–2",[2454,9736,9737],{},"Audit registry συστατικών με axe + χειροκίνητο screen reader pass· λίστα ελαττωμάτων ανά συστατικό",[2454,9739,9740],{},"5–8",[2341,9742,9743,9746,9749],{},[2454,9744,9745],{},"3–4",[2454,9747,9748],{},"Διόρθωση top-10 συστατικών (σημαντική HTML, εστίαση, ετικέτες)",[2454,9750,9751],{},"8–12",[2341,9753,9754,9757,9763],{},[2454,9755,9756],{},"5–6",[2454,9758,9759,9760,9762],{},"Προσθήκη κοινής ",[44,9761,8198],{}," εξόδου, διαχείριση εστίασης, υποστήριξη reduced motion σε επίπεδο διάταξης",[2454,9764,9765],{},"4–6",[2341,9767,9768,9771,9774],{},[2454,9769,9770],{},"7–8",[2454,9772,9773],{},"Παραμετροποίηση επιπέδων επικεφαλίδων· προσθήκη συνδυαστικών integration tests",[2454,9775,9765],{},[2341,9777,9778,9781,9784],{},[2454,9779,9780],{},"9–10",[2454,9782,9783],{},"Ενεργοποίηση jest-axe + Storybook a11y addon στο CI· αποκλεισμός merge σε regressions",[2454,9785,9786],{},"2–3",[2341,9788,9789,9792,9795],{},[2454,9790,9791],{},"11–12",[2454,9793,9794],{},"Πρώτη εξωτερική συνεδρία με χρήστες screen reader· διόρθωση όσων βρήκαν",[2454,9796,9797],{},"3–5",[2341,9799,9800,9803,9806],{},[2454,9801,9802],{},"Μετά",[2454,9804,9805],{},"Τριμηνιαίες δοκιμές χρηστών, εβδομαδιαίοι αυτοματοποιημένοι drift-έλεγχοι",[2454,9807,9808],{},"1 ημέρα\u002Fεβδομάδα",[16,9810,9811],{},"Σύνολο: περίπου 30–45 ημέρες μηχανικού για ουσιαστικό baseline σε μέση βιβλιοθήκη συστατικών, συν συντήρηση στο εξής. Παρουσιάστε το ως επένδυση ενός τριμήνου που εξαλείφει μια ολόκληρη επαναλαμβανόμενη κατηγορία νομικού, εσοδολογικού και φήμης κινδύνου.",[16,9813,9814],{},[748,9815,9816],{},"Μήτρα προτεραιοτήτων για triage.",[2299,9818,9819,9831],{},[2431,9820,9821],{},[2341,9822,9823,9825,9828],{},[2436,9824],{},[2436,9826,9827],{},"Υψηλή επίπτωση στον χρήστη",[2436,9829,9830],{},"Χαμηλή επίπτωση στον χρήστη",[2449,9832,9833,9846],{},[2341,9834,9835,9840,9843],{},[2454,9836,9837],{},[748,9838,9839],{},"Υψηλός νομικός κίνδυνος",[2454,9841,9842],{},"Διόρθωση αυτό το τρίμηνο",[2454,9844,9845],{},"Διόρθωση αυτό το εξάμηνο",[2341,9847,9848,9853,9855],{},[2454,9849,9850],{},[748,9851,9852],{},"Χαμηλός νομικός κίνδυνος",[2454,9854,9845],{},[2454,9856,9857],{},"Στο backlog με ημερομηνία",[16,9859,9860],{},"Ο νομικός κίνδυνος είναι υψηλός όταν η παραβίαση αφορά συναλλακτικό σενάριο (checkout, εγγραφή, διαχείριση λογαριασμού) ή οποιαδήποτε κυβερνητική επιφάνεια. Η επίπτωση στον χρήστη είναι υψηλή όταν το σφάλμα μπλοκάρει την ολοκλήρωση εργασίας για χρήστες υποστηρικτικής τεχνολογίας, και δεν υποβαθμίζει απλώς την άνεση.",[11,9862,9864],{"id":9863},"εργαλεία-δοκιμής","Εργαλεία Δοκιμής",[16,9866,9867],{},"Χρησιμοποιήστε αυτά τα εργαλεία για τον audit της βιβλιοθήκης συστατικών και των παραγόμενων εξόδων. Οι εκδόσεις παρακάτω ισχύουν για τα μέσα του 2025 — ελέγξτε τις τρέχουσες πριν από την υλοποίηση.",[16,9869,9870,9880],{},[748,9871,9872,9873,660,9876,9879],{},"axe-core (",[44,9874,9875],{},"axe-core@4.x",[44,9877,9878],{},"jest-axe@9.x","):"," Αυτοματοποιημένος έλεγχος προσβασιμότητας που εντοπίζει ~30% των προβλημάτων. Ενσωματώστε με jest-axe για κάλυψη unit tests.",[37,9882,9884],{"className":39,"code":9883,"language":41,"meta":42,"style":42},"import { axe, toHaveNoViolations } from 'jest-axe';\nexpect.extend(toHaveNoViolations);\n\ntest('MetricCard has no accessibility violations', async () => {\n  const { container } = render(\n    \u003CMetricCard label=\"Revenue\" value=\"$84,200\" change={12.4} \u002F>\n  );\n  expect(await axe(container)).toHaveNoViolations();\n});\n",[44,9885,9886,9900,9911,9915,9935,9953,9986,9990,10010],{"__ignoreMap":42},[47,9887,9888,9890,9893,9895,9898],{"class":49,"line":50},[47,9889,61],{"class":60},[47,9891,9892],{"class":64}," { axe, toHaveNoViolations } ",[47,9894,68],{"class":60},[47,9896,9897],{"class":71}," 'jest-axe'",[47,9899,75],{"class":64},[47,9901,9902,9905,9908],{"class":49,"line":57},[47,9903,9904],{"class":64},"expect.",[47,9906,9907],{"class":203},"extend",[47,9909,9910],{"class":64},"(toHaveNoViolations);\n",[47,9912,9913],{"class":49,"line":78},[47,9914,157],{"emptyLinePlaceholder":156},[47,9916,9917,9920,9922,9925,9927,9929,9931,9933],{"class":49,"line":93},[47,9918,9919],{"class":203},"test",[47,9921,225],{"class":64},[47,9923,9924],{"class":71},"'MetricCard has no accessibility violations'",[47,9926,660],{"class":64},[47,9928,954],{"class":60},[47,9930,1821],{"class":64},[47,9932,920],{"class":60},[47,9934,176],{"class":64},[47,9936,9937,9939,9941,9944,9947,9949,9951],{"class":49,"line":108},[47,9938,1177],{"class":60},[47,9940,1523],{"class":64},[47,9942,9943],{"class":169},"container",[47,9945,9946],{"class":64}," } ",[47,9948,1442],{"class":60},[47,9950,7627],{"class":203},[47,9952,896],{"class":64},[47,9954,9955,9957,9960,9962,9965,9968,9970,9972,9975,9977,9979,9981,9983],{"class":49,"line":123},[47,9956,1544],{"class":60},[47,9958,9959],{"class":64},"MetricCard label",[47,9961,1442],{"class":60},[47,9963,9964],{"class":71},"\"Revenue\"",[47,9966,9967],{"class":64}," value",[47,9969,1442],{"class":60},[47,9971,7502],{"class":71},[47,9973,9974],{"class":64}," change",[47,9976,1442],{"class":60},[47,9978,1558],{"class":64},[47,9980,7512],{"class":169},[47,9982,2169],{"class":64},[47,9984,9985],{"class":60},"\u002F>\n",[47,9987,9988],{"class":49,"line":138},[47,9989,1133],{"class":64},[47,9991,9992,9995,9997,9999,10002,10005,10008],{"class":49,"line":153},[47,9993,9994],{"class":203},"  expect",[47,9996,225],{"class":64},[47,9998,3144],{"class":60},[47,10000,10001],{"class":203}," axe",[47,10003,10004],{"class":64},"(container)).",[47,10006,10007],{"class":203},"toHaveNoViolations",[47,10009,5240],{"class":64},[47,10011,10012],{"class":49,"line":160},[47,10013,6847],{"class":64},[16,10015,10016,10022],{},[748,10017,10018,10019,9879],{},"Storybook Accessibility addon (",[44,10020,10021],{},"@storybook\u002Faddon-a11y@8.x"," Εκτελέστε ελέγχους axe απευθείας στο Storybook κατά την ανάπτυξη. Εντοπίζει προβλήματα πριν φτάσουν στις δοκιμές.",[16,10024,10025,10028],{},[748,10026,10027],{},"Δοκιμές screen reader:"," Το NVDA (Windows, δωρεάν) και το VoiceOver (macOS, ενσωματωμένο) είναι απαραίτητα για τη δοκιμή της εμπειρίας που τα αυτοματοποιημένα εργαλεία δεν μετρούν — πόσο κατανοητό είναι το παραγόμενο περιεχόμενο όταν ακούγεται δυνατά; Εκτεταμένο checklist στην ενότητα «Δοκιμές με Πραγματικούς Χρήστες» παραπάνω.",[16,10030,10031,10034],{},[748,10032,10033],{},"Πλοήγηση μόνο με πληκτρολόγιο:"," Αποσυνδέστε το ποντίκι και πλοηγηθείτε στην εφαρμογή αποκλειστικά με Tab, Shift+Tab, Enter, Space και βελάκια. Αυτός είναι ο ταχύτερος τρόπος να βρείτε παγίδες πληκτρολογίου.",[11,10036,10038],{"id":10037},"μη-διαπραγματεύσιμες-απαιτήσεις-τελική-λίστα","Μη Διαπραγματεύσιμες Απαιτήσεις: Τελική Λίστα",[16,10040,10041],{},"Πριν από την κυκλοφορία λειτουργίας Generative UI:",[3281,10043,10044,10047,10050,10053,10063,10071,10074,10080,10083,10086],{},[2614,10045,10046],{},"Κάθε συστατικό στο registry εργαλείων περνά axe χωρίς παραβάσεις",[2614,10048,10049],{},"Όλα τα διαδραστικά στοιχεία είναι προσβάσιμα με πληκτρολόγιο και λειτουργούν πλήρως",[2614,10051,10052],{},"Το χρώμα δεν είναι ποτέ ο μόνος φορέας νοήματος",[2614,10054,10055,10056,10058,10059,10062],{},"Η streaming έξοδος τυλίγεται σε ",[44,10057,8198],{}," region (και μόνο η ",[757,10060,10061],{},"πιο εξωτερική"," περιοχή το δηλώνει)",[2614,10064,10065,10066,10068,10069],{},"Τα skeletons έχουν ",[44,10067,8468],{}," και ενημερωτικό ",[44,10070,8696],{},[2614,10072,10073],{},"Τα SVG γραφήματα έχουν εναλλακτικό πίνακα δεδομένων",[2614,10075,10076,10077],{},"Όλα τα animations σέβονται το ",[44,10078,10079],{},"prefers-reduced-motion",[2614,10081,10082],{},"Τα επίπεδα επικεφαλίδων είναι παραμετροποιημένα στα συστατικά, όχι hardcoded",[2614,10084,10085],{},"Τα συνδυαστικά integration tests καλύπτουν τουλάχιστον τα τέσσερα παραπάνω πρότυπα",[2614,10087,10088],{},"Τουλάχιστον μία εξωτερική συνεδρία user testing με χρήστες screen reader ανά τρίμηνο",[16,10090,10091],{},"Η προσβασιμότητα ενσωματωμένη στη βιβλιοθήκη συστατικών δεν είναι επιβάρυνση. Είναι αυτό που κάνει την υπόσχεση «το AI μπορεί να συνθέσει οτιδήποτε» πραγματικότητα για όλους τους χρήστες. Και είναι αυτό που σας κρατά μακριά από τη δικαστική αίθουσα.",[16,10093,10094,10095,10099,10100,4670],{},"Σχετικό υλικό: πρακτικός οδηγός (",[779,10096,10098],{"href":10097},"\u002Flearn\u002Fgenerative-ui-react-practical-guide","Generative UI με React — Πρακτικός Οδηγός",") και οδηγός απόδοσης (",[779,10101,10103],{"href":10102},"\u002Flearn\u002Fperformance-optimization-genui","Βελτιστοποίηση Απόδοσης Generative UI",[3428,10105],{},[16,10107,10108],{},[757,10109,10110,10111,1999],{},"Κατασκευάζετε προσβάσιμο Generative UI για σύνθετη εφαρμογή; ",[779,10112,10113],{"href":3437},"Ας αναλύσουμε μαζί την πρόκλησή σας",[3441,10115,10116],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":42,"searchDepth":57,"depth":57,"links":10118},[10119,10120,10121,10122,10123,10124,10125,10126,10127,10128,10129,10130],{"id":7846,"depth":57,"text":7847},{"id":7872,"depth":57,"text":7873},{"id":8188,"depth":57,"text":8189},{"id":8475,"depth":57,"text":8476},{"id":8758,"depth":57,"text":8759},{"id":9129,"depth":57,"text":9130},{"id":9246,"depth":57,"text":9247},{"id":9551,"depth":57,"text":9552},{"id":9624,"depth":57,"text":9625},{"id":9684,"depth":57,"text":9685},{"id":9863,"depth":57,"text":9864},{"id":10037,"depth":57,"text":10038},"2026-01-22","Πρακτικός οδηγός για προσβάσιμα γεννητικά interfaces — screen readers, πλοήγηση με πληκτρολόγιο και συνδυαστικά προβλήματα προσβασιμότητας.",{"audit_status":7828},"\u002Fel\u002Flearn\u002Fgenerative-ui-accessibility-guide","11 λεπτά ανάγνωσης",{"title":7841,"description":10132},"el\u002Flearn\u002Fgenerative-ui-accessibility-guide",[10139,10140,3476,10141],"accessibility","wcag","inclusive-design","CdJK3-fwr6jelFSCqxpCpCclgnDuYHNzeeWjwdAGGH4",1778601178711]