Tutorial

Generative UI σε React: Πρακτικός Οδηγός

Πέντε παραγωγικά μοτίβα για AI-παραγόμενα components React: registry, διαχωρισμός, skeletons, error boundaries και διαχείριση κατάστασης — με trade-offs για EM και indie developers.

A
Alex12 λεπτά ανάγνωσης

Τα περισσότερα GenUI πρωτότυπα σκοντάφτουν στα ίδια πέντε σημεία

Τα demos Generative UI φαίνονται μαγικά. Οι εφαρμογές GenUI σε production σπάνε με προβλέψιμο τρόπο σε πέντε σημεία: εύθραυστη επιλογή εργαλείων, αγώνες δρόμου κατά το streaming, αναντιστοιχίες props στο runtime, απουσία fallback όταν το μοντέλο δεν είναι διαθέσιμο, και ανεξέλεγκτο κόστος inference. Αυτός ο οδηγός καλύπτει τα πέντε μοτίβα που κρατούν ζωντανή μια GenUI λειτουργία μετά το demo: registry, διαχωρισμός, skeletons, error boundary και κατάσταση — μαζί με τους συμβιβασμούς που κρύβει το καθένα, και συγκεκριμένες συστάσεις για τα δύο κοινά που συνήθως αποφασίζουν «αποστέλλουμε ή όχι»: τον engineering manager που επιλέγει stack και τον indie developer που κυκλοφορεί ένα side project με περιορισμένο προϋπολογισμό.

Γιατί React για Generative UI;

Το μοντέλο components του React είναι ιδανικό για Generative UI. Τα components είναι συνθέσιμα, τυποποιημένα και μπορούν να αποδοθούν τόσο στον server όσο και στον client. Όταν ένα AI μοντέλο «παράγει UI», στην ουσία επιλέγει και συνθέτει React components με συγκεκριμένα props.

Αυτός ο οδηγός καλύπτει τα μοτίβα που λειτουργούν σε production και τα λάθη που συναντώ σε ομάδες που μόλις ξεκινούν να κατασκευάζουν generative interfaces. Προϋποθέτω ότι έχεις ήδη ρυθμισμένο Next.js και γνωρίζεις τα βασικά του Vercel AI SDK — αυτό είναι το πρακτικό επίπεδο πάνω από αυτή τη βάση.

Μοτίβο 1: το Registry Εργαλείων

Η βάση κάθε συντηρήσιμου συστήματος Generative UI είναι ένα ρητό, κεντρικό registry components που έχει πρόσβαση το AI. Μην σκορπίζεις ορισμούς εργαλείων σε διάφορα server actions.

// lib/genui-registry.ts
import { z } from 'zod';
import { MetricCard } from '@/components/metric-card';
import { DataTable } from '@/components/data-table';
import { BarChart } from '@/components/bar-chart';
import { AlertBanner } from '@/components/alert-banner';
import { LineChart } from '@/components/line-chart';

export const tools = {
  metricCard: {
    description: 'Display a single KPI metric with a trend indicator. Use for scalar values like revenue, user count, or conversion rate.',
    parameters: z.object({
      label: z.string().describe('The metric name, e.g. "Monthly Revenue"'),
      value: z.string().describe('The formatted value, e.g. "$12,400"'),
      change: z.number().describe('Percentage change vs. previous period'),
      period: z.string().describe('The comparison period, e.g. "vs last month"'),
    }),
    component: MetricCard,
  },
  dataTable: {
    description: 'Display tabular data with sortable columns. Use when showing lists of items with multiple attributes.',
    parameters: z.object({
      columns: z.array(z.object({
        key: z.string(),
        label: z.string(),
        numeric: z.boolean().optional(),
      })),
      rows: z.array(z.record(z.string())),
      caption: z.string().optional(),
    }),
    component: DataTable,
  },
  barChart: {
    description: 'Display a bar chart for categorical comparisons. Use when comparing values across discrete categories.',
    parameters: z.object({
      title: z.string(),
      data: z.array(z.object({ label: z.string(), value: z.number() })),
      yAxisLabel: z.string().optional(),
    }),
    component: BarChart,
  },
  lineChart: {
    description: 'Display a line chart for time-series data. Use when showing trends over time.',
    parameters: z.object({
      title: z.string(),
      data: z.array(z.object({ date: z.string(), value: z.number() })),
      unit: z.string().optional(),
    }),
    component: LineChart,
  },
  alertBanner: {
    description: 'Display an important notice, warning, or success message. Use sparingly for genuinely important information.',
    parameters: z.object({
      type: z.enum(['info', 'warning', 'error', 'success']),
      title: z.string(),
      message: z.string(),
    }),
    component: AlertBanner,
  },
};

export type ToolName = keyof typeof tools;

Βασική παρατήρηση: το πεδίο description είναι αυτό που διαβάζει το AI για να αποφασίσει ποιο component θα χρησιμοποιήσει. Γράψε τις περιγραφές για το AI, όχι για ανθρώπους. Να είσαι συγκεκριμένος για το πότε κάθε component είναι κατάλληλο — και κρίσιμα, πότε δεν είναι.

Πρόσεξε ότι το lineChart λέει «time-series» και το barChart λέει «categorical». Χωρίς αυτή τη διάκριση, το AI θα κάνει τυχαίες επιλογές μεταξύ τους. Όσο πιο ακριβείς οι περιγραφές, τόσο καλύτερη η επιλογή component.

Πότε το μοτίβο δεν λειτουργεί. Ένα κεντρικό registry προϋποθέτει ότι τον κατάλογο διαχειρίζεται μία ομάδα. Αν τρεις product ομάδες θέλουν τα δικά τους components, το registry γίνεται σημείο συμφόρησης συντονισμού — κάθε νέο εργαλείο περνά από PR στην platform ομάδα. Εναλλακτικά, ένα federated registry ανά product surface εξοικονομεί συντονισμό αλλά πληρώνεις με διπλές περιγραφές και αποκλίνουσα ποιότητα. Κεντρική λύση για ένα προϊόν, federated για platform που εξυπηρετεί πολλές teams. Δες την επίσημη τεκμηρίωση streamUI στο Vercel AI SDK για το βασικό API.

Μοτίβο 2: Διαχωρισμός Registry από Streaming

Κράτα τον ορισμό του registry ξεχωριστό από την κλήση streamUI. Αυτό σου επιτρέπει να επαναχρησιμοποιείς ορισμούς εργαλείων σε πολλά server actions και κάνει το registry δοκιμάσιμο σε απομόνωση.

// lib/stream-with-tools.ts
import { streamUI } from 'ai/rsc';
import { openai } from '@ai-sdk/openai';
import { tools } from './genui-registry';

// Convert registry format to streamUI format
function buildStreamTools(toolNames: ToolName[]) {
  return Object.fromEntries(
    toolNames.map((name: ToolName) => [
      name,
      {
        description: tools[name].description,
        parameters: tools[name].parameters,
        generate: async function* (params: unknown) {
          yield <ToolSkeleton name={name} />;
          const Component = tools[name].component;
          // Προστασία από null: η εγγραφή στο registry μπορεί να είναι
          // λανθασμένη ή να έχει επαναφορτωθεί κατά το hot reload.
          if (!Component) {
            return <GenUIFallback error={new Error(`Missing component for tool: ${name}`)} resetErrorBoundary={() => {}} />;
          }
          return <Component {...(params as Record<string, unknown>)} />;
        },
      },
    ])
  );
}

// Server action for a data dashboard
export async function generateDashboard(query: string) {
  const result = await streamUI({
    model: openai('gpt-4o'),
    system: 'You are a data analyst assistant. Display information using the appropriate visualization tool.',
    prompt: query,
    tools: buildStreamTools(['metricCard', 'dataTable', 'barChart', 'lineChart', 'alertBanner']),
  });
  return result.value;
}

// Server action for a summary view (fewer tools = better focus)
export async function generateSummary(query: string) {
  const result = await streamUI({
    model: openai('gpt-4o'),
    system: 'You are a concise assistant. Show a summary with key metrics only.',
    prompt: query,
    tools: buildStreamTools(['metricCard', 'alertBanner']),
  });
  return result.value;
}

Το να περνάς υποσύνολο εργαλείων σε κάθε server action είναι σημαντικό. Ένα εστιασμένο σύνολο εργαλείων βελτιώνει την ποιότητα αποφάσεων του AI. Μην δίνεις στο AI 20 εργαλεία όταν αρκούν 5.

Πότε το μοτίβο δεν λειτουργεί. Ο διαχωρισμός registry και streaming προσθέτει ένα επιπλέον επίπεδο έμμεσης αναφοράς. Για πρωτότυπο με μία οθόνη και ένα εργαλείο, αυτό δεν είναι αρχιτεκτονική — είναι γραφειοκρατία. Κράτα τον ορισμό εργαλείου inline μέχρι να εμφανιστεί ένα δεύτερο server action.

Μοτίβο 3: Streaming με Skeletons

Μην εμφανίζεις ποτέ κενή οθόνη ενώ το AI παράγει. Εμφάνισε καταστάσεις skeleton φόρτωσης που αντιστοιχούν στο αναμενόμενο σχήμα εξόδου. Η οπτική συνέχεια μειώνει δραστικά την αντιλαμβανόμενη καθυστέρηση.

// components/tool-skeleton.tsx
import { ToolName } from '@/lib/genui-registry';

const SKELETON_HEIGHTS: Record<ToolName, string> = {
  metricCard: 'h-28',
  dataTable: 'h-48',
  barChart: 'h-64',
  lineChart: 'h-64',
  alertBanner: 'h-16',
};

export function ToolSkeleton({ name }: { name: ToolName }) {
  return (
    <div
      className={`animate-pulse rounded-lg bg-muted ${SKELETON_HEIGHTS[name] ?? 'h-32'} w-full`}
      aria-label="Loading..."
      aria-busy="true"
    />
  );
}

Για πιο ακριβή skeletons, αντανάκλα την εσωτερική δομή του component:

export function MetricCardSkeleton() {
  return (
    <div className="rounded-lg border bg-card p-6">
      <div className="h-4 w-24 animate-pulse rounded bg-muted" />
      <div className="mt-3 h-8 w-32 animate-pulse rounded bg-muted" />
      <div className="mt-2 h-3 w-16 animate-pulse rounded bg-muted" />
    </div>
  );
}

Όταν το skeleton αντικατοπτρίζει την εσωτερική δομή, η μετάβαση από skeleton σε φορτωμένο component γίνεται ομαλά — χωρίς μετατόπιση layout, χωρίς τρεμοπαίξιμο.

Πότε το μοτίβο δεν λειτουργεί. Προσαρμοσμένα skeletons για κάθε component διπλασιάζουν το surface συντήρησης: κάθε ενημέρωση component απαιτεί ενημέρωση skeleton. Για εσωτερικά εργαλεία χαμηλής κίνησης όπου η αντιλαμβανόμενη καθυστέρηση δεν επηρεάζει επιχειρηματικά, ένα γενικό γκρι ορθογώνιο είναι αρκετό. Κράτα τα χειροποίητα skeletons για surfaces που βλέπουν οι τελικοί χρήστες σε κάθε session.

Μοτίβο 4: Error Boundary για Παραγόμενο UI

Τα παραγόμενα components αποτυγχάνουν με διαφορετικούς τρόπους από τα χειροκίνητα. Το AI μπορεί να περάσει αριθμητική συμβολοσειρά όπου αναμένεται αριθμός, αρνητική τιμή όπου επιτρέπονται μόνο θετικές, ή κενό array σε component που χρειάζεται τουλάχιστον ένα στοιχείο.

Να τυλίγεις πάντα την παραγόμενη έξοδο σε error boundary:

// components/safe-genui.tsx
'use client';

import { ErrorBoundary } from 'react-error-boundary';

function GenUIFallback({ error, resetErrorBoundary }: {
  error: Error;
  resetErrorBoundary: () => void;
}) {
  return (
    <div className="rounded-lg border border-destructive/50 bg-destructive/5 p-4">
      <p className="text-sm font-medium text-destructive">
        This component could not render
      </p>
      <p className="mt-1 text-xs text-muted-foreground">{error.message}</p>
      <button
        onClick={resetErrorBoundary}
        className="mt-2 text-xs underline text-muted-foreground"
      >
        Try again
      </button>
    </div>
  );
}

export function SafeGenUI({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundary FallbackComponent={GenUIFallback}>
      {children}
    </ErrorBoundary>
  );
}

Τύλιγε κάθε τμήμα παραγόμενης εξόδου με <SafeGenUI>. Ένα σφάλμα απόδοσης σε ένα component δεν πρέπει να σπάει ολόκληρη την απόκριση. Τη βασική μηχανή παρέχει η βιβλιοθήκη react-error-boundary.

Πότε το μοτίβο δεν λειτουργεί. Το error boundary καταπίνει εξαιρέσεις. Αν δεν στέλνεις το error.message σε σύστημα παρακολούθησης (Sentry, GlitchTip, Datadog), το ίδιο bug θα εμφανίζεται σιωπηλά στο production για εβδομάδες. Boundary χωρίς logging είναι χειρότερο από έλλειψη boundary, γιατί κρύβει το σύμπτωμα.

Μοτίβο 5: Διαχείριση Κατάστασης για Παραγόμενες Αλληλεπιδράσεις

Τα components που παράγει το AI συχνά πρέπει να είναι διαδραστικά — πίνακας με ταξινόμηση, γράφημα με tooltips, φόρμα που υποβάλλει δεδομένα. Αυτή η διαδραστικότητα ζει μέσα στο ίδιο το component και δεν απαιτεί ειδική μέριμνα.

Αυτό που χρειάζεται σκέψη είναι όταν το παραγόμενο UI χρειαστεί να επηρεάσει κατάσταση εφαρμογής εκτός του component:

// Using React context to let generated components interact with the app
export const AppStateContext = createContext<{
  onDataSelected: (data: unknown) => void;
  onActionTriggered: (action: string, params: unknown) => void;
} | null>(null);

// In your generated component
function DataTable({ columns, rows }: DataTableProps) {
  const appState = useContext(AppStateContext);

  function handleRowClick(row: Record<string, string>) {
    appState?.onDataSelected(row);
  }

  return (
    <table>
      {/* ... */}
      {rows.map((row, i) => (
        <tr key={i} onClick={() => handleRowClick(row)} className="cursor-pointer hover:bg-muted">
          {/* ... */}
        </tr>
      ))}
    </table>
  );
}

Σχεδίαζε τα παραγόμενα components με σαφές API για εξωτερικές αλληλεπιδράσεις. Πέρνα callback props μέσω context αντί να εισάγεις global κατάσταση απευθείας — τα παραγόμενα components πρέπει να είναι μεταφερόμενα.

Πότε το μοτίβο δεν λειτουργεί. Η σύνδεση με context κάνει τα παραγόμενα components ακατάλληλα για απομονωμένες δοκιμές: σε κάθε Storybook story χρειάζεται πλέον να ανεβάζεις provider. Αν εξωτερική κατάσταση χρειάζεται μόνο ένα-δύο components, τιμιότερο είναι το prop drilling. Πέρνα στο context όταν τρία ή περισσότερα components μοιράζονται το ίδιο εξερχόμενο interface.

Μήτρα Επιλογής Μοτίβων

Για τον engineering manager που επιλέγει ποια μοτίβα να εισαγάγει πρώτα, οι συμβιβασμοί φαίνονται ως εξής:

ΜοτίβοΚόστος ΥλοποίησηςΌφελοςΜπορεί να παραλειφθεί αν
Registry1 μέραΚλιμακώνεται με τον κατάλογο· απαραίτητο για testabilityΈχεις ένα εργαλείο για πάντα
Διαχωρισμός registry–streaming2 ώρεςΕπαναχρησιμοποίηση μεταξύ surfaces· απομονωμένα unit testsΈνα server action
Skeletons1 μέρα/component (custom), 1 ώρα (universal)Αντιλαμβανόμενη καθυστέρηση κατά streaming· χρήσιμο για αργά μοντέλαΕσωτερικά εργαλεία χωρίς SLA
Error boundary2 ώρες + ενσωμάτωση loggingΑπαραίτητο για production· χωρίς αυτό κάθε bug props = λευκή οθόνηΠοτέ — αποστέλλεις πάντα
Εξωτερική κατάσταση0,5–2 μέρεςΧρειάζεται για GenUI που εκκινεί ενέργειες στην εφαρμογήDisplays μόνο για ανάγνωση

Το error boundary είναι η μόνη απόλυτη γραμμή. Τα υπόλοιπα τέσσερα ταξινομούνται κατά μέγεθος ομάδας: ο solodev προσθέτει skeletons τελευταία· μια ομάδα 5 ατόμων κυκλοφορεί το registry την πρώτη μέρα, γιατί το κόστος συντονισμού χωρίς αυτό υπερβαίνει το κόστος κατασκευής του.

Κόστος Κατοχής ανά Μέγεθος Ομάδας

Πρώτης τάξης εκτίμηση TCO για 12 μήνες με inference επιπέδου GPT-4o και προϊόν μέτριας κίνησης (10k γενέσεις την ημέρα). Πρόκειται για εκτιμήσεις — βαθμονόμησε με βάση τη δική σου τηλεμετρία πριν δεσμευτείς.

Μέγεθος ομάδαςΑνάπτυξη (εβδ. μηχ.)Inference ($/μήνα)Λειτουργία + on-call (ωρ. μηχ./μήνα)
Solo (indie)2–3 εβδομάδες$150–$4004–8
Μικρή ομάδα (3–5)4–6 εβδομάδες$400–$1.2008–16
Μεσαία ομάδα (10+)8–12 εβδομάδες$1.200–$5.000+16–40

Το inference κυριαρχεί στο κόστος σε κλίμακα. Ο φθηνότερος μοχλός είναι να μειώσεις τον αριθμό εργαλείων ανά server action (Μοτίβο 2) και να κάνεις cache τα επαναλαμβανόμενα prompts· δεύτερος μοχλός είναι να κατευθύνεις απλά αιτήματα σε μικρότερο μοντέλο.

Roadmap Υλοποίησης για Ομάδα

Εβδομάδες 1–2: κυκλοφόρησε Μοτίβο 4 (error boundaries) και Μοτίβο 1 (registry) με δύο-τρία εργαλεία υπό feature flag στο 5% των χρηστών. Εβδομάδες 3–4: πρόσθεσε Μοτίβο 3 (skeletons) και Μοτίβο 2 (διαχωρισμός)· επέκτεινε στο 25%. Εβδομάδες 5–8: πρόσθεσε Μοτίβο 5 (κατάσταση)· κυκλοφόρησε στο 100%. Σε κάθε gate κράτα το rollout σταθερό μέχρι η καθυστέρηση p95, το ποσοστό σφαλμάτων και το κόστος inference ανά session να μπουν στα δημοσιευμένα SLO. Μην προσθέτεις νέα εργαλεία στο registry μέχρι το πρώτο σύνολο να σταθεροποιηθεί.

Ανάπτυξη GenUI Εφαρμογής (σενάριο indie)

Αν είσαι solodev και θέλεις να κυκλοφορήσεις ένα GenUI feature αυτό το Σαββατοκύριακο, να η συντομότερη λογική διαδρομή:

  1. Ξεκίνα με create-next-app και App Router. Εγκατέστησε ai, @ai-sdk/openai, zod και react-error-boundary.
  2. Παράλειψε το Μοτίβο 2 στην πρώτη έκδοση — ορίσε δύο εργαλεία inline απευθείας στο server action.
  3. Χρησιμοποίησε το γενικό «γκρι ορθογώνιο» του Μοτίβου 3, όχι custom παραλλαγές. Τα custom να τα κυκλοφορείς όταν η λειτουργία αποκτήσει χρήστες.
  4. Τύλιξε το stream σε <SafeGenUI> από το Μοτίβο 4. Αυτό δεν μπορεί να παραλειφθεί.
  5. Κάνε deploy σε Vercel free ή Pro tier. Πρόσθεσε OPENAI_API_KEY στις μεταβλητές περιβάλλοντος. Το πρώτο deploy είναι git push.
  6. Βάλε σκληρό όριο δαπανών στο OpenAI key (το dashboard του OpenAI υποστηρίζει μηνιαία όρια), ώστε ένας loop μέσα σε loop να μην αδειάσει τον προϋπολογισμό μέσα στη νύχτα.

Εκτίμηση κόστους για hobby project (1.000 γενέσεις/μήνα): περίπου $5–$15 για inference, $0 για hosting στο Vercel hobby tier, $0 για παρακολούθηση μέσω ενσωματωμένων logs Vercel. Κατά την εκτίμησή μας, ο πρώτος αισθητός λογαριασμός εμφανίζεται γύρω στις 50k γενέσεις/μήνα· τότε ακριβώς αρχίζει να αποδίδει το Μοτίβο 2 (διαχωρισμός server actions) και το caching prompts.

Απλοποιημένο registry για indie κλίμακα — server action με ένα εργαλείο και skeleton, έτοιμο για αντιγραφή:

// app/actions.tsx
'use server'
import { streamUI } from 'ai/rsc'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
import { MetricCard } from '@/components/metric-card'
import { Skeleton } from '@/components/skeleton'

const metricSchema = z.object({
  value: z.number().describe('τρέχουσα αριθμητική τιμή μετρικής'),
  label: z.string().describe('human-readable όνομα μετρικής'),
  delta: z.number().describe('μεταβολή σε σχέση με προηγούμενη περίοδο σε ποσοστό'),
})

export async function generateUI(prompt: string) {
  const result = await streamUI({
    model: openai('gpt-4o-mini'),
    prompt,
    tools: {
      metricCard: {
        description: 'Εμφανίζει μία βασική μετρική με delta',
        parameters: metricSchema,
        generate: async function* (p: z.infer<typeof metricSchema>) {
          yield <Skeleton className="h-28 rounded bg-muted" />
          return <MetricCard {...p} period="vs last month" />
        },
      },
    },
  })
  return result.value
}

Και η κλήση client σε μία φόρμα:

// app/page.tsx
'use client'
import { useState } from 'react'
import { generateUI } from './actions'

export default function Page() {
  const [ui, setUI] = useState<React.ReactNode>(null)
  return (
    <form action={async (formData) => {
      setUI(await generateUI(formData.get('q') as string))
    }}>
      <input name="q" />
      <button>Δημιούργησε</button>
      <div>{ui}</div>
    </form>
  )
}

Πέρνα στο πλήρες σύνολο μοτίβων όταν η λειτουργία αποκτήσει πληρωτέους χρήστες ή ο κατάλογος εργαλείων μεγαλώσει σε τρία ή περισσότερα.

Συνηθισμένα Λάθη

Πολλά εργαλεία. Αν δώσεις στο AI 50 components να επιλέξει, οι αποφάσεις θα είναι ανεπαρκείς. Έχω δει ομάδες να ξεκινούν με 20+ εργαλεία και να ανακαλύπτουν ότι το AI επιλέγει σταθερά τα λάθος. Ξεκίνα με 5–8 καλά ορισμένα εργαλεία και επέκτεινε μόνο με βάση δεδομένα για ανκαλυπτόμενα αιτήματα.

Ασαφείς περιγραφές. «Εμφανίζει δεδομένα» δεν είναι χρήσιμη περιγραφή εργαλείου. «Εμφανίζει πινακοποιημένα δεδομένα με ταξινομήσιμες στήλες για λίστες στοιχείων με πολλά χαρακτηριστικά» εξηγεί ακριβώς στο AI πότε να το χρησιμοποιεί.

Χωρίς fallback. Όταν το AI μοντέλο δεν είναι διαθέσιμο ή επιστρέφει σφάλμα, οι χρήστες δεν βλέπουν τίποτα. Για κρίσιμα paths να υπάρχει πάντα στατικό fallback. Αν χρησιμοποιείς Generative UI για dashboard δεδομένων, να υπάρχει η στατική προεπιλεγμένη εμφάνιση για περιπτώσεις μη διαθεσιμότητας AI.

Παράλειψη Zod validation. Το AI μερικές φορές περνά απρόσμενα props — συμβολοσειρά αντί αριθμού, null αντί απαιτούμενης τιμής. Αυστηρή Zod validation εντοπίζει αυτά πριν φτάσουν στο component.

Υπερβολική γένεση. Δεν χρειάζεται κάθε αλληλεπίδραση Generative UI. Αν ένα στατικό component λειτουργεί, χρησιμοποίησέ το. Το GenUI προσθέτει 200–800ms καθυστέρηση και κοστίζει χρήματα. Χρησιμοποίησέ το όπου η μεταβλητότητα έχει πραγματική αξία.

Μη καταγραφή κλήσεων εργαλείων. Χωρίς logs για ποια εργαλεία επιλέγει το AI και ποιες παραμέτρους περνά, δεν έχεις δεδομένα για βελτίωση. Κατέγραψε τα πάντα από την πρώτη μέρα. Τα μοτίβα που θα δεις μετά από μία εβδομάδα χρήσης θα αλλάξουν τον τρόπο που γράφεις περιγραφές εργαλείων.

Λίστα Ελέγχου Παραγωγής

Πριν αποστείλεις Generative UI στην παραγωγή:

  • Όλα τα παραγόμενα components τυλιγμένα σε error boundaries
  • Καταστάσεις skeleton φόρτωσης για κάθε εργαλείο
  • Στατικό fallback όταν το AI δεν είναι διαθέσιμο ή επιστρέφει σφάλμα
  • Αυστηρή Zod validation σε όλες τις παραμέτρους εργαλείων
  • Καταγραφή κλήσεων εργαλείων (όνομα εργαλείου, παράμετροι, καθυστέρηση)
  • Παρακολούθηση καθυστέρησης (ειδοποίηση αν >2s μέχρι πρώτο component)
  • Παρακολούθηση κόστους ανά AI inference
  • Έλεγχος προσβασιμότητας όλων των παραγόμενων συνθέσεων components
  • Δοκιμή responsive σε κινητά για παραγόμενα layouts
  • Rate limiting στο server action

Σημείωση για τις Δοκιμές

Η δοκιμή Generative UI απαιτεί διαφορετική προσέγγιση από τις παραδοσιακές δοκιμές UI. Η σύντομη έκδοση:

  • Δοκίμασε τα components σε απομόνωση με τυπικά unit tests — είναι απλώς React components
  • Δοκίμασε τα Zod schemas ξεχωριστά για να βεβαιωθείς ότι δέχονται έγκυρες και απορρίπτουν άκυρες εισόδους
  • Για integration tests κατά του AI, δοκίμασε δομικές ιδιότητες (σωστό εργαλείο κλήθηκε, έγκυρες παράμετροι) όχι ακριβές περιεχόμενο (θερμοκρασία 22°)
  • Κάνε mock το AI στο CI και εκτέλεσε πραγματικά AI integration tests τη νύχτα

Αυτό το θέμα αξίζει το δικό του άρθρο. Προς το παρόν, τα μοτίβα validation και χειρισμού σφαλμάτων που κάνουν τις δοκιμές αξιόπιστες καλύπτονται στο Κατασκευή Generative UI με το Vercel AI SDK.

Εξετασμένες Εναλλακτικές

Τα παραπάνω μοτίβα προϋποθέτουν Vercel AI SDK με React Server Components. Δύο εναλλακτικές που αξίζει να γνωρίζεις πριν δεσμευτείς:

  • Tambo / κατάλογος components ως υπηρεσία. Opensource framework για AI-παραγόμενο UI σε React (github.com/tambo-ai/tambo, ~11k stars τον Μάιο 2026) που επιταχύνει το πρώτο demo (δεν χρειάζεται να γράψεις κώδικα registry) και κεντρώνει την ποιότητα περιγραφών. Κατάλληλο όταν η ταχύτητα ως πρώτο demo έχει μεγαλύτερη σημασία από το μακροπρόθεσμο μοναδιαίο κόστος.
  • Δηλωτικά JSON πρωτόκολλα όπως το Thesys C1 (κλειστό API) ή το A2UI v0.9 (ανοιχτή προδιαγραφή Google, Νοέμβριος 2025) αποσυνδέουν το μοντέλο από το React· οποιοσδήποτε client (web, mobile, φωνητικός) μπορεί να αποδώσει το ίδιο payload. Κατάλληλο όταν έχεις non-web surfaces — με κόστος τον δικό σου renderer.
  • Γυμνό JSON + χειροκίνητος dispatcher. Κανένα SDK. Γράφεις switch κατά ονόματα εργαλείων. Η φθηνότερη επιλογή σε μικρή κλίμακα, η δυσκολότερη στη συντήρηση μετά από πέντε εργαλεία.

Ο άξονας επιλογής είναι ανθεκτικότητα και portability έναντι χρόνου ανάπτυξης. Για τα περισσότερα προϊόντα React-only κερδίζει η διαδρομή SDK αυτού του άρθρου· για multi-surface ή vendor-neutral προϊόντα αξιολόγησε το A2UI.

Περαιτέρω Ανάγνωση


Εργάζεσαι σε υλοποίηση Generative UI σε React; Αποκτήστε εξειδικευμένη καθοδήγηση σε αρχιτεκτονική, απόδοση και ετοιμότητα παραγωγής.

ΚοινοποίησηTwitterLinkedInEmail
reactgenerative-uipatternsimplementation
A

Alex

Μηχανικός & Σύμβουλος Generative UI

Senior μηχανικός με εξειδίκευση σε διεπαφές με τεχνητή νοημοσύνη και συστήματα Generative UI. Βοηθά ομάδες προϊόντος να παραδίδουν γρηγορότερα με το κατάλληλο GenUI stack.

Μείνετε μπροστά στο Generative UI

Εβδομαδιαία άρθρα, ενημερώσεις framework και πρακτικοί οδηγοί υλοποίησης — απευθείας στα εισερχόμενά σας.

Σεβόμαστε το απόρρητό σας. Κατάργηση εγγραφής ανά πάσα στιγμή.

Χρειάζεστε βοήθεια για να υλοποιήσετε όσα μόλις διαβάσατε;

Κλείστε Δωρεάν Συμβουλευτική